From: Scott Worley Date: Thu, 27 Jan 2022 06:33:53 +0000 (-0800) Subject: Use a monotonic clock X-Git-Url: http://git.scottworley.com/vopamoi/commitdiff_plain/27c6778445473dedef08331260f1db3f2bea75ef?ds=sidebyside;hp=8ca3cda9da4db4ac0bd05462b54e04fabbf75451 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). --- 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();