2016-03-18 21:00:51 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var Immutable = require('immutable');
|
2016-04-07 07:42:54 +00:00
|
|
|
var Dataspace = require('./dataspace.js').Dataspace;
|
2016-03-18 21:00:51 +00:00
|
|
|
var Mux = require('./mux.js');
|
|
|
|
var Patch = require('./patch.js');
|
2016-05-08 15:33:39 +00:00
|
|
|
var Trie = require('./trie.js');
|
2016-03-18 21:00:51 +00:00
|
|
|
var Util = require('./util.js');
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
function spawnActor(state, bootFn) {
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.spawn(new Actor(state, bootFn));
|
2016-03-18 21:00:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function Actor(state, bootFn) {
|
|
|
|
this.state = state;
|
|
|
|
this.facets = Immutable.Set();
|
|
|
|
this.mux = new Mux.Mux();
|
|
|
|
|
|
|
|
this.boot = function() {
|
2016-05-10 04:40:53 +00:00
|
|
|
var self = this;
|
|
|
|
withCurrentFacet(null, function () {
|
|
|
|
bootFn.call(self.state);
|
|
|
|
});
|
|
|
|
self.checkForTermination();
|
2016-03-18 21:00:51 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Actor.prototype.handleEvent = function(e) {
|
|
|
|
this.facets.forEach(function (f) {
|
2016-05-10 04:40:53 +00:00
|
|
|
withCurrentFacet(f, function () { f.handleEvent(e); });
|
2016-03-18 21:00:51 +00:00
|
|
|
});
|
2016-05-12 21:37:14 +00:00
|
|
|
this.facets.forEach(function (f) {
|
|
|
|
withCurrentFacet(f, function () { f.refresh(); });
|
|
|
|
});
|
2016-03-19 18:47:39 +00:00
|
|
|
this.checkForTermination();
|
2016-03-18 21:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.addFacet = function(facet) {
|
|
|
|
this.facets = this.facets.add(facet);
|
|
|
|
};
|
|
|
|
|
2016-03-18 21:08:49 +00:00
|
|
|
Actor.prototype.removeFacet = function(facet) {
|
|
|
|
this.facets = this.facets.remove(facet);
|
2016-03-19 18:47:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Actor.prototype.checkForTermination = function() {
|
2016-03-18 21:08:49 +00:00
|
|
|
if (this.facets.isEmpty()) {
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.exit();
|
2016-03-18 21:08:49 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-03-18 21:00:51 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
function createFacet() {
|
2016-04-07 07:42:54 +00:00
|
|
|
return new Facet(Dataspace.activeBehavior());
|
2016-03-18 21:00:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function Facet(actor) {
|
|
|
|
this.actor = actor;
|
|
|
|
this.endpoints = Immutable.Map();
|
2016-03-18 21:13:52 +00:00
|
|
|
this.initBlocks = Immutable.List();
|
|
|
|
this.doneBlocks = Immutable.List();
|
2016-05-10 04:40:53 +00:00
|
|
|
this.children = Immutable.Set();
|
|
|
|
this.parent = Facet.current;
|
|
|
|
}
|
|
|
|
|
|
|
|
Facet.current = null;
|
|
|
|
|
|
|
|
function withCurrentFacet(facet, f) {
|
|
|
|
var previous = Facet.current;
|
|
|
|
Facet.current = facet;
|
|
|
|
var result;
|
|
|
|
try {
|
|
|
|
result = f();
|
|
|
|
} catch (e) {
|
|
|
|
Facet.current = previous;
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
Facet.current = previous;
|
|
|
|
return result;
|
2016-03-18 21:00:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Facet.prototype.handleEvent = function(e) {
|
|
|
|
var facet = this;
|
2016-05-10 04:40:53 +00:00
|
|
|
facet.endpoints.forEach(function(endpoint) {
|
2016-03-18 21:00:51 +00:00
|
|
|
endpoint.handlerFn.call(facet.actor.state, e);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.addAssertion = function(assertionFn) {
|
|
|
|
return this.addEndpoint(new Endpoint(assertionFn, function(e) {}));
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projectionFn, handlerFn) {
|
|
|
|
var facet = this;
|
2016-03-19 17:48:49 +00:00
|
|
|
switch (eventType) {
|
2016-03-18 21:00:51 +00:00
|
|
|
|
2016-03-19 17:48:49 +00:00
|
|
|
case 'message':
|
|
|
|
return this.addEndpoint(new Endpoint(subscriptionFn, function(e) {
|
|
|
|
if (e.type === 'message') {
|
|
|
|
var proj = projectionFn.call(facet.actor.state);
|
|
|
|
var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel);
|
2016-05-08 15:33:39 +00:00
|
|
|
var match = Trie.matchPattern(e.message, spec);
|
2016-03-18 21:00:51 +00:00
|
|
|
// console.log(match);
|
|
|
|
if (match) {
|
|
|
|
if (isTerminal) { facet.terminate(); }
|
|
|
|
Util.kwApply(handlerFn, facet.actor.state, match);
|
|
|
|
}
|
|
|
|
}
|
2016-03-19 17:48:49 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
case 'asserted': /* fall through */
|
|
|
|
case 'retracted':
|
|
|
|
return this.addEndpoint(new Endpoint(subscriptionFn, function(e) {
|
|
|
|
if (e.type === 'stateChange') {
|
|
|
|
var proj = projectionFn.call(facet.actor.state);
|
|
|
|
var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel);
|
2016-05-08 15:33:39 +00:00
|
|
|
var objects = Trie.projectObjects(eventType === 'asserted'
|
|
|
|
? e.patch.added
|
|
|
|
: e.patch.removed,
|
|
|
|
spec);
|
2016-03-21 01:01:17 +00:00
|
|
|
if (objects && objects.size > 0) {
|
2016-03-18 21:00:51 +00:00
|
|
|
// console.log(objects.toArray());
|
2016-03-19 18:47:39 +00:00
|
|
|
if (isTerminal) { facet.terminate(); }
|
2016-03-18 21:00:51 +00:00
|
|
|
objects.forEach(function (o) { Util.kwApply(handlerFn, facet.actor.state, o); });
|
|
|
|
}
|
|
|
|
}
|
2016-03-19 17:48:49 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
case 'risingEdge':
|
|
|
|
var endpoint = new Endpoint(function() { return Patch.emptyPatch; },
|
|
|
|
function(e) {
|
|
|
|
var newValue = subscriptionFn.call(facet.actor.state);
|
|
|
|
if (newValue && !this.currentValue) {
|
|
|
|
if (isTerminal) { facet.terminate(); }
|
|
|
|
handlerFn.call(facet.actor.state);
|
|
|
|
}
|
|
|
|
this.currentValue = newValue;
|
|
|
|
});
|
|
|
|
endpoint.currentValue = false;
|
|
|
|
return this.addEndpoint(endpoint);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error("Unsupported Facet eventType: " + eventType);
|
|
|
|
}
|
2016-03-18 21:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.addEndpoint = function(endpoint) {
|
|
|
|
var patch = endpoint.subscriptionFn.call(this.actor.state);
|
|
|
|
var r = this.actor.mux.addStream(patch);
|
|
|
|
this.endpoints = this.endpoints.set(r.pid, endpoint);
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.stateChange(r.deltaAggregate);
|
2016-03-18 21:00:51 +00:00
|
|
|
return this; // for chaining
|
|
|
|
};
|
|
|
|
|
2016-03-18 21:13:52 +00:00
|
|
|
Facet.prototype.addInitBlock = function(thunk) {
|
|
|
|
this.initBlocks = this.initBlocks.push(thunk);
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.addDoneBlock = function(thunk) {
|
|
|
|
this.doneBlocks = this.doneBlocks.push(thunk);
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2016-03-18 21:00:51 +00:00
|
|
|
Facet.prototype.refresh = function() {
|
|
|
|
var facet = this;
|
|
|
|
var aggregate = Patch.emptyPatch;
|
|
|
|
this.endpoints.forEach(function(endpoint, eid) {
|
|
|
|
var patch =
|
|
|
|
Patch.retract(Syndicate.__).andThen(endpoint.subscriptionFn.call(facet.actor.state));
|
|
|
|
var r = facet.actor.mux.updateStream(eid, patch);
|
|
|
|
aggregate = aggregate.andThen(r.deltaAggregate);
|
|
|
|
});
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.stateChange(aggregate);
|
2016-03-18 21:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.completeBuild = function() {
|
2016-03-18 21:13:52 +00:00
|
|
|
var facet = this;
|
2016-03-18 21:00:51 +00:00
|
|
|
this.actor.addFacet(this);
|
2016-05-10 04:40:53 +00:00
|
|
|
if (this.parent) {
|
|
|
|
this.parent.children = this.parent.children.add(this);
|
|
|
|
}
|
2016-05-10 21:02:31 +00:00
|
|
|
withCurrentFacet(facet, function () {
|
|
|
|
facet.initBlocks.forEach(function(b) { b.call(facet.actor.state); });
|
|
|
|
});
|
2016-03-18 21:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Facet.prototype.terminate = function() {
|
|
|
|
var facet = this;
|
|
|
|
var aggregate = Patch.emptyPatch;
|
|
|
|
this.endpoints.forEach(function(endpoint, eid) {
|
|
|
|
var r = facet.actor.mux.removeStream(eid);
|
|
|
|
aggregate = aggregate.andThen(r.deltaAggregate);
|
|
|
|
});
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.stateChange(aggregate);
|
2016-03-18 21:00:51 +00:00
|
|
|
this.endpoints = Immutable.Map();
|
2016-05-10 04:40:53 +00:00
|
|
|
if (this.parent) {
|
|
|
|
this.parent.children = this.parent.children.remove(this);
|
|
|
|
}
|
2016-03-18 21:08:49 +00:00
|
|
|
this.actor.removeFacet(this);
|
2016-05-10 21:02:31 +00:00
|
|
|
withCurrentFacet(facet, function () {
|
|
|
|
facet.doneBlocks.forEach(function(b) { b.call(facet.actor.state); });
|
|
|
|
});
|
2016-05-10 04:40:53 +00:00
|
|
|
this.children.forEach(function (child) {
|
|
|
|
child.terminate();
|
|
|
|
});
|
2016-03-18 21:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
function Endpoint(subscriptionFn, handlerFn) {
|
|
|
|
this.subscriptionFn = subscriptionFn;
|
|
|
|
this.handlerFn = handlerFn;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
module.exports.spawnActor = spawnActor;
|
|
|
|
module.exports.createFacet = createFacet;
|