Update
This commit is contained in:
parent
53b243ad8a
commit
4ea5b79f3f
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -65,26 +65,26 @@ var G = new Syndicate.Ground(function () {
|
||||||
(function () { var currentLocation = null; var selectedMarker = null;
|
(function () { var currentLocation = null; var selectedMarker = null;
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(brokerConnection(wsurl), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(brokerConnection(wsurl), 0); }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return (currentLocation) ? Syndicate.Patch.assert(toBroker(wsurl,currentLocation), 0) : Syndicate.Patch.emptyPatch; }))
|
.addAssertion((function() { var _ = Syndicate.__; return (currentLocation) ? Syndicate.Patch.assert(toBroker(wsurl, currentLocation), 0) : Syndicate.Patch.emptyPatch; }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#my_email','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#my_email','change',_), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#my_email', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#my_email', 'change', _), metalevel: 0 }; }), (function() {
|
||||||
var v = email_element.value.trim();
|
var v = email_element.value.trim();
|
||||||
if (currentLocation) currentLocation[1] = v;
|
if (currentLocation) currentLocation[1] = v;
|
||||||
localStorage.my_email = v;
|
localStorage.my_email = v;
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#group','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#group','change',_), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#group', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#group', 'change', _), metalevel: 0 }; }), (function() {
|
||||||
localStorage.group = group_element.value.trim();
|
localStorage.group = group_element.value.trim();
|
||||||
wsurl = wsurl_base + group_element.value.trim();
|
wsurl = wsurl_base + group_element.value.trim();
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#findMarker','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#findMarker','click',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#findMarker', 'click', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#findMarker', 'click', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
Syndicate.Dataspace.send(findMarker(document.getElementById('markerList').value));
|
Syndicate.Dataspace.send(findMarker(document.getElementById('markerList').value));
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#markerList','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#markerList','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#markerList', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#markerList', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
Syndicate.Dataspace.send(findMarker(document.getElementById('markerList').value));
|
Syndicate.Dataspace.send(findMarker(document.getElementById('markerList').value));
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub((locationRecord(_,_,_,_,_)), 0); }), (function() { var _ = Syndicate.__; return { assertion: ((Syndicate._$("loc",locationRecord(_,_,_,_,_)))), metalevel: 0 }; }), (function(loc) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub((locationRecord(_, _, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: ((Syndicate._$("loc",locationRecord(_, _, _, _, _)))), metalevel: 0 }; }), (function(loc) {
|
||||||
currentLocation = loc;
|
currentLocation = loc;
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl,locationRecord(_,_,_,_,_)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl,locationRecord((Syndicate._$("id")),(Syndicate._$("email")),_,_,_)), metalevel: 0 }; }), (function(id, email) { var ui = new Syndicate.UI.Anchor(); var marker = new google.maps.Marker({
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl, locationRecord(_, _, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl, locationRecord((Syndicate._$("id")), (Syndicate._$("email")), _, _, _)), metalevel: 0 }; }), (function(id, email) { var ui = new Syndicate.UI.Anchor(); var marker = new google.maps.Marker({
|
||||||
map: map,
|
map: map,
|
||||||
clickable: true,
|
clickable: true,
|
||||||
icon: 'https://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=32&d=retro'
|
icon: 'https://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=32&d=retro'
|
||||||
|
@ -105,19 +105,23 @@ Syndicate.Actor.createFacet()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var _cachedAssertion1467054033339_0 = (function() { var _ = Syndicate.__; return fromBroker(wsurl,locationRecord(id,email,_,_,_)); })();
|
var _cachedAssertion1468253137502_0 = (function() { var _ = Syndicate.__; return fromBroker(wsurl, locationRecord(id, email, _, _, _)); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addInitBlock((function() {
|
.addInitBlock((function() {
|
||||||
marker.addListener('click', Syndicate.Dataspace.wrap(function () {
|
marker.addListener('click', Syndicate.Dataspace.wrap(function () {
|
||||||
selectMarker();
|
selectMarker();
|
||||||
}));
|
}));
|
||||||
}))
|
}))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(ui.html('#markerList',Mustache.render(document.getElementById('markerList-option').innerHTML,{id:id,email:email})), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(ui.html('#markerList',
|
||||||
|
Mustache.render(document.getElementById('markerList-option').innerHTML, {
|
||||||
|
id: id,
|
||||||
|
email: email
|
||||||
|
})), 0); }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(findMarker(id), 0); }), (function() { var _ = Syndicate.__; return { assertion: findMarker(id), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(findMarker(id), 0); }), (function() { var _ = Syndicate.__; return { assertion: findMarker(id), metalevel: 0 }; }), (function() {
|
||||||
selectMarker();
|
selectMarker();
|
||||||
if (latestPosition) map.panTo(latestPosition);
|
if (latestPosition) map.panTo(latestPosition);
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl,locationRecord(id,email,_,_,_)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl,locationRecord(id,email,(Syndicate._$("timestamp")),(Syndicate._$("lat")),(Syndicate._$("lng")))), metalevel: 0 }; }), (function(timestamp, lat, lng) {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl, locationRecord(id, email, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl, locationRecord(id, email, (Syndicate._$("timestamp")), (Syndicate._$("lat")), (Syndicate._$("lng")))), metalevel: 0 }; }), (function(timestamp, lat, lng) {
|
||||||
latestTimestamp = new Date(timestamp);
|
latestTimestamp = new Date(timestamp);
|
||||||
latestPosition = {lat: lat, lng: lng};
|
latestPosition = {lat: lat, lng: lng};
|
||||||
marker.setPosition(latestPosition);
|
marker.setPosition(latestPosition);
|
||||||
|
@ -128,6 +132,6 @@ Syndicate.Actor.createFacet()
|
||||||
marker.setMap(null);
|
marker.setMap(null);
|
||||||
if (selectedMarker === marker) selectedMarker = null;
|
if (selectedMarker === marker) selectedMarker = null;
|
||||||
}))
|
}))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054033339_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054033339_0, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253137502_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253137502_0, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
|
||||||
});
|
});
|
||||||
}).startStepping();
|
}).startStepping();
|
||||||
|
|
|
@ -27,10 +27,10 @@ function todoListItemModel(initialId, initialTitle, initialCompleted) {
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(todo(this.id,this.title,this.completed), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(todo(this.id, this.title, this.completed), 0); }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setCompleted(this.id,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: setCompleted(this.id,(Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.completed = v; }))
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setCompleted(this.id, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: setCompleted(this.id, (Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.completed = v; }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setAllCompleted(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: setAllCompleted((Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.completed = v; }))
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setAllCompleted(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: setAllCompleted((Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.completed = v; }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setTitle(this.id,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: setTitle(this.id,(Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.title = v; }))
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setTitle(this.id, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: setTitle(this.id, (Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.title = v; }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(clearCompletedTodos(), 0); }), (function() { var _ = Syndicate.__; return { assertion: clearCompletedTodos(), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(clearCompletedTodos(), 0); }), (function() { var _ = Syndicate.__; return { assertion: clearCompletedTodos(), metalevel: 0 }; }), (function() {
|
||||||
if (this.completed) Syndicate.Dataspace.send(deleteTodo(this.id));
|
if (this.completed) Syndicate.Dataspace.send(deleteTodo(this.id));
|
||||||
}))
|
}))
|
||||||
|
@ -53,38 +53,48 @@ function todoListItemView(id) {
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
(function () {
|
(function () {
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id,(Syndicate._$("title")),(Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id, (Syndicate._$("title")), (Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
|
||||||
var _cachedAssertion1467054034537_0 = (function() { var _ = Syndicate.__; return todo(id,title,completed); })();
|
var _cachedAssertion1468253138670_0 = (function() { var _ = Syndicate.__; return todo(id, title, completed); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(show(completed), 0); }), (function() { var _ = Syndicate.__; return { assertion: show(completed), metalevel: 0 }; }), (function() {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(show(completed), 0); }), (function() { var _ = Syndicate.__; return { assertion: show(completed), metalevel: 0 }; }), (function() {
|
||||||
var _cachedAssertion1467054034537_1 = (function() { var _ = Syndicate.__; return show(completed); })();
|
var _cachedAssertion1468253138670_1 = (function() { var _ = Syndicate.__; return show(completed); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(this.ui.html('.todo-list',Mustache.render(getTemplate(this.editing?'todo-list-item-edit-template':'todo-list-item-view-template'),{id:id,title:title,completed_class:completed?"completed":"",checked:completed?"checked":"",}),id), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(this.ui.html('.todo-list',
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_1, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_1, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
Mustache.render(getTemplate(this.editing
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_0, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
? 'todo-list-item-edit-template'
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.toggle','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.toggle','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
: 'todo-list-item-view-template'),
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
completed_class: completed ? "completed" : "",
|
||||||
|
checked: completed ? "checked" : "",
|
||||||
|
}),
|
||||||
|
id), 0); }))
|
||||||
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_1, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_1, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_0, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.toggle', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.toggle', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
Syndicate.Dataspace.send(setCompleted(id, e.target.checked));
|
Syndicate.Dataspace.send(setCompleted(id, e.target.checked));
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.destroy','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.destroy','click',_), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.destroy', 'click', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.destroy', 'click', _), metalevel: 0 }; }), (function() {
|
||||||
Syndicate.Dataspace.send(deleteTodo(id));
|
Syndicate.Dataspace.send(deleteTodo(id));
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('label','dblclick',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('label','dblclick',_), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('label', 'dblclick', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('label', 'dblclick', _), metalevel: 0 }; }), (function() {
|
||||||
this.editing = true;
|
this.editing = true;
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit','keyup',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit','keyup',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit', 'keyup', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit', 'keyup', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
if (e.keyCode === ESCAPE_KEY_CODE || e.keyCode === ENTER_KEY_CODE) {
|
if (e.keyCode === ESCAPE_KEY_CODE || e.keyCode === ENTER_KEY_CODE) {
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit','blur',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit','blur',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit', 'blur', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit', 'blur', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
var newTitle = e.target.value.trim();
|
var newTitle = e.target.value.trim();
|
||||||
Syndicate.Dataspace.send((newTitle ? setTitle(id, newTitle) : deleteTodo(id)));
|
Syndicate.Dataspace.send((newTitle ? setTitle(id, newTitle) : deleteTodo(id)));
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
}))
|
}))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id,_,_), metalevel: 0 }; }), (function() {})).completeBuild(); })();
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id, _, _), metalevel: 0 }; }), (function() {})).completeBuild(); })();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +106,7 @@ var G = new Syndicate.Ground(function () {
|
||||||
Syndicate.Actor.spawnActor(new Object(), function() {
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
(function () {
|
(function () {
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.new-todo','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.new-todo','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.new-todo', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.new-todo', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
var newTitle = e.target.value.trim();
|
var newTitle = e.target.value.trim();
|
||||||
if (newTitle) Syndicate.Dataspace.send(createTodo(newTitle));
|
if (newTitle) Syndicate.Dataspace.send(createTodo(newTitle));
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
|
@ -109,35 +119,35 @@ Syndicate.Actor.createFacet()
|
||||||
(function () {
|
(function () {
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(activeTodoCount(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: activeTodoCount((Syndicate._$("count"))), metalevel: 0 }; }), (function(count) {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(activeTodoCount(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: activeTodoCount((Syndicate._$("count"))), metalevel: 0 }; }), (function(count) {
|
||||||
var _cachedAssertion1467054034537_2 = (function() { var _ = Syndicate.__; return activeTodoCount(count); })();
|
var _cachedAssertion1468253138670_2 = (function() { var _ = Syndicate.__; return activeTodoCount(count); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(this.ui.context('count').html('.todo-count strong',''+count), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(this.ui.context('count').html('.todo-count strong', '' + count), 0); }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return (count !== 1) ? Syndicate.Patch.assert(this.ui.context('plural').html('.todo-count span.s','s'), 0) : Syndicate.Patch.emptyPatch; }))
|
.addAssertion((function() { var _ = Syndicate.__; return (count !== 1) ? Syndicate.Patch.assert(, 0) : Syndicate.Patch.emptyPatch; }))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_2, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_2, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_2, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_2, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(totalTodoCount(0), 0); }), (function() { var _ = Syndicate.__; return { assertion: totalTodoCount(0), metalevel: 0 }; }), (function() {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(totalTodoCount(0), 0); }), (function() { var _ = Syndicate.__; return { assertion: totalTodoCount(0), metalevel: 0 }; }), (function() {
|
||||||
var _cachedAssertion1467054034537_3 = (function() { var _ = Syndicate.__; return totalTodoCount(0); })();
|
var _cachedAssertion1468253138670_3 = (function() { var _ = Syndicate.__; return totalTodoCount(0); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('section.main','class','hidden'), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('section.main', 'class', 'hidden'), 0); }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('footer.footer','class','hidden'), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('footer.footer', 'class', 'hidden'), 0); }))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_3, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_3, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_3, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_3, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(completedTodoCount(0), 0); }), (function() { var _ = Syndicate.__; return { assertion: completedTodoCount(0), metalevel: 0 }; }), (function() {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(completedTodoCount(0), 0); }), (function() { var _ = Syndicate.__; return { assertion: completedTodoCount(0), metalevel: 0 }; }), (function() {
|
||||||
var _cachedAssertion1467054034537_4 = (function() { var _ = Syndicate.__; return completedTodoCount(0); })();
|
var _cachedAssertion1468253138670_4 = (function() { var _ = Syndicate.__; return completedTodoCount(0); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('button.clear-completed','class','hidden'), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('button.clear-completed', 'class', 'hidden'), 0); }))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_4, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_4, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_4, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_4, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('button.clear-completed','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('button.clear-completed','click',_), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('button.clear-completed', 'click', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('button.clear-completed', 'click', _), metalevel: 0 }; }), (function() {
|
||||||
Syndicate.Dataspace.send(clearCompletedTodos());
|
Syndicate.Dataspace.send(clearCompletedTodos());
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(allCompleted(), 0); }), (function() { var _ = Syndicate.__; return { assertion: allCompleted(), metalevel: 0 }; }), (function() {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(allCompleted(), 0); }), (function() { var _ = Syndicate.__; return { assertion: allCompleted(), metalevel: 0 }; }), (function() {
|
||||||
var _cachedAssertion1467054034537_5 = (function() { var _ = Syndicate.__; return allCompleted(); })();
|
var _cachedAssertion1468253138670_5 = (function() { var _ = Syndicate.__; return allCompleted(); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addInitBlock((function() { Syndicate.Dataspace.send(Syndicate.UI.setProperty('.toggle-all', 'checked', true)); }))
|
.addInitBlock((function() { Syndicate.Dataspace.send(Syndicate.UI.setProperty('.toggle-all', 'checked', true)); }))
|
||||||
.addDoneBlock((function() { Syndicate.Dataspace.send(Syndicate.UI.setProperty('.toggle-all', 'checked', false)); }))
|
.addDoneBlock((function() { Syndicate.Dataspace.send(Syndicate.UI.setProperty('.toggle-all', 'checked', false)); }))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_5, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_5, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_5, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_5, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.toggle-all','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.toggle-all','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.toggle-all', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.toggle-all', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
|
||||||
Syndicate.Dataspace.send(setAllCompleted(e.target.checked));
|
Syndicate.Dataspace.send(setAllCompleted(e.target.checked));
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,_), metalevel: 0 }; }), (function(id) {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, _), metalevel: 0 }; }), (function(id) {
|
||||||
todoListItemView(id);
|
todoListItemView(id);
|
||||||
})).completeBuild(); })();
|
})).completeBuild(); })();
|
||||||
});
|
});
|
||||||
|
@ -147,11 +157,11 @@ Syndicate.Actor.createFacet()
|
||||||
var activeCount = 0;
|
var activeCount = 0;
|
||||||
(function () {
|
(function () {
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,(Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount++; else activeCount++; }))
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, (Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount++; else activeCount++; }))
|
||||||
.onEvent(false, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,(Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount--; else activeCount--; }))
|
.onEvent(false, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, (Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount--; else activeCount--; }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(activeTodoCount(activeCount), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(activeTodoCount(activeCount), 0); }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(completedTodoCount(completedCount), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(completedTodoCount(completedCount), 0); }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(totalTodoCount(activeCount+completedCount), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(totalTodoCount(activeCount + completedCount), 0); }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return (completedCount > 0 && activeCount === 0) ? Syndicate.Patch.assert(allCompleted(), 0) : Syndicate.Patch.emptyPatch; })).completeBuild(); })();
|
.addAssertion((function() { var _ = Syndicate.__; return (completedCount > 0 && activeCount === 0) ? Syndicate.Patch.assert(allCompleted(), 0) : Syndicate.Patch.emptyPatch; })).completeBuild(); })();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,26 +169,27 @@ Syndicate.Actor.createFacet()
|
||||||
(function () {
|
(function () {
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash((Syndicate._$("hash"))), metalevel: 0 }; }), (function(hash) {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash((Syndicate._$("hash"))), metalevel: 0 }; }), (function(hash) {
|
||||||
var _cachedAssertion1467054034537_6 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash(hash); })();
|
var _cachedAssertion1468253138670_6 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash(hash); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('ul.filters > li > a[href="#'+hash+'"]','class','selected'), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('ul.filters > li > a[href="#'+hash+'"]',
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_6, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_6, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
'class', 'selected'), 0); }))
|
||||||
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_6, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_6, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/'), metalevel: 0 }; }), (function() {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/'), metalevel: 0 }; }), (function() {
|
||||||
var _cachedAssertion1467054034537_7 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/'); })();
|
var _cachedAssertion1468253138670_7 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/'); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(true), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(true), 0); }))
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(false), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(false), 0); }))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_7, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_7, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_7, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_7, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/active'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/active'), metalevel: 0 }; }), (function() {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/active'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/active'), metalevel: 0 }; }), (function() {
|
||||||
var _cachedAssertion1467054034537_8 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/active'); })();
|
var _cachedAssertion1468253138670_8 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/active'); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(false), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(false), 0); }))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_8, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_8, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_8, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_8, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/completed'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/completed'), metalevel: 0 }; }), (function() {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/completed'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/completed'), metalevel: 0 }; }), (function() {
|
||||||
var _cachedAssertion1467054034537_9 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/completed'); })();
|
var _cachedAssertion1468253138670_9 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/completed'); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(true), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(true), 0); }))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_9, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_9, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_9, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_9, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
|
||||||
});
|
});
|
||||||
|
|
||||||
Syndicate.Actor.spawnActor(new Object(), function() {
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
|
@ -206,21 +217,21 @@ Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(createTodo(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: createTodo((Syndicate._$("title"))), metalevel: 0 }; }), (function(title) {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(createTodo(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: createTodo((Syndicate._$("title"))), metalevel: 0 }; }), (function(title) {
|
||||||
todoListItemModel(db.nextId++, title, false);
|
todoListItemModel(db.nextId++, title, false);
|
||||||
}))
|
}))
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,_), metalevel: 0 }; }), (function(id) {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, _), metalevel: 0 }; }), (function(id) {
|
||||||
var _cachedAssertion1467054034537_10 = (function() { var _ = Syndicate.__; return todo(id,_,_); })();
|
var _cachedAssertion1468253138670_10 = (function() { var _ = Syndicate.__; return todo(id, _, _); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id,(Syndicate._$("title")),(Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id, (Syndicate._$("title")), (Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
|
||||||
var _cachedAssertion1467054034537_11 = (function() { var _ = Syndicate.__; return todo(id,title,completed); })();
|
var _cachedAssertion1468253138670_11 = (function() { var _ = Syndicate.__; return todo(id, title, completed); })();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addInitBlock((function() {
|
.addInitBlock((function() {
|
||||||
db.todos[id] = {id: id, title: title, completed: completed};
|
db.todos[id] = {id: id, title: title, completed: completed};
|
||||||
localStorage['todos-syndicate'] = JSON.stringify(db);
|
localStorage['todos-syndicate'] = JSON.stringify(db);
|
||||||
}))
|
}))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_11, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_11, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_11, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_11, metalevel: 0 }; }), (function() {})).completeBuild(); }))
|
||||||
.addDoneBlock((function() {
|
.addDoneBlock((function() {
|
||||||
delete db.todos[id];
|
delete db.todos[id];
|
||||||
localStorage['todos-syndicate'] = JSON.stringify(db);
|
localStorage['todos-syndicate'] = JSON.stringify(db);
|
||||||
}))
|
}))
|
||||||
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_10, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_10, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_10, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_10, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
|
||||||
});
|
});
|
||||||
}).startStepping();
|
}).startStepping();
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
index.expanded.js
|
||||||
|
index.md
|
|
@ -0,0 +1,12 @@
|
||||||
|
TARGETS=index.expanded.js index.md
|
||||||
|
|
||||||
|
all: $(TARGETS)
|
||||||
|
|
||||||
|
%.expanded.js: %.js
|
||||||
|
../../bin/syndicatec $< > $@ || (rm -f $@; false)
|
||||||
|
|
||||||
|
%.md: %.js
|
||||||
|
sed -E -e 's:^: :g' -e 's:^ /// ?::g' -e 's:^ $$::g' < $< > $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGETS)
|
|
@ -0,0 +1,306 @@
|
||||||
|
"use strict";
|
||||||
|
var Syndicate = require('../../src/main.js');
|
||||||
|
/// -->
|
||||||
|
|
||||||
|
/// This is an extended two-buyer book-purchase protocol, based
|
||||||
|
/// loosely on an example given in:
|
||||||
|
///
|
||||||
|
/// > K. Honda, N. Yoshida, and M. Carbone, “Multiparty asynchronous
|
||||||
|
/// > session types,” POPL 2008.
|
||||||
|
|
||||||
|
/// # The Scenario
|
||||||
|
///
|
||||||
|
/// A book-seller responds to requests for book prices when asked. A
|
||||||
|
/// pair of prospective buyers run through a shopping list. For each
|
||||||
|
/// book, the first buyer offers to split the cost of the book with
|
||||||
|
/// the second. If the second has enough money left, it accepts;
|
||||||
|
/// otherwise, it rejects the offer, and the first buyer tries a
|
||||||
|
/// different split. If the second buyer agrees to a split, it then
|
||||||
|
/// negotiates the purchase of the book with the book-seller.
|
||||||
|
|
||||||
|
/// # The Protocol
|
||||||
|
|
||||||
|
/// ## Role: SELLER
|
||||||
|
///
|
||||||
|
/// - when interest in `bookQuote($title, _)` appears,
|
||||||
|
/// asserts `bookQuote(title, Maybe Float)`, `false` meaning not available,
|
||||||
|
/// and otherwise an asking-price.
|
||||||
|
/// - when interest in `order($title, $offer-price, _, _)` appears,
|
||||||
|
/// asserts `order(title, offer-price, false, false)` for "no sale", otherwise
|
||||||
|
/// `order(title, offer-price, PositiveInteger, String)`, an accepted sale.
|
||||||
|
|
||||||
|
/// ## Role: BUYER
|
||||||
|
///
|
||||||
|
/// - observes `bookQuote(title, $price)` to learn prices.
|
||||||
|
/// - observes `order(title, offer-price, $id, $delivery-date)` to make orders.
|
||||||
|
|
||||||
|
/// ## Role: SPLIT-PROPOSER
|
||||||
|
///
|
||||||
|
/// - observes `splitProposal(title, asking-price, contribution, $accepted)`
|
||||||
|
/// to make a split-proposal and learn whether it was accepted or not.
|
||||||
|
|
||||||
|
/// ## Role: SPLIT-DISPOSER
|
||||||
|
///
|
||||||
|
/// - when interest in `splitProposal($title, $asking-price, $contribution, _)`
|
||||||
|
/// appears, asserts `splitProposal(title, askingPrice, contribution, true)`
|
||||||
|
/// to indicate they are willing to go through with the deal, in which case
|
||||||
|
/// they then perform the role of BUYER for title/asking-price, or asserts
|
||||||
|
/// `splitProposal(title, asking-price, contribution, false)` to indicate they
|
||||||
|
/// are unwilling to go through with the deal.
|
||||||
|
|
||||||
|
/// # A Sample Run
|
||||||
|
///
|
||||||
|
/// Run the program with `../../bin/syndicatec index.js | node` from a
|
||||||
|
/// checkout of the Syndicate repository.
|
||||||
|
///
|
||||||
|
/// A learns that the price of Catch 22 is 2.22
|
||||||
|
/// A makes an offer to split the price of Catch 22 contributing 1.11
|
||||||
|
/// B is being asked to contribute 1.11 toward Catch 22 at price 2.22
|
||||||
|
/// B accepts the offer, leaving them with 3.8899999999999997 remaining funds
|
||||||
|
/// A learns that the split-proposal for Catch 22 was accepted
|
||||||
|
/// A learns that Encyclopaedia Brittannica is out-of-stock.
|
||||||
|
/// The order for Catch 22 has id 10001483, and will be delivered on March 9th
|
||||||
|
/// A learns that the price of Candide is 34.95
|
||||||
|
/// A makes an offer to split the price of Candide contributing 17.475
|
||||||
|
/// B is being asked to contribute 17.475 toward Candide at price 34.95
|
||||||
|
/// B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
/// A learns that the split-proposal for Candide was rejected
|
||||||
|
/// A makes an offer to split the price of Candide contributing 26.212500000000002
|
||||||
|
/// B is being asked to contribute 8.7375 toward Candide at price 34.95
|
||||||
|
/// B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
/// A learns that the split-proposal for Candide was rejected
|
||||||
|
/// A makes an offer to split the price of Candide contributing 30.581250000000004
|
||||||
|
/// B is being asked to contribute 4.368749999999999 toward Candide at price 34.95
|
||||||
|
/// B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
/// A learns that the split-proposal for Candide was rejected
|
||||||
|
/// A makes an offer to split the price of Candide contributing 32.765625
|
||||||
|
/// B is being asked to contribute 2.184375000000003 toward Candide at price 34.95
|
||||||
|
/// B accepts the offer, leaving them with 1.7056249999999968 remaining funds
|
||||||
|
/// A learns that the split-proposal for Candide was accepted
|
||||||
|
/// A learns that the price of The Wind in the Willows is 3.95
|
||||||
|
/// A makes an offer to split the price of The Wind in the Willows contributing 1.975
|
||||||
|
/// The order for Candide has id 10001484, and will be delivered on March 9th
|
||||||
|
/// B is being asked to contribute 1.975 toward The Wind in the Willows at price 3.95
|
||||||
|
/// B hasn't enough funds (1.7056249999999968 remaining)
|
||||||
|
/// A learns that the split-proposal for The Wind in the Willows was rejected
|
||||||
|
/// A makes an offer to split the price of The Wind in the Willows contributing 2.9625000000000004
|
||||||
|
/// B is being asked to contribute 0.9874999999999998 toward The Wind in the Willows at price 3.95
|
||||||
|
/// B accepts the offer, leaving them with 0.718124999999997 remaining funds
|
||||||
|
/// A learns that the split-proposal for The Wind in the Willows was accepted
|
||||||
|
/// A has bought everything they wanted!
|
||||||
|
/// The order for The Wind in the Willows has id 10001485, and will be delivered on March 9th
|
||||||
|
|
||||||
|
/// # The Code
|
||||||
|
|
||||||
|
/// ## Type Declarations
|
||||||
|
|
||||||
|
/// First, we declare *assertion types* for our protocol.
|
||||||
|
|
||||||
|
var bookQuote = Syndicate.Struct.makeConstructor("bookQuote", ["title","price"]);
|
||||||
|
var order = Syndicate.Struct.makeConstructor("order", ["title","price","id","deliveryDate"]);
|
||||||
|
|
||||||
|
var splitProposal = Syndicate.Struct.makeConstructor("splitProposal", ["title","price","contribution","accepted"]);
|
||||||
|
|
||||||
|
/// ## Utilities
|
||||||
|
|
||||||
|
/// This routine is under consideration for possible addition to the
|
||||||
|
/// core library.
|
||||||
|
///
|
||||||
|
function whileRelevantAssert(P) {
|
||||||
|
(function () {
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(P, 0); }))
|
||||||
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(P), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(P), metalevel: 0 }; }), (function() {})).completeBuild(); })();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Implementation: SELLER
|
||||||
|
|
||||||
|
function seller() {
|
||||||
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
|
||||||
|
this.books = {
|
||||||
|
"The Wind in the Willows": 3.95,
|
||||||
|
"Catch 22": 2.22,
|
||||||
|
"Candide": 34.95
|
||||||
|
};
|
||||||
|
this.nextOrderId = 10001483;
|
||||||
|
|
||||||
|
/// The seller responds to interest in bookQuotes by asserting a
|
||||||
|
/// responsive record, if one exists.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(bookQuote(_, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(bookQuote((Syndicate._$("title")), _)), metalevel: 0 }; }), (function(title) {
|
||||||
|
var _cachedAssertion1468253139911_0 = (function() { var _ = Syndicate.__; return Syndicate.observe(bookQuote(title, _)); })();
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(bookQuote(title, title in this.books ? this.books[title] : false), 0); }))
|
||||||
|
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253139911_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253139911_0, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
|
||||||
|
|
||||||
|
/// It also responds to order requests.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(order(_, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(order((Syndicate._$("title")), (Syndicate._$("offerPrice")), _, _)), metalevel: 0 }; }), (function(title, offerPrice) {
|
||||||
|
|
||||||
|
/// We cannot sell a book we do not have, and we will not sell for
|
||||||
|
/// less than our asking price.
|
||||||
|
|
||||||
|
var askingPrice = title in this.books ? this.books[title] : false;
|
||||||
|
if ((askingPrice === false) || (offerPrice < askingPrice)) {
|
||||||
|
whileRelevantAssert(order(title, offerPrice, false, false));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/// But if we can sell it, we do so by allocating an order ID and
|
||||||
|
/// replying to the orderer.
|
||||||
|
|
||||||
|
var orderId = this.nextOrderId++;
|
||||||
|
delete this.books[title];
|
||||||
|
|
||||||
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
|
whileRelevantAssert(order(title, offerPrice, orderId, "March 9th"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})).completeBuild(); })();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Implementation: SPLIT-PROPOSER and book-quote-requestor
|
||||||
|
|
||||||
|
function buyerA() {
|
||||||
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
/// Our actor remembers which books remain on its shopping list, and
|
||||||
|
/// tries to buy them one at a time, sharing costs with `buyerB`.
|
||||||
|
|
||||||
|
self.titles = ["Catch 22",
|
||||||
|
"Encyclopaedia Brittannica",
|
||||||
|
"Candide",
|
||||||
|
"The Wind in the Willows"];
|
||||||
|
|
||||||
|
/// JavaScript's callback-oriented blocking means that we express our
|
||||||
|
/// loop in almost a tail-recursive style, using helper functions
|
||||||
|
/// `buyBooks` and `trySplit`.
|
||||||
|
|
||||||
|
buyBooks();
|
||||||
|
|
||||||
|
function buyBooks() {
|
||||||
|
if (self.titles.length === 0) {
|
||||||
|
console.log("A has bought everything they wanted!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var title = self.titles.shift();
|
||||||
|
|
||||||
|
/// First, retrieve a quote for the title, and analyze the result.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(bookQuote(title, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: bookQuote(title, (Syndicate._$("price"))), metalevel: 0 }; }), (function(price) {
|
||||||
|
if (price === false) {
|
||||||
|
console.log("A learns that "+title+" is out-of-stock.");
|
||||||
|
buyBooks();
|
||||||
|
} else {
|
||||||
|
console.log("A learns that the price of "+title+" is "+price);
|
||||||
|
|
||||||
|
/// Next, repeatedly make split offers to a SPLIT-DISPOSER until
|
||||||
|
/// either one is accepted, or the contribution from the
|
||||||
|
/// SPLIT-DISPOSER becomes pointlessly small. We start the process by
|
||||||
|
/// offering to split the price of the book evenly.
|
||||||
|
|
||||||
|
trySplit(title, price, price / 2);
|
||||||
|
}
|
||||||
|
})).completeBuild(); })();
|
||||||
|
}
|
||||||
|
|
||||||
|
function trySplit(title, price, contribution) {
|
||||||
|
console.log("A makes an offer to split the price of "+title+
|
||||||
|
" contributing "+contribution);
|
||||||
|
|
||||||
|
/// If we are about to offer to split the price, but the other buyer
|
||||||
|
/// would contribute less than 10c, then it's not worth bothering; we
|
||||||
|
/// may as well buy it ourselves. Another version of the program could
|
||||||
|
/// perform the BUYER role here.
|
||||||
|
|
||||||
|
if (contribution > (price - 0.10)) {
|
||||||
|
console.log("A gives up on "+title+".");
|
||||||
|
buyBooks();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/// Make our proposal, and wait for a response.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(splitProposal(title, price, contribution, true), 0); }), (function() { var _ = Syndicate.__; return { assertion: splitProposal(title, price, contribution, true), metalevel: 0 }; }), (function() {
|
||||||
|
console.log("A learns that the split-proposal for "+title+" was accepted");
|
||||||
|
buyBooks();
|
||||||
|
}))
|
||||||
|
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(splitProposal(title, price, contribution, false), 0); }), (function() { var _ = Syndicate.__; return { assertion: splitProposal(title, price, contribution, false), metalevel: 0 }; }), (function() {
|
||||||
|
console.log("A learns that the split-proposal for "+title+" was rejected");
|
||||||
|
trySplit(title, price, contribution + ((price - contribution) / 2));
|
||||||
|
})).completeBuild(); })();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Implementation: SPLIT-DISPOSER and BUYER
|
||||||
|
|
||||||
|
function buyerB() {
|
||||||
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
|
|
||||||
|
/// This actor maintains a record of the amount of money it has left
|
||||||
|
/// to spend.
|
||||||
|
|
||||||
|
this.funds = 5.00;
|
||||||
|
|
||||||
|
/// It spends its time waiting for a SPLIT-PROPOSER to offer a
|
||||||
|
/// `splitProposal`.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(splitProposal(_, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(splitProposal((Syndicate._$("title")), (Syndicate._$("price")), (Syndicate._$("theirContribution")), _)), metalevel: 0 }; }), (function(title, price, theirContribution) {
|
||||||
|
var myContribution = price - theirContribution;
|
||||||
|
console.log("B is being asked to contribute "+myContribution+" toward "+title+
|
||||||
|
" at price "+price);
|
||||||
|
|
||||||
|
/// We may not be able to afford contributing this much.
|
||||||
|
|
||||||
|
if (myContribution > this.funds) {
|
||||||
|
console.log("B hasn't enough funds ("+this.funds+" remaining)");
|
||||||
|
whileRelevantAssert(splitProposal(title, price, theirContribution, false));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/// But if we *can* afford it, update our remaining funds and spawn a
|
||||||
|
/// small actor to handle the actual purchase now that we have agreed
|
||||||
|
/// on a split.
|
||||||
|
|
||||||
|
var remainingFunds = this.funds - myContribution;
|
||||||
|
console.log("B accepts the offer, leaving them with "+remainingFunds+" remaining funds");
|
||||||
|
this.funds = remainingFunds;
|
||||||
|
|
||||||
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
|
(function () {
|
||||||
|
Syndicate.Actor.createFacet()
|
||||||
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(splitProposal(title, price, theirContribution, true), 0); }))
|
||||||
|
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(order(title, price, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: order(title, price, (Syndicate._$("id")), (Syndicate._$("date"))), metalevel: 0 }; }), (function(id, date) {
|
||||||
|
console.log("The order for "+title+" has id "+id+
|
||||||
|
", and will be delivered on "+date);
|
||||||
|
})).completeBuild(); })();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})).completeBuild(); })();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Starting Configuration
|
||||||
|
|
||||||
|
new Syndicate.Ground(function () {
|
||||||
|
seller();
|
||||||
|
buyerA();
|
||||||
|
buyerB();
|
||||||
|
}).startStepping();
|
|
@ -0,0 +1,318 @@
|
||||||
|
/// ---
|
||||||
|
/// title: Two-Party Buyer Protocol
|
||||||
|
/// ---
|
||||||
|
|
||||||
|
/// <!--
|
||||||
|
var Syndicate = require('../../src/main.js');
|
||||||
|
/// -->
|
||||||
|
|
||||||
|
/// This is an extended two-buyer book-purchase protocol, based
|
||||||
|
/// loosely on an example given in:
|
||||||
|
///
|
||||||
|
/// > K. Honda, N. Yoshida, and M. Carbone, “Multiparty asynchronous
|
||||||
|
/// > session types,” POPL 2008.
|
||||||
|
|
||||||
|
/// # The Scenario
|
||||||
|
///
|
||||||
|
/// A book-seller responds to requests for book prices when asked. A
|
||||||
|
/// pair of prospective buyers run through a shopping list. For each
|
||||||
|
/// book, the first buyer offers to split the cost of the book with
|
||||||
|
/// the second. If the second has enough money left, it accepts;
|
||||||
|
/// otherwise, it rejects the offer, and the first buyer tries a
|
||||||
|
/// different split. If the second buyer agrees to a split, it then
|
||||||
|
/// negotiates the purchase of the book with the book-seller.
|
||||||
|
|
||||||
|
/// # The Protocol
|
||||||
|
|
||||||
|
/// ## Role: SELLER
|
||||||
|
///
|
||||||
|
/// - when interest in `bookQuote($title, _)` appears,
|
||||||
|
/// asserts `bookQuote(title, Maybe Float)`, `false` meaning not available,
|
||||||
|
/// and otherwise an asking-price.
|
||||||
|
/// - when interest in `order($title, $offer-price, _, _)` appears,
|
||||||
|
/// asserts `order(title, offer-price, false, false)` for "no sale", otherwise
|
||||||
|
/// `order(title, offer-price, PositiveInteger, String)`, an accepted sale.
|
||||||
|
|
||||||
|
/// ## Role: BUYER
|
||||||
|
///
|
||||||
|
/// - observes `bookQuote(title, $price)` to learn prices.
|
||||||
|
/// - observes `order(title, offer-price, $id, $delivery-date)` to make orders.
|
||||||
|
|
||||||
|
/// ## Role: SPLIT-PROPOSER
|
||||||
|
///
|
||||||
|
/// - observes `splitProposal(title, asking-price, contribution, $accepted)`
|
||||||
|
/// to make a split-proposal and learn whether it was accepted or not.
|
||||||
|
|
||||||
|
/// ## Role: SPLIT-DISPOSER
|
||||||
|
///
|
||||||
|
/// - when interest in `splitProposal($title, $asking-price, $contribution, _)`
|
||||||
|
/// appears, asserts `splitProposal(title, askingPrice, contribution, true)`
|
||||||
|
/// to indicate they are willing to go through with the deal, in which case
|
||||||
|
/// they then perform the role of BUYER for title/asking-price, or asserts
|
||||||
|
/// `splitProposal(title, asking-price, contribution, false)` to indicate they
|
||||||
|
/// are unwilling to go through with the deal.
|
||||||
|
|
||||||
|
/// # A Sample Run
|
||||||
|
///
|
||||||
|
/// Run the program with `../../bin/syndicatec index.js | node` from a
|
||||||
|
/// checkout of the Syndicate repository.
|
||||||
|
///
|
||||||
|
/// A learns that the price of Catch 22 is 2.22
|
||||||
|
/// A makes an offer to split the price of Catch 22 contributing 1.11
|
||||||
|
/// B is being asked to contribute 1.11 toward Catch 22 at price 2.22
|
||||||
|
/// B accepts the offer, leaving them with 3.8899999999999997 remaining funds
|
||||||
|
/// A learns that the split-proposal for Catch 22 was accepted
|
||||||
|
/// A learns that Encyclopaedia Brittannica is out-of-stock.
|
||||||
|
/// The order for Catch 22 has id 10001483, and will be delivered on March 9th
|
||||||
|
/// A learns that the price of Candide is 34.95
|
||||||
|
/// A makes an offer to split the price of Candide contributing 17.475
|
||||||
|
/// B is being asked to contribute 17.475 toward Candide at price 34.95
|
||||||
|
/// B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
/// A learns that the split-proposal for Candide was rejected
|
||||||
|
/// A makes an offer to split the price of Candide contributing 26.212500000000002
|
||||||
|
/// B is being asked to contribute 8.7375 toward Candide at price 34.95
|
||||||
|
/// B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
/// A learns that the split-proposal for Candide was rejected
|
||||||
|
/// A makes an offer to split the price of Candide contributing 30.581250000000004
|
||||||
|
/// B is being asked to contribute 4.368749999999999 toward Candide at price 34.95
|
||||||
|
/// B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
/// A learns that the split-proposal for Candide was rejected
|
||||||
|
/// A makes an offer to split the price of Candide contributing 32.765625
|
||||||
|
/// B is being asked to contribute 2.184375000000003 toward Candide at price 34.95
|
||||||
|
/// B accepts the offer, leaving them with 1.7056249999999968 remaining funds
|
||||||
|
/// A learns that the split-proposal for Candide was accepted
|
||||||
|
/// A learns that the price of The Wind in the Willows is 3.95
|
||||||
|
/// A makes an offer to split the price of The Wind in the Willows contributing 1.975
|
||||||
|
/// The order for Candide has id 10001484, and will be delivered on March 9th
|
||||||
|
/// B is being asked to contribute 1.975 toward The Wind in the Willows at price 3.95
|
||||||
|
/// B hasn't enough funds (1.7056249999999968 remaining)
|
||||||
|
/// A learns that the split-proposal for The Wind in the Willows was rejected
|
||||||
|
/// A makes an offer to split the price of The Wind in the Willows contributing 2.9625000000000004
|
||||||
|
/// B is being asked to contribute 0.9874999999999998 toward The Wind in the Willows at price 3.95
|
||||||
|
/// B accepts the offer, leaving them with 0.718124999999997 remaining funds
|
||||||
|
/// A learns that the split-proposal for The Wind in the Willows was accepted
|
||||||
|
/// A has bought everything they wanted!
|
||||||
|
/// The order for The Wind in the Willows has id 10001485, and will be delivered on March 9th
|
||||||
|
|
||||||
|
/// # The Code
|
||||||
|
|
||||||
|
/// ## Type Declarations
|
||||||
|
|
||||||
|
/// First, we declare *assertion types* for our protocol.
|
||||||
|
|
||||||
|
assertion type bookQuote(title, price);
|
||||||
|
assertion type order(title, price, id, deliveryDate);
|
||||||
|
|
||||||
|
assertion type splitProposal(title, price, contribution, accepted);
|
||||||
|
|
||||||
|
/// ## Utilities
|
||||||
|
|
||||||
|
/// This routine is under consideration for possible addition to the
|
||||||
|
/// core library.
|
||||||
|
///
|
||||||
|
function whileRelevantAssert(P) {
|
||||||
|
react {
|
||||||
|
assert P;
|
||||||
|
} until {
|
||||||
|
case retracted Syndicate.observe(P);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Implementation: SELLER
|
||||||
|
|
||||||
|
function seller() {
|
||||||
|
actor {
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
|
||||||
|
this.books = {
|
||||||
|
"The Wind in the Willows": 3.95,
|
||||||
|
"Catch 22": 2.22,
|
||||||
|
"Candide": 34.95
|
||||||
|
};
|
||||||
|
this.nextOrderId = 10001483;
|
||||||
|
|
||||||
|
/// The seller responds to interest in bookQuotes by asserting a
|
||||||
|
/// responsive record, if one exists.
|
||||||
|
|
||||||
|
react {
|
||||||
|
during Syndicate.observe(bookQuote($title, _)) {
|
||||||
|
assert bookQuote(title, title in this.books ? this.books[title] : false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// It also responds to order requests.
|
||||||
|
|
||||||
|
react {
|
||||||
|
on asserted Syndicate.observe(order($title, $offerPrice, _, _)) {
|
||||||
|
|
||||||
|
/// We cannot sell a book we do not have, and we will not sell for
|
||||||
|
/// less than our asking price.
|
||||||
|
|
||||||
|
var askingPrice = title in this.books ? this.books[title] : false;
|
||||||
|
if ((askingPrice === false) || (offerPrice < askingPrice)) {
|
||||||
|
whileRelevantAssert(order(title, offerPrice, false, false));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/// But if we can sell it, we do so by allocating an order ID and
|
||||||
|
/// replying to the orderer.
|
||||||
|
|
||||||
|
var orderId = this.nextOrderId++;
|
||||||
|
delete this.books[title];
|
||||||
|
|
||||||
|
actor {
|
||||||
|
whileRelevantAssert(order(title, offerPrice, orderId, "March 9th"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Implementation: SPLIT-PROPOSER and book-quote-requestor
|
||||||
|
|
||||||
|
function buyerA() {
|
||||||
|
actor {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
/// Our actor remembers which books remain on its shopping list, and
|
||||||
|
/// tries to buy them one at a time, sharing costs with `buyerB`.
|
||||||
|
|
||||||
|
self.titles = ["Catch 22",
|
||||||
|
"Encyclopaedia Brittannica",
|
||||||
|
"Candide",
|
||||||
|
"The Wind in the Willows"];
|
||||||
|
|
||||||
|
/// JavaScript's callback-oriented blocking means that we express our
|
||||||
|
/// loop in almost a tail-recursive style, using helper functions
|
||||||
|
/// `buyBooks` and `trySplit`.
|
||||||
|
|
||||||
|
buyBooks();
|
||||||
|
|
||||||
|
function buyBooks() {
|
||||||
|
if (self.titles.length === 0) {
|
||||||
|
console.log("A has bought everything they wanted!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var title = self.titles.shift();
|
||||||
|
|
||||||
|
/// First, retrieve a quote for the title, and analyze the result.
|
||||||
|
|
||||||
|
react until {
|
||||||
|
case asserted bookQuote(title, $price) {
|
||||||
|
if (price === false) {
|
||||||
|
console.log("A learns that "+title+" is out-of-stock.");
|
||||||
|
buyBooks();
|
||||||
|
} else {
|
||||||
|
console.log("A learns that the price of "+title+" is "+price);
|
||||||
|
|
||||||
|
/// Next, repeatedly make split offers to a SPLIT-DISPOSER until
|
||||||
|
/// either one is accepted, or the contribution from the
|
||||||
|
/// SPLIT-DISPOSER becomes pointlessly small. We start the process by
|
||||||
|
/// offering to split the price of the book evenly.
|
||||||
|
|
||||||
|
trySplit(title, price, price / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function trySplit(title, price, contribution) {
|
||||||
|
console.log("A makes an offer to split the price of "+title+
|
||||||
|
" contributing "+contribution);
|
||||||
|
|
||||||
|
/// If we are about to offer to split the price, but the other buyer
|
||||||
|
/// would contribute less than 10c, then it's not worth bothering; we
|
||||||
|
/// may as well buy it ourselves. Another version of the program could
|
||||||
|
/// perform the BUYER role here.
|
||||||
|
|
||||||
|
if (contribution > (price - 0.10)) {
|
||||||
|
console.log("A gives up on "+title+".");
|
||||||
|
buyBooks();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/// Make our proposal, and wait for a response.
|
||||||
|
|
||||||
|
react until {
|
||||||
|
case asserted splitProposal(title, price, contribution, true) {
|
||||||
|
console.log("A learns that the split-proposal for "+title+" was accepted");
|
||||||
|
buyBooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
case asserted splitProposal(title, price, contribution, false) {
|
||||||
|
console.log("A learns that the split-proposal for "+title+" was rejected");
|
||||||
|
trySplit(title, price, contribution + ((price - contribution) / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Implementation: SPLIT-DISPOSER and BUYER
|
||||||
|
|
||||||
|
function buyerB() {
|
||||||
|
actor {
|
||||||
|
|
||||||
|
/// This actor maintains a record of the amount of money it has left
|
||||||
|
/// to spend.
|
||||||
|
|
||||||
|
this.funds = 5.00;
|
||||||
|
|
||||||
|
/// It spends its time waiting for a SPLIT-PROPOSER to offer a
|
||||||
|
/// `splitProposal`.
|
||||||
|
|
||||||
|
react {
|
||||||
|
on asserted Syndicate.observe(splitProposal($title, $price, $theirContribution, _)) {
|
||||||
|
var myContribution = price - theirContribution;
|
||||||
|
console.log("B is being asked to contribute "+myContribution+" toward "+title+
|
||||||
|
" at price "+price);
|
||||||
|
|
||||||
|
/// We may not be able to afford contributing this much.
|
||||||
|
|
||||||
|
if (myContribution > this.funds) {
|
||||||
|
console.log("B hasn't enough funds ("+this.funds+" remaining)");
|
||||||
|
whileRelevantAssert(splitProposal(title, price, theirContribution, false));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/// But if we *can* afford it, update our remaining funds and spawn a
|
||||||
|
/// small actor to handle the actual purchase now that we have agreed
|
||||||
|
/// on a split.
|
||||||
|
|
||||||
|
var remainingFunds = this.funds - myContribution;
|
||||||
|
console.log("B accepts the offer, leaving them with "+remainingFunds+" remaining funds");
|
||||||
|
this.funds = remainingFunds;
|
||||||
|
|
||||||
|
actor {
|
||||||
|
react {
|
||||||
|
|
||||||
|
/// While waiting for order confirmation, take the opportunity to
|
||||||
|
/// signal to our SPLIT-PROPOSER that we accepted their proposal.
|
||||||
|
|
||||||
|
assert splitProposal(title, price, theirContribution, true);
|
||||||
|
|
||||||
|
/// When order confirmation arrives, this purchase is completed.
|
||||||
|
|
||||||
|
} until {
|
||||||
|
case asserted order(title, price, $id, $date) {
|
||||||
|
console.log("The order for "+title+" has id "+id+
|
||||||
|
", and will be delivered on "+date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Starting Configuration
|
||||||
|
|
||||||
|
ground dataspace {
|
||||||
|
seller();
|
||||||
|
buyerA();
|
||||||
|
buyerB();
|
||||||
|
}
|
|
@ -0,0 +1,318 @@
|
||||||
|
---
|
||||||
|
title: Two-Party Buyer Protocol
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
var Syndicate = require('../../src/main.js');
|
||||||
|
-->
|
||||||
|
|
||||||
|
This is an extended two-buyer book-purchase protocol, based
|
||||||
|
loosely on an example given in:
|
||||||
|
|
||||||
|
> K. Honda, N. Yoshida, and M. Carbone, “Multiparty asynchronous
|
||||||
|
> session types,” POPL 2008.
|
||||||
|
|
||||||
|
# The Scenario
|
||||||
|
|
||||||
|
A book-seller responds to requests for book prices when asked. A
|
||||||
|
pair of prospective buyers run through a shopping list. For each
|
||||||
|
book, the first buyer offers to split the cost of the book with
|
||||||
|
the second. If the second has enough money left, it accepts;
|
||||||
|
otherwise, it rejects the offer, and the first buyer tries a
|
||||||
|
different split. If the second buyer agrees to a split, it then
|
||||||
|
negotiates the purchase of the book with the book-seller.
|
||||||
|
|
||||||
|
# The Protocol
|
||||||
|
|
||||||
|
## Role: SELLER
|
||||||
|
|
||||||
|
- when interest in `bookQuote($title, _)` appears,
|
||||||
|
asserts `bookQuote(title, Maybe Float)`, `false` meaning not available,
|
||||||
|
and otherwise an asking-price.
|
||||||
|
- when interest in `order($title, $offer-price, _, _)` appears,
|
||||||
|
asserts `order(title, offer-price, false, false)` for "no sale", otherwise
|
||||||
|
`order(title, offer-price, PositiveInteger, String)`, an accepted sale.
|
||||||
|
|
||||||
|
## Role: BUYER
|
||||||
|
|
||||||
|
- observes `bookQuote(title, $price)` to learn prices.
|
||||||
|
- observes `order(title, offer-price, $id, $delivery-date)` to make orders.
|
||||||
|
|
||||||
|
## Role: SPLIT-PROPOSER
|
||||||
|
|
||||||
|
- observes `splitProposal(title, asking-price, contribution, $accepted)`
|
||||||
|
to make a split-proposal and learn whether it was accepted or not.
|
||||||
|
|
||||||
|
## Role: SPLIT-DISPOSER
|
||||||
|
|
||||||
|
- when interest in `splitProposal($title, $asking-price, $contribution, _)`
|
||||||
|
appears, asserts `splitProposal(title, askingPrice, contribution, true)`
|
||||||
|
to indicate they are willing to go through with the deal, in which case
|
||||||
|
they then perform the role of BUYER for title/asking-price, or asserts
|
||||||
|
`splitProposal(title, asking-price, contribution, false)` to indicate they
|
||||||
|
are unwilling to go through with the deal.
|
||||||
|
|
||||||
|
# A Sample Run
|
||||||
|
|
||||||
|
Run the program with `../../bin/syndicatec index.js | node` from a
|
||||||
|
checkout of the Syndicate repository.
|
||||||
|
|
||||||
|
A learns that the price of Catch 22 is 2.22
|
||||||
|
A makes an offer to split the price of Catch 22 contributing 1.11
|
||||||
|
B is being asked to contribute 1.11 toward Catch 22 at price 2.22
|
||||||
|
B accepts the offer, leaving them with 3.8899999999999997 remaining funds
|
||||||
|
A learns that the split-proposal for Catch 22 was accepted
|
||||||
|
A learns that Encyclopaedia Brittannica is out-of-stock.
|
||||||
|
The order for Catch 22 has id 10001483, and will be delivered on March 9th
|
||||||
|
A learns that the price of Candide is 34.95
|
||||||
|
A makes an offer to split the price of Candide contributing 17.475
|
||||||
|
B is being asked to contribute 17.475 toward Candide at price 34.95
|
||||||
|
B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
A learns that the split-proposal for Candide was rejected
|
||||||
|
A makes an offer to split the price of Candide contributing 26.212500000000002
|
||||||
|
B is being asked to contribute 8.7375 toward Candide at price 34.95
|
||||||
|
B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
A learns that the split-proposal for Candide was rejected
|
||||||
|
A makes an offer to split the price of Candide contributing 30.581250000000004
|
||||||
|
B is being asked to contribute 4.368749999999999 toward Candide at price 34.95
|
||||||
|
B hasn't enough funds (3.8899999999999997 remaining)
|
||||||
|
A learns that the split-proposal for Candide was rejected
|
||||||
|
A makes an offer to split the price of Candide contributing 32.765625
|
||||||
|
B is being asked to contribute 2.184375000000003 toward Candide at price 34.95
|
||||||
|
B accepts the offer, leaving them with 1.7056249999999968 remaining funds
|
||||||
|
A learns that the split-proposal for Candide was accepted
|
||||||
|
A learns that the price of The Wind in the Willows is 3.95
|
||||||
|
A makes an offer to split the price of The Wind in the Willows contributing 1.975
|
||||||
|
The order for Candide has id 10001484, and will be delivered on March 9th
|
||||||
|
B is being asked to contribute 1.975 toward The Wind in the Willows at price 3.95
|
||||||
|
B hasn't enough funds (1.7056249999999968 remaining)
|
||||||
|
A learns that the split-proposal for The Wind in the Willows was rejected
|
||||||
|
A makes an offer to split the price of The Wind in the Willows contributing 2.9625000000000004
|
||||||
|
B is being asked to contribute 0.9874999999999998 toward The Wind in the Willows at price 3.95
|
||||||
|
B accepts the offer, leaving them with 0.718124999999997 remaining funds
|
||||||
|
A learns that the split-proposal for The Wind in the Willows was accepted
|
||||||
|
A has bought everything they wanted!
|
||||||
|
The order for The Wind in the Willows has id 10001485, and will be delivered on March 9th
|
||||||
|
|
||||||
|
# The Code
|
||||||
|
|
||||||
|
## Type Declarations
|
||||||
|
|
||||||
|
First, we declare *assertion types* for our protocol.
|
||||||
|
|
||||||
|
assertion type bookQuote(title, price);
|
||||||
|
assertion type order(title, price, id, deliveryDate);
|
||||||
|
|
||||||
|
assertion type splitProposal(title, price, contribution, accepted);
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
This routine is under consideration for possible addition to the
|
||||||
|
core library.
|
||||||
|
|
||||||
|
function whileRelevantAssert(P) {
|
||||||
|
react {
|
||||||
|
assert P;
|
||||||
|
} until {
|
||||||
|
case retracted Syndicate.observe(P);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Implementation: SELLER
|
||||||
|
|
||||||
|
function seller() {
|
||||||
|
actor {
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
this.books = {
|
||||||
|
"The Wind in the Willows": 3.95,
|
||||||
|
"Catch 22": 2.22,
|
||||||
|
"Candide": 34.95
|
||||||
|
};
|
||||||
|
this.nextOrderId = 10001483;
|
||||||
|
|
||||||
|
The seller responds to interest in bookQuotes by asserting a
|
||||||
|
responsive record, if one exists.
|
||||||
|
|
||||||
|
react {
|
||||||
|
during Syndicate.observe(bookQuote($title, _)) {
|
||||||
|
assert bookQuote(title, title in this.books ? this.books[title] : false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
It also responds to order requests.
|
||||||
|
|
||||||
|
react {
|
||||||
|
on asserted Syndicate.observe(order($title, $offerPrice, _, _)) {
|
||||||
|
|
||||||
|
We cannot sell a book we do not have, and we will not sell for
|
||||||
|
less than our asking price.
|
||||||
|
|
||||||
|
var askingPrice = title in this.books ? this.books[title] : false;
|
||||||
|
if ((askingPrice === false) || (offerPrice < askingPrice)) {
|
||||||
|
whileRelevantAssert(order(title, offerPrice, false, false));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
But if we can sell it, we do so by allocating an order ID and
|
||||||
|
replying to the orderer.
|
||||||
|
|
||||||
|
var orderId = this.nextOrderId++;
|
||||||
|
delete this.books[title];
|
||||||
|
|
||||||
|
actor {
|
||||||
|
whileRelevantAssert(order(title, offerPrice, orderId, "March 9th"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Implementation: SPLIT-PROPOSER and book-quote-requestor
|
||||||
|
|
||||||
|
function buyerA() {
|
||||||
|
actor {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
Our actor remembers which books remain on its shopping list, and
|
||||||
|
tries to buy them one at a time, sharing costs with `buyerB`.
|
||||||
|
|
||||||
|
self.titles = ["Catch 22",
|
||||||
|
"Encyclopaedia Brittannica",
|
||||||
|
"Candide",
|
||||||
|
"The Wind in the Willows"];
|
||||||
|
|
||||||
|
JavaScript's callback-oriented blocking means that we express our
|
||||||
|
loop in almost a tail-recursive style, using helper functions
|
||||||
|
`buyBooks` and `trySplit`.
|
||||||
|
|
||||||
|
buyBooks();
|
||||||
|
|
||||||
|
function buyBooks() {
|
||||||
|
if (self.titles.length === 0) {
|
||||||
|
console.log("A has bought everything they wanted!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var title = self.titles.shift();
|
||||||
|
|
||||||
|
First, retrieve a quote for the title, and analyze the result.
|
||||||
|
|
||||||
|
react until {
|
||||||
|
case asserted bookQuote(title, $price) {
|
||||||
|
if (price === false) {
|
||||||
|
console.log("A learns that "+title+" is out-of-stock.");
|
||||||
|
buyBooks();
|
||||||
|
} else {
|
||||||
|
console.log("A learns that the price of "+title+" is "+price);
|
||||||
|
|
||||||
|
Next, repeatedly make split offers to a SPLIT-DISPOSER until
|
||||||
|
either one is accepted, or the contribution from the
|
||||||
|
SPLIT-DISPOSER becomes pointlessly small. We start the process by
|
||||||
|
offering to split the price of the book evenly.
|
||||||
|
|
||||||
|
trySplit(title, price, price / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function trySplit(title, price, contribution) {
|
||||||
|
console.log("A makes an offer to split the price of "+title+
|
||||||
|
" contributing "+contribution);
|
||||||
|
|
||||||
|
If we are about to offer to split the price, but the other buyer
|
||||||
|
would contribute less than 10c, then it's not worth bothering; we
|
||||||
|
may as well buy it ourselves. Another version of the program could
|
||||||
|
perform the BUYER role here.
|
||||||
|
|
||||||
|
if (contribution > (price - 0.10)) {
|
||||||
|
console.log("A gives up on "+title+".");
|
||||||
|
buyBooks();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Make our proposal, and wait for a response.
|
||||||
|
|
||||||
|
react until {
|
||||||
|
case asserted splitProposal(title, price, contribution, true) {
|
||||||
|
console.log("A learns that the split-proposal for "+title+" was accepted");
|
||||||
|
buyBooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
case asserted splitProposal(title, price, contribution, false) {
|
||||||
|
console.log("A learns that the split-proposal for "+title+" was rejected");
|
||||||
|
trySplit(title, price, contribution + ((price - contribution) / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Implementation: SPLIT-DISPOSER and BUYER
|
||||||
|
|
||||||
|
function buyerB() {
|
||||||
|
actor {
|
||||||
|
|
||||||
|
This actor maintains a record of the amount of money it has left
|
||||||
|
to spend.
|
||||||
|
|
||||||
|
this.funds = 5.00;
|
||||||
|
|
||||||
|
It spends its time waiting for a SPLIT-PROPOSER to offer a
|
||||||
|
`splitProposal`.
|
||||||
|
|
||||||
|
react {
|
||||||
|
on asserted Syndicate.observe(splitProposal($title, $price, $theirContribution, _)) {
|
||||||
|
var myContribution = price - theirContribution;
|
||||||
|
console.log("B is being asked to contribute "+myContribution+" toward "+title+
|
||||||
|
" at price "+price);
|
||||||
|
|
||||||
|
We may not be able to afford contributing this much.
|
||||||
|
|
||||||
|
if (myContribution > this.funds) {
|
||||||
|
console.log("B hasn't enough funds ("+this.funds+" remaining)");
|
||||||
|
whileRelevantAssert(splitProposal(title, price, theirContribution, false));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
But if we *can* afford it, update our remaining funds and spawn a
|
||||||
|
small actor to handle the actual purchase now that we have agreed
|
||||||
|
on a split.
|
||||||
|
|
||||||
|
var remainingFunds = this.funds - myContribution;
|
||||||
|
console.log("B accepts the offer, leaving them with "+remainingFunds+" remaining funds");
|
||||||
|
this.funds = remainingFunds;
|
||||||
|
|
||||||
|
actor {
|
||||||
|
react {
|
||||||
|
|
||||||
|
While waiting for order confirmation, take the opportunity to
|
||||||
|
signal to our SPLIT-PROPOSER that we accepted their proposal.
|
||||||
|
|
||||||
|
assert splitProposal(title, price, theirContribution, true);
|
||||||
|
|
||||||
|
When order confirmation arrives, this purchase is completed.
|
||||||
|
|
||||||
|
} until {
|
||||||
|
case asserted order(title, price, $id, $date) {
|
||||||
|
console.log("The order for "+title+" has id "+id+
|
||||||
|
", and will be delivered on "+date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Starting Configuration
|
||||||
|
|
||||||
|
ground dataspace {
|
||||||
|
seller();
|
||||||
|
buyerA();
|
||||||
|
buyerB();
|
||||||
|
}
|
Loading…
Reference in New Issue