2018-10-22 14:57:13 +00:00
|
|
|
"use strict";
|
2018-11-01 14:55:45 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS.
|
|
|
|
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
//---------------------------------------------------------------------------
|
2018-10-22 14:57:13 +00:00
|
|
|
|
2019-06-14 12:06:40 +00:00
|
|
|
if (require('preserves/src/singletonmodule.js')('syndicate-lang.org/syndicate-js',
|
|
|
|
require('../package.json').version,
|
2019-06-14 15:08:31 +00:00
|
|
|
'dataspace.js',
|
2019-06-14 12:06:40 +00:00
|
|
|
module)) return;
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
const Immutable = require("immutable");
|
2018-11-26 13:13:25 +00:00
|
|
|
const Preserves = require("preserves");
|
2019-06-13 12:19:28 +00:00
|
|
|
// const debug = require("debug")("syndicate/core:dataspace");
|
2018-11-15 23:24:58 +00:00
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
const Skeleton = require('./skeleton.js');
|
|
|
|
const $Special = require('./special.js');
|
|
|
|
const Bag = require('./bag.js');
|
|
|
|
const Assertions = require('./assertions.js');
|
|
|
|
const Dataflow = require('./dataflow.js');
|
|
|
|
|
|
|
|
const PRIORITY = Object.freeze({
|
|
|
|
QUERY_HIGH: 0,
|
|
|
|
QUERY: 1,
|
|
|
|
QUERY_HANDLER: 2,
|
|
|
|
NORMAL: 3,
|
|
|
|
GC: 4,
|
|
|
|
IDLE: 5,
|
|
|
|
_count: 6
|
|
|
|
});
|
|
|
|
|
2019-06-14 12:15:36 +00:00
|
|
|
function Dataspace(bootProc) {
|
|
|
|
this.nextId = 0;
|
|
|
|
this.index = new Skeleton.Index();
|
|
|
|
this.dataflow = new Dataflow.Graph();
|
|
|
|
this.runnable = Immutable.List();
|
|
|
|
this.pendingActions = Immutable.List([
|
|
|
|
new ActionGroup(null, Immutable.List([new Spawn(null, bootProc, Immutable.Set())]))]);
|
|
|
|
this.activatedModules = Immutable.Set();
|
|
|
|
this.actors = Immutable.Map();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parameters
|
|
|
|
Dataspace._currentFacet = null;
|
|
|
|
Dataspace._inScript = true;
|
|
|
|
|
2019-06-13 21:18:45 +00:00
|
|
|
Dataspace.BootSteps = Symbol.for('SyndicateBootSteps');
|
2018-11-01 23:30:17 +00:00
|
|
|
|
|
|
|
Dataspace.currentFacet = function () {
|
|
|
|
return Dataspace._currentFacet;
|
|
|
|
};
|
2018-10-27 19:32:12 +00:00
|
|
|
|
|
|
|
Dataspace.withNonScriptContext = function (thunk) {
|
|
|
|
let savedInScript = Dataspace._inScript;
|
|
|
|
Dataspace._inScript = false;
|
|
|
|
try {
|
|
|
|
return thunk();
|
|
|
|
} finally {
|
|
|
|
Dataspace._inScript = savedInScript;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.withCurrentFacet = function (facet, thunk) {
|
|
|
|
let savedFacet = Dataspace._currentFacet;
|
|
|
|
Dataspace._currentFacet = facet;
|
|
|
|
try {
|
2018-11-03 22:19:52 +00:00
|
|
|
// console.group('Facet', facet && facet.toString());
|
2018-10-27 19:32:12 +00:00
|
|
|
let result = thunk();
|
|
|
|
Dataspace._currentFacet = savedFacet;
|
|
|
|
return result;
|
|
|
|
} catch (e) {
|
|
|
|
let a = facet.actor;
|
|
|
|
a.abandonQueuedWork();
|
2018-11-02 12:24:54 +00:00
|
|
|
a._terminate(false);
|
2018-10-27 19:32:12 +00:00
|
|
|
Dataspace._currentFacet = savedFacet;
|
2018-11-03 22:19:30 +00:00
|
|
|
console.error('Actor ' + a.toString() + ' exited with exception:', e);
|
2018-11-03 22:19:52 +00:00
|
|
|
} finally {
|
|
|
|
// console.groupEnd();
|
2018-10-27 19:32:12 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.wrap = function (f) {
|
|
|
|
let savedFacet = Dataspace._currentFacet;
|
|
|
|
return function () {
|
|
|
|
let actuals = arguments;
|
2018-11-02 12:24:54 +00:00
|
|
|
Dataspace.withCurrentFacet(savedFacet, function () {
|
|
|
|
f.apply(savedFacet.fields, actuals);
|
2018-11-01 23:30:17 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.wrapExternal = function (f) {
|
|
|
|
let savedFacet = Dataspace._currentFacet;
|
|
|
|
let ac = savedFacet.actor;
|
|
|
|
return function () {
|
2018-12-16 06:50:52 +00:00
|
|
|
if (savedFacet.isLive) {
|
|
|
|
let actuals = arguments;
|
|
|
|
ac.dataspace.start();
|
|
|
|
ac.pushScript(function () {
|
|
|
|
Dataspace.withCurrentFacet(savedFacet, function () {
|
|
|
|
f.apply(this, actuals);
|
|
|
|
});
|
2018-11-01 23:30:17 +00:00
|
|
|
});
|
2018-12-16 06:50:52 +00:00
|
|
|
}
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2018-11-01 23:30:17 +00:00
|
|
|
Dataspace.backgroundTask = function (k) {
|
2018-11-11 15:48:46 +00:00
|
|
|
return Dataspace._currentFacet.actor.dataspace.ground().backgroundTask(k);
|
2018-11-01 23:30:17 +00:00
|
|
|
};
|
2018-10-27 19:32:12 +00:00
|
|
|
|
|
|
|
Dataspace.referenceField = function (obj, prop) {
|
|
|
|
if (!(prop in obj)) {
|
|
|
|
Dataspace._currentFacet.actor.dataspace.dataflow.recordObservation(
|
|
|
|
Immutable.List.of(obj, prop));
|
|
|
|
}
|
|
|
|
return obj[prop];
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.declareField = function (obj, prop, init) {
|
|
|
|
if (prop in obj) {
|
|
|
|
obj[prop] = init;
|
|
|
|
} else {
|
|
|
|
Dataspace._currentFacet.actor.dataspace.dataflow.defineObservableProperty(
|
|
|
|
obj,
|
|
|
|
prop,
|
|
|
|
init,
|
2018-11-26 13:13:25 +00:00
|
|
|
{
|
|
|
|
objectId: Immutable.List.of(obj, prop),
|
|
|
|
noopGuard: Preserves.is
|
|
|
|
});
|
2018-10-27 19:32:12 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.deleteField = function (obj, prop) {
|
|
|
|
Dataspace._currentFacet.actor.dataspace.dataflow.recordDamage(Immutable.List.of(obj, prop));
|
|
|
|
return delete obj[prop];
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.runScripts = function () { // TODO: rename?
|
|
|
|
this.runPendingScripts();
|
|
|
|
this.performPendingActions();
|
|
|
|
return !this.runnable.isEmpty() || !this.pendingActions.isEmpty();
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.runPendingScripts = function () {
|
|
|
|
let runnable = this.runnable;
|
|
|
|
this.runnable = Immutable.List();
|
|
|
|
runnable.forEach((ac) => { ac.runPendingScripts(); /* TODO: rename? */ });
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.performPendingActions = function () {
|
|
|
|
let groups = this.pendingActions;
|
|
|
|
this.pendingActions = Immutable.List();
|
|
|
|
groups.forEach((group) => {
|
|
|
|
group.actions.forEach((action) => {
|
2018-11-03 22:19:52 +00:00
|
|
|
// console.log('[DATASPACE]', group.actor && group.actor.toString(), action);
|
2018-10-27 19:32:12 +00:00
|
|
|
action.perform(this, group.actor);
|
|
|
|
this.runPendingScripts();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.commitActions = function (ac, pending) {
|
|
|
|
this.pendingActions = this.pendingActions.push(new ActionGroup(ac, pending));
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.refreshAssertions = function () {
|
|
|
|
Dataspace.withNonScriptContext(() => {
|
|
|
|
this.dataflow.repairDamage((subjectId) => {
|
|
|
|
let [facet, eid] = subjectId;
|
|
|
|
if (facet.isLive) { // TODO: necessary test, or tautological?
|
|
|
|
let ac = facet.actor;
|
|
|
|
Dataspace.withCurrentFacet(facet, () => {
|
|
|
|
facet.endpoints.get(eid).refresh(this, ac, facet);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-11-28 17:52:35 +00:00
|
|
|
Dataspace.prototype.addActor = function (name, bootProc, initialAssertions, parentActor) {
|
|
|
|
let ac = new Actor(this, name, initialAssertions, parentActor && parentActor.id);
|
2019-06-13 12:19:28 +00:00
|
|
|
// debug('Spawn', ac && ac.toString());
|
2018-10-27 19:32:12 +00:00
|
|
|
this.applyPatch(ac, ac.adhocAssertions.snapshot());
|
|
|
|
ac.addFacet(null, () => {
|
|
|
|
// Root facet is a dummy "system" facet that exists to hold
|
|
|
|
// one-or-more "user" "root" facets.
|
2018-11-01 23:30:17 +00:00
|
|
|
ac.addFacet(Dataspace._currentFacet, bootProc);
|
2018-10-27 19:32:12 +00:00
|
|
|
// ^ The "true root", user-visible facet.
|
|
|
|
initialAssertions.forEach((a) => { ac.adhocRetract(a); });
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.applyPatch = function (ac, delta) {
|
2019-06-13 12:19:28 +00:00
|
|
|
// if (!delta.isEmpty()) debug('applyPatch BEGIN', ac && ac.toString());
|
2018-11-02 00:16:00 +00:00
|
|
|
let removals = [];
|
2018-10-27 19:32:12 +00:00
|
|
|
delta.forEach((count, a) => {
|
|
|
|
if (a !== void 0) {
|
2018-11-02 00:16:00 +00:00
|
|
|
if (count > 0) {
|
2019-06-13 12:19:28 +00:00
|
|
|
// debug('applyPatch +', a && a.toString());
|
2018-11-11 13:58:00 +00:00
|
|
|
this.adjustIndex(a, count);
|
2018-11-02 00:16:00 +00:00
|
|
|
} else {
|
|
|
|
removals.push([count, a]);
|
|
|
|
}
|
2018-12-13 16:39:56 +00:00
|
|
|
if (ac) ac.cleanupChanges.change(a, -count);
|
2018-10-27 19:32:12 +00:00
|
|
|
}
|
|
|
|
});
|
2018-11-02 00:16:00 +00:00
|
|
|
removals.forEach(([count, a]) => {
|
2019-06-13 12:19:28 +00:00
|
|
|
// debug('applyPatch -', a && a.toString());
|
2018-11-11 13:58:00 +00:00
|
|
|
this.adjustIndex(a, count);
|
2018-11-02 00:16:00 +00:00
|
|
|
});
|
2019-06-13 12:19:28 +00:00
|
|
|
// if (!delta.isEmpty()) debug('applyPatch END');
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-11-28 17:52:35 +00:00
|
|
|
Dataspace.prototype.sendMessage = function (m, sendingActor) {
|
2019-06-13 12:19:28 +00:00
|
|
|
// debug('sendMessage', sendingActor && sendingActor.toString(), m.toString());
|
2018-11-11 13:58:00 +00:00
|
|
|
this.index.sendMessage(m);
|
2018-11-28 17:52:35 +00:00
|
|
|
// this.index.sendMessage(m, (leaf, _m) => {
|
|
|
|
// sendingActor.touchedTopics = sendingActor.touchedTopics.add(leaf);
|
|
|
|
// });
|
2018-11-11 13:58:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.adjustIndex = function (a, count) {
|
|
|
|
return this.index.adjustAssertion(a, count);
|
|
|
|
};
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Dataspace.prototype.subscribe = function (handler) {
|
|
|
|
this.index.addHandler(handler, handler.callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype.unsubscribe = function (handler) {
|
|
|
|
this.index.removeHandler(handler, handler.callback);
|
|
|
|
};
|
|
|
|
|
2018-11-11 13:59:01 +00:00
|
|
|
Dataspace.prototype.endpointHook = function (facet, endpoint) {
|
|
|
|
};
|
|
|
|
|
2018-11-26 21:58:40 +00:00
|
|
|
Dataspace.prototype._debugString = function (outerIndent) {
|
|
|
|
const pieces = [];
|
|
|
|
pieces.push(this.index.root._debugString(outerIndent));
|
|
|
|
outerIndent = outerIndent || '\n';
|
|
|
|
pieces.push(outerIndent + 'FACET TREE');
|
|
|
|
this.actors.forEach((a) => {
|
|
|
|
pieces.push(outerIndent + ' ' + a.toString());
|
|
|
|
function walkFacet(indent, f) {
|
|
|
|
pieces.push(indent + f.toString());
|
|
|
|
f.endpoints.forEach((ep) => {
|
|
|
|
pieces.push(indent + ' - ' + ep.id + ': ' + (ep.assertion && ep.assertion.toString()));
|
|
|
|
});
|
|
|
|
f.children.forEach((child) => { walkFacet(indent + ' ', child); });
|
|
|
|
}
|
|
|
|
a.rootFacet.children.forEach((child) => { walkFacet(outerIndent + ' ', child); });
|
|
|
|
});
|
|
|
|
pieces.push(outerIndent + 'ACTORS');
|
|
|
|
this.actors.forEach((a) => pieces.push(outerIndent + ' ' + a.toString()));
|
|
|
|
return pieces.join('');
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.prototype._dotGraph = function () {
|
|
|
|
let id = 0;
|
|
|
|
const assertionIds = {};
|
2018-11-28 17:52:35 +00:00
|
|
|
|
|
|
|
const nodes = [];
|
|
|
|
const edges = [];
|
|
|
|
const pieces = [];
|
|
|
|
|
|
|
|
function emitNode(type, id, _label, attrs) {
|
|
|
|
const label = _str(_label);
|
|
|
|
pieces.push(`\n ${id} [label=${JSON.stringify(label)}];`);
|
|
|
|
nodes.push(Object.assign({}, attrs || {}, {type, id, label}));
|
|
|
|
}
|
|
|
|
|
|
|
|
function emitEdge(source, target, maybeDir) {
|
|
|
|
pieces.push(`\n ${source} -- ${target} [dir=${maybeDir || 'none'}];`);
|
|
|
|
edges.push({source, target, dir: maybeDir || 'none'});
|
|
|
|
}
|
|
|
|
|
2018-11-26 21:58:40 +00:00
|
|
|
function _aId(aStr) {
|
2018-11-28 17:52:35 +00:00
|
|
|
// if (aStr.startsWith('observe(Request(') || aStr.startsWith('Request(')) return null;
|
|
|
|
// if (aStr.startsWith('observe(Connection(') || aStr.startsWith('Connection(')) return null;
|
2018-11-26 21:58:40 +00:00
|
|
|
if (!(aStr in assertionIds)) assertionIds[aStr] = id++;
|
|
|
|
return assertionIds[aStr];
|
|
|
|
}
|
2018-11-28 17:52:35 +00:00
|
|
|
|
|
|
|
let topics = Immutable.Map();
|
|
|
|
function topicForLeaf(leaf) {
|
|
|
|
if (topics.has(leaf)) {
|
|
|
|
return topics.get(leaf);
|
|
|
|
} else {
|
|
|
|
const topic = {id: id++, hasEmitter: false, senders: {}, inbound: {}, outbound: {}};
|
|
|
|
topics = topics.set(leaf, topic);
|
|
|
|
return topic;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-26 21:58:40 +00:00
|
|
|
function _str(a) {
|
|
|
|
return '' + a;
|
|
|
|
}
|
2018-11-28 17:52:35 +00:00
|
|
|
|
2018-11-26 21:58:40 +00:00
|
|
|
pieces.push('graph G {');
|
|
|
|
pieces.push('\n overlap=false;');
|
2018-11-28 17:52:35 +00:00
|
|
|
|
2018-11-26 21:58:40 +00:00
|
|
|
this.actors.forEach((ac) => {
|
2018-11-28 17:52:35 +00:00
|
|
|
const acId = ac.id;
|
|
|
|
emitNode('actor', `ac_${acId}`, ac.toString());
|
|
|
|
if (this.actors.has(ac.parentId)) {
|
|
|
|
emitEdge(`ac_${ac.parentId}`, `ac_${acId}`, 'forward');
|
|
|
|
}
|
|
|
|
// ac.touchedTopics.forEach((leaf) => {
|
|
|
|
// const topic = topicForLeaf(leaf);
|
|
|
|
// topic.senders[acId] = true;
|
|
|
|
// topic.hasEmitter = true;
|
|
|
|
// });
|
|
|
|
// ac.touchedTopics = Immutable.Set();
|
2018-11-26 21:58:40 +00:00
|
|
|
function walkFacet(parent) {
|
|
|
|
return (f) => {
|
|
|
|
const facetId = id++;
|
2018-11-28 17:52:35 +00:00
|
|
|
emitNode('facet', `facet_${facetId}`, `Facet ${f.id}`, {parent});
|
|
|
|
emitEdge(parent, `facet_${facetId}`);
|
2018-11-26 21:58:40 +00:00
|
|
|
f.endpoints.forEach((ep) => {
|
2018-11-28 17:52:35 +00:00
|
|
|
if (ep.assertion !== void 0) {
|
|
|
|
const aId = _aId(_str(ep.assertion));
|
|
|
|
if (aId) {
|
|
|
|
emitNode('endpoint', `ep_${ep.id}`, ep.id);
|
|
|
|
emitEdge(`facet_${facetId}`, `ep_${ep.id}`);
|
|
|
|
emitEdge(`ep_${ep.id}`, `assn_${aId}`);
|
|
|
|
}
|
2018-11-26 21:58:40 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
f.children.forEach(walkFacet(`facet_${facetId}`));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
ac.rootFacet.children.forEach(walkFacet(`ac_${acId}`));
|
|
|
|
});
|
2018-11-28 17:52:35 +00:00
|
|
|
|
|
|
|
function walkNode(n) {
|
|
|
|
n.edges.forEach((table) => table.forEach(walkNode));
|
|
|
|
n.continuation.leafMap.forEach((cvMap) => cvMap.forEach((leaf) => {
|
|
|
|
const topic = topicForLeaf(leaf);
|
|
|
|
leaf.cachedAssertions.forEach((observed_assertion) => {
|
|
|
|
const observed_assertion_id = _aId(_str(observed_assertion));
|
|
|
|
if (observed_assertion_id) {
|
|
|
|
topic.inbound[observed_assertion_id] = true;
|
|
|
|
topic.hasEmitter = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
leaf.handlerMap.forEach((handler) => {
|
|
|
|
handler.callbacks.forEach((cb) => {
|
|
|
|
const observing_assertion_id = _aId(_str(cb.__endpoint.handler.assertion));
|
|
|
|
if (observing_assertion_id) {
|
|
|
|
topic.outbound[observing_assertion_id] = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
walkNode(this.index.root);
|
|
|
|
|
|
|
|
for (const a in assertionIds) {
|
|
|
|
emitNode('assertion', `assn_${assertionIds[a]}`, a);
|
|
|
|
}
|
|
|
|
|
|
|
|
topics.forEach((topic) => {
|
|
|
|
if (topic.hasEmitter) {
|
|
|
|
emitNode('topic', 'topic_' + topic.id, ''); // `Topic ${topic.id}`);
|
|
|
|
for (const acId in topic.senders) {
|
|
|
|
emitEdge(`ac_${acId}`, `topic_${topic.id}`, 'forward');
|
|
|
|
}
|
|
|
|
for (const aId in topic.inbound) {
|
|
|
|
emitEdge(`assn_${aId}`, `topic_${topic.id}`, 'forward');
|
|
|
|
}
|
|
|
|
for (const aId in topic.outbound) {
|
|
|
|
emitEdge(`topic_${topic.id}`, `assn_${aId}`, 'forward');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-11-26 21:58:40 +00:00
|
|
|
pieces.push('\n}');
|
2018-11-28 17:52:35 +00:00
|
|
|
|
|
|
|
require('fs').writeFileSync('d.json', 'var dataspaceContents = ' + JSON.stringify({nodes, edges}, null, 2));
|
|
|
|
|
2018-11-26 21:58:40 +00:00
|
|
|
return pieces.join('');
|
|
|
|
};
|
|
|
|
|
2018-11-28 17:52:35 +00:00
|
|
|
function Actor(dataspace, name, initialAssertions, parentActorId) {
|
2018-10-22 14:57:13 +00:00
|
|
|
this.id = dataspace.nextId++;
|
|
|
|
this.dataspace = dataspace;
|
|
|
|
this.name = name;
|
|
|
|
this.rootFacet = null;
|
|
|
|
this.isRunnable = false;
|
|
|
|
this.pendingScripts = [];
|
|
|
|
for (let i = 0; i < PRIORITY._count; i++) { this.pendingScripts.push(Immutable.List()); }
|
|
|
|
this.pendingActions = Immutable.List();
|
2018-10-27 19:32:12 +00:00
|
|
|
this.adhocAssertions = new Bag.MutableBag(initialAssertions); // no negative counts allowed
|
2018-10-22 14:57:13 +00:00
|
|
|
this.cleanupChanges = new Bag.MutableBag(); // negative counts allowed!
|
2018-11-28 17:52:35 +00:00
|
|
|
this.parentId = parentActorId;
|
|
|
|
// this.touchedTopics = Immutable.Set();
|
|
|
|
dataspace.actors = dataspace.actors.set(this.id, this);
|
2018-10-22 14:57:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Actor.prototype.runPendingScripts = function () {
|
|
|
|
while (true) {
|
|
|
|
let script = this.popNextScript();
|
|
|
|
if (!script) break;
|
|
|
|
script();
|
|
|
|
this.dataspace.refreshAssertions();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isRunnable = false;
|
|
|
|
let pending = this.pendingActions;
|
|
|
|
if (!pending.isEmpty()) {
|
|
|
|
this.pendingActions = Immutable.List();
|
|
|
|
this.dataspace.commitActions(this, pending);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.popNextScript = function () {
|
|
|
|
let scripts = this.pendingScripts;
|
|
|
|
for (let i = 0; i < PRIORITY._count; i++) {
|
|
|
|
let q = scripts[i];
|
|
|
|
if (!q.isEmpty()) {
|
|
|
|
scripts[i] = q.shift();
|
|
|
|
return q.first();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.abandonQueuedWork = function () {
|
|
|
|
this.pendingActions = Immutable.List();
|
|
|
|
for (let i = 0; i < PRIORITY._count; i++) { this.pendingScripts[i] = Immutable.List(); }
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.scheduleScript = function (unwrappedThunk, priority) {
|
|
|
|
this.pushScript(Dataspace.wrap(unwrappedThunk), priority);
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.pushScript = function (wrappedThunk, priority) {
|
|
|
|
// The wrappedThunk must already have code for ensuring
|
|
|
|
// _currentFacet is correct inside it. Compare with scheduleScript.
|
|
|
|
if (priority === void 0) {
|
|
|
|
priority = PRIORITY.NORMAL;
|
|
|
|
}
|
|
|
|
if (!this.isRunnable) {
|
|
|
|
this.isRunnable = true;
|
|
|
|
this.dataspace.runnable = this.dataspace.runnable.push(this);
|
|
|
|
}
|
|
|
|
this.pendingScripts[priority] = this.pendingScripts[priority].push(wrappedThunk);
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.addFacet = function (parentFacet, bootProc, checkInScript) {
|
|
|
|
if (checkInScript === true && !Dataspace._inScript) {
|
|
|
|
throw new Error("Cannot add facet outside script; are you missing a `react { ... }`?");
|
|
|
|
}
|
|
|
|
let f = new Facet(this, parentFacet);
|
|
|
|
Dataspace.withCurrentFacet(f, () => {
|
|
|
|
Dataspace.withNonScriptContext(() => {
|
|
|
|
bootProc.call(f.fields);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
this.pushScript(() => {
|
|
|
|
if ((parentFacet && !parentFacet.isLive) || f.isInert()) {
|
|
|
|
f._terminate();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-11-02 12:24:54 +00:00
|
|
|
Actor.prototype._terminate = function (emitPatches) {
|
2018-10-27 19:32:12 +00:00
|
|
|
// Abruptly terminates an entire actor, without running stop-scripts etc.
|
2018-11-02 12:24:54 +00:00
|
|
|
if (emitPatches) {
|
|
|
|
this.pushScript(() => {
|
|
|
|
this.adhocAssertions.snapshot().forEach((_count, a) => { this.retract(a); });
|
|
|
|
});
|
|
|
|
}
|
2018-10-27 19:32:12 +00:00
|
|
|
if (this.rootFacet) {
|
2018-11-02 12:24:54 +00:00
|
|
|
this.rootFacet._abort(emitPatches);
|
2018-10-27 19:32:12 +00:00
|
|
|
}
|
|
|
|
this.pushScript(() => { this.enqueueScriptAction(new Quit()); });
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.enqueueScriptAction = function (action) {
|
|
|
|
this.pendingActions = this.pendingActions.push(action);
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.pendingPatch = function () {
|
|
|
|
if (!this.pendingActions.isEmpty()) {
|
|
|
|
let p = this.pendingActions.last();
|
|
|
|
if (p instanceof Patch) {
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let p = new Patch(Bag.Bag());
|
|
|
|
this.enqueueScriptAction(p);
|
|
|
|
return p;
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.assert = function (a) { this.pendingPatch().adjust(a, +1); };
|
|
|
|
Actor.prototype.retract = function (a) { this.pendingPatch().adjust(a, -1); };
|
|
|
|
|
2018-11-04 19:52:53 +00:00
|
|
|
Actor.prototype.adhocRetract = function (a) {
|
2018-11-26 13:13:25 +00:00
|
|
|
a = Preserves.fromJS(a);
|
2018-11-04 19:52:53 +00:00
|
|
|
if (this.adhocAssertions.change(a, -1, true) === Bag.PRESENT_TO_ABSENT) {
|
|
|
|
this.retract(a);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.adhocAssert = function (a) {
|
2018-11-26 13:13:25 +00:00
|
|
|
a = Preserves.fromJS(a);
|
2018-11-04 19:52:53 +00:00
|
|
|
if (this.adhocAssertions.change(a, +1) === Bag.ABSENT_TO_PRESENT) {
|
|
|
|
this.assert(a);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
Actor.prototype.toString = function () {
|
|
|
|
let s = 'Actor(' + this.id;
|
2018-11-19 22:20:10 +00:00
|
|
|
if (this.name !== void 0 && this.name !== null) s = s + ',' + this.name.toString();
|
2018-10-22 14:57:13 +00:00
|
|
|
return s + ')';
|
|
|
|
};
|
|
|
|
|
|
|
|
function Patch(changes) {
|
|
|
|
this.changes = changes;
|
|
|
|
}
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Patch.prototype.perform = function (ds, ac) {
|
|
|
|
ds.applyPatch(ac, this.changes);
|
|
|
|
};
|
|
|
|
|
|
|
|
Patch.prototype.adjust = function (a, count) {
|
2018-11-15 23:24:58 +00:00
|
|
|
if (a !== void 0) {
|
|
|
|
var _net;
|
2018-11-26 13:13:25 +00:00
|
|
|
({bag: this.changes, net: _net} = Bag.change(this.changes, Preserves.fromJS(a), count));
|
2018-11-15 23:24:58 +00:00
|
|
|
}
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
function Message(body) {
|
|
|
|
this.body = body;
|
|
|
|
}
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Message.prototype.perform = function (ds, ac) {
|
|
|
|
if (this.body !== void 0) {
|
2018-11-28 17:52:35 +00:00
|
|
|
ds.sendMessage(Preserves.fromJS(this.body), ac);
|
2018-10-27 19:32:12 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.send = function (body) {
|
2018-11-29 17:39:15 +00:00
|
|
|
if (!Dataspace._inScript) {
|
|
|
|
throw new Error("Cannot `send` during facet setup; are you missing an `on start { ... }`?");
|
|
|
|
}
|
2018-11-01 23:30:17 +00:00
|
|
|
Dataspace._currentFacet.enqueueScriptAction(new Message(body));
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
function Spawn(name, bootProc, initialAssertions) {
|
|
|
|
this.name = name;
|
|
|
|
this.bootProc = bootProc;
|
2018-10-27 19:32:12 +00:00
|
|
|
this.initialAssertions = initialAssertions || Immutable.Set();
|
2018-10-22 14:57:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Spawn.prototype.perform = function (ds, ac) {
|
2018-11-28 17:52:35 +00:00
|
|
|
ds.addActor(this.name, this.bootProc, this.initialAssertions, ac);
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.spawn = function (name, bootProc, initialAssertions) {
|
2018-11-29 17:39:15 +00:00
|
|
|
if (!Dataspace._inScript) {
|
|
|
|
throw new Error("Cannot `spawn` during facet setup; are you missing an `on start { ... }`?");
|
|
|
|
}
|
2018-11-01 23:30:17 +00:00
|
|
|
Dataspace._currentFacet.enqueueScriptAction(new Spawn(name, bootProc, initialAssertions));
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function Quit() { // TODO: rename? Perhaps to Cleanup?
|
|
|
|
// Pseudo-action - not for userland use.
|
2018-10-22 14:57:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Quit.prototype.perform = function (ds, ac) {
|
|
|
|
ds.applyPatch(ac, ac.cleanupChanges.snapshot());
|
2018-11-28 17:52:35 +00:00
|
|
|
ds.actors = ds.actors.remove(ac.id);
|
2019-06-13 12:19:28 +00:00
|
|
|
// debug('Quit', ac && ac.toString());
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
function DeferredTurn(continuation) {
|
|
|
|
this.continuation = continuation;
|
|
|
|
}
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
DeferredTurn.prototype.perform = function (ds, ac) {
|
2019-06-13 12:19:28 +00:00
|
|
|
// debug('DeferredTurn', ac && ac.toString());
|
2018-10-27 19:32:12 +00:00
|
|
|
ac.pushScript(this.continuation);
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.deferTurn = function (continuation) {
|
2018-11-29 17:39:15 +00:00
|
|
|
if (!Dataspace._inScript) {
|
|
|
|
throw new Error("Cannot defer turn during facet setup; are you missing an `on start { ... }`?");
|
|
|
|
}
|
2018-11-01 23:30:17 +00:00
|
|
|
Dataspace._currentFacet.enqueueScriptAction(new DeferredTurn(Dataspace.wrap(continuation)));
|
|
|
|
};
|
|
|
|
|
|
|
|
function Activation(mod) {
|
|
|
|
this.mod = mod;
|
|
|
|
}
|
|
|
|
|
|
|
|
Activation.prototype.perform = function (ds, ac) {
|
|
|
|
if (!ds.activatedModules.includes(this.mod)) {
|
2019-06-13 12:19:28 +00:00
|
|
|
// debug('Activation', this.mod.id);
|
2018-11-01 23:30:17 +00:00
|
|
|
ds.activatedModules = ds.activatedModules.add(this.mod);
|
|
|
|
this.mod.exports[Dataspace.BootSteps].steps.forEach((a) => {
|
2018-11-03 22:19:52 +00:00
|
|
|
// console.log('[ACTIVATION]', ac && ac.toString(), a);
|
2018-11-01 23:30:17 +00:00
|
|
|
a.perform(ds, ac);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Dataspace.activate = function (modExports) {
|
|
|
|
let { module } = modExports[Dataspace.BootSteps] || {};
|
|
|
|
if (module) {
|
|
|
|
Dataspace._currentFacet.enqueueScriptAction(new Activation(module));
|
|
|
|
}
|
|
|
|
return modExports;
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
function ActionGroup(actor, actions) {
|
|
|
|
this.actor = actor;
|
|
|
|
this.actions = actions;
|
|
|
|
}
|
|
|
|
|
|
|
|
function Facet(actor, parent) {
|
|
|
|
this.id = actor.dataspace.nextId++;
|
|
|
|
this.isLive = true;
|
|
|
|
this.actor = actor;
|
|
|
|
this.parent = parent;
|
2018-10-27 19:32:12 +00:00
|
|
|
this.endpoints = Immutable.Map();
|
2018-10-22 14:57:13 +00:00
|
|
|
this.stopScripts = Immutable.List();
|
|
|
|
this.children = Immutable.Set();
|
2018-10-27 19:32:12 +00:00
|
|
|
if (parent) {
|
|
|
|
parent.children = parent.children.add(this);
|
|
|
|
this.fields = Dataflow.Graph.newScope(parent.fields);
|
|
|
|
} else {
|
|
|
|
if (actor.rootFacet) {
|
|
|
|
throw new Error("INVARIANT VIOLATED: Attempt to add second root facet");
|
|
|
|
}
|
|
|
|
actor.rootFacet = this;
|
|
|
|
this.fields = Dataflow.Graph.newScope({});
|
|
|
|
}
|
2018-10-22 14:57:13 +00:00
|
|
|
}
|
|
|
|
|
2018-11-02 12:24:54 +00:00
|
|
|
Facet.prototype._abort = function (emitPatches) {
|
2018-10-27 19:32:12 +00:00
|
|
|
this.isLive = false;
|
2018-11-02 12:24:54 +00:00
|
|
|
this.children.forEach((child) => { child._abort(emitPatches); });
|
|
|
|
this.retractAssertionsAndSubscriptions(emitPatches);
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-11-02 12:24:54 +00:00
|
|
|
Facet.prototype.retractAssertionsAndSubscriptions = function (emitPatches) {
|
2018-10-27 19:32:12 +00:00
|
|
|
let ac = this.actor;
|
|
|
|
let ds = ac.dataspace;
|
|
|
|
ac.pushScript(() => {
|
|
|
|
this.endpoints.forEach((ep) => {
|
2018-11-02 12:24:54 +00:00
|
|
|
ep.destroy(ds, ac, this, emitPatches);
|
2018-10-27 19:32:12 +00:00
|
|
|
});
|
|
|
|
this.endpoints = Immutable.Map();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.isInert = function () {
|
|
|
|
return this.endpoints.isEmpty() && this.children.isEmpty();
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype._terminate = function () {
|
|
|
|
if (this.isLive) {
|
|
|
|
let ac = this.actor;
|
|
|
|
let parent = this.parent;
|
|
|
|
if (parent) {
|
|
|
|
parent.children = parent.children.remove(this);
|
|
|
|
} else {
|
|
|
|
ac.rootFacet = null;
|
|
|
|
}
|
|
|
|
this.isLive = false;
|
|
|
|
|
|
|
|
this.children.forEach((child) => { child._terminate(); });
|
|
|
|
|
|
|
|
// Run stop-scripts after terminating children. This means that
|
|
|
|
// children's stop-scripts run before ours.
|
|
|
|
ac.pushScript(() => {
|
|
|
|
Dataspace.withCurrentFacet(this, () => {
|
|
|
|
this.stopScripts.forEach((s) => { s.call(this.fields); });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-11-02 12:24:54 +00:00
|
|
|
this.retractAssertionsAndSubscriptions(true);
|
2018-10-27 19:32:12 +00:00
|
|
|
ac.pushScript(() => {
|
|
|
|
if (parent) {
|
|
|
|
if (parent.isInert()) {
|
|
|
|
parent._terminate();
|
|
|
|
}
|
|
|
|
} else {
|
2018-11-02 12:24:54 +00:00
|
|
|
ac._terminate(true);
|
2018-10-27 19:32:12 +00:00
|
|
|
}
|
|
|
|
}, PRIORITY.GC);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.stop = function (continuation) {
|
|
|
|
Dataspace.withCurrentFacet(this.parent, () => {
|
|
|
|
this.actor.scheduleScript(() => {
|
|
|
|
this._terminate();
|
|
|
|
this.actor.scheduleScript(() => {
|
2018-11-04 19:40:39 +00:00
|
|
|
if (continuation) {
|
|
|
|
continuation.call(this.fields); // TODO: is this the correct scope to use??
|
|
|
|
}
|
2018-10-27 19:32:12 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-11-29 17:39:15 +00:00
|
|
|
Facet.prototype.addStartScript = function (s) {
|
|
|
|
if (Dataspace._inScript) {
|
|
|
|
throw new Error("Cannot `on start` outside facet setup");
|
|
|
|
}
|
|
|
|
this.actor.scheduleScript(s);
|
|
|
|
};
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Facet.prototype.addStopScript = function (s) {
|
2018-11-29 17:39:15 +00:00
|
|
|
if (Dataspace._inScript) {
|
|
|
|
throw new Error("Cannot `on stop` outside facet setup");
|
|
|
|
}
|
2018-10-27 19:32:12 +00:00
|
|
|
this.stopScripts = this.stopScripts.push(s);
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.addEndpoint = function (updateFun, isDynamic) {
|
2018-11-11 13:59:01 +00:00
|
|
|
const ep = new Endpoint(this, isDynamic === void 0 ? true : isDynamic, updateFun);
|
|
|
|
this.actor.dataspace.endpointHook(this, ep);
|
|
|
|
return ep;
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2019-06-03 11:06:04 +00:00
|
|
|
Facet.prototype._addRawObserverEndpoint = function (specThunk, callbacks) {
|
|
|
|
this.addEndpoint(() => {
|
|
|
|
const spec = specThunk();
|
|
|
|
if (spec === void 0) {
|
|
|
|
return [void 0, null];
|
|
|
|
} else {
|
|
|
|
const analysis = Skeleton.analyzeAssertion(spec);
|
|
|
|
analysis.callback = Dataspace.wrap((evt, vs) => {
|
|
|
|
let cb = null;
|
|
|
|
switch (evt) {
|
|
|
|
case Skeleton.EVENT_ADDED: cb = callbacks.add; break;
|
|
|
|
case Skeleton.EVENT_REMOVED: cb = callbacks.del; break;
|
|
|
|
case Skeleton.EVENT_MESSAGE: cb = callbacks.msg; break;
|
|
|
|
}
|
|
|
|
if (cb) cb(vs);
|
|
|
|
});
|
|
|
|
return [Assertions.Observe(spec), analysis];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.addObserverEndpoint = function (specThunk, callbacks) {
|
|
|
|
const self = this;
|
|
|
|
function scriptify(f) {
|
|
|
|
return f && ((vs) => self.actor.scheduleScript(() => f(vs)));
|
|
|
|
}
|
|
|
|
this._addRawObserverEndpoint(specThunk, {
|
|
|
|
add: scriptify(callbacks.add),
|
|
|
|
del: scriptify(callbacks.del),
|
|
|
|
msg: scriptify(callbacks.msg),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Facet.prototype.addDataflow = function (subjectFun, priority) {
|
|
|
|
return this.addEndpoint(() => {
|
|
|
|
let subjectId = this.actor.dataspace.dataflow.currentSubjectId;
|
|
|
|
this.actor.scheduleScript(() => {
|
2018-12-13 20:16:47 +00:00
|
|
|
if (this.isLive) {
|
|
|
|
this.actor.dataspace.dataflow.withSubject(subjectId, () => subjectFun.call(this.fields));
|
|
|
|
}
|
2018-10-27 19:32:12 +00:00
|
|
|
}, priority);
|
|
|
|
return [void 0, null];
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-11-01 23:30:17 +00:00
|
|
|
Facet.prototype.enqueueScriptAction = function (action) {
|
|
|
|
this.actor.enqueueScriptAction(action);
|
|
|
|
};
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
Facet.prototype.toString = function () {
|
|
|
|
let s = 'Facet(' + this.actor.id;
|
2018-11-19 22:20:10 +00:00
|
|
|
if (this.actor.name !== void 0 && this.actor.name !== null) {
|
|
|
|
s = s + ',' + this.actor.name.toString();
|
|
|
|
}
|
2018-10-22 14:57:13 +00:00
|
|
|
s = s + ',' + this.id;
|
|
|
|
let f = this.parent;
|
|
|
|
while (f != null) {
|
|
|
|
s = s + ':' + f.id;
|
|
|
|
f = f.parent;
|
|
|
|
}
|
|
|
|
return s + ')';
|
|
|
|
};
|
|
|
|
|
2018-11-01 23:30:17 +00:00
|
|
|
function ActionCollector() {
|
|
|
|
this.actions = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
ActionCollector.prototype.enqueueScriptAction = function (a) {
|
|
|
|
this.actions.push(a);
|
|
|
|
};
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
function Endpoint(facet, isDynamic, updateFun) {
|
|
|
|
if (Dataspace._inScript) {
|
|
|
|
throw new Error("Cannot add endpoint in script; are you missing a `react { ... }`?");
|
|
|
|
}
|
|
|
|
let ac = facet.actor;
|
|
|
|
let ds = ac.dataspace;
|
|
|
|
this.id = ds.nextId++;
|
2018-10-22 14:57:13 +00:00
|
|
|
this.updateFun = updateFun;
|
2018-10-27 19:32:12 +00:00
|
|
|
let [initialAssertion, initialHandler] = ds.dataflow.withSubject(
|
|
|
|
isDynamic ? [facet, this.id] : false,
|
|
|
|
() => updateFun.call(facet.fields));
|
|
|
|
this._install(ds, ac, initialAssertion, initialHandler);
|
|
|
|
facet.endpoints = facet.endpoints.set(this.id, this);
|
2018-10-22 14:57:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
Endpoint.prototype._install = function (ds, ac, assertion, handler) {
|
|
|
|
this.assertion = assertion;
|
|
|
|
this.handler = handler;
|
|
|
|
ac.assert(this.assertion);
|
2018-11-26 21:58:40 +00:00
|
|
|
if (this.handler) {
|
|
|
|
this.handler.callback.__endpoint = this; // for reflection/debugging
|
|
|
|
ds.subscribe(this.handler);
|
|
|
|
}
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-11-02 12:24:54 +00:00
|
|
|
Endpoint.prototype._uninstall = function (ds, ac, emitPatches) {
|
|
|
|
if (emitPatches) { ac.retract(this.assertion); }
|
2018-10-27 19:32:12 +00:00
|
|
|
if (this.handler) { ds.unsubscribe(this.handler); }
|
|
|
|
};
|
|
|
|
|
|
|
|
Endpoint.prototype.refresh = function (ds, ac, facet) {
|
|
|
|
let [newAssertion, newHandler] = this.updateFun.call(facet.fields);
|
2018-11-26 13:13:25 +00:00
|
|
|
if (newAssertion !== void 0) newAssertion = Preserves.fromJS(newAssertion);
|
2018-10-27 19:32:12 +00:00
|
|
|
if (!Immutable.is(newAssertion, this.assertion)) {
|
2018-11-02 12:24:54 +00:00
|
|
|
this._uninstall(ds, ac, true);
|
2018-10-27 19:32:12 +00:00
|
|
|
this._install(ds, ac, newAssertion, newHandler);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-11-02 12:24:54 +00:00
|
|
|
Endpoint.prototype.destroy = function (ds, ac, facet, emitPatches) {
|
2018-10-27 19:32:12 +00:00
|
|
|
ds.dataflow.forgetSubject([facet, this.id]);
|
|
|
|
// ^ TODO: this won't work because of object identity problems! Why
|
|
|
|
// does the Racket implementation do this, when the old JS
|
|
|
|
// implementation doesn't?
|
2018-11-11 13:58:16 +00:00
|
|
|
facet.endpoints = facet.endpoints.remove(this.id);
|
2018-11-02 12:24:54 +00:00
|
|
|
this._uninstall(ds, ac, emitPatches);
|
2018-10-27 19:32:12 +00:00
|
|
|
};
|
|
|
|
|
2018-10-22 14:57:13 +00:00
|
|
|
Endpoint.prototype.toString = function () {
|
|
|
|
return 'Endpoint(' + this.id + ')';
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2018-10-27 19:32:12 +00:00
|
|
|
module.exports.Dataspace = Dataspace;
|
2018-11-01 23:30:17 +00:00
|
|
|
module.exports.ActionCollector = ActionCollector;
|
2019-05-30 21:52:40 +00:00
|
|
|
module.exports.PRIORITY = PRIORITY;
|