From 5621685052685f8980da3ccbe5a00c11f11be8d9 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 30 Aug 2014 14:51:42 -0700 Subject: [PATCH] Make boot() return (optional) initialGestalts instead of having a separate argument to spawn(). Fixes failing test case for initial actor route signalling. --- examples/chat-raw/index.js | 4 +-- examples/dom-raw/index.js | 11 +++++--- examples/multidom/index.js | 13 +++++---- examples/smoketest-webworker/index.js | 5 +++- src/actor.js | 14 +++++++--- src/dom-driver.js | 13 +++++---- src/jquery-driver.js | 9 ++++--- src/minimart.js | 34 +++++++++++++++--------- src/spy.js | 2 +- src/wake-detector.js | 2 +- test/test-actor.js | 38 +++++++++++++++++---------- test/test-route.js | 7 +++++ 12 files changed, 99 insertions(+), 53 deletions(-) diff --git a/examples/chat-raw/index.js b/examples/chat-raw/index.js index 997ff1c..9daeaa2 100644 --- a/examples/chat-raw/index.js +++ b/examples/chat-raw/index.js @@ -76,7 +76,7 @@ $(document).ready(function () { // Monitor connection, notifying connectivity changes state: "crashed", // start with this to avoid spurious initial message print boot: function () { - World.updateRoutes([sub(["broker_state", __], 0, 1)]); + return [sub(["broker_state", __], 0, 1)]; }, handleEvent: function (e) { if (e.type === "routes") { @@ -94,7 +94,7 @@ $(document).ready(function () { World.spawn({ // Actual chat functionality boot: function () { - World.updateRoutes(this.subscriptions()); + return this.subscriptions(); }, nym: function () { return $("#nym").val(); }, currentStatus: function () { return $("#status").val(); }, diff --git a/examples/dom-raw/index.js b/examples/dom-raw/index.js index a797a34..1ddddd1 100644 --- a/examples/dom-raw/index.js +++ b/examples/dom-raw/index.js @@ -13,15 +13,18 @@ $(document).ready(function () { Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy"); World.spawn({ + boot: function () { + return [pub(["DOM", "#clicker-holder", "clicker", + ["button", ["span", [["style", "font-style: italic"]], "Click me!"]]]), + pub("bump_count"), + sub(["jQuery", "button.clicker", "click", __])]; + }, handleEvent: function (e) { if (e.type === "message" && e.message[0] === "jQuery") { World.send("bump_count"); } } - }, [pub(["DOM", "#clicker-holder", "clicker", - ["button", ["span", [["style", "font-style: italic"]], "Click me!"]]]), - pub("bump_count"), - sub(["jQuery", "button.clicker", "click", __])]); + }); World.spawn({ counter: 0, diff --git a/examples/multidom/index.js b/examples/multidom/index.js index e1fc398..e49cf5e 100644 --- a/examples/multidom/index.js +++ b/examples/multidom/index.js @@ -29,6 +29,13 @@ $(document).ready(function () { Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy"); // local World.spawn({ + boot: function () { + return [pub(domWrap("#clicker-holder", localId + "-clicker", + ["button", ["span", [["style", "font-style: italic"]], + "Click me! (" + localId + ")"]])), + pub("bump_count"), + sub(jQueryWrap("button."+localId+"-clicker", "click", __))]; + }, handleEvent: function (e) { console.log(JSON.stringify(e)); if (e.type === "message" @@ -40,11 +47,7 @@ $(document).ready(function () { World.send("bump_count"); } } - }, [pub(domWrap("#clicker-holder", localId + "-clicker", - ["button", ["span", [["style", "font-style: italic"]], - "Click me! (" + localId + ")"]])), - pub("bump_count"), - sub(jQueryWrap("button."+localId+"-clicker", "click", __))]); + }); World.spawn({ counter: 0, diff --git a/examples/smoketest-webworker/index.js b/examples/smoketest-webworker/index.js index 5ec3112..639782d 100644 --- a/examples/smoketest-webworker/index.js +++ b/examples/smoketest-webworker/index.js @@ -12,6 +12,9 @@ $(document).ready(function () { World.spawn({ name: 'GestaltDisplay', + boot: function () { + return [sub(__, 0, 10), pub(__, 0, 10)]; + }, handleEvent: function (e) { if (e.type === "routes") { var gd = document.getElementById('gestalt-display'); @@ -21,7 +24,7 @@ $(document).ready(function () { gd.appendChild(t); } } - }, [sub(__, 0, 10), pub(__, 0, 10)]); + }); World.spawn(new Actor(function () { this.counter = 0; diff --git a/src/actor.js b/src/actor.js index e55d239..975cd20 100644 --- a/src/actor.js +++ b/src/actor.js @@ -13,8 +13,9 @@ function Actor(bootfn) { try { Actor._chunks = []; bootfn.call(this); - finalizeActor(this, Actor._chunks); + var initialGestalt = finalizeActor(this, Actor._chunks); Actor._chunks = oldChunks; + return [initialGestalt]; } catch (e) { Actor._chunks = oldChunks; throw e; @@ -123,7 +124,7 @@ function finalizeActor(behavior, chunks) { var compiledProjections = {}; var previousObjs = {}; - behavior.updateRoutes = function () { + behavior._computeRoutes = function () { var newRoutes = Route.emptyGestalt; for (var i = 0; i < chunks.length; i++) { var chunk = chunks[i]; @@ -159,7 +160,11 @@ function finalizeActor(behavior, chunks) { } } } - World.updateRoutes([newRoutes]); + return newRoutes; + }; + + behavior.updateRoutes = function () { + World.updateRoutes([this._computeRoutes()]); }; behavior.handleEvent = function (e) { @@ -254,7 +259,8 @@ function finalizeActor(behavior, chunks) { if (chunk.options.removed) { behavior[chunk.options.removed] = []; } } } - behavior.updateRoutes(); + + return behavior._computeRoutes(); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/dom-driver.js b/src/dom-driver.js index 6bfe77d..aa02b8d 100644 --- a/src/dom-driver.js +++ b/src/dom-driver.js @@ -17,9 +17,7 @@ function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) { fragmentClass, fragmentSpec, domWrapFunction, - jQueryWrapFunction), - [sub(domWrapFunction(selector, fragmentClass, fragmentSpec)), - sub(domWrapFunction(selector, fragmentClass, fragmentSpec), 0, 1)]); + jQueryWrapFunction)); }; World.spawn(d); } @@ -40,12 +38,14 @@ function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQu DOMFragment.prototype.boot = function () { var self = this; var monitoring = - sub(this.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec), 1, 2); + sub(self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec), 1, 2); + World.spawn(new World(function () { Minimart.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass, 1, self.jQueryWrapFunction); World.spawn({ + boot: function () { return [monitoring] }, handleEvent: function (e) { if (e.type === "routes") { var level = e.gestalt.getLevel(1, 0); // find participant peers @@ -54,8 +54,11 @@ DOMFragment.prototype.boot = function () { } } } - }, [monitoring]); + }); })); + + return [sub(self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec)), + sub(self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec), 0, 1)] }; DOMFragment.prototype.handleEvent = function (e) { diff --git a/src/jquery-driver.js b/src/jquery-driver.js index 17731cd..55dad8e 100644 --- a/src/jquery-driver.js +++ b/src/jquery-driver.js @@ -18,9 +18,7 @@ function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) { selector, eventName, metaLevel, - wrapFunction), - [pub(wrapFunction(selector, eventName, __), metaLevel), - pub(wrapFunction(selector, eventName, __), metaLevel, 1)]); + wrapFunction)); }; World.spawn(d); } @@ -47,6 +45,11 @@ function JQueryEventRouter(baseSelector, selector, eventName, metaLevel, wrapFun this.handler); } +JQueryEventRouter.prototype.boot = function () { + return [pub(this.wrapFunction(this.selector, this.eventName, __), this.metaLevel), + pub(this.wrapFunction(this.selector, this.eventName, __), this.metaLevel, 1)]; +}; + JQueryEventRouter.prototype.handleEvent = function (e) { if (e.type === "routes" && e.gestalt.isEmpty()) { this.computeNodes().off(this.eventName, this.handler); diff --git a/src/minimart.js b/src/minimart.js index 973349e..8292a29 100644 --- a/src/minimart.js +++ b/src/minimart.js @@ -19,10 +19,8 @@ function pub(pattern, metaLevel, level) { return Route.simpleGestalt(true, pattern, metaLevel, level); } -function spawn(behavior, initialGestalts) { - return { type: "spawn", - behavior: behavior, - initialGestalt: Route.gestaltUnion(initialGestalts || []) }; +function spawn(behavior) { + return { type: "spawn", behavior: behavior }; } function updateRoutes(gestalts) { @@ -85,8 +83,8 @@ World.updateRoutes = function (gestalts) { World.current().enqueueAction(World.activePid(), updateRoutes(gestalts)); }; -World.spawn = function (behavior, initialGestalts) { - World.current().enqueueAction(World.activePid(), spawn(behavior, initialGestalts)); +World.spawn = function (behavior) { + World.current().enqueueAction(World.activePid(), spawn(behavior)); }; World.exit = function (exn) { @@ -226,13 +224,18 @@ World.prototype.performAction = function (pid, action) { switch (action.type) { case "spawn": var pid = World.nextPid++; - var newGestalt = action.initialGestalt.label(pid); - this.processTable[pid] = { gestalt: newGestalt, behavior: action.behavior }; - if (action.behavior.boot) { - this.asChild(pid, function () { action.behavior.boot() }); + var entry = { gestalt: Route.emptyGestalt, behavior: action.behavior }; + this.processTable[pid] = entry; + if (entry.behavior.boot) { + var initialGestalts = this.asChild(pid, function () { return entry.behavior.boot() }); + if (initialGestalts) { + entry.gestalt = Route.gestaltUnion(initialGestalts).label(pid); + } this.markPidRunnable(pid); } - this.applyAndIssueRoutingUpdate(Route.emptyGestalt, newGestalt, pid); + if (!Route.emptyGestalt.equals(entry.gestalt)) { + this.applyAndIssueRoutingUpdate(Route.emptyGestalt, entry.gestalt, pid); + } break; case "routes": if (pid in this.processTable) { @@ -315,6 +318,11 @@ World.prototype.dispatchEvent = function (e) { } }; +World.prototype.boot = function () { + // Needed in order for the new World to be marked as "runnable", so + // its initial actions get performed. +}; + World.prototype.handleEvent = function (e) { switch (e.type) { case "routes": @@ -436,8 +444,8 @@ DemandMatcher.prototype.debugState = function () { DemandMatcher.prototype.boot = function () { var observerLevel = 1 + Math.max(this.demandLevel, this.supplyLevel); - World.updateRoutes([sub(this.demandPattern, this.metaLevel, observerLevel), - pub(this.supplyPattern, this.metaLevel, observerLevel)]); + return [sub(this.demandPattern, this.metaLevel, observerLevel), + pub(this.supplyPattern, this.metaLevel, observerLevel)]; }; DemandMatcher.prototype.handleEvent = function (e) { diff --git a/src/spy.js b/src/spy.js index a1aca3c..6d14b8d 100644 --- a/src/spy.js +++ b/src/spy.js @@ -12,7 +12,7 @@ function Spy(label, useJson, observationLevel) { } Spy.prototype.boot = function () { - World.updateRoutes([sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)]); + return [sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)]; }; Spy.prototype.handleEvent = function (e) { diff --git a/src/wake-detector.js b/src/wake-detector.js index 4cea772..14500fb 100644 --- a/src/wake-detector.js +++ b/src/wake-detector.js @@ -17,8 +17,8 @@ function WakeDetector(period) { WakeDetector.prototype.boot = function () { var self = this; - World.updateRoutes([pub(this.message)]); this.timerId = setInterval(World.wrap(function () { self.trigger(); }), this.period); + return [pub(this.message)]; }; WakeDetector.prototype.handleEvent = function (e) {}; diff --git a/test/test-actor.js b/test/test-actor.js index 1f45794..32839ff 100644 --- a/test/test-actor.js +++ b/test/test-actor.js @@ -47,10 +47,11 @@ describe("configurationTrace", function() { it("should yield an appropriate trace", function () { checkTrace(function (trace) { World.spawn({ + boot: function () { return [sub(__)] }, handleEvent: function (e) { trace(e); } - }, [sub(__)]); + }); World.send(123); World.send(234); }, [Minimart.updateRoutes([]), @@ -64,9 +65,15 @@ describe("nonempty initial routes", function () { it("should be immediately signalled to the process", function () { // Specifically, no Minimart.updateRoutes([]) first. checkTrace(function (trace) { - World.spawn({ handleEvent: function (e) { - World.spawn({ handleEvent: trace }, [sub(["A", __], 0, 1)]) - }}, [pub(["A", __])]); + World.spawn({ + boot: function () { return [pub(["A", __])] }, + handleEvent: function (e) { + World.spawn({ + boot: function () { return [sub(["A", __], 0, 1)] }, + handleEvent: trace + }); + } + }); }, [Minimart.updateRoutes([pub(["A", __]).label(1)])]); }); }); @@ -74,16 +81,19 @@ describe("nonempty initial routes", function () { describe("actor with nonempty initial routes", function () { it("shouldn't see initial empty conversational context", function () { checkTrace(function (trace) { - World.spawn({ handleEvent: function (e) { - World.spawn(new Actor(function () { - Actor.observeAdvertisers( - function () { return ["A", __] }, - { presence: "isPresent" }, - function () { - trace(["isPresent", this.isPresent]); - }); - })); - }}, [pub(["A", __])]); + World.spawn({ + boot: function () { return [pub(["A", __])] }, + handleEvent: function (e) { + World.spawn(new Actor(function () { + Actor.observeAdvertisers( + function () { return ["A", __] }, + { presence: "isPresent" }, + function () { + trace(["isPresent", this.isPresent]); + }); + })); + } + }); }, [["isPresent", true]]); }); }); diff --git a/test/test-route.js b/test/test-route.js index 4230e3e..39a6f42 100644 --- a/test/test-route.js +++ b/test/test-route.js @@ -297,6 +297,13 @@ describe("matcher equality", function () { }); describe("gestalt equality", function () { + it("should distinguish emptyGestalt reliably", function () { + expect(r.simpleGestalt(false, r.__, 0, 10) + .union(r.simpleGestalt(true, r.__, 0, 10)) + .equals(r.emptyGestalt)) + .to.be(false); + }); + it("should not rely on object identity", function () { expect(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0)) .equals(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))))