From 9312a28226c0b6f664077d25ec32359fc63a0b18 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 17 May 2016 20:04:37 -0400 Subject: [PATCH] Only fire asserted/retracted when first/last interest (dis)/appears; same intent as yesterday's commit b1f7816 --- js/compiler/demo-proper-interest-tracking.js | 86 ++++++++++++++++++++ js/src/actor.js | 39 ++++++++- js/src/trie.js | 33 ++++++++ 3 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 js/compiler/demo-proper-interest-tracking.js diff --git a/js/compiler/demo-proper-interest-tracking.js b/js/compiler/demo-proper-interest-tracking.js new file mode 100644 index 0000000..6beb22b --- /dev/null +++ b/js/compiler/demo-proper-interest-tracking.js @@ -0,0 +1,86 @@ +// Illustrates the response of asserted / retracted / during to +// observation of assertions discarding some of their dimensions. +// +// Should eventually be turned into some kind of test case. + +var Syndicate = require('./src/main.js'); + +var Dataspace = Syndicate.Dataspace; +var Patch = Syndicate.Patch; + +var __ = Syndicate.__; + +assertion type ready(what); +assertion type entry(key, val); + +ground dataspace { + actor { + react { + assert ready("listener"); + on asserted entry($key, _) { + console.log('key asserted', key); + react { + on asserted entry(key, $value) { console.log('binding', key, '--->', value); } + on retracted entry(key, $value) { console.log('binding', key, '-/->', value); } + } until { + case retracted entry(key, _) { + console.log('key retracted', key); + } + } + } + } + } + + actor { + react { + assert ready('other-listener'); + during entry($key, _) { + do { console.log('(other-listener) key asserted', key); } + during entry(key, $value) { + do { console.log('(other-listener) binding', key, '--->', value); } + finally { console.log('(other-listener) binding', key, '-/->', value); } + } + finally { console.log('(other-listener) key retracted', key); } + } + } + } + + function pause(k) { + console.log('pause'); + react { + assert ready('pause'); + } until { + case asserted ready("pause") { + return k(); + } + } + } + + actor { + react until { + case asserted ready("listener") { + react until { + case asserted ready('other-listener') { + Dataspace.stateChange(Patch.assert(entry('a', 1))); + Dataspace.stateChange(Patch.assert(entry('a', 2))); + Dataspace.stateChange(Patch.assert(entry('b', 3))); + Dataspace.stateChange(Patch.assert(entry('c', 33))); + Dataspace.stateChange(Patch.assert(entry('a', 4))); + Dataspace.stateChange(Patch.assert(entry('a', 5))); + pause(function () { + Dataspace.stateChange(Patch.retract(entry('a', 2))); + Dataspace.stateChange(Patch.retract(entry('c', 33))); + Dataspace.stateChange(Patch.assert(entry('a', 9))); + pause(function () { + Dataspace.stateChange(Patch.retract(entry('a', __))); + pause(function () { + console.log('done'); + }); + }); + }); + } + } + } + } + } +} diff --git a/js/src/actor.js b/js/src/actor.js index 0cb0f3f..0a5995a 100644 --- a/js/src/actor.js +++ b/js/src/actor.js @@ -18,6 +18,7 @@ function Actor(state, bootFn) { this.state = state; this.facets = Immutable.Set(); this.mux = new Mux.Mux(); + this.previousKnowledge = Trie.emptyTrie; this.knowledge = Trie.emptyTrie; this.pendingActions = []; @@ -32,6 +33,7 @@ function Actor(state, bootFn) { Actor.prototype.handleEvent = function(e) { if (e.type === 'stateChange') { + this.previousKnowledge = this.knowledge; this.knowledge = e.patch.updateInterests(this.knowledge); } if (this.pendingActions.length > 0) { @@ -81,6 +83,7 @@ function Facet(actor) { this.doneBlocks = Immutable.List(); this.children = Immutable.Set(); this.parent = Facet.current; + this.terminated = false; } Facet.current = null; @@ -146,10 +149,18 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec : e.patch.removed, spec); if (objects && objects.size > 0) { - // console.log(objects.toArray()); - if (isTerminal) { facet.terminate(); } facet.actor.pushAction(function () { - objects.forEach(function (o) { Util.kwApply(handlerFn, facet.actor.state, o); }); + var shouldTerminate = isTerminal; + objects.forEach(function (o) { + var instantiated = Trie.instantiateProjection(spec, o); + if (facet.interestWas(eventType, instantiated)) { + if (shouldTerminate) { + shouldTerminate = false; + facet.terminate(); + } + Util.kwApply(handlerFn, facet.actor.state, o); + } + }); }); } } @@ -175,6 +186,20 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec } }; +Facet.prototype.interestWas = function(assertedOrRetracted, pat) { + function orStar(a, b) { return (a || b); } + var oldExists = Trie.matchValue(this.actor.previousKnowledge, pat, false, orStar); + var newExists = Trie.matchValue(this.actor.knowledge, pat, false, orStar); + switch (assertedOrRetracted) { + case 'asserted': + return !oldExists && newExists; + case 'retracted': + return oldExists && !newExists; + default: + throw new Error("Unexpected assertedOrRetracted in Facet.interestWas: " + assertedOrRetracted); + } +}; + Facet.prototype.addEndpoint = function(endpoint) { var patch = endpoint.subscriptionFn.call(this.actor.state); var r = this.actor.mux.addStream(patch); @@ -220,20 +245,28 @@ Facet.prototype.completeBuild = function() { Facet.prototype.terminate = function() { var facet = this; + + if (facet.terminated) return; + facet.terminated = true; + var aggregate = Patch.emptyPatch; this.endpoints.forEach(function(endpoint, eid) { var r = facet.actor.mux.removeStream(eid); aggregate = aggregate.andThen(r.deltaAggregate); }); Dataspace.stateChange(aggregate); + this.endpoints = Immutable.Map(); if (this.parent) { this.parent.children = this.parent.children.remove(this); } + this.actor.removeFacet(this); + withCurrentFacet(facet, function () { facet.doneBlocks.forEach(function(b) { b.call(facet.actor.state); }); }); + this.children.forEach(function (child) { child.terminate(); }); diff --git a/js/src/trie.js b/js/src/trie.js index 807db06..c8c015a 100644 --- a/js/src/trie.js +++ b/js/src/trie.js @@ -765,6 +765,38 @@ function projectObjects(m, projection) { return trieKeysToObjects(trieKeys(project(m, projection), names.length), names); } +// (Projection, CaptureObject) -> Pattern +function instantiateProjection(p, obj) { + var captureCount = 0; + return walk(p); + + function walk(p) { + if (isCapture(p)) { + var thisCapture = captureCount++; + var name = captureName(p); + return (name !== null) ? obj[name] : obj['$' + thisCapture]; + } + + if (Array.isArray(p)) { + var result = []; + for (var i = 0; i < p.length; i++) { + result.push(walk(p[i])); + } + return result; + } + + if (Struct.isStructure(p)) { + var resultFields = []; + for (var i = 0; i < p.meta.arity; i++) { + resultFields[i] = walk(p[i]); + } + return new Struct.Structure(p.meta, resultFields); + } + + return p; + } +} + function prettyTrie(m, initialIndent) { var acc = []; walk(initialIndent || 0, m); @@ -928,6 +960,7 @@ module.exports.trieKeys = trieKeys; module.exports.captureToObject = captureToObject; module.exports.trieKeysToObjects = trieKeysToObjects; module.exports.projectObjects = projectObjects; +module.exports.instantiateProjection = instantiateProjection; module.exports.prettyTrie = prettyTrie; module.exports.trieToJSON = trieToJSON; module.exports.trieFromJSON = trieFromJSON;