2014-07-24 00:21:51 +00:00
|
|
|
var Route = require("./route.js");
|
2014-08-02 07:30:52 +00:00
|
|
|
var Util = require("./util.js");
|
2014-07-24 00:21:51 +00:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
// TODO: trigger-guards as per minimart
|
2013-08-12 02:23:24 +00:00
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2013-09-27 11:14:27 +00:00
|
|
|
/* Events and Actions */
|
2013-08-12 02:23:24 +00:00
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
var __ = Route.__;
|
|
|
|
var _$ = Route._$;
|
2013-10-31 12:10:53 +00:00
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
function sub(pattern, metaLevel, level) {
|
2014-07-24 00:21:51 +00:00
|
|
|
return Route.simpleGestalt(false, pattern, metaLevel, level);
|
2013-08-12 02:23:24 +00:00
|
|
|
}
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
function pub(pattern, metaLevel, level) {
|
2014-07-24 00:21:51 +00:00
|
|
|
return Route.simpleGestalt(true, pattern, metaLevel, level);
|
2013-08-12 02:23:24 +00:00
|
|
|
}
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
function spawn(behavior, initialGestalts) {
|
2013-09-27 11:14:27 +00:00
|
|
|
return { type: "spawn",
|
|
|
|
behavior: behavior,
|
2014-07-24 00:21:51 +00:00
|
|
|
initialGestalt: Route.gestaltUnion(initialGestalts || []) };
|
2014-05-26 18:36:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateRoutes(gestalts) {
|
2014-07-24 00:21:51 +00:00
|
|
|
return { type: "routes", gestalt: Route.gestaltUnion(gestalts) };
|
2013-08-12 02:23:24 +00:00
|
|
|
}
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
function pendingRoutingUpdate(aggregate, affectedSubgestalt, knownTarget) {
|
|
|
|
return { type: "pendingRoutingUpdate",
|
|
|
|
aggregate: aggregate,
|
|
|
|
affectedSubgestalt: affectedSubgestalt,
|
|
|
|
knownTarget: knownTarget };
|
2013-08-12 02:23:24 +00:00
|
|
|
}
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
function sendMessage(m, metaLevel, isFeedback) {
|
2013-10-30 15:50:05 +00:00
|
|
|
return { type: "message",
|
2013-09-27 11:14:27 +00:00
|
|
|
metaLevel: (metaLevel === undefined) ? 0 : metaLevel,
|
|
|
|
message: m,
|
|
|
|
isFeedback: (isFeedback === undefined) ? false : isFeedback };
|
|
|
|
}
|
|
|
|
|
2014-02-28 14:46:59 +00:00
|
|
|
function shutdownWorld() {
|
|
|
|
return { type: "shutdownWorld" };
|
|
|
|
}
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Configurations */
|
|
|
|
|
2013-10-17 14:50:51 +00:00
|
|
|
function World(bootFn) {
|
2014-03-10 16:15:16 +00:00
|
|
|
this.alive = true;
|
2013-08-12 02:23:24 +00:00
|
|
|
this.eventQueue = [];
|
2014-05-26 18:36:57 +00:00
|
|
|
this.runnablePids = {};
|
2014-07-24 00:21:51 +00:00
|
|
|
this.partialGestalt = Route.emptyGestalt; // Only gestalt from local processes
|
|
|
|
this.fullGestalt = Route.emptyGestalt ;; // partialGestalt unioned with downwardGestalt
|
2013-08-12 02:23:24 +00:00
|
|
|
this.processTable = {};
|
2014-07-21 23:20:24 +00:00
|
|
|
this.tombstones = {};
|
2014-07-24 00:21:51 +00:00
|
|
|
this.downwardGestalt = Route.emptyGestalt;
|
2013-09-27 11:14:27 +00:00
|
|
|
this.processActions = [];
|
2013-11-03 17:31:09 +00:00
|
|
|
this.asChild(-1, bootFn, true);
|
2013-08-12 02:23:24 +00:00
|
|
|
}
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
/* Class state / methods */
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.nextPid = 0;
|
|
|
|
|
2013-10-18 08:58:08 +00:00
|
|
|
World.stack = [];
|
|
|
|
|
|
|
|
World.current = function () {
|
2014-05-26 18:36:57 +00:00
|
|
|
return World.stack[World.stack.length - 1][0];
|
|
|
|
};
|
|
|
|
|
|
|
|
World.activePid = function () {
|
|
|
|
return World.stack[World.stack.length - 1][1];
|
2013-10-18 08:58:08 +00:00
|
|
|
};
|
2013-09-27 11:14:27 +00:00
|
|
|
|
|
|
|
World.send = function (m, metaLevel, isFeedback) {
|
2014-05-26 18:36:57 +00:00
|
|
|
World.current().enqueueAction(World.activePid(), sendMessage(m, metaLevel, isFeedback));
|
2013-09-27 11:14:27 +00:00
|
|
|
};
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.updateRoutes = function (gestalts) {
|
|
|
|
World.current().enqueueAction(World.activePid(), updateRoutes(gestalts));
|
2013-09-27 11:14:27 +00:00
|
|
|
};
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.spawn = function (behavior, initialGestalts) {
|
|
|
|
World.current().enqueueAction(World.activePid(), spawn(behavior, initialGestalts));
|
2013-10-18 08:58:08 +00:00
|
|
|
};
|
|
|
|
|
2013-10-22 18:14:51 +00:00
|
|
|
World.exit = function (exn) {
|
2014-05-26 18:36:57 +00:00
|
|
|
World.current().kill(World.activePid(), exn);
|
2013-10-22 18:14:51 +00:00
|
|
|
};
|
|
|
|
|
2014-02-28 14:46:59 +00:00
|
|
|
World.shutdownWorld = function () {
|
2014-05-26 18:36:57 +00:00
|
|
|
World.current().enqueueAction(World.activePid(), shutdownWorld());
|
2014-02-28 14:46:59 +00:00
|
|
|
};
|
|
|
|
|
2013-10-18 08:58:08 +00:00
|
|
|
World.withWorldStack = function (stack, f) {
|
|
|
|
var oldStack = World.stack;
|
|
|
|
World.stack = stack;
|
|
|
|
var result = null;
|
|
|
|
try {
|
|
|
|
result = f();
|
|
|
|
} catch (e) {
|
|
|
|
World.stack = oldStack;
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
World.stack = oldStack;
|
|
|
|
return result;
|
2013-09-27 11:14:27 +00:00
|
|
|
};
|
|
|
|
|
2013-10-17 14:50:51 +00:00
|
|
|
World.wrap = function (f) {
|
2013-10-18 08:58:08 +00:00
|
|
|
var savedStack = World.stack.slice();
|
|
|
|
return function () {
|
|
|
|
var actuals = arguments;
|
|
|
|
return World.withWorldStack(savedStack, function () {
|
2014-05-26 18:36:57 +00:00
|
|
|
var result = World.current().asChild(World.activePid(), function () {
|
2013-10-18 08:58:08 +00:00
|
|
|
return f.apply(null, actuals);
|
|
|
|
});
|
2014-05-26 18:36:57 +00:00
|
|
|
for (var i = World.stack.length - 1; i >= 0; i--) {
|
|
|
|
World.stack[i][0].markPidRunnable(World.stack[i][1]);
|
|
|
|
}
|
2013-10-18 08:58:08 +00:00
|
|
|
return result;
|
|
|
|
});
|
|
|
|
};
|
2013-10-17 14:50:51 +00:00
|
|
|
};
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
/* Instance methods */
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.prototype.enqueueAction = function (pid, action) {
|
|
|
|
this.processActions.push([pid, action]);
|
2013-10-22 18:14:51 +00:00
|
|
|
};
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
// 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
|
2014-07-24 00:21:51 +00:00
|
|
|
&& Route.is_emptySet(this.runnablePids);
|
2013-09-27 11:14:27 +00:00
|
|
|
};
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.prototype.markPidRunnable = function (pid) {
|
|
|
|
this.runnablePids[pid] = [pid];
|
2013-09-27 11:14:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.step = function () {
|
|
|
|
this.dispatchEvents();
|
|
|
|
this.performActions();
|
2014-05-26 18:36:57 +00:00
|
|
|
this.stepChildren();
|
|
|
|
return this.alive && !this.isInert();
|
2013-10-17 14:50:51 +00:00
|
|
|
};
|
|
|
|
|
2013-11-03 17:31:09 +00:00
|
|
|
World.prototype.asChild = function (pid, f, omitLivenessCheck) {
|
|
|
|
if (!(pid in this.processTable) && !omitLivenessCheck) {
|
|
|
|
console.warn("World.asChild eliding invocation of dead process", pid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.stack.push([this, pid]);
|
2013-09-27 11:14:27 +00:00
|
|
|
var result = null;
|
|
|
|
try {
|
|
|
|
result = f();
|
|
|
|
} catch (e) {
|
|
|
|
this.kill(pid, e);
|
2013-08-12 02:23:24 +00:00
|
|
|
}
|
2014-05-26 18:36:57 +00:00
|
|
|
if (World.stack.pop()[0] !== this) {
|
|
|
|
throw new Error("Internal error: World stack imbalance");
|
2013-10-18 08:58:08 +00:00
|
|
|
}
|
2013-09-27 11:14:27 +00:00
|
|
|
return result;
|
2013-08-12 02:23:24 +00:00
|
|
|
};
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
World.prototype.kill = function (pid, exn) {
|
2013-10-17 15:38:47 +00:00
|
|
|
if (exn && exn.stack) {
|
2013-10-22 18:14:51 +00:00
|
|
|
console.log("Process exited", pid, exn, exn.stack);
|
2013-10-17 15:38:47 +00:00
|
|
|
} else {
|
2013-10-22 18:14:51 +00:00
|
|
|
console.log("Process exited", pid, exn);
|
2013-10-17 15:38:47 +00:00
|
|
|
}
|
2013-11-03 20:56:12 +00:00
|
|
|
var p = this.processTable[pid];
|
|
|
|
if (p && p.behavior.trapexit) {
|
|
|
|
this.asChild(pid, function () { return p.behavior.trapexit(exn); });
|
|
|
|
}
|
2013-09-27 11:14:27 +00:00
|
|
|
delete this.processTable[pid];
|
2014-05-26 18:36:57 +00:00
|
|
|
if (p) {
|
2014-07-22 16:36:04 +00:00
|
|
|
if (exn) {
|
|
|
|
p.exitReason = exn;
|
|
|
|
this.tombstones[pid] = p;
|
|
|
|
}
|
2014-07-24 00:21:51 +00:00
|
|
|
this.applyAndIssueRoutingUpdate(p.gestalt, Route.emptyGestalt);
|
2014-05-26 18:36:57 +00:00
|
|
|
}
|
2013-09-27 11:14:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.stepChildren = function () {
|
2014-05-26 18:36:57 +00:00
|
|
|
var pids = this.runnablePids;
|
|
|
|
this.runnablePids = {};
|
|
|
|
for (var pid in pids) {
|
2013-09-27 11:14:27 +00:00
|
|
|
var p = this.processTable[pid];
|
2014-05-26 18:36:57 +00:00
|
|
|
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);
|
2013-09-27 11:14:27 +00:00
|
|
|
}
|
|
|
|
}
|
2013-08-12 02:23:24 +00:00
|
|
|
};
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
World.prototype.performActions = function () {
|
|
|
|
var queue = this.processActions;
|
|
|
|
this.processActions = [];
|
2013-08-12 02:23:24 +00:00
|
|
|
var item;
|
2014-03-10 16:15:16 +00:00
|
|
|
while ((item = queue.shift()) && this.alive) {
|
2013-08-12 02:23:24 +00:00
|
|
|
this.performAction(item[0], item[1]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-09-27 11:14:27 +00:00
|
|
|
World.prototype.dispatchEvents = function () {
|
|
|
|
var queue = this.eventQueue;
|
|
|
|
this.eventQueue = [];
|
|
|
|
var item;
|
|
|
|
while ((item = queue.shift())) {
|
|
|
|
this.dispatchEvent(item);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-12 02:23:24 +00:00
|
|
|
World.prototype.performAction = function (pid, action) {
|
|
|
|
switch (action.type) {
|
|
|
|
case "spawn":
|
2014-05-26 18:36:57 +00:00
|
|
|
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);
|
|
|
|
}
|
2014-07-24 00:21:51 +00:00
|
|
|
this.applyAndIssueRoutingUpdate(Route.emptyGestalt, newGestalt, pid);
|
2013-08-12 02:23:24 +00:00
|
|
|
break;
|
|
|
|
case "routes":
|
2013-10-18 08:58:08 +00:00
|
|
|
if (pid in this.processTable) {
|
|
|
|
// it may not be: this might be the routing update from a
|
|
|
|
// kill of the process
|
2014-05-26 18:36:57 +00:00
|
|
|
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);
|
2013-10-18 08:58:08 +00:00
|
|
|
}
|
2013-08-12 02:23:24 +00:00
|
|
|
break;
|
2013-10-30 15:50:05 +00:00
|
|
|
case "message":
|
2013-09-27 11:14:27 +00:00
|
|
|
if (action.metaLevel === 0) {
|
|
|
|
this.eventQueue.push(action);
|
|
|
|
} else {
|
|
|
|
World.send(action.message, action.metaLevel - 1, action.isFeedback);
|
|
|
|
}
|
2013-08-12 02:23:24 +00:00
|
|
|
break;
|
2014-02-28 14:46:59 +00:00
|
|
|
case "shutdownWorld":
|
2014-03-10 16:15:16 +00:00
|
|
|
this.alive = false; // force us to stop doing things immediately
|
2014-02-28 14:46:59 +00:00
|
|
|
World.exit();
|
|
|
|
break;
|
2013-08-12 02:23:24 +00:00
|
|
|
default:
|
2014-05-26 18:36:57 +00:00
|
|
|
var exn = new Error("Action type " + action.type + " not understood");
|
|
|
|
exn.action = action;
|
|
|
|
throw exn;
|
2013-08-12 02:23:24 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.prototype.updateFullGestalt = function () {
|
|
|
|
this.fullGestalt = this.partialGestalt.union(this.downwardGestalt);
|
2013-08-12 02:23:24 +00:00
|
|
|
};
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
World.prototype.issueLocalRoutingUpdate = function (affectedSubgestalt, knownTarget) {
|
|
|
|
this.eventQueue.push(pendingRoutingUpdate(this.fullGestalt,
|
|
|
|
affectedSubgestalt,
|
|
|
|
knownTarget));
|
2013-10-26 17:39:30 +00:00
|
|
|
};
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
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()]);
|
2013-08-12 02:23:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.dispatchEvent = function (e) {
|
2014-05-26 18:36:57 +00:00
|
|
|
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];
|
2014-05-27 17:15:32 +00:00
|
|
|
if (pid === "out") console.warn("Would have delivered a routing update to environment");
|
2014-05-26 18:36:57 +00:00
|
|
|
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;
|
2013-09-27 11:14:27 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.handleEvent = function (e) {
|
2013-08-12 02:23:24 +00:00
|
|
|
switch (e.type) {
|
|
|
|
case "routes":
|
2014-05-26 18:36:57 +00:00
|
|
|
var oldDownward = this.downwardGestalt;
|
|
|
|
this.downwardGestalt = e.gestalt.label("out").lift();
|
|
|
|
this.updateFullGestalt();
|
|
|
|
this.issueLocalRoutingUpdate(oldDownward.union(this.downwardGestalt), null);
|
2013-08-12 02:23:24 +00:00
|
|
|
break;
|
2013-10-30 15:50:05 +00:00
|
|
|
case "message":
|
2013-09-27 11:14:27 +00:00
|
|
|
this.eventQueue.push(sendMessage(e.message, e.metaLevel + 1, e.isFeedback));
|
2013-08-12 02:23:24 +00:00
|
|
|
break;
|
|
|
|
default:
|
2014-05-26 18:36:57 +00:00
|
|
|
var exn = new Error("Event type " + e.type + " not understood");
|
|
|
|
exn.event = e;
|
|
|
|
throw exn;
|
2013-10-22 18:14:51 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-21 23:20:24 +00:00
|
|
|
/* Debugging, management, and monitoring */
|
|
|
|
|
2014-07-21 23:06:51 +00:00
|
|
|
World.prototype.processTree = function () {
|
|
|
|
var kids = [];
|
|
|
|
for (var pid in this.processTable) {
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
if (p.behavior instanceof World) {
|
|
|
|
kids.push([pid, p.behavior.processTree()]);
|
|
|
|
} else {
|
|
|
|
kids.push([pid, p]);
|
|
|
|
}
|
|
|
|
}
|
2014-07-21 23:20:24 +00:00
|
|
|
for (var pid in this.tombstones) {
|
|
|
|
kids.push([pid, this.tombstones[pid]]);
|
|
|
|
}
|
|
|
|
kids.sort();
|
2014-07-21 23:06:51 +00:00
|
|
|
return kids;
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.textProcessTree = function (ownPid) {
|
|
|
|
var lines = [];
|
|
|
|
|
|
|
|
function dumpProcess(prefix, pid, p) {
|
2014-07-25 03:02:00 +00:00
|
|
|
if (Array.isArray(p)) {
|
2014-07-21 23:06:51 +00:00
|
|
|
lines.push(prefix + '--+ ' + pid);
|
|
|
|
for (var i = 0; i < p.length; i++) {
|
|
|
|
dumpProcess(prefix + ' |', p[i][0], p[i][1]);
|
|
|
|
}
|
|
|
|
lines.push(prefix);
|
|
|
|
} else {
|
|
|
|
var label = p.behavior.name || p.behavior.constructor.name || '';
|
2014-07-21 23:20:24 +00:00
|
|
|
var tombstoneString = p.exitReason ? ' (EXITED: ' + p.exitReason + ') ' : '';
|
2014-07-21 23:06:51 +00:00
|
|
|
lines.push(prefix + '-- ' + pid + ': ' + label +
|
2014-07-21 23:20:24 +00:00
|
|
|
tombstoneString +
|
2014-07-21 23:06:51 +00:00
|
|
|
JSON.stringify(p.behavior, function (k, v) {
|
|
|
|
return k === 'name' ? undefined : v;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dumpProcess('', ownPid || '', this.processTree());
|
|
|
|
return lines.join('\n');
|
|
|
|
};
|
|
|
|
|
2014-07-21 23:20:24 +00:00
|
|
|
World.prototype.clearTombstones = function () {
|
|
|
|
this.tombstones = {};
|
|
|
|
for (var pid in this.processTable) {
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
if (p.behavior instanceof World) {
|
|
|
|
p.behavior.clearTombstones();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-22 18:14:51 +00:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Utilities: matching demand for some service */
|
|
|
|
|
2014-05-26 18:36:57 +00:00
|
|
|
function DemandMatcher(projection, metaLevel, options) {
|
2014-08-02 07:30:52 +00:00
|
|
|
options = Util.extend({
|
2013-10-30 13:13:03 +00:00
|
|
|
demandLevel: 0,
|
|
|
|
supplyLevel: 0,
|
2014-06-07 01:16:20 +00:00
|
|
|
demandSideIsSubscription: false
|
2014-02-28 14:47:34 +00:00
|
|
|
}, options);
|
2014-07-24 00:21:51 +00:00
|
|
|
this.pattern = Route.projectionToPattern(projection);
|
|
|
|
this.projectionSpec = Route.compileProjection(projection);
|
2014-06-07 01:16:20 +00:00
|
|
|
this.metaLevel = metaLevel | 0;
|
2013-10-30 13:13:03 +00:00
|
|
|
this.demandLevel = options.demandLevel;
|
|
|
|
this.supplyLevel = options.supplyLevel;
|
|
|
|
this.demandSideIsSubscription = options.demandSideIsSubscription;
|
2014-05-26 18:36:57 +00:00
|
|
|
this.onDemandIncrease = function (captures) {
|
|
|
|
console.error("Unhandled increase in demand for route", captures);
|
2013-10-30 13:13:03 +00:00
|
|
|
};
|
2014-05-26 18:36:57 +00:00
|
|
|
this.onSupplyDecrease = function (captures) {
|
|
|
|
console.error("Unhandled decrease in supply for route", captures);
|
2013-10-22 18:14:51 +00:00
|
|
|
};
|
2014-05-26 18:36:57 +00:00
|
|
|
this.currentDemand = {};
|
|
|
|
this.currentSupply = {};
|
2013-10-22 18:14:51 +00:00
|
|
|
}
|
|
|
|
|
2013-10-30 13:13:03 +00:00
|
|
|
DemandMatcher.prototype.boot = function () {
|
2014-05-26 18:36:57 +00:00
|
|
|
var observerLevel = 1 + Math.max(this.demandLevel, this.supplyLevel);
|
|
|
|
World.updateRoutes([sub(this.pattern, this.metaLevel, observerLevel),
|
|
|
|
pub(this.pattern, this.metaLevel, observerLevel)]);
|
2013-10-30 13:13:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
DemandMatcher.prototype.handleEvent = function (e) {
|
|
|
|
if (e.type === "routes") {
|
2014-05-26 18:36:57 +00:00
|
|
|
this.handleGestalt(e.gestalt);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DemandMatcher.prototype.handleGestalt = function (gestalt) {
|
2014-05-27 03:22:03 +00:00
|
|
|
var newDemandMatcher = gestalt.project(this.projectionSpec,
|
2014-05-26 18:36:57 +00:00
|
|
|
!this.demandSideIsSubscription,
|
2014-05-27 03:22:03 +00:00
|
|
|
this.metaLevel,
|
|
|
|
this.demandLevel);
|
|
|
|
var newSupplyMatcher = gestalt.project(this.projectionSpec,
|
2014-05-26 18:36:57 +00:00
|
|
|
this.demandSideIsSubscription,
|
2014-05-27 03:22:03 +00:00
|
|
|
this.metaLevel,
|
|
|
|
this.supplyLevel);
|
2014-07-24 00:21:51 +00:00
|
|
|
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);
|
2014-05-26 18:36:57 +00:00
|
|
|
this.currentDemand = newDemand;
|
|
|
|
this.currentSupply = newSupply;
|
|
|
|
for (var k in demandIncr) this.onDemandIncrease(demandIncr[k]);
|
|
|
|
for (var k in supplyDecr) this.onSupplyDecrease(supplyDecr[k]);
|
2013-10-30 13:13:03 +00:00
|
|
|
};
|
2013-10-22 18:14:51 +00:00
|
|
|
|
2013-10-31 12:10:53 +00:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Utilities: deduplicator */
|
|
|
|
|
|
|
|
function Deduplicator(ttl_ms) {
|
|
|
|
this.ttl_ms = ttl_ms || 10000;
|
|
|
|
this.queue = [];
|
|
|
|
this.map = {};
|
|
|
|
this.timerId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Deduplicator.prototype.accept = function (m) {
|
|
|
|
var s = JSON.stringify(m);
|
|
|
|
if (s in this.map) return false;
|
|
|
|
var entry = [(+new Date()) + this.ttl_ms, s, m];
|
|
|
|
this.map[s] = entry;
|
|
|
|
this.queue.push(entry);
|
|
|
|
|
|
|
|
if (this.timerId === null) {
|
|
|
|
var self = this;
|
|
|
|
this.timerId = setInterval(function () { self.expireMessages(); },
|
|
|
|
this.ttl_ms > 1000 ? 1000 : this.ttl_ms);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
Deduplicator.prototype.expireMessages = function () {
|
|
|
|
var now = +new Date();
|
|
|
|
while (this.queue.length > 0 && this.queue[0][0] <= now) {
|
|
|
|
var entry = this.queue.shift();
|
|
|
|
delete this.map[entry[1]];
|
|
|
|
}
|
|
|
|
if (this.queue.length === 0) {
|
|
|
|
clearInterval(this.timerId);
|
|
|
|
this.timerId = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.__ = __;
|
|
|
|
module.exports._$ = _$;
|
|
|
|
|
|
|
|
module.exports.sub = sub;
|
|
|
|
module.exports.pub = pub;
|
|
|
|
module.exports.spawn = spawn;
|
|
|
|
module.exports.updateRoutes = updateRoutes;
|
|
|
|
module.exports.sendMessage = sendMessage;
|
|
|
|
module.exports.shutdownWorld = shutdownWorld;
|
|
|
|
|
|
|
|
module.exports.World = World;
|
|
|
|
module.exports.DemandMatcher = DemandMatcher;
|
|
|
|
module.exports.Deduplicator = Deduplicator;
|
|
|
|
module.exports.Route = Route;
|