Skip to content

Matrix Lifecycle

Kenneth Tilton edited this page Oct 12, 2023 · 14 revisions

Matrix Lifecycle

The Matrix (MX) lifecycle has a well-defined beginning, middle, and end.

  • Beginning --The API make function. It creates a new model, and immediately calls md-awaken to initialize and connect all reactive properties. md-awaken forces the evaluation of all formulas, and invokes any :watch functions, 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 time make returns them. Where property formulas make other models, those get recursively awakened before the original call to make returns.
  • 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, any watch functions 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-quiesce handlers being called, and with reach reactive cell also quiesced, disconnecting from other cells, and again having optional on-quiesce functions called.

Let us look at each in detail.

Beginning -- A Big Bang

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 watch function, it gets dispatched immediately, in case a widget requires external initialization;
    • n.b.: the prior-value passed to the watch function during md-awaken is the symbol unbound.
    • 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. Hence Big Bang.

JIT Structure

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.

Middle

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.

End

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.

Clone this wiki locally