Fixes to forum value propagation logic
This commit is contained in:
parent
3cf24c6fa1
commit
e7ff4254a3
|
@ -50,9 +50,3 @@ At a minimum this can be a list where each item includes the identifier of the c
|
||||||
Each validation pool can then keep a record of the reputation staked by each voter, and the identifier of the corresponding post.
|
Each validation pool can then keep a record of the reputation staked by each voter, and the identifier of the corresponding post.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
So: If some incoming reputation can’t be propagated due to a constraint like this, the question is what should happen with that unspent power. I think right now, I have it so when that limit is reached on leaching from PostA, the extra reputation accrues evenly (because of +0.5 citation) equally to PostX and PostZ.
|
|
||||||
|
|
||||||
Intuitively, we might want less value accruing to PostX, as it seems intended as a small utility post to transfer power.
|
|
||||||
|
|
||||||
However, we can combine these mechanisms. PostX
|
|
||||||
|
|
|
@ -5,11 +5,10 @@ export class Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
async log(src, dest, msg, obj, symbol = '->>') {
|
async log(src, dest, msg, obj, symbol = '->>') {
|
||||||
const logObj = false;
|
|
||||||
if (this.scene.sequence) {
|
if (this.scene.sequence) {
|
||||||
await this.scene.sequence.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) : ''
|
JSON.stringify(obj) ?? ''
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Action } from './action.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from './crypto.js';
|
||||||
import params from '../params.js';
|
import params from '../params.js';
|
||||||
import { ReputationHolder } from './reputation-holder.js';
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
import { displayNumber } from '../util.js';
|
import { displayNumber, EPSILON } from '../util.js';
|
||||||
|
|
||||||
const CITATION = 'citation';
|
const CITATION = 'citation';
|
||||||
const BALANCE = 'balance';
|
const BALANCE = 'balance';
|
||||||
|
@ -20,10 +20,19 @@ class Post extends Actor {
|
||||||
this.initialValue = 0;
|
this.initialValue = 0;
|
||||||
this.citations = postContent.citations;
|
this.citations = postContent.citations;
|
||||||
this.title = postContent.title;
|
this.title = postContent.title;
|
||||||
const revaluationTotal = this.citations.reduce((total, { weight }) => total += Math.abs(weight), 0);
|
const leachingTotal = this.citations
|
||||||
if (revaluationTotal > params.revaluationLimit) {
|
.filter(({ weight }) => weight < 0)
|
||||||
throw new Error('Post revaluation total exceeds revaluation limit '
|
.reduce((total, { weight }) => total += -weight, 0);
|
||||||
+ `(${revaluationTotal} > ${params.revaluationLimit})`);
|
const donationTotal = this.citations
|
||||||
|
.filter(({ weight }) => weight > 0)
|
||||||
|
.reduce((total, { weight }) => total += weight, 0);
|
||||||
|
if (leachingTotal > params.revaluationLimit) {
|
||||||
|
throw new Error('Post leaching total exceeds revaluation limit '
|
||||||
|
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
||||||
|
}
|
||||||
|
if (donationTotal > params.revaluationLimit) {
|
||||||
|
throw new Error('Post donation total exceeds revaluation limit '
|
||||||
|
+ `(${donationTotal} > ${params.revaluationLimit})`);
|
||||||
}
|
}
|
||||||
if (this.citations.some(({ weight }) => Math.abs(weight) > 1)) {
|
if (this.citations.some(({ weight }) => Math.abs(weight) > 1)) {
|
||||||
throw new Error('Each citation weight must be in the range [-1, 1]');
|
throw new Error('Each citation weight must be in the range [-1, 1]');
|
||||||
|
@ -130,11 +139,24 @@ export class Forum extends ReputationHolder {
|
||||||
* @param {Edge} edge
|
* @param {Edge} edge
|
||||||
* @param {Object} opaqueData
|
* @param {Object} opaqueData
|
||||||
*/
|
*/
|
||||||
async propagateValue(edge, { rewardsAccumulator, increment, depth = 0 }) {
|
async propagateValue(edge, {
|
||||||
|
rewardsAccumulator, increment, depth = 0, initialNegative = false,
|
||||||
|
}) {
|
||||||
const postVertex = edge.to;
|
const postVertex = edge.to;
|
||||||
const post = postVertex?.data;
|
const post = postVertex?.data;
|
||||||
|
const balanceFromInbound = this.posts.getEdgeWeight(BALANCE, edge.from, edge.to) ?? 0;
|
||||||
|
this.actions.propagateValue.log(edge.from.data, post, `(${increment}) balance (${balanceFromInbound})`);
|
||||||
|
|
||||||
this.actions.propagateValue.log(edge.from.data, post, `(${increment})`);
|
if (!!params.referenceChainLimit && depth > params.referenceChainLimit) {
|
||||||
|
this.actions.propagateValue.log(
|
||||||
|
edge.from.data,
|
||||||
|
post,
|
||||||
|
`referenceChainLimit (${params.referenceChainLimit}) reached`,
|
||||||
|
null,
|
||||||
|
'-x',
|
||||||
|
);
|
||||||
|
return increment;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('propagateValue start', {
|
console.log('propagateValue start', {
|
||||||
from: edge.from.id,
|
from: edge.from.id,
|
||||||
|
@ -142,58 +164,37 @@ export class Forum extends ReputationHolder {
|
||||||
depth,
|
depth,
|
||||||
value: post.value,
|
value: post.value,
|
||||||
increment,
|
increment,
|
||||||
|
balanceFromInbound,
|
||||||
|
initialNegative,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) {
|
let totalOutboundAmount = 0;
|
||||||
let totalOutboundAmount = 0;
|
|
||||||
let totalRefundFromOutbound = 0;
|
for (const citationEdge of postVertex.getEdges(CITATION, true)) {
|
||||||
for (const citationEdge of postVertex.getEdges(CITATION, true)) {
|
const { weight } = citationEdge;
|
||||||
const { to: citedPostVertex, weight } = citationEdge;
|
let outboundAmount = weight * increment;
|
||||||
let outboundAmount = weight * increment;
|
const balanceToOutbound = this.posts.getEdgeWeight(BALANCE, citationEdge.from, citationEdge.to) ?? 0;
|
||||||
const balanceEdge = this.posts.getEdge(BALANCE, postVertex, citedPostVertex);
|
// We need to ensure that we propagate no more reputation than we leached
|
||||||
const balance = balanceEdge?.weight ?? 0;
|
if (initialNegative) {
|
||||||
// We need to ensure that we propagate no more reputation than we leached
|
outboundAmount = outboundAmount < 0
|
||||||
if (depth > 0 && weight < 0) {
|
? Math.max(outboundAmount, -balanceToOutbound)
|
||||||
outboundAmount = outboundAmount < 0
|
: Math.min(outboundAmount, -balanceToOutbound);
|
||||||
? Math.max(outboundAmount, -balance)
|
}
|
||||||
: Math.min(outboundAmount, -balance);
|
if (Math.abs(outboundAmount) > EPSILON) {
|
||||||
}
|
|
||||||
const refundFromOutbound = await this.propagateValue(citationEdge, {
|
const refundFromOutbound = await this.propagateValue(citationEdge, {
|
||||||
rewardsAccumulator,
|
rewardsAccumulator,
|
||||||
increment: outboundAmount,
|
increment: outboundAmount,
|
||||||
depth: depth + 1,
|
depth: depth + 1,
|
||||||
|
initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
|
||||||
});
|
});
|
||||||
totalRefundFromOutbound += Math.abs(refundFromOutbound);
|
|
||||||
outboundAmount -= refundFromOutbound;
|
outboundAmount -= refundFromOutbound;
|
||||||
this.posts.setEdgeWeight(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
|
this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount);
|
||||||
|
|
||||||
totalOutboundAmount += outboundAmount;
|
totalOutboundAmount += outboundAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we know what value could not be propagated as negative citations,
|
|
||||||
// Let's see what happens if we redistribute it among the positive citations :)
|
|
||||||
|
|
||||||
const positiveCitations = postVertex.getEdges(CITATION, true).filter(({ weight }) => weight > 0);
|
|
||||||
const totalPositiveCitationWeight = positiveCitations.reduce((total, { weight }) => total += weight, 0);
|
|
||||||
|
|
||||||
for (const citationEdge of positiveCitations) {
|
|
||||||
const { to: citedPostVertex, weight } = citationEdge;
|
|
||||||
const outboundAmount = totalRefundFromOutbound * (weight / totalPositiveCitationWeight);
|
|
||||||
const balance = this.posts.getEdgeWeight(BALANCE, postVertex, citedPostVertex) ?? 0;
|
|
||||||
await this.propagateValue(citationEdge, {
|
|
||||||
rewardsAccumulator,
|
|
||||||
increment: outboundAmount,
|
|
||||||
depth: depth + 1,
|
|
||||||
});
|
|
||||||
this.posts.setEdgeWeight(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
|
|
||||||
totalOutboundAmount += outboundAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And what if they don't have any positive citations? The value should just accrue to this post.
|
|
||||||
|
|
||||||
increment -= totalOutboundAmount * params.leachingValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
increment -= totalOutboundAmount * params.leachingValue;
|
||||||
|
|
||||||
const rawNewValue = post.value + increment;
|
const rawNewValue = post.value + increment;
|
||||||
const newValue = Math.max(0, rawNewValue);
|
const newValue = Math.max(0, rawNewValue);
|
||||||
const appliedIncrement = newValue - post.value;
|
const appliedIncrement = newValue - post.value;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ERC721 } from './erc721.js';
|
import { ERC721 } from './erc721.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from './crypto.js';
|
||||||
|
|
||||||
const EPSILON = 2.23e-16;
|
import { EPSILON } from '../util.js';
|
||||||
|
|
||||||
class Lock {
|
class Lock {
|
||||||
constructor(tokenId, amount, duration) {
|
constructor(tokenId, amount, duration) {
|
||||||
|
|
|
@ -135,7 +135,6 @@ export class Scene {
|
||||||
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
||||||
this.flowchart = new MermaidDiagram(box, logBox);
|
this.flowchart = new MermaidDiagram(box, logBox);
|
||||||
this.flowchart.log(`graph ${direction}`, false);
|
this.flowchart.log(`graph ${direction}`, false);
|
||||||
this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +147,6 @@ export class Scene {
|
||||||
const logBox = container.addBox('Flowchart text').addClass('dim');
|
const logBox = container.addBox('Flowchart text').addClass('dim');
|
||||||
const flowchart = new MermaidDiagram(box, logBox);
|
const flowchart = new MermaidDiagram(box, logBox);
|
||||||
flowchart.log(`graph ${direction}`, false);
|
flowchart.log(`graph ${direction}`, false);
|
||||||
this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false);
|
|
||||||
this.flowcharts.set(id, flowchart);
|
this.flowcharts.set(id, flowchart);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,34 +70,25 @@ export class WDAG {
|
||||||
return Array.from(this.vertices.values()).map(({ data }) => data);
|
return Array.from(this.vertices.values()).map(({ data }) => data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdgeLabel(label) {
|
static getEdgeKey({ from, to }) {
|
||||||
return this.edgeLabels.get(label);
|
return btoa([from.id, to.id]).replaceAll(/[^A-Z]+/g, '');
|
||||||
}
|
|
||||||
|
|
||||||
getOrCreateEdgeLabel(label) {
|
|
||||||
let edges = this.getEdgeLabel(label);
|
|
||||||
if (!edges) {
|
|
||||||
edges = new Map();
|
|
||||||
this.edgeLabels.set(label, edges);
|
|
||||||
}
|
|
||||||
return edges;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdge(label, from, to) {
|
getEdge(label, from, to) {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
|
if (!from || !to) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
const edges = this.edgeLabels.get(label);
|
const edges = this.edgeLabels.get(label);
|
||||||
return edges?.get(JSON.stringify([from.id, to.id]));
|
const edgeKey = WDAG.getEdgeKey({ from, to });
|
||||||
|
return edges?.get(edgeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdgeWeight(label, from, to) {
|
getEdgeWeight(label, from, to) {
|
||||||
return this.getEdge(label, from, to)?.weight;
|
return this.getEdge(label, from, to)?.weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getEdgeKey({ from, to }) {
|
|
||||||
return btoa([from.id, to.id]).replaceAll(/[^A-Z]+/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
getEdgeHtml({ from, to }) {
|
getEdgeHtml({ from, to }) {
|
||||||
let html = '<table>';
|
let html = '<table>';
|
||||||
for (const { label, weight } of this.getEdges(null, from, to)) {
|
for (const { label, weight } of this.getEdges(null, from, to)) {
|
||||||
|
@ -109,7 +100,7 @@ export class WDAG {
|
||||||
|
|
||||||
getEdgeFlowchartNode(edge) {
|
getEdgeFlowchartNode(edge) {
|
||||||
const edgeKey = WDAG.getEdgeKey(edge);
|
const edgeKey = WDAG.getEdgeKey(edge);
|
||||||
return `${edgeKey}(${this.getEdgeHtml(edge)}):::edge`;
|
return `${edgeKey}(${this.getEdgeHtml(edge)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setEdgeWeight(label, from, to, weight) {
|
setEdgeWeight(label, from, to, weight) {
|
||||||
|
@ -137,6 +128,7 @@ export class WDAG {
|
||||||
from.edges.from.push(edge);
|
from.edges.from.push(edge);
|
||||||
to.edges.to.push(edge);
|
to.edges.to.push(edge);
|
||||||
this.flowchart?.log(`${from.id} --- ${this.getEdgeFlowchartNode(edge)} --> ${to.id}`);
|
this.flowchart?.log(`${from.id} --- ${this.getEdgeFlowchartNode(edge)} --> ${to.id}`);
|
||||||
|
this.flowchart?.log(`class ${WDAG.getEdgeKey(edge)} edge`);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,3 +45,6 @@ th {
|
||||||
td {
|
td {
|
||||||
background-color: #0c2025;
|
background-color: #0c2025;
|
||||||
}
|
}
|
||||||
|
.edge > rect {
|
||||||
|
fill: #216262 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<li>
|
<li>
|
||||||
Forum
|
Forum
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="./tests/forum1.test.html">Negative citation of a negative citation with max strength</a></li>
|
<li><a href="./tests/forum1.test.html">Negative citation of a negative citation</a></li>
|
||||||
<li><a href="./tests/forum2.test.html">Negative citation of a weaker negative citation</a></li>
|
<li><a href="./tests/forum2.test.html">Negative citation of a weaker negative citation</a></li>
|
||||||
<li><a href="./tests/forum3.test.html">Redistribute power</a></li>
|
<li><a href="./tests/forum3.test.html">Redistribute power</a></li>
|
||||||
<li><a href="./tests/forum4.test.html">Redistribute power through subsequent support</a></li>
|
<li><a href="./tests/forum4.test.html">Redistribute power through subsequent support</a></li>
|
||||||
|
|
|
@ -7,16 +7,18 @@ import { Forum } from '../../classes/forum.js';
|
||||||
import { PostContent } from '../../classes/post-content.js';
|
import { PostContent } from '../../classes/post-content.js';
|
||||||
import params from '../../params.js';
|
import params from '../../params.js';
|
||||||
|
|
||||||
const DEFAULT_DELAY_MS = 1;
|
|
||||||
const POOL_DURATION_MS = 50;
|
|
||||||
|
|
||||||
export class ForumTest {
|
export class ForumTest {
|
||||||
constructor() {
|
constructor(options) {
|
||||||
this.scene = null;
|
this.scene = null;
|
||||||
this.forum = null;
|
this.forum = null;
|
||||||
this.bench = null;
|
this.bench = null;
|
||||||
this.experts = null;
|
this.experts = null;
|
||||||
this.posts = null;
|
this.posts = null;
|
||||||
|
this.options = {
|
||||||
|
defaultDelayMs: 1,
|
||||||
|
poolDurationMs: 50,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async newExpert() {
|
async newExpert() {
|
||||||
|
@ -44,15 +46,15 @@ export class ForumTest {
|
||||||
postContent,
|
postContent,
|
||||||
{
|
{
|
||||||
fee,
|
fee,
|
||||||
duration: POOL_DURATION_MS,
|
duration: this.options.poolDurationMs,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
this.posts.push(postId);
|
this.posts.push(postId);
|
||||||
await delay(POOL_DURATION_MS);
|
await delay(this.options.poolDurationMs);
|
||||||
await pool.evaluateWinningConditions();
|
await pool.evaluateWinningConditions();
|
||||||
await this.scene.endSection();
|
await this.scene.endSection();
|
||||||
await delay(DEFAULT_DELAY_MS);
|
await delay(this.options.defaultDelayMs);
|
||||||
return postId;
|
return postId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe('Forum', () => {
|
||||||
await forumTest.setup();
|
await forumTest.setup();
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Negative citation of a negative citation with max strength', async () => {
|
context('Negative citation of a negative citation', async () => {
|
||||||
it('Post1', async () => {
|
it('Post1', async () => {
|
||||||
const { forum, experts, posts } = forumTest;
|
const { forum, experts, posts } = forumTest;
|
||||||
await forumTest.addPost(experts[0], 10);
|
await forumTest.addPost(experts[0], 10);
|
||||||
|
|
|
@ -23,13 +23,13 @@ describe('Forum', () => {
|
||||||
|
|
||||||
it('Post3 cites Post2 and negatively cites Post1', async () => {
|
it('Post3 cites Post2 and negatively cites Post1', async () => {
|
||||||
const { forum, experts, posts } = forumTest;
|
const { forum, experts, posts } = forumTest;
|
||||||
await forumTest.addPost(experts[0], 20, [
|
await forumTest.addPost(experts[0], 10, [
|
||||||
{ postId: posts[0], weight: -0.5 },
|
{ postId: posts[0], weight: -1 },
|
||||||
{ postId: posts[1], weight: 0.5 },
|
{ postId: posts[1], weight: 1 },
|
||||||
]);
|
]);
|
||||||
forum.getPost(posts[0]).value.should.equal(0);
|
forum.getPost(posts[0]).value.should.equal(0);
|
||||||
forum.getPost(posts[1]).value.should.equal(20);
|
forum.getPost(posts[1]).value.should.equal(20);
|
||||||
forum.getPost(posts[2]).value.should.equal(20);
|
forum.getPost(posts[2]).value.should.equal(10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,8 +31,8 @@ describe('Forum', () => {
|
||||||
|
|
||||||
it('Post3 cites Post2 and negatively cites Post1', async () => {
|
it('Post3 cites Post2 and negatively cites Post1', async () => {
|
||||||
await forumTest.addPost(experts[0], 0, [
|
await forumTest.addPost(experts[0], 0, [
|
||||||
{ postId: posts[0], weight: -0.5 },
|
{ postId: posts[0], weight: -1 },
|
||||||
{ postId: posts[1], weight: 0.5 },
|
{ postId: posts[1], weight: 1 },
|
||||||
]);
|
]);
|
||||||
forum.getPost(posts[0]).value.should.equal(10);
|
forum.getPost(posts[0]).value.should.equal(10);
|
||||||
forum.getPost(posts[1]).value.should.equal(10);
|
forum.getPost(posts[1]).value.should.equal(10);
|
||||||
|
@ -40,23 +40,23 @@ describe('Forum', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Post4 cites Post3 to strengthen its effect', async () => {
|
it('Post4 cites Post3 to strengthen its effect', async () => {
|
||||||
await forumTest.addPost(experts[0], 20, [
|
await forumTest.addPost(experts[0], 10, [
|
||||||
{ postId: posts[2], weight: 1 },
|
{ postId: posts[2], weight: 1 },
|
||||||
]);
|
]);
|
||||||
forum.getPost(posts[0]).value.should.equal(0);
|
forum.getPost(posts[0]).value.should.equal(0);
|
||||||
forum.getPost(posts[1]).value.should.equal(20);
|
forum.getPost(posts[1]).value.should.equal(20);
|
||||||
forum.getPost(posts[2]).value.should.equal(20);
|
forum.getPost(posts[2]).value.should.equal(10);
|
||||||
forum.getPost(posts[3]).value.should.equal(0);
|
forum.getPost(posts[3]).value.should.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Post5 cites Post3 to strengthen its effect', async () => {
|
it('Post5 cites Post3 to strengthen its effect', async () => {
|
||||||
await forumTest.addPost(experts[0], 20, [
|
await forumTest.addPost(experts[0], 10, [
|
||||||
{ postId: posts[2], weight: 1 },
|
{ postId: posts[2], weight: 1 },
|
||||||
]);
|
]);
|
||||||
console.log('test5', { posts });
|
console.log('test5', { posts });
|
||||||
forum.getPost(posts[0]).value.should.equal(0);
|
forum.getPost(posts[0]).value.should.equal(0);
|
||||||
forum.getPost(posts[1]).value.should.equal(20);
|
forum.getPost(posts[1]).value.should.equal(30);
|
||||||
forum.getPost(posts[2]).value.should.equal(20);
|
forum.getPost(posts[2]).value.should.equal(10);
|
||||||
forum.getPost(posts[3]).value.should.equal(0);
|
forum.getPost(posts[3]).value.should.equal(0);
|
||||||
forum.getPost(posts[4]).value.should.equal(0);
|
forum.getPost(posts[4]).value.should.equal(0);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
const timers = new Map();
|
const timers = new Map();
|
||||||
|
|
||||||
|
export const EPSILON = 2.23e-16;
|
||||||
|
|
||||||
export const debounce = async (fn, delayMs) => {
|
export const debounce = async (fn, delayMs) => {
|
||||||
const timer = timers.get(fn);
|
const timer = timers.get(fn);
|
||||||
if (timer) {
|
if (timer) {
|
||||||
|
|
Loading…
Reference in New Issue