JavaScriptCore for game scripting with iOS & Swift
Deon Botha• Nov 19, 2014A casual game side project I work on from time to time recently reached the point where it made sense to incorporate an embedded scripting engine. In the past I’ve had good experiences with Lua, however since the release of iOS 7 I’ve been looking for an excuse to play with the JavaScriptCore framework.
The JavaScriptCore Framework allows you to evaluate JavaScript programs from within a C-based program. It also lets you insert custom objects to the JavaScript environment.
This post is not intended to be an introduction to the JavaScriptCore Framework. Instead, If you’re looking for a starting point then take a look at the following first:
In the interest of brevity I’ll cover only a small subset of the game functionality which is currently handled by my scripting engine – the in-game tutorial system. This should provide a good foundation to build on for your own games.
The Scripts
Tutorials in my game are linear in nature with the player being introduced to various game mechanics in sequence. A typical (although simplified) in-game tutorial script in JavaScript would look something like this:
Given the linearity of my in-game tutorials it makes sense to be able to “pause” script execution until certain goals
have been completed by the player. The astute JavaScript developers amongst you may immediately question how the various
wait()
functions work, after all JavaScript by it’s very nature is event-driven with most (if not all) virtual
machines relying on a single threaded execution model. All I/O is typically non-blocking and asynchronous. So how do we
pause execution within a JavaScriptCore JSVirtualMachine
? Well I’ll get to that, first lets lay
the foundations for the scripting engine itself.
Exposing Swift functions to JavaScript
To kick things off lets look at the Swift methods that we plan on making accessible within JavaScript:
We’ll get to the implementations shortly but nothing too tricky here. They closely match up with the functions seen earlier in the tutorial script.
The JavaScriptCore Scripting Engine
Next lets start to look at the definition of the scripting engine:
The engineQueue
is a dispatch queue to which we will submit any task that manipulates the JSContext
,
this is done to keep JavaScript to Swift callback execution off the main thread.
The key thing notice in the above code snippet is that the JSContext
is instantiated on a separate thread
and not the main thread. This needs to be the case (unfortunately for reasons that are not entirely obvious or
documented outside of source code) to avoid blocking the main thread when we “pause” JavaScript execution in the various
wait()
functions.
The ScriptEngine
object instance is made accessible in JavaScript through the $
variable. In the evaluateScript
call we expose the core functions from ScriptingEngineExports
that that will be accessible to our external JavaScript
scripts. Calling any of these functions from within JavaScript will result in the related Swift method being invoked
(not on the main thread) on the ScriptEngine
object instance.
Running scripts
Loading a running a script is relatively straightforward. Included is some logic that means the extension is assumed to
be .js
if not specified. Notice again how we manipulate the JSContext
off the main thread by submitting
a task to the engineQueue
, this guarantees all JavaScript to Swift calls to occur off the main thread:
A script can be run in the following fashion:
Pausing JSVirtualMachine execution
Now lets take a look at how we can pause JavaScript execution in the various wait()
functions. We’ll start with the
simplest variant that pauses execution for a specified period of time:
This wait()
function (as well as all others that we’ll cover shortly) is guaranteed to be called on a separate thread
managed by the engineQueue
i.e. not on the main thread. sleepForTimeInterval
will thus
put this thread to sleep rather than the main thread. JavaScript execution will stop until the thread wakes up from
sleeping whilst our main thread is still free to handle user input and run other game simulation logic. Next onto the
marginally trickier wait functions:
waitForTap()
& waitForLevelEnd()
are exported instance methods called when the corresponding JavaScript functions
are invoked, they’re guaranteed to be run off the main thread. waitForCondition(condition:predicate:)
is where
engineQueue
thread execution is blocked (and thus JavaScript execution paused) until the appropriate condition is
signalled.
notifyTap()
& notifyLevelEnd()
signal on the appropriate condition and wake the engineQueue
thread (assuming it
was blocked on the signalled condition) allowing JavaScript execution to resume. These two methods should be called by
your game engine when the appropriate events arise.
Triggering game events
The remaining exported functions are implementation specific and will vary from game to game. The way I like to
deal with them in the ScriptEngine
is to post NSNotification
’s and then deal those notifications elsewhere as
appropriate.
The end
Hopefully the above should provide a foundation to build on for your own games. I’ll be sure to update here with a link to the full game source code once it hits the App Store.