Integration with client¶
This section gives an overview of the typical usage of the VM, which is to run a user’s Python program within the web-app. Much of the below also applies to running programs in the unit-test framework.
Set-up phase (building a user’s Python code)¶
The user’s program will have classes including various decorated
methods, for example methods decorated
@pytch.when_green_flag_clicked
. [[1]] Such a decorator attaches
an attribute to the function object, marking it as being a handler for
a particular kind of event. This happens purely within the Python
world.
When the user clicks the green “play” button in the webapp, the user’s
code is imported using one of Skulpt’s entry points —
Sk.importMainWithBody()
. This gives us a ‘module’, and we then
process that module in various ways. This happens in
import_with_auto_configure()
. If you strip away the
error-handling and other ancillary work, this process really consists
of two calls: Sk.importMainWithBody()
and
maybe_auto_configure_project()
.
(We keep a global reference to the current “live” project, which is an object within the user’s imported module. This keeps everything alive.)
The auto-configure process looks through the module object for
subclasses of Sprite
or Stage
. For each, it creates a
JavaScript object to represent that Actor
. The JS constructor of
that JS Actor
includes a call to a JS method which registers the
event handlers.
[[2]] That method looks through the Python class for functions (methods), and, if the any of the attributes mentioned at [[1]] above exist, calls a JS method which adds the Python method to one of various maps which track which methods of which classes should be invoked when various events happen. As an example, green-flag methods are handled here by adding a handler object to a “green-flag handlers” array-like object.
Now everything just sits there until an event happens.
Creating threads when an events happens¶
First some Threads
are created:
The client (webapp or unit test framework) calls the current live
project’s on_green_flag_clicked()
method. The project iterates
over its Actor
instances, each of which iterates over its
registered green-flag handlers. All this eventually comes down to a
series of calls to ThreadGroup.create_thread()
, one per handler
per Sprite
/Actor
instance.
The first two arguments to create_thread()
are the “Python
function” (which is the unbound method found when iterating over the
attributes of a class in [[2]] above) and the “Python argument” which
is the instance of the Sprite
on which the Thread
will run.
These are used to create a new Thread
. These arguments are
captured [[3]] in a closure stored in the skulpt_susp
slot of the
new Thread
. The JS code in this closure will in due course invoke
the user’s Python code — Sk.misceval.callsimOrSuspend(py_callable,
py_arg)
.
So far we’ve only scheduled that Thread
for running. No user
Python code has run yet.
Running threads¶
Threads are run as follows:
Pytch scheduling is based around “frames” (as in animation or display
frames, not stack frames). The application (webapp or unit test
framework) repeatedly calls Project.one_frame()
. This (as well as
various housekeeping) calls one_frame()
on each of its
ThreadGroup
instances, each of which in turn calls one_frame()
on each of its Thread
instances.
We finally reach the point where the user’s Python code runs:
Thread.one_frame()
.
The nub of this method is a call to this.skulpt_susp.resume()
.
The first time a new thread runs, this (via the set-up in [[3]] above)
comes down to invoking the unbound Python method with the Sprite
instance as argument, which is equivalent to calling that Python
method on that Python Sprite
instance. The rest of
one_frame()
deals with the three different cases that can happen
when running the user’s code.
- User’s code runs to completion
In this case, the thread has finished, becomes a zombie, and is reaped in due course.
- User’s code invokes a “syscall”
This means our Pytch VM has to do something (e.g., broadcast a message, start a sound playing). In this case, we use Skulpt’s “suspension” mechanism (essentially a continuation) to allow resumption in a future call to
one_frame()
.- User’s code raises a Python exception
In this case, the client is notified of the exception and the thread put into a state indicating it raised an exception.
Summary¶
To boil this down to just the part which get a reference to the Python callable within the user’s code and the part that invokes that code:
Sk.importMainWithBody()
returns a “module”, which we can look
through for Python callables (in our case, methods of classes of
interest).
Sk.misceval.callsimOrSuspend()
runs those Python callables with
arguments.