Rewrite JS DemandMatcher to handle important latency-related corner cases.

This commit is contained in:
Tony Garnock-Jones 2016-05-13 20:13:09 -04:00
parent 68cde5be6c
commit 628ba87c54
7 changed files with 590 additions and 117 deletions

57
doc/demand-matcher.dot Normal file
View File

@ -0,0 +1,57 @@
digraph G {
node[shape=box];
// s0000 idle
// s1000 error
// s0100 supply
// s1100 running
// s1010 starting
// s0011 starting_unwanted
// s1011 starting_doomed
// s0101 unwanted
// s1101 running_doomed
idle -> starting [label="D+/start"];
supply -> starting [label="D+,S-/start"];
error -> idle [label="D-"];
error -> running [label="S+"];
error -> unwanted [label="D-,S+"];
running -> unwanted [label="D-"];
running -> error [label="S-/error"];
unwanted -> idle [label="S-"];
unwanted -> starting [label="D+,S-/start"];
running_doomed -> starting [label="S-/start"];
running_doomed -> idle [label="D-,S-"];
starting -> starting_unwanted [label="D-"];
starting -> running [label="S+"];
starting -> unwanted [label="D-,S+"];
starting_unwanted -> unwanted [label="S+"];
starting_unwanted -> running_doomed [label="D+,S+"];
starting_doomed -> running_doomed [label="S+"];
starting_doomed -> unwanted [label="D-,S+"];
idle -> supply [label="S+"];
idle -> running [label="D+S+"];
supply -> running [label="D+"];
supply -> idle [label="S-"];
running -> idle [label="D-,S-"];
unwanted -> running_doomed [label="D+"];
running_doomed -> unwanted [label="D-"];
starting_unwanted -> starting_doomed [label="D+"];
starting_doomed -> starting_unwanted [label="D-"];
// s0001 -> impossible [label="any"];
// s0010 -> impossible [label="any"];
// s1001 -> impossible [label="any"];
// s0110 -> impossible [label="any"];
// s1110 -> impossible [label="any"];
// s0111 -> impossible [label="any"];
// s1111 -> impossible [label="any"];
}

376
doc/demand-matcher.md Normal file
View File

@ -0,0 +1,376 @@
# Demand-matching and Supervision
The Demand Matcher pattern (in `demand-matcher.rkt` and in
`demand-matcher.js`'s `DemandMatcher` class) tracks assertions
representing some abstract *demand* for a resource, and causes the
creation or acquisition of matching *supply* of that resource.
To do this, it tracks the state of each *instance* of the resource.
Each resource instance (called a "task") is uniquely identified by a
projection of the dataspace.
The basic idea is that:
- When demand for a task is detected, it is started.
- Each started task signals its presence to the DemandMatcher.
- When demand drops, the task should detect this and exit.
- If the task exits unexpectedly, this is an error, and the
DemandMatcher prints a warning.
## Latency causes problems
However, because there can be some latency between requesting the
start of a task and its signalling its presence to the DemandMatcher,
we can't just figure out what to do based on the presence or absence
of demand and supply for a task. We also need to track a few more bits
of information.
When demand for a task drops briefly, we *expect* a drop in supply,
*even demand increases again before we detect a supply drop*. For this
reason, in some circumstances, the default task supervision strategy
of DemandMatcher *recreates* supply on supply drop in some
circumstances. It keeps track of whether a supply increase is
expected, and of whether a supply decrease is expected for each task.
It becomes an important part of the DemandMatcher protocol for a task
instance to always drop its supply assertion in response to a drop in
demand. This works well in Syndicate implementations that preserve all
assertion transitions, but not at all well where brief transitions may
be elided. In those cases, we will have to reach for a more heuristic
approach involving something akin to Erlang's "Maximum Restart
Intensity" and/or other kinds of time-based decision. For now though,
the precise case works fine.
While it seems simple enough to imagine, the details are rather
fiddly.
## Working out the algorithm that defaultTaskSupervisor should use
We may assume some expected task behaviour: that it will eventually
assert supply, and *then* upon demand drop eventually exit.
◇(supply ∧ (¬demand ⇒ ◇ terminate)) (?!?!)
### Complete table of actions
Each row in this table describes actions taken in a particular
circumstance by `defaultTaskSupervisor`.
The table has seven columns:
- `∃D`, whether demand for the task exists currently
- `∃S`, whether supply for the task exists currently
- `ΔD`, whether (and in which direction) demand is changing now
- `ΔS`, whether (and in which direction) supply is changing now
- `expS+`, whether we expect a supply increase at some point in future
- `expS-`, whether we expect a supply decrease at some point in future
- and an action to take in this circumstance.
The first two values are drawn from the state of the DemandMatcher;
the second two, from the patch event the DemandMatcher is currently
processing; and the third two are private state variables of the task
supervisor itself.
∃D ∃S ΔD ΔS expS+ expS- Action
---------------------------------------------------------------------------
- - + - - Start task, set expS+
- - + - - No action (but slightly weird)
- - + + - - No action (but slightly weird)
- Y + - - No action (pre-extant supply)
- Y - - - No action
- Y + - - - Start task, set expS+
Y - - - - Demand goes after unexpected supply drop
Y - + - - Spontaneous recovery from unexpected supply drop
Y - - + - - Spontaneous recovery from unexpected supply drop; set expS-
Y Y - - - Set expS-
Y Y - - - Unexpected supply drop error
Y Y - - - - No action (but slightly weird)
- - + - Y Impossible (expS- would be clear or expS+ set)
- - + - Y Impossible (expS- would be clear or expS+ set)
- - + + - Y Impossible (expS- would be clear or expS+ set)
- Y + - Y No action
- Y - - Y Clear expS-
- Y + - - Y Clear expS-, start task, set expS+
Y - - - Y Impossible (expS+ would be set)
Y - + - Y Impossible (expS+ would be set)
Y - - + - Y Impossible (expS+ would be set)
Y Y - - Y No action
Y Y - - Y Clear expS-, start task, set expS+
Y Y - - - Y Clear expS-
- - + Y - Impossible (expS+ would be clear or expS- set)
- - + Y - Impossible (expS+ would be clear or expS- set)
- - + + Y - Impossible (expS+ would be clear or expS- set)
- Y + Y - Impossible (expS+ would be clear)
- Y - Y - Impossible (expS+ would be clear)
- Y + - Y - Impossible (expS+ would be clear)
Y - - Y - Set expS-
Y - + Y - Clear expS+
Y - - + Y - Clear expS+, set expS-
Y Y - Y - Impossible (expS+ would be clear)
Y Y - Y - Impossible (expS+ would be clear)
Y Y - - Y - Impossible (expS+ would be clear)
- - + Y Y No action
- - + Y Y Clear expS+
- - + + Y Y Clear expS+
- Y + Y Y Impossible (expS+ would be clear)
- Y - Y Y Impossible (expS+ would be clear)
- Y + - Y Y Impossible (expS+ would be clear)
Y - - Y Y No action
Y - + Y Y Clear expS+
Y - - + Y Y Clear expS+
Y Y - Y Y Impossible (expS+ would be clear)
Y Y - Y Y Impossible (expS+ would be clear)
Y Y - - Y Y Impossible (expS+ would be clear)
#### Actions and transitions involving actions
From the table, we learn that the possible actions are:
- `START`, Start task, set expS+
- `EXPDROP`, Set expS-
- `GOTDROP`, Clear expS-
- `RUNNING`, Clear expS+
There are also a couple of pseudo-actions, `ERROR` for an unexpected
supply drop, and `RECOVER` for circumstances marking spontaneous
recovery after an unexpected supply drop.
The final four columns in this table are the new states of the
DemandMatcher and the task supervisor.
∃D ∃S ΔD ΔS expS+ expS- Actions Next: ∃D ∃S expS+ expS-
---------------------------------------------------------------------------
- - + - - START Y - Y -
- Y + - - - START Y - Y -
Y - - - - RECOVERY - - - -
Y - + - - RECOVER Y Y - -
Y - - + - - RECOVER EXPDROP - Y - Y
Y Y - - - EXPDROP - Y - Y
Y Y - - - ERROR Y - - -
- Y - - Y GOTDROP - - - -
- Y + - - Y GOTDROP START Y - Y -
Y Y - - Y GOTDROP START Y - Y -
Y Y - - - Y GOTDROP - - - -
Y - - Y - EXPDROP - - Y Y
Y - + Y - RUNNING Y Y - -
Y - - + Y - RUNNING EXPDROP - Y - Y
- - + Y Y RUNNING - Y - Y
- - + + Y Y RUNNING Y Y - Y
Y - + Y Y RUNNING Y Y - Y
Y - - + Y Y RUNNING - Y - Y
#### Impossible states
Some states are impossible to reach.
It is impossible for neither supply nor demand to exist, when either
but not both of a rise or a drop in supply is expected:
∃D ∃S ΔD ΔS expS+ expS-
---------------------------------------------------------------------------
- - - Y Impossible (expS- would be clear or expS+ set)
- - Y - Impossible (expS+ would be clear or expS- set)
It is impossible for demand but no supply to exist, when a drop in
supply is expected but no rise in supply is expected:
∃D ∃S ΔD ΔS expS+ expS-
---------------------------------------------------------------------------
Y - - Y Impossible (expS+ would be set)
It is impossible for supply to exist while a rise in supply is
expected:
∃D ∃S ΔD ΔS expS+ expS-
---------------------------------------------------------------------------
- Y Y - Impossible (expS+ would be clear)
Y Y Y - Impossible (expS+ would be clear)
- Y Y Y Impossible (expS+ would be clear)
Y Y Y Y Impossible (expS+ would be clear)
#### Transitions involving only DemandMatcher state change
Where no task supervisor state changes and no actions are needed:
∃D ∃S ΔD ΔS expS+ expS- Actions Next: ∃D ∃S expS+ expS-
---------------------------------------------------------------------------
- - + - - - Y - -
- - + + - - Y Y - -
- Y + - - Y Y - -
- Y - - - - - - -
Y Y - - - - - - - -
- Y + - Y Y Y - Y
Y Y - - Y - Y - Y
- - + Y Y Y - Y Y
Y - - Y Y - - Y Y
### Transition diagram
![DemandMatcher task supervisor transition diagram](demand-matcher.png)
### From state machine to implementation
We can give the reachable states reasonable names:
∃D ∃S expS+ expS- Name
---------------------------------------
- - - - IDLE
Y - Y - STARTING
Y Y - - RUNNING
- Y - Y UNWANTED
Y - Y Y STARTING_DOOMED
- - Y Y STARTING_UNWANTED
Y Y - Y RUNNING_DOOMED
- Y - - SUPPLY
Y - - - ERROR
However, writing out the full state machine in terms of these states
doesn't exploit all the redundancy in the machine.
Instead, let's group transitions by their effects on the task
supervisor's state, the "expected" bits. There are only four possible
actions (excluding warnings related to recovery etc.):
START - set expS+ (and start a task instance)
RUNNING - clear expS+
EXPDROP - set expS-
GOTDROP - clear expS-
---------------------------------------------------------------------------
Leave expS+ alone, set expS-:
Y - - + - - EXPDROP - Y - Y
Y Y - - - EXPDROP - Y - Y
Y - - Y - EXPDROP - - Y Y
Leave expS+ alone, clear expS-:
- Y - - Y GOTDROP - - - -
Y Y - - - Y GOTDROP - - - -
Set expS+, leave expS- alone:
- - + - - START Y - Y -
- Y + - - - START Y - Y -
Set expS+, clear expS-:
- Y + - - Y START GOTDROP Y - Y -
Y Y - - Y START GOTDROP Y - Y -
Clear expS+, leave expS- alone:
Y - + Y - RUNNING Y Y - -
- - + Y Y RUNNING - Y - Y
- - + + Y Y RUNNING Y Y - Y
Y - + Y Y RUNNING Y Y - Y
Y - - + Y Y RUNNING - Y - Y
Clear expS+, set expS-:
Y - - + Y - RUNNING EXPDROP - Y - Y
---------------------------------------------------------------------------
Now, let's look at those grouped by specific action (some rows will
appear twice, because some rows involve more than one action):
Expdrop:
Y - - + - - EXPDROP - Y - Y
Y Y - - - EXPDROP - Y - Y
Y - - Y - EXPDROP - - Y Y
Y - - + Y - RUNNING EXPDROP - Y - Y
- "Set expS- whenever a drop in demand is detected, and either (a)
increase in supply is detected, (b) supply exists and is not
falling, or (c) supply is expected to exist."
Gotdrop:
- Y - - Y GOTDROP - - - -
Y Y - - - Y GOTDROP - - - -
- Y + - - Y START GOTDROP Y - Y -
Y Y - - Y START GOTDROP Y - Y -
- "Clear expS- whenever a drop in supply is detected."
Start:
- - + - - START Y - Y -
- Y + - - - START Y - Y -
- Y + - - Y START GOTDROP Y - Y -
Y Y - - Y START GOTDROP Y - Y -
- "Set expS+ and start a task whenever expS+ is clear and demand
becomes or remains high and supply becomes or remains low UNLESS
demand is already high, supply drops, and expS- is clear, which is
the 'unexpected drop' error case."
Running:
Y - + Y - RUNNING Y Y - -
- - + Y Y RUNNING - Y - Y
- - + + Y Y RUNNING Y Y - Y
Y - + Y Y RUNNING Y Y - Y
Y - - + Y Y RUNNING - Y - Y
Y - - + Y - RUNNING EXPDROP - Y - Y
- "Clear expS+ whenever supply increases."
---------------------------------------------------------------------------
Now let's take those rules and check them against the full rulebase:
"Set expS- whenever a drop in demand is detected, and either (a)
increase in supply is detected, (b) supply exists and is not
falling, or (c) supply is expected to exist."
Y - - + - - RECOVER EXPDROP - Y - Y
Y Y - - - EXPDROP - Y - Y
Y - - Y - EXPDROP - - Y Y
Y - - + Y - RUNNING EXPDROP - Y - Y
Y - - + Y Y RUNNING - Y - Y
Y Y - - Y - Y - Y
Y - - Y Y - - Y Y
"Clear expS- whenever a drop in supply is detected."
- Y + - - - START Y - Y -
Y Y - - - ERROR Y - - -
- Y - - Y GOTDROP - - - -
- Y + - - Y GOTDROP START Y - Y -
Y Y - - Y GOTDROP START Y - Y -
Y Y - - - Y GOTDROP - - - -
- Y - - - - - - -
Y Y - - - - - - - -
"Set expS+ and start a task whenever expS+ is clear and demand
becomes or remains high and supply becomes or remains low UNLESS
demand is already high, supply drops, and expS- is clear, which is
the 'unexpected drop' error case."
- - + - - START Y - Y -
- Y + - - - START Y - Y -
Y Y - - - ERROR Y - - -
- Y + - - Y GOTDROP START Y - Y -
Y Y - - Y GOTDROP START Y - Y -
"Clear expS+ whenever supply increases."
Y - + - - RECOVER Y Y - -
Y - - + - - RECOVER EXPDROP - Y - Y
Y - + Y - RUNNING Y Y - -
Y - - + Y - RUNNING EXPDROP - Y - Y
- - + Y Y RUNNING - Y - Y
- - + + Y Y RUNNING Y Y - Y
Y - + Y Y RUNNING Y Y - Y
Y - - + Y Y RUNNING - Y - Y
- - + - - - Y - -
- - + + - - Y Y - -
By looking at the next-state columns corresponding to the action
described, we can see that each predicate used to decide whether to
set or clear each state bit is a sound overapproximation of the
behaviour we want.

