UNPKG

@lf-lang/reactor-ts

Version:

A reactor-oriented programming framework in TypeScript

217 lines (198 loc) 7.18 kB
import type { Sortable, PrioritySetElement, ReactionSandbox, MutationSandbox, Reactor, TimeValue, ArgList, Variable } from "./internal"; import {Log, Timer, Tag, Startup} from "./internal"; /** * A number that indicates a reaction's position with respect to other * reactions in an acyclic precedence graph. * @see ReactionQueue */ export type Priority = number; /** * Generic base class for reactions. The type parameter `T` denotes the type of * the argument list of the `react` function that that is applied to when this * reaction gets triggered. * * @author Marten Lohstroh <marten@berkeley.edu> */ export class Reaction<T extends Variable[]> implements Sortable<Priority>, PrioritySetElement<Priority> { /** * Priority derived from this reaction's location in the dependency graph * that spans the entire hierarchy of components inside the top-level reactor * that this reaction is also embedded in. */ private priority: Priority = 0; // Number.MAX_SAFE_INTEGER; /** * Pointer to the next reaction, used by the runtime when this reaction is staged * for execution at the current logical time. */ public next: PrioritySetElement<Priority> | undefined; /** * Construct a new reaction by passing in a reference to the reactor that * will own it, an object to execute the its `react` and `late` functions * on, a list of triggers, the arguments to pass into `react` and `late`, * an implementation of this reaction's `react` function, an optional * deadline to be observed, and an optional custom implementation of the * `late` function that is invoked when logical time lags behind physical time * with a margin that exceeds the time interval denoted by the deadline. * @param reactor The owner of this reaction. * @param sandbox The `this` object for `react` and `late`. * @param trigs The ports, actions, or timers, which, when they receive * values, will trigger this reaction. * @param args The arguments to be passed to `react` and `late`. * @param react Function that gets execute when triggered and "on time." * @param deadline The maximum amount by which logical time may lag behind * physical time when `react` has been triggered and is ready to execute. * @param late Function that gets execute when triggered and "late." */ constructor( private readonly reactor: Reactor, private readonly sandbox: ReactionSandbox, readonly trigs: Variable[], readonly args: [...ArgList<T>], private readonly react: (...args: ArgList<T>) => void, private deadline?: TimeValue, private readonly late: (...args: ArgList<T>) => void = () => { Log.globalLogger.warn("Deadline violation occurred!"); } ) {} /** * Indicates whether or not this reaction is active. A reaction become * active when its container starts up, inactive when its container * shuts down. */ public active = false; /** * Return true if this reaction is triggered immediately (by startup or a * timer with zero offset). */ isTriggeredImmediately(): boolean { return ( this.trigs.filter( (trig) => trig instanceof Startup || (trig instanceof Timer && trig.offset.isZero()) ).length > 0 ); } /** * Return the priority of this reaction. It determines the execution order among * reactions staged for execution at the same logical time. */ getPriority(): Priority { return this.priority; } /** * Return whether or not this reaction has priority over another. * @param another Reaction to compare this reaction's priority against. */ hasPriorityOver(another: PrioritySetElement<Priority> | undefined): boolean { if (another != null && this.getPriority() < another.getPriority()) { return true; } else { return false; } } /** * Return whether another, newly staged reaction is equal to this one. * Because reactions are just object references, no updating is necessary. * Returning true just signals that the scheduler shouldn't stage it twice. * @param node */ updateIfDuplicateOf(node: PrioritySetElement<Priority> | undefined): boolean { return Object.is(this, node); } /** * Invoke the react function in the appropriate sandbox and with the argument * list that was specified upon the construction of this reaction object. */ public doReact(): void { Log.debug( this, () => ">>> Reacting >>> " + this.constructor.name + " >>> " + this.toString() ); Log.debug(this, () => `Reaction deadline: ${this.deadline}`); // If this reaction was loaded onto the reaction queue but the trigger(s) // absorbed by a mutation that routed the value(s) elsewhere, then return // without invoking the reaction. if (!this.active) { return; } // Test if this reaction has a deadline which has been violated. // This is the case if the reaction has a defined timeout and // logical time + timeout < physical time if ( this.deadline != null && this.sandbox.util .getCurrentTag() .getLaterTag(this.deadline) .isSmallerThan(new Tag(this.sandbox.util.getCurrentPhysicalTime(), 0)) ) { this.late.apply(this.sandbox, this.args); // late } else { this.react.apply(this.sandbox, this.args); // on time } } /** * Set a deadline for this reaction. The given time value denotes the maximum * allowable amount by which logical time may lag behind physical time at the * point that this reaction is ready to execute. If this maximum lag is * exceeded, the `late` function is executed instead of the `react` function. * @param deadline The deadline to set to this reaction. */ public setDeadline(deadline: TimeValue): this { this.deadline = deadline; return this; } /** * Set for reaction priority, to be used only by the runtime environment. * The priority of each reaction is determined on the basis of its * dependencies on other reactions. * @param priority The priority for this reaction. */ public setPriority(priority: number): void { this.priority = priority; } /** * Return string representation of the reaction. */ public toString(): string { return `${this.reactor._getFullyQualifiedName()}[R${this.reactor._getReactionIndex( this as unknown as Reaction<Variable[]> )}]`; } } export class Procedure<T extends Variable[]> extends Reaction<T> {} export class Mutation<T extends Variable[]> extends Reaction<T> { readonly parent: Reactor; constructor( __parent__: Reactor, sandbox: MutationSandbox, trigs: Variable[], args: [...ArgList<T>], react: (...args: ArgList<T>) => void, deadline?: TimeValue, late?: (...args: ArgList<T>) => void ) { super(__parent__, sandbox, trigs, args, react, deadline, late); this.parent = __parent__; } /** * @override */ public toString(): string { return `${this.parent._getFullyQualifiedName()}[M${this.parent._getReactionIndex( this as unknown as Reaction<Variable[]> )}]`; } }