diff --git a/js/examples/smoketest/index.html b/js/examples/smoketest/index.html
new file mode 100644
index 0000000..f5a020e
--- /dev/null
+++ b/js/examples/smoketest/index.html
@@ -0,0 +1,13 @@
+
+
+
+ Syndicate: Smoketest
+
+
+
+
+
+
+ Smoketest
+
+
diff --git a/js/examples/smoketest/index.js b/js/examples/smoketest/index.js
new file mode 100644
index 0000000..8c9dcc5
--- /dev/null
+++ b/js/examples/smoketest/index.js
@@ -0,0 +1,31 @@
+var G;
+$(document).ready(function () {
+ var Network = Syndicate.Network;
+ var sub = Syndicate.sub;
+ var __ = Syndicate.__;
+ var _$ = Syndicate._$;
+
+ G = new Syndicate.Ground(function () {
+ console.log('starting ground boot');
+
+ Network.spawn({
+ counter: 0,
+ boot: function () {},
+ handleEvent: function (e) {},
+ step: function () {
+ Network.send(["beep", this.counter++]);
+ return this.counter <= 10;
+ }
+ });
+
+ Network.spawn({
+ boot: function () { return sub(["beep", __]); },
+ handleEvent: function (e) {
+ if (e.type === 'message') {
+ console.log("beep!", e.message[1]);
+ }
+ }
+ });
+ });
+ G.startStepping();
+});
diff --git a/js/src/ground.js b/js/src/ground.js
new file mode 100644
index 0000000..c5e5d41
--- /dev/null
+++ b/js/src/ground.js
@@ -0,0 +1,77 @@
+var Immutable = require('immutable');
+var Syndicate = require('./syndicate.js');
+var Network = Syndicate.Network;
+
+function Ground(bootFn) {
+ var self = this;
+ this.stepperId = null;
+ this.baseStack = Immutable.List.of({ network: this, activePid: -1 });
+ Network.withNetworkStack(this.baseStack, function () {
+ self.network = new Network(bootFn);
+ });
+}
+
+Ground.prototype.step = function () {
+ var self = this;
+ return Network.withNetworkStack(this.baseStack, function () {
+ return self.network.step();
+ });
+};
+
+Ground.prototype.checkPid = function (pid) {
+ if (pid !== -1) console.error('Weird pid in Ground markPidRunnable', pid);
+};
+
+Ground.prototype.markPidRunnable = function (pid) {
+ this.checkPid(pid);
+ this.startStepping();
+};
+
+Ground.prototype.startStepping = function () {
+ var self = this;
+ if (this.stepperId) return;
+ if (this.step()) {
+ this.stepperId = setTimeout(function () {
+ self.stepperId = null;
+ self.startStepping();
+ }, 0);
+ }
+};
+
+Ground.prototype.stopStepping = function () {
+ if (this.stepperId) {
+ clearTimeout(this.stepperId);
+ this.stepperId = null;
+ }
+};
+
+Ground.prototype.kill = function (pid, exn) {
+ this.checkPid(pid);
+ console.log("Ground network terminated");
+ this.stopStepping();
+};
+
+Ground.prototype.enqueueAction = function (pid, action) {
+ this.checkPid(pid);
+
+ switch (action.type) {
+ case 'stateChange':
+ if (action.patch.isNonEmpty()) {
+ console.error('You have subscribed to a nonexistent event source.',
+ action.patch.pretty());
+ }
+ break;
+
+ case 'message':
+ console.error('You have sent a message into the outer void.', action);
+ break;
+
+ default:
+ console.error('Internal error: unexpected action at ground level', action);
+ break;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+module.exports.Ground = Ground;
diff --git a/js/src/main.js b/js/src/main.js
index b7a05c2..fd5bd3d 100644
--- a/js/src/main.js
+++ b/js/src/main.js
@@ -26,12 +26,11 @@ copyKeys(['emptyPatch',
'observe', 'atMeta', 'advertise',
'isObserve', 'isAtMeta', 'isAdvertise',
'assert', 'retract', 'sub', 'unsub', 'pub', 'unpub',
- 'patchSeq',
- 'prettyPatch'],
+ 'patchSeq'],
module.exports,
module.exports.Patch);
-// module.exports.Ground = require("./ground.js").Ground;
+module.exports.Ground = require("./ground.js").Ground;
// module.exports.Actor = require("./actor.js").Actor;
// module.exports.Spy = require("./spy.js").Spy;
// module.exports.WakeDetector = require("./wake-detector.js").WakeDetector;
diff --git a/js/src/mux.js b/js/src/mux.js
index f244845..6247235 100644
--- a/js/src/mux.js
+++ b/js/src/mux.js
@@ -87,7 +87,7 @@ function computeAffectedPids(routingTable, delta) {
Mux.prototype.routeMessage = function (body) {
if (Route.matchValue(this.routingTable, body) === null) {
- return Route.matchValue(m.routingTable, Patch.observe(body)) || Immutable.Set();
+ return Route.matchValue(this.routingTable, Patch.observe(body)) || Immutable.Set();
} else {
// Some other stream has declared body
return Immutable.Set();
diff --git a/js/src/patch.js b/js/src/patch.js
index 7077d3f..1a3b838 100644
--- a/js/src/patch.js
+++ b/js/src/patch.js
@@ -206,9 +206,9 @@ Patch.prototype.projectObjects = function (compiledProjection) {
Route.projectObjects(this.removed, compiledProjection)];
};
-function prettyPatch(p) {
- return ("<<<<<<<< Removed:\n" + Route.prettyTrie(p.removed) +
- "======== Added:\n" + Route.prettyTrie(p.added) +
+Patch.prototype.pretty = function () {
+ return ("<<<<<<<< Removed:\n" + Route.prettyTrie(this.removed) +
+ "======== Added:\n" + Route.prettyTrie(this.added) +
">>>>>>>>\n");
}
@@ -240,4 +240,3 @@ module.exports.unpub = unpub;
module.exports.patchSeq = patchSeq;
module.exports.computePatch = computePatch;
module.exports.biasedIntersection = biasedIntersection;
-module.exports.prettyPatch = prettyPatch;
diff --git a/js/src/syndicate.js b/js/src/syndicate.js
index a64c392..9939bc0 100644
--- a/js/src/syndicate.js
+++ b/js/src/syndicate.js
@@ -101,37 +101,43 @@ Network.exit = function (exn) {
Network.current().kill(Network.activePid(), exn);
};
-Network.terminateNetwork = function () {
+Network.exitNetwork = function () {
Network.enqueueAction(terminateNetwork());
};
+Network.inertBehavior = {
+ handleEvent: function (e) {}
+};
+
// Instance methods
Network.prototype.asChild = function (pid, f, omitLivenessCheck) {
+ var self = this;
var p = this.processTable.get(pid, null);
if (!omitLivenessCheck && (p === null)) {
console.warn("Network.asChild eliding invocation of dead process", pid);
return;
}
- return Network.withWorldStack(Network.stack.push({ network: this, activePid: pid }),
- function () {
- try {
- return f(p);
- } catch (e) {
- this.kill(pid, e);
- }
- });
+ return Network.withNetworkStack(
+ Network.stack.push({ network: this, activePid: pid }),
+ function () {
+ try {
+ return f(p);
+ } catch (e) {
+ self.kill(pid, e);
+ }
+ });
};
Network.prototype.kill = function (pid, exn) {
if (exn && exn.stack) {
- console.log("Process exited", pid, exn, exn.stack);
+ console.log("Process exiting", pid, exn, exn.stack);
} else {
- console.log("Process exited", pid, exn);
+ console.log("Process exiting", pid, exn);
}
var p = this.processTable.get(pid);
- this.processTable = this.processTable.remove(pid);
+ this.processTable = this.processTable.set(pid, { behavior: Network.inertBehavior });
if (p) {
if (p.behavior.trapexit) {
this.asChild(pid, function () { return p.behavior.trapexit(exn); }, true);
@@ -172,13 +178,14 @@ Network.prototype.enqueueAction = function (pid, action) {
};
Network.prototype.dispatchActions = function () {
+ var self = this;
var actionQueue = this.pendingActions;
this.pendingActions = Immutable.List();
var alive = true;
actionQueue.forEach(function (entry) {
var pid = entry[0];
var action = entry[1];
- if (!this.interpretAction(pid, action)) {
+ if (!self.interpretAction(pid, action)) {
alive = false;
return false;
}
@@ -191,19 +198,22 @@ Network.prototype.markRunnable = function (pid) {
};
Network.prototype.runRunnablePids = function () {
+ var self = this;
var pidSet = this.runnablePids;
this.runnablePids = Immutable.Set();
pidSet.forEach(function (pid) {
- var childBusy = this.asChild(pid, function (p) {
+ var childBusy = self.asChild(pid, function (p) {
return p.behavior.step // exists, haven't called it yet
&& p.behavior.step();
});
- if (childBusy) this.markRunnable(pid);
+ if (childBusy) self.markRunnable(pid);
});
return true;
};
Network.prototype.interpretAction = function (pid, action) {
+ var self = this;
+
switch (action.type) {
case 'stateChange':
var oldMux = this.mux.shallowCopy();
@@ -218,7 +228,7 @@ Network.prototype.interpretAction = function (pid, action) {
Network.send(action.message[1]);
} else {
this.mux.routeMessage(action.message).forEach(function (pid) {
- this.deliverEvent(pid, action);
+ self.deliverEvent(pid, action);
});
}
return true;
@@ -240,6 +250,8 @@ Network.prototype.interpretAction = function (pid, action) {
case 'terminate':
var oldMux = this.mux.shallowCopy();
this.deliverPatches(oldMux, this.mux.removeStream(pid));
+ console.log("Process exit complete", pid);
+ this.processTable = this.processTable.remove(pid);
return true;
case 'terminateNetwork':
@@ -254,15 +266,16 @@ Network.prototype.interpretAction = function (pid, action) {
};
Network.prototype.deliverPatches = function (oldMux, updateStreamResult) {
+ var self = this;
var events = Mux.computeEvents(oldMux, this.mux, updateStreamResult);
events.eventMap.forEach(function (patch, pid) {
- this.deliverEvent(pid, stateChange(patch));
+ self.deliverEvent(pid, stateChange(patch));
});
events.metaEvents.forEach(Network.stateChange);
};
Network.prototype.deliverEvent = function (pid, event) {
- var childBusy = this.asChild(pid, function (p) { return p.handleEvent(event); });
+ var childBusy = this.asChild(pid, function (p) { return p.behavior.handleEvent(event); });
if (childBusy) this.markRunnable(pid);
};
diff --git a/js/test/test-mux.js b/js/test/test-mux.js
index f269a38..661f0b5 100644
--- a/js/test/test-mux.js
+++ b/js/test/test-mux.js
@@ -13,7 +13,7 @@ function checkPrettyTrie(m, expected) {
}
function checkPrettyPatch(p, expectedAdded, expectedRemoved) {
- expect(Patch.prettyPatch(p)).to.equal(
+ expect(p.pretty()).to.equal(
('<<<<<<<< Removed:\n' + expectedRemoved.join('\n') +
'======== Added:\n' + expectedAdded.join('\n') +
'>>>>>>>>\n'));
diff --git a/js/test/test-patch.js b/js/test/test-patch.js
index bf0f2c2..ec083a1 100644
--- a/js/test/test-patch.js
+++ b/js/test/test-patch.js
@@ -8,7 +8,7 @@ var __ = Route.__;
var _$ = Route._$;
function checkPrettyPatch(p, expectedAdded, expectedRemoved) {
- expect(Patch.prettyPatch(p)).to.equal(
+ expect(p.pretty()).to.equal(
('<<<<<<<< Removed:\n' + expectedRemoved.join('\n') +
'======== Added:\n' + expectedAdded.join('\n') +
'>>>>>>>>\n'));