@byloth/core 2.0.0-rc.4 → 2.0.0-rc.6
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/dist/core.js +334 -267
- package/dist/core.js.map +1 -1
- package/dist/core.umd.cjs +2 -2
- package/dist/core.umd.cjs.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +2 -1
- package/src/models/aggregators/aggregated-async-iterator.ts +1 -6
- package/src/models/aggregators/aggregated-iterator.ts +1 -6
- package/src/models/game-loop.ts +7 -9
- package/src/models/index.ts +1 -1
- package/src/models/promises/index.ts +2 -1
- package/src/models/promises/thenable.ts +97 -0
- package/src/models/timers/clock.ts +23 -6
- package/src/models/timers/countdown.ts +50 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byloth/core",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.6",
|
|
4
4
|
"description": "An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Core",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
},
|
|
48
48
|
"types": "./src/index.ts",
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@byloth/eslint-config-typescript": "^3.0.
|
|
51
|
-
"@types/node": "^
|
|
50
|
+
"@byloth/eslint-config-typescript": "^3.0.1",
|
|
51
|
+
"@types/node": "^22.9.0",
|
|
52
52
|
"husky": "^9.1.6",
|
|
53
53
|
"typescript": "^5.6.3",
|
|
54
|
-
"vite": "^5.4.
|
|
54
|
+
"vite": "^5.4.11"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"dev": "vite",
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VERSION = "2.0.0-rc.
|
|
1
|
+
export const VERSION = "2.0.0-rc.6";
|
|
2
2
|
|
|
3
3
|
export type { Constructor, Interval, Timeout } from "./core/types.js";
|
|
4
4
|
|
|
@@ -29,6 +29,7 @@ export {
|
|
|
29
29
|
SmartIterator,
|
|
30
30
|
SmartAsyncIterator,
|
|
31
31
|
SmartPromise,
|
|
32
|
+
Thenable,
|
|
32
33
|
TimeoutException,
|
|
33
34
|
TimedPromise,
|
|
34
35
|
TypeException,
|
|
@@ -190,12 +190,7 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
|
|
|
190
190
|
for await (const [key, element] of elements)
|
|
191
191
|
{
|
|
192
192
|
const index = indexes.get(key) ?? 0;
|
|
193
|
-
if (index >= limit)
|
|
194
|
-
{
|
|
195
|
-
if (indexes.values().every((value) => value >= limit)) { break; }
|
|
196
|
-
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
193
|
+
if (index >= limit) { continue; }
|
|
199
194
|
|
|
200
195
|
yield [key, element];
|
|
201
196
|
|
|
@@ -178,12 +178,7 @@ export default class AggregatedIterator<K extends PropertyKey, T>
|
|
|
178
178
|
for (const [key, element] of elements)
|
|
179
179
|
{
|
|
180
180
|
const index = indexes.get(key) ?? 0;
|
|
181
|
-
if (index >= limit)
|
|
182
|
-
{
|
|
183
|
-
if (indexes.values().every((value) => value >= limit)) { break; }
|
|
184
|
-
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
181
|
+
if (index >= limit) { continue; }
|
|
187
182
|
|
|
188
183
|
yield [key, element];
|
|
189
184
|
|
package/src/models/game-loop.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import type { Interval } from "../core/types.js";
|
|
1
2
|
import { isBrowser } from "../helpers.js";
|
|
2
|
-
import { TimeUnit } from "../utils/date.js";
|
|
3
3
|
|
|
4
4
|
import { FatalErrorException, RuntimeException } from "./exceptions/index.js";
|
|
5
5
|
|
|
6
6
|
export default class GameLoop
|
|
7
7
|
{
|
|
8
|
-
protected _handle?: number;
|
|
8
|
+
protected _handle?: number | Interval;
|
|
9
9
|
|
|
10
10
|
protected _startTime: number;
|
|
11
11
|
public get startTime(): number
|
|
@@ -27,7 +27,7 @@ export default class GameLoop
|
|
|
27
27
|
protected _start: () => void;
|
|
28
28
|
protected _stop: () => void;
|
|
29
29
|
|
|
30
|
-
public constructor(callback: FrameRequestCallback,
|
|
30
|
+
public constructor(callback: FrameRequestCallback, msIfNotBrowser = 40)
|
|
31
31
|
{
|
|
32
32
|
this._startTime = 0;
|
|
33
33
|
this._isRunning = false;
|
|
@@ -41,24 +41,22 @@ export default class GameLoop
|
|
|
41
41
|
this._handle = window.requestAnimationFrame(this._start);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
this._stop = () => window.cancelAnimationFrame(this._handle
|
|
44
|
+
this._stop = () => window.cancelAnimationFrame(this._handle as number);
|
|
45
45
|
}
|
|
46
46
|
else
|
|
47
47
|
{
|
|
48
48
|
// eslint-disable-next-line no-console
|
|
49
49
|
console.warn(
|
|
50
50
|
"Not a browser environment detected. " +
|
|
51
|
-
`Using setInterval@${
|
|
51
|
+
`Using setInterval@${msIfNotBrowser}ms instead of requestAnimationFrame...`
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
this._start = () =>
|
|
55
55
|
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this._handle = (setInterval(() => callback(this.elapsedTime), delay) as unknown) as number;
|
|
56
|
+
this._handle = setInterval(() => callback(this.elapsedTime), msIfNotBrowser);
|
|
59
57
|
};
|
|
60
58
|
|
|
61
|
-
this._stop = () => clearInterval(this._handle
|
|
59
|
+
this._stop = () => clearInterval(this._handle as Interval);
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
|
package/src/models/index.ts
CHANGED
|
@@ -28,7 +28,7 @@ import GameLoop from "./game-loop.js";
|
|
|
28
28
|
|
|
29
29
|
export { SmartIterator, SmartAsyncIterator } from "./iterators/index.js";
|
|
30
30
|
export { JSONStorage } from "./json/index.js";
|
|
31
|
-
export { DeferredPromise, SmartPromise, TimedPromise } from "./promises/index.js";
|
|
31
|
+
export { DeferredPromise, SmartPromise, Thenable, TimedPromise } from "./promises/index.js";
|
|
32
32
|
|
|
33
33
|
import Publisher from "./publisher.js";
|
|
34
34
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import DeferredPromise from "./deferred-promise.js";
|
|
2
2
|
import SmartPromise from "./smart-promise.js";
|
|
3
|
+
import Thenable from "./thenable.js";
|
|
3
4
|
import TimedPromise from "./timed-promise.js";
|
|
4
5
|
|
|
5
|
-
export { DeferredPromise, SmartPromise, TimedPromise };
|
|
6
|
+
export { DeferredPromise, SmartPromise, Thenable, TimedPromise };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export default class Thenable<T> implements Promise<T>
|
|
2
|
+
{
|
|
3
|
+
protected _onFulfilled: (result: T) => T;
|
|
4
|
+
protected _resolve(result: T): T
|
|
5
|
+
{
|
|
6
|
+
return this._onFulfilled(result);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
public constructor()
|
|
10
|
+
{
|
|
11
|
+
this._onFulfilled = (result: T) => result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public then(onFulfilled?: null): Thenable<T>;
|
|
15
|
+
public then<F = T>(onFulfilled: (result: T) => F, onRejected?: null): Thenable<F>;
|
|
16
|
+
public then<F = T, R = never>(onFulfilled: (result: T) => F, onRejected: (reason: unknown) => R)
|
|
17
|
+
: Thenable<F | R>;
|
|
18
|
+
public then<F = T, R = never>(onFulfilled?: ((result: T) => F) | null, onRejected?: ((reason: unknown) => R) | null)
|
|
19
|
+
: Thenable<F | R>
|
|
20
|
+
{
|
|
21
|
+
if (onRejected)
|
|
22
|
+
{
|
|
23
|
+
const _previousOnFulfilled = this._onFulfilled;
|
|
24
|
+
this._onFulfilled = (result: T) =>
|
|
25
|
+
{
|
|
26
|
+
try
|
|
27
|
+
{
|
|
28
|
+
result = _previousOnFulfilled(result);
|
|
29
|
+
|
|
30
|
+
return (onFulfilled!(result) as unknown) as T;
|
|
31
|
+
}
|
|
32
|
+
catch (error)
|
|
33
|
+
{
|
|
34
|
+
return (onRejected(error) as unknown) as T;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
else if (onFulfilled)
|
|
39
|
+
{
|
|
40
|
+
const _previousOnFulfilled = this._onFulfilled;
|
|
41
|
+
this._onFulfilled = (result: T) =>
|
|
42
|
+
{
|
|
43
|
+
result = _previousOnFulfilled(result);
|
|
44
|
+
|
|
45
|
+
return (onFulfilled(result) as unknown) as T;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (this as unknown) as Thenable<F | R>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public catch(onRejected?: null): Thenable<T>;
|
|
53
|
+
public catch<R = never>(onRejected: (reason: unknown) => R): Thenable<T | R>;
|
|
54
|
+
public catch<R = never>(onRejected?: ((reason: unknown) => R) | null): Thenable<T | R>
|
|
55
|
+
{
|
|
56
|
+
if (onRejected)
|
|
57
|
+
{
|
|
58
|
+
const _previousOnFulfilled = this._onFulfilled;
|
|
59
|
+
this._onFulfilled = (result) =>
|
|
60
|
+
{
|
|
61
|
+
try
|
|
62
|
+
{
|
|
63
|
+
return _previousOnFulfilled(result);
|
|
64
|
+
}
|
|
65
|
+
catch (error)
|
|
66
|
+
{
|
|
67
|
+
return (onRejected(error) as unknown) as T;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this as Thenable<T | R>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public finally(onFinally?: (() => void) | null): Thenable<T>
|
|
76
|
+
{
|
|
77
|
+
if (onFinally)
|
|
78
|
+
{
|
|
79
|
+
const _previousOnFulfilled = this._onFulfilled;
|
|
80
|
+
this._onFulfilled = (result) =>
|
|
81
|
+
{
|
|
82
|
+
try
|
|
83
|
+
{
|
|
84
|
+
return _previousOnFulfilled(result);
|
|
85
|
+
}
|
|
86
|
+
finally
|
|
87
|
+
{
|
|
88
|
+
onFinally();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public readonly [Symbol.toStringTag]: string = "Thenable";
|
|
97
|
+
}
|
|
@@ -6,13 +6,17 @@ import Publisher from "../publisher.js";
|
|
|
6
6
|
|
|
7
7
|
export default class Clock extends GameLoop
|
|
8
8
|
{
|
|
9
|
-
protected
|
|
9
|
+
protected _starter: Publisher;
|
|
10
|
+
protected _stopper: Publisher;
|
|
11
|
+
protected _ticker: Publisher<[number]>;
|
|
10
12
|
|
|
11
|
-
public constructor(
|
|
13
|
+
public constructor(msIfNotBrowser: number = TimeUnit.Second)
|
|
12
14
|
{
|
|
13
|
-
super((elapsedTime) => this.
|
|
15
|
+
super((elapsedTime) => this._ticker.publish(elapsedTime), msIfNotBrowser);
|
|
14
16
|
|
|
15
|
-
this.
|
|
17
|
+
this._starter = new Publisher();
|
|
18
|
+
this._stopper = new Publisher();
|
|
19
|
+
this._ticker = new Publisher();
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
public start(elapsedTime = 0): void
|
|
@@ -20,6 +24,8 @@ export default class Clock extends GameLoop
|
|
|
20
24
|
if (this._isRunning) { throw new RuntimeException("The clock has already been started."); }
|
|
21
25
|
|
|
22
26
|
super.start(elapsedTime);
|
|
27
|
+
|
|
28
|
+
this._starter.publish();
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
public stop(): void
|
|
@@ -27,16 +33,27 @@ export default class Clock extends GameLoop
|
|
|
27
33
|
if (!(this._isRunning)) { throw new RuntimeException("The clock hadn't yet started."); }
|
|
28
34
|
|
|
29
35
|
super.stop();
|
|
36
|
+
|
|
37
|
+
this._stopper.publish();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public onStart(callback: () => void): () => void
|
|
41
|
+
{
|
|
42
|
+
return this._starter.subscribe(callback);
|
|
43
|
+
}
|
|
44
|
+
public onStop(callback: () => void): () => void
|
|
45
|
+
{
|
|
46
|
+
return this._stopper.subscribe(callback);
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
public onTick(callback: (elapsedTime: number) => void, tickStep = 0): () => void
|
|
33
50
|
{
|
|
34
51
|
if (tickStep < 0) { throw new RangeException("The tick step must be a non-negative number."); }
|
|
35
|
-
if (tickStep === 0) { return this.
|
|
52
|
+
if (tickStep === 0) { return this._ticker.subscribe(callback); }
|
|
36
53
|
|
|
37
54
|
let lastTick = 0;
|
|
38
55
|
|
|
39
|
-
return this.
|
|
56
|
+
return this._ticker.subscribe((elapsedTime: number) =>
|
|
40
57
|
{
|
|
41
58
|
if ((elapsedTime - lastTick) < tickStep) { return; }
|
|
42
59
|
|
|
@@ -9,7 +9,11 @@ import Publisher from "../publisher.js";
|
|
|
9
9
|
export default class Countdown extends GameLoop
|
|
10
10
|
{
|
|
11
11
|
protected _deferrer?: DeferredPromise<void>;
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
protected _expirer: Publisher;
|
|
14
|
+
protected _starter: Publisher;
|
|
15
|
+
protected _stopper: Publisher<[unknown]>;
|
|
16
|
+
protected _ticker: Publisher<[number]>;
|
|
13
17
|
|
|
14
18
|
protected _duration: number;
|
|
15
19
|
public get duration(): number
|
|
@@ -22,22 +26,44 @@ export default class Countdown extends GameLoop
|
|
|
22
26
|
return this._duration - this.elapsedTime;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
public constructor(duration: number,
|
|
29
|
+
public constructor(duration: number, msIfNotBrowser: number = TimeUnit.Second)
|
|
26
30
|
{
|
|
27
31
|
const callback = () =>
|
|
28
32
|
{
|
|
29
33
|
const remainingTime = this.remainingTime;
|
|
30
|
-
this.
|
|
34
|
+
this._ticker.publish(remainingTime);
|
|
35
|
+
|
|
36
|
+
if (remainingTime <= 0)
|
|
37
|
+
{
|
|
38
|
+
this._deferrerStop();
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
this._expirer.publish();
|
|
41
|
+
}
|
|
33
42
|
};
|
|
34
43
|
|
|
35
|
-
super(callback,
|
|
44
|
+
super(callback, msIfNotBrowser);
|
|
45
|
+
|
|
46
|
+
this._expirer = new Publisher();
|
|
47
|
+
this._starter = new Publisher();
|
|
48
|
+
this._stopper = new Publisher();
|
|
49
|
+
this._ticker = new Publisher();
|
|
36
50
|
|
|
37
|
-
this._publisher = new Publisher();
|
|
38
51
|
this._duration = duration;
|
|
39
52
|
}
|
|
40
53
|
|
|
54
|
+
protected _deferrerStop(reason?: unknown): void
|
|
55
|
+
{
|
|
56
|
+
if (!(this._isRunning)) { throw new RuntimeException("The countdown hadn't yet started."); }
|
|
57
|
+
if (!(this._deferrer)) { throw new FatalErrorException(); }
|
|
58
|
+
|
|
59
|
+
super.stop();
|
|
60
|
+
|
|
61
|
+
if (reason !== undefined) { this._deferrer.reject(reason); }
|
|
62
|
+
else { this._deferrer.resolve(); }
|
|
63
|
+
|
|
64
|
+
this._deferrer = undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
public start(remainingTime: number = this.duration): SmartPromise<void>
|
|
42
68
|
{
|
|
43
69
|
if (this._isRunning) { throw new RuntimeException("The countdown has already been started."); }
|
|
@@ -46,29 +72,39 @@ export default class Countdown extends GameLoop
|
|
|
46
72
|
this._deferrer = new DeferredPromise();
|
|
47
73
|
super.start(this.duration - remainingTime);
|
|
48
74
|
|
|
75
|
+
this._starter.publish();
|
|
76
|
+
|
|
49
77
|
return this._deferrer;
|
|
50
78
|
}
|
|
51
79
|
public stop(reason?: unknown): void
|
|
52
80
|
{
|
|
53
|
-
|
|
54
|
-
if (!(this._deferrer)) { throw new FatalErrorException(); }
|
|
81
|
+
this._deferrerStop(reason);
|
|
55
82
|
|
|
56
|
-
|
|
83
|
+
this._stopper.publish(reason);
|
|
84
|
+
}
|
|
57
85
|
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
public onExpire(callback: () => void): () => void
|
|
87
|
+
{
|
|
88
|
+
return this._expirer.subscribe(callback);
|
|
89
|
+
}
|
|
60
90
|
|
|
61
|
-
|
|
91
|
+
public onStart(callback: () => void): () => void
|
|
92
|
+
{
|
|
93
|
+
return this._starter.subscribe(callback);
|
|
94
|
+
}
|
|
95
|
+
public onStop(callback: (reason?: unknown) => void): () => void
|
|
96
|
+
{
|
|
97
|
+
return this._stopper.subscribe(callback);
|
|
62
98
|
}
|
|
63
99
|
|
|
64
100
|
public onTick(callback: (remainingTime: number) => void, tickStep = 0): () => void
|
|
65
101
|
{
|
|
66
102
|
if (tickStep < 0) { throw new RangeException("The tick step must be a non-negative number."); }
|
|
67
|
-
if (tickStep === 0) { return this.
|
|
103
|
+
if (tickStep === 0) { return this._ticker.subscribe(callback); }
|
|
68
104
|
|
|
69
105
|
let lastTick = 0;
|
|
70
106
|
|
|
71
|
-
return this.
|
|
107
|
+
return this._ticker.subscribe((remainingTime: number) =>
|
|
72
108
|
{
|
|
73
109
|
if ((lastTick - remainingTime) < tickStep) { return; }
|
|
74
110
|
|