Skip to content

How the execution of Scratch like code in Greenfoot works

VictorNorman edited this page Aug 24, 2016 · 20 revisions

Background

Scratch and Greenfoot share many similarities:

  • They both have a 2-dimensional "canvas".
  • Sprites/Actors are associated with images.
  • Basic movement and sensing are similar: Sprites/Actors can be moved to locations, turned to point in different directions, can sense if they are touching the edge or each other, can react to keypresses and mouse clicks, etc.
  • Sounds can be played.

While on the surface Scratch and Greenfoot look very similar, their execution models differ greatly.

  • Scratch seems to execute all scripts in parallel. Multiple forever loops just seem to run at the same time, along with "event handlers" -- scripts that are started when a key is pressed or the stage is clicked.
  • Greenfoot calls each Actor's act() method once each "tick".

Making Greenfoot behave the way that Scratch programmers are used to is difficult. Consider code like this:

The code has basically two parts: an initialization section followed by a forever loop.

In Greenfoot, we might code this up by creating a constructor for the Actor (something that isn't there by default) and putting the initialization code in the constructor, and then putting the statements inside the forever loop into the act() code. That's a very different model than students are used to.

Even worse, how do you code this up in Greenfoot?

If the following were possible, would you write something like

move(3);
turn(45);
wait(1);

in the act() method? If you try that, you'll see that it does not produce the same behavior as in Scratch.

The ScratchFoot Solution

The primary goal of ScratchFoot is to convert Scratch code into Greenfoot code such that the Scratch user can clearly understand how the Java code corresponds to the Scratch scripts from which they came. Thus, we want to maintain the Scratch execution model in Greenfoot. We want to simulate concurrency in Greenfoot, which does not support concurrency, as Greenfoot is not thread-safe. We do this by implementing user-layer threads, and limiting one thread at a time to be accessing the Greenfoot API.

Before we can get into the nitty-gritty details, you have to understand the basic structure of the classes. Greenfoot provides two basic classes -- Actor and World. The Actor class corresponds to each Scratch Sprite, and offers the API you would expect -- functions to move the Actor, turn the Actor, detect if a key has been pressed, detect collisions, etc. Greenfoot programmers create subclasses of Actor, one for each kind of object that needs to move around the screen, etc. Greenfoot calls the act() method of each Actor subclass, once per frame. This act() method is usually where most of the code for the object goes.

ScratchFoot subclasses Actor with the class Scratch, and adds its own API there. This API implements much of the functionality of Scratch. For example, the class Scratch implements functions like ifOnEdgeBounce(), hide(), switchToCostume(), changeSizeBy(), and so on.

Scratch's "hat" blocks, like when Green Flag Clicked, when I Receive Message, and so on (any block that can begin a script), are implemented in ScratchFoot in two parts. First, a callback must be registered in the object's constructor. Second, the callback must be implemented. The first parameter to each callback is a Sequence, whose purpose is explained below.

In the Scratch class, we have created a concept of a Sequence. A Sequence is an opaque object that represents an executable script in Scratch. The ScratchFoot callback code needs to pass this Sequence back to Greenfoot whenever the code makes a call like wait(), glideTo(), or broadcastAndWait(). Additionally, every "forever" or "repeat" loop in a callback must make a yield() call within the loop, passing back the Sequence object. yield(), implemented in Scratch, does as you might expect: it allows any other pending active thread to run. (In Operating Systems, this is called "cooperative multitasking.") This yield() call is crucial, as it is the functionality that makes multiple scripts seem to run in parallel. In fact, as I understand it, Scratch's execution engine itself inserts a yield in the loops inside scripts, in order to simulate truly parallel execution.

An example is in order. ScratchFoot would convert the example Scratch scripts given above into this code, which would be found in the class that is a subclass of Actor:

public TheConstructor()
{
    // register callbacks to be called when the scenario is started.
    whenFlagClicked("whenFlagClickedCb0");
    whenFlagClicked("whenFlagClickedCb1");
}

public void whenFlagClickedCb0(Sequence s) 
{
    while (1) {    // forever loop
        move(3);
        yield(s);
    }
}

public void whenFlagClickedCb1(Sequence s)
{
    while (1) {    // forever loop
        turnRight(45);
        wait(s, 1);
        yield(s);
    }
}