From 27c6778445473dedef08331260f1db3f2bea75ef Mon Sep 17 00:00:00 2001 From: Scott Worley Date: Wed, 26 Jan 2022 22:33:53 -0800 Subject: [PATCH 1/1] Use a monotonic clock Generally assume that clock values always increase to simplify reasoning everywhere clock values are used. Then enforce that assumption. Clock values don't really, truly always go up because page reloads & multiple devices, but since I'm not currently planning to spend the effort that would be required to perfectly handle this in all cases, making it happen in fewer cases is a win (rather than just making bugs I want to fix harder to reproduce). --- vopamoi.ts | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/vopamoi.ts b/vopamoi.ts index 04c5d41..271354f 100644 --- a/vopamoi.ts +++ b/vopamoi.ts @@ -13,6 +13,22 @@ function splitN(str: string, delimiter: string, limit: number = MAX_SAFE_INTEGER return at === -1 ? [str] : [str.substring(0, at)].concat(splitN(str.substring(at + delimiter.length), delimiter, limit - 1)); } +// A clock that never goes backwards; monotonic. +function Clock() { + var previousNow = Date.now(); + return { + now: function (): number { + const now = Date.now(); + if (now > previousNow) { + previousNow = now; + return now; + } + return ++previousNow; + }, + }; +} +const clock = Clock(); + const Model = { addTask: function (timestamp: string, description: string): Element { const task = document.createElement("div"); @@ -143,25 +159,25 @@ const undoLog: string[] = []; const UI = { addTask: function (description: string): Element { - const now = Date.now(); + const now = clock.now(); undoLog.push(`State ${now} deleted`); return log.recordAndApply(`${now} Create ${description}`); }, edit: function (createTimestamp: string, newDescription: string, oldDescription: string) { undoLog.push(`Edit ${createTimestamp} ${oldDescription}`); - return log.recordAndApply(`${Date.now()} Edit ${createTimestamp} ${newDescription}`); + return log.recordAndApply(`${clock.now()} Edit ${createTimestamp} ${newDescription}`); }, setPriority: function (createTimestamp: string, newPriority: number, oldPriority: number) { undoLog.push(`Priority ${createTimestamp} ${oldPriority}`); - return log.recordAndApply(`${Date.now()} Priority ${createTimestamp} ${newPriority}`); + return log.recordAndApply(`${clock.now()} Priority ${createTimestamp} ${newPriority}`); }, setState: function (createTimestamp: string, newState: string, oldState: string) { undoLog.push(`State ${createTimestamp} ${oldState}`); - return log.recordAndApply(`${Date.now()} State ${createTimestamp} ${newState}`); + return log.recordAndApply(`${clock.now()} State ${createTimestamp} ${newState}`); }, undo: function () { if (undoLog.length > 0) { - return log.recordAndApply(`${Date.now()} ${undoLog.pop()}`); + return log.recordAndApply(`${clock.now()} ${undoLog.pop()}`); } }, }; @@ -267,7 +283,7 @@ const BrowserUI = { // Change task's priority to be between other tasks a and b. setPriority: function (task: Element, a: Element | null, b: Element | null) { const aPriority = a === null ? 0 : Model.getPriority(a); - const bPriority = b === null ? Date.now() : Model.getPriority(b); + const bPriority = b === null ? clock.now() : Model.getPriority(b); console.assert(aPriority < bPriority, aPriority, "<", bPriority); const span = bPriority - aPriority; const newPriority = aPriority + 0.1 * span + 0.8 * span * Math.random(); -- 2.44.1