diff --git a/js/src/mux.js b/js/src/mux.js index e15c0d6..f244845 100644 --- a/js/src/mux.js +++ b/js/src/mux.js @@ -42,16 +42,17 @@ Mux.prototype.updateStream = function (pid, unclampedPatch) { deltaAggregate: deltaAggregate }; }; -function computePatches(oldMux, newMux, updateStreamResult) { +function computeEvents(oldMux, newMux, updateStreamResult) { var actingPid = updateStreamResult.pid; var delta = updateStreamResult.delta; var deltaAggregate = updateStreamResult.deltaAggregate; var oldRoutingTable = oldMux.routingTable; var newRoutingTable = newMux.routingTable; - var affectedPids = computeAffectedPids(oldRoutingTable, delta).remove("meta"); + var affectedPids = computeAffectedPids(oldRoutingTable, delta).add(actingPid).remove("meta"); return { eventMap: Immutable.Map().withMutations(function (result) { affectedPids.forEach(function (pid) { + var patchForPid; if (pid === actingPid) { var part1 = new Patch.Patch(Patch.biasedIntersection(newRoutingTable, delta.added), Patch.biasedIntersection(oldRoutingTable, delta.removed)); @@ -59,9 +60,12 @@ function computePatches(oldMux, newMux, updateStreamResult) { newMux.interestsOf(pid)), Patch.biasedIntersection(deltaAggregate.removed, oldMux.interestsOf(pid))); - results.set(pid, part1.unsafeUnion(part2)); + patchForPid = part1.unsafeUnion(part2); } else { - result.set(pid, updateStreamResult.deltaAggregate.viewFrom(oldMux.interestsOf(pid))); + patchForPid = updateStreamResult.deltaAggregate.viewFrom(oldMux.interestsOf(pid)); + } + if (patchForPid.isNonEmpty()) { + result.set(pid, patchForPid); } }); }), @@ -97,5 +101,5 @@ Mux.prototype.interestsOf = function (pid) { /////////////////////////////////////////////////////////////////////////// module.exports.Mux = Mux; -module.exports.computePatches = computePatches; +module.exports.computeEvents = computeEvents; module.exports.computeAffectedPids = computeAffectedPids; diff --git a/js/src/patch.js b/js/src/patch.js index 964f4d9..507e9dc 100644 --- a/js/src/patch.js +++ b/js/src/patch.js @@ -63,6 +63,11 @@ function unpub(p, metaLevel) { /////////////////////////////////////////////////////////////////////////// +Patch.prototype.equals = function (other) { + if (!(other instanceof Patch)) return false; + return Immutable.is(this.added, other.added) && Immutable.is(this.removed, other.removed); +}; + Patch.prototype.isEmpty = function () { return this.added === Route.emptyTrie && this.removed === Route.emptyTrie; }; diff --git a/js/src/route.js b/js/src/route.js index 4f943e6..30a44a9 100644 --- a/js/src/route.js +++ b/js/src/route.js @@ -656,7 +656,12 @@ function trieStep(m, key) { if (is_emptyTrie(m)) return emptyTrie; if (m instanceof $WildcardSequence) return (is_keyClose(key) ? m.trie : m); if (m instanceof $Success) return emptyTrie; - return rlookupWild(m, key) || rlookup(m, __); + var result = rlookupWild(m, key); + if (result) return result; + var wildEdge = rlookup(m, __); + if (is_keyOpen(key)) return rwildseq(wildEdge); + if (is_keyClose(key)) return (wildEdge instanceof $WildcardSequence) ? wildEdge.trie : emptyTrie; + return wildEdge; } function relabel(m, f) { @@ -1025,7 +1030,7 @@ function prettyTrie(m, initialIndent) { module.exports.__ = __; module.exports.SOA = SOA; -module.exports.EOA = SOA; +module.exports.EOA = EOA; module.exports.$Capture = $Capture; module.exports.$Special = $Special; module.exports._$ = _$; @@ -1050,3 +1055,11 @@ module.exports.trieKeys = trieKeys; module.exports.trieKeysToObjects = trieKeysToObjects; module.exports.projectObjects = projectObjects; module.exports.prettyTrie = prettyTrie; + +// For testing +module.exports._testing = { + rsuccess: rsuccess, + rseq: rseq, + rwild: rwild, + rwildseq: rwildseq +}; diff --git a/js/test/test-mux.js b/js/test/test-mux.js index dfdf244..f269a38 100644 --- a/js/test/test-mux.js +++ b/js/test/test-mux.js @@ -19,14 +19,14 @@ function checkPrettyPatch(p, expectedAdded, expectedRemoved) { '>>>>>>>>\n')); } -describe('mux stream', function () { - function getM() { - var m = new Mux.Mux(); - expect(m.addStream(Patch.assert(1).andThen(Patch.assert(2))).pid).to.equal(0); - expect(m.addStream(Patch.assert(3).andThen(Patch.assert(2))).pid).to.equal(1); - return m; - } +function getM() { + var m = new Mux.Mux(); + expect(m.addStream(Patch.assert(1).andThen(Patch.assert(2))).pid).to.equal(0); + expect(m.addStream(Patch.assert(3).andThen(Patch.assert(2))).pid).to.equal(1); + return m; +} +describe('mux stream', function () { describe('addition', function () { it('should union interests appropriately', function () { var m = getM(); @@ -75,5 +75,183 @@ describe('mux stream', function () { [' 3 >{[1]}']); }); }); + + describe('removal', function () { + it('should remove streams properly', function () { + var m = getM(); + var updateStreamResult = m.removeStream(1); + expect(updateStreamResult.pid).to.equal(1); + checkPrettyPatch(updateStreamResult.delta, + ['::: nothing'], + [' 2 >{[1]}', + ' 3 >{[1]}']); + checkPrettyTrie(m.routingTable, [' 1 >{[0]}', + ' 2 >{[0]}']); + checkPrettyTrie(m.interestsOf(0), [' 1 >{[0]}', + ' 2 >{[0]}']); + checkPrettyTrie(m.interestsOf(1), ['::: nothing']); + checkPrettyPatch(updateStreamResult.deltaAggregate, + ['::: nothing'], + [' 3 >{[1]}']); + }); + }); }); +describe('computeEvents', function () { + describe('for new assertions and existing specific interest', function () { + var oldM = new Mux.Mux(); + oldM.addStream(Patch.sub(1)); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.addStream(Patch.assert(1).andThen(Patch.assert(2))); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should yield just the assertion of interest', function () { + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(0).strip().equals(Patch.assert(1))).to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }); + + describe('for removed assertions', function () { + var oldM = new Mux.Mux(); + oldM.addStream(Patch.sub([__])); + var newM = oldM.shallowCopy(); + newM.addStream(Patch.assert([1]).andThen(Patch.assert([2]))); + var updateStreamResult = newM.updateStream(1, Patch.retract([1])); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should yield just the specific retraction', function () { + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(0).strip().equals(Patch.retract([1]))).to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }); + + describe('for new assertions and existing general interest', function () { + var oldM = new Mux.Mux(); + oldM.addStream(Patch.sub([__])); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.addStream(Patch.assert([1]).andThen(Patch.assert([2]))); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should yield both new assertions', function () { + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(0).strip().equals(Patch.assert([1]).andThen(Patch.assert([2])))) + .to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }) + + describe('for new specific interest and existing assertion', function () { + var oldM = new Mux.Mux(); + oldM.addStream(Patch.assert(1).andThen(Patch.assert(2))); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.addStream(Patch.sub(1)); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should yield just the assertion of interest', function () { + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(1).strip().equals(Patch.assert(1))).to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }); + + describe('for new general interest and existing assertion', function () { + var oldM = new Mux.Mux(); + oldM.addStream(Patch.assert([1]).andThen(Patch.assert([2]))); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.addStream(Patch.sub([__])); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should yield just the assertion of interest', function () { + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(1).strip().equals(Patch.assert([1]).andThen(Patch.assert([2])))) + .to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }); + + describe('for removed general interest', function () { + var oldM = new Mux.Mux(); + oldM.addStream(Patch.assert([1]).andThen(Patch.assert([2]))); + oldM.addStream(Patch.sub([__])); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.updateStream(1, Patch.unsub([__])); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should yield both retractions', function () { + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(1).strip().equals(Patch.retract([1]).andThen(Patch.retract([2])))) + .to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }); + + describe('for removed specific interest', function () { + it('should yield three appropriate events for three intercessions', function () { + var oldM = new Mux.Mux(); + oldM.addStream(Patch.assert([1]).andThen(Patch.assert([2]))); + oldM.addStream(Patch.sub([__])); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.updateStream(1, Patch.unsub([1])); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(1).strip().equals(Patch.retract([1]))).to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + + oldM = newM; + newM = oldM.shallowCopy(); + events = Mux.computeEvents(oldM, newM, newM.updateStream(1, Patch.unsub([2]))); + + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(1).strip().equals(Patch.retract([2]))) + .to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + + oldM = newM; + newM = oldM.shallowCopy(); + events = Mux.computeEvents(oldM, newM, newM.updateStream(0, Patch.assert([3]))); + + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(1).strip().equals(Patch.assert([3]))).to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }); + + describe('for new meta assertion', function () { + var oldM = new Mux.Mux(); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.addStream(Patch.assert(Patch.atMeta(1)).andThen(Patch.assert(2))); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should yield no local events and one meta event', function () { + expect(events.eventMap.size).to.be(0); + expect(events.metaEvents.size).to.be(1); + expect(events.metaEvents.get(0).strip().equals(Patch.assert(1))).to.be(true); + }); + }); + + describe('for adding supply and demand simultaneously', function () { + var oldM = new Mux.Mux(); + var newM = oldM.shallowCopy(); + var updateStreamResult = newM.addStream(Patch.assert(1).andThen(Patch.sub(1))); + var events = Mux.computeEvents(oldM, newM, updateStreamResult); + + it('should send a patch back', function () { + expect(events.eventMap.size).to.be(1); + expect(events.metaEvents.size).to.be(1); + expect(events.eventMap.get(0).strip().equals(Patch.assert(1))).to.be(true); + expect(events.metaEvents.get(0).equals(Patch.emptyPatch)).to.be(true); + }); + }); +}); diff --git a/js/test/test-route.js b/js/test/test-route.js index 14e41af..889f736 100644 --- a/js/test/test-route.js +++ b/js/test/test-route.js @@ -408,3 +408,11 @@ describe("Projection with no captures", function () { [' "X" ★ >{["A"]}']); }); }); + +describe('trieStep', function () { + it('should expand wildcard when given SOA', function () { + expect(Immutable.is(r.trieStep(r.compilePattern(true, r.__), r.SOA), + r._testing.rwildseq(r._testing.rseq(r.EOA, r._testing.rsuccess(true))))) + .to.be(true); + }); +});