This is going to be our first dev-log post here π¦
State and statelessβ
Let me give you an example
{RGBColor[1,0,0], Disk[{0,0}, 1]}
What you do see here?
A stateless RGBColor
symbol modifies the local scope of a List
substituting a new color value read later by Disk
. Is the last one is also stateless? No
The beauty and simplicity of dynamics implemented in WLJS Interpreter
{RGBColor[1,0,0], Disk[{0,0}, Offload[radius]]}
is that each instance of a Disk
has its own state - DOM element, a couple of properties such as position, radius, color, opacity and etc. It is important to note, that radius
here is also an instance with its own state, determined by a symbol radius
defined on Wolfram Kernel. Two nested instances can see each other form a couple. When a child changes, a parent is reevaluated with a new data
radius = 1.0;
Graphics[{RGBColor[1,0,0], Disk[{0,0}, Offload[radius]]}]
EventHandler[InputRange[0,1,0.1], Function[value, radius = value]]
What about color?..
Virtual containersβ
The name is a bit weird, however, the idea is that if an interpreter sees an attribute of a symbol
g2d.Disk = async (args, env) => {
//... normal evaluation
}
g2d.Disk.update = async () => {
//... when child mutates
}
g2d.Disk.virtual = true
It alters the interpretation and creates a sort of container for this symbol to be evaluated inside it. This container has a local memory, identity and can see other such containers.
We can do in the same way and add this attribute to RGBColor
g2d.RGBColor.virtual = true
Then the construction
{RGBColor[color // Offload], Disk[{0,0}, 1]}
Will work for sure. However, now we coupled the following symbols
RGBColor
color
How to bind Disk
to RGBColor
instance, which cannot be directly seen?
New update methods and coupling schemesβ
What we can do is to provide sort of a reference to env
variable to a list of potentially coupled objects, i.e.
g2d.RGBColor = async () => {
//... create references list
const refs = [];
env.exposed.colorRefs = refs;
}
g2d.RGBColor.update = async () => {
//... execute one by one using new data
env.exposed.colorRefs.forEach((instance) => {
instance.execute({method: 'updateColor', color: newColor});
})
}
g2d.RGBColor.virtual = true;
This scheme will update all connected instances. To connect we need to add a couple of line to Disk
and other primitives
g2d.Disk = async () => {
//...
if (env.colorRefs) {
//append this instance to a list of references
env.colorRefs.push(env.root);
}
}
g2d.Disk.update = async () => {}
//... regular update method
//for nested expressions
}
g2d.Disk.updateColor = (args, env) => {
//new method just for updating color!
env.local.object.attr('fill', env.color);
}
Here we also defined a additional method for updating just a color of a primitive. The same can be done for Opacity
as well.
Of course by turning RGBColor
from stateless function into a sort of object comes with a additional overhead for an interpreter and memory. However later on we will check it on our performance tests.
Examplesβ
This opens up more possibilities for dynamics. In principle, this was the last thing, which was missing for a long time for a complete dynamic evaluating in WLJS Notebook.
Let us see it on a simple example
color = {1,0,0};
Graphics[{RGBColor[color // Offload], Disk[{0,0}, 1]}]
EventHandler[InputJoystick[], Function[xy,
color = Normalize[{xy[[1]], xy[[2]], 0.5}] // Abs;
]]
Or using Opacity
and blending between two Disk
s
opacity = 0.5;
Graphics[{Opacity[Offload[opacity]], Red, Disk[{0,0}, 1], Blue, Opacity[Offload[1.0 - opacity]], Disk[{0,0}, 1]}]
EventHandler[InputRange[0,1,0.1], Function[value,
opacity = value;
]]
Or even cooler - combining it with a traditional dynamics as well
opacity = 0.5;
Graphics[{Opacity[Offload[opacity]], Red, Disk[{0,0}, Offload[1-opacity]], Blue, Opacity[Offload[1.0 - opacity]], Disk[{0,0}, Offload[opacity]]}, ImagePadding->None]
EventHandler[InputRange[0,1,0.1], Function[value,
opacity = value;
]]
Benchmarkingβ
I have created a complete suite of tests to check the performance of the system as a whole and by certain sections, i.e.
- Wolfram Kernel
- WLJS Interpreter
- HTTP and WebSockets
- 2D/3D dynamic graphics
- Stress test with many dynamic objects
It provides stats in the end as well as comparison to others results (if you shared)
more is better, all bars a normalized
You can download this notebook by the link down below Benchmark
According to multiple tests this new feature does not actually impacts the performance that much
The most impact I expected from a test called Bubbles, which involves a lot of creation and destruction of many graphics objects. However, it seems very weak.
See you next time β¨ Kirill