Add support for flowchart diagrams alongside sequence

This commit is contained in:
Ladd Hoffman 2023-01-05 14:21:45 -06:00
parent b644d6c119
commit 1066372371
21 changed files with 270 additions and 148 deletions

View File

@ -4,9 +4,9 @@ export class Action {
this.scene = scene; this.scene = scene;
} }
log(src, dest, msg, obj, symbol = '->>') { async log(src, dest, msg, obj, symbol = '->>') {
const logObj = false; const logObj = false;
this.scene.log( await this.scene.sequence.log(
`${src.name} ${symbol} ${dest.name} : ${this.name} ${msg ?? ''} ${ `${src.name} ${symbol} ${dest.name} : ${this.name} ${msg ?? ''} ${
logObj && obj ? JSON.stringify(obj) : '' logObj && obj ? JSON.stringify(obj) : ''
}`, }`,

View File

@ -5,7 +5,6 @@ export class Actor {
this.callbacks = new Map(); this.callbacks = new Map();
this.status = this.scene.addDisplayValue(`${this.name} status`); this.status = this.scene.addDisplayValue(`${this.name} status`);
this.status.set('New'); this.status.set('New');
this.scene.log(`participant ${this.name}`);
this.values = new Map(); this.values = new Map();
this.active = 0; this.active = 0;
this.scene.registerActor(this); this.scene.registerActor(this);
@ -13,31 +12,31 @@ export class Actor {
activate() { activate() {
this.active += 1; this.active += 1;
this.scene.log(`activate ${this.name}`); this.scene.sequence.log(`activate ${this.name}`, false);
} }
deactivate() { async deactivate() {
if (!this.active) { if (!this.active) {
throw new Error(`${this.name} is not active, can not deactivate`); throw new Error(`${this.name} is not active, can not deactivate`);
} }
this.active -= 1; this.active -= 1;
this.scene.log(`deactivate ${this.name}`); await this.scene.sequence.log(`deactivate ${this.name}`);
} }
send(dest, action, detail) { async send(dest, action, detail) {
action.log(this, dest, detail ? JSON.stringify(detail) : ''); await action.log(this, dest, detail ? JSON.stringify(detail) : '');
dest.recv(this, action, detail); await dest.recv(this, action, detail);
return this; return this;
} }
recv(src, action, detail) { async recv(src, action, detail) {
const cb = this.callbacks.get(action.name); const cb = this.callbacks.get(action.name);
if (!cb) { if (!cb) {
throw new Error( throw new Error(
`[${this.scene.name} actor ${this.name} does not have a callback registered for ${action.name}`, `[${this.scene.name} actor ${this.name} does not have a callback registered for ${action.name}`,
); );
} }
cb(src, detail); await cb(src, detail);
return this; return this;
} }
@ -56,7 +55,7 @@ export class Actor {
return this; return this;
} }
setValue(label, value) { async setValue(label, value) {
if (typeof value === 'number') { if (typeof value === 'number') {
value = value.toFixed(2); value = value.toFixed(2);
} }
@ -66,7 +65,7 @@ export class Actor {
this.values.set(label, displayValue); this.values.set(label, displayValue);
} }
if (value !== displayValue.get()) { if (value !== displayValue.get()) {
this.scene.log(`note over ${this.name} : ${label} = ${value}`); await this.scene.sequence.log(`note over ${this.name} : ${label} = ${value}`);
} }
displayValue.set(value); displayValue.set(value);
return this; return this;

View File

@ -18,8 +18,6 @@ export class Bench extends Actor {
this.actions = { this.actions = {
createValidationPool: new Action('create validation pool', scene), createValidationPool: new Action('create validation pool', scene),
}; };
this.activate();
} }
listValidationPools() { listValidationPools() {
@ -56,7 +54,7 @@ export class Bench extends Actor {
.reduce((acc, cur) => (acc += cur), 0); .reduce((acc, cur) => (acc += cur), 0);
} }
initiateValidationPool({ async initiateValidationPool({
postId, postId,
fee, fee,
duration, duration,
@ -84,7 +82,7 @@ export class Bench extends Actor {
this.scene, this.scene,
); );
this.validationPools.set(validationPool.id, validationPool); this.validationPools.set(validationPool.id, validationPool);
this.actions.createValidationPool.log(this, validationPool); await this.actions.createValidationPool.log(this, validationPool);
validationPool.activate(); validationPool.activate();
return validationPool; return validationPool;
} }

View File

@ -1,11 +1,15 @@
import { DisplayValue } from './display-value.js'; import { DisplayValue } from './display-value.js';
import { CryptoUtil } from './crypto.js';
export class Box { export class Box {
constructor(name, parentEl, elementType = 'div') { constructor(name, parentEl, elementType = 'div') {
this.name = name; this.name = name;
this.el = document.createElement(elementType); this.el = document.createElement(elementType);
this.el.id = `box_${CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8)}`;
this.el.classList.add('box'); this.el.classList.add('box');
this.el.setAttribute('box-name', name); if (name) {
this.el.setAttribute('box-name', name);
}
if (parentEl) { if (parentEl) {
parentEl.appendChild(this.el); parentEl.appendChild(this.el);
} }
@ -27,8 +31,7 @@ export class Box {
} }
addBox(name, elementType) { addBox(name, elementType) {
const box = new Box(name, null, elementType); const box = new Box(name, this.el, elementType);
this.el.appendChild(box.el);
return box; return box;
} }
@ -46,11 +49,6 @@ export class Box {
return this.el.innerText; return this.el.innerText;
} }
setId(id) {
this.el.id = (id || this.name).replace(/ /g, '');
return this;
}
getId() { getId() {
return this.el.id; return this.el.id;
} }

View File

@ -33,7 +33,7 @@ export class Business extends Actor {
async submitRequest(fee, content) { async submitRequest(fee, content) {
const request = new Request(fee, content); const request = new Request(fee, content);
this.requests.set(request.id, request); this.requests.set(request.id, request);
this.actions.assignWork.log(this, this.availability); await this.actions.assignWork.log(this, this.availability);
this.worker = await this.availability.assignWork(request.id); this.worker = await this.availability.assignWork(request.id);
return request.id; return request.id;
} }
@ -54,13 +54,13 @@ export class Business extends Actor {
requestId, requestId,
workEvidence, workEvidence,
}); });
this.actions.submitPost.log(this, this.forum); await this.actions.submitPost.log(this, this.forum);
const postId = await this.forum.addPost(reputationPublicKey, post); const postId = await this.forum.addPost(reputationPublicKey, post);
// Initiate a validation pool for this work evidence. // Initiate a validation pool for this work evidence.
// Validation pool supports secret ballots but we aren't using that here, since we want // Validation pool supports secret ballots but we aren't using that here, since we want
// the post to be attributable to the reputation holder. // the post to be attributable to the reputation holder.
this.actions.initiateValidationPool.log(this, this.bench); await this.actions.initiateValidationPool.log(this, this.bench);
const pool = await this.bench.initiateValidationPool({ const pool = await this.bench.initiateValidationPool({
postId, postId,
fee: request.fee, fee: request.fee,

View File

@ -1,5 +1,4 @@
import { Actor } from './actor.js'; import { Actor } from './actor.js';
import { Action } from './action.js'; import { Action } from './action.js';
import { PostMessage } from './message.js'; import { PostMessage } from './message.js';
import { CryptoUtil } from './crypto.js'; import { CryptoUtil } from './crypto.js';
@ -25,7 +24,6 @@ export class Expert extends Actor {
// this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey); // this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey);
this.reputationPublicKey = this.name; this.reputationPublicKey = this.name;
this.status.set('Initialized'); this.status.set('Initialized');
this.activate();
return this; return this;
} }
@ -33,19 +31,19 @@ export class Expert extends Actor {
// TODO: Include fee // TODO: Include fee
const postMessage = new PostMessage({ post, stake }); const postMessage = new PostMessage({ post, stake });
await postMessage.sign(this.reputationKey); await postMessage.sign(this.reputationKey);
this.actions.submitPostViaNetwork.log(this, forumNode, null, { id: post.id }); await this.actions.submitPostViaNetwork.log(this, forumNode, null, { id: post.id });
// For now, directly call forumNode.receiveMessage(); // For now, directly call forumNode.receiveMessage();
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON())); await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
} }
async submitPost(forum, postContent) { async submitPost(forum, postContent) {
// TODO: Include fee // TODO: Include fee
this.actions.submitPost.log(this, forum); await this.actions.submitPost.log(this, forum);
return forum.addPost(this.reputationPublicKey, postContent); return forum.addPost(this.reputationPublicKey, postContent);
} }
async submitPostWithFee(bench, forum, postContent, poolOptions) { async submitPostWithFee(bench, forum, postContent, poolOptions) {
this.actions.submitPost.log(this, forum); await this.actions.submitPost.log(this, forum);
const postId = await forum.addPost(this.reputationPublicKey, postContent); const postId = await forum.addPost(this.reputationPublicKey, postContent);
const pool = await this.initiateValidationPool(bench, { ...poolOptions, postId, anonymous: false }); const pool = await this.initiateValidationPool(bench, { ...poolOptions, postId, anonymous: false });
return { postId, pool }; return { postId, pool };
@ -59,12 +57,12 @@ export class Expert extends Actor {
} else { } else {
poolOptions.signingPublicKey = this.reputationPublicKey; poolOptions.signingPublicKey = this.reputationPublicKey;
} }
this.actions.initiateValidationPool.log( await this.actions.initiateValidationPool.log(
this, this,
bench, bench,
`(fee: ${poolOptions.fee})`, `(fee: ${poolOptions.fee})`,
); );
const pool = bench.initiateValidationPool(poolOptions); const pool = await bench.initiateValidationPool(poolOptions);
this.validationPools.set(pool.id, poolOptions); this.validationPools.set(pool.id, poolOptions);
return pool; return pool;
} }
@ -82,7 +80,7 @@ export class Expert extends Actor {
} }
// TODO: encrypt vote // TODO: encrypt vote
// TODO: sign message // TODO: sign message
this.actions.castVote.log( await this.actions.castVote.log(
this, this,
validationPool, validationPool,
`(${position ? 'for' : 'against'}, stake: ${stake}, anonymous: ${anonymous})`, `(${position ? 'for' : 'against'}, stake: ${stake}, anonymous: ${anonymous})`,
@ -95,12 +93,12 @@ export class Expert extends Actor {
async revealIdentity(validationPool) { async revealIdentity(validationPool) {
const { signingPublicKey } = this.validationPools.get(validationPool.id); const { signingPublicKey } = this.validationPools.get(validationPool.id);
// TODO: sign message // TODO: sign message
this.actions.revealIdentity.log(this, validationPool); await this.actions.revealIdentity.log(this, validationPool);
validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey); validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey);
} }
async registerAvailability(availability, stake) { async registerAvailability(availability, stake) {
this.actions.registerAvailability.log(this, availability, `(stake: ${stake})`); await this.actions.registerAvailability.log(this, availability, `(stake: ${stake})`);
await availability.register(this.reputationPublicKey, stake); await availability.register(this.reputationPublicKey, stake);
} }
@ -111,7 +109,7 @@ export class Expert extends Actor {
} }
async submitWork(business, requestId, evidence, { tokenLossRatio, duration }) { async submitWork(business, requestId, evidence, { tokenLossRatio, duration }) {
this.actions.submitWork.log(this, business); await this.actions.submitWork.log(this, business);
return business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration }); return business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration });
} }
} }

