@fluidframework/core-utils 2.0.0-internal.6.1.1 → 2.0.0-internal.6.3.0

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.
Files changed (67) hide show
  1. package/.eslintrc.js +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +32 -6
  4. package/dist/assert.d.ts +16 -0
  5. package/dist/assert.d.ts.map +1 -0
  6. package/dist/assert.js +24 -0
  7. package/dist/assert.js.map +1 -0
  8. package/dist/delay.d.ts +10 -0
  9. package/dist/delay.d.ts.map +1 -0
  10. package/dist/delay.js +14 -0
  11. package/dist/delay.js.map +1 -0
  12. package/dist/heap.d.ts +82 -0
  13. package/dist/heap.d.ts.map +1 -0
  14. package/dist/heap.js +139 -0
  15. package/dist/heap.js.map +1 -0
  16. package/dist/index.d.ts +6 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +16 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/promises.d.ts +37 -0
  21. package/dist/promises.d.ts.map +1 -0
  22. package/dist/promises.js +57 -0
  23. package/dist/promises.js.map +1 -0
  24. package/dist/timer.d.ts +105 -0
  25. package/dist/timer.d.ts.map +1 -0
  26. package/dist/timer.js +186 -0
  27. package/dist/timer.js.map +1 -0
  28. package/dist/unreachable.d.ts +21 -0
  29. package/dist/unreachable.d.ts.map +1 -0
  30. package/dist/unreachable.js +27 -0
  31. package/dist/unreachable.js.map +1 -0
  32. package/lib/assert.d.ts +16 -0
  33. package/lib/assert.d.ts.map +1 -0
  34. package/lib/assert.js +20 -0
  35. package/lib/assert.js.map +1 -0
  36. package/lib/delay.d.ts +10 -0
  37. package/lib/delay.d.ts.map +1 -0
  38. package/lib/delay.js +10 -0
  39. package/lib/delay.js.map +1 -0
  40. package/lib/heap.d.ts +82 -0
  41. package/lib/heap.d.ts.map +1 -0
  42. package/lib/heap.js +135 -0
  43. package/lib/heap.js.map +1 -0
  44. package/lib/index.d.ts +6 -0
  45. package/lib/index.d.ts.map +1 -1
  46. package/lib/index.js +6 -0
  47. package/lib/index.js.map +1 -1
  48. package/lib/promises.d.ts +37 -0
  49. package/lib/promises.d.ts.map +1 -0
  50. package/lib/promises.js +53 -0
  51. package/lib/promises.js.map +1 -0
  52. package/lib/timer.d.ts +105 -0
  53. package/lib/timer.d.ts.map +1 -0
  54. package/lib/timer.js +180 -0
  55. package/lib/timer.js.map +1 -0
  56. package/lib/unreachable.d.ts +21 -0
  57. package/lib/unreachable.d.ts.map +1 -0
  58. package/lib/unreachable.js +23 -0
  59. package/lib/unreachable.js.map +1 -0
  60. package/package.json +7 -9
  61. package/src/assert.ts +22 -0
  62. package/src/delay.ts +11 -0
  63. package/src/heap.ts +174 -0
  64. package/src/index.ts +13 -0
  65. package/src/promises.ts +60 -0
  66. package/src/timer.ts +279 -0
  67. package/src/unreachable.ts +23 -0
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * This function can be used to assert at compile time that a given value has type never.
7
+ * One common usage is in the default case of a switch block,
8
+ * to ensure that all cases are explicitly handled.
9
+ *
10
+ * Example:
11
+ * ```typescript
12
+ * const bool: true | false = ...;
13
+ * switch(bool) {
14
+ * case true: {...}
15
+ * case false: {...}
16
+ * default: unreachableCase(bool);
17
+ * }
18
+ * ```
19
+ */
20
+ export function unreachableCase(_, message = "Unreachable Case") {
21
+ throw new Error(message);
22
+ }
23
+ //# sourceMappingURL=unreachable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unreachable.js","sourceRoot":"","sources":["../src/unreachable.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,CAAQ,EAAE,OAAO,GAAG,kBAAkB;IACrE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AAC1B,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * This function can be used to assert at compile time that a given value has type never.\n * One common usage is in the default case of a switch block,\n * to ensure that all cases are explicitly handled.\n *\n * Example:\n * ```typescript\n * const bool: true | false = ...;\n * switch(bool) {\n * case true: {...}\n * case false: {...}\n * default: unreachableCase(bool);\n * }\n * ```\n */\nexport function unreachableCase(_: never, message = \"Unreachable Case\"): never {\n\tthrow new Error(message);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/core-utils",
3
- "version": "2.0.0-internal.6.1.1",
3
+ "version": "2.0.0-internal.6.3.0",
4
4
  "description": "Not intended for use outside the Fluid client repo.",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  "main": "dist/index.js",
