How Time Works
Time is one of the central notions of react-sim
.
A react-sim Model
will maintain:
tick
(integer), a representation of the progress in the simulation,data
(anything), the internal state of the data to be shown,isPlaying
(boolean), a flag that controls whether the animation is running or not.
In addition to these main properties, Model
also maintains:
cachedData
(object), which caches data which has already been calculated for a giventick
,results
(array), where information on the previous runs of the simulation can be stored.
The simulation author is expected to pass properties such as:
initialParams
(object), a list of params for the simulation. They can be overridden by the controls.initData
(function), which takesparams
as input and outputs the first value fordata
.updateData
(function), which takes:params
,data
,tick
,cachedData
, andresults
as inputs, and outputs a new value fordata
.
The simulation lifecycle
Initial state
When a <Model />
is created it will:
- initiate the interal
params
from the values ofinitParams
. - initiate
tick
- either through theminTime
or theinitialTick
prop.minTime
takes precedence. - run
initData
and generate a first value fordata
from theparams
.
The frames will not render until initData
has run, but they will render once data
has been initialized once.
If the isPlaying
prop of the <Model />
is set to true, it will start playing
. Else, the simulation doesn't change until controls trigger it.
Playing
When the simulation is playing, every so often it will try to update data
and refresh the Frames
.
By default, the simulation refreshes 60 times per second.
It's possible to make it go slower by providing a delay
prop to Model
. The simulation will wait that long (in ms) to refresh the animation.
Each time the animation refreshes, the simulation will progress by 1 tick (i.e. the tick
value will increase by 1.)
It's also possible to make it go faster with a ticksPerAnimation
prop. If this value is greater than 1, then the simualtion will try to update data
that many times before re-rendering Frames
.
We can use both values at once. For instance, we can have a simulation that, every 100ms, updates 500 times.
Playing stops if:
- the user pauses or stops the simulation,
ticks
reaches the maximum value,- the simulation completes.
When the simulation pauses, isPlaying
simply switches to false. Nothing else changes, tick
stays the same, so does data
, etc.
The default timer provides an easy way for a user to trigger a pause
. Model has an internal method that pauses, which can be exposed to controls.
When the simulation stops, isPlaying
switches to false, but data is also re-initiated:
tick
goes back to its original value,initData
fires again and overwritesdata
,cachedData
is emptied,results
is left unchanged.
The simulation completes when updateData
triggered a condition where it can't go any further.
When this happens:
isPlaying
switches to false, but:tick
is unchanged,data
is unchanged,- A
result
for this run may be appended toresults
.
The simulation won't reset unless manually triggered.
When to update or cache data
Each time tick
changes, data
is updated.
If using cached data, and tick
changes to a value for which cachedData
has precomputed values, then the simulation will simply retrieve those values and not do further calculations.
This can happen, for instance, if a user uses the time slider to move back in time.
Else, we're going to figure out which is the latest value of tick
for which we have data, and through how many cycles do we have to go to get to the current value of tick
- which can be just 1 if tick is simply incrementing along the animation, but which could be more if ticksPerAnimation
is set, or if we are moving foward in time with the time slider, and run updateData
as many times as needed.
updateData
will stop running if:
tick
reaches the maximum value, or- the simulation completes.
Each time it successfully runs, if caching data, it will update cachedData
.
If a simulation is never going to complete, and has no maximum time value, and there's no reason to go back in time, then it may be a good idea to disable caching, as the system will eventually run out of memory. You can do that by setting the noCache
property of the Model
to true
.
Updating data
Updating data is done through the updateData
function, which takes as arguments an object with the following properties:
data
: the existingdata
,tick
: the next value oftick
,params
: all theparams
of the simulation,cachedData
: the cached values ofdata
for previous ticks,results
: the results of previous runs,complete
: a function that signals that the simulation is complete. If it's provided an argument (result), that is added to theresults
property of the simulation.
updateData
will return an updated value for data
. Even if it completes, it's expected to return data
.
The main idea behind updateData
taking the existing data
as an argument is that it can operates as a mathematical sequence.
For example, in the following Fibonacci spiral example:
the updateData
function is a very simple recursion:
function updateData({ data, tick }) {if (tick === 0) {return [0];}if (tick === 1) {return [0, 1];}const lastNumber = data[tick - 1] + data[tick - 2];return [...data, lastNumber];}
But updateData
has access to other properties of the simulation, and can use past values of data, or even information on previous runs of the simulation, as needed.
updateData
can also not use any of these arguments and provide values unrelated to the previous state of the simulation.