Hook dataflow.js into Syndicate/js; add "during ... actor { ... }"
This commit is contained in:
parent
e2575c3ea1
commit
41693b897c
|
@ -42,15 +42,14 @@ var forEachChild = (function () {
|
|||
return forEachChild;
|
||||
})();
|
||||
|
||||
function buildActor(constructorES5, nameExpOpt, block) {
|
||||
function buildActor(nameExpOpt, block) {
|
||||
var nameExpStr;
|
||||
if (nameExpOpt.numChildren === 1) {
|
||||
nameExpStr = ', ' + nameExpOpt.asES5;
|
||||
} else {
|
||||
nameExpStr = '';
|
||||
}
|
||||
return 'Syndicate.Actor.spawnActor(new '+constructorES5+', '+
|
||||
'function() ' + block.asES5 + nameExpStr + ');';
|
||||
return 'Syndicate.Actor.spawnActor(function() ' + block.asES5 + nameExpStr + ');';
|
||||
}
|
||||
|
||||
function buildFacet(facetBlock, transitionBlock) {
|
||||
|
@ -58,11 +57,11 @@ function buildFacet(facetBlock, transitionBlock) {
|
|||
'\nSyndicate.Actor.createFacet()' +
|
||||
(facetBlock ? facetBlock.asES5 : '') +
|
||||
(transitionBlock ? transitionBlock.asES5 : '') +
|
||||
'.completeBuild(); })();';
|
||||
'.completeBuild(); }).call(this);';
|
||||
}
|
||||
|
||||
function buildOnEvent(isTerminal, eventType, subscription, projection, bindings, body) {
|
||||
return '\n.onEvent(' + isTerminal + ', ' + JSON.stringify(eventType) + ', ' +
|
||||
return '\n.onEvent(Syndicate.Actor.PRIORITY_NORMAL, ' + isTerminal + ', ' + JSON.stringify(eventType) + ', ' +
|
||||
subscription + ', ' + projection +
|
||||
', (function(' + bindings.join(', ') + ') ' + body + '))';
|
||||
}
|
||||
|
@ -86,11 +85,8 @@ function buildCaseEvent(eventPattern, body) {
|
|||
}
|
||||
|
||||
var modifiedSourceActions = {
|
||||
ActorStatement_noConstructor: function(_actor, _namedOpt, nameExpOpt, block) {
|
||||
return buildActor('Object()', nameExpOpt, block);
|
||||
},
|
||||
ActorStatement_withConstructor: function(_actor, ctorExp, _namedOpt, nameExpOpt, block) {
|
||||
return buildActor(ctorExp.asES5, nameExpOpt, block);
|
||||
ActorStatement: function(_actor, _namedOpt, nameExpOpt, block) {
|
||||
return buildActor(nameExpOpt, block);
|
||||
},
|
||||
|
||||
DataspaceStatement_ground: function(_ground, _dataspace, maybeId, block) {
|
||||
|
@ -133,6 +129,22 @@ var modifiedSourceActions = {
|
|||
label + ', ' + JSON.stringify(formals) + ');';
|
||||
},
|
||||
|
||||
FieldDeclarationStatement: function(_field, memberExpr, _eq, initExpr, sc) {
|
||||
return 'Syndicate.Actor.declareField(' + memberExpr.memberObjectExpr.asES5 + ', ' +
|
||||
memberExpr.memberPropExpr.asES5 + ', ' + initExpr.asES5 + ')' +
|
||||
sc.interval.contents;
|
||||
},
|
||||
|
||||
MemberExpression_fieldRefExp: function (_field, memberExpr) {
|
||||
return 'Syndicate.Actor.referenceField(' + memberExpr.memberObjectExpr.asES5 + ', ' +
|
||||
memberExpr.memberPropExpr.asES5 + ')';
|
||||
},
|
||||
|
||||
UnaryExpression_fieldDelExp: function (_delete, _field, memberExpr) {
|
||||
return 'Syndicate.Actor.deleteField(' + memberExpr.memberObjectExpr.asES5 + ', ' +
|
||||
memberExpr.memberPropExpr.asES5 + ')';
|
||||
},
|
||||
|
||||
SendMessageStatement: function(_colons, expr, sc) {
|
||||
return 'Syndicate.Dataspace.send(' + expr.asES5 + ')' + sc.interval.contents;
|
||||
},
|
||||
|
@ -165,6 +177,9 @@ var modifiedSourceActions = {
|
|||
FacetSituation_onEvent: function (_on, _event, id, block) {
|
||||
return '\n.addOnEventHandler((function(' + id.asES5 + ') ' + block.asES5 + '))';
|
||||
},
|
||||
FacetSituation_dataflow: function (_dataflow, block) {
|
||||
return '\n.addDataflow((function () ' + block.asES5 + '))';
|
||||
},
|
||||
FacetSituation_during: function(_during, pattern, facetBlock) {
|
||||
var cachedAssertionVar = gensym('cachedAssertion');
|
||||
return buildOnEvent(false,
|
||||
|
@ -184,6 +199,28 @@ var modifiedSourceActions = {
|
|||
'{}') +
|
||||
'.completeBuild(); }');
|
||||
},
|
||||
FacetSituation_duringActor: function(_during, pattern, _actor, _named, nameExpOpt, facetBlock) {
|
||||
var cachedAssertionVar = gensym('cachedAssertion');
|
||||
var actorBlock = {
|
||||
asES5: '{ ' + facetBlock.facetVarDecls +
|
||||
'\nSyndicate.Actor.createFacet()' +
|
||||
facetBlock.asES5 +
|
||||
buildOnEvent(true,
|
||||
'retracted',
|
||||
pattern.instantiatedSubscription(cachedAssertionVar),
|
||||
pattern.instantiatedProjection(cachedAssertionVar),
|
||||
[],
|
||||
'{}') +
|
||||
'.completeBuild(); }'
|
||||
};
|
||||
return buildOnEvent(false,
|
||||
'asserted',
|
||||
pattern.subscription,
|
||||
pattern.projection,
|
||||
pattern.bindings,
|
||||
'{ var '+cachedAssertionVar+' = '+pattern.instantiatedAssertion+';'+
|
||||
'\n' + buildActor(nameExpOpt, actorBlock) + ' }');
|
||||
},
|
||||
|
||||
AssertWhenClause: function(_when, _lparen, expr, _rparen) {
|
||||
return expr.asES5;
|
||||
|
@ -199,6 +236,24 @@ var modifiedSourceActions = {
|
|||
|
||||
semantics.extendAttribute('modifiedSource', modifiedSourceActions);
|
||||
|
||||
semantics.addAttribute('memberObjectExpr', {
|
||||
MemberExpression_propRefExp: function(objExpr, _dot, id) {
|
||||
return objExpr;
|
||||
},
|
||||
MemberExpression_arrayRefExp: function(objExpr, _lbrack, propExpr, _rbrack) {
|
||||
return objExpr;
|
||||
}
|
||||
});
|
||||
|
||||
semantics.addAttribute('memberPropExpr', {
|
||||
MemberExpression_propRefExp: function(objExpr, _dot, id) {
|
||||
return { asES5: JSON.stringify(id.interval.contents) };
|
||||
},
|
||||
MemberExpression_arrayRefExp: function(objExpr, _lbrack, propExpr, _rbrack) {
|
||||
return propExpr;
|
||||
}
|
||||
});
|
||||
|
||||
semantics.addAttribute('facetVarDecls', {
|
||||
FacetBlock: function (_leftParen, varDecls, _init, _situations, _done, _rightParen) {
|
||||
return varDecls.asES5.join(' ');
|
||||
|
@ -269,7 +324,7 @@ semantics.addAttribute('instantiatedAssertion', {
|
|||
var fragments = [];
|
||||
fragments.push('(function() { var _ = Syndicate.__; return ');
|
||||
children.forEach(function (c) { fragments.push(c.buildSubscription('instantiated')); });
|
||||
fragments.push('; })()');
|
||||
fragments.push('; }).call(this)');
|
||||
return fragments.join('');
|
||||
}
|
||||
});
|
||||
|
@ -345,6 +400,12 @@ semantics.addOperation('buildSubscription(mode)', {
|
|||
}
|
||||
},
|
||||
|
||||
MemberExpression_fieldRefExp: function (_field, memberExpr) {
|
||||
return 'Syndicate.Actor.referenceField(' +
|
||||
memberExpr.memberObjectExpr.buildSubscription(this.args.mode) + ', ' +
|
||||
memberExpr.memberPropExpr.buildSubscription(this.args.mode) + ')';
|
||||
},
|
||||
|
||||
identifier: function(_name) {
|
||||
var i = this.interval.contents;
|
||||
if (i[0] === '$' && i.length > 1) {
|
||||
|
|
|
@ -7,10 +7,13 @@ message type deposit(amount);
|
|||
|
||||
ground dataspace {
|
||||
actor {
|
||||
this.balance = 0;
|
||||
field this.balance = 0;
|
||||
|
||||
react {
|
||||
assert account(this.balance);
|
||||
dataflow {
|
||||
console.log("Balance inside account is", this.balance);
|
||||
}
|
||||
on message deposit($amount) {
|
||||
this.balance += amount;
|
||||
}
|
||||
|
|
|
@ -20,19 +20,19 @@ assertion type foo(x, y);
|
|||
|
||||
ground dataspace {
|
||||
actor {
|
||||
var x = 123;
|
||||
field this.x = 123;
|
||||
react {
|
||||
assert foo(x, 999);
|
||||
assert foo(this.x, 999);
|
||||
|
||||
during foo(x, $v) {
|
||||
during foo(this.x, $v) {
|
||||
do {
|
||||
console.log('x=', x, 'v=', v);
|
||||
if (x === 123) {
|
||||
x = 124;
|
||||
console.log('x=', this.x, 'v=', v);
|
||||
if (this.x === 123) {
|
||||
this.x = 124;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
console.log('finally for x=', x, 'v=', v);
|
||||
console.log('finally for x=', this.x, 'v=', v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
// bin/syndicatec compiler/demo-filesystem.js | node
|
||||
|
||||
// Good output:
|
||||
//
|
||||
// At least one reader exists for: hello.txt
|
||||
// hello.txt has content undefined
|
||||
// hello.txt has content "a"
|
||||
// hello.txt has content undefined
|
||||
// hello.txt has content "c"
|
||||
// hello.txt has content "quit demo"
|
||||
// The hello.txt file contained 'quit demo', so we will quit
|
||||
// second observer sees that hello.txt content is "final contents"
|
||||
// No remaining readers exist for: hello.txt
|
||||
|
||||
var Syndicate = require('./src/main.js');
|
||||
|
||||
assertion type file(name, content) = "file";
|
||||
|
@ -17,16 +29,16 @@ ground dataspace {
|
|||
do {
|
||||
console.log("At least one reader exists for:", name);
|
||||
}
|
||||
assert file(name, this.files[name]);
|
||||
assert file(name, field this.files[name]);
|
||||
finally {
|
||||
console.log("No remaining readers exist for:", name);
|
||||
}
|
||||
}
|
||||
on message saveFile($name, $newcontent) {
|
||||
this.files[name] = newcontent;
|
||||
field this.files[name] = newcontent;
|
||||
}
|
||||
on message deleteFile($name) {
|
||||
delete this.files[name];
|
||||
delete field this.files[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ Syndicate <: ES5 {
|
|||
| DataspaceStatement
|
||||
| ActorFacetStatement
|
||||
| AssertionTypeDeclarationStatement
|
||||
| FieldDeclarationStatement
|
||||
| SendMessageStatement
|
||||
|
||||
FunctionBodyBlock = "{" FunctionBody "}" // odd that this isn't in es5.ohm somewhere
|
||||
|
||||
ActorStatement
|
||||
= actor ~named CallExpression (named Expression<withIn>)? FunctionBodyBlock -- withConstructor
|
||||
| actor (named Expression<withIn>)? FunctionBodyBlock -- noConstructor
|
||||
= actor (named Expression<withIn>)? FunctionBodyBlock
|
||||
|
||||
DataspaceStatement
|
||||
= ground dataspace identifier? FunctionBodyBlock -- ground
|
||||
|
@ -31,22 +31,33 @@ Syndicate <: ES5 {
|
|||
AssertionTypeDeclarationStatement
|
||||
= (assertion | message) type identifier "(" FormalParameterList ")" ("=" stringLiteral)? #(sc)
|
||||
|
||||
FieldDeclarationStatement = field MemberExpression "=" AssignmentExpression<withIn> #(sc)
|
||||
MemberExpression += field MemberExpression -- fieldRefExp
|
||||
UnaryExpression += delete field MemberExpression -- fieldDelExp
|
||||
|
||||
SendMessageStatement = "::" Expression<withIn> #(sc)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Ongoing event handlers.
|
||||
|
||||
FacetBlock = "{" (VariableStatement | FunctionDeclaration)* FacetInitBlock? FacetSituation* FacetDoneBlock? "}"
|
||||
FacetBlock = "{"
|
||||
(VariableStatement | FieldDeclarationStatement | FunctionDeclaration)*
|
||||
FacetInitBlock?
|
||||
FacetSituation*
|
||||
FacetDoneBlock?
|
||||
"}"
|
||||
FacetStateTransitionBlock = "{" FacetStateTransition* "}"
|
||||
|
||||
FacetInitBlock = do FunctionBodyBlock
|
||||
FacetDoneBlock = finally FunctionBodyBlock
|
||||
|
||||
FacetSituation
|
||||
= assert FacetPattern AssertWhenClause? #(sc) -- assert
|
||||
| on FacetEventPattern FunctionBodyBlock -- event
|
||||
| on event identifier FunctionBodyBlock -- onEvent
|
||||
| during FacetPattern FacetBlock -- during
|
||||
= assert FacetPattern AssertWhenClause? #(sc) -- assert
|
||||
| on FacetEventPattern FunctionBodyBlock -- event
|
||||
| on event identifier FunctionBodyBlock -- onEvent
|
||||
| dataflow FunctionBodyBlock -- dataflow
|
||||
| during FacetPattern FacetBlock -- during
|
||||
| during FacetPattern actor (named Expression<withIn>)? FacetBlock -- duringActor
|
||||
|
||||
AssertWhenClause = when "(" Expression<withIn> ")"
|
||||
|
||||
|
@ -76,9 +87,11 @@ Syndicate <: ES5 {
|
|||
assert = "assert" ~identifierPart
|
||||
asserted = "asserted" ~identifierPart
|
||||
assertion = "assertion" ~identifierPart
|
||||
dataflow = "dataflow" ~identifierPart
|
||||
dataspace = "dataspace" ~identifierPart
|
||||
during = "during" ~identifierPart
|
||||
event = "event" ~identifierPart
|
||||
field = "field" ~identifierPart
|
||||
ground = "ground" ~identifierPart
|
||||
message = "message" ~identifierPart
|
||||
metalevel = "metalevel" ~identifierPart
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*.expanded.js
|
|
@ -0,0 +1,7 @@
|
|||
all: index.expanded.js
|
||||
|
||||
%.expanded.js: %.js
|
||||
../../bin/syndicatec $< > $@ || (rm -f $@; false)
|
||||
|
||||
clean:
|
||||
rm -f *.expanded.js
|
|
@ -1,14 +0,0 @@
|
|||
"use strict";
|
||||
new Syndicate.Ground(function () {
|
||||
Syndicate.UI.spawnUIDriver();
|
||||
|
||||
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||
var counter = 0;
|
||||
var ui = new Syndicate.UI.Anchor();
|
||||
Syndicate.Actor.createFacet()
|
||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(ui.html('#button-label',''+counter), 0); }))
|
||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#counter','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#counter','click',_), metalevel: 0 }; }), (function() {
|
||||
counter++;
|
||||
})).completeBuild();
|
||||
});
|
||||
}).startStepping();
|
|
@ -2,12 +2,12 @@ ground dataspace {
|
|||
Syndicate.UI.spawnUIDriver();
|
||||
|
||||
actor {
|
||||
var counter = 0;
|
||||
field this.counter = 0;
|
||||
var ui = new Syndicate.UI.Anchor();
|
||||
react {
|
||||
assert ui.html('#button-label', '' + counter);
|
||||
assert ui.html('#button-label', '' + this.counter);
|
||||
on message Syndicate.UI.globalEvent('#counter', 'click', _) {
|
||||
counter++;
|
||||
this.counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ function spawnChatApp() {
|
|||
|
||||
actor {
|
||||
var ui = new Syndicate.UI.Anchor();
|
||||
field this.nym;
|
||||
field this.status;
|
||||
react {
|
||||
on asserted inputValue('#nym', $v) { this.nym = v; }
|
||||
on asserted inputValue('#status', $v) { this.status = v; }
|
||||
|
@ -83,17 +85,11 @@ assertion type inputValue(selector, value);
|
|||
function spawnInputChangeMonitor() {
|
||||
actor {
|
||||
react {
|
||||
on asserted Syndicate.observe(inputValue($selector, _)) {
|
||||
actor {
|
||||
this.value = $(selector).val();
|
||||
react {
|
||||
assert inputValue(selector, this.value);
|
||||
on message Syndicate.UI.globalEvent(selector, 'change', $e) {
|
||||
this.value = e.target.value;
|
||||
}
|
||||
} until {
|
||||
case retracted Syndicate.observe(inputValue(selector, _));
|
||||
}
|
||||
during Syndicate.observe(inputValue($selector, _)) actor {
|
||||
field this.value = $(selector).val();
|
||||
assert inputValue(selector, this.value);
|
||||
on message Syndicate.UI.globalEvent(selector, 'change', $e) {
|
||||
this.value = e.target.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ function spawnRemoteListener() {
|
|||
|
||||
function spawnStoveSwitch() {
|
||||
actor {
|
||||
this.powerOn = false;
|
||||
field this.powerOn = false;
|
||||
this.ui = new Syndicate.UI.Anchor();
|
||||
react {
|
||||
assert componentPresent('stove switch');
|
||||
|
@ -85,7 +85,7 @@ function spawnStoveSwitch() {
|
|||
|
||||
function spawnPowerDrawMonitor() {
|
||||
actor {
|
||||
this.watts = 0;
|
||||
field this.watts = 0;
|
||||
this.ui = new Syndicate.UI.Anchor();
|
||||
react {
|
||||
assert componentPresent('power draw monitor');
|
||||
|
|
|
@ -40,7 +40,7 @@ ground dataspace G {
|
|||
var geocoder = new google.maps.Geocoder();
|
||||
|
||||
var wsurl_base = 'wss://demo-broker.syndicate-lang.org:8443/location/';
|
||||
var wsurl = wsurl_base + group_element.value.trim();
|
||||
field this.wsurl = wsurl_base + group_element.value.trim();
|
||||
|
||||
var watchId = ('geolocation' in navigator)
|
||||
&& navigator.geolocation.watchPosition(Syndicate.Dataspace.wrap(function (pos) {
|
||||
|
@ -62,21 +62,21 @@ ground dataspace G {
|
|||
}));
|
||||
|
||||
react {
|
||||
var currentLocation = null;
|
||||
field this.currentLocation = null;
|
||||
var selectedMarker = null;
|
||||
|
||||
assert brokerConnection(wsurl);
|
||||
assert toBroker(wsurl, currentLocation) when (currentLocation);
|
||||
assert brokerConnection(this.wsurl);
|
||||
assert toBroker(this.wsurl, this.currentLocation) when (this.currentLocation);
|
||||
|
||||
on message Syndicate.UI.globalEvent('#my_email', 'change', _) {
|
||||
var v = email_element.value.trim();
|
||||
if (currentLocation) currentLocation[1] = v;
|
||||
if (this.currentLocation) this.currentLocation = this.currentLocation.set(1, v);
|
||||
localStorage.my_email = v;
|
||||
}
|
||||
|
||||
on message Syndicate.UI.globalEvent('#group', 'change', _) {
|
||||
localStorage.group = group_element.value.trim();
|
||||
wsurl = wsurl_base + group_element.value.trim();
|
||||
this.wsurl = wsurl_base + group_element.value.trim();
|
||||
}
|
||||
|
||||
on message Syndicate.UI.globalEvent('#findMarker', 'click', $e) {
|
||||
|
@ -87,10 +87,10 @@ ground dataspace G {
|
|||
}
|
||||
|
||||
on message ($loc = locationRecord(_, _, _, _, _)) {
|
||||
currentLocation = loc;
|
||||
this.currentLocation = loc;
|
||||
}
|
||||
|
||||
during fromBroker(wsurl, locationRecord($id, $email, _, _, _)) {
|
||||
during fromBroker(this.wsurl, locationRecord($id, $email, _, _, _)) {
|
||||
var ui = new Syndicate.UI.Anchor();
|
||||
var marker = new google.maps.Marker({
|
||||
map: map,
|
||||
|
@ -131,7 +131,7 @@ ground dataspace G {
|
|||
selectMarker();
|
||||
if (latestPosition) map.panTo(latestPosition);
|
||||
}
|
||||
on asserted fromBroker(wsurl, locationRecord(id, email, $timestamp, $lat, $lng)) {
|
||||
on asserted fromBroker(this.wsurl, locationRecord(id, email, $timestamp, $lat, $lng)) {
|
||||
latestTimestamp = new Date(timestamp);
|
||||
latestPosition = {lat: lat, lng: lng};
|
||||
marker.setPosition(latestPosition);
|
||||
|
|
|
@ -14,8 +14,8 @@ ground dataspace G {
|
|||
var color = tinycolor('hsl ' + (Math.random() * 360 | 0) + ' 100% 50%').toHexString();
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
var publishedX = x;
|
||||
var publishedY = y;
|
||||
field this.publishedX = x;
|
||||
field this.publishedY = y;
|
||||
|
||||
function clamp(v) {
|
||||
var limit = 9.8;
|
||||
|
@ -28,10 +28,10 @@ ground dataspace G {
|
|||
|
||||
assert Syndicate.UI.uiAttribute('rect#my_color', 'fill', color);
|
||||
|
||||
assert toBroker(wsurl, point(color, publishedX, publishedY));
|
||||
assert toBroker(wsurl, point(color, this.publishedX, this.publishedY));
|
||||
on message Syndicate.Timer.periodicTick(100) {
|
||||
publishedX = x;
|
||||
publishedY = y;
|
||||
this.publishedX = x;
|
||||
this.publishedY = y;
|
||||
}
|
||||
|
||||
on message Syndicate.UI.windowEvent('deviceorientation', $e) {
|
||||
|
|
|
@ -6,16 +6,16 @@ ground dataspace {
|
|||
actor {
|
||||
react until {
|
||||
case asserted Syndicate.observe(beep(_)) {
|
||||
var counter = 0;
|
||||
field this.counter = 0;
|
||||
react {
|
||||
do {
|
||||
:: beep(counter++);
|
||||
:: beep(this.counter++);
|
||||
}
|
||||
on message beep(_) {
|
||||
:: beep(counter++);
|
||||
:: beep(this.counter++);
|
||||
}
|
||||
} until {
|
||||
case (counter >= 10);
|
||||
case (this.counter > 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@ ground dataspace G {
|
|||
|
||||
actor {
|
||||
var ui = new Syndicate.UI.Anchor();
|
||||
field this.angle;
|
||||
field this.handX;
|
||||
field this.handY;
|
||||
|
||||
react {
|
||||
assert ui.html('#clock',
|
||||
'<svg width="300px" viewBox="0 0 100 100">'+
|
||||
|
|
|
@ -20,7 +20,7 @@ function spawnModel() {
|
|||
function spawnView() {
|
||||
actor named 'view' {
|
||||
var ui = new Syndicate.UI.Anchor();
|
||||
var orderColumn = 2;
|
||||
field this.orderColumn = 2;
|
||||
|
||||
function cell(text) {
|
||||
// Should escape text in a real application.
|
||||
|
@ -28,13 +28,13 @@ function spawnView() {
|
|||
}
|
||||
|
||||
react {
|
||||
on message setSortColumn($c) { orderColumn = c; }
|
||||
on message setSortColumn($c) { this.orderColumn = c; }
|
||||
|
||||
during person($id, $firstName, $lastName, $address, $age) {
|
||||
assert ui.context(id)
|
||||
.html('table#the-table tbody',
|
||||
'<tr>' + [id, firstName, lastName, address, age].map(cell).join('') + '</tr>',
|
||||
[id, firstName, lastName, address, age][orderColumn]);
|
||||
[id, firstName, lastName, address, age][this.orderColumn]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,24 +26,24 @@ function piece(text, pos, lo, hi, cls) {
|
|||
|
||||
function spawnGui() {
|
||||
actor {
|
||||
this.text = '';
|
||||
this.pos = 0;
|
||||
this.highlightState = false;
|
||||
|
||||
this.updateDisplay = function () {
|
||||
var text = this.text;
|
||||
var pos = this.pos;
|
||||
var highlight = this.highlightState;
|
||||
var hLeft = highlight ? highlight[0] : 0;
|
||||
var hRight = highlight ? highlight[1] : 0;
|
||||
document.getElementById("fieldContents").innerHTML = highlight
|
||||
? piece(text, pos, 0, hLeft, "normal") +
|
||||
piece(text, pos, hLeft, hRight, "highlight") +
|
||||
piece(text, pos, hRight, text.length + 1, "normal")
|
||||
: piece(text, pos, 0, text.length + 1, "normal");
|
||||
};
|
||||
|
||||
react {
|
||||
field this.text = '';
|
||||
field this.pos = 0;
|
||||
field this.highlightState = false;
|
||||
|
||||
dataflow {
|
||||
var text = this.text;
|
||||
var pos = this.pos;
|
||||
var highlight = this.highlightState;
|
||||
var hLeft = highlight ? highlight[0] : 0;
|
||||
var hRight = highlight ? highlight[1] : 0;
|
||||
document.getElementById("fieldContents").innerHTML = highlight
|
||||
? piece(text, pos, 0, hLeft, "normal") +
|
||||
piece(text, pos, hLeft, hRight, "highlight") +
|
||||
piece(text, pos, hRight, text.length + 1, "normal")
|
||||
: piece(text, pos, 0, text.length + 1, "normal");
|
||||
}
|
||||
|
||||
on message globalEvent("#inputRow", "+keydown", $event) {
|
||||
switch (event.keyCode) {
|
||||
case 37 /* left */: :: fieldCommand("cursorLeft"); break;
|
||||
|
@ -67,12 +67,10 @@ function spawnGui() {
|
|||
on asserted fieldContents($text, $pos) {
|
||||
this.text = text;
|
||||
this.pos = pos;
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
on asserted highlight($state) {
|
||||
this.highlightState = state;
|
||||
this.updateDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,10 +81,10 @@ function spawnGui() {
|
|||
|
||||
function spawnModel() {
|
||||
actor {
|
||||
this.fieldValue = "initial";
|
||||
this.cursorPos = this.fieldValue.length; /* positions address gaps between characters */
|
||||
|
||||
react {
|
||||
field this.fieldValue = "initial";
|
||||
field this.cursorPos = this.fieldValue.length; /* positions address gaps between characters */
|
||||
|
||||
assert fieldContents(this.fieldValue, this.cursorPos);
|
||||
|
||||
on message fieldCommand("cursorLeft") {
|
||||
|
@ -126,29 +124,28 @@ function spawnModel() {
|
|||
|
||||
function spawnSearch() {
|
||||
actor {
|
||||
this.fieldValue = "";
|
||||
this.highlight = false;
|
||||
|
||||
this.search = function () {
|
||||
var searchtext = document.getElementById("searchBox").value;
|
||||
if (searchtext) {
|
||||
var pos = this.fieldValue.indexOf(searchtext);
|
||||
this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
|
||||
} else {
|
||||
this.highlight = false;
|
||||
}
|
||||
};
|
||||
|
||||
react {
|
||||
field this.searchtext = document.getElementById("searchBox").value;
|
||||
field this.fieldValue = "";
|
||||
field this.highlight = false;
|
||||
|
||||
assert highlight(this.highlight);
|
||||
|
||||
dataflow {
|
||||
if (this.searchtext) {
|
||||
var pos = this.fieldValue.indexOf(this.searchtext);
|
||||
this.highlight = (pos !== -1) && [pos, pos + this.searchtext.length];
|
||||
} else {
|
||||
this.highlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
on message globalEvent("#searchBox", "input", $event) {
|
||||
this.search();
|
||||
this.searchtext = document.getElementById("searchBox").value;
|
||||
}
|
||||
|
||||
on asserted fieldContents($text, _) {
|
||||
this.fieldValue = text;
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ assertion type show(completed);
|
|||
|
||||
function todoListItemModel(initialId, initialTitle, initialCompleted) {
|
||||
actor {
|
||||
this.id = initialId;
|
||||
this.title = initialTitle;
|
||||
this.completed = initialCompleted;
|
||||
|
||||
react {
|
||||
field this.id = initialId;
|
||||
field this.title = initialTitle;
|
||||
field this.completed = initialCompleted;
|
||||
|
||||
assert todo(this.id, this.title, this.completed);
|
||||
|
||||
on message setCompleted(this.id, $v) { this.completed = v; }
|
||||
|
@ -58,8 +58,9 @@ function getTemplate(id) {
|
|||
function todoListItemView(id) {
|
||||
actor {
|
||||
this.ui = new Syndicate.UI.Anchor();
|
||||
this.editing = false;
|
||||
react {
|
||||
field this.editing = false;
|
||||
|
||||
during todo(id, $title, $completed) {
|
||||
during show(completed) {
|
||||
assert this.ui.html('.todo-list',
|
||||
|
@ -158,15 +159,15 @@ ground dataspace G {
|
|||
}
|
||||
|
||||
actor {
|
||||
var completedCount = 0;
|
||||
var activeCount = 0;
|
||||
react {
|
||||
on asserted todo($id, _, $completed) { if (completed) completedCount++; else activeCount++; }
|
||||
on retracted todo($id, _, $completed) { if (completed) completedCount--; else activeCount--; }
|
||||
assert activeTodoCount(activeCount);
|
||||
assert completedTodoCount(completedCount);
|
||||
assert totalTodoCount(activeCount + completedCount);
|
||||
assert allCompleted() when (completedCount > 0 && activeCount === 0);
|
||||
field this.completedCount = 0;
|
||||
field this.activeCount = 0;
|
||||
on asserted todo($id, _, $c) { if (c) this.completedCount++; else this.activeCount++; }
|
||||
on retracted todo($id, _, $c) { if (c) this.completedCount--; else this.activeCount--; }
|
||||
assert activeTodoCount(this.activeCount);
|
||||
assert completedTodoCount(this.completedCount);
|
||||
assert totalTodoCount(this.activeCount + this.completedCount);
|
||||
assert allCompleted() when (this.completedCount > 0 && this.activeCount === 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,19 +91,27 @@ function seller() {
|
|||
/// We give our actor two state variables: a dictionary recording our
|
||||
/// inventory of books (mapping title to price), and a counter
|
||||
/// tracking the next order ID to be allocated.
|
||||
///
|
||||
/// We mark each property (entry) in the `books` table as a *field* so
|
||||
/// that dependency-tracking on a per-book basis is enabled. As a result,
|
||||
/// when a book is sold, any client still interested in its price will
|
||||
/// learn that the book is no longer available.
|
||||
///
|
||||
/// We do not enable dependency-tracking for either the `books` table
|
||||
/// itself or the `nextOrderId` field: nothing depends on tracking
|
||||
/// changes in their values.
|
||||
|
||||
this.books = {
|
||||
"The Wind in the Willows": 3.95,
|
||||
"Catch 22": 2.22,
|
||||
"Candide": 34.95
|
||||
};
|
||||
this.books = {};
|
||||
field this.books["The Wind in the Willows"] = 3.95;
|
||||
field this.books["Catch 22"] = 2.22;
|
||||
field this.books["Candide"] = 34.95;
|
||||
this.nextOrderId = 10001483;
|
||||
|
||||
/// Looking up a price yields `false` if no such book is in our
|
||||
/// inventory.
|
||||
|
||||
this.priceOf = function (title) {
|
||||
return (title in this.books) && this.books[title];
|
||||
return (title in this.books) && field this.books[title];
|
||||
};
|
||||
|
||||
/// The seller responds to interest in bookQuotes by asserting a
|
||||
|
@ -134,7 +142,7 @@ function seller() {
|
|||
/// replying to the orderer.
|
||||
|
||||
var orderId = this.nextOrderId++;
|
||||
delete this.books[title];
|
||||
delete field this.books[title];
|
||||
|
||||
actor {
|
||||
whileRelevantAssert(
|
||||
|
|
220
js/src/actor.js
220
js/src/actor.js
|
@ -8,20 +8,22 @@ var Mux = require('./mux.js');
|
|||
var Patch = require('./patch.js');
|
||||
var Trie = require('./trie.js');
|
||||
var Util = require('./util.js');
|
||||
var Dataflow = require('./dataflow.js');
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
function spawnActor(state, bootFn, optName) {
|
||||
Dataspace.spawn(new Actor(state, bootFn, optName));
|
||||
function spawnActor(bootFn, optName) {
|
||||
Dataspace.spawn(new Actor(bootFn, optName));
|
||||
}
|
||||
|
||||
function Actor(state, bootFn, optName) {
|
||||
this.state = state;
|
||||
function Actor(bootFn, optName) {
|
||||
this.fields = {};
|
||||
this.facets = Immutable.Set();
|
||||
this.mux = new Mux.Mux();
|
||||
this.previousKnowledge = Trie.emptyTrie;
|
||||
this.knowledge = Trie.emptyTrie;
|
||||
this.pendingActions = [];
|
||||
this.dataflowGraph = new Dataflow.Graph();
|
||||
|
||||
if (typeof optName !== 'undefined') {
|
||||
this.name = optName;
|
||||
|
@ -29,36 +31,82 @@ function Actor(state, bootFn, optName) {
|
|||
|
||||
this.boot = function() {
|
||||
var self = this;
|
||||
withCurrentFacet(null, function () {
|
||||
bootFn.call(self.state);
|
||||
withCurrentFacet(self, null, function () {
|
||||
bootFn.call(self.fields);
|
||||
});
|
||||
self.checkForTermination();
|
||||
this.quiesce();
|
||||
};
|
||||
}
|
||||
|
||||
Actor.current = null;
|
||||
|
||||
(function () {
|
||||
var priorities = ['PRIORITY_QUERY_HIGH',
|
||||
'PRIORITY_QUERY',
|
||||
'PRIORITY_QUERY_HANDLER',
|
||||
'PRIORITY_NORMAL'];
|
||||
for (var i = 0; i < priorities.length; i++) {
|
||||
Actor[priorities[i]] = i;
|
||||
}
|
||||
})();
|
||||
|
||||
Actor.prototype.nextPendingAction = function (probe) {
|
||||
for (var i = 0; i < this.pendingActions.length; i++) {
|
||||
var q = this.pendingActions[i];
|
||||
if (q.length > 0) {
|
||||
return probe ? true : q.shift();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Actor.prototype.handleEvent = function(e) {
|
||||
var actor = this;
|
||||
if (e.type === 'stateChange') {
|
||||
this.previousKnowledge = this.knowledge;
|
||||
this.knowledge = e.patch.updateInterests(this.knowledge);
|
||||
}
|
||||
if (this.pendingActions.length > 0) {
|
||||
if (this.nextPendingAction(true)) {
|
||||
throw new Error('Syndicate: pendingActions must not be nonempty at start of handleEvent');
|
||||
}
|
||||
this.facets.forEach(function (f) {
|
||||
withCurrentFacet(f, function () { f.handleEvent(e); });
|
||||
withCurrentFacet(actor, f, function () { f.handleEvent(e); });
|
||||
});
|
||||
while (this.pendingActions.length) {
|
||||
var entry = this.pendingActions.shift();
|
||||
withCurrentFacet(entry.facet, entry.action);
|
||||
this.quiesce();
|
||||
};
|
||||
|
||||
Actor.prototype.quiesce = function() {
|
||||
var actor = this;
|
||||
|
||||
while (true) {
|
||||
var entry = this.nextPendingAction(false);
|
||||
if (!entry) break;
|
||||
|
||||
withCurrentFacet(actor, entry.facet, entry.action);
|
||||
|
||||
this.dataflowGraph.repairDamage(function (subjectId) {
|
||||
var facet = subjectId[0];
|
||||
var endpoint = subjectId[1];
|
||||
withCurrentFacet(actor, facet, function () {
|
||||
// TODO: coalesce patches within a single actor
|
||||
var aggregate = Patch.emptyPatch;
|
||||
var patch = Patch.retract(__).andThen(endpoint.subscriptionFn.call(facet.fields));
|
||||
var r = facet.actor.mux.updateStream(endpoint.eid, patch);
|
||||
aggregate = aggregate.andThen(r.deltaAggregate);
|
||||
Dataspace.stateChange(aggregate);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.facets.forEach(function (f) {
|
||||
withCurrentFacet(f, function () { f.refresh(); });
|
||||
});
|
||||
|
||||
this.checkForTermination();
|
||||
};
|
||||
|
||||
Actor.prototype.pushAction = function (a) {
|
||||
this.pendingActions.push({facet: Facet.current, action: a});
|
||||
Actor.prototype.pushAction = function (a, priorityOpt) {
|
||||
var priority = typeof priorityOpt === 'undefined' ? Actor.PRIORITY_NORMAL : priorityOpt;
|
||||
while (this.pendingActions.length < priority + 1) {
|
||||
this.pendingActions.push([]);
|
||||
}
|
||||
this.pendingActions[priority].push({facet: Facet.current, action: a});
|
||||
};
|
||||
|
||||
Actor.prototype.addFacet = function(facet) {
|
||||
|
@ -88,29 +136,34 @@ function Facet(actor) {
|
|||
this.doneBlocks = Immutable.List();
|
||||
this.children = Immutable.Set();
|
||||
this.parent = Facet.current;
|
||||
this.fields = Dataflow.Graph.newScope((this.parent && this.parent.fields) || actor.fields);
|
||||
this.terminated = false;
|
||||
}
|
||||
|
||||
Facet.current = null;
|
||||
|
||||
function withCurrentFacet(facet, f) {
|
||||
var previous = Facet.current;
|
||||
function withCurrentFacet(actor, facet, f) {
|
||||
var previousActor = Actor.current;
|
||||
var previousFacet = Facet.current;
|
||||
Actor.current = actor;
|
||||
Facet.current = facet;
|
||||
var result;
|
||||
try {
|
||||
result = f();
|
||||
} catch (e) {
|
||||
Facet.current = previous;
|
||||
Actor.current = previousActor;
|
||||
Facet.current = previousFacet;
|
||||
throw e;
|
||||
}
|
||||
Facet.current = previous;
|
||||
Actor.current = previousActor;
|
||||
Facet.current = previousFacet;
|
||||
return result;
|
||||
}
|
||||
|
||||
Facet.prototype.handleEvent = function(e) {
|
||||
var facet = this;
|
||||
facet.endpoints.forEach(function(endpoint) {
|
||||
endpoint.handlerFn.call(facet.actor.state, e);
|
||||
endpoint.handlerFn.call(facet.fields, e);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -118,27 +171,47 @@ Facet.prototype.addAssertion = function(assertionFn) {
|
|||
return this.addEndpoint(new Endpoint(assertionFn, function(e) {}));
|
||||
};
|
||||
|
||||
Facet.prototype.addOnEventHandler = function(handler) {
|
||||
Facet.prototype.addOnEventHandler = function(handler, priorityOpt) {
|
||||
var facet = this;
|
||||
return this.addEndpoint(new Endpoint(function () { return Patch.emptyPatch; }, function (e) {
|
||||
facet.actor.pushAction(function () { handler(e); });
|
||||
facet.actor.pushAction(function () { handler(e); }, priorityOpt);
|
||||
}));
|
||||
};
|
||||
|
||||
Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projectionFn, handlerFn) {
|
||||
Facet.prototype.addDataflow = function(subjectFunction) {
|
||||
var facet = this;
|
||||
return this.addEndpoint(new Endpoint(function () {
|
||||
var subjectId = facet.actor.dataflowGraph.currentSubjectId;
|
||||
facet.actor.pushAction(function () {
|
||||
facet.actor.dataflowGraph.withSubject(subjectId, function () {
|
||||
subjectFunction.call(facet.fields);
|
||||
});
|
||||
});
|
||||
return Patch.emptyPatch;
|
||||
}, function (e) {}));
|
||||
};
|
||||
|
||||
Facet.prototype.onEvent = function(priority,
|
||||
isTerminal,
|
||||
eventType,
|
||||
subscriptionFn,
|
||||
projectionFn,
|
||||
handlerFn)
|
||||
{
|
||||
var facet = this;
|
||||
switch (eventType) {
|
||||
|
||||
case 'message':
|
||||
return this.addEndpoint(new Endpoint(subscriptionFn, function(e) {
|
||||
if (e.type === 'message') {
|
||||
var proj = projectionFn.call(facet.actor.state);
|
||||
var proj = projectionFn.call(facet.fields);
|
||||
var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel);
|
||||
var match = Trie.matchPattern(e.message, spec);
|
||||
// console.log(match);
|
||||
if (match) {
|
||||
if (isTerminal) { facet.terminate(); }
|
||||
facet.actor.pushAction(function () { Util.kwApply(handlerFn, facet.actor.state, match); });
|
||||
facet.actor.pushAction(function () { Util.kwApply(handlerFn, facet.fields, match); },
|
||||
priority);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -147,7 +220,7 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec
|
|||
case 'retracted':
|
||||
return this.addEndpoint(new Endpoint(subscriptionFn, function(e) {
|
||||
if (e.type === 'stateChange') {
|
||||
var proj = projectionFn.call(facet.actor.state);
|
||||
var proj = projectionFn.call(facet.fields);
|
||||
var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel);
|
||||
var objects = Trie.projectObjects(eventType === 'asserted'
|
||||
? e.patch.added
|
||||
|
@ -163,8 +236,8 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec
|
|||
facet.terminate();
|
||||
}
|
||||
facet.actor.pushAction(function () {
|
||||
Util.kwApply(handlerFn, facet.actor.state, o);
|
||||
});
|
||||
Util.kwApply(handlerFn, facet.fields, o);
|
||||
}, priority);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -172,17 +245,17 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec
|
|||
}));
|
||||
|
||||
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(); }
|
||||
facet.actor.pushAction(function () {
|
||||
handlerFn.call(facet.actor.state);
|
||||
});
|
||||
}
|
||||
this.currentValue = newValue;
|
||||
});
|
||||
var endpoint = new Endpoint(function() {
|
||||
var newValue = subscriptionFn.call(facet.fields);
|
||||
if (newValue && !this.currentValue) {
|
||||
if (isTerminal) { facet.terminate(); }
|
||||
facet.actor.pushAction(function () {
|
||||
handlerFn.call(facet.fields);
|
||||
}, priority);
|
||||
}
|
||||
this.currentValue = newValue;
|
||||
return Patch.emptyPatch;
|
||||
}, function(e) {});
|
||||
endpoint.currentValue = false;
|
||||
return this.addEndpoint(endpoint);
|
||||
|
||||
|
@ -206,11 +279,15 @@ Facet.prototype.interestWas = function(assertedOrRetracted, pat) {
|
|||
};
|
||||
|
||||
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);
|
||||
var facet = this;
|
||||
var patch = facet.actor.dataflowGraph.withSubject([facet, endpoint], function () {
|
||||
return endpoint.subscriptionFn.call(facet.fields);
|
||||
});
|
||||
var r = facet.actor.mux.addStream(patch);
|
||||
endpoint.eid = r.pid;
|
||||
facet.endpoints = facet.endpoints.set(endpoint.eid, endpoint);
|
||||
Dataspace.stateChange(r.deltaAggregate);
|
||||
return this; // for chaining
|
||||
return facet; // for chaining
|
||||
};
|
||||
|
||||
Facet.prototype.addInitBlock = function(thunk) {
|
||||
|
@ -223,28 +300,17 @@ Facet.prototype.addDoneBlock = function(thunk) {
|
|||
return this;
|
||||
};
|
||||
|
||||
Facet.prototype.refresh = function() {
|
||||
var facet = this;
|
||||
var aggregate = Patch.emptyPatch;
|
||||
this.endpoints.forEach(function(endpoint, eid) {
|
||||
var patch = Patch.retract(__).andThen(endpoint.subscriptionFn.call(facet.actor.state));
|
||||
var r = facet.actor.mux.updateStream(eid, patch);
|
||||
aggregate = aggregate.andThen(r.deltaAggregate);
|
||||
});
|
||||
Dataspace.stateChange(aggregate);
|
||||
};
|
||||
|
||||
Facet.prototype.completeBuild = function() {
|
||||
var facet = this;
|
||||
this.actor.addFacet(this);
|
||||
if (this.parent) {
|
||||
this.parent.children = this.parent.children.add(this);
|
||||
}
|
||||
withCurrentFacet(facet, function () {
|
||||
facet.initBlocks.forEach(function(b) { b.call(facet.actor.state); });
|
||||
withCurrentFacet(this.actor, facet, function () {
|
||||
facet.initBlocks.forEach(function(b) { b.call(facet.fields); });
|
||||
});
|
||||
var initialEvent = _Dataspace.stateChange(new Patch.Patch(facet.actor.knowledge, Trie.emptyTrie));
|
||||
withCurrentFacet(facet, function () { facet.handleEvent(initialEvent); });
|
||||
withCurrentFacet(this.actor, facet, function () { facet.handleEvent(initialEvent); });
|
||||
};
|
||||
|
||||
Facet.prototype.terminate = function() {
|
||||
|
@ -267,13 +333,13 @@ Facet.prototype.terminate = function() {
|
|||
|
||||
this.actor.removeFacet(this);
|
||||
|
||||
withCurrentFacet(facet, function () {
|
||||
facet.doneBlocks.forEach(function(b) { b.call(facet.actor.state); });
|
||||
});
|
||||
|
||||
this.children.forEach(function (child) {
|
||||
child.terminate();
|
||||
});
|
||||
|
||||
withCurrentFacet(this.actor, facet, function () {
|
||||
facet.doneBlocks.forEach(function(b) { b.call(facet.fields); });
|
||||
});
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -281,9 +347,37 @@ Facet.prototype.terminate = function() {
|
|||
function Endpoint(subscriptionFn, handlerFn) {
|
||||
this.subscriptionFn = subscriptionFn;
|
||||
this.handlerFn = handlerFn;
|
||||
this.eid = 'uninitialized_eid'; // initialized later
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
function referenceField(obj, prop) {
|
||||
if (!(prop in obj)) {
|
||||
Actor.current.dataflowGraph.recordObservation(Immutable.List.of(obj, prop));
|
||||
}
|
||||
return obj[prop];
|
||||
}
|
||||
|
||||
function declareField(obj, prop, init) {
|
||||
if (prop in obj) {
|
||||
obj[prop] = init;
|
||||
} else {
|
||||
Actor.current.dataflowGraph.defineObservableProperty(obj, prop, init, {
|
||||
objectId: Immutable.List.of(obj, prop)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteField(obj, prop) {
|
||||
Actor.current.dataflowGraph.recordDamage(Immutable.List.of(obj, prop));
|
||||
return delete obj[prop];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
module.exports.spawnActor = spawnActor;
|
||||
module.exports.createFacet = createFacet;
|
||||
module.exports.referenceField = referenceField;
|
||||
module.exports.declareField = declareField;
|
||||
module.exports.deleteField = deleteField;
|
||||
|
|
|
@ -9,7 +9,6 @@ function Graph() {
|
|||
this.edgesReverse = Immutable.Map();
|
||||
this.damagedNodes = Immutable.Set();
|
||||
this.currentSubjectId = null;
|
||||
this.enforceSubjectPresence = true;
|
||||
}
|
||||
|
||||
Graph.prototype.withSubject = function (subjectId, f) {
|
||||
|
@ -30,8 +29,6 @@ Graph.prototype.recordObservation = function (objectId) {
|
|||
if (this.currentSubjectId) {
|
||||
this.edgesForward = MapSet.add(this.edgesForward, objectId, this.currentSubjectId);
|
||||
this.edgesReverse = MapSet.add(this.edgesReverse, this.currentSubjectId, objectId);
|
||||
} else if (this.enforceSubjectPresence) {
|
||||
throw new Error('Attempt to observe ' + objectId + ' with no currentSubjectId');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -80,7 +77,7 @@ Graph.prototype.repairDamage = function (repairNode) {
|
|||
Graph.prototype.defineObservableProperty = function (obj, prop, value, maybeOptions) {
|
||||
var graph = this;
|
||||
var options = typeof maybeOptions === 'undefined' ? {} : maybeOptions;
|
||||
var objectId = '__' + (options.baseId || prop);
|
||||
var objectId = options.objectId || '__' + prop;
|
||||
Object.defineProperty(obj, prop, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
|
|
|
@ -33,6 +33,7 @@ module.exports.Reflect = require("./reflect.js");
|
|||
module.exports.WakeDetector = require("./wake-detector-driver.js");
|
||||
module.exports.Codec = require("./codec.js");
|
||||
module.exports.Broker = require("./broker.js");
|
||||
module.exports.Dataflow = require("./dataflow.js");
|
||||
|
||||
module.exports.Patch = require("./patch.js");
|
||||
copyKeys(['emptyPatch',
|
||||
|
|
|
@ -54,6 +54,19 @@ function Structure(meta, fields) {
|
|||
}
|
||||
}
|
||||
|
||||
Structure.prototype.clone = function () {
|
||||
return new Structure(this.meta, this.fields);
|
||||
};
|
||||
|
||||
Structure.prototype.get = function (index) {
|
||||
return this[index];
|
||||
};
|
||||
|
||||
Structure.prototype.set = function (index, value) {
|
||||
var s = this.clone();
|
||||
s[index] = s.fields[index] = value;
|
||||
};
|
||||
|
||||
function reviveStructs(j) {
|
||||
if (Array.isArray(j)) {
|
||||
return j.map(reviveStructs);
|
||||
|
|
|
@ -7,7 +7,7 @@ var Dataflow = require('../src/dataflow.js');
|
|||
|
||||
function Cell(graph, initialValue, name) {
|
||||
this.objectId = graph.defineObservableProperty(this, 'value', initialValue, {
|
||||
baseId: name,
|
||||
objectId: name,
|
||||
noopGuard: function (a, b) {
|
||||
return a === b;
|
||||
}
|
||||
|
@ -50,7 +50,6 @@ describe('dataflow edges, damage and subjects', function () {
|
|||
describe('DerivedCell', function () {
|
||||
describe('simple case', function () {
|
||||
var g = new Dataflow.Graph();
|
||||
g.enforceSubjectPresence = false;
|
||||
var c = DerivedCell(g, 'c', function () { return 123; });
|
||||
var d = DerivedCell(g, 'd', function () { return c.value * 2; });
|
||||
it('should be properly initialized', function () {
|
||||
|
@ -79,7 +78,6 @@ describe('DerivedCell', function () {
|
|||
|
||||
describe('a more complex case', function () {
|
||||
var g = new Dataflow.Graph();
|
||||
g.enforceSubjectPresence = false;
|
||||
|
||||
function add(a, b) { return a + b; }
|
||||
var xs = new Cell(g, Immutable.List.of(1, 2, 3, 4), 'xs');
|
||||
|
@ -132,7 +130,6 @@ describe('DerivedCell', function () {
|
|||
|
||||
describe('scopes', function () {
|
||||
var g = new Dataflow.Graph();
|
||||
g.enforceSubjectPresence = false;
|
||||
|
||||
function buildScopes() {
|
||||
var rootScope = {};
|
||||
|
@ -147,6 +144,9 @@ describe('scopes', function () {
|
|||
expect(ss.root.p).to.be(123);
|
||||
expect(ss.mid.p).to.be(123);
|
||||
expect(ss.outer.p).to.be(123);
|
||||
expect('p' in ss.root).to.be(true);
|
||||
expect('p' in ss.mid).to.be(true);
|
||||
expect('p' in ss.outer).to.be(true);
|
||||
});
|
||||
|
||||
it('should make changes at root visible at leaves', function () {
|
||||
|
@ -173,6 +173,9 @@ describe('scopes', function () {
|
|||
expect(ss.outer.p).to.be(123);
|
||||
expect(ss.mid.p).to.be(undefined);
|
||||
expect(ss.root.p).to.be(undefined);
|
||||
expect('p' in ss.root).to.be(false);
|
||||
expect('p' in ss.mid).to.be(false);
|
||||
expect('p' in ss.outer).to.be(true);
|
||||
});
|
||||
|
||||
it('should hide middle definitions from roots but show to leaves', function () {
|
||||
|
@ -181,5 +184,8 @@ describe('scopes', function () {
|
|||
expect(ss.outer.p).to.be(123);
|
||||
expect(ss.mid.p).to.be(123);
|
||||
expect(ss.root.p).to.be(undefined);
|
||||
expect('p' in ss.root).to.be(false);
|
||||
expect('p' in ss.mid).to.be(true);
|
||||
expect('p' in ss.outer).to.be(true);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue