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;
}
log(src, dest, msg, obj, symbol = '->>') {
async log(src, dest, msg, obj, symbol = '->>') {
const logObj = false;
this.scene.log(
await this.scene.sequence.log(
`${src.name} ${symbol} ${dest.name} : ${this.name} ${msg ?? ''} ${
logObj && obj ? JSON.stringify(obj) : ''
}`,

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ export class Business extends Actor {
async submitRequest(fee, content) {
const request = new Request(fee, content);
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);
return request.id;
}
@ -54,13 +54,13 @@ export class Business extends Actor {
requestId,
workEvidence,
});
this.actions.submitPost.log(this, this.forum);
await this.actions.submitPost.log(this, this.forum);
const postId = await this.forum.addPost(reputationPublicKey, post);
// Initiate a validation pool for this work evidence.
// Validation pool supports secret ballots but we aren't using that here, since we want
// 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({
postId,
fee: request.fee,

View File

@ -1,5 +1,4 @@
import { Actor } from './actor.js';
import { Action } from './action.js';
import { PostMessage } from './message.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 = this.name;
this.status.set('Initialized');
this.activate();
return this;
}
@ -33,19 +31,19 @@ export class Expert extends Actor {
// TODO: Include fee
const postMessage = new PostMessage({ post, stake });
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();
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
}
async submitPost(forum, postContent) {
// TODO: Include fee
this.actions.submitPost.log(this, forum);
await this.actions.submitPost.log(this, forum);
return forum.addPost(this.reputationPublicKey, postContent);
}
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 pool = await this.initiateValidationPool(bench, { ...poolOptions, postId, anonymous: false });
return { postId, pool };
@ -59,12 +57,12 @@ export class Expert extends Actor {
} else {
poolOptions.signingPublicKey = this.reputationPublicKey;
}
this.actions.initiateValidationPool.log(
await this.actions.initiateValidationPool.log(
this,
bench,
`(fee: ${poolOptions.fee})`,
);
const pool = bench.initiateValidationPool(poolOptions);
const pool = await bench.initiateValidationPool(poolOptions);
this.validationPools.set(pool.id, poolOptions);
return pool;
}
@ -82,7 +80,7 @@ export class Expert extends Actor {
}
// TODO: encrypt vote
// TODO: sign message
this.actions.castVote.log(
await this.actions.castVote.log(
this,
validationPool,
`(${position ? 'for' : 'against'}, stake: ${stake}, anonymous: ${anonymous})`,
@ -95,12 +93,12 @@ export class Expert extends Actor {
async revealIdentity(validationPool) {
const { signingPublicKey } = this.validationPools.get(validationPool.id);
// TODO: sign message
this.actions.revealIdentity.log(this, validationPool);
await this.actions.revealIdentity.log(this, validationPool);
validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey);
}
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);
}
@ -111,7 +109,7 @@ export class Expert extends Actor {
}
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 });
}
}

View File

