Adapt marketplace implementation to fastrouting

This commit is contained in:
Tony Garnock-Jones 2014-05-26 14:36:57 -04:00
parent 00b92fdf1c
commit 03773b6d6d
8 changed files with 266 additions and 448 deletions

View File

@ -1,11 +1,11 @@
// DOM fragment display driver
function spawnDOMDriver() {
var d = new DemandMatcher(["DOM", __, __, __], 0, {demandSideIsSubscription: false});
d.onDemandIncrease = function (r) {
var selector = r.pattern[1];
var fragmentClass = r.pattern[2];
var fragmentSpec = r.pattern[3];
var d = new DemandMatcher(["DOM", _$, _$, _$], 0, {demandSideIsSubscription: false});
d.onDemandIncrease = function (captures) {
var selector = captures[0];
var fragmentClass = captures[1];
var fragmentSpec = captures[2];
World.spawn(new DOMFragment(selector, fragmentClass, fragmentSpec),
[sub(["DOM", selector, fragmentClass, fragmentSpec]),
sub(["DOM", selector, fragmentClass, fragmentSpec], 0, 1)]);
@ -28,11 +28,8 @@ DOMFragment.prototype.boot = function () {
World.spawn({
handleEvent: function (e) {
if (e.type === "routes") {
var needed = false;
for (var i = 0; i < e.routes.length; i++) {
needed = needed || (e.routes[i].level === 0); // find participant peers
}
if (e.routes.length > 0 && !needed) {
var level = e.gestalt.getLevel(1, 0); // find participant peers
if (!e.gestalt.isEmpty() && level.isEmpty()) {
World.shutdownWorld();
}
}
@ -42,7 +39,7 @@ DOMFragment.prototype.boot = function () {
};
DOMFragment.prototype.handleEvent = function (e) {
if (e.type === "routes" && e.routes.length === 0) {
if (e.type === "routes" && e.gestalt.isEmpty()) {
for (var i = 0; i < this.nodes.length; i++) {
var n = this.nodes[i];
n.parentNode.removeChild(n);

View File

@ -10,6 +10,7 @@
<!-- <script src="../../third-party/bootstrap/js/bootstrap.min.js"></script> -->
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../route.js"></script>
<script src="../../marketplace.js"></script>
<script src="../../spy.js"></script>
<script src="../../dom-driver.js"></script>

View File

@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<title>JS Marketplace: Smoketest</title>
<meta charset="utf-8">
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../route.js"></script>
<script src="../../marketplace.js"></script>
<script src="../../spy.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>Smoketest</h1>
</body>
</html>

View File

@ -0,0 +1,24 @@
var G;
$(document).ready(function () {
G = new Ground(function () {
console.log('starting ground boot');
World.spawn(new Spy("GROUND", true));
World.spawn({
counter: 0,
handleEvent: function (e) {},
step: function () {
World.send(["beep", this.counter++]);
return this.counter <= 10;
}
}, [pub(["beep", __])]);
World.spawn({
handleEvent: function (e) {
if (e.type === "message" && e.message[0] === "beep") {
console.log("beep!", e.message[1]);
}
}
}, [sub(["beep", __])]);
});
G.startStepping();
});

10
jquery-driver.js vendored
View File

@ -2,10 +2,10 @@
function spawnJQueryDriver(baseSelector, metaLevel) {
metaLevel = metaLevel || 0;
var d = new DemandMatcher(["jQuery", __, __, __], metaLevel);
d.onDemandIncrease = function (r) {
var selector = r.pattern[1];
var eventName = r.pattern[2];
var d = new DemandMatcher(["jQuery", _$, _$, __], metaLevel);
d.onDemandIncrease = function (captures) {
var selector = captures[0];
var eventName = captures[1];
World.spawn(new JQueryEventRouter(baseSelector, selector, eventName, metaLevel),
[pub(["jQuery", selector, eventName, __], metaLevel),
pub(["jQuery", selector, eventName, __], metaLevel, 1)]);
@ -31,7 +31,7 @@ function JQueryEventRouter(baseSelector, selector, eventName, metaLevel) {
}
JQueryEventRouter.prototype.handleEvent = function (e) {
if (e.type === "routes" && e.routes.length === 0) {
if (e.type === "routes" && e.gestalt.isEmpty()) {
this.computeNodes().off(this.eventName, this.handler);
World.exit();
}

View File

@ -1,121 +1,34 @@
/*---------------------------------------------------------------------------*/
/* Unification */
var __ = new Object(); /* wildcard marker */
__.__ = "__";
function unificationFailed() {
throw {unificationFailed: true};
}
function unify1(a, b) {
var i;
if (a === __) return b;
if (b === __) return a;
if (a === b) return a;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) unificationFailed();
var result = new Array(a.length);
for (i = 0; i < a.length; i++) {
result[i] = unify1(a[i], b[i]);
}
return result;
}
if (typeof a === "object" && typeof b === "object") {
/* TODO: consider other kinds of matching. I've chosen to
require any field mentioned by either side to be present in
both. Does that make sense? */
var result = ({});
for (i in a) { if (a.hasOwnProperty(i)) result[i] = true; }
for (i in b) { if (b.hasOwnProperty(i)) result[i] = true; }
for (i in result) {
if (result.hasOwnProperty(i)) {
result[i] = unify1(a[i], b[i]);
}
}
return result;
}
unificationFailed();
}
function unify(a, b) {
try {
// console.log("unify", JSON.stringify(a), JSON.stringify(b));
return {result: unify1(a, b)};
} catch (e) {
if (e.unificationFailed) return undefined;
throw e;
}
}
function anyUnify(aa, bb) {
for (var i = 0; i < aa.length; i++) {
for (var j = 0; j < bb.length; j++) {
if (unify(aa[i], bb[j])) return true;
}
}
return false;
}
// TODO: trigger-guards as per minimart
/*---------------------------------------------------------------------------*/
/* Events and Actions */
function Route(isSubscription, pattern, metaLevel, level) {
this.isSubscription = isSubscription;
this.pattern = pattern;
this.metaLevel = (metaLevel === undefined) ? 0 : metaLevel;
this.level = (level === undefined) ? 0 : level;
}
Route.prototype.drop = function () {
if (this.metaLevel === 0) { return null; }
return new Route(this.isSubscription, this.pattern, this.metaLevel - 1, this.level);
};
Route.prototype.lift = function () {
return new Route(this.isSubscription, this.pattern, this.metaLevel + 1, this.level);
};
Route.prototype.toJSON = function () {
return [this.isSubscription ? "sub" : "pub", this.pattern, this.metaLevel, this.level];
};
Route.prototype.visibilityToRoute = function (other, overrideOtherLevel) {
if (!this.isSubscription !== other.isSubscription) return undefined;
if (this.metaLevel !== other.metaLevel) return undefined;
if (this.level >= (overrideOtherLevel || other.level)) return undefined;
return unify(this.pattern, other.pattern); // returns undefined if unification fails
};
Route.fromJSON = function (j) {
switch (j[0]) {
case "sub": return new Route(true, j[1], j[2], j[3]);
case "pub": return new Route(false, j[1], j[2], j[3]);
default: throw { message: "Invalid JSON-encoded route: " + JSON.stringify(j) };
}
};
var __ = route.__;
var _$ = route._$;
function sub(pattern, metaLevel, level) {
return new Route(true, pattern, metaLevel, level);
return route.simpleGestalt(false, pattern, metaLevel, level);
}
function pub(pattern, metaLevel, level) {
return new Route(false, pattern, metaLevel, level);
return route.simpleGestalt(true, pattern, metaLevel, level);
}
function spawn(behavior, initialRoutes) {
function spawn(behavior, initialGestalts) {
return { type: "spawn",
behavior: behavior,
initialRoutes: (initialRoutes === undefined) ? [] : initialRoutes };
initialGestalt: route.gestaltUnion(initialGestalts || []) };
}
function updateRoutes(routes) {
return { type: "routes", routes: routes };
function updateRoutes(gestalts) {
return { type: "routes", gestalt: route.gestaltUnion(gestalts) };
}
function pendingRoutingUpdate(aggregate, affectedSubgestalt, knownTarget) {
return { type: "pendingRoutingUpdate",
aggregate: aggregate,
affectedSubgestalt: affectedSubgestalt,
knownTarget: knownTarget };
}
function sendMessage(m, metaLevel, isFeedback) {
@ -129,104 +42,53 @@ function shutdownWorld() {
return { type: "shutdownWorld" };
}
/*---------------------------------------------------------------------------*/
/* Metafunctions */
function dropRoutes(routes) {
var result = [];
for (var i = 0; i < routes.length; i++) {
var r = routes[i].drop();
if (r) { result.push(r); }
}
return result;
}
function liftRoutes(routes) {
var result = [];
for (var i = 0; i < routes.length; i++) {
result.push(routes[i].lift());
}
return result;
}
function intersectRoutes(rs1, rs2) {
var result = [];
for (var i = 0; i < rs1.length; i++) {
for (var j = 0; j < rs2.length; j++) {
var ri = rs1[i];
var rj = rs2[j];
var u = ri.visibilityToRoute(rj);
if (u) {
var rk = new Route(ri.isSubscription, u.result, ri.metaLevel, ri.level);
result.push(rk);
}
}
}
return result;
}
function filterEvent(e, routes) {
switch (e.type) {
case "routes":
return updateRoutes(intersectRoutes(e.routes, routes));
case "message":
for (var i = 0; i < routes.length; i++) {
var r = routes[i];
if (e.metaLevel === r.metaLevel
&& e.isFeedback === !r.isSubscription
&& unify(e.message, r.pattern))
{
return e;
}
}
return null;
default:
throw { message: "Event type " + e.type + " not filterable",
event: e };
}
}
/*---------------------------------------------------------------------------*/
/* Configurations */
function World(bootFn) {
this.nextPid = 0;
this.alive = true;
this.eventQueue = [];
this.runnablePids = {};
this.partialGestalt = route.emptyGestalt; // Only gestalt from local processes
this.fullGestalt = route.emptyGestalt ;; // partialGestalt unioned with downwardGestalt
this.processTable = {};
this.downwardRoutes = [];
this.downwardGestalt = route.emptyGestalt;
this.processActions = [];
this.activePid = null;
this.stepperId = null;
this.asChild(-1, bootFn, true);
}
/* Class state / methods */
World.nextPid = 0;
World.stack = [];
World.current = function () {
return World.stack[World.stack.length - 1];
return World.stack[World.stack.length - 1][0];
};
World.activePid = function () {
return World.stack[World.stack.length - 1][1];
};
World.send = function (m, metaLevel, isFeedback) {
World.current().enqueueAction(sendMessage(m, metaLevel, isFeedback));
World.current().enqueueAction(World.activePid(), sendMessage(m, metaLevel, isFeedback));
};
World.updateRoutes = function (routes) {
World.current().enqueueAction(updateRoutes(routes));
World.updateRoutes = function (gestalts) {
World.current().enqueueAction(World.activePid(), updateRoutes(gestalts));
};
World.spawn = function (behavior, initialRoutes) {
World.current().enqueueAction(spawn(behavior, initialRoutes));
World.spawn = function (behavior, initialGestalts) {
World.current().enqueueAction(World.activePid(), spawn(behavior, initialGestalts));
};
World.exit = function (exn) {
World.current().killActive(exn);
World.current().kill(World.activePid(), exn);
};
World.shutdownWorld = function () {
World.current().enqueueAction(shutdownWorld());
World.current().enqueueAction(World.activePid(), shutdownWorld());
};
World.withWorldStack = function (stack, f) {
@ -245,14 +107,15 @@ World.withWorldStack = function (stack, f) {
World.wrap = function (f) {
var savedStack = World.stack.slice();
var savedPid = World.current().activePid;
return function () {
var actuals = arguments;
return World.withWorldStack(savedStack, function () {
var result = World.current().asChild(savedPid, function () {
var result = World.current().asChild(World.activePid(), function () {
return f.apply(null, actuals);
});
World.stack[0].startStepping();
for (var i = World.stack.length - 1; i >= 0; i--) {
World.stack[i][0].markPidRunnable(World.stack[i][1]);
}
return result;
});
};
@ -260,40 +123,28 @@ World.wrap = function (f) {
/* Instance methods */
World.prototype.killActive = function (exn) {
this.kill(this.activePid, exn);
World.prototype.enqueueAction = function (pid, action) {
this.processActions.push([pid, action]);
};
World.prototype.enqueueAction = function (action) {
this.processActions.push([this.activePid, action]);
// The code is written to maintain the runnablePids set carefully, to
// ensure we can locally decide whether we're inert or not without
// having to search the whole deep process tree.
World.prototype.isInert = function () {
return this.eventQueue.length === 0
&& this.processActions.length === 0
&& route.is_emptySet(this.runnablePids);
};
World.prototype.isQuiescent = function () {
return this.eventQueue.length === 0 && this.processActions.length === 0;
World.prototype.markPidRunnable = function (pid) {
this.runnablePids[pid] = [pid];
};
World.prototype.step = function () {
this.dispatchEvents();
this.performActions();
return this.alive && (this.stepChildren() || !this.isQuiescent());
};
World.prototype.startStepping = function () {
var self = this;
if (this.stepperId) return;
if (this.step()) {
this.stepperId = setTimeout(function () {
self.stepperId = null;
self.startStepping();
}, 0);
}
};
World.prototype.stopStepping = function () {
if (this.stepperId) {
clearTimeout(this.stepperId);
this.stepperId = null;
}
this.stepChildren();
return this.alive && !this.isInert();
};
World.prototype.asChild = function (pid, f, omitLivenessCheck) {
@ -302,17 +153,15 @@ World.prototype.asChild = function (pid, f, omitLivenessCheck) {
return;
}
World.stack.push(this);
World.stack.push([this, pid]);
var result = null;
this.activePid = pid;
try {
result = f();
} catch (e) {
this.kill(pid, e);
}
this.activePid = null;
if (World.stack.pop() !== this) {
throw { message: "Internal error: World stack imbalance" };
if (World.stack.pop()[0] !== this) {
throw new Error("Internal error: World stack imbalance");
}
return result;
};
@ -328,19 +177,21 @@ World.prototype.kill = function (pid, exn) {
this.asChild(pid, function () { return p.behavior.trapexit(exn); });
}
delete this.processTable[pid];
this.issueRoutingUpdate();
if (p) {
this.applyAndIssueRoutingUpdate(p.gestalt, route.emptyGestalt);
}
};
World.prototype.stepChildren = function () {
var someChildBusy = false;
for (var pid in this.processTable) {
var pids = this.runnablePids;
this.runnablePids = {};
for (var pid in pids) {
var p = this.processTable[pid];
if (p.behavior.step /* exists, haven't called it yet */) {
var childBusy = this.asChild(pid, function () { return p.behavior.step() });
someChildBusy = someChildBusy || childBusy;
if (p && p.behavior.step /* exists, haven't called it yet */) {
var childBusy = this.asChild(pid | 0, function () { return p.behavior.step() });
if (childBusy) this.markPidRunnable(pid);
}
}
return someChildBusy;
};
World.prototype.performActions = function () {
@ -364,18 +215,25 @@ World.prototype.dispatchEvents = function () {
World.prototype.performAction = function (pid, action) {
switch (action.type) {
case "spawn":
var pid = this.nextPid++;
this.processTable[pid] = { routes: action.initialRoutes, behavior: action.behavior };
if (action.behavior.boot) { this.asChild(pid, function () { action.behavior.boot() }); }
this.issueRoutingUpdate();
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() });
this.markPidRunnable(pid);
}
this.applyAndIssueRoutingUpdate(route.emptyGestalt, newGestalt, pid);
break;
case "routes":
if (pid in this.processTable) {
// it may not be: this might be the routing update from a
// kill of the process
this.processTable[pid].routes = action.routes;
var oldGestalt = this.processTable[pid].gestalt;
var newGestalt = action.gestalt.label(pid|0);
// ^ pid|0: convert pid from string (table key!) to integer
this.processTable[pid].gestalt = newGestalt;
this.applyAndIssueRoutingUpdate(oldGestalt, newGestalt, pid);
}
this.issueRoutingUpdate();
break;
case "message":
if (action.metaLevel === 0) {
@ -389,179 +247,138 @@ World.prototype.performAction = function (pid, action) {
World.exit();
break;
default:
throw { message: "Action type " + action.type + " not understood",
action: action };
var exn = new Error("Action type " + action.type + " not understood");
exn.action = action;
throw exn;
}
};
World.prototype.aggregateRoutes = function (base) {
var acc = base.slice();
for (var pid in this.processTable) {
var p = this.processTable[pid];
for (var i = 0; i < p.routes.length; i++) {
acc.push(p.routes[i]);
}
}
return acc;
World.prototype.updateFullGestalt = function () {
this.fullGestalt = this.partialGestalt.union(this.downwardGestalt);
};
World.prototype.issueLocalRoutingUpdate = function () {
this.eventQueue.push(updateRoutes(this.aggregateRoutes(this.downwardRoutes)));
World.prototype.issueLocalRoutingUpdate = function (affectedSubgestalt, knownTarget) {
this.eventQueue.push(pendingRoutingUpdate(this.fullGestalt,
affectedSubgestalt,
knownTarget));
};
World.prototype.issueRoutingUpdate = function () {
this.issueLocalRoutingUpdate();
World.updateRoutes(dropRoutes(this.aggregateRoutes([])));
World.prototype.applyAndIssueRoutingUpdate = function (oldg, newg, knownTarget) {
knownTarget = typeof knownTarget === 'undefined' ? null : knownTarget;
this.partialGestalt = this.partialGestalt.erasePath(oldg).union(newg);
this.updateFullGestalt();
this.issueLocalRoutingUpdate(oldg.union(newg), knownTarget);
World.updateRoutes([this.partialGestalt.drop()]);
};
World.prototype.dispatchEvent = function (e) {
for (var pid in this.processTable) {
var p = this.processTable[pid];
var e1 = filterEvent(e, p.routes);
// console.log("filtering", e, p.routes, e1);
if (e1) { this.asChild(pid, function () { p.behavior.handleEvent(e1) }); }
switch (e.type) {
case "pendingRoutingUpdate":
var pids = e.affectedSubgestalt.match(e.aggregate);
if (e.knownTarget !== null) pids.unshift(e.knownTarget);
for (var i = 0; i < pids.length; i++) {
var pid = pids[i];
if (pid === "out") console.warning("Would have delivered a routing update to environment");
var p = this.processTable[pid];
if (p) {
var g = e.aggregate.filter(p.gestalt);
this.asChild(pid, function () { p.behavior.handleEvent(updateRoutes([g])) });
this.markPidRunnable(pid);
}
}
break;
case "message":
var pids = this.partialGestalt.matchValue(e.message, e.metaLevel, e.isFeedback);
for (var i = 0; i < pids.length; i++) {
var pid = pids[i];
var p = this.processTable[pid];
this.asChild(pid, function () { p.behavior.handleEvent(e) });
this.markPidRunnable(pid);
}
break;
default:
var exn = new Error("Event type " + e.type + " not dispatchable");
exn.event = e;
throw exn;
}
};
World.prototype.handleEvent = function (e) {
switch (e.type) {
case "routes":
this.downwardRoutes = liftRoutes(e.routes);
this.issueLocalRoutingUpdate();
var oldDownward = this.downwardGestalt;
this.downwardGestalt = e.gestalt.label("out").lift();
this.updateFullGestalt();
this.issueLocalRoutingUpdate(oldDownward.union(this.downwardGestalt), null);
break;
case "message":
this.eventQueue.push(sendMessage(e.message, e.metaLevel + 1, e.isFeedback));
break;
default:
throw { message: "Event type " + e.type + " not understood",
event: e };
var exn = new Error("Event type " + e.type + " not understood");
exn.event = e;
throw exn;
}
};
/*---------------------------------------------------------------------------*/
/* Utilities: detecting presence/absence events via routing events */
function PresenceDetector(initialRoutes) {
this.state = this._digestRoutes(initialRoutes === undefined ? [] : initialRoutes);
}
PresenceDetector.prototype._digestRoutes = function (routes) {
var newState = {};
for (var i = 0; i < routes.length; i++) {
newState[JSON.stringify(routes[i].toJSON())] = routes[i];
}
return newState;
};
PresenceDetector.prototype.getRouteList = function () {
var rs = [];
for (var k in this.state) { rs.push(this.state[k]); }
return rs;
};
PresenceDetector.prototype.handleRoutes = function (routes) {
var added = [];
var removed = [];
var newState = this._digestRoutes(routes);
for (var k in newState) {
if (!(k in this.state)) {
added.push(newState[k]);
} else {
delete this.state[k];
}
}
for (var k in this.state) {
removed.push(this.state[k]);
}
this.state = newState;
return { added: added, removed: removed };
};
PresenceDetector.prototype.presenceExistsFor = function (probeRoute) {
for (var k in this.state) {
var existingRoute = this.state[k];
if (existingRoute.visibleToRoute(probeRoute, Infinity) &&
(existingRoute.level === probeRoute.level))
{
return true;
}
}
return false;
};
/*---------------------------------------------------------------------------*/
/* Utilities: matching demand for some service */
function DemandMatcher(pattern, metaLevel, options) {
function DemandMatcher(projection, metaLevel, options) {
options = $.extend({
demandLevel: 0,
supplyLevel: 0,
demandSideIsSubscription: true
}, options);
this.pattern = pattern;
this.pattern = route.projectionToPattern(projection);
this.projectionSpec = route.compileProjection(projection);
this.metaLevel = metaLevel;
this.demandLevel = options.demandLevel;
this.supplyLevel = options.supplyLevel;
this.demandSideIsSubscription = options.demandSideIsSubscription;
this.onDemandIncrease = function (r) {
console.error("Unhandled increase in demand for route", r);
this.onDemandIncrease = function (captures) {
console.error("Unhandled increase in demand for route", captures);
};
this.onSupplyDecrease = function (r) {
console.error("Unhandled decrease in supply for route", r);
this.onSupplyDecrease = function (captures) {
console.error("Unhandled decrease in supply for route", captures);
};
this.state = new PresenceDetector();
this.currentDemand = {};
this.currentSupply = {};
}
DemandMatcher.prototype.boot = function () {
World.updateRoutes([this.computeDetector(true),
this.computeDetector(false)]);
var observerLevel = 1 + Math.max(this.demandLevel, this.supplyLevel);
World.updateRoutes([sub(this.pattern, this.metaLevel, observerLevel),
pub(this.pattern, this.metaLevel, observerLevel)]);
};
DemandMatcher.prototype.handleEvent = function (e) {
if (e.type === "routes") {
this.handleRoutes(e.routes);
this.handleGestalt(e.gestalt);
}
};
DemandMatcher.prototype.computeDetector = function (demandSide) {
var maxLevel = (this.demandLevel > this.supplyLevel ? this.demandLevel : this.supplyLevel);
return new Route(this.demandSideIsSubscription ? !demandSide : demandSide,
this.pattern,
this.metaLevel,
maxLevel + 1);
};
DemandMatcher.prototype.handleRoutes = function (routes) {
var changes = this.state.handleRoutes(routes);
this.incorporateChanges(true, changes.added);
this.incorporateChanges(false, changes.removed);
};
DemandMatcher.prototype.incorporateChanges = function (isArrivals, routeList) {
var relevantChangeDetector = this.computeDetector(isArrivals);
var expectedChangeLevel = isArrivals ? this.demandLevel : this.supplyLevel;
var expectedPeerLevel = isArrivals ? this.supplyLevel : this.demandLevel;
for (var i = 0; i < routeList.length; i++) {
var changed = routeList[i];
if (changed.level != expectedChangeLevel) continue;
var relevantChangedN = intersectRoutes([changed], [relevantChangeDetector]);
if (relevantChangedN.length === 0) continue;
var relevantChanged = relevantChangedN[0]; /* there can be only one */
var peerDetector = new Route(relevantChanged.isSubscription,
relevantChanged.pattern,
relevantChanged.metaLevel,
expectedPeerLevel + 1);
var peerRoutes = intersectRoutes(this.state.getRouteList(), [peerDetector]);
var peerExists = false;
for (var j = 0; j < peerRoutes.length; j++) {
if (peerRoutes[j].level == expectedPeerLevel) {
peerExists = true;
break;
}
}
if (isArrivals && !peerExists) { this.onDemandIncrease(relevantChanged); }
if (!isArrivals && peerExists) { this.onSupplyDecrease(relevantChanged); }
}
DemandMatcher.prototype.handleGestalt = function (gestalt) {
var newDemandMatcher = gestalt.project(this.metaLevel,
this.demandLevel,
!this.demandSideIsSubscription,
this.projectionSpec);
var newSupplyMatcher = gestalt.project(this.metaLevel,
this.supplyLevel,
this.demandSideIsSubscription,
this.projectionSpec)
var newDemand = route.arrayToSet(route.matcherKeys(newDemandMatcher));
var newSupply = route.arrayToSet(route.matcherKeys(newSupplyMatcher));
var demandDelta = route.setSubtract(newDemand, this.currentDemand);
var supplyDelta = route.setSubtract(this.currentSupply, newSupply);
var demandIncr = route.setSubtract(demandDelta, newSupply);
var supplyDecr = route.setIntersect(supplyDelta, newDemand);
this.currentDemand = newDemand;
this.currentSupply = newSupply;
for (var k in demandIncr) this.onDemandIncrease(demandIncr[k]);
for (var k in supplyDecr) this.onSupplyDecrease(supplyDecr[k]);
};
/*---------------------------------------------------------------------------*/
@ -607,27 +424,51 @@ Deduplicator.prototype.expireMessages = function () {
function Ground(bootFn) {
var self = this;
this.stepperId = null;
this.state = new PresenceDetector();
World.withWorldStack([this], function () {
World.withWorldStack([[this, -1]], function () {
self.world = new World(bootFn);
});
}
Ground.prototype.step = function () {
var self = this;
return World.withWorldStack([this], function () {
return World.withWorldStack([[this, -1]], function () {
return self.world.step();
});
};
Ground.prototype.startStepping = World.prototype.startStepping;
Ground.prototype.stopStepping = World.prototype.stopStepping;
Ground.prototype.checkPid = function (pid) {
if (pid !== -1) console.error("Weird pid in Ground markPidRunnable", pid);
};
Ground.prototype.enqueueAction = function (action) {
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.enqueueAction = function (pid, action) {
this.checkPid(pid);
if (action.type === 'routes') {
var added = this.state.handleRoutes(action.routes).added;
if (added.length > 0) {
console.error("You have subscribed to a nonexistent event source.", added);
if (!action.gestalt.isEmpty()) {
console.error("You have subscribed to a nonexistent event source.",
action.gestalt.pretty());
}
} else {
console.error("You have sent a message into the outer void.", action);

View File

@ -1,95 +1,34 @@
function spawnRoutingTableWidget(selector, fragmentClass) {
function sortedBy(xs, f) {
var keys = [];
var result = [];
for (var i = 0; i < xs.length; i++) {
keys.push([f(xs[i]), i]);
}
keys.sort();
for (var i = 0; i < xs.length; i++) {
result.push(xs[keys[i][1]]);
}
return result;
}
function count_uniqBy(xs, f) {
var r = [];
if (xs.length === 0) return [];
var last = xs[0];
var lastKey = f(xs[0]);
var count = 1;
function fin() {
r.push([count, last]);
}
for (var i = 1; i < xs.length; i++) {
var fi = f(xs[i]);
if (fi === lastKey) {
count++;
} else {
fin();
last = xs[i];
lastKey = fi;
count = 1;
}
}
fin();
return r;
}
function spawnRoutingTableWidget(selector, fragmentClass, observationLevel) {
observationLevel = observationLevel || 10;
// ^ arbitrary: should be Infinity, when route.js supports it. TODO
World.spawn({
boot: function () { this.updateState(); },
state: [],
nextState: [],
state: route.emptyGestalt.serialize(),
nextState: route.emptyGestalt.serialize(),
timer: false,
digestRoutes: function (rs) {
var s = [];
function key(r) { return JSON.stringify([r.pattern,
r.isSubscription ? r.level : -r.level,
r.isSubscription]); }
for (var i = 0; i < rs.length; i++) {
var p = rs[i].pattern;
if (p[0] !== "DOM" || p[1] !== selector || p[2] !== fragmentClass) {
s.push(rs[i]);
}
}
s = sortedBy(s, key);
s = count_uniqBy(s, key);
return s;
localGestalt: (sub( ["DOM", selector, fragmentClass, __], 0, 2)
.union(pub(["DOM", selector, fragmentClass, __], 0, 2))
.telescoped()),
digestGestalt: function (g) {
return g.stripLabel().erasePath(this.localGestalt).serialize();
},
updateState: function () {
var elts = ["ul", {"class": "routing-table"}];
for (var i = 0; i < this.state.length; i++) {
var r = this.state[i];
var levelstr;
switch (r[1].level) {
case 0: levelstr = "participant"; break;
case 1: levelstr = "observer"; break;
case 2: levelstr = "metaobserver"; break;
default: levelstr = "level " + r[1].level; break;
}
var polarity = r[1].isSubscription ? "sub" : "pub";
var pat = JSON.stringify(r[1].pattern).replace(/{"__":"__"}/g, '★');
elts.push(["li",
["span", {"class": "repeatcount"}, r[0]],
["span", {"class": "times"}, " × "],
["span", {"class": polarity + " route"},
["span", {"class": "level", "data-level": r[1].level}, levelstr],
["span", {"class": "polarity"}, polarity],
["span", {"class": "pattern"}, pat]]]);
}
World.updateRoutes([sub(__, 0, Infinity),
pub(__, 0, Infinity),
var elts = ["ul", {"class": "routing-table"},
["li", ["pre", route.deserializeGestalt(this.state).pretty()]]];
World.updateRoutes([sub(__, 0, observationLevel),
pub(__, 0, observationLevel),
pub(["DOM", selector, fragmentClass, elts])]);
},
handleEvent: function (e) {
var self = this;
if (e.type === "routes") {
self.nextState = self.digestRoutes(e.routes);
self.nextState = self.digestGestalt(e.gestalt);
if (self.timer) {
clearTimeout(self.timer);
self.timer = false;

7
spy.js
View File

@ -1,18 +1,19 @@
// Generic Spy
function Spy(label, useJson) {
function Spy(label, useJson, observationLevel) {
this.label = label || "SPY";
this.observationLevel = observationLevel || 10; // arbitrary. Should be Infinity. TODO
this.useJson = useJson;
}
Spy.prototype.boot = function () {
World.updateRoutes([sub(__, 0, Infinity), pub(__, 0, Infinity)]);
World.updateRoutes([sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)]);
};
Spy.prototype.handleEvent = function (e) {
switch (e.type) {
case "routes":
console.log(this.label, "routes", this.useJson ? JSON.stringify(e.routes) : e.routes);
console.log(this.label, "routes", e.gestalt.pretty());
break;
case "message":
var messageRepr;