A Functional Reactive Game Engine: Sodium Explained

Previously I introduced SodiumFRP as the Functional Reactive Programming library I chose to use while building my FRPGameEngine. I briefly explained some core concepts of the library and here I will explain more in-depth how SodiumFRP works.

The main data types are Cells and Streams. As mentioned before, Streams are ways to define how variables get assigned. Cells hold values that are defined using streams. For example Stream.send(“hello world”) could result in a Cell getting assigned the value 11. The .send would trigger the user-defined ‘mapping’ behaviour that would propagate the changes. What does this mean? If Cell = Stream.map(helloWorldString -> helloWorldString.stringSize) then when Stream.send(“hello world”) is called the Cell’s data would be updated immediately and automatically. This is the property of propagation.

Yellow = String, Blue = Integer
Yellow = String, Blue = Integer
HelloWorldExample
And the actual code.

The :: operator passes in the function length which is the same as passing in the lambda string -> string.length().

Along with the map operation shown above there are several other operations that can be used in defining the flow of data.

Merge – takes two streams and combines the flows of data into one. This allows for two Streams to be able to update the same Cell.

Cell = Stream1.merge(Stream2).map(String::length)MergeExample

Now Stream1.send and Stream2.send can assign values to the Cell.

Hold – is an initialiser.

Cell = Stream.hold(0)

The above code assigns Cell with the value of 0 before Stream.send has been called.

Map – changes one stream type to another stream type.

Stream<Integer> = Stream<String>.map(dataAsString -> dataAsString.sizeAsInteger)

The map function here turns the String stream into an Integer stream, just as the previous code example show. Map is a very useful operation and is one of the most used operations in the FRPGameEngine.

Coalesce – isn’t a widely used operation but it’s crucial. When streams are sent simultaneously this operation dictates how the data should behave.

CoalesceExample

A practical use case would be when mapping several behaviours from a single keypress stream.

Filter – is a way of telling the stream to stop propagating if a condition is not met.

FilterExample

The filter here prevent an empty string from being sent and so the cell isn’t updated. Also note that any further operations after the filter stops the stream are not called.

accum – will accumulate the values that pass through it and perform some user-defined function.

AccumExample

Cell would be assigned the startValue same way it is using ‘hold’. If startValue = 0 then Cell will end up having value of 5 as 0 + 2 + 3 == 5.

Update – is useful but dangerous. Cell.Update() return a Stream from the Cell which is triggered every time the cell is updated with a new value. Update is dangerous because if it gets merged into Streams that affect the Cell itself then there can be some strange results.

Sample – returns the current value contained inside a Cell. This is useful for interacting with non FRP external libraries and other such areas. I use sample a lot for maths and rendering.

Lift – is a cross between update and merge. Lift takes two cells and returns a new cell that is updated when either of the two cells are updated. This works similar to update but is better as a Stream isn’t exposed and therefore can’t be used to create strange behaviour.

Cell3 = Cell1.lift(Cell2)

Cell3 = Cell1.update().merge(Cell2.update())

These two examples have the same behaviour.

There are more operations that can be used in SodiumFRP but these are some of the operations I’ve used more off. For more info on SodiumFRP watch the authors video or look at the code. Also look at my FRPGameEngine on github.

P.S sorry this post took so long to write. I’ve been rather lazy lately with my blogging of this project.

Leave a comment