Refactor jQuery driver to use new PresenceDetector and DemandMatcher.
This commit is contained in:
parent
35f17da335
commit
e79b0092ed
|
@ -22,6 +22,7 @@
|
|||
</p>
|
||||
<p>
|
||||
<button id="testButton">Test button</button>
|
||||
<button id="testButton2">Test button 2</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
76
index.js
76
index.js
|
@ -9,50 +9,41 @@ Spy.prototype.handleEvent = function (e) {
|
|||
console.log("SPY", e);
|
||||
};
|
||||
|
||||
function JQueryDriver() {
|
||||
this.handlerMap = {};
|
||||
function JQueryEventRouter(selector, eventName) {
|
||||
var self = this;
|
||||
this.selector = selector;
|
||||
this.eventName = eventName;
|
||||
this.handler =
|
||||
World.wrap(function (e) { World.send(["jQuery", self.selector, self.eventName, e]); });
|
||||
$(this.selector).on(this.eventName, this.handler);
|
||||
}
|
||||
|
||||
JQueryDriver.prototype.updateHandlerMap = function (routes) {
|
||||
var newMap = {};
|
||||
for (var i = 0; i < routes.length; i++) {
|
||||
var selector = routes[i].pattern[1];
|
||||
var eventName = routes[i].pattern[2];
|
||||
if (typeof(selector) === 'string' && typeof(eventName) === 'string') {
|
||||
var key = JSON.stringify([selector, eventName]);
|
||||
var handler = this.handlerMap[key];
|
||||
if (!handler) {
|
||||
handler = (function (selector, eventName) { // JS is broken
|
||||
return World.wrap(function (e) {
|
||||
World.send(["jQuery", selector, eventName, e]);
|
||||
});
|
||||
})(selector, eventName);
|
||||
console.log("jQuery", "installing", selector, eventName);
|
||||
$(selector).on(eventName, handler);
|
||||
}
|
||||
newMap[key] = handler;
|
||||
}
|
||||
JQueryEventRouter.prototype.handleEvent = function (e) {
|
||||
if (e.type === "routes" && e.routes.length === 0) {
|
||||
$(this.selector).off(this.eventName, this.handler);
|
||||
World.exit();
|
||||
}
|
||||
for (var key in this.handlerMap) {
|
||||
if (this.handlerMap.hasOwnProperty(key) && !(key in newMap)) {
|
||||
var keyArray = JSON.parse(key);
|
||||
var handler = this.handlerMap[key];
|
||||
var selector = keyArray[0];
|
||||
var eventName = keyArray[1];
|
||||
console.log("jQuery", "removing", selector, eventName);
|
||||
$(selector).off(eventName, handler);
|
||||
}
|
||||
}
|
||||
this.handlerMap = newMap;
|
||||
};
|
||||
|
||||
function JQueryDriver() {
|
||||
var self = this;
|
||||
this.state = new DemandMatcher(true, function (r) {
|
||||
var selector = r.pattern[1];
|
||||
var eventName = r.pattern[2];
|
||||
World.spawn(new JQueryEventRouter(selector, eventName),
|
||||
[pub(["jQuery", selector, eventName, __]),
|
||||
pub(["jQuery", selector, eventName, __], 0, 1)]);
|
||||
});
|
||||
}
|
||||
|
||||
JQueryDriver.prototype.boot = function () {
|
||||
World.updateRoutes([pub(["jQuery", __, __, __], 0, 1)]);
|
||||
World.updateRoutes([pub(["jQuery", __, __, __], 0, 1),
|
||||
sub(["jQuery", __, __, __], 0, 1)]);
|
||||
};
|
||||
|
||||
JQueryDriver.prototype.handleEvent = function (e) {
|
||||
if (e.type === "routes") {
|
||||
this.updateHandlerMap(e.routes);
|
||||
this.state.handleRoutes(e.routes);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -61,19 +52,24 @@ var g = new Ground(function () {
|
|||
World.spawn(new Spy());
|
||||
World.spawn(new JQueryDriver());
|
||||
World.spawn({
|
||||
step: function () { console.log('dummy step'); },
|
||||
// step: function () { console.log('dummy step'); },
|
||||
boot: function () {
|
||||
console.log('dummy boot');
|
||||
World.updateRoutes([sub(["jQuery", "#testButton", "click", __]), sub(__, 1)]);
|
||||
World.updateRoutes([sub(["jQuery", "#testButton", "click", __]),
|
||||
sub(["jQuery", "#testButton2", "click", __]),
|
||||
sub(__, 1)]);
|
||||
World.send({msg: 'hello outer world'}, 1);
|
||||
World.send({msg: 'hello inner world'}, 0);
|
||||
},
|
||||
handleEvent: function (e) {
|
||||
if (e.type === "send" && e.message[0] === "jQuery") {
|
||||
console.log("got a click");
|
||||
World.updateRoutes([]);
|
||||
} else {
|
||||
console.log('dummy handleEvent', e);
|
||||
if (e.message[1] === "#testButton") {
|
||||
console.log("got a click");
|
||||
World.updateRoutes([sub(["jQuery", "#testButton2", "click", __])]);
|
||||
} else {
|
||||
console.log("got a click 2");
|
||||
// World.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
138
marketplace.js
138
marketplace.js
|
@ -81,6 +81,10 @@ 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];
|
||||
};
|
||||
|
||||
function sub(pattern, metaLevel, level) {
|
||||
return new Route(true, pattern, metaLevel, level);
|
||||
}
|
||||
|
@ -126,27 +130,31 @@ function liftRoutes(routes) {
|
|||
return result;
|
||||
}
|
||||
|
||||
function filterEvent(e, routes) {
|
||||
switch (e.type) {
|
||||
case "routes":
|
||||
var result = [];
|
||||
for (var i = 0; i < e.routes.length; i++) {
|
||||
for (var j = 0; j < routes.length; j++) {
|
||||
var ri = e.routes[i];
|
||||
var rj = routes[j];
|
||||
if (ri.isSubscription === !rj.isSubscription
|
||||
&& ri.metaLevel === rj.metaLevel
|
||||
&& ri.level < rj.level)
|
||||
{
|
||||
var u = unify(ri.pattern, rj.pattern);
|
||||
if (u) {
|
||||
var rk = new Route(ri.isSubscription, u.result, ri.metaLevel, ri.level);
|
||||
result.push(rk);
|
||||
}
|
||||
function intersectRoutes(rs1, rs2, ignoreLevels) {
|
||||
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];
|
||||
if (ri.isSubscription === !rj.isSubscription
|
||||
&& ri.metaLevel === rj.metaLevel
|
||||
&& (ignoreLevels || (ri.level < rj.level)))
|
||||
{
|
||||
var u = unify(ri.pattern, rj.pattern);
|
||||
if (u) {
|
||||
var rk = new Route(ri.isSubscription, u.result, ri.metaLevel, ri.level);
|
||||
result.push(rk);
|
||||
}
|
||||
}
|
||||
}
|
||||
return updateRoutes(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function filterEvent(e, routes) {
|
||||
switch (e.type) {
|
||||
case "routes":
|
||||
return updateRoutes(intersectRoutes(e.routes, routes));
|
||||
case "send":
|
||||
for (var i = 0; i < routes.length; i++) {
|
||||
var r = routes[i];
|
||||
|
@ -198,6 +206,10 @@ World.spawn = function (behavior, initialRoutes) {
|
|||
World.current().enqueueAction(spawn(behavior, initialRoutes));
|
||||
};
|
||||
|
||||
World.exit = function (exn) {
|
||||
World.current().killActive(exn);
|
||||
};
|
||||
|
||||
World.withWorldStack = function (stack, f) {
|
||||
var oldStack = World.stack;
|
||||
World.stack = stack;
|
||||
|
@ -229,6 +241,10 @@ World.wrap = function (f) {
|
|||
|
||||
/* Instance methods */
|
||||
|
||||
World.prototype.killActive = function (exn) {
|
||||
this.kill(this.activePid, exn);
|
||||
};
|
||||
|
||||
World.prototype.enqueueAction = function (action) {
|
||||
this.processActions.push([this.activePid, action]);
|
||||
};
|
||||
|
@ -279,9 +295,9 @@ World.prototype.asChild = function (pid, f) {
|
|||
|
||||
World.prototype.kill = function (pid, exn) {
|
||||
if (exn && exn.stack) {
|
||||
console.log("Killed process", pid, exn, exn.stack);
|
||||
console.log("Process exited", pid, exn, exn.stack);
|
||||
} else {
|
||||
console.log("Killed process", pid, exn);
|
||||
console.log("Process exited", pid, exn);
|
||||
}
|
||||
delete this.processTable[pid];
|
||||
this.issueRoutingUpdate();
|
||||
|
@ -386,12 +402,89 @@ World.prototype.handleEvent = function (e) {
|
|||
}
|
||||
};
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* 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.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 (probeRoute.isSubscription === !existingRoute.isSubscription
|
||||
&& probeRoute.metaLevel === existingRoute.metaLevel
|
||||
&& unify(probeRoute.pattern, existingRoute.pattern))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Utilities: matching demand for some service */
|
||||
|
||||
function DemandMatcher(demandSideIsSubscription, demandIncreaseHandler, supplyDecreaseHandler) {
|
||||
this.demandSideIsSubscription = demandSideIsSubscription;
|
||||
this.demandIncreaseHandler = demandIncreaseHandler;
|
||||
this.supplyDecreaseHandler = supplyDecreaseHandler || function (r) {
|
||||
console.error("Unexpected drop in supply for route", r);
|
||||
};
|
||||
this.state = new PresenceDetector();
|
||||
}
|
||||
|
||||
DemandMatcher.prototype.handleRoutes = function (routes) {
|
||||
var changes = this.state.handleRoutes(routes);
|
||||
for (var i = 0; i < changes.added.length; i++) {
|
||||
if (changes.added[i].isSubscription === this.demandSideIsSubscription
|
||||
&& !this.state.presenceExistsFor(changes.added[i]))
|
||||
{
|
||||
this.demandIncreaseHandler(changes.added[i]);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < changes.removed.length; i++) {
|
||||
if (changes.removed[i].isSubscription === !this.demandSideIsSubscription
|
||||
&& this.state.presenceExistsFor(changes.removed[i]))
|
||||
{
|
||||
this.supplyDecreaseHandler(changes.removed[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Ground interface */
|
||||
|
||||
function Ground(bootFn) {
|
||||
var self = this;
|
||||
this.stepperId = null;
|
||||
this.state = new PresenceDetector();
|
||||
World.withWorldStack([this], function () {
|
||||
self.world = new World(bootFn);
|
||||
});
|
||||
|
@ -409,8 +502,9 @@ Ground.prototype.stopStepping = World.prototype.stopStepping;
|
|||
|
||||
Ground.prototype.enqueueAction = function (action) {
|
||||
if (action.type === 'routes') {
|
||||
if (action.routes.length > 0) {
|
||||
console.error("You have subscribed to a nonexistent event source.", action);
|
||||
var added = this.state.handleRoutes(action.routes).added;
|
||||
if (added.length > 0) {
|
||||
console.error("You have subscribed to a nonexistent event source.", added);
|
||||
}
|
||||
} else {
|
||||
console.error("You have sent a message into the outer void.", action);
|
||||
|
|
Loading…
Reference in New Issue