Comps

Overview

The sys.comp library defines a general purpose ontology for modeling component/block oriented systems. Haxall provides a specific implementation of these specs for component oriented applications that all leverage a standard set of Fantom APIs and data flow engine. This framework is used for the Ion user interface stack and the SkySpark rule engine.

The key Fantom APIs are:

  • Comp: mixin for all Haxall components
  • CompObj: base class for all Haxall components
  • CompSpace: top-level application for managing a tree of components and their execution

Comps

Haxall components are modeled by the Comp mixin and CompObj class. All instances of Comp are created in the context of a CompSpace. When created they are automatically bound to a Xeto spec and assigned a unique id.

There are two different mechanisms used to bind an instance of Comp to a spec:

  1. A Fantom constructor will bind to the closest spec
  2. Using CompSpace.create will create a component with that exact spec and instantiate the closest Fantom class

The component's spec defines its slots. Slots come in two flavors: fields and methods. A slot typed as a Func with one parameter is a method, and any other non-function slot is a field. For example:

MyComp : Comp {
  field: Str
  method: Func { arg: Str, returns: Str }
}

You can add additional fields to a component instance using Comp.set and Comp.add. We call these slots dynamic because they exist only on the instance and not formally defined by spec. However, you cannot define a new dynamic method. Methods can only be defined in the spec statically for all instances of that type.

When a Comp is first created it will always have a unique id within the scope of its CompSpace. But it starts life unmounted and not part of the CompSpace. It becomes mounted once it gets added somewhere under the CompSpace.root component. Once mounted it will be executed and can be resolved via CompSpace.readById. You can check mount status via Comp.isMounted.

CompSpace Lifecycle

All Comp instances must be created in the context of a CompSpace in order to be bound to a Namespace. You do this by installing an instance of CompSpace as an actor local for the current thread. It is then used as the factory for all components on that thread. The typical lifecycle for a CompSpace is:

  1. Define Namespace for the components
  2. Construct instance using CompSpace.make with namespace
  3. Install as the actor local via CompSpace.install
  4. Load the root via CompSpace.load, or will default to CompObj root
  5. Start via CompSpace.start
  6. Execution loop calls to CompSpace.execute
  7. Stop via CompSpace.stop
  8. Uninstall via CompSpace.uninstall

Links

The Haxall component engine uses a standardized data flow engine based on links. Xeto defines a standardized link model described here. Haxall uses this model to automatically propagate links between component slots during execution.

Links are always defined on the to/input component and refer back to the from/output component/slot. They are just normal dicts that use the fromRef and fromSlot tags:

// models link from @a.aSlot -> @b.bSlot
@b: Comp {
  links: {
    bSlot: Link { fromRef:@a, fromSlot:"aSlot" }
  }
}

The following table defines how links propagate based on slot type:

From To Behavior
field prop When from changes, push the new value to to
field method When from changes, call to with new value as argument
method field When from is called, push return value to to
method method When from is called, call to with return value as argument

Execution

Component execution is managed by the CompSpace.execute method. An external application must periodically call this method to perform one execution cycle of the space.

During execution each component in the space that has been marked for execution receives its Comp.onExecute callback. This callback should be used to update the component outputs from inputs. Components that mutate their state based on clock time, should use CompContext.now to monitor elapsed time.

There are several ways a component can be scheduled for execution:

  1. Explicitly call the Comp.execute which sets a flag to execute the component on the next cycle (or current cycle if it has not been processed yet)

  2. Schedule periodic executions by overriding Comp.onExecuteFreq

  3. Components are automatically scheduled for execute if any non-output slot is modified by set/add/remove; outputs are specifically tagged with the output marker tag

Whenever a component updates a field or calls a method, the new value is queued to be pushed to any linked slots. During execution each componet pushes any queued updates, which may in turn cause the linked components to execute. The CompSpace automatically sorts component execution order by link topology. However if there are circular links then it may take several execution cycles to fully propagate link changes.

Axon

Haxall components can define custom execute logic using Axon by creating a method named onExecute. This method is called with the CompContext as an argument:

MyIncrement : Comp {
  in: Number <transient>
  out: Number <output, transient>
  onExecute: Func <axon:"""this.set("out",  this->in + 1)""">
}

Note: today we don't allow the dot operator to get/set slots, so you must use the trap(), get(), and set() functions.

Haxall 4.0.6 ∙ 21-Jul-2026 09:48 EDT