Stella Streaming Example

Motivating Example

A modern approach to building "real-time" or "live" applications are stream-based frameworks such as ReactiveX, which describes the API of a class of streaming frameworks in over 18 languages. These frameworks provide abstractions for data streams together with an extensive collection of built-in operators to transform and combine them. Consider a temperature monitoring application that visualises live measurements of many heterogenous sensors. Depending on units of measurement and user preferences, measurements may have to be transformed from one unit to another. This can be done by mapping a conversion function over a stream of measurements using some built-in map operator, resulting in a new stream of data.

Streaming frameworks are often designed sequentially, i.e. new input data is first propagated to all connected streams before the next input can be accepted, and parallelising this process is non-trivial. With composable behaviours we can design a simple framework where streams and operators are actors, such that multiple computations can run in parallel.

diagram of actor behaviours

Figure 1: Composition of behaviours in an actor-based streaming framework.

Figure 1 depicts the different behaviours involved in our streaming framework. Every behaviour lists the methods that it provides, and for clarity we also list when a behaviour expects a certain method to be present in its composer that it does not implement itself. The framework provides a WebWorkerStream behaviour to spawn a web worker and to abstract over its messages as a data stream, and also 1 built-in Map operator to map a function over a stream. Common functionality for operators is factored out into an Operator behaviour (which also behaves like a stream), and common functionality of streams is factored out into Stream and Subscribable. Stream implements functionality for publishing and receiving values, Subscribable simply keeps a list of other streams (actors) that should receive new publications.

This page contains all of the necessary code to implement this framework as a minimal proof of concept. Scripts loaded by this webpage are:

  • stella-evaluator.js: Bundled package of the runtime of the prototype Stella language. A Stella program is represented by a string in standard JavaScript, which is compiled/analysed and evaluated.
  • worker.js: A file that implements the logic of a simple Web Worker that emits 1 number per second.
  • Stella source code: The Stella program that is run can be viewed directly in the source code of this page which can be viewed in the browser. Below we briefly sum up each of the behaviours.
Behaviour
Description
Main (program entrypoint)

Implements the first behaviour of the application. This behaviour is spawned into an actor by the Stella runtime. When started, it will spawn a new "WebWorkerStream" actor which will simply count from 1 to infinity (starting at 0) and publish every number in 1 second increments, and a "Map" actor increments this number by 1. Both numbers are printed to the console.

Stream

Implements common functionality of data streams, which in this case is simply the program logic for publishing and receiving data. The publish method sends a new value to all subscribers of a stream (which are stored elsewhere, accessed via a self message send), and the receive method by default simply returns the symbol 'do-nothing

Subscribable

Keeps a list of subscribers to stream. Subscribers (actors) can be added/removed via the subscribe and unsubscribe methods, and the collect method iterates over the collection.

WebWorkerStream

To keep the code from this motivating example completely self-contained within one webpage (i.e. without relying on external servers), the input data of the program is generated by a Web Worker written in standard JavaScript. We provide a native actor behavour JSWebWorker that starts a Web Worker from a standard JavaScript file, in this case ./worker.js. Whenever the spawned web worker procudes a message, it is received in the mailbox of the WebWorkerStream as an on-message message. The data received from the worker is simply converted to a number value and published.

Operator

The Operator behaviour implements common functionality of streaming operators, which in this case simply amounts to ensuring that all operators behave like data streams.

Map

The Map behaviour implements an operator to map a function over a stream. It overrides the receive method of Operator (acquired from Stream) to incorporate the functionality of mapping a function to all received values, and publishing the result of the function application.

Playground

Note that this is only a prototype, and that there is hardly any useful error reporting in the case of parse, compile, or runtime errors.

When clicking the button below, the program of the motivating example is compiled and evaluated. 2 strings will be printed to the console: the output of WebWorkerStream (which is a number), and the output of the instance of Map (which is the the number published by WebWorkerStream + 1).

Console output: