-
Notifications
You must be signed in to change notification settings - Fork 9
Matrix Lifecycle
The Matrix (MX) lifecycle has a well-defined beginning, middle, and end.
-
Beginning --The API
makefunction. It creates a new model, and immediately callsmd-awakento initialize and connect all reactive properties.md-awakenforces the evaluation of all formulas, and invokes any:watchfunctions, be they on formulas or input cells. We want models to hit the ground running, if you will, meaning they are fully operational by the timemakereturns them. Where property formulasmakeother models, those get recursively awakened before the original call tomakereturns. -
Middle -- Now external events arrive asynchronously. Event handlers are free to change those model properties designated as "inputs", with each change propagating fully to other, dependent properties. The set of all properties forms a DAG. The entire DAG is made current with a change to X before
(mset! X 42)returns. Along the way, anywatchfunctions of changed properties are dispatched.- Watch functions can enqueue reactive changes for execution after the current change completes propagating. A global "watch manager" can be declared to handle deferred changes, should they require coordination.
-
End -- If a formula that generated models runs a second time, and produces a different set of models, the lost models are "quiesced", with any
on-quiescehandlers being called, and with reach reactivecellalso quiesced, disconnecting from other cells, and again having optionalon-quiescefunctions called.
Let us look at each in detail.
The largest MX app, of perhaps hundreds of widgets, gets built by a single call to make, seemingly to create and activate one model. The rest of the app gets created as formula properties of the first model are evaluated, some of which create other models. This continues recursively, until a full application structure is produced, rooted by the original. Think "Big Bang".
Let us look at the details of the processing done by make:
- some internals housekeeping we can ignore;
- each property gets awakened, in the order they appear;
- if a property is provided an input cell, the initial value is installed in the property;
- if a property receives a formula cell, the formula is run at once to produce the property's initial value;
- if a "formula->input" cell is provided, it runs once to compute an initial value from arbitrary other state, then converts to an input cell. Future changes to the dependency state is ignored. This can be compared to an OO constructor function doing interesting, one-time initialization of a new instance;
- if a property has a
watchfunction, it gets dispatched immediately, in case a widget requires external initialization; - n.b.: the prior-value passed to the watch function during
md-awakenis the symbolunbound. - a watch function can decide to make a state change, but must use a "deferral" API to enqueue the change for later.
- if a formula
makes nested models, they are immediately awakened. This can recursively expand as far as memory allows. HenceBig Bang.
The experienced MX developer may spot something scary in the Big Bang:
- given: that formulas are free to navigate to any other model in the larger "Matrix" of models; and
- given: that properties are awakened in no particular order:
- problem: how can a formula be sure the model, to which it wants to navigate, has been created?
The answer is JIT (just in time) model building. The navigation utility does an expanding depth-first, left-to-right search starting from some starting model. It does this by asking for the kids of the model. If they have not yet been computed, then they will be JIT, each enqueued for their own awakening. If the descendant search fails, the parent of the original model is searched similarly.
We were surprised when this worked, and take it as a good sign that it worked out of the box.
The one tweak made was not to establish dependencies on the kids property during navigation, even though, by contrast, a formula explicitly reading a kids property does establish a dependency. The rationale: if we are simply navigating to X to use it, our dependency is on X, not the incidental navigation to find X. But if a formula explicitly reads another model's kids, it cares if they change.
The middle phase of a model's life is simple:
- if they have input cells, arbitrary procedural code can mutate them;
- if they have formula cells, these might get triggered by a change to dependencies, input or formulaic;
- if a property changes, any watch function will run;
- nb. watch functions, can enqueue mutations of input cells for execution once the current mutation finishes propagating
- nb. because formulas can compute structure, the overall population of models, the matrix, changes over time to make apps responsive.
When a containing model decides it no longer needs nested models, their abandonment is noted by MX internals and they are quiesced.
The function md-quiesce calls c-quiesce on any cells attached to properties, then adjusts the model's meta to indicate it is no longer active. If it has an on-quiesce property, that will be dispatched by the MX engine. Useful for resource clean-up.
Likewise, any cell can have an on-quiesce property, and those get dispatched.
Finally, all cells "unlink" the two-way connections they have with dependents or dependencies.
.