Lifecycle and Identity
Evolve's template system works on a concept called Structural Identity
. This means that the compiler
figures out which 'scope' an element belongs to and ties that element's lifecycle to the scope. Scopes
are created whenever their control flow and all elements declared in that scope are bound to its lifecycle.
This means we only create elements when we enter a scope for the first time and we disable or destroy elements
when the scope is no longer in use. Every frame as your template execute the system figures out if the control flow
being run was also run last frame or not and decides if it needs to create the elements within this scope or if
it should use the same instances from last frame. One of the great things about this system is that you can declare
state
variables anywhere inside of a scope and they will retain their values across frames.
Here is how a frame is built for each template in your game.
- Was this the first time this scope was entered?
- if so then invoke any
created
hooks that are defined on the elements
- if so then invoke any
- Was this created this frame or previously disabled?
- if so then invoke any
enabled
hooks
- if so then invoke any
- Set all of the per-frame 'bindings' for the currently executing element. This includes:
- parameters
- non constant styles
- non constant attributes
- invoke any before early input hooks
- process input
- invoke any after early input hooks
- invoke any update hooks
- invoke any after update input hooks
- recursively visit all of the children of this template
- invoke any late input hooks
- invoke any finish hooks
template Example(required bool showGroup1) render {
if(showGroup1) {
// a new scope is created for the true case
// the two text elements below are bound to this scope
// if this branch does not execute in a given frame,
// all elements that are descendents of this scope will be
// disabled (or destroyed if the scope is marked with :destructive)
Text("Element 1");
Text("Element 2");
}
else {
// a new scope is created for the else case
// Element 3 is a member of this scope.
Text("Element 3");
}
}
Asynchronous Lifecycle
There are two classes of events which are not executed as your code runs each frame: disable
and destroy
.
For performance reasons we only run these handlers at the start of the next frame. It is illegal / undefined behavior if you have a disable/destroy handler that uses a closure to close over any fields / state in a template. Generally, you should not have to worry about this because the system makes it very hard to do, but in case you are tempted to trick the system into doing something crazy: you've been warned!