View File

@ -34,7 +34,7 @@ export class ForumNode extends Actor {
.filter((forumNode) => forumNode.keyPair.publicKey !== this.keyPair.publicKey); .filter((forumNode) => forumNode.keyPair.publicKey !== this.keyPair.publicKey);
for (const forumNode of otherForumNodes) { for (const forumNode of otherForumNodes) {
// For now just call receiveMessage on the target node // For now just call receiveMessage on the target node
this.actions.peerMessage.log(this, forumNode, null, message.content); await this.actions.peerMessage.log(this, forumNode, null, message.content);
await forumNode.receiveMessage(JSON.stringify(message.toJSON())); await forumNode.receiveMessage(JSON.stringify(message.toJSON()));
} }
} }
@ -61,7 +61,7 @@ export class ForumNode extends Actor {
try { try {
await Message.verify(messageJson); await Message.verify(messageJson);
} catch (e) { } catch (e) {
this.actions.processMessage.log(this, this, 'invalid signature', messageJson, '-x'); await this.actions.processMessage.log(this, this, 'invalid signature', messageJson, '-x');
console.log(`${this.name}: received message with invalid signature`); console.log(`${this.name}: received message with invalid signature`);
return; return;
} }
@ -87,7 +87,7 @@ export class ForumNode extends Actor {
if (!post.id) { if (!post.id) {
post.id = CryptoUtil.randomUUID(); post.id = CryptoUtil.randomUUID();
} }
this.actions.storePost.log(this, this, null, { authorId, post, stake }); await this.actions.storePost.log(this, this, null, { authorId, post, stake });
this.forumView.addPost(authorId, post.id, post, stake); this.forumView.addPost(authorId, post.id, post, stake);
} }