BIN
doc/demand-matcher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -31,12 +31,12 @@ function spawnBrokerClientDriver() {
Dataspace.spawn(
new DemandMatcher([brokerConnection(URL)],
[brokerConnection(URL)],
function (c) {
Dataspace.spawn(new BrokerClientConnection(c.url));
},
{
demandMetaLevel: 1,
supplyMetaLevel: 0,
onDemandIncrease: function (c) {
Dataspace.spawn(new BrokerClientConnection(c.url));
}
supplyMetaLevel: 0
}));
}));
}

View File

@ -3,6 +3,100 @@ var Trie = require('./trie.js');
var Patch = require('./patch.js');
var Util = require('./util.js');
///////////////////////////////////////////////////////////////////////////
// Protocol between DemandMatcher and taskSupervisor functions
// Bits:
var IS_CHANGING = 1;
var IS_PRESENT = 2;
// Bit combinations:
var LOW = 0 ;
var RISING = IS_CHANGING ;
var HIGH = IS_PRESENT ;
var FALLING = IS_PRESENT | IS_CHANGING ;
///////////////////////////////////////////////////////////////////////////
// Default task supervision strategy. See syndicate/doc/demand-matcher.md.
function defaultTaskSupervisor(demandState, supplyState, supervisionState, taskFn, errorFn) {
var oldESI = supervisionState ? supervisionState.expectSupplyIncrease : false;
var oldESD = supervisionState ? supervisionState.expectSupplyDecrease : false;
var newESI = oldESI;
var newESD = oldESD;
if ((demandState === FALLING) && ((supplyState === RISING) ||
(supplyState === HIGH) ||
oldESI)) {
newESD = true;
}
if (!oldESI && ((demandState === RISING) ||
(demandState === HIGH)) && ((supplyState === LOW) ||
(supplyState === FALLING))) {
if ((demandState === HIGH) && !oldESD) {
errorFn("Syndicate: DemandMatcher detected unexpected drop in supply");
} else {
taskFn();
newESI = true;
}
}
if (supplyState === FALLING) newESD = false;
if (supplyState === RISING) newESI = false;
if (newESI || newESD) {
return { expectSupplyIncrease: newESI, expectSupplyDecrease: newESD };
} else {
return null;
}
}
///////////////////////////////////////////////////////////////////////////
// DemandMatcher itself
function DemandMatcher(demandSpecs, supplySpecs, startTask, options) {
options = Util.extend({
metaLevel: 0,
demandMetaLevel: null,
supplyMetaLevel: null,
taskSupervisor: defaultTaskSupervisor,
}, options);
if (typeof startTask !== 'function') {
throw new Error("Syndicate: DemandMatcher expects 'startTask' function as third argument");
}
this.demandProjectionNames = ensureMatchingProjectionNames(demandSpecs);
this.supplyProjectionNames = ensureMatchingProjectionNames(supplySpecs);
ensureMatchingProjectionNames([demandSpecs[0], supplySpecs[0]]);
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.taskSupervisor = options.taskSupervisor;
this.startTask = startTask;
this.currentDemand = Immutable.Set();
this.currentSupply = Immutable.Set();
this.supervisionStates = Immutable.Map();
}
function ensureMatchingProjectionNames(specs) {
if (!(specs.length > 0)) {
throw new Error("Syndicate: DemandMatcher needs at least one spec");
@ -21,52 +115,6 @@ function ensureMatchingProjectionNames(specs) {
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) {
@ -83,6 +131,50 @@ DemandMatcher.prototype.handleEvent = function (e) {
}
};
DemandMatcher.prototype.handlePatch = function (p) {
var self = this;
var dN = self.demandProjectionNames.length;
var sN = self.supplyProjectionNames.length;
var addedDemand = self.extractKeys(p.added, self.demandProjections, dN, 'demand');
var removedDemand = self.extractKeys(p.removed, self.demandProjections, dN, 'demand');
var addedSupply = self.extractKeys(p.added, self.supplyProjections, sN, 'supply');
var removedSupply = self.extractKeys(p.removed, self.supplyProjections, sN, 'supply');
// Though the added and removed sets of patches are always disjoint,
// *after projection* this may not hold. Cancel out any overlaps.
var demandOverlap = addedDemand.intersect(removedDemand);
var supplyOverlap = addedSupply.intersect(removedSupply);
addedDemand = addedDemand.subtract(demandOverlap);
removedDemand = removedDemand.subtract(demandOverlap);
addedSupply = addedSupply.subtract(supplyOverlap);
removedSupply = removedSupply.subtract(supplyOverlap);
var allTasks = addedDemand.union(addedSupply).union(removedDemand).union(removedSupply);
allTasks.forEach(function (captures) {
function taskFn() {
self.startTask(Trie.captureToObject(captures, self.demandProjectionNames));
}
function errorFn(msg) {
console.error(msg, captures);
}
var demandState = computeState(self.currentDemand, addedDemand, removedDemand, captures);
var supplyState = computeState(self.currentSupply, addedSupply, removedSupply, captures);
var oldSupervisionState = self.supervisionStates.get(captures, null);
var newSupervisionState = self.taskSupervisor(demandState,
supplyState,
oldSupervisionState,
taskFn,
errorFn);
self.supervisionStates = self.supervisionStates.set(captures, newSupervisionState);
});
self.currentSupply = self.currentSupply.union(addedSupply).subtract(removedSupply);
self.currentDemand = self.currentDemand.union(addedDemand).subtract(removedDemand);
};
DemandMatcher.prototype.extractKeys = function (trie, projections, keyCount, whichSide) {
var ks = Immutable.Set();
projections.forEach(function (proj) {
@ -97,53 +189,11 @@ DemandMatcher.prototype.extractKeys = function (trie, projections, keyCount, whi
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');
// Though the added and removed sets of patches are always disjoint,
// *after projection* this may not hold. Cancel out any overlaps.
var demandOverlap = addedDemand.intersect(removedDemand);
var supplyOverlap = addedSupply.intersect(removedSupply);
addedDemand = addedDemand.subtract(demandOverlap);
removedDemand = removedDemand.subtract(demandOverlap);
addedSupply = addedSupply.subtract(supplyOverlap);
removedSupply = removedSupply.subtract(supplyOverlap);
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);
};
function computeState(current, added, removed, captures) {
var isPresent = current.has(captures);
var isChanging = added.has(captures) || removed.has(captures);
return (isPresent ? IS_PRESENT : 0) | (isChanging ? IS_CHANGING : 0);
}
///////////////////////////////////////////////////////////////////////////

View File

@ -15,18 +15,14 @@ function spawnTimerDriver() {
Dataspace.spawn(
new DemandMatcher([Patch.observe(periodicTick(_$('intervalMS')))],
[Patch.advertise(periodicTick(_$('intervalMS')))],
{
onDemandIncrease: function (c) {
Dataspace.spawn(new Tick(c.intervalMS));
}
function (c) {
Dataspace.spawn(new Tick(c.intervalMS));
}));
Dataspace.spawn(
new DemandMatcher([Patch.observe(timeLaterThan(_$('deadlineMS')))],
[Patch.advertise(timeLaterThan(_$('deadlineMS')))],
{
onDemandIncrease: function (c) {
Dataspace.spawn(new Alarm(c.deadlineMS));
}
function (c) {
Dataspace.spawn(new Alarm(c.deadlineMS));
}));
}

View File

@ -78,29 +78,23 @@ function spawnUIDriver(options) {
Dataspace.spawn(
new DemandMatcher([Patch.observe(globalEventProj)],
[Patch.advertise(globalEventProj)],
{
onDemandIncrease: function (c) {
Dataspace.spawn(new GlobalEventSupply(c.selector, c.eventType));
}
function (c) {
Dataspace.spawn(new GlobalEventSupply(c.selector, c.eventType));
}));
var windowEventProj = windowEvent(_$('eventType'), __);
Dataspace.spawn(
new DemandMatcher([Patch.observe(windowEventProj)],
[Patch.advertise(windowEventProj)],
{
onDemandIncrease: function (c) {
Dataspace.spawn(new WindowEventSupply(c.eventType));
}
function (c) {
Dataspace.spawn(new WindowEventSupply(c.eventType));
}));
Dataspace.spawn(
new DemandMatcher([uiFragment(_$('fragmentId'), __, __)],
[uiFragmentExists(_$('fragmentId'))],
{
onDemandIncrease: function (c) {
Dataspace.spawn(new UIFragment(c.fragmentId));
}
function (c) {
Dataspace.spawn(new UIFragment(c.fragmentId));
}));
Dataspace.spawn(new LocationHashTracker(options.defaultLocationHash || '/'));