April 11th, 2010

Dynamic features, part 2

Another new dynamic feature is the ability to get information on local variables, anywhere in the stack.

There are actually two different ways of getting access to the locals, depending on your needs.  The simple way is to get a snapshot of the locals as part of a stack trace.  If you pass a special flag to t3GetStackTrace(), it'll build a lookup table for each stack level retrieved, and add it to the T3StackInfo object for the level.

  // get the stack information for our immediate caller
  local s = T3GetStackTrace(1, T3GetStackLocals);

  // get the local variable table for the frame
  local l = s.locals_;

  // get the value of local variable 'i' in the frame
  local caller_i = l['i'];

It's pretty simple: the lookup table is keyed by variable name, so you simply ask for the variable's value by name.  It's an ordinary lookup table, so if you want to build a list of the locals, you can enumerate the keys.  The table is a snapshot of the locals at the moment you call the stack trace function - in other words, it's just a copy, so if the locals subsequently change, the table won't be affected.  Likewise, if you change the contents of the table, it won't affect the actual locals in the frame.

The more complicated way to get the locals is to retrieve a "frame descriptor" object.  This is also obtained through the stack trace.  The interesting thing about a frame descriptor is that it's a live reference to the actual frame variables.  That means that as the variables change, their values as seen through the descriptor change in real time.  What's more, you can actually change the values of the locals via the descriptor - and doing so will change the variables themselves, not just copies.

  local s = t3GetStackFrame(1, T3GetStackFrames);
  local f = s.frameDesc_;
  f['i'] = 100;

That will immediately change the value of 'i' in the caller to 100.

The really cool thing about frame descriptors is that they effectively make the frame immortal.  This is a little like the way anonymous functions work: an anonymous function can keep referring to its enclosing scope's live variables even after the enclosing scope has returned to its caller.  In the same way, a frame descriptor can keep referring to its scope's variables even after that routine has returned.  As long as you keep a reference to the frame descriptor, the frame stays around.  They even survive save/restore.

Frame descriptors also give you more information about the frame than you get via the simple local snapshot table.  It has a number of methods for retrieving other information: you can tell if the frame is in fact still running (that is, whether or not that routine has returned to its caller), and you can get the method context variables for the frame (self, definingobj, etc).

As with most of the features in the "dynamic" series, this kind of thing is probably more interesting to library writers than to game writers.  A couple of obvious applications are debugging and message generation.