Search This Blog

Tuesday, May 24, 2011

iPhone Scripting with Squirrel

So after a bit more work I have completely integrated Squirrel into a functioning iPhone application.

My application is a semi-realtime game-like system with response times required to be less than a millisecond or so.  There is a bluetooth component as well as various networking and other requirements.  When events happen a sequence of data is passed to other applications (running on OS or another iOS device).

My first iOS application of Squirrel is to pre-process the data before sending it off.  This is nothing fancy - basically mapping values, checking ranges, that sort of thing - based on a users-selected "personality".

The first decision was where to put the scripting files.  In my case this means putting them into the iOS application resources area (where things like images, audio files, and so on are also stored).  At this point I am using Squirrel source (.nut) files.  This makes for easy testing and debugging at this point.  At some point closer to the product release I plan to switch that over to compiled binary files (.cnut) in order to avoid the wrath of Apple.

At some point in the future I can also see having some sort of app-based purchasing function that integrates with Squirrel as well - enabling features, loading in layers of functionality that allow a user access to purchased components, and so on.

At this point I plan to have the purchasing happen outside the app, say via some web site.  The iOS app would be told to synchronize with the site and, when it did, it would discover feature packs to down load and install (all based on an encrypted device-based key).  The Apple problems with security and scripting are many - for example someone figures out how to edit the scripts and changes them to do harm in some way.  However, for binary compiled scripts I don't see any problem - they are certainly as safe as paying $100 USD for an Apple developer license.

So with all this in mind I adopted the convention of declaring each Squirrel file to have an initialize(self) function (self being, in this case, the invoker of the file).  This allows any sort of "installation" functionality (checking permissions, moving extra files, etc.) to be handled.

I also adopted the convention of using, at least for menu-based selections of functionality, the notion of a global Squirrel pool of class instances along with the notion of a currently selected one.  So, for example, suppose I have a "personality" capability in the iOS app.

I create a base Squirrel file that declares the base class for the personality, a global table of loaded personalities, and a global "current personality pointer".  When the iOS app starts up this is loaded and initialized and the current pointer is set to the loaded class (typically the "default" class").

As alternate personalities are loaded (as other Squirrel files) they come in as extensions to the bass class and get stored as entries in the global table (indexed by some user-readable name).

I set up calls to return the list of entries in the table and to allow the user to set the current pointer to one of them.

The only tricky part of this was figuring out in a .c file what to do to invoke the class instances.  Basically you do the code below to access a Squirrel global called "currentPointer" that holds a class instance that responds to "classFunction":

 sq_pushroottable(vm);
 
  // "currentPointer" to be fetched from "global table"
  sq_pushstring(vm, _SC("currentPointer"), -1);
 
  if (SQ_SUCCEEDED(sq_get(vm, -2)))
  {
    // the class instance held by "currentPointer" now on
    // the top of the Squirrel stack.
    //
    // Now we want to find a function in the class.
    // We do a "get" relative to the instance for this
    //
    sq_pushstring(vm, _SC("classFunction"), -1);
   
    if (SQ_SUCCEEDED(sq_get(vm, -2)))
    {
      // Now the closure for "classFunction" is
      // on the TOS.
      //
      // Next get the class instance for that closure.
      //
      sq_pushstring(vm, _SC("currentPointer"), -1);
     
      if (SQ_SUCCEEDED(sq_get(vm, -4)))
      {
        // STACK(-4) "classFunction" closure
        // STACK(-3) instance of class
        //
        // user parameters to function
        // STACK(-2) param1
        sq_pushuserpointer(vm, (SQUserPointer)self);
        // STACK(-1) param2
        sq_pushinteger(vm, a);
        // STACK(0) param3                      
        sq_pushinteger(vm, b);                      
       
        if (SQ_SUCCEEDED(sq_call(vm, 4, SQTrue, SQTrue)))
        {
          sq_getinteger(vm, sq_gettop(vm), &retVal);
          success = YES;
          // function returns integer
          *result = (int)retVal;
        }
      }
    }
  }


Once you have this in place invoking class instances in Squirrel is easy.

Another problem area I uncovered is that of reentrancy.  This is where you have an application running with multiple threads (either directly or indirectly through use of something like NSTimer).

In either case you have to make sure that for a given Squirrel VM you only have one instance active in the VM at any given time.  (At least this was my experience - if I am wrong hopefully someone will let me know...)

For a variety of reasons this is trickier than it might seem at first because you can run into serious problems as you increase the number of locks involved beyond one.  Fortunately for my application the locking happens at a fairly high level above the Squirrel calls (for other reasons unrelated) which leaves any calls into the Squirrel VM already effectively serialized.

Squirrel is a scripting language and does not appear to support the notion of things asynchronous outside events (yes it has its own threads and so forth but that is all within the VM).  So, for example, if I want some Squirrel code to run on an NSTimer tick I have to ensure that that when this happens the timer is the exclusive owner of the VM for the duration of the tick.

I think this could solved by having allowing Squirrel to support multiple local "stacks" and having read/write access to the core VM resources controlled by a lock specific to that VM.  This would allow things like timer actions and asynchronous events to work seamlessly within the Squirrel architecture without having to support high-level lock granulatiry.  The locking could be supported by callbacks (which could be compiled in) when referencing global resources so efficiency would not be impacted unless necessary.

You could also add a language construct like "volatile" that would tell Squirrel that a given object had to be locked before accessing it.

It looks like to me that in the long run support for volatility would be a requirement but for my purposes I can get around all this for now...

No comments:

Post a Comment