15
15
  "module": "lib/index.js",
16
16
  "types": "dist/index.d.ts",
17
- "nyc": {
17
+ "c8": {
18
18
  "all": true,
19
19
  "cache-dir": "nyc/.cache",
20
20
  "exclude": [
@@ -39,13 +39,13 @@
39
39
  "@fluid-tools/build-cli": "^0.22.0",
40
40
  "@fluidframework/build-common": "^2.0.0",
41
41
  "@fluidframework/build-tools": "^0.22.0",
42
- "@fluidframework/eslint-config-fluid": "^2.0.0",
43
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.1.1 <2.0.0-internal.6.2.0",
42
+ "@fluidframework/eslint-config-fluid": "^2.1.0",
43
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.3.0 <2.0.0-internal.6.4.0",
44
44
  "@microsoft/api-extractor": "^7.34.4",
45
45
  "@types/mocha": "^9.1.1",
46
46
  "@types/node": "^16.18.38",
47
47
  "@types/sinon": "^7.0.13",
48
- "concurrently": "^7.6.0",
48
+ "c8": "^7.7.1",
49
49
  "copyfiles": "^2.4.1",
50
50
  "cross-env": "^7.0.3",
51
51
  "eslint": "~8.6.0",
@@ -54,7 +54,6 @@
54
54
  "mocha-json-output-reporter": "^2.0.1",
55
55
  "mocha-multi-reporters": "^1.5.1",
56
56
  "moment": "^2.21.0",
57
- "nyc": "^15.1.0",
58
57
  "prettier": "~2.6.2",
59
58
  "rimraf": "^4.4.0",
60
59
  "sinon": "^7.4.2",
@@ -75,7 +74,7 @@
75
74
  "build:esnext": "tsc --project ./tsconfig.esnext.json",
76
75
  "build:test": "tsc --project ./src/test/tsconfig.json",
77
76
  "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
78
- "clean": "rimraf --glob \"dist\" \"lib\" \"*.tsbuildinfo\" \"*.build.log\"",
77
+ "clean": "rimraf --glob 'dist' 'lib' '*.tsbuildinfo' '*.build.log' '_api-extractor-temp' 'nyc'",
79
78
  "eslint": "eslint --format stylish src",
80
79
  "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
81
80
  "format": "npm run prettier:fix",
@@ -85,9 +84,8 @@
85
84
  "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
86
85
  "test": "npm run test:mocha",
87
86
  "test:benchmark:report": "mocha --node-option unhandled-rejections=strict,expose-gc --exit --perfMode --fgrep @Benchmark --reporter @fluid-tools/benchmark/dist/MochaReporter.js --timeout 60000",
88
- "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
87
+ "test:coverage": "c8 npm test",
89
88
  "test:mocha": "mocha",
90
- "test:mocha:multireport": "cross-env FLUID_TEST_MULTIREPORT=1 npm run test:mocha",
91
89
  "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
92
90
  "tsc": "tsc",
93
91
  "typetests:gen": "fluid-type-test-generator",
package/src/assert.ts ADDED
@@ -0,0 +1,22 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * A browser friendly assert library.
8
+ * Use this instead of the 'assert' package, which has a big impact on bundle sizes.
9
+ * @param condition - The condition that should be true, if the condition is false an error will be thrown.
10
+ * Only use this API when `false` indicates a logic error in the problem and thus a bug that should be fixed.
11
+ * @param message - The message to include in the error when the condition does not hold.
12
+ * A number should not be specified manually: use a string.
13
+ * Before a release, policy-check should be run, which will convert any asserts still using strings to
14
+ * use numbered error codes instead.
15
+ */
16
+ export function assert(condition: boolean, message: string | number): asserts condition {
17
+ if (!condition) {
18
+ throw new Error(
19
+ typeof message === "number" ? `0x${message.toString(16).padStart(3, "0")}` : message,
20
+ );
21
+ }
22
+ }
package/src/delay.ts ADDED
@@ -0,0 +1,11 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Returns a promise that resolves after `timeMs`.
8
+ * @param timeMs - Time in milliseconds to wait.
9
+ */
10
+ export const delay = async (timeMs: number): Promise<void> =>
11
+ new Promise((resolve) => setTimeout(() => resolve(), timeMs));
package/src/heap.ts ADDED
@@ -0,0 +1,174 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Interface for a comparer.
8
+ */
9
+ export interface IComparer<T> {
10
+ /**
11
+ * The minimum value of type T.
12
+ */
13
+ min: T;
14
+
15
+ /**
16
+ * Compare the two value
17
+ *
18
+ * @returns 0 if the value is equal, negative number if a is smaller then b, positive number otherwise
19
+ */
20
+ compare(a: T, b: T): number;
21
+ }
22
+
23
+ /**
24
+ * A comparer for numbers.
25
+ */
26
+ export const NumberComparer: IComparer<number> = {
27
+ /**
28
+ * The compare function for numbers.
29
+ * @returns The difference of the two numbers.
30
+ */
31
+ compare: (a, b): number => a - b,
32
+
33
+ /**
34
+ * The minimum value of a JavaScript number, which is `Number.MIN_VALUE`.
35
+ */
36
+ min: Number.MIN_VALUE,
37
+ };
38
+
39
+ /**
40
+ * Interface to a node in {@link Heap}.
41
+ */
42
+ export interface IHeapNode<T> {
43
+ value: T;
44
+ position: number;
45
+ }
46
+
47
+ /**
48
+ * Ordered {@link https://en.wikipedia.org/wiki/Heap_(data_structure) | Heap} data structure implementation.
49
+ */
50
+ export class Heap<T> {
51
+ private L: IHeapNode<T>[];
52
+
53
+ /**
54
+ * Creates an instance of `Heap` with comparer.
55
+ * @param comp - A comparer that specify how elements are ordered.
56
+ */
57
+ constructor(public comp: IComparer<T>) {
58
+ this.L = [{ value: comp.min, position: 0 }];
59
+ }
60
+
61
+ /**
62
+ * Return the smallest element in the heap as determined by the order of the comparer
63
+ *
64
+ * @returns Heap node containing the smallest element
65
+ */
66
+ public peek(): IHeapNode<T> {
67
+ return this.L[1];
68
+ }
69
+
70
+ /**
71
+ * Get and remove the smallest element in the heap as determined by the order of the comparer
72
+ *
73
+ * @returns The smallest value in the heap
74
+ */
75
+ public get(): T {
76
+ this.swap(1, this.count());
77
+ const x = this.L.pop();
78
+ this.fixdown(1);
79
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
80
+ return x!.value;
81
+ }
82
+
83
+ /**
84
+ * Add a value to the heap
85
+ *
86
+ * @param x - value to add
87
+ * @returns The heap node that contains the value
88
+ */
89
+ public add(x: T): IHeapNode<T> {
90
+ const node = { value: x, position: this.L.length };
91
+ this.L.push(node);
92
+ this.fixup(this.count());
93
+
94
+ return node;
95
+ }
96
+
97
+ /**
98
+ * Allows for the Heap to be updated after a node's value changes.
99
+ */
100
+ public update(node: IHeapNode<T>): void {
101
+ const k = node.position;
102
+ if (this.isGreaterThanParent(k)) {
103
+ this.fixup(k);
104
+ } else {
105
+ this.fixdown(k);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Removes the given node from the heap.
111
+ *
112
+ * @param node - The node to remove from the heap.
113
+ */
114
+ public remove(node: IHeapNode<T>): void {
115
+ // Move the node we want to remove to the end of the array
116
+ const position = node.position;
117
+ this.swap(node.position, this.L.length - 1);
118
+ this.L.splice(this.L.length - 1);
119
+
120
+ // Update the swapped node assuming we didn't remove the end of the list
121
+ if (position !== this.L.length) {
122
+ this.update(this.L[position]);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Get the number of elements in the Heap.
128
+ *
129
+ * @returns The number of elements in the Heap.
130
+ */
131
+ public count(): number {
132
+ return this.L.length - 1;
133
+ }
134
+
135
+ private fixup(pos: number): void {
136
+ let k = pos;
137
+ while (this.isGreaterThanParent(k)) {
138
+ // eslint-disable-next-line no-bitwise
139
+ const parent = k >> 1;
140
+ this.swap(k, parent);
141
+ k = parent;
142
+ }
143
+ }
144
+
145
+ private isGreaterThanParent(k: number): boolean {
146
+ // eslint-disable-next-line no-bitwise
147
+ return k > 1 && this.comp.compare(this.L[k >> 1].value, this.L[k].value) > 0;
148
+ }
149
+
150
+ private fixdown(pos: number): void {
151
+ let k = pos;
152
+ // eslint-disable-next-line no-bitwise
153
+ while (k << 1 <= this.count()) {
154
+ // eslint-disable-next-line no-bitwise
155
+ let j = k << 1;
156
+ if (j < this.count() && this.comp.compare(this.L[j].value, this.L[j + 1].value) > 0) {
157
+ j++;
158
+ }
159
+ if (this.comp.compare(this.L[k].value, this.L[j].value) <= 0) {
160
+ break;
161
+ }
162
+ this.swap(k, j);
163
+ k = j;
164
+ }
165
+ }
166
+
167
+ private swap(k: number, j: number): void {
168
+ const tmp = this.L[k];
169
+ this.L[k] = this.L[j];
170
+ this.L[k].position = k;
171
+ this.L[j] = tmp;
172
+ this.L[j].position = j;
173
+ }
174
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,19 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ export { assert } from "./assert";
6
7
  export { compareArrays } from "./compare";
8
+ export { delay } from "./delay";
9
+ export { Heap, IComparer, IHeapNode, NumberComparer } from "./heap";
7
10
  export { Lazy, LazyPromise } from "./lazy";
8
11
  export { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from "./promiseCache";
12
+ export { Deferred } from "./promises";
13
+ export {
14
+ IPromiseTimer,
15
+ IPromiseTimerResult,
16
+ ITimer,
17
+ PromiseTimer,
18
+ setLongTimeout,
19
+ Timer,
20
+ } from "./timer";
21
+ export { unreachableCase } from "./unreachable";
@@ -0,0 +1,60 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * A deferred creates a promise and the ability to resolve or reject it
8
+ */
9
+ export class Deferred<T> {
10
+ private readonly p: Promise<T>;
11
+ private res: ((value: T | PromiseLike<T>) => void) | undefined;
12
+ private rej: ((reason?: any) => void) | undefined;
13
+ private completed: boolean = false;
14
+
15
+ constructor() {
16
+ this.p = new Promise<T>((resolve, reject) => {
17
+ this.res = resolve;
18
+ this.rej = reject;
19
+ });
20
+ }
21
+ /**
22
+ * Returns whether the underlying promise has been completed
23
+ */
24
+ public get isCompleted(): boolean {
25
+ return this.completed;
26
+ }
27
+
28
+ /**
29
+ * Retrieves the underlying promise for the deferred
30
+ *
31
+ * @returns the underlying promise
32
+ */
33
+ public get promise(): Promise<T> {
34
+ return this.p;
35
+ }
36
+
37
+ /**
38
+ * Resolves the promise
39
+ *
40
+ * @param value - the value to resolve the promise with
41
+ */
42
+ public resolve(value: T | PromiseLike<T>): void {
43
+ if (this.res !== undefined) {
44
+ this.completed = true;
45
+ this.res(value);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Rejects the promise
51
+ *
52
+ * @param value - the value to reject the promise with
53
+ */
54
+ public reject(error: any): void {
55
+ if (this.rej !== undefined) {
56
+ this.completed = true;
57
+ this.rej(error);
58
+ }
59
+ }
60
+ }
package/src/timer.ts ADDED
@@ -0,0 +1,279 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert } from "./assert";
7
+ import { Deferred } from "./promises";
8
+
9
+ export interface ITimer {
10
+ /**
11
+ * True if timer is currently running
12
+ */
13
+ readonly hasTimer: boolean;
14
+
15
+ /**
16
+ * Starts the timer
17
+ */
18
+ start(): void;
19
+
20
+ /**
21
+ * Cancels the timer if already running
22
+ */
23
+ clear(): void;
24
+ }
25
+
26
+ interface ITimeout {
27
+ /**
28
+ * Tick that timeout was started.
29
+ */
30
+ startTick: number;
31
+
32
+ /**
33
+ * Timeout duration in ms.
34
+ */
35
+ duration: number;
36
+
37
+ /**
38
+ * Handler to execute when timeout ends.
39
+ */
40
+ handler: () => void;
41
+ }
42
+
43
+ interface IRunningTimerState extends ITimeout {
44
+ /**
45
+ * JavaScript Timeout object.
46
+ */
47
+ timeout: ReturnType<typeof setTimeout>;
48
+
49
+ /**
50
+ * Intended duration in ms.
51
+ */
52
+ intendedDuration: number;
53
+
54
+ /**
55
+ * Intended restart timeout.
56
+ */
57
+ restart?: ITimeout;
58
+ }
59
+
60
+ const maxSetTimeoutMs = 0x7fffffff; // setTimeout limit is MAX_INT32=(2^31-1).
61
+
62
+ /**
63
+ * Sets timeouts like the setTimeout function allowing timeouts to exceed the setTimeout's max timeout limit.
64
+ * Timeouts may not be exactly accurate due to browser implementations and the OS.
65
+ * https://stackoverflow.com/questions/21097421/what-is-the-reason-javascript-settimeout-is-so-inaccurate
66
+ * @param timeoutFn - Executed when the timeout expires
67
+ * @param timeoutMs - Duration of the timeout in milliseconds
68
+ * @param setTimeoutIdFn - Executed to update the timeout if multiple timeouts are required when
69
+ * timeoutMs greater than maxTimeout
70
+ * @returns The initial timeout
71
+ */
72
+ export function setLongTimeout(
73
+ timeoutFn: () => void,
74
+ timeoutMs: number,
75
+ setTimeoutIdFn?: (timeoutId: ReturnType<typeof setTimeout>) => void,
76
+ ): ReturnType<typeof setTimeout> {
77
+ // The setTimeout max is 24.8 days before looping occurs.
78
+ let timeoutId: ReturnType<typeof setTimeout>;
79
+ if (timeoutMs > maxSetTimeoutMs) {
80
+ const newTimeoutMs = timeoutMs - maxSetTimeoutMs;
81
+ timeoutId = setTimeout(
82
+ () => setLongTimeout(timeoutFn, newTimeoutMs, setTimeoutIdFn),
83
+ maxSetTimeoutMs,
84
+ );
85
+ } else {
86
+ timeoutId = setTimeout(() => timeoutFn(), Math.max(timeoutMs, 0));
87
+ }
88
+
89
+ setTimeoutIdFn?.(timeoutId);
90
+ return timeoutId;
91
+ }
92
+
93
+ /**
94
+ * This class is a thin wrapper over setTimeout and clearTimeout which
95
+ * makes it simpler to keep track of recurring timeouts with the same
96
+ * or similar handlers and timeouts. This class supports long timeouts
97
+ * or timeouts exceeding (2^31)-1 ms or approximately 24.8 days.
98
+ */
99
+ export class Timer implements ITimer {
100
+ /**
101
+ * Returns true if the timer is running.
102
+ */
103
+ public get hasTimer(): boolean {
104
+ return !!this.runningState;
105
+ }
106
+
107
+ private runningState: IRunningTimerState | undefined;
108
+
109
+ constructor(
110
+ private readonly defaultTimeout: number,
111
+ private readonly defaultHandler: () => void,
112
+ private readonly getCurrentTick: () => number = (): number => Date.now(),
113
+ ) {}
114
+
115
+ /**
116
+ * Calls setTimeout and tracks the resulting timeout.
117
+ * @param ms - overrides default timeout in ms
118
+ * @param handler - overrides default handler
119
+ */
120
+ public start(
121
+ ms: number = this.defaultTimeout,
122
+ handler: () => void = this.defaultHandler,
123
+ ): void {
124
+ this.startCore(ms, handler, ms);
125
+ }
126
+
127
+ /**
128
+ * Calls clearTimeout on the underlying timeout if running.
129
+ */
130
+ public clear(): void {
131
+ if (!this.runningState) {
132
+ return;
133
+ }
134
+ clearTimeout(this.runningState.timeout);
135
+ this.runningState = undefined;
136
+ }
137
+
138
+ /**
139
+ * Restarts the timer with the new handler and duration.
140
+ * If a new handler is passed, the original handler may
141
+ * never execute.
142
+ * This is a potentially more efficient way to clear and start
143
+ * a new timer.
144
+ * @param ms - overrides previous or default timeout in ms
145
+ * @param handler - overrides previous or default handler
146
+ */
147
+ public restart(ms?: number, handler?: () => void): void {
148
+ if (!this.runningState) {
149
+ // If restart is called first, it behaves as a call to start
150
+ this.start(ms, handler);
151
+ } else {
152
+ const duration = ms ?? this.runningState.intendedDuration;
153
+ const handlerToUse =
154
+ handler ?? this.runningState.restart?.handler ?? this.runningState.handler;
155
+ const remainingTime = this.calculateRemainingTime(this.runningState);
156
+
157
+ if (duration < remainingTime) {
158
+ // If remaining time exceeds restart duration, do a hard restart.
159
+ // The existing timeout time is too long.
160
+ this.start(duration, handlerToUse);
161
+ } else if (duration === remainingTime) {
162
+ // The existing timeout time is perfect, just update handler and data.
163
+ this.runningState.handler = handlerToUse;
164
+ this.runningState.restart = undefined;
165
+ this.runningState.intendedDuration = duration;
166
+ } else {
167
+ // If restart duration exceeds remaining time, set restart info.
168
+ // Existing timeout will start a new timeout for remaining time.
169
+ this.runningState.restart = {
170
+ startTick: this.getCurrentTick(),
171
+ duration,
172
+ handler: handlerToUse,
173
+ };
174
+ }
175
+ }
176
+ }
177
+
178
+ private startCore(duration: number, handler: () => void, intendedDuration: number): void {
179
+ this.clear();
180
+ this.runningState = {
181
+ startTick: this.getCurrentTick(),
182
+ duration,
183
+ intendedDuration,
184
+ handler,
185
+ timeout: setLongTimeout(
186
+ () => this.handler(),
187
+ duration,
188
+ (timer: number) => {
189
+ if (this.runningState !== undefined) {
190
+ this.runningState.timeout = timer;
191
+ }
192
+ },
193
+ ),
194
+ };
195
+ }
196
+
197
+ private handler(): void {
198
+ assert(!!this.runningState, 0x764 /* Running timer missing handler */);
199
+ const restart = this.runningState.restart;
200
+ if (restart !== undefined) {
201
+ // Restart with remaining time
202
+ const remainingTime = this.calculateRemainingTime(restart);
203
+ this.startCore(remainingTime, () => restart.handler(), restart.duration);
204
+ } else {
205
+ // Run clear first, in case the handler decides to start again
206
+ const handler = this.runningState.handler;
207
+ this.clear();
208
+ handler();
209
+ }
210
+ }
211
+
212
+ private calculateRemainingTime(runningTimeout: ITimeout): number {
213
+ const elapsedTime = this.getCurrentTick() - runningTimeout.startTick;
214
+ return runningTimeout.duration - elapsedTime;
215
+ }
216
+ }
217
+
218
+ export interface IPromiseTimerResult {
219
+ timerResult: "timeout" | "cancel";
220
+ }
221
+
222
+ /**
223
+ * Timer which offers a promise that fulfills when the timer
224
+ * completes.
225
+ */
226
+ export interface IPromiseTimer extends ITimer {
227
+ /**
228
+ * Starts the timer and returns a promise that
229
+ * resolves when the timer times out or is canceled.
230
+ */
231
+ start(): Promise<IPromiseTimerResult>;
232
+ }
233
+
234
+ /**
235
+ * This class is a wrapper over setTimeout and clearTimeout which
236
+ * makes it simpler to keep track of recurring timeouts with the
237
+ * same handlers and timeouts, while also providing a promise that
238
+ * resolves when it times out.
239
+ */
240
+ export class PromiseTimer implements IPromiseTimer {
241
+ private deferred?: Deferred<IPromiseTimerResult>;
242
+ private readonly timer: Timer;
243
+
244
+ /**
245
+ * {@inheritDoc Timer.hasTimer}
246
+ */
247
+ public get hasTimer(): boolean {
248
+ return this.timer.hasTimer;
249
+ }
250
+
251
+ constructor(defaultTimeout: number, defaultHandler: () => void) {
252
+ this.timer = new Timer(defaultTimeout, () => this.wrapHandler(defaultHandler));
253
+ }
254
+
255
+ /**
256
+ * {@inheritDoc IPromiseTimer.start}
257
+ */
258
+ public async start(ms?: number, handler?: () => void): Promise<IPromiseTimerResult> {
259
+ this.clear();
260
+ this.deferred = new Deferred<IPromiseTimerResult>();
261
+ this.timer.start(ms, handler ? (): void => this.wrapHandler(handler) : undefined);
262
+ return this.deferred.promise;
263
+ }
264
+
265
+ public clear(): void {
266
+ this.timer.clear();
267
+ if (this.deferred) {
268
+ this.deferred.resolve({ timerResult: "cancel" });
269
+ this.deferred = undefined;
270
+ }
271
+ }
272
+
273
+ protected wrapHandler(handler: () => void): void {
274
+ handler();
275
+ assert(!!this.deferred, 0x765 /* Handler executed without deferred */);
276
+ this.deferred.resolve({ timerResult: "timeout" });
277
+ this.deferred = undefined;
278
+ }
279
+ }
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * This function can be used to assert at compile time that a given value has type never.
8
+ * One common usage is in the default case of a switch block,
9
+ * to ensure that all cases are explicitly handled.
10
+ *
11
+ * Example:
12
+ * ```typescript
13
+ * const bool: true | false = ...;
14
+ * switch(bool) {
15
+ * case true: {...}
16
+ * case false: {...}
17
+ * default: unreachableCase(bool);
18
+ * }
19
+ * ```
20
+ */
21
+ export function unreachableCase(_: never, message = "Unreachable Case"): never {
22
+ throw new Error(message);
23
+ }