View File

@ -13,6 +13,7 @@ class Post extends Actor {
this.authorPublicKey = authorPublicKey; this.authorPublicKey = authorPublicKey;
this.value = 0; this.value = 0;
this.citations = postContent.citations; this.citations = postContent.citations;
this.title = postContent.title;
this.totalCitationWeight = this.citations.reduce((total, { weight }) => total += weight, 0); this.totalCitationWeight = this.citations.reduce((total, { weight }) => total += weight, 0);
if (this.totalCitationWeight > params.revaluationLimit) { if (this.totalCitationWeight > params.revaluationLimit) {
throw new Error('Post total citation weight exceeds revaluation limit ' throw new Error('Post total citation weight exceeds revaluation limit '
@ -23,8 +24,8 @@ class Post extends Actor {
} }
} }
setPostValue(value) { async setPostValue(value) {
this.setValue('value', value); await this.setValue('value', value);
this.value = value; this.value = value;
} }
@ -39,7 +40,7 @@ class Post extends Actor {
export class Forum extends Actor { export class Forum extends Actor {
constructor(name, scene) { constructor(name, scene) {
super(name, scene); super(name, scene);
this.posts = new Graph(); this.posts = new Graph(scene);
this.actions = { this.actions = {
addPost: new Action('add post', scene), addPost: new Action('add post', scene),
}; };
@ -47,9 +48,9 @@ export class Forum extends Actor {
async addPost(authorId, postContent) { async addPost(authorId, postContent) {
const post = new Post(this, authorId, postContent); const post = new Post(this, authorId, postContent);
this.actions.addPost.log(this, post); await this.actions.addPost.log(this, post);
this.posts.addVertex(post.id, post); this.posts.addVertex(post.id, post, post.title);
for (const { postId: citedPostId, weight } of postContent.citations) { for (const { postId: citedPostId, weight } of post.citations) {
this.posts.addEdge('citation', post.id, citedPostId, { weight }); this.posts.addEdge('citation', post.id, citedPostId, { weight });
} }
return post.id; return post.id;
@ -63,7 +64,7 @@ export class Forum extends Actor {
return this.posts.getVertices(); return this.posts.getVertices();
} }
propagateValue(postId, increment, depth = 0) { async propagateValue(postId, increment, depth = 0) {
if (depth > params.maxPropagationDepth) { if (depth > params.maxPropagationDepth) {
return []; return [];
} }
@ -78,7 +79,7 @@ export class Forum extends Actor {
// Increment the value of the given post // Increment the value of the given post
const postValue = post.getPostValue(); const postValue = post.getPostValue();
post.setPostValue(postValue + increment); await post.setPostValue(postValue + increment);
// Award reputation to post author // Award reputation to post author
console.log('reward for post author', post.authorPublicKey, increment); console.log('reward for post author', post.authorPublicKey, increment);
@ -86,7 +87,7 @@ export class Forum extends Actor {
// Recursively distribute reputation to citations, according to weights // Recursively distribute reputation to citations, according to weights
for (const { postId: citedPostId, weight } of post.citations) { for (const { postId: citedPostId, weight } of post.citations) {
addRewards(this.propagateValue(citedPostId, weight * increment, depth + 1)); addRewards(await this.propagateValue(citedPostId, weight * increment, depth + 1));
} }
return rewards; return rewards;

View File

@ -1,6 +1,3 @@
import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs';
import { debounce } from '../util.js';
export class Vertex { export class Vertex {
constructor(data) { constructor(data) {
this.data = data; this.data = data;
@ -29,13 +26,14 @@ export class Edge {
export class CategorizedEdges {} export class CategorizedEdges {}
export class Graph { export class Graph {
constructor() { constructor(scene) {
this.scene = scene;
this.vertices = new Map(); this.vertices = new Map();
this.edgeLabels = new Map(); this.edgeLabels = new Map();
this.nextVertexId = 0; this.nextVertexId = 0;
} }
addVertex(id, data) { addVertex(id, data, label) {
// Support simple case of auto-incremented numeric ids // Support simple case of auto-incremented numeric ids
if (typeof id === 'object') { if (typeof id === 'object') {
data = id; data = id;
@ -46,6 +44,9 @@ export class Graph {
} }
const vertex = new Vertex(data); const vertex = new Vertex(data);
this.vertices.set(id, vertex); this.vertices.set(id, vertex);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${id}[${label ?? id}]`);
}
return this; return this;
} }
@ -83,6 +84,9 @@ export class Graph {
this.setEdge(label, from, to, edge); this.setEdge(label, from, to, edge);
this.getVertex(from).edges.from.push(edge); this.getVertex(from).edges.from.push(edge);
this.getVertex(to).edges.to.push(edge); this.getVertex(to).edges.to.push(edge);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${from} --> ${to}`);
}
return this; return this;
} }
@ -101,20 +105,4 @@ export class Graph {
countVertices() { countVertices() {
return this.vertices.size; return this.vertices.size;
} }
async renderGraph() {
const render = async () => {
const dateStart = new Date();
const graph = await mermaid.mermaidAPI.render(
this.seqDiagramElement.getId(),
this.logBox.getInnerText(),
);
this.seqDiagramBox.setInnerHTML(graph);
if (!this.dateLastRender) {
this.dateLastRender = new Date();
}
this.dateLastRender = dateStart;
};
debounce(render, 100);
}
} }

View File

@ -28,18 +28,27 @@ export class PostContent {
return this; return this;
} }
setTitle(title) {
this.title = title;
return this;
}
toJSON() { toJSON() {
return { return {
content: this.content, content: this.content,
citations: this.citations.map((citation) => citation.toJSON()), citations: this.citations.map((citation) => citation.toJSON()),
...(this.id ? { id: this.id } : {}), ...(this.id ? { id: this.id } : {}),
title: this.title,
}; };
} }
static fromJSON({ id, content, citations }) { static fromJSON({
id, content, citations, title,
}) {
const post = new PostContent(content); const post = new PostContent(content);
post.citations = citations.map((citation) => Citation.fromJSON(citation)); post.citations = citations.map((citation) => Citation.fromJSON(citation));
post.id = id; post.id = id;
post.title = title;
return post; return post;
} }
} }

View File

@ -3,24 +3,47 @@ import { Actor } from './actor.js';
import { Action } from './action.js'; import { Action } from './action.js';
import { debounce } from '../util.js'; import { debounce } from '../util.js';
class MermaidDiagram {
constructor(box) {
this.box = box;
this.container = this.box.addBox('Container');
this.element = this.box.addBox('Element');
this.renderBox = this.box.addBox('Render');
this.box.addBox('Spacer').setInnerHTML(' ');
this.logBox = this.box.addBox('Log');
}
async log(msg, render = true) {
this.logBox.addBox().setInnerHTML(msg).monospace();
if (render) {
await this.render();
}
return this;
}
async render() {
const render = async () => {
const innerText = this.logBox.getInnerText();
const graph = await mermaid.mermaidAPI.render(
this.element.getId(),
innerText,
);
this.renderBox.setInnerHTML(graph);
};
await debounce(render, 100);
}
}
export class Scene { export class Scene {
constructor(name, rootBox) { constructor(name, rootBox) {
this.name = name; this.name = name;
this.box = rootBox.addBox(name); this.box = rootBox.addBox(name);
this.titleBox = this.box.addBox().setInnerHTML(name); this.titleBox = this.box.addBox('Title').setInnerHTML(name);
this.box.addBox('Spacer').setInnerHTML(' '); this.box.addBox('Spacer').setInnerHTML(' ');
this.displayValuesBox = this.box.addBox(`${this.name}-values`); this.displayValuesBox = this.box.addBox('Values');
this.box.addBox('Spacer').setInnerHTML(' '); this.box.addBox('Spacer').setInnerHTML(' ');
this.actors = new Set(); this.actors = new Set();
this.seqDiagramContainer = this.box.addBox(
`${this.name}-seq-diagram-container`,
);
this.seqDiagramElement = this.box
.addBox(`${this.name}-seq-diagram-element`)
.setId();
this.seqDiagramBox = this.box.addBox(`${this.name}-seq-diagram`);
this.box.addBox('Spacer').setInnerHTML(' ');
this.logBox = this.box.addBox(`${this.name}-log`);
mermaid.mermaidAPI.initialize({ mermaid.mermaidAPI.initialize({
startOnLoad: false, startOnLoad: false,
theme: 'base', theme: 'base',
@ -28,18 +51,37 @@ export class Scene {
darkMode: true, darkMode: true,
primaryColor: '#2a5b6c', primaryColor: '#2a5b6c',
primaryTextColor: '#b6b6b6', primaryTextColor: '#b6b6b6',
// lineColor: '#349cbd',
lineColor: '#57747d',
signalColor: '#57747d',
// signalColor: '#349cbd',
noteBkgColor: '#516f77', noteBkgColor: '#516f77',
noteTextColor: '#cecece', noteTextColor: '#cecece',
activationBkgColor: '#1d3f49', activationBkgColor: '#1d3f49',
activationBorderColor: '#569595', activationBorderColor: '#569595',
signalColor: '#57747d',
}, },
}); });
this.dateLastRender = null;
} }
addActor(name) { withSequenceDiagram() {
const box = this.box.addBox('Sequence diagram');
this.sequence = new MermaidDiagram(box);
this.sequence.log('sequenceDiagram', false);
return this;
}
withFlowchart(direction = 'BT') {
const box = this.box.addBox('Flowchart');
this.flowchart = new MermaidDiagram(box);
this.flowchart.log(`graph ${direction}`, false);
return this;
}
async addActor(name) {
const actor = new Actor(name, this); const actor = new Actor(name, this);
if (this.sequence) {
await this.scene.sequence.log(`participant ${name}`);
}
return actor; return actor;
} }
@ -57,12 +99,6 @@ export class Scene {
return dv; return dv;
} }
log(msg) {
this.logBox.addBox().setInnerHTML(msg).monospace();
this.renderSequenceDiagram();
return this;
}
deactivateAll() { deactivateAll() {
for (const actor of this.actors.values()) { for (const actor of this.actors.values()) {
while (actor.active) { while (actor.active) {
@ -70,20 +106,4 @@ export class Scene {
} }
} }
} }
async renderSequenceDiagram() {
const render = async () => {
const dateStart = new Date();
const graph = await mermaid.mermaidAPI.render(
this.seqDiagramElement.getId(),
this.logBox.getInnerText(),
);
this.seqDiagramBox.setInnerHTML(graph);
if (!this.dateLastRender) {
this.dateLastRender = new Date();
}
this.dateLastRender = dateStart;
};
debounce(render, 100);
}
} }

View File

@ -193,12 +193,12 @@ export class ValidationPool extends Actor {
if (quorumMet) { if (quorumMet) {
this.setStatus(`Resolved - ${result ? 'Won' : 'Lost'}`); this.setStatus(`Resolved - ${result ? 'Won' : 'Lost'}`);
this.scene.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`); this.scene.sequence.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`);
this.applyTokenLocking(); this.applyTokenLocking();
this.distributeTokens(result); await this.distributeTokens(result);
} else { } else {
this.setStatus('Resolved - Quorum not met'); this.setStatus('Resolved - Quorum not met');
this.scene.log(`note over ${this.name} : Quorum not met`); this.scene.sequence.log(`note over ${this.name} : Quorum not met`);
} }
this.deactivate(); this.deactivate();
@ -206,7 +206,7 @@ export class ValidationPool extends Actor {
return result; return result;
} }
distributeTokens({ votePasses }) { async distributeTokens({ votePasses }) {
const rewards = new Map(); const rewards = new Map();
const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value); const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value);
@ -231,7 +231,7 @@ export class ValidationPool extends Actor {
if (votePasses && !!this.forum) { if (votePasses && !!this.forum) {
// Recurse through forum to determine reputation effects // Recurse through forum to determine reputation effects
const forumReputationEffects = this.forum.propagateValue(this.postId, this.tokensMinted); const forumReputationEffects = await this.forum.propagateValue(this.postId, this.tokensMinted);
for (const [id, value] of forumReputationEffects) { for (const [id, value] of forumReputationEffects) {
addReward(id, value); addReward(id, value);
} }

View File

@ -14,5 +14,6 @@
<li><a href="/tests/debounce.html">Debounce</a></li> <li><a href="/tests/debounce.html">Debounce</a></li>
<li><a href="/tests/availability.html">Availability</a></li> <li><a href="/tests/availability.html">Availability</a></li>
<li><a href="/tests/forum.html">Forum</a></li> <li><a href="/tests/forum.html">Forum</a></li>
<li><a href="/tests/flowchart.html">Flowchart</a></li>
</ul> </ul>
</body> </body>

View File

@ -56,13 +56,13 @@
const updateDisplayValues = async () => { const updateDisplayValues = async () => {
for (const expert of experts) { for (const expert of experts) {
expert.setValue( await expert.setValue(
"rep", "rep",
bench.reputations.getTokens(expert.reputationPublicKey) bench.reputations.getTokens(expert.reputationPublicKey)
); );
} }
bench.setValue("total rep", bench.getTotalReputation()); await bench.setValue("total rep", bench.getTotalReputation());
await scene.renderSequenceDiagram(); await scene.sequence.render();
}; };
const updateDisplayValuesAndDelay = async () => { const updateDisplayValuesAndDelay = async () => {
@ -77,7 +77,7 @@
request = await expert.getAssignedWork(availability, business); request = await expert.getAssignedWork(availability, business);
if (request) { if (request) {
worker = expert; worker = expert;
worker.actions.getAssignedWork.log(worker, availability); await worker.actions.getAssignedWork.log(worker, availability);
worker.activate(); worker.activate();
break; break;
} }

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<head>
<title>Debounce test</title>
<link type="text/css" rel="stylesheet" href="/index.css" />
</head>
<body>
<div id="debounce-test"></div>
</body>
<script type="module">
import { Box } from "/classes/box.js";
import { Scene } from "/classes/scene.js";
import { debounce, delay } from "/util.js";
const rootElement = document.getElementById("debounce-test");
const rootBox = new Box("rootBox", rootElement).flex();
const scene = (window.scene = new Scene("Debounce test", rootBox));
let eventCount = 0;
const event = () => {
eventCount++;
console.log(`event ${eventCount}`);
};
await debounce(event, 500);
await debounce(event, 500);
await delay(500);
await debounce(event, 500);
await debounce(event, 500);
if (eventCount !== 2) {
throw new Error(`Expected 2 events, got ${eventCount}`);
}
console.log(`eventCount: ${eventCount}`);
</script>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<head>
<title>Flowchart test</title>
<link type="text/css" rel="stylesheet" href="/index.css" />
</head>
<body>
<div id="flowchart-test"></div>
</body>
<script type="module">
import { Box } from "/classes/box.js";
import { Scene } from "/classes/scene.js";
import { Actor } from "/classes/actor.js";
import { Action } from "/classes/action.js";
import { Expert } from "/classes/expert.js";
import { delay } from "/util.js";
const DEFAULT_DELAY_INTERVAL = 500;
const rootElement = document.getElementById("flowchart-test");
const rootBox = new Box("rootBox", rootElement).flex();
const scene = (window.scene = new Scene("Flowchart test", rootBox));
scene.withSequenceDiagram();
const actor1 = new Actor("A", scene);
const actor2 = new Actor("B", scene);
const action1 = new Action("Action 1", scene);
await action1.log(actor1, actor2);
await actor1.setValue("value", 1);
await scene.withFlowchart();
await scene.flowchart.log("A --> B");
await delay(DEFAULT_DELAY_INTERVAL);
action1.log(actor1, actor2);
await delay(DEFAULT_DELAY_INTERVAL);
await scene.flowchart.log("A --> C");
</script>

View File

@ -43,7 +43,7 @@
await window.forumNode2.processNextMessage(); await window.forumNode2.processNextMessage();
await window.forumNode3.processNextMessage(); await window.forumNode3.processNextMessage();
await window.scene.renderSequenceDiagram(); await window.scene.sequence.render();
}, 100); }, 100);
// const blockchain = new Blockchain(); // const blockchain = new Blockchain();

View File

@ -23,9 +23,9 @@
const rootElement = document.getElementById("forum-test"); const rootElement = document.getElementById("forum-test");
const rootBox = new Box("rootBox", rootElement).flex(); const rootBox = new Box("rootBox", rootElement).flex();
const scene = (window.scene = new Scene("Forum test", rootBox).log( const scene = (window.scene = new Scene("Forum test", rootBox));
"sequenceDiagram" scene.withSequenceDiagram();
)); scene.withFlowchart();
const experts = (window.experts = []); const experts = (window.experts = []);
const newExpert = async () => { const newExpert = async () => {
@ -44,13 +44,12 @@
const updateDisplayValues = async () => { const updateDisplayValues = async () => {
for (const expert of experts) { for (const expert of experts) {
expert.setValue( await expert.setValue(
"rep", "rep",
bench.reputations.getTokens(expert.reputationPublicKey) bench.reputations.getTokens(expert.reputationPublicKey)
); );
} }
bench.setValue("total rep", bench.getTotalReputation()); await bench.setValue("total rep", bench.getTotalReputation());
await scene.renderSequenceDiagram();
}; };
const updateDisplayValuesAndDelay = async (delayMs) => { const updateDisplayValuesAndDelay = async (delayMs) => {
@ -63,7 +62,7 @@
const { postId: postId1, pool: pool1 } = await expert1.submitPostWithFee( const { postId: postId1, pool: pool1 } = await expert1.submitPostWithFee(
bench, bench,
forum, forum,
new PostContent({ hello: "there" }), new PostContent({ hello: "there" }).setTitle("Post 1"),
{ {
fee: 10, fee: 10,
duration: 1000, duration: 1000,
@ -81,7 +80,9 @@
const { postId: postId2, pool: pool2 } = await expert2.submitPostWithFee( const { postId: postId2, pool: pool2 } = await expert2.submitPostWithFee(
bench, bench,
forum, forum,
new PostContent({ hello: "to you as well" }).addCitation(postId1, 0.5), new PostContent({ hello: "to you as well" })
.setTitle("Post 2")
.addCitation(postId1, 0.5),
{ {
fee: 10, fee: 10,
duration: 1000, duration: 1000,
@ -99,7 +100,9 @@
const { pool: pool3 } = await expert3.submitPostWithFee( const { pool: pool3 } = await expert3.submitPostWithFee(
bench, bench,
forum, forum,
new PostContent({ hello: "y'all" }).addCitation(postId2, 0.5), new PostContent({ hello: "y'all" })
.setTitle("Post 3")
.addCitation(postId2, 0.5),
{ {
fee: 10, fee: 10,
duration: 1000, duration: 1000,

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<head>
<title>Forum Graph</title>
<link type="text/css" rel="stylesheet" href="/index.css" />
</head>
<body>
<div id="graph-test"></div>
</body>
<script type="module">
import { Box } from "/classes/box.js";
import { Scene } from "/classes/scene.js";
import { Graph } from "/classes/graph.js";
const rootElement = document.getElementById("graph-test");
const rootBox = new Box("rootBox", rootElement).flex();
window.scene = new Scene("Graph test", rootBox);
window.graph = new Graph();
window.v = [];
function addVertex() {
const vertex = window.graph.addVertex({ seq: window.v.length });
window.v.push(vertex);
}
addVertex();
addVertex();
addVertex();
addVertex();
addVertex();
window.graph.addEdge("e1", 0, 1);
</script>

View File

@ -17,9 +17,8 @@
const rootElement = document.getElementById("validation-pool"); const rootElement = document.getElementById("validation-pool");
const rootBox = new Box("rootBox", rootElement).flex(); const rootBox = new Box("rootBox", rootElement).flex();
const scene = (window.scene = new Scene("Validation Pool test", rootBox).log( const scene = (window.scene = new Scene("Validation Pool test", rootBox));
"sequenceDiagram" await scene.withSequenceDiagram();
));
const expert1 = (window.expert1 = await new Expert( const expert1 = (window.expert1 = await new Expert(
"Expert1", "Expert1",
scene scene
@ -31,21 +30,21 @@
const bench = (window.bench = new Bench(undefined, "Bench", scene)); const bench = (window.bench = new Bench(undefined, "Bench", scene));
const updateDisplayValues = async () => { const updateDisplayValues = async () => {
expert1.setValue( await expert1.setValue(
"rep", "rep",
bench.reputations.getTokens(expert1.reputationPublicKey) bench.reputations.getTokens(expert1.reputationPublicKey)
); );
expert2.setValue( await expert2.setValue(
"rep", "rep",
bench.reputations.getTokens(expert2.reputationPublicKey) bench.reputations.getTokens(expert2.reputationPublicKey)
); );
bench.setValue("total rep", bench.getTotalReputation()); await bench.setValue("total rep", bench.getTotalReputation());
// With params.lockingTimeExponent = 0 and params.activeVoterThreshold = null, // With params.lockingTimeExponent = 0 and params.activeVoterThreshold = null,
// these next 3 propetries are all equal to total rep // these next 3 propetries are all equal to total rep
// bench.setValue('available rep', bench.getTotalAvailableReputation()); // await bench.setValue('available rep', bench.getTotalAvailableReputation());
// bench.setValue('active rep', bench.getTotalActiveReputation()); // await bench.setValue('active rep', bench.getTotalActiveReputation());
// bench.setValue('active available rep', bench.getTotalActiveAvailableReputation()); // await bench.setValue('active available rep', bench.getTotalActiveAvailableReputation());
await scene.renderSequenceDiagram(); await scene.sequence.render();
}; };
updateDisplayValues(); updateDisplayValues();

View File

@ -1,17 +1,20 @@
const timeouts = new Map(); const timers = new Map();
export const debounce = (fn, delay) => { export const debounce = async (fn, delayMs) => {
const key = fn.toString(); const timer = timers.get(fn);
if (!timeouts.get(key)) { if (timer) {
timeouts.set(key, setTimeout(async () => { return timer.result;
timeouts.delete(key);
await fn();
}, delay));
} }
const result = await fn();
timers.set(fn, { result });
setTimeout(() => {
timers.delete(fn);
}, delayMs);
return result;
}; };
export const delay = async (ms) => { export const delay = async (delayMs) => {
await new Promise((resolve) => { await new Promise((resolve) => {
setTimeout(resolve, ms); setTimeout(resolve, delayMs);
}); });
}; };