@hydranium/protocol 1.0.0-next.270 → 1.0.0-next.272
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/clock.d.ts +12 -10
- package/lib/clock.d.ts.map +1 -1
- package/lib/clock.js.map +1 -1
- package/lib/debouncer.d.ts +3 -3
- package/lib/debouncer.d.ts.map +1 -1
- package/lib/debouncer.js +6 -9
- package/lib/debouncer.js.map +1 -1
- package/lib/testing/fake-clock.d.ts +8 -1
- package/lib/testing/fake-clock.d.ts.map +1 -1
- package/lib/testing/fake-clock.js +8 -2
- package/lib/testing/fake-clock.js.map +1 -1
- package/package.json +1 -1
- package/src/clock.ts +12 -10
- package/src/debouncer.ts +7 -10
- package/src/testing/fake-clock.ts +15 -2
package/lib/clock.d.ts
CHANGED
|
@@ -12,9 +12,11 @@ import { Disposable } from './util';
|
|
|
12
12
|
* there is no `start` — you create it at the moment timing begins.
|
|
13
13
|
*
|
|
14
14
|
* Backed by a monotonic source (`performance.now`), so it is sub-millisecond
|
|
15
|
-
* precise and immune to wall-clock
|
|
16
|
-
* `Date.now` delta go backwards; a stopwatch cannot).
|
|
17
|
-
*
|
|
15
|
+
* precise and immune to wall-clock steps (a time sync or a manual change can
|
|
16
|
+
* make a `Date.now` delta go backwards; a stopwatch cannot). In Node on Linux
|
|
17
|
+
* it also leaves out time the system spends suspended, so a duration spanning
|
|
18
|
+
* a suspend reads as the awake time only. Use this for every duration, a TTL
|
|
19
|
+
* included, and {@link Clock.now} for "what time is it".
|
|
18
20
|
*
|
|
19
21
|
* It holds no scheduled resource and is therefore NOT a {@link Disposable} —
|
|
20
22
|
* the disposable thing is a {@link Clock.setTimer}. Reads are lazy, so a fake
|
|
@@ -42,16 +44,16 @@ export interface Stopwatch {
|
|
|
42
44
|
* testable instead of waiting on real wall-clock. The {@link SystemClock}
|
|
43
45
|
* default delegates to the platform; tests bind `makeFakeClock`.
|
|
44
46
|
*
|
|
45
|
-
* Two distinct readings on purpose: {@link now} is wall-clock
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
47
|
+
* Two distinct readings on purpose: {@link now} is wall-clock, for a
|
|
48
|
+
* timestamp, while {@link stopwatch} measures durations off a monotonic
|
|
49
|
+
* source. They are different clocks; do not subtract two {@link now} readings
|
|
50
|
+
* to measure a duration. Framework code reads time only through a `Clock`, so
|
|
51
|
+
* a platform workaround lives in one place.
|
|
49
52
|
*
|
|
50
53
|
* A cross-head contract, so it carries no platform or DI-framework coupling.
|
|
51
54
|
*
|
|
52
|
-
* Note:
|
|
53
|
-
*
|
|
54
|
-
* measure would invert their meaning.
|
|
55
|
+
* Note: code that measures real CPU, heap or event-loop time never takes the
|
|
56
|
+
* bound `Clock`: faking the time it measures would invert its meaning.
|
|
55
57
|
*/
|
|
56
58
|
export interface Clock {
|
|
57
59
|
/** Current wall-clock time in milliseconds (the `Date.now` reading). */
|
package/lib/clock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC
|
|
1
|
+
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,SAAS;IACvB,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,GAAG,IAAI,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,IAAI,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,KAAK;IACnB,wEAAwE;IACxE,GAAG,IAAI,MAAM,CAAC;IACd;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAAC;IACvD,yDAAyD;IACzD,SAAS,IAAI,SAAS,CAAC;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACxC;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,SAAS,CAAC,CAAC;CAC/E;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,eAA6C,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,KAAK,CAAC,CAAC;IACrB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,CAAC;IACV;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CACpB;AAyCD;;;;;;;;GAQG;AACH,qBAAa,WAAY,YAAW,KAAK;IACtC,GAAG,IAAI,MAAM,CAEZ;IAED,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAIrD;IAED,SAAS,IAAI,SAAS,CAErB;IAED,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAUxC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,SAAS,CAAC,CAgB3E;CACH"}
|
package/lib/clock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAElF,iCAAoC;
|
|
1
|
+
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"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"}
|
package/lib/debouncer.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
|
-
import { type Clock } from './clock';
|
|
9
|
+
import { type Clock, type Stopwatch } from './clock';
|
|
10
10
|
import { ObservableValue, type MaybeObservableValue } from './observable-value';
|
|
11
11
|
import { type Disposable } from './util';
|
|
12
12
|
/** Construction options for {@link Debouncer}. */
|
|
@@ -52,8 +52,8 @@ export declare class Debouncer implements Disposable {
|
|
|
52
52
|
protected readonly delay: ObservableValue<number>;
|
|
53
53
|
protected readonly maxWaitMs?: number;
|
|
54
54
|
protected timer?: Disposable;
|
|
55
|
-
/**
|
|
56
|
-
protected
|
|
55
|
+
/** Running since the first {@link schedule} of the current pending window; drives {@link DebouncerOptions.maxWaitMs}. */
|
|
56
|
+
protected sinceFirstSchedule?: Stopwatch;
|
|
57
57
|
constructor(clock: Clock, fn: () => void, options: DebouncerOptions);
|
|
58
58
|
/** True while a fire is armed (scheduled, not yet fired or cancelled). */
|
|
59
59
|
get pending(): boolean;
|
package/lib/debouncer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debouncer.d.ts","sourceRoot":"","sources":["../src/debouncer.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"debouncer.d.ts","sourceRoot":"","sources":["../src/debouncer.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEzC,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC/C;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAU,YAAW,UAAU;IAQtC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK;IAC/B,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI;IARpC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAClD,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC7B,yHAAyH;IACzH,SAAS,CAAC,kBAAkB,CAAC,EAAE,SAAS,CAAC;IAEzC,YACsB,KAAK,EAAE,KAAK,EACZ,EAAE,EAAE,MAAM,IAAI,EACjC,OAAO,EAAE,gBAAgB,EAI3B;IAED,0EAA0E;IAC1E,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;OAKG;IACH,QAAQ,IAAI,IAAI,CASf;IAED,wFAAwF;IACxF,KAAK,IAAI,IAAI,CAIZ;IAED,sDAAsD;IACtD,MAAM,IAAI,IAAI,CAIb;IAED,2CAA2C;IAC3C,OAAO,IAAI,IAAI,CAEd;IAED,SAAS,CAAC,IAAI,IAAI,IAAI,CAKrB;CACH"}
|
package/lib/debouncer.js
CHANGED
|
@@ -33,8 +33,8 @@ class Debouncer {
|
|
|
33
33
|
delay;
|
|
34
34
|
maxWaitMs;
|
|
35
35
|
timer;
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/** Running since the first {@link schedule} of the current pending window; drives {@link DebouncerOptions.maxWaitMs}. */
|
|
37
|
+
sinceFirstSchedule;
|
|
38
38
|
constructor(clock, fn, options) {
|
|
39
39
|
this.clock = clock;
|
|
40
40
|
this.fn = fn;
|
|
@@ -52,13 +52,10 @@ class Debouncer {
|
|
|
52
52
|
* elapses since the first pending call.
|
|
53
53
|
*/
|
|
54
54
|
schedule() {
|
|
55
|
-
|
|
56
|
-
if (this.firstScheduledAt === undefined) {
|
|
57
|
-
this.firstScheduledAt = now;
|
|
58
|
-
}
|
|
55
|
+
this.sinceFirstSchedule ??= this.clock.stopwatch();
|
|
59
56
|
let wait = Math.max(0, this.delay.value);
|
|
60
57
|
if (this.maxWaitMs !== undefined) {
|
|
61
|
-
const untilCeiling = this.
|
|
58
|
+
const untilCeiling = this.maxWaitMs - this.sinceFirstSchedule.elapsedMs;
|
|
62
59
|
wait = Math.min(wait, Math.max(0, untilCeiling));
|
|
63
60
|
}
|
|
64
61
|
this.timer?.dispose();
|
|
@@ -74,7 +71,7 @@ class Debouncer {
|
|
|
74
71
|
cancel() {
|
|
75
72
|
this.timer?.dispose();
|
|
76
73
|
this.timer = undefined;
|
|
77
|
-
this.
|
|
74
|
+
this.sinceFirstSchedule = undefined;
|
|
78
75
|
}
|
|
79
76
|
/** Cancel any pending fire and release. */
|
|
80
77
|
dispose() {
|
|
@@ -83,7 +80,7 @@ class Debouncer {
|
|
|
83
80
|
fire() {
|
|
84
81
|
this.timer?.dispose();
|
|
85
82
|
this.timer = undefined;
|
|
86
|
-
this.
|
|
83
|
+
this.sinceFirstSchedule = undefined;
|
|
87
84
|
this.fn();
|
|
88
85
|
}
|
|
89
86
|
}
|
package/lib/debouncer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debouncer.js","sourceRoot":"","sources":["../src/debouncer.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAGlF,yDAAgF;AAwBhF;;;;;;;;;;;;;;;;GAgBG;AACH;IAQyB,KAAK;IACL,EAAE;IARL,KAAK,CAA0B;IAC/B,SAAS,CAAU;IAC5B,KAAK,CAAc;IAC7B,
|
|
1
|
+
{"version":3,"file":"debouncer.js","sourceRoot":"","sources":["../src/debouncer.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAGlF,yDAAgF;AAwBhF;;;;;;;;;;;;;;;;GAgBG;AACH;IAQyB,KAAK;IACL,EAAE;IARL,KAAK,CAA0B;IAC/B,SAAS,CAAU;IAC5B,KAAK,CAAc;IAC7B,yHAAyH;IAC/G,kBAAkB,CAAa;IAEzC,YACsB,KAAY,EACZ,EAAc,EACjC,OAAyB;qBAFN,KAAK;kBACL,EAAE;QAGrB,IAAI,CAAC,KAAK,GAAG,kCAAe,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACtC,CAAC;IAED,0EAA0E;IAC1E,IAAI,OAAO;QACR,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;IACnC,CAAC;IAED;;;;;OAKG;IACH,QAAQ;QACL,IAAI,CAAC,kBAAkB,KAAK,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACnD,IAAI,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC;YACxE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED,wFAAwF;IACxF,KAAK;QACF,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,CAAC;IACJ,CAAC;IAED,sDAAsD;IACtD,MAAM;QACH,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,kBAAkB,GAAG,SAAS,CAAC;IACvC,CAAC;IAED,2CAA2C;IAC3C,OAAO;QACJ,IAAI,CAAC,MAAM,EAAE,CAAC;IACjB,CAAC;IAES,IAAI;QACX,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,kBAAkB,GAAG,SAAS,CAAC;QACpC,IAAI,CAAC,EAAE,EAAE,CAAC;IACb,CAAC;CACH"}
|
|
@@ -19,6 +19,12 @@ export interface FakeClock extends Clock {
|
|
|
19
19
|
* chain settle before advancing.
|
|
20
20
|
*/
|
|
21
21
|
advance(ms: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* Move `now()` by `ms`, backwards when negative, leaving stopwatches and
|
|
24
|
+
* timers where they are: the wall-clock step a time sync or a manual change makes,
|
|
25
|
+
* which a duration taken from two `now()` readings gets wrong.
|
|
26
|
+
*/
|
|
27
|
+
jumpWallClock(ms: number): void;
|
|
22
28
|
/** How many {@link Clock.setTimer} timers have neither fired nor been disposed. */
|
|
23
29
|
pendingTimers(): number;
|
|
24
30
|
}
|
|
@@ -28,7 +34,8 @@ export interface FakeClock extends Clock {
|
|
|
28
34
|
* (`Date.now` for `now()`, `performance.now()` for the stopwatch); the fake
|
|
29
35
|
* collapses them onto ONE virtual time axis, so a single
|
|
30
36
|
* {@link FakeClock.advance} drives `now()`, every live stopwatch's elapsed, and
|
|
31
|
-
* any due timer together.
|
|
37
|
+
* any due timer together; only {@link FakeClock.jumpWallClock} pulls `now()`
|
|
38
|
+
* off that axis.
|
|
32
39
|
*
|
|
33
40
|
* Server-free and DI-free — lives in `@hydranium/protocol/testing` so every
|
|
34
41
|
* head can bind it without pulling in a server package.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-clock.d.ts","sourceRoot":"","sources":["../../src/testing/fake-clock.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,KAAK,EAA+B,MAAM,UAAU,CAAC;AAGnE,2FAA2F;AAC3F,MAAM,WAAW,SAAU,SAAQ,KAAK;IACrC;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mFAAmF;IACnF,aAAa,IAAI,MAAM,CAAC;CAC1B;AASD
|
|
1
|
+
{"version":3,"file":"fake-clock.d.ts","sourceRoot":"","sources":["../../src/testing/fake-clock.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,KAAK,EAA+B,MAAM,UAAU,CAAC;AAGnE,2FAA2F;AAC3F,MAAM,WAAW,SAAU,SAAQ,KAAK;IACrC;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mFAAmF;IACnF,aAAa,IAAI,MAAM,CAAC;CAC1B;AASD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,SAAS,CAEvE"}
|
|
@@ -17,7 +17,8 @@ const util_1 = require("../util");
|
|
|
17
17
|
* (`Date.now` for `now()`, `performance.now()` for the stopwatch); the fake
|
|
18
18
|
* collapses them onto ONE virtual time axis, so a single
|
|
19
19
|
* {@link FakeClock.advance} drives `now()`, every live stopwatch's elapsed, and
|
|
20
|
-
* any due timer together.
|
|
20
|
+
* any due timer together; only {@link FakeClock.jumpWallClock} pulls `now()`
|
|
21
|
+
* off that axis.
|
|
21
22
|
*
|
|
22
23
|
* Server-free and DI-free — lives in `@hydranium/protocol/testing` so every
|
|
23
24
|
* head can bind it without pulling in a server package.
|
|
@@ -36,12 +37,17 @@ function makeFakeClock(options = {}) {
|
|
|
36
37
|
class VirtualClock extends clock_1.SystemClock {
|
|
37
38
|
current;
|
|
38
39
|
timers = [];
|
|
40
|
+
/** What {@link jumpWallClock} has added to `now()`. */
|
|
41
|
+
wallClockOffset = 0;
|
|
39
42
|
constructor(current) {
|
|
40
43
|
super();
|
|
41
44
|
this.current = current;
|
|
42
45
|
}
|
|
43
46
|
now() {
|
|
44
|
-
return this.current;
|
|
47
|
+
return this.current + this.wallClockOffset;
|
|
48
|
+
}
|
|
49
|
+
jumpWallClock(ms) {
|
|
50
|
+
this.wallClockOffset += ms;
|
|
45
51
|
}
|
|
46
52
|
setTimer(callback, ms) {
|
|
47
53
|
const timer = { at: this.current + ms, callback, disposed: false, fired: false };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-clock.js","sourceRoot":"","sources":["../../src/testing/fake-clock.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAElF,oCAAmE;AACnE,kCAAqC;
|
|
1
|
+
{"version":3,"file":"fake-clock.js","sourceRoot":"","sources":["../../src/testing/fake-clock.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/clock.ts
CHANGED
|
@@ -14,9 +14,11 @@ import { Disposable } from './util';
|
|
|
14
14
|
* there is no `start` — you create it at the moment timing begins.
|
|
15
15
|
*
|
|
16
16
|
* Backed by a monotonic source (`performance.now`), so it is sub-millisecond
|
|
17
|
-
* precise and immune to wall-clock
|
|
18
|
-
* `Date.now` delta go backwards; a stopwatch cannot).
|
|
19
|
-
*
|
|
17
|
+
* precise and immune to wall-clock steps (a time sync or a manual change can
|
|
18
|
+
* make a `Date.now` delta go backwards; a stopwatch cannot). In Node on Linux
|
|
19
|
+
* it also leaves out time the system spends suspended, so a duration spanning
|
|
20
|
+
* a suspend reads as the awake time only. Use this for every duration, a TTL
|
|
21
|
+
* included, and {@link Clock.now} for "what time is it".
|
|
20
22
|
*
|
|
21
23
|
* It holds no scheduled resource and is therefore NOT a {@link Disposable} —
|
|
22
24
|
* the disposable thing is a {@link Clock.setTimer}. Reads are lazy, so a fake
|
|
@@ -45,16 +47,16 @@ export interface Stopwatch {
|
|
|
45
47
|
* testable instead of waiting on real wall-clock. The {@link SystemClock}
|
|
46
48
|
* default delegates to the platform; tests bind `makeFakeClock`.
|
|
47
49
|
*
|
|
48
|
-
* Two distinct readings on purpose: {@link now} is wall-clock
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* Two distinct readings on purpose: {@link now} is wall-clock, for a
|
|
51
|
+
* timestamp, while {@link stopwatch} measures durations off a monotonic
|
|
52
|
+
* source. They are different clocks; do not subtract two {@link now} readings
|
|
53
|
+
* to measure a duration. Framework code reads time only through a `Clock`, so
|
|
54
|
+
* a platform workaround lives in one place.
|
|
52
55
|
*
|
|
53
56
|
* A cross-head contract, so it carries no platform or DI-framework coupling.
|
|
54
57
|
*
|
|
55
|
-
* Note:
|
|
56
|
-
*
|
|
57
|
-
* measure would invert their meaning.
|
|
58
|
+
* Note: code that measures real CPU, heap or event-loop time never takes the
|
|
59
|
+
* bound `Clock`: faking the time it measures would invert its meaning.
|
|
58
60
|
*/
|
|
59
61
|
export interface Clock {
|
|
60
62
|
/** Current wall-clock time in milliseconds (the `Date.now` reading). */
|
package/src/debouncer.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import { type Clock } from './clock';
|
|
10
|
+
import { type Clock, type Stopwatch } from './clock';
|
|
11
11
|
import { ObservableValue, type MaybeObservableValue } from './observable-value';
|
|
12
12
|
import { type Disposable } from './util';
|
|
13
13
|
|
|
@@ -53,8 +53,8 @@ export class Debouncer implements Disposable {
|
|
|
53
53
|
protected readonly delay: ObservableValue<number>;
|
|
54
54
|
protected readonly maxWaitMs?: number;
|
|
55
55
|
protected timer?: Disposable;
|
|
56
|
-
/**
|
|
57
|
-
protected
|
|
56
|
+
/** Running since the first {@link schedule} of the current pending window; drives {@link DebouncerOptions.maxWaitMs}. */
|
|
57
|
+
protected sinceFirstSchedule?: Stopwatch;
|
|
58
58
|
|
|
59
59
|
constructor(
|
|
60
60
|
protected readonly clock: Clock,
|
|
@@ -77,13 +77,10 @@ export class Debouncer implements Disposable {
|
|
|
77
77
|
* elapses since the first pending call.
|
|
78
78
|
*/
|
|
79
79
|
schedule(): void {
|
|
80
|
-
|
|
81
|
-
if (this.firstScheduledAt === undefined) {
|
|
82
|
-
this.firstScheduledAt = now;
|
|
83
|
-
}
|
|
80
|
+
this.sinceFirstSchedule ??= this.clock.stopwatch();
|
|
84
81
|
let wait = Math.max(0, this.delay.value);
|
|
85
82
|
if (this.maxWaitMs !== undefined) {
|
|
86
|
-
const untilCeiling = this.
|
|
83
|
+
const untilCeiling = this.maxWaitMs - this.sinceFirstSchedule.elapsedMs;
|
|
87
84
|
wait = Math.min(wait, Math.max(0, untilCeiling));
|
|
88
85
|
}
|
|
89
86
|
this.timer?.dispose();
|
|
@@ -101,7 +98,7 @@ export class Debouncer implements Disposable {
|
|
|
101
98
|
cancel(): void {
|
|
102
99
|
this.timer?.dispose();
|
|
103
100
|
this.timer = undefined;
|
|
104
|
-
this.
|
|
101
|
+
this.sinceFirstSchedule = undefined;
|
|
105
102
|
}
|
|
106
103
|
|
|
107
104
|
/** Cancel any pending fire and release. */
|
|
@@ -112,7 +109,7 @@ export class Debouncer implements Disposable {
|
|
|
112
109
|
protected fire(): void {
|
|
113
110
|
this.timer?.dispose();
|
|
114
111
|
this.timer = undefined;
|
|
115
|
-
this.
|
|
112
|
+
this.sinceFirstSchedule = undefined;
|
|
116
113
|
this.fn();
|
|
117
114
|
}
|
|
118
115
|
}
|
|
@@ -22,6 +22,12 @@ export interface FakeClock extends Clock {
|
|
|
22
22
|
* chain settle before advancing.
|
|
23
23
|
*/
|
|
24
24
|
advance(ms: number): void;
|
|
25
|
+
/**
|
|
26
|
+
* Move `now()` by `ms`, backwards when negative, leaving stopwatches and
|
|
27
|
+
* timers where they are: the wall-clock step a time sync or a manual change makes,
|
|
28
|
+
* which a duration taken from two `now()` readings gets wrong.
|
|
29
|
+
*/
|
|
30
|
+
jumpWallClock(ms: number): void;
|
|
25
31
|
/** How many {@link Clock.setTimer} timers have neither fired nor been disposed. */
|
|
26
32
|
pendingTimers(): number;
|
|
27
33
|
}
|
|
@@ -39,7 +45,8 @@ interface FakeTimer {
|
|
|
39
45
|
* (`Date.now` for `now()`, `performance.now()` for the stopwatch); the fake
|
|
40
46
|
* collapses them onto ONE virtual time axis, so a single
|
|
41
47
|
* {@link FakeClock.advance} drives `now()`, every live stopwatch's elapsed, and
|
|
42
|
-
* any due timer together.
|
|
48
|
+
* any due timer together; only {@link FakeClock.jumpWallClock} pulls `now()`
|
|
49
|
+
* off that axis.
|
|
43
50
|
*
|
|
44
51
|
* Server-free and DI-free — lives in `@hydranium/protocol/testing` so every
|
|
45
52
|
* head can bind it without pulling in a server package.
|
|
@@ -58,13 +65,19 @@ export function makeFakeClock(options: { now?: number } = {}): FakeClock {
|
|
|
58
65
|
*/
|
|
59
66
|
class VirtualClock extends SystemClock implements FakeClock {
|
|
60
67
|
protected readonly timers: FakeTimer[] = [];
|
|
68
|
+
/** What {@link jumpWallClock} has added to `now()`. */
|
|
69
|
+
protected wallClockOffset = 0;
|
|
61
70
|
|
|
62
71
|
constructor(protected current: number) {
|
|
63
72
|
super();
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
override now(): number {
|
|
67
|
-
return this.current;
|
|
76
|
+
return this.current + this.wallClockOffset;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
jumpWallClock(ms: number): void {
|
|
80
|
+
this.wallClockOffset += ms;
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
override setTimer(callback: () => void, ms: number): Disposable {
|