Our paper entitled “Advanced Control Reactivity for Embedded Systems” has been accepted in the Workshop on Reactivity, Events and Modularity (REM) this year in Indianapolis!
REM is a workshop inside the SPLASH/OOPSLA conference.
The preprint version for the paper can be downloaded here.
This is a more theoretical paper in comparison to our previous for SenSys.
We discuss how to build advanced control mechanisms on top of the reactive primitives of Céu and present a formal specification of the language.
The “Rocks!” game is a spaceship shooter game for 2 simultaneous players on the same Android tablet:
The code is open source and is extensively commented. I plan to write a tutorial from it:
The game also compiles to run in the desktop.
Here’s a running session of the game (with a much faster speed):
The new version of Céu is released!
What’s new in v0.6:
- Added support for asynchronous threads.
- Added support for the C preprocessor.
- Added tuple types for events.
Céu in a Box (CiB) is a Linux-based distribution that comes pre-installed with the Céu Programming Language environment.
The distribution contains the compiler together with bindings for the Arduino, TinyOS, and SDL platforms.
CiB is distributed as a single .ova file to be used with VirtualBox.
For a glimpse at the runtime costs of Céu, I created a simple application with SDL that animates 10000 rectangles on the screen.
My hypothesis is that lines of execution in Céu (a.k.a trails) are cheap and should be used carelessly, even for simple activities such as waiting for a single event and terminating. This way, the testing application in Céu has a peek of 40000 trails active at the same time, which are all visited by the scheduler on every frame.
I compare the implementation in Céu with a simpler application in C to serve as a benchmark.
The application in raw C is damn simple:
- Declares a vector of 10000 rectangles.
- Initializes each to 10×10 width/height, a random (x,y) position, and a random x velocity.
- Animates them continuously, restarting from left when reaching the right side of the screen.
- Achieved FPS = 120 .
- The program has 5 lines of execution:
- On SDL_QUIT event: terminates the application.
- Every frame: clears the screen.
- Every 40ms: creates 30 new Rect organisms.
- after 30s: kills all of them at once and restart the process
- Every frame: updates the screen.
- Every second: calculates the FPS.
(technically they are 7 lines of execution because (3) and (5) are split in two)
- The class Rect has 4 lines of execution:
- Every 200ms: varies the rectangle width, height, r, g, b (with a random epsilon).
- Every frame: animates the rectangle respecting its velocity, terminating when it reaches the end of the screen.
- Every frame: redraws the rectangle on screen.
- On termination: runs a simple finalizer (which maintains the number of organisms alive).
- Achieved FPS = 107 . (when 10000 rectangles are on screen)
The main differences between the two implementations are marked in bold above and are summarized as follows:
- The rectangles in Céu are allocated dynamically: in the peek, we have 750 rectangles being allocated (in the left side) and reclaimed (in the right side) every second.
- Each rectangle on screen (10000 at most) has 4 active trails, all of which are visited by the scheduler every frame (4 x 10000 = 40000 lightweight threads active at the same time).
- Finally, every 30 seconds the program restarts the spawning loop, killing all 10000 active organisms at once, running the associated finalizers, and reclaiming all memory.
Céu is a source-to-source compiler that generates single-threaded ansi-C code. This way, the generated code for the test application, although incomprehensible, can be compiled with gcc and tested in any computer that has SDL2:
% gcc download_code.c -lSDL2 .
The simple SDL application in C is used as a benchmark to compare to a more complex application written in Céu, which shows a decrease around 10% in FPS.
Although the application in C could be re-implemented to achieve the same functionality (for a more strict comparison), the initial numbers are already satisfactory and make the point that the trail-oriented programming style of Céu is realistic, even in a highly dynamic scenario.
This is the follow up to the previous post on creating dynamic applications in Céu.
As introduced there, organisms are the main abstraction mechanism in Céu, reconciling objects and lines of execution in a single construct. The example below, extracted from the previous post, creates two instances of a class that prints the “Hello World!” message every second:
class HelloWorld with var int id; do every 1s do _printf("[%d] Hello world!\n", this.id); end end var HelloWorld hello1, hello2; hello1.id = 1; hello2.id = 2; await FOREVER; .
One thing that arises in the code is how the organisms hello1 and hello2 are declared as local variables, using the same syntax of a normal variable declaration (e.g. “var int x=0”). This implies that an organism follows the standard scoping rules of conventional imperative languages, i.e., its memory is reclaimed when its enclosing block goes out of scope. Additionally, all of its lines of execution are seamlessly killed.
Céu supports three different ways to manage the life cycle of organisms automatically: local variables, anonymous allocations, and named allocations.
The simplest way to manage organisms is to declare them as local variables, as shown in the previous example. As in the example, the two organisms don’t go out of scope, let’s include an explicit block to declare hello2 so that it goes out of scope after 5 seconds:
var HelloWorld hello1; hello1.id = 1; do var HelloWorld hello2; hello2.id = 2; await 5s; end await FOREVER; .
The organism hello2 runs in parallel with the do-end construct and is active while the block is in scope. After 5 seconds, the block goes out of scope and kills hello2, also reclaiming all its memory. As an outcome, the message “ Hello world!” stops to show up in the video.
The order of execution between blocks and organisms is determined: code inside a block executes before the organisms declared inside it. This way, the do-end has priority over hello1 (they are both in the top-level block), while await 5 has priority over hello2 (they are both inside the do-end). The order the messages appear in the video is correct, hello2 always awakes before hello1. Also, hello2 is killed before printing for the 5th time, because the await 5s has higher priority and terminates the block before hello2 has the chance to execute for the last time.
Regarding memory usage, a local organism has an associated slot in the “stack” of its enclosing block, which is calculated at compile time. Blocks in sequence can share memory slots. Local organisms do not incur overhead for calls to malloc/free.
True dynamic applications often need to create an arbitrary number of entities while the application is running. Céu supports a spawn command to dynamically create and execute a new organism:
do var int i = 1; every 1s do spawn HelloWorld with this.id = i; end; i = i + 1; end end .
We now spawn a new organism every second passing a different id in the constructor (the code between with-end). We can see in the video that every second a new organism starts printing its message on the screen. Again, the printing order is deterministic and never changes.
Note that the spawned organisms are anonymous, because there’s no way to refer to them after they are created. Anonymous organisms are useful when the interaction with the block that creates them happens only in the constructor, as in the example above.
Note also the we use an enclosing do-end apparently with no purpose in the code. However, in order to also provide seamless memory reclamation for anonymous organisms, a spawn statement must be enclosed by a do-end that defines the scope of its instances. This way, when the do-end block goes out of scope, all organisms are reclaimed in the same way local organisms are.
In the next example, we surround the previous code with a par/or that restarts the outer loop after 5 seconds:
loop do par/or do await 5s; with do var int i = 1; every 1s do spawn HelloWorld with this.id = i; end; i = i + 1; end end end end .
Now, after creating new instances during 5 seconds, the par/or terminates the do-end scope and all organisms are killed. The loop makes this pattern to execute continuously.
An anonymous organism can also be safely reclaimed when its body terminates, given that no one can refer and access its fields.
The most flexible way to deal with dynamic organisms is through the new statement, which not only spawns a new organism but also returns a reference to it:
do var HelloWorld* hello = new HelloWorld; hello:id = 1; ... // some more code end .
In the example, the returned reference is assigned to the variable hello, which is of type HelloWorld* (a pointer to the class). The organism can be manipulated through the colon operator (:), which is equivalent to the arrow operator in C (->).
A named organism is automatically reclaimed when the block holding the pointer it was first assigned goes out of scope. In the example, when the do-end block in which the hello pointer is declared goes out of scope, the referred instance is reclaimed.
For safety reasons, Céu does not allow a pointer to “escape” to an outer block. Without this precaution, a reference could last longer than the organism it points, yielding a dangling pointer in the program. In the following example, both the assignment to outer and the call to _cfunc are refused, given that their scope are broader the that of variable hello:
var HelloWorld* outer; do var HelloWorld* hello = new HelloWorld; hello:id = 1; outer = hello; // this assignment is refused at compile time _cfunc(hello); // this call is refused at compile time ... // some more code end .
In order to compile this code, we need to include finalizers to properly handle the organism going out of scope:
var HelloWorld* outer; do var HelloWorld* hello = new HelloWorld; hello:id = 1; finalize outer = hello; // outer > hello with ... // this code is executed just before do-end goes out of scope end _cfunc(hello) // _cfunc > hello (_cfunc is a global function) finalize with ... // this code is executed just before do-end goes out of scope end; end .
A finalize block is tied to the scope of the dangerous pointer and gets executed automatically just before the associated block terminates. This way, programmers have means to handle the organism being reclaimed in a safe way.
Céu support three different ways to deal with dynamic allocation of organisms:
- Local organisms should be used when the number of instances is known a priori.
- Anonymous allocations should be used when the number of instances is arbitrary and the program only interacts with them at instantiation time.
- Named allocations are the most flexible, but should only be used when the first two methods don’t apply.
In all cases, memory and trails reclamation is handled seamlessly, without programming efforts.
In practice, given that organisms have lines of execution and can react to the environment by themselves, anonymous organisms should be preferred over named organisms in order to avoid dealing with references explicitly.
In the next post, I’ll show a simple evaluation of the runtime costs of organisms in Céu.