syndicate-2017/js/src/demand-matcher.js

142 lines
4.8 KiB
JavaScript

var Immutable = require('immutable');
var Trie = require('./trie.js');
var Patch = require('./patch.js');
var Util = require('./util.js');
function ensureMatchingProjectionNames(specs) {
if (!(specs.length > 0)) {
throw new Error("Syndicate: DemandMatcher needs at least one spec");
}
var names = null;
specs.forEach(function (spec) {
if (names === null) {
names = Trie.projectionNames(spec);
} else {
if (JSON.stringify(names) !== JSON.stringify(Trie.projectionNames(spec))) {
throw new Error("Syndicate: DemandMatcher needs identical capture names");
}
}
});
return names;
}
function defaultHandler(side, movement) {
return function (captures) {
console.error("Syndicate: Unhandled "+movement+" in "+side, captures);
};
}
function DemandMatcher(demandSpecs, supplySpecs, options) {
options = Util.extend({
metaLevel: 0,
demandMetaLevel: null,
supplyMetaLevel: null,
onDemandIncrease: defaultHandler('demand', 'increase'),
onDemandDecrease: function (captures) {},
onSupplyIncrease: function (captures) {},
onSupplyDecrease: defaultHandler('supply', 'decrease')
}, options);
this.demandProjectionNames = ensureMatchingProjectionNames(demandSpecs);
this.supplyProjectionNames = ensureMatchingProjectionNames(supplySpecs);
this.demandSpecs = demandSpecs;
this.supplySpecs = supplySpecs;
this.demandPatterns = demandSpecs.map(function (s) { return Trie.projectionToPattern(s); });
this.supplyPatterns = supplySpecs.map(function (s) { return Trie.projectionToPattern(s); });
this.demandMetaLevel =
(options.demandMetaLevel === null) ? options.metaLevel : options.demandMetaLevel;
this.supplyMetaLevel =
(options.supplyMetaLevel === null) ? options.metaLevel : options.supplyMetaLevel;
function metaWrap(n) {
return function (s) { return Patch.prependAtMeta(s, n); };
}
this.demandProjections = demandSpecs.map(metaWrap(this.demandMetaLevel));
this.supplyProjections = supplySpecs.map(metaWrap(this.supplyMetaLevel));
this.onDemandIncrease = options.onDemandIncrease;
this.onDemandDecrease = options.onDemandDecrease;
this.onSupplyIncrease = options.onSupplyIncrease;
this.onSupplyDecrease = options.onSupplyDecrease;
this.currentDemand = Immutable.Set();
this.currentSupply = Immutable.Set();
}
DemandMatcher.prototype.boot = function () {
var p = Patch.emptyPatch;
function extend(ml) {
return function (pat) { p = p.andThen(Patch.sub(pat, ml)); };
}
this.demandPatterns.forEach(extend(this.demandMetaLevel));
this.supplyPatterns.forEach(extend(this.supplyMetaLevel));
return p;
};
DemandMatcher.prototype.handleEvent = function (e) {
if (e.type === "stateChange") {
this.handlePatch(e.patch);
}
};
DemandMatcher.prototype.extractKeys = function (trie, projections, keyCount, whichSide) {
var ks = Immutable.Set();
projections.forEach(function (proj) {
var moreKs = Trie.trieKeys(Trie.project(trie, proj), keyCount);
if (!moreKs) {
throw new Error("Syndicate: wildcard "+whichSide+" detected:\n" +
JSON.stringify(proj) + "\n" +
Trie.prettyTrie(trie));
}
ks = ks.union(moreKs);
});
return ks;
};
DemandMatcher.prototype.handlePatch = function (p) {
var self = this;
var dN = self.demandProjectionNames.length;
var sN = self.supplyProjectionNames.length;
var addedDemand = this.extractKeys(p.added, self.demandProjections, dN, 'demand');
var removedDemand = this.extractKeys(p.removed, self.demandProjections, dN, 'demand');
var addedSupply = this.extractKeys(p.added, self.supplyProjections, sN, 'supply');
var removedSupply = this.extractKeys(p.removed, self.supplyProjections, sN, 'supply');
self.currentSupply = self.currentSupply.union(addedSupply);
self.currentDemand = self.currentDemand.subtract(removedDemand);
removedSupply.forEach(function (captures) {
if (self.currentDemand.has(captures)) {
self.onSupplyDecrease(Trie.captureToObject(captures, self.supplyProjectionNames));
}
});
addedSupply.forEach(function (captures) {
if (!self.currentDemand.has(captures)) {
self.onSupplyIncrease(Trie.captureToObject(captures, self.supplyProjectionNames));
}
});
removedDemand.forEach(function (captures) {
if (self.currentSupply.has(captures)) {
self.onDemandDecrease(Trie.captureToObject(captures, self.demandProjectionNames));
}
});
addedDemand.forEach(function (captures) {
if (!self.currentSupply.has(captures)) {
self.onDemandIncrease(Trie.captureToObject(captures, self.demandProjectionNames));
}
});
self.currentSupply = self.currentSupply.subtract(removedSupply);
self.currentDemand = self.currentDemand.union(addedDemand);
};
///////////////////////////////////////////////////////////////////////////
module.exports.DemandMatcher = DemandMatcher;