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