@ -34,7 +34,7 @@ export class ForumNode extends Actor {
.filter((forumNode) => forumNode.keyPair.publicKey !== this.keyPair.publicKey);
for (const forumNode of otherForumNodes) {
// 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()));
}
}
@ -61,7 +61,7 @@ export class ForumNode extends Actor {
try {
await Message.verify(messageJson);
} 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`);
return;
}
@ -87,7 +87,7 @@ export class ForumNode extends Actor {
if (!post.id) {
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);
}

View File

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

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 {
constructor(data) {
this.data = data;
@ -29,13 +26,14 @@ export class Edge {
export class CategorizedEdges {}
export class Graph {
constructor() {
constructor(scene) {
this.scene = scene;
this.vertices = new Map();
this.edgeLabels = new Map();
this.nextVertexId = 0;
}
addVertex(id, data) {
addVertex(id, data, label) {
// Support simple case of auto-incremented numeric ids
if (typeof id === 'object') {
data = id;
@ -46,6 +44,9 @@ export class Graph {
}
const vertex = new Vertex(data);
this.vertices.set(id, vertex);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${id}[${label ?? id}]`);
}
return this;
}
@ -83,6 +84,9 @@ export class Graph {
this.setEdge(label, from, to, edge);
this.getVertex(from).edges.from.push(edge);
this.getVertex(to).edges.to.push(edge);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${from} --> ${to}`);
}
return this;
}
@ -101,20 +105,4 @@ export class Graph {
countVertices() {
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;
}
setTitle(title) {
this.title = title;
return this;
}
toJSON() {
return {
content: this.content,
citations: this.citations.map((citation) => citation.toJSON()),
...(this.id ? { id: this.id } : {}),
title: this.title,
};
}
static fromJSON({ id, content, citations }) {
static fromJSON({
id, content, citations, title,
}) {
const post = new PostContent(content);
post.citations = citations.map((citation) => Citation.fromJSON(citation));
post.id = id;
post.title = title;
return post;
}
}

View File

@ -3,24 +3,47 @@ import { Actor } from './actor.js';
import { Action } from './action.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 {
constructor(name, rootBox) {
this.name = 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.displayValuesBox = this.box.addBox(`${this.name}-values`);
this.displayValuesBox = this.box.addBox('Values');
this.box.addBox('Spacer').setInnerHTML(' ');
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({
startOnLoad: false,
theme: 'base',
@ -28,18 +51,37 @@ export class Scene {
darkMode: true,
primaryColor: '#2a5b6c',
primaryTextColor: '#b6b6b6',
// lineColor: '#349cbd',
lineColor: '#57747d',
signalColor: '#57747d',
// signalColor: '#349cbd',
noteBkgColor: '#516f77',
noteTextColor: '#cecece',
activationBkgColor: '#1d3f49',
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);
if (this.sequence) {
await this.scene.sequence.log(`participant ${name}`);
}
return actor;
}
@ -57,12 +99,6 @@ export class Scene {
return dv;
}
log(msg) {
this.logBox.addBox().setInnerHTML(msg).monospace();
this.renderSequenceDiagram();
return this;
}
deactivateAll() {
for (const actor of this.actors.values()) {
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) {
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.distributeTokens(result);
await this.distributeTokens(result);
} else {
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();
@ -206,7 +206,7 @@ export class ValidationPool extends Actor {
return result;
}
distributeTokens({ votePasses }) {
async distributeTokens({ votePasses }) {
const rewards = new Map();
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) {
// 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) {
addReward(id, value);
}

View File

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

View File

@ -56,13 +56,13 @@
const updateDisplayValues = async () => {
for (const expert of experts) {
expert.setValue(
await expert.setValue(
"rep",
bench.reputations.getTokens(expert.reputationPublicKey)
);
}
bench.setValue("total rep", bench.getTotalReputation());
await scene.renderSequenceDiagram();
await bench.setValue("total rep", bench.getTotalReputation());
await scene.sequence.render();
};
const updateDisplayValuesAndDelay = async () => {
@ -77,7 +77,7 @@
request = await expert.getAssignedWork(availability, business);
if (request) {
worker = expert;
worker.actions.getAssignedWork.log(worker, availability);
await worker.actions.getAssignedWork.log(worker, availability);
worker.activate();
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.forumNode3.processNextMessage();
await window.scene.renderSequenceDiagram();
await window.scene.sequence.render();
}, 100);
// const blockchain = new Blockchain();

View File

@ -23,9 +23,9 @@
const rootElement = document.getElementById("forum-test");
const rootBox = new Box("rootBox", rootElement).flex();
const scene = (window.scene = new Scene("Forum test", rootBox).log(
"sequenceDiagram"
));
const scene = (window.scene = new Scene("Forum test", rootBox));
scene.withSequenceDiagram();
scene.withFlowchart();
const experts = (window.experts = []);
const newExpert = async () => {
@ -44,13 +44,12 @@
const updateDisplayValues = async () => {
for (const expert of experts) {
expert.setValue(
await expert.setValue(
"rep",
bench.reputations.getTokens(expert.reputationPublicKey)
);
}
bench.setValue("total rep", bench.getTotalReputation());
await scene.renderSequenceDiagram();
await bench.setValue("total rep", bench.getTotalReputation());
};
const updateDisplayValuesAndDelay = async (delayMs) => {
@ -63,7 +62,7 @@
const { postId: postId1, pool: pool1 } = await expert1.submitPostWithFee(
bench,
forum,
new PostContent({ hello: "there" }),
new PostContent({ hello: "there" }).setTitle("Post 1"),
{
fee: 10,
duration: 1000,
@ -81,7 +80,9 @@
const { postId: postId2, pool: pool2 } = await expert2.submitPostWithFee(
bench,
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,
duration: 1000,
@ -99,7 +100,9 @@
const { pool: pool3 } = await expert3.submitPostWithFee(
bench,
forum,
new PostContent({ hello: "y'all" }).addCitation(postId2, 0.5),
new PostContent({ hello: "y'all" })
.setTitle("Post 3")
.addCitation(postId2, 0.5),
{
fee: 10,
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 rootBox = new Box("rootBox", rootElement).flex();
const scene = (window.scene = new Scene("Validation Pool test", rootBox).log(
"sequenceDiagram"
));
const scene = (window.scene = new Scene("Validation Pool test", rootBox));
await scene.withSequenceDiagram();
const expert1 = (window.expert1 = await new Expert(
"Expert1",
scene
@ -31,21 +30,21 @@
const bench = (window.bench = new Bench(undefined, "Bench", scene));
const updateDisplayValues = async () => {
expert1.setValue(
await expert1.setValue(
"rep",
bench.reputations.getTokens(expert1.reputationPublicKey)
);
expert2.setValue(
await expert2.setValue(
"rep",
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,
// these next 3 propetries are all equal to total rep
// bench.setValue('available rep', bench.getTotalAvailableReputation());
// bench.setValue('active rep', bench.getTotalActiveReputation());
// bench.setValue('active available rep', bench.getTotalActiveAvailableReputation());
await scene.renderSequenceDiagram();
// await bench.setValue('available rep', bench.getTotalAvailableReputation());
// await bench.setValue('active rep', bench.getTotalActiveReputation());
// await bench.setValue('active available rep', bench.getTotalActiveAvailableReputation());
await scene.sequence.render();
};
updateDisplayValues();

View File

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