Track more information in order to produce an interesting visualisation

This commit is contained in:
Tony Garnock-Jones 2018-11-28 17:52:35 +00:00
parent c316c0646c
commit 3318ac9e4e
6 changed files with 352 additions and 57 deletions

15
packages/broker/d.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<style>
body { padding: 0; border: 0; margin: 0; height: 100vh; overflow: hidden; }
</style>
<meta charset="utf-8">
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d.json"></script>
<svg id="conversation-animation"></svg>
<script src="d.js"></script>
</body>
</html>

212
packages/broker/d.js Normal file
View File

@ -0,0 +1,212 @@
var svg = d3.select("svg#conversation-animation");
svg.attr("width", document.body.clientWidth);
svg.attr("height", document.body.clientHeight);
svg.append("defs").selectAll("marker")
.data(["arrowhead", "arrowhead_actor"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", function (d) { return d === "arrowhead_actor" ? 125 : 25; })
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5 L0, -5")
.style("stroke", "black")
.style("opacity", "1");
var width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var zoom = d3.zoom()
.scaleExtent([1 / 16, 2])
.wheelDelta(function () { return -d3.event.deltaY * (d3.event.deltaMode ? 60 : 1) / 500; })
.filter(function () { return d3.event.type === 'wheel' || d3.event.button === 1; });
var initialTransform = d3.zoomIdentity.translate(width/2, height/2).scale(0.5);
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom)
.call(zoom.transform, initialTransform);
var g = svg.append("g").attr("transform", initialTransform),
link = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".link"),
node = g.append("g").attr("stroke", "#fff").attr("stroke-width", 1.5).selectAll(".node");
zoom.on("zoom", function () { g.attr("transform", d3.event.transform) });
var simulation = d3.forceSimulation()
.force("link", d3.forceLink()
.distance(function(d) {
if (d.target.type === "topic" && d.source.type === "actor") return 100;
if (d.target.type === "topic" || d.source.type === "topic") return 5;
if (d.target.type === "assertion" && d.source.type === "endpoint") return 10;
if (d.target.type === "facet" && d.source.type === "facet") return 50;
if (d.target.type === "facet" && d.source.type === "actor") return 50;
if (d.target.type === "actor" && d.source.type === "actor") return 500;
return 100;
})
.strength(function(d) {
if (d.target.type === "actor" && d.source.type === "actor") return 0.1;
return 0.5;
})
.id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(0, 0))
.on("tick", function () {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
// function mkActor(n,c) { return {type: 'actor', id: n, color: c}; }
// function mkAssertion(n) { return {type: 'assertion', id: n}; }
// function mkLink(s,t,v) { return {source: s, target: t, value: v || 1}; }
var matchWeight = 10;
var graph = dataspaceContents;
// var clientCounter = 1;
// function clickHandler() {
// var currentCounter = clientCounter;
// clientCounter++;
// var c = "Client" + currentCounter;
// var n = "name" + currentCounter;
// graph.nodes.push(mkActor(c, "#FFC0C0"));
// graph.nodes.push(mkAssertion(entry(n,"127.0.0."+currentCounter)));
// graph.nodes.push(mkAssertion(obs(entry(n,"*"))));
// graph.nodes.push(mkAssertion(obs(laterThan(currentCounter))));
// graph.edges.push(mkLink("Cache", obs(entry(n,"*"))));
// graph.edges.push(mkLink("Server", entry(n,"127.0.0."+currentCounter)));
// graph.edges.push(mkLink(c, obs(entry(n,"*"))));
// graph.edges.push(mkLink("Cache", obs(laterThan(currentCounter))));
// graph.edges.push(mkLink(entry(n,"127.0.0."+currentCounter), obs(entry(n,"*")), matchWeight));
// graph.edges.push(mkLink(obs(entry(n,"*")), obs(obs(entry("*","*"))), matchWeight));
// graph.edges.push(mkLink(obs(laterThan(currentCounter)), obs(obs(laterThan("*"))), matchWeight));
// setTimeout(function () {
// graph.nodes.splice(graph.nodes.findIndex(function (n) { return n.id === c; }), 1);
// graph.edges.splice(graph.edges.findIndex(function (l) { return l.source.id === c; }), 1);
// setupGraph();
// }, 3000);
// setupGraph();
// }
// svg.on("click", clickHandler);
// svg.on("doubleclick", clickHandler);
function setupGraph() {
node = node.data(graph.nodes, function(d) { return d.id; });
node.exit().remove();
var group = node.enter().append("g")
.attr("class", function(d) { return d.type; })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.append("circle")
.attr("r", function(d) {
switch (d.type) {
case "actor": return 60;
case "facet": return 20;
case "endpoint": return 10;
default: return 5;
}
})
.attr("fill", function(d) {
switch (d.type) {
case "actor": return "green";
case "facet": return "cyan";
case "topic": return "blue";
case "endpoint": return "yellow";
default: return d.color || "red";
}
})
.on("click", function(d) {
var text = d3.select('#label_' + d.id);
text.style('opacity', text.style('opacity') == 1 ? 0 : 1);
release_pinning(d);
})
group.append("text")
.attr("id", function(d) { return 'label_' + d.id; })
.attr("stroke", "none")
.attr("text-anchor", function(d) {
switch (d.type) {
case "actor": return "middle";
default: return "start";
}
})
.attr("dx", function(d) {
switch (d.type) {
case "actor": return 0;
case "facet": return 20;
default: return 12;
}
})
.attr("dy", ".35em")
.style("pointer-events", "none")
.style("opacity", 0)
.text(function(d) { return d.label; });
node = group.merge(node);
link = link.data(graph.edges, function(d) { return d.source.id + "-" + d.target.id; });
link.exit().remove();
link = link.enter().append("line")
.attr("stroke-width", 1)
.style("marker-end", function (d) {
if (!(d.dir === 'forward' || d.dir === 'both')) return void 0;
// TODO: gross! Why don't I get d.target being an object here?
if (d.target.startsWith('ac_')) return "url('#arrowhead_actor')";
return "url('#arrowhead')";
})
.merge(link);
// Update and restart the simulation.
simulation.nodes(graph.nodes);
simulation.force("link").links(graph.edges);
simulation.alpha(3).restart();
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
// d.fx = null;
// d.fy = null;
}
function release_pinning(d) {
d.fx = null;
d.fy = null;
}
setupGraph();

View File

@ -44,7 +44,7 @@ function Dataspace(bootProc) {
this.pendingActions = Immutable.List([
new ActionGroup(null, Immutable.List([new Spawn(null, bootProc, Immutable.Set())]))]);
this.activatedModules = Immutable.Set();
this.actors = Immutable.Set();
this.actors = Immutable.Map();
}
// Parameters
@ -184,8 +184,8 @@ Dataspace.prototype.refreshAssertions = function () {
});
};
Dataspace.prototype.addActor = function (name, bootProc, initialAssertions) {
let ac = new Actor(this, name, initialAssertions);
Dataspace.prototype.addActor = function (name, bootProc, initialAssertions, parentActor) {
let ac = new Actor(this, name, initialAssertions, parentActor && parentActor.id);
this.applyPatch(ac, ac.adhocAssertions.snapshot());
ac.addFacet(null, () => {
// Root facet is a dummy "system" facet that exists to hold
@ -213,8 +213,11 @@ Dataspace.prototype.applyPatch = function (ac, delta) {
});
};
Dataspace.prototype.sendMessage = function (m) {
Dataspace.prototype.sendMessage = function (m, sendingActor) {
this.index.sendMessage(m);
// this.index.sendMessage(m, (leaf, _m) => {
// sendingActor.touchedTopics = sendingActor.touchedTopics.add(leaf);
// });
};
Dataspace.prototype.adjustIndex = function (a, count) {
@ -256,62 +259,72 @@ Dataspace.prototype._debugString = function (outerIndent) {
Dataspace.prototype._dotGraph = function () {
let id = 0;
const assertionIds = {};
const nodes = [];
const edges = [];
const pieces = [];
function emitNode(type, id, _label, attrs) {
const label = _str(_label);
pieces.push(`\n ${id} [label=${JSON.stringify(label)}];`);
nodes.push(Object.assign({}, attrs || {}, {type, id, label}));
}
function emitEdge(source, target, maybeDir) {
pieces.push(`\n ${source} -- ${target} [dir=${maybeDir || 'none'}];`);
edges.push({source, target, dir: maybeDir || 'none'});
}
function _aId(aStr) {
// if (aStr.startsWith('observe(Request(') || aStr.startsWith('Request(')) return null;
// if (aStr.startsWith('observe(Connection(') || aStr.startsWith('Connection(')) return null;
if (!(aStr in assertionIds)) assertionIds[aStr] = id++;
return assertionIds[aStr];
}
const assertions = {};
let topics = Immutable.Map();
function topicForLeaf(leaf) {
if (topics.has(leaf)) {
return topics.get(leaf);
} else {
const topic = {id: id++, hasEmitter: false, senders: {}, inbound: {}, outbound: {}};
topics = topics.set(leaf, topic);
return topic;
}
}
function _str(a) {
return '' + a;
}
function walkNode(n) {
n.edges.forEach((table) => table.forEach(walkNode));
n.continuation.leafMap.forEach((cvMap) => cvMap.forEach((leaf) => {
leaf.handlerMap.forEach((handler) => {
handler.callbacks.forEach((cb) => {
const observing_assertion_str = _str(cb.__endpoint.handler.assertion);
leaf.cachedAssertions.forEach((observed_assertion) => {
const observed_assertion_str = _str(observed_assertion);
_aId(observed_assertion_str);
if (!(observed_assertion_str in assertions)) assertions[observed_assertion_str] = {};
const t = assertions[observed_assertion_str];
if (!(observing_assertion_str in t)) t[observing_assertion_str] = true;
_aId(observing_assertion_str);
});
});
});
}));
}
walkNode(this.index.root);
const pieces = [];
pieces.push('graph G {');
pieces.push('\n overlap=false;');
for (const a in assertionIds) {
pieces.push(`\n assn_${assertionIds[a]} [label=${JSON.stringify(a).replace(/ /g,'\\n')}];`);
}
for (const observed in assertions) {
for (const observing in assertions[observed]) {
pieces.push(`\n assn_${assertionIds[observed]} -- assn_${assertionIds[observing]};`);
}
}
this.actors.forEach((ac) => {
const acId = id++;
pieces.push(`\n ac_${acId} [label=${JSON.stringify(ac.toString())}];`);
const acId = ac.id;
emitNode('actor', `ac_${acId}`, ac.toString());
if (this.actors.has(ac.parentId)) {
emitEdge(`ac_${ac.parentId}`, `ac_${acId}`, 'forward');
}
// ac.touchedTopics.forEach((leaf) => {
// const topic = topicForLeaf(leaf);
// topic.senders[acId] = true;
// topic.hasEmitter = true;
// });
// ac.touchedTopics = Immutable.Set();
function walkFacet(parent) {
return (f) => {
const facetId = id++;
pieces.push(`\n facet_${facetId} [label="Facet ${f.id}"];`);
pieces.push(`\n ${parent} -- facet_${facetId};`);
emitNode('facet', `facet_${facetId}`, `Facet ${f.id}`, {parent});
emitEdge(parent, `facet_${facetId}`);
f.endpoints.forEach((ep) => {
const aStr = _str(ep.assertion);
pieces.push(`\n ep_${ep.id} [label="${ep.id}"];`);
pieces.push(`\n facet_${facetId} -- ep_${ep.id};`);
if (aStr in assertionIds) {
pieces.push(`\n ep_${ep.id} -- assn_${assertionIds[_str(ep.assertion)]};`);
} else {
const aId = _aId(aStr);
pieces.push(`\n assn_${aId} [label=${JSON.stringify(aStr).replace(/ /g,'\\n')}];`);
pieces.push(`\n ep_${ep.id} -- assn_${aId};`);
if (ep.assertion !== void 0) {
const aId = _aId(_str(ep.assertion));
if (aId) {
emitNode('endpoint', `ep_${ep.id}`, ep.id);
emitEdge(`facet_${facetId}`, `ep_${ep.id}`);
emitEdge(`ep_${ep.id}`, `assn_${aId}`);
}
}
});
f.children.forEach(walkFacet(`facet_${facetId}`));
@ -319,11 +332,57 @@ Dataspace.prototype._dotGraph = function () {
}
ac.rootFacet.children.forEach(walkFacet(`ac_${acId}`));
});
function walkNode(n) {
n.edges.forEach((table) => table.forEach(walkNode));
n.continuation.leafMap.forEach((cvMap) => cvMap.forEach((leaf) => {
const topic = topicForLeaf(leaf);
leaf.cachedAssertions.forEach((observed_assertion) => {
const observed_assertion_id = _aId(_str(observed_assertion));
if (observed_assertion_id) {
topic.inbound[observed_assertion_id] = true;
topic.hasEmitter = true;
}
});
leaf.handlerMap.forEach((handler) => {
handler.callbacks.forEach((cb) => {
const observing_assertion_id = _aId(_str(cb.__endpoint.handler.assertion));
if (observing_assertion_id) {
topic.outbound[observing_assertion_id] = true;
}
});
});
}));
}
walkNode(this.index.root);
for (const a in assertionIds) {
emitNode('assertion', `assn_${assertionIds[a]}`, a);
}
topics.forEach((topic) => {
if (topic.hasEmitter) {
emitNode('topic', 'topic_' + topic.id, ''); // `Topic ${topic.id}`);
for (const acId in topic.senders) {
emitEdge(`ac_${acId}`, `topic_${topic.id}`, 'forward');
}
for (const aId in topic.inbound) {
emitEdge(`assn_${aId}`, `topic_${topic.id}`, 'forward');
}
for (const aId in topic.outbound) {
emitEdge(`topic_${topic.id}`, `assn_${aId}`, 'forward');
}
}
});
pieces.push('\n}');
require('fs').writeFileSync('d.json', 'var dataspaceContents = ' + JSON.stringify({nodes, edges}, null, 2));
return pieces.join('');
};
function Actor(dataspace, name, initialAssertions) {
function Actor(dataspace, name, initialAssertions, parentActorId) {
this.id = dataspace.nextId++;
this.dataspace = dataspace;
this.name = name;
@ -334,7 +393,9 @@ function Actor(dataspace, name, initialAssertions) {
this.pendingActions = Immutable.List();
this.adhocAssertions = new Bag.MutableBag(initialAssertions); // no negative counts allowed
this.cleanupChanges = new Bag.MutableBag(); // negative counts allowed!
dataspace.actors = dataspace.actors.add(this);
this.parentId = parentActorId;
// this.touchedTopics = Immutable.Set();
dataspace.actors = dataspace.actors.set(this.id, this);
}
Actor.prototype.runPendingScripts = function () {
@ -478,7 +539,7 @@ function Message(body) {
Message.prototype.perform = function (ds, ac) {
if (this.body !== void 0) {
ds.sendMessage(Preserves.fromJS(this.body));
ds.sendMessage(Preserves.fromJS(this.body), ac);
}
};
@ -493,7 +554,7 @@ function Spawn(name, bootProc, initialAssertions) {
}
Spawn.prototype.perform = function (ds, ac) {
ds.addActor(this.name, this.bootProc, this.initialAssertions);
ds.addActor(this.name, this.bootProc, this.initialAssertions, ac);
};
Dataspace.spawn = function (name, bootProc, initialAssertions) {
@ -506,7 +567,7 @@ function Quit() { // TODO: rename? Perhaps to Cleanup?
Quit.prototype.perform = function (ds, ac) {
ds.applyPatch(ac, ac.cleanupChanges.snapshot());
ds.actors = ds.actors.remove(ac);
ds.actors = ds.actors.remove(ac.id);
};
function DeferredTurn(continuation) {

View File

@ -107,6 +107,14 @@ function bootModule(mod) {
process.on('SIGQUIT', () => {
console.log('---------------------------------------------------------------------------');
console.log(g._debugString());
g._dotGraph();
// const child_process = require('child_process');
// const sp = child_process.spawn('dotpreview.sh 100% neato', {
// shell: true,
// stdio: ['pipe', 'ignore', 'ignore']
// });
// sp.stdin.end(g._dotGraph());
});
g.start();
}

View File

@ -270,8 +270,9 @@ Index.prototype.adjustAssertion = function(outerValue, delta) {
Index.prototype.addAssertion = function(v) { this.adjustAssertion(v, +1); };
Index.prototype.removeAssertion = function (v) { this.adjustAssertion(v, -1); };
Index.prototype.sendMessage = function(v) {
this.root.modify(v, ()=>{}, ()=>{}, (h, vs) => {
const _nop = () => {};
Index.prototype.sendMessage = function(v, leafCallback) {
this.root.modify(v, _nop, leafCallback || _nop, (h, vs) => {
h.callbacks.forEach((cb) => {
cb(EVENT_MESSAGE, vs);
return true;

View File

@ -29,10 +29,8 @@ spawn named 'chatserver' {
stop on retracted S.Duplex(id);
assert Present(me);
during Present($who) {
on start send S.Push(id, `${who} arrived.\n`, null);
on stop send S.Push(id, `${who} departed.\n`, null);
}
on asserted Present($who) send S.Push(id, `${who} arrived.\n`, null);
on retracted Present($who) send S.Push(id, `${who} departed.\n`, null);
on message S.Line(id, $line) send Speak(me, line);
on message Speak($who, $what) send S.Push(id, `${who}: ${what}\n`, null);