diff --git a/__tests__/ActionTrigger.test.ts b/__tests__/ActionTrigger.test.ts index 772495693..77ee7752c 100644 --- a/__tests__/ActionTrigger.test.ts +++ b/__tests__/ActionTrigger.test.ts @@ -1,6 +1,4 @@ -import {App, Triggers, Args} from '../src/core/reactor'; -import {Origin, TimeValue} from '../src/core/time'; -import {Reactor, Timer, Action, Sched} from '../src/core/reactor'; +import {App, Triggers, Args, Origin, TimeValue, Reactor, Timer, Sched, Action} from '../src/core/internal'; //Upon initialization, this reactor should produce an //output event @@ -67,9 +65,8 @@ export class ActionTrigger extends Reactor { class ActionTriggerTest extends App { aTrigger: ActionTrigger; - constructor(name: string, timeout: TimeValue, success?: ()=> void, fail?: ()=>void){ + constructor(timeout: TimeValue, success?: ()=> void, fail?: ()=>void){ super(timeout, false, false, success, fail); - this._setAlias(name); this.aTrigger = new ActionTrigger(this); } } @@ -86,7 +83,7 @@ describe('ActionTrigger', function () { }; //Tell the reactor runtime to successfully terminate after 3 seconds. - var aTriggerTest = new ActionTriggerTest("ActionTriggerTest", TimeValue.secs(3), done, failure); + var aTriggerTest = new ActionTriggerTest(TimeValue.secs(3), done, failure); //Don't give the runtime the done callback because we don't care if it terminates aTriggerTest._start(); diff --git a/__tests__/Adder.test.ts b/__tests__/Adder.test.ts index 8e67c875b..500709f0e 100644 --- a/__tests__/Adder.test.ts +++ b/__tests__/Adder.test.ts @@ -1,5 +1,5 @@ -import {App, IOPort, Reactor, InPort, OutPort, Present, Args, Triggers} from '../src/core/reactor'; +import { IOPort, App, Reactor, Present, Args, Triggers, InPort, OutPort} from '../src/core/internal'; export class Adder extends Reactor { diff --git a/__tests__/Clock.test.ts b/__tests__/Clock.test.ts index 4b3762965..2659153e0 100644 --- a/__tests__/Clock.test.ts +++ b/__tests__/Clock.test.ts @@ -1,7 +1,6 @@ 'use strict'; -import {Timer, Action, App, Sched, Triggers, Args} from '../src/core/reactor'; -import {TimeValue, TimeUnit, Origin} from "../src/core/time" +import { Action,Timer, App, Sched, Triggers, Args,TimeValue, TimeUnit, Origin } from '../src/core/internal'; /** * This app tests simultaneous events. @@ -23,9 +22,8 @@ export class Clock extends App { a2 = new Action(this, Origin.logical); a3 = new Action(this, Origin.logical); - constructor(name: string, timeout: TimeValue, success: () => void, fail: () => void) { + constructor(timeout: TimeValue, success: () => void, fail: () => void) { super(timeout, false, false, success, fail); - this._alias = name; this.addReaction( new Triggers(this.t1), new Args(this.schedulable(this.a1)), @@ -102,7 +100,7 @@ describe('clock', function () { }; //Tell the reactor runtime to successfully terminate after 6 seconds. - var clock = new Clock("Clock", TimeValue.secs(6), done, fail); + var clock = new Clock(TimeValue.secs(6), done, fail); //Don't give the runtime the done callback because we don't care if it terminates clock._start(); diff --git a/__tests__/HierarchicalSingleEvent.test.ts b/__tests__/HierarchicalSingleEvent.test.ts index 711c36836..a94700ff7 100644 --- a/__tests__/HierarchicalSingleEvent.test.ts +++ b/__tests__/HierarchicalSingleEvent.test.ts @@ -1,5 +1,5 @@ -import {Reactor, OutPort, InPort, App, Parameter} from '../src/core/reactor'; -import {TimeValue} from "../src/core/time" +import {Reactor, App, Parameter, OutPort, InPort, TimeValue} from '../src/core/internal'; + import {SingleEvent} from '../src/share/SingleEvent'; import {Logger} from '../src/share/Logger'; @@ -31,9 +31,8 @@ class SETest extends App { seContainer: SEContainer; logContainer: LogContainer; - constructor(name:string, timeout: TimeValue, keepAlive: boolean = false, fast: boolean = false, success: ()=> void, fail: ()=>void ){ + constructor(timeout: TimeValue, keepAlive: boolean = false, fast: boolean = false, success: ()=> void, fail: ()=>void ){ super(timeout, keepAlive, fast, success, fail) - this._setAlias(name); this.seContainer = new SEContainer(this); this.logContainer = new LogContainer(this); @@ -61,7 +60,7 @@ describe('HierarchicalSingleEvent', function () { }; // Tell the reactor runtime to successfully terminate after 3 seconds. - let seTest = new SETest("SingleEventTesterApp", TimeValue.secs(3), false, false, done, failReactor); + let seTest = new SETest(TimeValue.secs(3), false, false, done, failReactor); // Normally _setAllParents would be called as part of the initialization // process for starting an app, but we call it directly here to set diff --git a/__tests__/Logger.test.ts b/__tests__/Logger.test.ts index dfb357749..1d899d653 100644 --- a/__tests__/Logger.test.ts +++ b/__tests__/Logger.test.ts @@ -1,9 +1,5 @@ import {Logger} from '../src/share/Logger' -import {Reactor, InPort, Read, Triggers, Args, State, Present, ReactionSandbox, App} from '../src/core/reactor'; -import { TimeValue, TimeUnit } from '../src/core/time'; -import { Log, LogLevel } from '../src/core/util' - - +import {Reactor, App, Log, LogLevel} from '../src/core/internal'; const _reactor:Reactor = new App() const lg:Logger = new Logger(_reactor , 10) diff --git a/__tests__/OutputEvent.test.ts b/__tests__/OutputEvent.test.ts index 943e1b5d4..bbc8241c6 100644 --- a/__tests__/OutputEvent.test.ts +++ b/__tests__/OutputEvent.test.ts @@ -1,5 +1,4 @@ -import {App, Reactor, Parameter, Args, Triggers} from '../src/core/reactor'; -import {TimeValue} from "../src/core/time" +import {App, Reactor, Parameter, Args, Triggers, TimeValue} from '../src/core/internal'; import {SingleEvent} from '../src/share/SingleEvent'; /** diff --git a/__tests__/OutputGet.test.ts b/__tests__/OutputGet.test.ts index 18a1f5785..570f30607 100644 --- a/__tests__/OutputGet.test.ts +++ b/__tests__/OutputGet.test.ts @@ -1,6 +1,5 @@ -import {OutPort, App, Timer, Write, Triggers, Args} from '../src/core/reactor'; -import {TimeValue} from "../src/core/time"; -import { Log } from '../src/core/util'; + +import {App, Timer, Write, Triggers, Args, OutPort, TimeValue, Log} from '../src/core/internal'; class OutputGetTest extends App { @@ -10,7 +9,6 @@ class OutputGetTest extends App { constructor(timeout: TimeValue, name:string, success: ()=> void, failure: ()=>void){ super(timeout, true, false, success, failure); Log.global.debug(">>>>>>>>----" + this.util) - this._setAlias(name); this.addReaction( new Triggers(this.t), new Args(this.writable(this.o)), diff --git a/__tests__/SingleEvent.test.ts b/__tests__/SingleEvent.test.ts index be86d15cb..a9e4fdbf7 100644 --- a/__tests__/SingleEvent.test.ts +++ b/__tests__/SingleEvent.test.ts @@ -1,5 +1,4 @@ -import {App, Parameter} from '../src/core/reactor'; -import {TimeValue} from "../src/core/time" +import {App, Parameter, TimeValue} from '../src/core/internal'; import {SingleEvent} from '../src/share/SingleEvent'; import {Logger} from '../src/share/Logger'; @@ -9,7 +8,6 @@ class SETest extends App { constructor(timeout: TimeValue, success: ()=> void, failure: ()=>void ) { super(timeout, false, false, success, failure); - this._setAlias("SETest"); this.singleEvent = new SingleEvent(this, new Parameter("foo")); this.logger = new Logger(this, "foo"); diff --git a/__tests__/alarm.ts b/__tests__/alarm.ts index 17134a932..77ec2b642 100644 --- a/__tests__/alarm.ts +++ b/__tests__/alarm.ts @@ -1,4 +1,4 @@ -import { TimeValue, TimeUnit, Alarm } from '../src/core/time'; +import { TimeValue, TimeUnit, Alarm } from '../src/core/internal'; import NanoTimer from 'nanotimer'; var timerA = new NanoTimer(); diff --git a/__tests__/bank.ts b/__tests__/bank.ts new file mode 100644 index 000000000..8f068fb3e --- /dev/null +++ b/__tests__/bank.ts @@ -0,0 +1,56 @@ +import { Bank , Reactor, App, Timer, Triggers, Args, Present, OutPort, InPort, TimeValue } from "../src/core/internal"; + +class Periodic extends Reactor { + + t: Timer = new Timer(this, 0, TimeValue.sec(1)); + o: OutPort = new OutPort(this) + constructor(parent: Reactor) { + super(parent) + this.addReaction( + new Triggers(this.t), + new Args(this.t), + function (this) { + console.log(this.getBankIndex()); + } + ); + } +} + +class Generic extends Reactor { + input: InPort = new InPort(this); +} + +describe('Check bank index', () => { + + class myApp extends App { + b = new Bank(this, 3, Periodic, this) + c = new Bank, [Reactor]>(this, 2, Generic, this); + constructor() { + super(); + test('contained bank member name', () => { + expect(this.b.get(0)._getFullyQualifiedName()).toBe("myApp.b[0]") + expect(this.b.get(1)._getFullyQualifiedName()).toBe("myApp.b[1]") + expect(this.b.get(2)._getFullyQualifiedName()).toBe("myApp.b[2]") + }) + it('contained bank member index', () => { + expect(this.b.get(0).getBankIndex()).toBe(0); + expect(this.b.get(1).getBankIndex()).toBe(1); + expect(this.b.get(2).getBankIndex()).toBe(2); + }); + + it('generic bank', () => { + this.c.all().forEach(r => expect(typeof r.input == "number")) + }); + var foo = this.b.port((member) => member.o) + var bar = [this.b.get(0).o, this.b.get(1).o, this.b.get(2).o] + it('select port', () => { + for (let i=0; i < foo.length; i++) { + expect(foo[i]).toBe(bar[i]); + } + }); + } + } + + new myApp(); + +}); diff --git a/__tests__/connection.test.ts b/__tests__/connection.test.ts new file mode 100644 index 000000000..2ae0cf62d --- /dev/null +++ b/__tests__/connection.test.ts @@ -0,0 +1,99 @@ +import { Reactor, App, Triggers, Args, State, OutPort, InPort, TimeUnit, TimeValue } from '../src/core/internal'; + +describe('Check canConnect', () => { + class Source extends Reactor { + out: OutPort = new OutPort(this) + } + class Destination extends Reactor { + in: InPort = new InPort(this) + out: InPort = new InPort(this) + } + + class TestApp extends App { + source: Source + destination: Destination + + constructor() { + super() + this.source = new Source(this) + this.destination = new Destination(this) + + it('canConnect success out->in', () => { + expect(this.canConnect(this.source.out, this.destination.in)).toBe(true) + }) + + it('canConnect success out->out', () => { + expect(this.canConnect(this.source.out, this.destination.out)).toBe(true) + }) + + it('canConnect failure', () => { + expect(this.canConnect(this.destination.in, this.source.out)).toBe(false) + }) + } + } + var testApp = new TestApp() +}) + +describe('Check _connect', () => { + jest.setTimeout(5000); + + class Source extends Reactor { + out: OutPort = new OutPort(this) + constructor(container: Reactor) { + super(container); + this.addReaction( + new Triggers(this.startup), + new Args(this.writable(this.out)), + function(this, __out) { + __out.set(100); + + } + ); + } + } + class Destination extends Reactor { + in: InPort = new InPort(this) + received: State = new State(0) + constructor(container: Reactor) { + super(container) + this.addReaction( + new Triggers(this.in), + new Args(this.in, this.received), + function(this, __in, __received) { + let tmp = __in.get(); + try + { + if(tmp) + { + __received.set(tmp) + } + } finally { + + } + } + ) + } + } + + class TestApp extends App { + source: Source + destination: Destination + + constructor(timeout: TimeValue, success?: () => void, fail?: () => void) { + super(timeout, false, false, success, fail) + this.source = new Source(this) + this.destination = new Destination(this) + this._connect(this.source.out, this.destination.in) + } + } + + it("_connect success", done => { + function fail() { + throw new Error("Test has failed."); + }; + + let testApp = new TestApp(TimeValue.withUnits(1,TimeUnit.nsec), done, fail) + testApp._start() + expect(testApp.destination.received.get()).toBe(100) + }) +}) diff --git a/__tests__/dependencies.ts b/__tests__/dependencies.ts index 88fa3e607..efe36bfdc 100644 --- a/__tests__/dependencies.ts +++ b/__tests__/dependencies.ts @@ -1,6 +1,5 @@ -import {SortableDependencyGraph, Sortable, PrioritySetElement, PrioritySet, Log, LogLevel} from '../src/core/util'; -import {Reactor, App, Triggers, InPort, Args, ArgList, Startup, Shutdown} from '../src/core/reactor'; -import {Reaction, Priority} from "../src/core/reaction" +import {Reactor, App, Triggers, Args, InPort, Reaction, Priority, + SortableDependencyGraph, Sortable, PrioritySet, Log, StringUtil} from '../src/core/internal'; //Log.setGlobalLevel(Log.levels.DEBUG); @@ -103,11 +102,13 @@ describe('Manually constructed precedence graphs', () => { expect(graph.size()[0]).toEqual(6); // V expect(graph.size()[1]).toEqual(7); // E expect(graph.toString()).toBe( -`digraph G { -"App/R[R0]"->"App/R[R1]"->"App/R[R4]"->"App/R[R3]"->"App/R[R5]"; -"App/R[R0]"->"App/R[R4]"; -"App/R[R1]"->"App/R[R2]"->"App/R[R3]"; -}`); + StringUtil.dontIndent + `digraph G { + "app.R[R0]"->"app.R[R1]"->"app.R[R4]"->"app.R[R3]"->"app.R[R5]"; + "app.R[R0]"->"app.R[R4]"; + "app.R[R1]"->"app.R[R2]"->"app.R[R3]"; + }` + ); }); it('initial priorities', () => { @@ -126,9 +127,9 @@ describe('Manually constructed precedence graphs', () => { expect(graph.size()[1]).toEqual(6); // E expect(graph.toString()).toBe( `digraph G { -"App/R[R0]"->"App/R[R1]"->"App/R[R2]"->"App/R[R3]"->"App/R[R5]"; -"App/R[R1]"->"App/R[R4]"; -"App/R[R0]"->"App/R[R4]"; +"app.R[R0]"->"app.R[R1]"->"app.R[R2]"->"app.R[R3]"->"app.R[R5]"; +"app.R[R1]"->"app.R[R4]"; +"app.R[R0]"->"app.R[R4]"; }`); }); @@ -138,10 +139,12 @@ describe('Manually constructed precedence graphs', () => { expect(graph.size()[1]).toEqual(3); // E Log.global.debug(graph.toString()); expect(graph.toString()).toBe( -`digraph G { -"App/R[R2]"->"App/R[R3]"->"App/R[R5]"; -"App/R[R0]"->"App/R[R4]"; -}`); + StringUtil.dontIndent + `digraph G { + "app.R[R2]"->"app.R[R3]"->"app.R[R5]"; + "app.R[R0]"->"app.R[R4]"; + }` + ); }); it('add node 7, make 3 dependent on it', () => { @@ -151,11 +154,13 @@ describe('Manually constructed precedence graphs', () => { expect(graph.size()[1]).toEqual(4); // E Log.global.debug(graph.toString()); expect(graph.toString()).toBe( -`digraph G { -"App/R[R2]"->"App/R[R3]"->"App/R[R5]"; -"App/R[R0]"->"App/R[R4]"; -"App/R[R2]"->"App/R[R6]"; -}`); + StringUtil.dontIndent + `digraph G { + "app.R[R2]"->"app.R[R3]"->"app.R[R5]"; + "app.R[R0]"->"app.R[R4]"; + "app.R[R2]"->"app.R[R6]"; + }` + ); }); it('reassign priorities', () => { diff --git a/__tests__/hierarchy.ts b/__tests__/hierarchy.ts index 99d9f6626..56ce25604 100644 --- a/__tests__/hierarchy.ts +++ b/__tests__/hierarchy.ts @@ -1,108 +1,73 @@ -import {Reactor, OutPort, InPort, App} from '../src/core/reactor'; +import {Reactor, App, InPort, OutPort} from '../src/core/internal'; -var app = new App(); - -class Component extends Reactor { +class InOut extends Reactor { a: InPort = new InPort(this); b: OutPort = new OutPort(this); - constructor(parent: Reactor, alias:string) { + constructor(parent: Reactor) { super(parent); - this._setAlias(alias); } - child: Reactor | undefined; } -describe('Container to Contained', () => { - - var container = new Component(app, "Container"); - var contained = new Component(container, "Contained"); - var grandcontained = new Component(contained, "GrandContained"); - - container.child = contained; - contained.child = grandcontained; - - var container2 = new Component(app, "Container2"); - var contained2 = new Component(container2, "Contained2"); - - container2.child = contained2; - - // Normally _setAllParents would be called as part of the initialization - // process for starting an app, but we call it directly here to set - // parent attributes needed for this test. - // container._setAllParents(null); - // container2._setAllParents(null); - - // it('reactor with self as child', () => { - // expect(() => { - // let loopy = new Component(app, "Loopy"); - // loopy.child = loopy; - // loopy._checkAllParents(null); - // }).toThrowError(); - // }); - - // it('reactor with a port constructed with the wrong parent', () => { - // expect(() => { - // let badPortComponent = new Component(app, "BadPortComponent"); - // let otherComponent = new Component(app, "OtherComponent"); - - // // this port has been incorrectly constructed because it - // // is an attribute of badPortComponent, but is set in the constructor - // // with otherComponent as its parent - // badPortComponent.a = new InPort(otherComponent); - - // // _setAllParents should throw an error - // badPortComponent._checkAllParents(null); - // }).toThrowError(); - // }); +var app = new class extends App { + container = new class extends InOut { + contained = new class extends InOut { + containedAgain = new InOut(this) + }(this) + }(this) + foo = new InOut(this) +}() +describe('Container to Contained', () => { + it('app name', () => { + expect(app.toString()).toBe("app"); + }); + it('contained reactor name', () => { - // expect(contained._getName()).toBe("Contained"); - expect(contained.toString()).toBe("App/Container/child (Contained)"); + expect(app.container.contained.toString()).toBe("app.container.contained"); }); it('container reactor name', () =>{ - // expect(container._getName()).toBe("Container"); - expect(container.toString()).toBe("App/Container"); + expect(app.container.toString()).toBe("app.container"); }) it('testing canConnect', () => { - expect(container.canConnect(container.a, contained.a)).toBe(true); - expect(container.canConnect(contained.a, container.a)).toBe(false); - expect(container.canConnect(contained.a, contained.b)).toBe(false); - expect(container.canConnect(contained.b, contained.a)).toBe(true); + expect(app.container.canConnect(app.container.a, app.container.contained.a)).toBe(true); + expect(app.container.canConnect(app.container.contained.a, app.container.a)).toBe(false); + expect(app.container.canConnect(app.container.contained.a, app.container.contained.b)).toBe(false); + expect(app.container.canConnect(app.container.contained.b, app.container.contained.a)).toBe(true); - expect(container.canConnect(container.a, contained.b)).toBe(false); - expect(container.canConnect(contained.b, container.a)).toBe(false); + expect(app.container.canConnect(app.container.a, app.container.contained.b)).toBe(false); + expect(app.container.canConnect(app.container.contained.b, app.container.a)).toBe(false); - expect(container.canConnect(container.b, contained.a)).toBe(false); - expect(container.canConnect(contained.a, container.b)).toBe(false); + expect(app.container.canConnect(app.container.b, app.container.contained.a)).toBe(false); + expect(app.container.canConnect(app.container.contained.a, app.container.b)).toBe(false); - expect(container.canConnect(container.b, contained.b)).toBe(false); - expect(container.canConnect(contained.b, container.b)).toBe(true); + expect(app.container.canConnect(app.container.b, app.container.contained.b)).toBe(false); + expect(app.container.canConnect(app.container.contained.b, app.container.b)).toBe(true); - expect(container.canConnect(contained.a, contained2.a)).toBe(false); - expect(container.canConnect(contained.a, contained2.b)).toBe(false); - expect(container.canConnect(contained2.a, contained.a)).toBe(false); - expect(container.canConnect(contained2.a, contained.a)).toBe(false); + expect(app.container.canConnect(app.container.contained.a, app.foo.a)).toBe(false); + expect(app.container.canConnect(app.container.contained.a, app.foo.b)).toBe(false); + expect(app.container.canConnect(app.foo.a, app.container.contained.a)).toBe(false); + expect(app.container.canConnect(app.foo.a, app.container.contained.a)).toBe(false); - expect(container.canConnect(contained2.a, container.b)).toBe(false); - expect(container.canConnect(contained2.a, container.a)).toBe(false); + expect(app.container.canConnect(app.foo.a, app.container.b)).toBe(false); + expect(app.container.canConnect(app.foo.a, app.container.a)).toBe(false); - expect(container.child).toBeDefined(); + // expect(app.container.contained).toBeDefined(); - if (container.child) { - expect(container.child.canConnect(grandcontained.a, contained.a)).toBe(false); - expect(container.child.canConnect(grandcontained.b, contained.b)).toBe(true); - expect(container.child.canConnect(grandcontained.a, container.a)).toBe(false); - expect(container.child.canConnect(grandcontained.b, container.b)).toBe(false); - expect(container.child.canConnect(grandcontained.a, container2.a)).toBe(false); - expect(container.child.canConnect(grandcontained.b, container2.b)).toBe(false); - expect(container.child.canConnect(grandcontained.a, contained2.a)).toBe(false); - expect(container.child.canConnect(grandcontained.b, contained2.b)).toBe(false); - } + // if (container.child) { + expect(app.container.contained.canConnect(app.container.contained.containedAgain.a, app.container.contained.a)).toBe(false); + expect(app.container.contained.canConnect(app.container.contained.containedAgain.b, app.container.contained.b)).toBe(true); + expect(app.container.contained.canConnect(app.container.contained.containedAgain.a, app.container.a)).toBe(false); + expect(app.container.contained.canConnect(app.container.contained.containedAgain.b, app.container.b)).toBe(false); + expect(app.container.contained.canConnect(app.container.contained.containedAgain.a, app.foo.a)).toBe(false); + expect(app.container.contained.canConnect(app.container.contained.containedAgain.b, app.foo.b)).toBe(false); + expect(app.container.contained.canConnect(app.container.contained.containedAgain.a, app.foo.a)).toBe(false); + expect(app.container.contained.canConnect(app.container.contained.containedAgain.b, app.foo.b)).toBe(false); + //} }); }); \ No newline at end of file diff --git a/__tests__/multiport.test.ts b/__tests__/multiport.test.ts new file mode 100644 index 000000000..c4c4e8282 --- /dev/null +++ b/__tests__/multiport.test.ts @@ -0,0 +1,92 @@ +import { Args, Triggers, Reactor, App, InMultiPort, OutMultiPort } from "../src/core/internal"; +class TwoInTwoOut extends Reactor { + inp = new InMultiPort(this, 2); + out = new OutMultiPort(this, 2); + + foo = new class extends InMultiPort { + constructor(container: Reactor) { + + test('create multiport with no container', () => { + expect( + () => { + // @ts-ignore + super(null, 4) + } + ).toThrowError("Cannot instantiate component without a parent.") + }) + super(container, 4) + test('make port writable with invalid key', () => { + expect( + () => { + this.asWritable(Symbol()) + } + ).toThrowError(`Referenced port is out of scope: myApp.${container._getName()}.foo`) + }) + } + }(this) + + constructor(parent: Reactor) { + super(parent) + let writer = this.allWritable(this.inp) + test('check inactive during construction', () => { + expect(this._active).toBe(false) + }) + test('check multiport width', () => { + expect(this.inp.width()).toBe(2) + expect(writer.width()).toBe(2) + }) + this.addReaction( + new Triggers(this.inp), + new Args(this.inp), + function (this, inp) { + test('check read values', () => { + expect(inp.channel(0).get()).toBe(42); + expect(inp.get(0)).toBe(42); + expect(inp.channel(1).get()).toBe(69); + expect(inp.get(1)).toBe(69); + expect(inp.values()).toEqual([42, 69]) + }); + test('print input port names', () => { + expect(inp._getName()).toBe("inp"); + expect(inp.channel(0)._getName()).toBe("inp[0]"); + expect(inp.channel(1)._getName()).toBe("inp[1]"); + expect(inp.channel(0)._getFullyQualifiedName()).toBe("myApp.y.inp[0]"); + expect(inp.channel(1)._getFullyQualifiedName()).toBe("myApp.y.inp[1]"); + }); + } + ); + this.addReaction( + new Triggers(this.startup), + new Args(this.allWritable(this.out)), + function (out) { + test('start up reaction triggered', () => { + expect(true).toBe(true); + }) + test('check multiport values before and after writing', () => { + expect(out.values()).toEqual([undefined, undefined]) + out.set(0, 42) + out.set(1, 69) + expect(out.get(0)).toBe(42); + expect(out.get(1)).toBe(69); + expect(out.values()).toEqual([42, 69]) + }) + } + ); + } +} + + +class myApp extends App { + + x = new TwoInTwoOut(this); + y = new TwoInTwoOut(this); + + constructor() { + super(); + this._connectMulti([this.x.out], [this.y.inp], false) + } + +} + +var app = new myApp(); +app._start() diff --git a/__tests__/mutations.test.ts b/__tests__/mutations.test.ts index 86486c4ee..acf696e71 100644 --- a/__tests__/mutations.test.ts +++ b/__tests__/mutations.test.ts @@ -1,7 +1,4 @@ -import {Reactor, App, Triggers, InPort, Args, OutPort, Timer, State} from '../src/core/reactor'; -import {TimeValue} from '../src/core/time'; -import {Log, LogLevel} from '../src/core/util'; - +import {Reactor, App, Triggers, Args, Timer, OutPort, InPort, TimeValue} from '../src/core/internal'; class Source extends Reactor { @@ -112,7 +109,7 @@ class ScatterGather extends App { class ZenoClock extends Reactor { tick:Timer; constructor(owner: Reactor, iteration: number) { - super(owner, "ZenoClock(" + iteration + ")") + super(owner) console.log("Creating ZenoClock " + iteration) this.tick = new Timer(this, 0, 0) this.addReaction(new Triggers(this.tick), new Args(this.tick), function(this, tick) { @@ -132,9 +129,10 @@ class ZenoClock extends Reactor { } class Zeno extends App { + readonly zeno = new ZenoClock(this, 1) constructor(timeout: TimeValue, success: () => void, fail: () => void) { super(timeout, false, false, success, fail); - new ZenoClock(this, 1) + var self = this; this.addReaction(new Triggers(this.shutdown), new Args(), function(this) { diff --git a/__tests__/reactors.errors.test.ts b/__tests__/reactors.errors.test.ts index 11002a976..93da0af0b 100644 --- a/__tests__/reactors.errors.test.ts +++ b/__tests__/reactors.errors.test.ts @@ -1,8 +1,7 @@ -import {Reactor, App, Triggers, InPort, Args, ArgList, Startup, Shutdown, CalleePort, CallerPort, Present} from '../src/core/reactor'; -import {TimeUnit, TimeValue} from '../src/core/time'; -import { Log, LogLevel, SortableDependencyGraph, Sortable } from '../src/core/util'; -import { writer } from 'repl'; -import { doesNotMatch } from 'assert'; +import { + Reactor, App, Triggers, Args, CalleePort, CallerPort, Present, InPort, + TimeUnit, TimeValue, Log, LogLevel +} from '../src/core/internal'; class R extends Reactor { diff --git a/__tests__/reactors.test.ts b/__tests__/reactors.test.ts index c22f44510..fa3ae082b 100644 --- a/__tests__/reactors.test.ts +++ b/__tests__/reactors.test.ts @@ -1,7 +1,4 @@ -import {Reactor, App, Triggers, InPort, Args, ArgList, Startup, Shutdown, CalleePort, CallerPort, Port, Present, OutPort, Action, Timer} from '../src/core/reactor'; -import {TimeUnit, TimeValue, Origin } from '../src/core/time'; -import { Log, LogLevel, SortableDependencyGraph, Sortable } from '../src/core/util'; -import { doesNotMatch } from 'assert'; +import { Reactor, App, Triggers, Args, Timer, OutPort, InPort, TimeUnit, TimeValue, Origin, Log, LogLevel, Action } from '../src/core/internal'; /* Set a port in startup to get thing going */ class Starter extends Reactor { diff --git a/__tests__/simple.ts b/__tests__/simple.ts index 696c1796a..cccc6d16e 100644 --- a/__tests__/simple.ts +++ b/__tests__/simple.ts @@ -1,4 +1,4 @@ -import {Reactor, OutPort, InPort, App} from "../src/core/reactor"; +import {Reactor, App, InPort, OutPort, StringUtil} from "../src/core/internal"; class MyActor extends Reactor { @@ -13,8 +13,8 @@ import {Reactor, OutPort, InPort, App} from "../src/core/reactor"; a: InPort<{t: number}> = new InPort(this); b: OutPort<{t: number, y: string}> = new OutPort(this); - constructor(parent:Reactor, alias: string) { - super(parent, alias) + constructor(parent:Reactor) { + super(parent) } } @@ -25,45 +25,36 @@ describe('Test names for contained reactors', () => { port: InPort = new InPort(this); x = new MyActor(this); - y = new MyActor2(this, "Foo"); + y = new MyActor2(this); - constructor(name: string, someParam: string) { + constructor() { super(undefined); - this._setAlias(name); it('contained actor name', () => { expect(this.x._getName()).toBe("x"); }); - it('contained actor Alias', () => { - expect(this.x._getAlias()).toBe(""); - }); - it('contained actor FQN', () => { - expect(this.x._getFullyQualifiedName()).toBe("Hello World/x"); + expect(this.x._getFullyQualifiedName()).toBe("myApp.x"); }); it('contained actor toString', () => { - expect(this.x.toString()).toBe("Hello World/x"); + expect(this.x.toString()).toBe("myApp.x"); }); it('contained actor FQN', () => { - expect(this.x.toString()).toBe("Hello World/x"); + expect(this.x.toString()).toBe("myApp.x"); }); it('contained actor with alias FQN', () => { - expect(this.y.toString()).toBe("Hello World/y (Foo)"); + expect(this.y.toString()).toBe("myApp.y"); }); it('uncontained actor name', () => { - expect(this.toString()).toBe("Hello World"); + expect(this.toString()).toBe("myApp"); }); - it('uncontained actor Alias', () => { - expect(this._getAlias()).toBe(name); - }); - - it('check whehter App is not contained by itself', () => { + it('check whether App is not contained by itself', () => { expect(this._isContainedBy(this)).toBeFalsy(); }); @@ -81,8 +72,8 @@ describe('Test names for contained reactors', () => { it('graph before connect', () => { expect(this._getPrecedenceGraph().toString()).toBe( "digraph G {" + "\n" + - "\"Hello World/x[M0]\"->\"Hello World[M0]\";" + "\n" + - "\"Hello World/y (Foo)[M0]\"->\"Hello World[M0]\";" + "\n" + + "\"myApp.x[M0]\"->\"myApp[M0]\";" + "\n" + + "\"myApp.y[M0]\"->\"myApp[M0]\";" + "\n" + "}"); }); @@ -100,11 +91,12 @@ describe('Test names for contained reactors', () => { it('graph after connect and before disconnect', () => { expect(this._getPrecedenceGraph().toString()).toBe( - "digraph G {" + "\n" + - "\"Hello World/x/a\"->\"Hello World/y (Foo)/b\";" + "\n" + - "\"Hello World/x[M0]\"->\"Hello World[M0]\";" + "\n" + - "\"Hello World/y (Foo)[M0]\"->\"Hello World[M0]\";" + "\n" + - "}"); + StringUtil.dontIndent + `digraph G { + "myApp.x.a"->"myApp.y.b"; + "myApp.x[M0]"->"myApp[M0]"; + "myApp.y[M0]"->"myApp[M0]"; + }`); }); @@ -115,7 +107,6 @@ describe('Test names for contained reactors', () => { } } - var app = new myApp("Hello World", "!"); - + var app = new myApp(); }); diff --git a/lingua-franca-ref.txt b/lingua-franca-ref.txt index a499ec2ef..03d16af6a 100644 --- a/lingua-franca-ref.txt +++ b/lingua-franca-ref.txt @@ -1 +1 @@ -a768c680dc39b5ff7344bd9253916bf6e77c0d68 +e996c5e5f3ac02e85fd330c82cd759b1c76f372f diff --git a/package-lock.json b/package-lock.json index 41d2ba7b2..01aa86229 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,8 @@ "heap": "^0.2.6", "microtime": "^3.0.0", "nanotimer": "^0.3.15", - "ulog": "^1.1.0" + "ulog": "^1.1.0", + "uuid": "^8.3.2" }, "devDependencies": { "@babel/cli": "^7.4.3", @@ -32,6 +33,7 @@ "@babel/preset-typescript": "^7.7.7", "@types/jest": "^26.0.24", "@types/node": "^16.3.3", + "@types/uuid": "^8.3.4", "dtslint": "^3.4.2", "jest": "^27.0.6", "marked": ">=4.0.10", @@ -3364,6 +3366,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "node_modules/@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -3386,9 +3394,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -4416,29 +4424,6 @@ "wrap-ansi": "^6.2.0" } }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -5689,9 +5674,9 @@ } }, "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -6314,6 +6299,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", @@ -9139,15 +9133,6 @@ "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -9157,20 +9142,6 @@ "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime/node_modules/strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -9983,15 +9954,6 @@ "node": ">=8" } }, - "node_modules/jest/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest/node_modules/jest-cli": { "version": "27.0.6", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.0.6.tgz", @@ -10026,20 +9988,6 @@ } } }, - "node_modules/jest/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest/node_modules/strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -11847,6 +11795,32 @@ "node": ">=8" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -12539,6 +12513,14 @@ "dev": true, "optional": true }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-to-istanbul": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", @@ -12747,29 +12729,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -12892,15 +12851,6 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -12933,32 +12883,6 @@ "engines": { "node": ">=8" } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } } }, "dependencies": { @@ -15417,6 +15341,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -15439,9 +15369,9 @@ "dev": true }, "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true }, "acorn-globals": { @@ -16248,23 +16178,6 @@ "wrap-ansi": "^6.2.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -17258,9 +17171,9 @@ "optional": true }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -17733,6 +17646,12 @@ "dev": true, "optional": true }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", @@ -18058,12 +17977,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "jest-cli": { "version": "27.0.6", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.0.6.tgz", @@ -18084,17 +17997,6 @@ "yargs": "^16.0.3" } }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -20048,29 +19950,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -22009,6 +21894,28 @@ } } }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -22524,6 +22431,11 @@ "dev": true, "optional": true }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "v8-to-istanbul": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", @@ -22692,23 +22604,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -22797,12 +22692,6 @@ "path-exists": "^4.0.0" } }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -22826,26 +22715,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } } } }, diff --git a/package.json b/package.json index 8c51e21bf..cca3d8fce 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "heap": "^0.2.6", "microtime": "^3.0.0", "nanotimer": "^0.3.15", - "ulog": "^1.1.0" + "ulog": "^1.1.0", + "uuid": "^8.3.2" }, "devDependencies": { "@babel/cli": "^7.4.3", @@ -26,13 +27,14 @@ "@babel/preset-typescript": "^7.7.7", "@types/jest": "^26.0.24", "@types/node": "^16.3.3", + "@types/uuid": "^8.3.4", "dtslint": "^3.4.2", "jest": "^27.0.6", + "marked": ">=4.0.10", "parsimmon": "1.13.0", "ts-jest": "^27.0.3", "typedoc": "^0.21.4", - "typescript": "^4.3.5", - "marked": ">=4.0.10" + "typescript": "^4.3.5" }, "files": [ "lib" diff --git a/src/benchmark/PingPong.ts b/src/benchmark/PingPong.ts index ca3362526..2c8f7f5c8 100644 --- a/src/benchmark/PingPong.ts +++ b/src/benchmark/PingPong.ts @@ -79,7 +79,7 @@ export class PingPong extends App { super(timeout, keepAlive, fast, success, fail); this.ping = new Ping(this, 1000000) //1000000 this.pong = new Pong(this) - this._connect(this.ping.client, this.pong.server) + this._connectCall(this.ping.client, this.pong.server) } } // =============== END reactor class PingPong diff --git a/src/core/action.ts b/src/core/action.ts new file mode 100644 index 000000000..551d7fdc7 --- /dev/null +++ b/src/core/action.ts @@ -0,0 +1,142 @@ +import type {Absent, Present, Read, Sched} from "./internal" +import { + Reactor, Log, TaggedEvent, + getCurrentPhysicalTime, Origin, Tag, TimeUnit, TimeValue, + ScheduledTrigger, TriggerManager +} from "./internal"; + +const defaultMIT = TimeValue.withUnits(1, TimeUnit.nsec); // FIXME + +export abstract class SchedulableAction implements Sched { + abstract get(): T | undefined; + abstract schedule(extraDelay: 0 | TimeValue, value: T, intendedTag?: Tag): void; +} + +/** + * An action denotes a self-scheduled event. + * An action, like an input, can cause reactions to be invoked. + * Whereas inputs are provided by other reactors, actions are scheduled + * by this reactor itself, either in response to some observed external + * event or as a delayed response to some input event. The action can be + * scheduled by a reactor by invoking the schedule function in a reaction + * or in an asynchronous callback that has been set up in a reaction. + */ + export class Action extends ScheduledTrigger implements Read { + + readonly origin: Origin; + readonly minDelay: TimeValue; + readonly minInterArrival: TimeValue = defaultMIT; + + public get(): T | Absent { + if (this.isPresent()) { + return this.value; + } else { + return undefined; + } + } + + public asSchedulable(key: Symbol | undefined): Sched { + if (this._key === key) { + return this.scheduler + } + throw Error("Invalid reference to container.") + } + + public getManager(key: Symbol | undefined): TriggerManager { + if (this._key == key) { + return this.manager + } + throw Error("Unable to grant access to manager.") + } + + protected scheduler = new class extends SchedulableAction { + get(): T | undefined { + return this.action.get() + } + constructor(private action: Action) { + super() + } + schedule(extraDelay: 0 | TimeValue, value: T, intendedTag?: Tag): void { + if (!(extraDelay instanceof TimeValue)) { + extraDelay = TimeValue.secs(0); + } + + var tag = this.action.runtime.util.getCurrentTag(); + var delay = this.action.minDelay.add(extraDelay); + + tag = tag.getLaterTag(delay); + + if (this.action.origin == Origin.physical) { + // If the resulting timestamp from delay is less than the current physical time + // on the platform, then the timestamp becomes the current physical time. + // Otherwise the tag is computed like a logical action's tag. + + let physicalTime = getCurrentPhysicalTime(); + if (tag.time.isEarlierThan(physicalTime)) { + tag = new Tag(getCurrentPhysicalTime(), 0); + } else { + tag = tag.getMicroStepLater(); + } + } + + if (this.action instanceof FederatePortAction) { + if (intendedTag === undefined) { + throw new Error("FederatedPortAction must have an intended tag from RTI."); + } + if (intendedTag <= this.action.runtime.util.getCurrentTag()) { + throw new Error("Intended tag must be greater than current tag. Intended tag" + + intendedTag + " Current tag: " + this.action.runtime.util.getCurrentTag()); + } + Log.debug(this, () => "Using intended tag from RTI, similar to schedule_at_tag(tag) with an intended tag: " + + intendedTag); + tag = intendedTag; + } else if (this.action.origin == Origin.logical && !(this.action instanceof Startup)) { + tag = tag.getMicroStepLater(); + } + + Log.debug(this, () => "Scheduling " + this.action.origin + + " action " + this.action._getFullyQualifiedName() + " with tag: " + tag); + + this.action.runtime.schedule(new TaggedEvent(this.action, tag, value)); + } + }(this) + + /** + * Construct a new action. + * @param __container__ The reactor containing this action. + * @param origin Optional. If physical, then the hardware clock on the local + * platform is used to determine the tag of the resulting event. If logical, + * the current logical time (plus one microstep) is used as the offset. + * @param minDelay Optional. Defaults to 0. Specifies the intrinsic delay of + * any events resulting from scheduling this action. + * @param minInterArrival Optional. Defaults to 1 nsec. Specifies the minimum + * intrinsic delay between to occurrences of this action. + */ + constructor(__container__: Reactor, origin: Origin, minDelay: TimeValue = TimeValue.secs(0), minInterArrival: TimeValue = defaultMIT) { + super(__container__); + this.origin = origin; + this.minDelay = minDelay; + } + + public toString() { + return this._getFullyQualifiedName(); + } +} + +export class Startup extends Action { // FIXME: this should not be a schedulable trigger, just a trigger + constructor(__parent__: Reactor) { + super(__parent__, Origin.logical) + } +} + +export class Shutdown extends Action { + constructor(__parent__: Reactor) { + super(__parent__, Origin.logical) + } +} + +export class FederatePortAction extends Action { + constructor(__parent__: Reactor) { + super(__parent__, Origin.logical) + } +} \ No newline at end of file diff --git a/src/core/bank.ts b/src/core/bank.ts new file mode 100644 index 000000000..04d662a6f --- /dev/null +++ b/src/core/bank.ts @@ -0,0 +1,77 @@ +import { Port, Present, Reactor } from './internal'; + +/** + * Type that describes a class with a constructor of which the arguments + * are of type `ReactorArgs`. + */ +export type ReactorClass = { + new(...args: ReactorArgs): T; +} + +/** + * Type that describes a tuple of arguments passed into the constructor + * of a reactor class. + */ +export type ReactorArgs = T extends any[] ? T : never; + +/** + * A bank of reactor instances. + */ +export class Bank { + + /** + * Array of reactor instances that constitute the bank. + */ + private readonly members: Array = new Array(); + + /** + * A mapping from containing reactors to indices corresponding to the member + * of a contained bank that is currently being initialized (if there is one). + */ + public static readonly initializationMap: Map = new Map() + + /** + * Construct a new bank of given width on the basis of a given reactor class and a list of arguments. + * @param width the width of the bank + * @param cls the class to construct reactor instances of that will populate the bank + * @param args the arguments to pass into the constructor of the given reactor class + */ + constructor(container: Reactor, width: number, cls: ReactorClass, ...args: ReactorArgs) { + for (let i = 0; i < width; i++) { + Bank.initializationMap.set(container, i) + console.log("Setting initializing to " + i) + this.members.push(Reflect.construct(cls, args, cls)); + } + Bank.initializationMap.delete(container) + } + + /** + * Return all reactor instances in this bank. + * @returns all reactor instances in this bank + */ + public all(): Array { + return this.members + } + + /** + * Return the reactor instance that corresponds to the given index. + * @param index index of the reactor instance inside this bank + * @returns the reactor instances that corresponds to the given index + */ + public get(index: number): T { + return this.members[index] + } + + /** + * Return a list of ports selected across all bank members by the given lambda. + * @param selector lambda function that takes a reactor of type T and return a port of type P + * @returns a list of ports selected across all bank members by the given lambda + */ + public port

>(selector: (reactor: T) => P): Array

{ + return this.all().reduce((acc, val) => acc.concat(selector(val)), new Array

(0)) + } + + public toString() { + return "bank(" + this.members.length + ")" + } +} diff --git a/src/core/cli.ts b/src/core/cli.ts index 945b1816b..7a3c52561 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -1,7 +1,6 @@ import commandLineArgs from 'command-line-args'; import commandLineUsage from 'command-line-usage'; -import {TimeUnit, TimeValue} from './time'; -import { LogLevel } from './util'; +import {TimeUnit, TimeValue, LogLevel} from './internal'; //---------------------------------------------------------------------// // Command Line Arguments Helper Functions // diff --git a/src/core/component.ts b/src/core/component.ts index 730aabe1f..2929f95e8 100644 --- a/src/core/component.ts +++ b/src/core/component.ts @@ -1,4 +1,4 @@ -import {Reactor, App, Runtime} from "./reactor"; +import {Reactor, App, Runtime, MultiPort, IOPort, Bank} from "./internal" /** * Base class for named objects embedded in a hierarchy of reactors. Each @@ -11,10 +11,7 @@ import {Reactor, App, Runtime} from "./reactor"; */ export abstract class Component { - /** - * An optional alias for this component. - */ - protected _alias: string | undefined; + public static pathSeparator = '.' /** * A symbol that identifies this component, and it also used to selectively @@ -40,8 +37,7 @@ export abstract class Component { * constructor in order to establish a link with the runtime object. * @param alias An optional alias for the component. */ - constructor(container: Reactor | null, alias?:string) { - this._alias = alias + constructor(container: Reactor | null) { if (container !== null) { // Register. @@ -53,7 +49,7 @@ export abstract class Component { // Apps are self-contained. this._container = this } else { - throw Error("Cannot instantiate component without a parent.") + throw new Error("Cannot instantiate component without a parent.") } } } @@ -75,7 +71,7 @@ export abstract class Component { } /** - * Report whether this component has been registed with its container or not. + * Report whether this component has been registered with its container or not. * In principle, all components should have a container, but at the time of * their construction there is a brief period where they are not. This is the * only moment that a component is allowed register with its container. @@ -109,75 +105,101 @@ export abstract class Component { return false; } - /** - * Return the alias of this component, or an empty string if none was set. - */ - public _getAlias(): string { - if (this._alias) return this._alias - else return "" - } - /** * Return a string that identifies this component. * The name is a path constructed as `[App]/[..]/[Container]/[This]`. */ - _getFullyQualifiedName(): string { - var path = ""; + _getFullyQualifiedName(): string { if (!(this instanceof App)) { - path = this._container._getFullyQualifiedName(); - } - if (path != "") { - path += "/" + this._getName(); + return this._container._getFullyQualifiedName() + Component.pathSeparator + this._getName(); } else { - path = this._getName(); + return this._getName() } - return path; } /** - * Return a string that identifies this component within its container. + * Given a component and its container (the global object if the component + * is an `App`), return the key of the entry that matches the component. + * @param component a component of which the object is assumed to be its + * container + * @param object the assumed container of the component + * @returns the key of the entry that matches the component */ - public _getName(): string { + public static keyOfMatchingEntry(component: Component, object: Object) { + for (const [key, value] of Object.entries(object)) { + if (value === component) { + return `${key}` + } + } + } - var name = "" - if (!(this instanceof App)) { - for (const [key, value] of Object.entries(this._container)) { - if (value === this) { - name = `${key}` - break + /** + * Given a port and its containing reactor, return the key of the entry that matches + * a multiport found in the reactor that matches the port. + * @param port a port that is assumed to be a constituent of a multiport declared on + * the given reactor + * @param reactor a reactor that is assumed to have a multiport of which one of the + * constituents is the given port + * @returns an identifier for the port based on its location in a matching multiport + */ + public static keyOfMatchingMultiport(port: Component, reactor: Reactor) { + for (const [key, value] of Object.entries(reactor)) { + if (value instanceof MultiPort) { + let channels = value.channels() + for (let i=0; i < channels.length; i++) { + if (channels[i] === port) { + return `${key}[${i}]` + } } } } + } - if (this._alias) { - if (name == "") { - name = this._alias - } else { - name += ` (${this._alias})` + public static keyOfMatchingBank(member: Component, reactor: Reactor) { + for (const [key, value] of Object.entries(reactor)) { + if (value instanceof Bank) { + let members = value.all() + for (let i=0; i < members.length; i++) { + if (members[i] === member) { + return `${key}[${i}]` + } + } } } - // Return the constructor name in case the component wasn't found in - // its container and doesn't have an alias. - if (name == "") { - name = this.constructor.name - } - - return name } /** - * Return the container of this component. + * Return a string that identifies this component within its container. + * If no such string was found, return the name of the constructor. */ - protected _getContainer(): Reactor { - return this._container + public _getName(): string { + var name + + if (this instanceof App) { + name = this._name + } else { + name = Component.keyOfMatchingEntry(this, this._container) + } + + if (!name && this instanceof IOPort) { + name = Component.keyOfMatchingMultiport(this, this._container) + } + + if (!name && this instanceof Reactor) { + name = Component.keyOfMatchingBank(this, this._container) + } + + if (name) { + return name + } else { + return this.constructor.name + } } /** - * Set an alias to override the name assigned to this component by its - * container. - * @param alias An alternative name. + * Return the container of this component. */ - protected _setAlias(alias: string) { - this._alias = alias + protected _getContainer(): Reactor { + return this._container } } diff --git a/src/core/event.ts b/src/core/event.ts new file mode 100644 index 000000000..1be6c90af --- /dev/null +++ b/src/core/event.ts @@ -0,0 +1,61 @@ +import type { Tag, ScheduledTrigger, Present, PrioritySetElement } from "./internal"; + +/** + * An event is caused by a timer or a scheduled action. Each event is tagged + * with a time instant and may carry a value of arbitrary type. The tag will + * determine the event's position with respect to other events in the event + * queue. + */ + export class TaggedEvent implements PrioritySetElement { + + /** + * Pointer to the next element of the priority set that this event might + * be hooked into. + */ + public next: PrioritySetElement | undefined; + + /** + * Construct a new tagged event. + * @param trigger The trigger of this event. + * @param tag The tag at which this event occurs. + * @param value The value associated with this event. + * + */ + constructor(public trigger: ScheduledTrigger, public tag: Tag, public value: T) { + } + + /** + * Return true if this event has a smaller tag than the given event, false + * otherwise. + * @param node The event to compare this event's tag against. + */ + hasPriorityOver(node: PrioritySetElement | undefined) { + if (node) { + return this.getPriority().isSmallerThan(node.getPriority()); + } else { + return false; + } + } + + /** + * Determine whether the given event is a duplicate of this one. If so, assign the + * value this event to the given one. Otherwise, return false. + * @param node The event adopt the value from if it is a duplicate of this one. + */ + updateIfDuplicateOf(node: PrioritySetElement | undefined) { + if (node && node instanceof TaggedEvent) { + if (this.trigger === node.trigger && this.tag.isSimultaneousWith(node.tag)) { + node.value = this.value; // update the value + return true; + } + } + return false; + } + + /** + * Return the tag associated with this event. + */ + getPriority(): Tag { + return this.tag; + } +} \ No newline at end of file diff --git a/src/core/federation.ts b/src/core/federation.ts index 1088984fb..65c365d28 100644 --- a/src/core/federation.ts +++ b/src/core/federation.ts @@ -1,8 +1,10 @@ -import {Log} from './util'; -import {Tag, TimeValue, TimeUnit, Origin, getCurrentPhysicalTime, Alarm} from './time'; + import {Socket, createConnection, SocketConnectOpts} from 'net' import {EventEmitter} from 'events'; -import {Action, Present, TaggedEvent, App, FederatePortAction} from './reactor' +import { + Log, Tag, TimeValue, Origin, getCurrentPhysicalTime, Alarm, + Present, App, Action, FederatePortAction, TaggedEvent +} from './internal'; //---------------------------------------------------------------------// // Federated Execution Constants and Enums // @@ -311,7 +313,7 @@ class RTIClient extends EventEmitter { this.socket?.write(buffer); this.socket?.write(this.federationID); } catch (e) { - Log.error(this, () => {return e.toString()}); + Log.error(this, () => {return `${e}`}); } // Finally, emit a connected event. @@ -371,7 +373,7 @@ class RTIClient extends EventEmitter { try { this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } @@ -382,7 +384,7 @@ class RTIClient extends EventEmitter { try { this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } @@ -405,7 +407,7 @@ class RTIClient extends EventEmitter { Log.debug(this, () => {return `Sending RTI start time: ${myPhysicalTime}`}); this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } @@ -430,7 +432,7 @@ class RTIClient extends EventEmitter { + `federate ID: ${destFederateID} and port ID: ${destPortID}.`}); this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } @@ -460,7 +462,7 @@ class RTIClient extends EventEmitter { + `, time: ${time.toString('hex')}.`}); this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } @@ -481,7 +483,7 @@ class RTIClient extends EventEmitter { Log.debug(this, () => {return "Sending RTI logical time complete: " + completeTime.toString('hex');}); this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } @@ -496,7 +498,7 @@ class RTIClient extends EventEmitter { Log.debug(this, () => {return "Sending RTI resign.";}); this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } @@ -516,7 +518,7 @@ class RTIClient extends EventEmitter { Log.debug(this, () => {return "Sending RTI Next Event Time.";}); this.socket?.write(msg); } catch (e) { - Log.error(this, () => {return e}); + Log.error(this, () => {return `${e}`}); } } diff --git a/src/core/internal.ts b/src/core/internal.ts new file mode 100644 index 000000000..60564a36b --- /dev/null +++ b/src/core/internal.ts @@ -0,0 +1,16 @@ +export * from "./types" +export * from "./strings" +export * from "./time" +export * from "./util" +export * from "./reaction" +export * from "./component" +export * from "./trigger" +export * from "./action" +export * from "./state" +export * from "./port" +export * from "./multiport" +export * from "./reactor" +export * from "./bank" +export * from "./event" +export * from "./cli" + diff --git a/src/core/multiport.ts b/src/core/multiport.ts new file mode 100644 index 000000000..1ce3ac0a9 --- /dev/null +++ b/src/core/multiport.ts @@ -0,0 +1,257 @@ +import { + Absent, InPort, IOPort, MultiRead, MultiReadWrite, OutPort, Present, + Reactor, Runtime, WritablePort, Trigger, TriggerManager, Reaction, Component +} from "./internal"; +import { WritableMultiPort } from "./port"; + +/** + * A trigger that represents a multiport. All of channels of a multiport can be read. + * To obtain a writable version, see @link{Reactor.allWritable()}. + * + * @author Marten Lohstroh + * @author Hokeun Kim + */ +export abstract class MultiPort extends Trigger implements MultiRead { + + /** + * Return all channels of this multiport. + */ + abstract channels(): Array> + + /** + * Return the channel identified by the given index. + * @param index the index of the requested channel + */ + abstract channel(index: number): IOPort + + /** + * The channels of this multiport. + */ + protected _channels: Array> + + /** @inheritdoc */ + private readonly _width: number + + /** + * Given an array of ports (channels), return an array holding the ports' + * current values. + * @param ports the ports to return the values of + * @returns the current values of the given ports + */ + public static values(ports: Array>): Array { + let values = new Array(ports.length); + for (let i = 0; i < values.length; i++) { + values[i] = ports[i].get(); + } + return values + } + + /** + * Construct a new multiport. + * @param container the reactor that will contain the new instance + * @param width the number of channels of newly created instance + */ + constructor(private container: Reactor, width: number) { + super(container) + this._channels = new Array(width) + this._width = width + } + + /** + * Obtain a writable version of this port, provided that the caller holds the required key. + * @param key + */ + public asWritable(key: Symbol | undefined): WritableMultiPort { + if (this._key === key) { + return this.writer + } + throw Error("Referenced port is out of scope: " + this._getFullyQualifiedName()) + } + + /** @inheritdoc */ + public get(index: number): T | undefined { + return this._channels[index].get() + } + + /** @inheritdoc */ + getManager(key: Symbol | undefined): TriggerManager { + if (this._key == key) { + return this.manager + } + throw Error("Unable to grant access to manager.") + } + + /** + * Return whether this multiport has any channels that are present. + * @returns true if there are any present channels + */ + isPresent(): boolean { + return this.channels().some(channel => channel.isPresent()) + } + + /** + * Return the number of channels of this multiport. + * @returns the number of channels + */ + public width(): number { + return this._width + } + + /** + * Return an array of which the elements represent the current value of + * each channel, which may either be present or absent (i.e., undefined). + * @returns an array of values + */ + public values(): Array { + return MultiPort.values(this._channels) + } + + /** + * Inner class instance to let the container configure this port. + */ + protected manager = new class implements TriggerManager { + /** @inheritdoc */ + constructor(private port: MultiPort) { } + + /** @inheritdoc */ + getContainer(): Reactor { + return this.port.getContainer() + } + + /** @inheritdoc */ + addReaction(reaction: Reaction): void { + this.port.channels().forEach( + channel => channel.getManager( + this.getContainer()._getKey(channel) + ).addReaction(reaction)) + } + + /** @inheritdoc */ + delReaction(reaction: Reaction): void { + this.port.channels().forEach( + channel => channel.getManager( + this.port._key + ).delReaction(reaction) + ) + } + }(this) + + /** + * Unimplemented method (multiports require not access to the runtime object). + */ + public _receiveRuntimeObject(runtime: Runtime): void { + throw new Error("Multiports do not request to be linked to the" + + " runtime object, hence this method shall not be invoked."); + } + + /** + * Inner class instance to gain access to MultiWrite interface. + */ + protected writer = new class extends WritableMultiPort { + + getPorts(): IOPort[] { + return this.port._channels + } + + /** + * Storage for obtained writers. + */ + private readonly cache: Array> + + /** @inheritdoc */ + constructor(private port: MultiPort) { + super() + this.cache = new Array(); + } + + /** @inheritdoc */ + public get(index: number): T | undefined { + return this.port._channels[index].get() + } + + /** @inheritdoc */ + public set(index: number, value: T): void { + let writableChannel = this.cache[index] + if (writableChannel === undefined) { + writableChannel = this.port.getContainer() + .writable(this.port._channels[index]) + this.cache[index] = writableChannel + } + writableChannel.set(value) + } + + /** @inheritdoc */ + public width(): number { + return this.port.width() + } + + /** @inheritdoc */ + public values(): Array { + return this.port.values() + } + }(this) + + public toString() { + return this.container.toString() + "." + Component.keyOfMatchingEntry(this, this.container) + } + +} + +/** + * A trigger that represents an input port that is also multiport. + * + * @author Marten Lohstroh + * @author Hokeun Kim + */ +export class InMultiPort extends MultiPort { + + /** @inheritdoc */ + public channel(index: number): InPort { + return this._channels[index] + } + + /** @inheritdoc */ + public channels(): Array> { + return this._channels + } + + /** @inheritdoc */ + constructor(container: Reactor, width: number) { + super(container, width) + for (let i = 0; i < width; i++) { + this._channels[i] = new InPort(container) + } + } +} + +/** + * A trigger that represents an output port that is also multiport. + * + * @author Marten Lohstroh + * @author Hokeun Kim + */ +export class OutMultiPort extends MultiPort { + + /** @inheritdoc */ + constructor(container: Reactor, width: number) { + super(container, width) + for (let i = 0; i < width; i++) { + this._channels[i] = new OutPort(container) + } + } + + /** @inheritdoc */ + public channel(index: number): OutPort { + return this._channels[index] + } + + /** @inheritdoc */ + public channels(): Array> { + return this._channels + } + + /** @inheritdoc */ + public values(): Array { + return MultiPort.values(this._channels) + } +} diff --git a/src/core/port.ts b/src/core/port.ts new file mode 100644 index 000000000..ea1163237 --- /dev/null +++ b/src/core/port.ts @@ -0,0 +1,195 @@ +import { + Reaction, Reactor, Runtime, Tag, Trigger, TriggerManager, + Absent, MultiReadWrite, Present, ReadWrite, Log +} from "./internal" + +export abstract class Port extends Trigger { + + protected receivers: Set> = new Set(); + + protected runtime!: Runtime; + + constructor(container: Reactor) { + super(container) + this._linkToRuntimeObject() + } + + /** The tag associated with this port's value, or undefined is there is none. */ + protected tag: Tag | undefined; + + /** The current value associated with this port. */ + protected value: T | Absent; + + public _receiveRuntimeObject(runtime: Runtime) { + if (!this.runtime) { + this.runtime = runtime + } else { + throw new Error("Can only establish link to runtime once. Name: " + this._getFullyQualifiedName()) + } + } + + /** + * Returns true if the connected port's value has been set; false otherwise + */ + public isPresent() { + + Log.debug(this, () => "In isPresent()...") + Log.debug(this, () => "value: " + this.value); + Log.debug(this, () => "tag: " + this.tag); + Log.debug(this, () => "time: " + this.runtime.util.getCurrentLogicalTime()) + + if (this.value !== undefined + && this.tag !== undefined + && this.tag.isSimultaneousWith(this.runtime.util.getCurrentTag())) { + return true; + } else { + return false; + } + } +} + +/** + * Abstract class for a writable port. It is intended as a wrapper for a + * regular port. In addition to a get method, it also has a set method and + * a method for retrieving the port that it wraps. + * We have this abstract class so that we can do `instanceof` checks. + */ +export abstract class WritablePort implements ReadWrite { + abstract get(): T | undefined + abstract set(value: T): void + abstract getPort(): IOPort +} + +export abstract class WritableMultiPort implements MultiReadWrite { + abstract get(index: number): T | undefined + abstract set(index: number, value: T): void + abstract width(): number + abstract values(): Array + abstract getPorts(): Array> +} + +/** + * Interface for a writable multi port, intended as a wrapper for a multi port. + */ +interface IOPortManager extends TriggerManager { + addReceiver(port: WritablePort): void; + delReceiver(port: WritablePort): void; +} + +export abstract class IOPort extends Port { + + /** + * Return the value set to this port. Return `Absent` if the connected + * output did not have its value set at the current logical time. + */ + public get(): T | Absent { + if (this.isPresent()) { + return this.value; + } else { + return undefined; + } + } + + /** + * Only the holder of the key may obtain a writable port. + * @param key + */ + public asWritable(key: Symbol | undefined): WritablePort { + if (this._key === key) { + return this.writer + } + throw Error("Referenced port is out of scope: " + this._getFullyQualifiedName()) // FIXME: adjust messages for other methods as well + // FIXME: we could potentially do this for reads/triggers as well just for scope rule enforcement + } + + /** + * + * @param container Reference to the container of this port + * (or the container thereof). + */ + public getManager(key: Symbol | undefined): IOPortManager { + if (this._key == key) { + return this.manager + } + throw Error("Unable to grant access to manager.") + } + + /** + * Inner class instance to gain access to Write interface. + */ + protected writer = new class extends WritablePort { + constructor(private port: IOPort) { + super() + } + + public set(value: T): void { + this.port.value = value; + this.port.tag = this.port.runtime.util.getCurrentTag(); + // Set values in downstream receivers. + this.port.receivers.forEach(p => p.set(value)) + // Stage triggered reactions for execution. + this.port.reactions.forEach(r => this.port.runtime.stage(r)) + } + + public get(): T | Absent { + return this.port.get() + } + + public getPort(): IOPort { + return this.port + } + + public toString(): string { + return this.port.toString() + } + + }(this) + + /** + * Inner class instance to let the container configure this port. + */ + protected manager: IOPortManager = new class implements IOPortManager { + constructor(private port: IOPort) { } + getContainer(): Reactor { + return this.port._getContainer() + } + + /** + * Add the given port to the list of receivers. If the connection was + * established at runtime and the upstream port already has a value, + * immediately propagate the value to the newly connected receiver. + * @param port A newly connected downstream port. + */ + addReceiver(port: WritablePort): void { + this.port.receivers.add(port) + if (this.port.runtime.isRunning()) { + let val = this.port.get() + if (val !== undefined) { + port.set(val) + } + } + } + delReceiver(port: WritablePort): void { + this.port.receivers.delete(port) + } + addReaction(reaction: Reaction): void { + this.port.reactions.add(reaction) + } + delReaction(reaction: Reaction): void { + this.port.reactions.delete(reaction) + } + }(this) + + toString(): string { + return this._getFullyQualifiedName(); + } +} + + +export class OutPort extends IOPort { + +} + +export class InPort extends IOPort { + +} diff --git a/src/core/reaction.ts b/src/core/reaction.ts index f1f873672..ca1da3e67 100644 --- a/src/core/reaction.ts +++ b/src/core/reaction.ts @@ -1,6 +1,10 @@ -import {Sortable, PrioritySetElement, Log} from "./util" -import {Reactor, ReactionSandbox, Triggers, Args, ArgList, Startup, Timer, MutationSandbox} from "./reactor" -import {TimeValue, Tag} from "./time"; +import { + Sortable, PrioritySetElement, Log, + ReactionSandbox, Timer, MutationSandbox, + Reactor, TimeValue, Tag, + ArgList, Args, Triggers, + Startup +} from "./internal" /** * A number that indicates a reaction's position with respect to other @@ -158,7 +162,7 @@ export class Reaction implements Sortable, PrioritySetElement = T extends Variable[] ? T : never; - -/** - * Type for data exchanged between ports. - */ -export type Present = (number | bigint | string | boolean | symbol | object | null); - -/** - * Type for simple variables that are both readable and writable. - */ -export type ReadWrite = Read & Write; - -/** - * A variable can be read, written to, or scheduled. Variables may be passed to - * reactions in an argument list. - * @see Read - * @see Write - * @see Sched - */ -export type Variable = Read - -//--------------------------------------------------------------------------// -// Constants // -//--------------------------------------------------------------------------// - -const defaultMIT = TimeValue.withUnits(1, TimeUnit.nsec); // FIXME - //--------------------------------------------------------------------------// // Interfaces // //--------------------------------------------------------------------------// @@ -67,355 +31,19 @@ export interface Call extends Write, Read { invoke(args: A): R | undefined; } -/** - * Interface for readable variables. - */ -export interface Read { - get(): T | Absent; -} -/** - * Interface for schedulable actions. - */ -export interface Sched extends Read { - schedule: (extraDelay: TimeValue | 0, value: T, intendedTag?: Tag) => void; - // FIXME: it makes sense to be able to check the presence of a (re)schedulable action. -} - -/** - * Interface for writable ports. - */ -export interface Write { - set: (value: T) => void; -} - -/** - * Abstract class for a writable port. It is intended as a wrapper for a - * regular port. In addition to a get method, it also has a set method and - * a method for retrieving the port that it wraps. - */ -export abstract class WritablePort implements ReadWrite { - abstract get(): T | undefined; - abstract set(value: T): void; - abstract getPort(): IOPort -} /** * Abstract class for a schedulable action. It is intended as a wrapper for a * regular action. In addition to a get method, it also has a schedule method * that allows for the action to be scheduled. */ -export abstract class SchedulableAction implements Sched { - abstract get(): T | undefined; - abstract schedule(extraDelay: 0 | TimeValue, value: T, intendedTag?: Tag): void; -} + //--------------------------------------------------------------------------// // Core Reactor Classes // //--------------------------------------------------------------------------// -/** - * An event is caused by a timer or a scheduled action. Each event is tagged - * with a time instant and may carry a value of arbitrary type. The tag will - * determine the event's position with respect to other events in the event - * queue. - */ -export class TaggedEvent implements PrioritySetElement { - - /** - * Pointer to the next element of the priority set that this event might - * be hooked into. - */ - public next: PrioritySetElement | undefined; - - /** - * Construct a new tagged event. - * @param trigger The trigger of this event. - * @param tag The tag at which this event occurs. - * @param value The value associated with this event. - * - */ - constructor(public trigger: ScheduledTrigger, public tag: Tag, public value: T) { - } - - /** - * Return true if this event has a smaller tag than the given event, false - * otherwise. - * @param node The event to compare this event's tag against. - */ - hasPriorityOver(node: PrioritySetElement | undefined) { - if (node) { - return this.getPriority().isSmallerThan(node.getPriority()); - } else { - return false; - } - } - - /** - * Determine whether the given event is a duplicate of this one. If so, assign the - * value this event to the given one. Otherwise, return false. - * @param node The event adopt the value from if it is a duplicate of this one. - */ - updateIfDuplicateOf(node: PrioritySetElement | undefined) { - if (node && node instanceof TaggedEvent) { - if (this.trigger === node.trigger && this.tag.isSimultaneousWith(node.tag)) { - node.value = this.value; // update the value - return true; - } - } - return false; - } - - /** - * Return the tag associated with this event. - */ - getPriority(): Tag { - return this.tag; - } -} -/** - * Abstract class for a trigger. A trigger may be an action, port, or timer. - */ -abstract class Trigger extends Component { - - /** - * Reactions to trigger. - */ - protected reactions: Set> = new Set(); - - /** - * Request the manager of this trigger. The request will only be honored - * if the correct key is given. Each component has a unique symbol (a key) - * that is handed to the owner upon instantiation of the component. If the - * wrong key is supplied, return undefined. - * @param key The private key embedded in this trigger. - */ - abstract getManager(key: Symbol | undefined): TriggerManager; - - /** - * Return the owner of this trigger. - */ - public getContainer(): Reactor | null { - return this._getContainer() - } - - /** - * Return whether or not this trigger is present. - */ - abstract isPresent(): boolean; - -} - -/** - * - */ -abstract class ScheduledTrigger extends Trigger { - protected value: T | Absent = undefined; - protected tag: Tag | undefined; - - protected runtime!: Runtime; - - constructor(container: Reactor, alias?: string) { // FIXME: do we really want the alias here? Probably not. - super(container, alias) - this._linkToRuntimeObject() - } - - /** - * Update the current value of this timer in accordance with the given - * event, and trigger any reactions that list this timer as their trigger. - * @param e Timestamped event. - */ - public update(e: TaggedEvent):void { - - if (!e.tag.isSimultaneousWith(this.runtime.util.getCurrentTag())) { - throw new Error("Time of event does not match current logical time."); - } - if (e.trigger === this) { - this.value = e.value - this.tag = e.tag; - for (let r of this.reactions) { - this.runtime.stage(r) - } - } else { - throw new Error("Attempt to update action using incompatible event."); - } - } - - public getManager(key: Symbol | undefined): TriggerManager { - if (this._key == key) { - return this.manager - } - throw Error("Unable to grant access to manager.") - } - - /** - * Returns true if this action was scheduled for the current - * logical time. This result is not affected by whether it - * has a value. - */ - public isPresent() { - if (this.tag === undefined) { - // This action has never been scheduled before. - return false; - } - if (this.tag.isSimultaneousWith(this.runtime.util.getCurrentTag())) { - return true; - } else { - return false; - } - } - - protected manager = new class implements TriggerManager { - constructor(private trigger: ScheduledTrigger) { } - getContainer(): Reactor { - return this.trigger._getContainer() - } - addReaction(reaction: Reaction): void { - this.trigger.reactions.add(reaction) - } - delReaction(reaction: Reaction): void { - this.trigger.reactions.delete(reaction) - } - }(this) - - public _receiveRuntimeObject(runtime: Runtime) { - if (!this.runtime) { - this.runtime = runtime - } else { - throw new Error("Can only establish link to runtime once.") - } - } - -} - -/** - * An action denotes a self-scheduled event. - * An action, like an input, can cause reactions to be invoked. - * Whereas inputs are provided by other reactors, actions are scheduled - * by this reactor itself, either in response to some observed external - * event or as a delayed response to some input event. The action can be - * scheduled by a reactor by invoking the schedule function in a reaction - * or in an asynchronous callback that has been set up in a reaction. - */ -export class Action extends ScheduledTrigger implements Read { - - readonly origin: Origin; - readonly minDelay: TimeValue; - readonly minInterArrival: TimeValue = defaultMIT; - - public get(): T | Absent { - if (this.isPresent()) { - return this.value; - } else { - return undefined; - } - } - - public asSchedulable(key: Symbol | undefined): Sched { - if (this._key === key) { - return this.scheduler - } - throw Error("Invalid reference to container.") - } - - public getManager(key: Symbol | undefined): TriggerManager { - if (this._key == key) { - return this.manager - } - throw Error("Unable to grant access to manager.") - } - - protected scheduler = new class extends SchedulableAction { - get(): T | undefined { - return this.action.get() - } - constructor(private action: Action) { - super() - } - schedule(extraDelay: 0 | TimeValue, value: T, intendedTag?: Tag): void { - if (!(extraDelay instanceof TimeValue)) { - extraDelay = TimeValue.secs(0); - } - - var tag = this.action.runtime.util.getCurrentTag(); - var delay = this.action.minDelay.add(extraDelay); - - tag = tag.getLaterTag(delay); - - if (this.action.origin == Origin.physical) { - // If the resulting timestamp from delay is less than the current physical time - // on the platform, then the timestamp becomes the current physical time. - // Otherwise the tag is computed like a logical action's tag. - - let physicalTime = getCurrentPhysicalTime(); - if (tag.time.isEarlierThan(physicalTime)) { - tag = new Tag(getCurrentPhysicalTime(), 0); - } else { - tag = tag.getMicroStepLater(); - } - } - - if (this.action instanceof FederatePortAction) { - if (intendedTag === undefined) { - throw new Error("FederatedPortAction must have an intended tag from RTI."); - } - if (intendedTag <= this.action.runtime.util.getCurrentTag()) { - throw new Error("Intended tag must be greater than current tag. Intended tag" + - intendedTag + " Current tag: " + this.action.runtime.util.getCurrentTag()); - } - Log.debug(this, () => "Using intended tag from RTI, similar to schedule_at_tag(tag) with an intended tag: " + - intendedTag); - tag = intendedTag; - } else if (this.action.origin == Origin.logical && !(this.action instanceof Startup)) { - tag = tag.getMicroStepLater(); - } - - Log.debug(this, () => "Scheduling " + this.action.origin + - " action " + this.action._getFullyQualifiedName() + " with tag: " + tag); - - this.action.runtime.schedule(new TaggedEvent(this.action, tag, value)); - } - }(this) - - /** - * Construct a new action. - * @param __container__ The reactor containing this action. - * @param origin Optional. If physical, then the hardware clock on the local - * platform is used to determine the tag of the resulting event. If logical, - * the current logical time (plus one microstep) is used as the offset. - * @param minDelay Optional. Defaults to 0. Specifies the intrinsic delay of - * any events resulting from scheduling this action. - * @param minInterArrival Optional. Defaults to 1 nsec. Specifies the minimum - * intrinsic delay between to occurrences of this action. - */ - constructor(__container__: Reactor, origin: Origin, minDelay: TimeValue = TimeValue.secs(0), minInterArrival: TimeValue = defaultMIT) { - super(__container__); - this.origin = origin; - this.minDelay = minDelay; - } - - public toString() { - return this._getFullyQualifiedName(); - } -} - -export class Startup extends Action { // FIXME: this should not be a schedulable trigger, just a trigger - constructor(__parent__: Reactor) { - super(__parent__, Origin.logical) - } -} - -export class Shutdown extends Action { - constructor(__parent__: Reactor) { - super(__parent__, Origin.logical) - } -} - -export class FederatePortAction extends Action { - constructor(__parent__: Reactor) { - super(__parent__, Origin.logical) - } -} export class Parameter implements Read { constructor(private value: T) { @@ -425,37 +53,7 @@ export class Parameter implements Read { } } -/** - * A state variable. This class refines the Read interface by letting `get` - * return T rather than T | Absent. If the state should be nullable or - * uninitialized, this has to be reflected explicitly in T. - */ -export class State implements Read, Write { - - /** - * Create a new state variable and assign it an initial value. - * @param value The initial value to assign to this state variable. - */ - constructor(private value: T) { - - } - - /** - * Return the current value of this state variable. - */ - get(): T { - return this.value; - }; - /** - * Set the current value of this state variable. - * @param value - */ - set(value: T) { - this.value = value; - }; - -} /** * A timer is an attribute of a reactor which periodically (or just once) @@ -512,19 +110,6 @@ export class Timer extends ScheduledTrigger implements Read { } -export class Args { - tuple: T; - constructor(...args: T) { - this.tuple = args; - } -} - -export class Triggers { - list: Variable[]; - constructor(trigger: Variable, ...triggers: Variable[]) { - this.list = triggers.concat(trigger) - } -} /** * A reactor is a software component that reacts to input events, timer events, @@ -536,26 +121,43 @@ export class Triggers { export abstract class Reactor extends Component { /** - * Data structure to keep track of register components. + * Data structure to keep track of registered components. * Note: declare this class member before any other ones as they may * attempt to access it. */ private _keyChain: Map = new Map() /** - * Collection of priviledged functions that are passed down from the + * This graph has in it all the dependencies implied by this container's + * ports, reactions, and connections. + */ + protected _dependencyGraph: DependencyGraph | Reaction> = new DependencyGraph() + + /** + * The runtime object, which has a collection of privileged functions that are passed down from the * container. */ private _runtime!: Runtime; /** - * This graph has in it all the dependencies implied by this reactor's - * ports, reactions, and connections. + * Index that specifies the location of the reactor instance in a bank, + * if it is a member of one. */ - private _dependencyGraph: DependencyGraph | Reaction> = new DependencyGraph() + private _bankIndex: number; /** - * This graph has some overlap with the reactors dependency, but is + * Return the location of the reactor instance in a bank, + * if it is a member of one; return -1 otherwise. + */ + public getBankIndex(): number { + if (this._bankIndex === undefined) { + return -1 + } + return this._bankIndex + } + + /** + * This graph has some overlap with the reactors dependency graph, but is * different in two respects: * - transitive dependencies between ports have been collapsed; and * - it incorporates the causality interfaces of all contained reactors. @@ -628,6 +230,9 @@ export abstract class Reactor extends Component { * @param key The component's key. */ public _register(component: Component, key: Symbol) { + if (component === undefined || component === null) { + throw new Error("Unable to register undefined or null component") + } if (component._isRegistered()) { throw new Error("Unable to register " + component._getFullyQualifiedName() @@ -730,7 +335,7 @@ export abstract class Reactor extends Component { * @param key The key that verifies the containment relation between this * reactor and the component, with at most one level of indirection. */ - protected _getKey(component: Trigger, key?: Symbol): Symbol | undefined { + public _getKey(component: Trigger, key?: Symbol): Symbol | undefined { if (component._isContainedBy(this) || this._key === key) { return this._keyChain.get(component) } else if (!(component instanceof Action) && @@ -768,7 +373,9 @@ export abstract class Reactor extends Component { constructor(private reactor: Reactor) { this.reactor = reactor this.util = reactor.util + this.getBankIndex = () => reactor.getBankIndex() } + getBankIndex: () => number; /** * @@ -776,8 +383,14 @@ export abstract class Reactor extends Component { * @param dst */ public connect - (src: CallerPort | IOPort, dst: CalleePort | IOPort) { - return this.reactor._connect(src, dst); + (src: CallerPort | IOPort, dst: CalleePort | IOPort) { + if (src instanceof CallerPort && dst instanceof CalleePort) { + return this.reactor._connectCall(src, dst); + } else if (src instanceof IOPort && dst instanceof IOPort) { + return this.reactor._connect(src, dst); + } else { + // ERROR + } } /** @@ -802,18 +415,27 @@ export abstract class Reactor extends Component { */ private _ReactionSandbox = class implements ReactionSandbox { public util: UtilityFunctions; + public getBankIndex: () => number; constructor(public reactor: Reactor) { this.util = reactor.util + this.getBankIndex = () => reactor.getBankIndex() } + } /** * Create a new reactor. * @param container The container of this reactor. */ - - constructor(container: Reactor | null, alias?:string) { - super(container, alias); + constructor(container: Reactor | null) { + super(container); + this._bankIndex = -1 + if (container !== null) { + let index = Bank.initializationMap.get(container) + if (index !== undefined) { + this._bankIndex = index + } + } this._linkToRuntimeObject() this.shutdown = new Shutdown(this); @@ -862,7 +484,13 @@ export abstract class Reactor extends Component { // return this._active // } - protected writable(port: IOPort): ReadWrite { + // + + public allWritable(port: MultiPort): WritableMultiPort { + return port.asWritable(this._getKey(port)); + } + + public writable(port: IOPort): WritablePort { return port.asWritable(this._getKey(port)); } @@ -909,11 +537,12 @@ export abstract class Reactor extends Component { if (t instanceof Trigger) { t.getManager(this._getKey(t)).addReaction(reaction) } - + // Also record this trigger as a dependency. if (t instanceof IOPort) { this._dependencyGraph.addEdge(reaction, t) - //this._addDependency(t, reaction); + } else if (t instanceof MultiPort) { + t.channels().forEach(channel => this._dependencyGraph.addEdge(reaction, channel)) } else { Log.debug(this, () => ">>>>>>>> not a dependency: " + t); } @@ -926,23 +555,31 @@ export abstract class Reactor extends Component { if (a instanceof IOPort) { this._dependencyGraph.addEdge(reaction, a) sources.add(a) - } - if (a instanceof CalleePort) { + } else if (a instanceof MultiPort) { + a.channels().forEach(channel => { + this._dependencyGraph.addEdge(reaction, channel) + sources.add(channel) + }) + } else if (a instanceof CalleePort) { this._dependencyGraph.addEdge(a, reaction) - } - if (a instanceof CallerPort) { + } else if (a instanceof CallerPort) { this._dependencyGraph.addEdge(reaction, a) } // Only necessary if we want to add actions to the dependency graph. - if (a instanceof Action) { + else if (a instanceof Action) { // dep } - if (a instanceof SchedulableAction) { + else if (a instanceof SchedulableAction) { // antidep - } - if (a instanceof WritablePort) { + } else if (a instanceof WritablePort) { this._dependencyGraph.addEdge(a.getPort(), reaction) effects.add(a.getPort()) + } else if (a instanceof WritableMultiPort) { + a.getPorts().forEach(channel => { + this._dependencyGraph.addEdge(channel, reaction) + effects.add(channel) + }) + } } // Make effects dependent on sources. @@ -1087,7 +724,6 @@ export abstract class Reactor extends Component { let lastCaller = p.getManager(this._getKey(p)).getLastCaller() if (procedure && lastCaller) { let effects = this._dependencyGraph.getBackEdges(procedure) - //console.log(">>>>>>>>>>>> last caller:" + lastCaller) for (let e of effects) { if (!(e instanceof CalleePort)) { // Also add edge to the local graph. @@ -1250,6 +886,25 @@ protected _getFirstReactionOrMutation(): Reaction | undefined { return false; } + public canConnectCall + (src: CallerPort, dst: CalleePort) { + // FIXME: can we change the inheritance relationship so that we can overload? + + if (this._runtime.isRunning() == false) { + // console.log("Connecting before running") + // Validate connections between callers and callees. + + if (src._isContainedByContainerOf(this) && dst._isContainedByContainerOf(this)) { + return true + } + return false + + } else { + // FIXME + } + } + + /** * Returns true if a given source port can be connected to the * given destination port, false otherwise. Valid connections @@ -1266,8 +921,8 @@ protected _getFirstReactionOrMutation(): Reaction | undefined { * @param src The start point of a new connection. * @param dst The end point of a new connection. */ - public canConnect - (src: CallerPort | IOPort, dst: CalleePort | IOPort) { + public canConnect + (src: IOPort, dst: IOPort) { // Immediate rule out trivial self loops. if (src === dst) { return false @@ -1276,35 +931,24 @@ protected _getFirstReactionOrMutation(): Reaction | undefined { if (this._runtime.isRunning() == false) { // console.log("Connecting before running") // Validate connections between callers and callees. - if (src instanceof CalleePort) { - return false - } - if (src instanceof CallerPort) { - if (dst instanceof CalleePort && - src._isContainedByContainerOf(this) && dst._isContainedByContainerOf(this)) { - return true - } - return false - } // Additional checks for regular ports. - if (dst instanceof IOPort) { - console.log("IOPort") - // Rule out write conflicts. - // - (between reactors) - if (!(dst instanceof CalleePort) && - this._dependencyGraph.getBackEdges(dst).size > 0) { - return false; - } - // - between reactors and reactions (NOTE: check also needs to happen - // in addReaction) - var deps = this._dependencyGraph.getEdges(dst) // FIXME this will change with multiplex ports - if (deps != undefined && deps.size > 0) { - return false; - } + console.log("IOPort") + // Rule out write conflicts. + // - (between reactors) + if (this._dependencyGraph.getBackEdges(dst).size > 0) { + return false; + } - return this._isInScope(src, dst) + // - between reactors and reactions (NOTE: check also needs to happen + // in addReaction) + var deps = this._dependencyGraph.getEdges(dst) // FIXME this will change with multiplex ports + if (deps != undefined && deps.size > 0) { + return false; } + + return this._isInScope(src, dst) + } else { // Attempt to make a connection while executing. // Check the local dependency graph to figure out whether this change @@ -1318,7 +962,7 @@ protected _getFirstReactionOrMutation(): Reaction | undefined { for (let r of this._getOwnReactors()) { graph.merge(r._getCausalityInterface()) } - + // Add the new edge. graph.addEdge(dst, src) @@ -1376,6 +1020,26 @@ protected _getFirstReactionOrMutation(): Reaction | undefined { } } + /** + * Connect a source port to a downstream destination port without canConnect() check. + * This must be used with caution after checking canConnect for the given ports. + * @param src The source port to connect. + * @param dst The destination port to connect. + */ + private _uncheckedConnect(src: IOPort, dst:IOPort) { + Log.debug(this, () => "connecting " + src + " and " + dst); + // Add dependency implied by connection to local graph. + this._dependencyGraph.addEdge(dst, src); + // Register receiver for value propagation. + let writer = dst.asWritable(this._getKey(dst)); + src.getManager(this._getKey(src)).addReceiver + (writer as WritablePort); + let val = src.get() + if (this._runtime.isRunning() && val !== undefined) { + writer.set(val) + } + } + /** * Connect a source port to a downstream destination port. If a source is a * regular port, then the type variable of the source has to be a subtype of @@ -1388,60 +1052,112 @@ protected _getFirstReactionOrMutation(): Reaction | undefined { * @param src The source port to connect. * @param dst The destination port to connect. */ - protected _connect - (src: CallerPort | IOPort, dst: CalleePort | IOPort) { + protected _connect(src: IOPort, dst:IOPort) { + if (src === undefined || src === null) { + throw new Error("Cannot connect unspecified source"); + } + if (dst === undefined || dst === null) { + throw new Error("Cannot connect unspecified destination"); + } if (this.canConnect(src, dst)) { - Log.debug(this, () => "connecting " + src + " and " + dst); - if (src instanceof CallerPort && dst instanceof CalleePort) { - // Treat connections between callers and callees separately. - // Note that because A extends T and S extends R, we can safely - // cast CalleePort to CalleePort. - src.remotePort = ((dst as unknown) as CalleePort); - // Register the caller in the callee reactor so that it can - // establish dependencies on the callers. - let calleeManager = dst.getManager(this._getKey(dst)) - let callerManager = src.getManager(this._getKey(src)) - let container = callerManager.getContainer() - let callers = new Set>() - container._dependencyGraph.getBackEdges(src).forEach((dep) => { - if (dep instanceof Reaction) { - callers.add(dep) - } + this._uncheckedConnect(src, dst); + } else { + throw new Error("ERROR connecting " + src + " to " + dst); + } + } + + protected _connectMulti( + src: Array | IOPort>, + dest: Array | IOPort>, + repeatLeft: boolean) { + let leftPorts = new Array>(0) + let rightPorts = new Array>(0) + + // TODO(hokeun): Check if the multiport's container is Bank when Bank is implemented. + src.forEach(port => { + if (port instanceof MultiPort) { + port.channels().forEach(singlePort => { + leftPorts.push(singlePort) }) - let first = container._getFirst(callers) - let last = container._getLast(callers) - let lastCaller = calleeManager.getLastCaller() - if (lastCaller !== undefined) { - // This means the callee port is bound to a reaction and - // there may be zero or more callers. We now continue - // building a chain of callers. - if (first) { - this._dependencyGraph.addEdge(first, lastCaller) - } else { - this._dependencyGraph.addEdge(src, dst) - } - if (last) - calleeManager.setLastCaller(last) - } else { - throw new Error("No procedure linked to callee" - + " port `${procedure}`.") + } else if (port instanceof IOPort) { + leftPorts.push(port) + } + }) + + dest.forEach(port => { + if (port instanceof MultiPort) { + port.channels().forEach(singlePort => { + rightPorts.push(singlePort) + }) + } else if (port instanceof IOPort) { + rightPorts.push(port) + } + }) + + if (repeatLeft) { + const leftPortsSize = leftPorts.length + for (let i = 0; leftPorts.length < rightPorts.length; i++) { + leftPorts.push(leftPorts[i % leftPortsSize]) + } + } + + if (leftPorts.length < rightPorts.length) { + Log.warn(null, () => "There are more right ports than left ports. ", + "Not all ports will be connected!") + } else if (leftPorts.length > rightPorts.length) { + Log.warn(null, () => "There are more left ports than right ports. ", + "Not all ports will be connected!") + } + + + for (let i = 0; i < leftPorts.length && i < rightPorts.length; i++) { + if (!this.canConnect(leftPorts[i], rightPorts[i])) { + throw new Error("ERROR connecting " + leftPorts[i] + " to " + rightPorts[i] + + "in multiple connections from " + src + " to " + dest) + } + } + for (let i = 0; i < leftPorts.length && i < rightPorts.length; i++) { + this._uncheckedConnect(leftPorts[i], rightPorts[i]) + } + } + + protected _connectCall + (src: CallerPort, dst: CalleePort) { + if (this.canConnectCall(src, dst)) { + Log.debug(this, () => "connecting " + src + " and " + dst); + // Treat connections between callers and callees separately. + // Note that because A extends T and S extends R, we can safely + // cast CalleePort to CalleePort. + src.remotePort = ((dst as unknown) as CalleePort); + // Register the caller in the callee reactor so that it can + // establish dependencies on the callers. + let calleeManager = dst.getManager(this._getKey(dst)) + let callerManager = src.getManager(this._getKey(src)) + let container = callerManager.getContainer() + let callers = new Set>() + container._dependencyGraph.getBackEdges(src).forEach((dep) => { + if (dep instanceof Reaction) { + callers.add(dep) } - - } else if (src instanceof IOPort && dst instanceof IOPort) { - Log.debug(this, () => "connecting " + src + " and " + dst); - // Add dependency implied by connection to local graph. - this._dependencyGraph.addEdge(dst, src); - // Register receiver for value propagation. - let writer = dst.asWritable(this._getKey(dst)); - src.getManager(this._getKey(src)).addReceiver - (writer as WritablePort); - let val = src.get() - if (this._runtime.isRunning() && val !== undefined) { - //console.log(">>>>>>>>>>>>>>>>>>>>>>>>><<<<<>>>>>>>>>>>>>>>>>>>>>") - writer.set(val) + }) + let first = container._getFirst(callers) + let last = container._getLast(callers) + let lastCaller = calleeManager.getLastCaller() + if (lastCaller !== undefined) { + // This means the callee port is bound to a reaction and + // there may be zero or more callers. We now continue + // building a chain of callers. + if (first) { + this._dependencyGraph.addEdge(first, lastCaller) + } else { + this._dependencyGraph.addEdge(src, dst) } + if (last) + calleeManager.setLastCaller(last) + } else { + throw new Error("No procedure linked to callee" + + " port `${procedure}`.") } - } else { throw new Error("ERROR connecting " + src + " to " + dst); } @@ -1613,187 +1329,19 @@ protected _getFirstReactionOrMutation(): Reaction | undefined { } } -export abstract class Port extends Trigger implements Read { - - protected runtime!: Runtime; - - constructor(container: Reactor, alias?: string) { - super(container, alias) - this._linkToRuntimeObject() - } - - /** The time stamp associated with this port's value. */ - protected tag: Tag | undefined; - - /** The value associated with this port. */ - protected value: T | Absent; - - abstract get(): T | undefined; - - public _receiveRuntimeObject(runtime: Runtime) { - if (!this.runtime) { - this.runtime = runtime - } else { - throw new Error("Can only establish link to runtime once. Name: " + this._getFullyQualifiedName()) - } - } - - /** - * Returns true if the connected port's value has been set; false otherwise - */ - public isPresent() { - - Log.debug(this, () => "In isPresent()...") - Log.debug(this, () => "value: " + this.value); - Log.debug(this, () => "tag: " + this.tag); - Log.debug(this, () => "time: " + this.runtime.util.getCurrentLogicalTime()) - - if (this.value !== undefined - && this.tag !== undefined - && this.tag.isSimultaneousWith(this.runtime.util.getCurrentTag())) { - return true; - } else { - return false; - } - } -} - -export abstract class IOPort extends Port { - - protected receivers: Set> = new Set(); - - /** - * Return the value set to this port. Return `Absent` if the connected - * output did not have its value set at the current logical time. - */ - public get(): T | Absent { - if (this.isPresent()) { - return this.value; - } else { - return undefined; - } - } - - /** - * Only the holder of the key may obtain a writable port. - * @param key - */ - public asWritable(key: Symbol | undefined): WritablePort { - if (this._key === key) { - return this.writer - } - throw Error("Referenced port is out of scope: " + this._getFullyQualifiedName()) // FIXME: adjust messages for other methods as well - // FIXME: we could potentially do this for reads/triggers as well just for scope rule enforcement - } - - /** - * - * @param container Reference to the container of this port - * (or the container thereof). - */ - public getManager(key: Symbol | undefined): IOPortManager { - if (this._key == key) { - return this.manager - } - throw Error("Unable to grant access to manager.") - } - - /** - * Inner class instance to gain access to Write interface. - */ - protected writer = new class extends WritablePort { - constructor(private port:IOPort) { - super() - } - public set(value: T): void { - this.port.value = value; - this.port.tag = this.port.runtime.util.getCurrentTag(); - // Set values in downstream receivers. - this.port.receivers.forEach(p => p.set(value)) - //console.log("Set called. The number of reactions is: " + this.port.reactions.size) - // Stage triggered reactions for execution. - this.port.reactions.forEach(r => this.port.runtime.stage(r)) - } - public get(): T | Absent { - return this.port.get() - } - - public getPort(): IOPort { - return this.port - } - - public toString(): string { - return this.port.toString() - } - - }(this) - - /** - * Inner class instance to let the container configure this port. - */ - protected manager:IOPortManager = new class implements IOPortManager { - constructor(private port:IOPort) {} - getContainer(): Reactor { - return this.port._getContainer() - } - /** - * Add the given port to the list of receivers. If the connection was - * established at runtime and the upstream port already has a value, - * immediately propagate the value to the newly connected receiver. - * @param port A newly connected downstream port. - */ - addReceiver(port: WritablePort): void { - this.port.receivers.add(port) - if (this.port.runtime.isRunning()) { - let val = this.port.get() - if (val !== undefined) { - port.set(val) - } - } - } - delReceiver(port: WritablePort): void { - this.port.receivers.delete(port) - } - addReaction(reaction: Reaction): void { - this.port.reactions.add(reaction) - //console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - } - delReaction(reaction: Reaction): void { - this.port.reactions.delete(reaction) - } - }(this) - toString(): string { - return this._getFullyQualifiedName(); - } -} interface ComponentManager { getOwner(): Reactor; } -interface TriggerManager { - getContainer():Reactor; - addReaction(reaction: Reaction): void; - delReaction(reaction: Reaction): void; -} -interface IOPortManager extends TriggerManager { - addReceiver(port: WritablePort): void; - delReceiver(port: WritablePort): void; -} - -export class OutPort extends IOPort { - -} -export class InPort extends IOPort { -} /** * A caller port sends arguments of type T and receives a response of type R. @@ -1973,23 +1521,13 @@ class ReactionQueue extends PrioritySet { export interface Runtime { util:UtilityFunctions; - //core: CoreFunctions; stage(reaction: Reaction): void; initialize(timer: Timer): void; schedule(e: TaggedEvent): void; delete(r: Reactor): void; isRunning(): boolean; } - -// interface CoreFunctions { -// stage(reaction: Reaction): void; -// initialize(timer: Timer): void; -// schedule(e: TaggedEvent): void; -// mark(r: Reactor): void; -// isRunning(): boolean; -// } - -interface UtilityFunctions { // +interface UtilityFunctions { requestStop(): void; reportError(message?: string): void; requestErrorStop(message?: string): void; @@ -2003,6 +1541,7 @@ interface UtilityFunctions { // } export interface MutationSandbox extends ReactionSandbox { + connect (src: CallerPort | IOPort, dst: CalleePort | IOPort):void; @@ -2022,15 +1561,17 @@ export interface ReactionSandbox { * Collection of utility functions accessible from within a `react` function. */ util: UtilityFunctions - + getBankIndex: () => number } + export class App extends Reactor { readonly _alarm = new Alarm(); private _errored = false private _errorMessage?: string + readonly _uuid = uuidv4() /** * Set of reactions to stage when this app starts executing. @@ -2102,9 +1643,8 @@ export class App extends Reactor { }(this); - /** - * + * Inner class that provides access to the Runtime object. */ private __runtime: Runtime = new class implements Runtime { util: UtilityFunctions @@ -2170,6 +1710,7 @@ export class App extends Reactor { this.app._timersToSchedule.add(timer) } } + /** * Push an event onto the event queue. * @param e Tagged event to push onto the event queue. @@ -2197,7 +1738,7 @@ export class App extends Reactor { * Mark a reactor for deletion. At the end of logical time at which * this method was invoked the reactor will be removed from its * container. - * @param r + * @param r The reactor to be deleted. */ public delete(r: Reactor): void { this.app._reactorsToRemove.push(r) @@ -2289,6 +1830,8 @@ export class App extends Reactor { private snooze: Action; + readonly _name:string + /** * Create a new top-level reactor. * @param executionTimeout Optional parameter to let the execution of the app time out. @@ -2303,7 +1846,15 @@ export class App extends Reactor { public success: () => void = () => {}, public failure: () => void = () => {}) { super(null); - + + let name = this.constructor.name + if (name == "") { + name = "app" + } else { + name = name.charAt(0).toLowerCase() + name.slice(1) + } + this._name = name + // Update pointer to runtime object for this reactor and // its startup and shutdown action since the inner class // instance this.__runtime isn't initialized up until here. diff --git a/src/core/state.ts b/src/core/state.ts new file mode 100644 index 000000000..f6d8aa015 --- /dev/null +++ b/src/core/state.ts @@ -0,0 +1,33 @@ +import { Read, Write } from "./internal"; + +/** + * A state variable. This class refines the Read interface by letting `get` + * return T rather than T | Absent. If the state should be nullable or + * uninitialized, this has to be reflected explicitly in T. + */ + export class State implements Read, Write { + + /** + * Create a new state variable and assign it an initial value. + * @param value The initial value to assign to this state variable. + */ + constructor(private value: T) { + + } + + /** + * Return the current value of this state variable. + */ + get(): T { + return this.value; + }; + + /** + * Set the current value of this state variable. + * @param value + */ + set(value: T) { + this.value = value; + }; + +} \ No newline at end of file diff --git a/src/core/strings.ts b/src/core/strings.ts new file mode 100644 index 000000000..f82e14793 --- /dev/null +++ b/src/core/strings.ts @@ -0,0 +1,29 @@ + +/** + * Utility class for handling strings, for example, to format diagraph + * string representation. + */ +export class StringUtil { + /** + * Remove indentation in the multi-line string. + * @param template Multi-line string whose indentation should be removed. + * @returns String without indentation. + */ + public static dontIndent(template: TemplateStringsArray){ + return ('' + template.toString()).replace(/(\n)\s+/g, '$1'); + } + + public static toRegex(template: TemplateStringsArray, ...keys:any[]) { + return (function(...values:any[]) { + let dict = values[values.length - 1] || {}; + let result = [template[0]]; + keys.forEach(function(key, i) { + let value = Number.isInteger(key) ? values[key] : dict[key]; + result.push(value, template[i + 1]); + }); + return result.join('').replace(/(\n)\s+/g, '$1'); + }); + } + + +} diff --git a/src/core/trigger.ts b/src/core/trigger.ts new file mode 100644 index 000000000..4be8d4c1a --- /dev/null +++ b/src/core/trigger.ts @@ -0,0 +1,127 @@ +import { Component, TaggedEvent, Reaction, Tag } from "./internal"; + +import type { Reactor, Runtime, Absent, Present } from "./internal"; + +export interface TriggerManager { + getContainer(): Reactor; + addReaction(reaction: Reaction): void; + delReaction(reaction: Reaction): void; +} + +/** + * Abstract class for a trigger. A trigger may be an action, port, or timer. + */ + export abstract class Trigger extends Component { + + /** + * Reactions to trigger. + */ + protected reactions: Set> = new Set(); + + /** + * Request the manager of this trigger. The request will only be honored + * if the correct key is given. Each component has a unique symbol (a key) + * that is handed to the owner upon instantiation of the component. If the + * wrong key is supplied, return undefined. + * @param key The private key embedded in this trigger. + */ + abstract getManager(key: Symbol | undefined): TriggerManager; + + + /** + * Return whether or not this trigger is present. + */ + abstract isPresent(): boolean; + + public getContainer() { + return this._getContainer() + } + } + + +/** + * + */ + export abstract class ScheduledTrigger extends Trigger { + protected value: T | Absent = undefined; + protected tag: Tag | undefined; + + protected runtime!: Runtime; + + constructor(container: Reactor) { + super(container) + this._linkToRuntimeObject() + } + + /** + * Update the current value of this timer in accordance with the given + * event, and trigger any reactions that list this timer as their trigger. + * @param e Timestamped event. + */ + public update(e: TaggedEvent):void { + + if (!e.tag.isSimultaneousWith(this.runtime.util.getCurrentTag())) { + throw new Error("Time of event does not match current logical time."); + } + if (e.trigger === this) { + this.value = e.value + this.tag = e.tag; + for (let r of this.reactions) { + this.runtime.stage(r) + } + } else { + throw new Error("Attempt to update action using incompatible event."); + } + } + + public getManager(key: Symbol | undefined): TriggerManager { + if (this._key == key) { + return this.manager + } + throw Error("Unable to grant access to manager.") + } + + /** + * Returns true if this action was scheduled for the current + * logical time. This result is not affected by whether it + * has a value. + */ + public isPresent() { + if (this.tag === undefined) { + // This action has never been scheduled before. + return false; + } + if (this.tag.isSimultaneousWith(this.runtime.util.getCurrentTag())) { + return true; + } else { + return false; + } + } + + protected manager = new class implements TriggerManager { + constructor(private trigger: ScheduledTrigger) { } + getContainer(): Reactor { + return this.trigger._getContainer() + } + addReaction(reaction: Reaction): void { + this.trigger.reactions.add(reaction) + } + delReaction(reaction: Reaction): void { + this.trigger.reactions.delete(reaction) + } + }(this) + + public _receiveRuntimeObject(runtime: Runtime) { + if (!this.runtime) { + this.runtime = runtime + } else { + throw new Error("Can only establish link to runtime once.") + } + } + +} + +// FIXME(marten): move these to trigger.ts and let them extend trigger + + + diff --git a/src/core/types.ts b/src/core/types.ts new file mode 100644 index 000000000..03589b26f --- /dev/null +++ b/src/core/types.ts @@ -0,0 +1,109 @@ + +import { Tag, TimeValue, Trigger } from "./internal"; + +/** + * A variable can be read, written to, or scheduled. Variables may be passed to + * reactions in an argument list. + * @see Read + * @see Write + * @see Sched + */ + export type Variable = Read | MultiRead + +/** + * Interface for writable ports. + */ + export interface Write { + set: (value: T) => void; +} + +/** + * Type for simple variables that are both readable and writable. + */ + export type ReadWrite = Read & Write; + + export type MultiReadWrite = MultiRead & MultiWrite; + + /** + * Interface for readable variables. + */ +export interface Read { + get(): T | Absent; + } + + export interface MultiRead { + + /** + * Return the number of channels. + */ + width(): number + + /** + * Given an index that identifies a particular channel, return the current + * value of the identified channel. + * @param index the index that identifies the channel to return the value of + * @returns the value that corresponds to the identified channel + */ + get(index: number): T | Absent + } + + export interface MultiWrite { + /** + * Return the number of channels. + */ + width(): number + + set: (index: number, value: T) => void; + + values(): Array + } + + //--------------------------------------------------------------------------// +// Types // +//--------------------------------------------------------------------------// + +/** + * Type that denotes the absence of a value. + * @see Variable + */ +export type Absent = undefined; + +/** + * Conditional type for argument lists of reactions. If the type variable + * `T` is inferred to be a subtype of `Variable[]` it will yield `T`; it + * will yield `never` if `T` is not a subtype of `Variable[]`. + * @see Reaction + */ +export type ArgList = T extends Variable[] ? T : never; + +export type ParmList = T extends any[] ? T : never; + +/** + * Type for data exchanged between ports. + */ +export type Present = (number | bigint | string | boolean | symbol | object | null); + + +export class Args { + tuple: T; + constructor(...args: T) { + this.tuple = args; + } +} + +export class Triggers { + list: Trigger[]; + constructor(trigger: Trigger, ...triggers: Trigger[]) { + this.list = triggers.concat(trigger) + } +} + + +/** + * Interface for schedulable actions. + */ + export interface Sched extends Read { + schedule: (extraDelay: TimeValue | 0, value: T, intendedTag?: Tag) => void; + // FIXME: it makes sense to be able to check the presence of a (re)schedulable action. +} + diff --git a/src/core/util.ts b/src/core/util.ts index a2843bc51..12deeb457 100644 --- a/src/core/util.ts +++ b/src/core/util.ts @@ -328,6 +328,9 @@ export class DependencyGraph { function printChain(node: T, chain: Array) { dot += "\n"; dot += '"' + node + '"' + if ((node as Object).toString() == "[object Object]") { + console.error("Encountered node with no toString() implementation: " + (node as Object).constructor) + } while (chain.length > 0) { dot += "->" + '"' + chain.pop() + '"'; } diff --git a/src/share/Logger.ts b/src/share/Logger.ts index 06ea2604c..8703b5f84 100644 --- a/src/share/Logger.ts +++ b/src/share/Logger.ts @@ -1,4 +1,5 @@ -import {Reactor, InPort, Read, Triggers, Args, State, Present, ReactionSandbox} from '../core/reactor'; +import {Reactor, Read, Triggers, Args, Present, ReactionSandbox, InPort,State} from '../core/internal'; + function print(this:ReactionSandbox, i: Read, expected: State) { const received = i.get(); diff --git a/src/share/SingleEvent.ts b/src/share/SingleEvent.ts index ab05659e6..89349389b 100644 --- a/src/share/SingleEvent.ts +++ b/src/share/SingleEvent.ts @@ -1,5 +1,5 @@ -import {Reactor, OutPort, Timer, Write, Triggers, Args, ArgList, ReactionSandbox, Present, State, Parameter, Variable} from '../core/reactor'; +import {Reactor, Timer, Write, Triggers, Args, ArgList, ReactionSandbox, Present, Parameter, OutPort} from '../core/internal'; function produceOutput(this: ReactionSandbox, o: Write, payload:Parameter) { o.set(payload.get());