@byloth/core 2.0.0-rc.8 → 2.0.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.
- package/dist/core.js +3372 -609
- 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 +13 -10
- package/src/core/types.ts +41 -0
- package/src/helpers.ts +11 -2
- package/src/index.ts +12 -9
- package/src/models/aggregators/aggregated-async-iterator.ts +765 -21
- package/src/models/aggregators/aggregated-iterator.ts +698 -22
- package/src/models/aggregators/reduced-iterator.ts +699 -10
- package/src/models/aggregators/types.ts +153 -10
- package/src/models/callbacks/callable-object.ts +42 -6
- package/src/models/callbacks/index.ts +2 -2
- package/src/models/callbacks/publisher.ts +140 -5
- package/src/models/callbacks/switchable-callback.ts +143 -5
- package/src/models/callbacks/types.ts +16 -0
- package/src/models/exceptions/core.ts +112 -3
- package/src/models/exceptions/index.ts +340 -13
- package/src/models/index.ts +4 -8
- package/src/models/iterators/smart-async-iterator.ts +687 -22
- package/src/models/iterators/smart-iterator.ts +631 -21
- package/src/models/iterators/types.ts +268 -9
- package/src/models/json/json-storage.ts +388 -110
- package/src/models/json/types.ts +10 -1
- package/src/models/promises/deferred-promise.ts +75 -5
- package/src/models/promises/index.ts +1 -3
- package/src/models/promises/smart-promise.ts +232 -4
- package/src/models/promises/timed-promise.ts +38 -1
- package/src/models/promises/types.ts +84 -2
- package/src/models/timers/clock.ts +91 -19
- package/src/models/timers/countdown.ts +152 -22
- package/src/models/timers/game-loop.ts +243 -0
- package/src/models/timers/index.ts +2 -1
- package/src/models/types.ts +6 -5
- package/src/utils/async.ts +43 -0
- package/src/utils/curve.ts +75 -0
- package/src/utils/date.ts +204 -10
- package/src/utils/dom.ts +16 -2
- package/src/utils/index.ts +3 -2
- package/src/utils/iterator.ts +200 -17
- package/src/utils/math.ts +55 -3
- package/src/utils/random.ts +109 -2
- package/src/utils/string.ts +11 -0
- package/src/models/game-loop.ts +0 -83
- package/src/models/promises/long-running-task.ts +0 -294
- package/src/models/promises/thenable.ts +0 -97
|
@@ -1,19 +1,162 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
|
-
|
|
3
1
|
import type { MaybePromise } from "../promises/types.js";
|
|
4
2
|
|
|
3
|
+
/**
|
|
4
|
+
* An utility type that represents an {@link https://en.wikipedia.org/wiki/Iteratee|iteratee}-like function
|
|
5
|
+
* with the addition of a `key` parameter, compared to the JavaScript's standard ones.
|
|
6
|
+
* It can be used to transform the elements of an aggregated iterable.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const iteratee: KeyedIteratee<string, number, string> = (key: string, value: number) => `${value}`;
|
|
10
|
+
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
|
|
11
|
+
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
|
|
12
|
+
* .map(iteratee);
|
|
13
|
+
*
|
|
14
|
+
* console.log(results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @template K The type of the key used to aggregate elements in the iterable.
|
|
18
|
+
* @template T The type of the elements in the iterable.
|
|
19
|
+
* @template R The type of the return value of the iteratee. Default is `void`.
|
|
20
|
+
*/
|
|
5
21
|
export type KeyedIteratee<K extends PropertyKey, T, R = void> = (key: K, value: T, index: number) => R;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An utility type that represents an asynchronous {@link https://en.wikipedia.org/wiki/Iteratee|iteratee}-like
|
|
25
|
+
* function with the addition of a `key` parameter.
|
|
26
|
+
* It can be used to transform the elements of an aggregated iterable asynchronously.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* const iteratee: AsyncKeyedIteratee<string, number, string> = async (key: string, value: number) => `${value}`;
|
|
30
|
+
* const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
|
|
31
|
+
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
|
|
32
|
+
* .map(iteratee);
|
|
33
|
+
*
|
|
34
|
+
* console.log(await results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @template K The type of the key used to aggregate elements in the iterable.
|
|
38
|
+
* @template T The type of the elements in the iterable.
|
|
39
|
+
* @template R The type of the return value of the iteratee. Default is `void`.
|
|
40
|
+
*/
|
|
6
41
|
export type AsyncKeyedIteratee<K extends PropertyKey, T, R = void> = (key: K, value: T, index: number) => Promise<R>;
|
|
7
|
-
export type MaybeAsyncKeyedIteratee<K extends PropertyKey, T, R = void> = (key: K, value: T, index: number) => MaybePromise<R>;
|
|
8
42
|
|
|
9
|
-
|
|
43
|
+
/**
|
|
44
|
+
* An utility type that represents an {@link https://en.wikipedia.org/wiki/Iteratee|iteratee}-like function
|
|
45
|
+
* with the addition of a `key` parameter that can be either synchronous or asynchronous.
|
|
46
|
+
* It can be used to transform the elements of an aggregated iterable.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* const iteratee: AsyncKeyedIteratee<string, number, string> = [async] (key: string, value: number) => `${value}`;
|
|
50
|
+
* const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
|
|
51
|
+
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
|
|
52
|
+
* .map(iteratee);
|
|
53
|
+
*
|
|
54
|
+
* console.log(await results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @template K The type of the key used to aggregate elements in the iterable.
|
|
58
|
+
* @template T The type of the elements in the iterable.
|
|
59
|
+
* @template R The type of the return value of the iteratee. Default is `void`.
|
|
60
|
+
*/
|
|
61
|
+
export type MaybeAsyncKeyedIteratee<K extends PropertyKey, T, R = void> =
|
|
62
|
+
(key: K, value: T, index: number) => MaybePromise<R>;
|
|
10
63
|
|
|
11
|
-
|
|
12
|
-
|
|
64
|
+
/**
|
|
65
|
+
* An utility type that represents a {@link https://en.wikipedia.org/wiki/Predicate_(mathematical_logic)|predicate}-like
|
|
66
|
+
* function with the addition of a `key` parameter, compared to the JavaScript's standard ones,
|
|
67
|
+
* which act as a
|
|
68
|
+
* {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates|type guard}.
|
|
69
|
+
* It can be used to filter the elements of an aggregated iterable
|
|
70
|
+
* while allowing the type-system to infer them correctly.
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* const predicate: KeyedTypeGuardPredicate<string, number | string, string> =
|
|
74
|
+
* (key: string, value: number | string): value is string => typeof value === "string";
|
|
75
|
+
*
|
|
76
|
+
* const results = new SmartIterator<number | string>([-3, -1, "0", 2, 3, "5", 6, "8"])
|
|
77
|
+
* .groupBy((value) => Number(value) % 2 === 0 ? "even" : "odd")
|
|
78
|
+
* .filter(predicate);
|
|
79
|
+
*
|
|
80
|
+
* console.log(results.toObject()); // { odd: ["0", "5", "8"], even: [] }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @template K The type of the key used to aggregate elements in the iterable.
|
|
84
|
+
* @template T The type of the elements in the iterable.
|
|
85
|
+
* @template R
|
|
86
|
+
* The type of the return value of the predicate.
|
|
87
|
+
* It must be a subtype of `T`. Default is `T`.
|
|
88
|
+
*/
|
|
89
|
+
export type KeyedTypeGuardPredicate<K extends PropertyKey, T, R extends T> =
|
|
90
|
+
(key: K, value: T, index: number) => value is R;
|
|
13
91
|
|
|
14
|
-
//
|
|
15
|
-
|
|
92
|
+
// These types need this Issue to be solved: https://github.com/microsoft/TypeScript/issues/37681
|
|
93
|
+
//
|
|
94
|
+
// export type AsyncKeyedTypeGuardPredicate<K extends PropertyKey, T, R extends T> =
|
|
95
|
+
// (key: K, value: T, index: number) => value is Promise<R>;
|
|
96
|
+
// export type MaybeAsyncKeyedTypeGuardPredicate<K extends PropertyKey, T, R extends T> =
|
|
97
|
+
// (key: K, value: T, index: number) => value is MaybePromise<R>;
|
|
16
98
|
|
|
99
|
+
/**
|
|
100
|
+
* An utility type that represents a reducer-like function.
|
|
101
|
+
* It can be used to reduce the elements of an aggregated iterable into a single value.
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* const sum: KeyedReducer<string, number, number> =
|
|
105
|
+
* (key: string, accumulator: number, value: number) => accumulator + value;
|
|
106
|
+
*
|
|
107
|
+
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
|
|
108
|
+
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
|
|
109
|
+
* .reduce(sum);
|
|
110
|
+
*
|
|
111
|
+
* console.log(results.toObject()); // { odd: 4, even: 16 }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @template K The type of the key used to aggregate elements in the iterable.
|
|
115
|
+
* @template T The type of the elements in the iterable.
|
|
116
|
+
* @template A The type of the accumulator.
|
|
117
|
+
*/
|
|
17
118
|
export type KeyedReducer<K extends PropertyKey, T, A> = (key: K, accumulator: A, value: T, index: number) => A;
|
|
18
|
-
|
|
19
|
-
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* An utility type that represents an asynchronous reducer-like function.
|
|
122
|
+
* It can be used to reduce the elements of an aggregated iterable into a single value.
|
|
123
|
+
*
|
|
124
|
+
* ```ts
|
|
125
|
+
* const sum: AsyncKeyedReducer<string, number, number> =
|
|
126
|
+
* async (key: string, accumulator: number, value: number) => accumulator + value;
|
|
127
|
+
*
|
|
128
|
+
* const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
|
|
129
|
+
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
|
|
130
|
+
* .reduce(sum);
|
|
131
|
+
*
|
|
132
|
+
* console.log(await results.toObject()); // { odd: 4, even: 16 }
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @template K The type of the key used to aggregate elements in the iterable.
|
|
136
|
+
* @template T The type of the elements in the iterable.
|
|
137
|
+
* @template A The type of the accumulator.
|
|
138
|
+
*/
|
|
139
|
+
export type AsyncKeyedReducer<K extends PropertyKey, T, A> =
|
|
140
|
+
(key: K, accumulator: A, value: T, index: number) => Promise<A>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* An utility type that represents a reducer-like function that can be either synchronous or asynchronous.
|
|
144
|
+
* It can be used to reduce the elements of an aggregated iterable into a single value.
|
|
145
|
+
*
|
|
146
|
+
* ```ts
|
|
147
|
+
* const sum: MaybeAsyncKeyedReducer<string, number, number> =
|
|
148
|
+
* [async] (key: string, accumulator: number, value: number) => accumulator + value;
|
|
149
|
+
*
|
|
150
|
+
* const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
|
|
151
|
+
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
|
|
152
|
+
* .reduce(sum);
|
|
153
|
+
*
|
|
154
|
+
* console.log(await results.toObject()); // { odd: 4, even: 16 }
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @template K The type of the key used to aggregate elements in the iterable.
|
|
158
|
+
* @template T The type of the elements in the iterable.
|
|
159
|
+
* @template A The type of the accumulator.
|
|
160
|
+
*/
|
|
161
|
+
export type MaybeAsyncKeyedReducer<K extends PropertyKey, T, A> =
|
|
162
|
+
(key: K, accumulator: A, value: T, index: number) => MaybePromise<A>;
|
|
@@ -2,16 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Callback } from "./types.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
(...args: Parameters<T>) => ReturnType<T>;
|
|
5
|
+
const SmartFunction = (Function as unknown) as new<A extends unknown[] = [], R = void>(...args: string[])
|
|
6
|
+
=> (...args: A) => R;
|
|
8
7
|
|
|
8
|
+
/**
|
|
9
|
+
* An abstract class that can be used to implement callable objects.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* class ActivableCallback extends CallableObject<(evt: PointerEvent) => void>
|
|
13
|
+
* {
|
|
14
|
+
* public enabled = false;
|
|
15
|
+
* protected _invoke(): void
|
|
16
|
+
* {
|
|
17
|
+
* if (this.enabled) { [...] }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* const callback = new ActivableCallback();
|
|
22
|
+
*
|
|
23
|
+
* window.addEventListener("pointerdown", () => { callback.enabled = true; });
|
|
24
|
+
* window.addEventListener("pointermove", callback);
|
|
25
|
+
* window.addEventListener("pointerup", () => { callback.enabled = false; });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @template T
|
|
29
|
+
* The type signature of the callback function.
|
|
30
|
+
* It must be a function. Default is `(...args: any[]) => any`.
|
|
31
|
+
*/
|
|
9
32
|
export default abstract class CallableObject<T extends Callback<any[], any> = () => void>
|
|
10
|
-
extends SmartFunction<T
|
|
33
|
+
extends SmartFunction<Parameters<T>, ReturnType<T>>
|
|
11
34
|
{
|
|
35
|
+
/**
|
|
36
|
+
* Initializes a new instance of the {@link CallableObject} class.
|
|
37
|
+
*/
|
|
12
38
|
public constructor()
|
|
13
39
|
{
|
|
14
|
-
super(`return this.
|
|
40
|
+
super(`return this._invoke(...arguments);`);
|
|
15
41
|
|
|
16
42
|
const self = this.bind(this);
|
|
17
43
|
Object.setPrototypeOf(this, self);
|
|
@@ -19,5 +45,15 @@ export default abstract class CallableObject<T extends Callback<any[], any> = ()
|
|
|
19
45
|
return self as this;
|
|
20
46
|
}
|
|
21
47
|
|
|
22
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The method that will be called when the object is invoked.
|
|
50
|
+
* It must be implemented by the derived classes.
|
|
51
|
+
*
|
|
52
|
+
* @param args The arguments that have been passed to the object.
|
|
53
|
+
*
|
|
54
|
+
* @returns The return value of the method.
|
|
55
|
+
*/
|
|
56
|
+
protected abstract _invoke(...args: Parameters<T>): ReturnType<T>;
|
|
57
|
+
|
|
58
|
+
public readonly [Symbol.toStringTag]: string = "CallableObject";
|
|
23
59
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import CallableObject
|
|
1
|
+
import CallableObject from "./callable-object.js";
|
|
2
2
|
import Publisher from "./publisher.js";
|
|
3
3
|
import SwitchableCallback from "./switchable-callback.js";
|
|
4
4
|
|
|
5
|
-
export { CallableObject, Publisher,
|
|
5
|
+
export { CallableObject, Publisher, SwitchableCallback };
|
|
@@ -2,17 +2,131 @@ import { ReferenceException } from "../exceptions/index.js";
|
|
|
2
2
|
|
|
3
3
|
import type { Callback } from "./types.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A class implementing the
|
|
7
|
+
* {@link https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern|Publish-subscribe} pattern.
|
|
8
|
+
*
|
|
9
|
+
* It can be used to create a simple event system where objects can subscribe
|
|
10
|
+
* to events and receive notifications when the events are published.
|
|
11
|
+
* It's a simple and efficient way to decouple the objects and make them communicate with each other.
|
|
12
|
+
*
|
|
13
|
+
* Using generics, it's also possible to define the type of the events and the callbacks that can be subscribed to them.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* interface EventsMap
|
|
17
|
+
* {
|
|
18
|
+
* "player:spawn": (evt: SpawnEvent) => void;
|
|
19
|
+
* "player:move": ({ x, y }: Point) => void;
|
|
20
|
+
* "player:death": () => void;
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* const publisher = new Publisher<EventsMap>();
|
|
24
|
+
*
|
|
25
|
+
* let unsubscribe: () => void;
|
|
26
|
+
* publisher.subscribe("player:death", unsubscribe);
|
|
27
|
+
* publisher.subscribe("player:spawn", (evt) =>
|
|
28
|
+
* {
|
|
29
|
+
* unsubscribe = publisher.subscribe("player:move", ({ x, y }) => { [...] });
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @template T
|
|
34
|
+
* A map containing the names of the emittable events and the
|
|
35
|
+
* related callback signatures that can be subscribed to them.
|
|
36
|
+
* Default is `Record<string, () => void>`.
|
|
37
|
+
*/
|
|
5
38
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
39
|
export default class Publisher<T extends { [K in keyof T]: Callback<any[], any> } = Record<string, Callback>>
|
|
7
40
|
{
|
|
41
|
+
/**
|
|
42
|
+
* A map containing all the subscribers for each event.
|
|
43
|
+
*
|
|
44
|
+
* The keys are the names of the events they are subscribed to.
|
|
45
|
+
* The values are the arrays of the subscribers themselves.
|
|
46
|
+
*/
|
|
8
47
|
protected _subscribers: Map<keyof T, Callback<unknown[], unknown>[]>;
|
|
9
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Initializes a new instance of the {@link Publisher} class.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* const publisher = new Publisher();
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
10
56
|
public constructor()
|
|
11
57
|
{
|
|
12
58
|
this._subscribers = new Map();
|
|
13
59
|
}
|
|
14
60
|
|
|
15
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Unsubscribes all the subscribers from all the events.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* publisher.subscribe("player:spawn", (evt) => { [...] });
|
|
66
|
+
* publisher.subscribe("player:move", (coords) => { [...] });
|
|
67
|
+
* publisher.subscribe("player:move", () => { [...] });
|
|
68
|
+
* publisher.subscribe("player:move", ({ x, y }) => { [...] });
|
|
69
|
+
* publisher.subscribe("player:death", () => { [...] });
|
|
70
|
+
*
|
|
71
|
+
* // All these subscribers are working fine...
|
|
72
|
+
*
|
|
73
|
+
* publisher.clear();
|
|
74
|
+
*
|
|
75
|
+
* // ... but now they're all gone!
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
public clear(): void
|
|
79
|
+
{
|
|
80
|
+
this._subscribers.clear();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Publishes an event to all the subscribers.
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* publisher.subscribe("player:move", (coords) => { [...] });
|
|
88
|
+
* publisher.subscribe("player:move", ({ x, y }) => { [...] });
|
|
89
|
+
* publisher.subscribe("player:move", (evt) => { [...] });
|
|
90
|
+
*
|
|
91
|
+
* publisher.publish("player:move", { x: 10, y: 20 });
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @template K The key of the map containing the callback signature to publish.
|
|
95
|
+
*
|
|
96
|
+
* @param event The name of the event to publish.
|
|
97
|
+
* @param args The arguments to pass to the subscribers.
|
|
98
|
+
*
|
|
99
|
+
* @returns An array containing the return values of all the subscribers.
|
|
100
|
+
*/
|
|
101
|
+
public publish<K extends keyof T>(event: K, ...args: Parameters<T[K]>): ReturnType<T[K]>[]
|
|
102
|
+
{
|
|
103
|
+
const subscribers = this._subscribers.get(event);
|
|
104
|
+
if (!(subscribers)) { return []; }
|
|
105
|
+
|
|
106
|
+
return subscribers.slice()
|
|
107
|
+
.map((subscriber) => subscriber(...args)) as ReturnType<T[K]>[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Subscribes a new subscriber to an event.
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* let unsubscribe: () => void;
|
|
115
|
+
* publisher.subscribe("player:death", unsubscribe);
|
|
116
|
+
* publisher.subscribe("player:spawn", (evt) =>
|
|
117
|
+
* {
|
|
118
|
+
* unsubscribe = publisher.subscribe("player:move", ({ x, y }) => { [...] });
|
|
119
|
+
* });
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @template K The key of the map containing the callback signature to subscribe.
|
|
123
|
+
*
|
|
124
|
+
* @param event The name of the event to subscribe to.
|
|
125
|
+
* @param subscriber The subscriber to add to the event.
|
|
126
|
+
*
|
|
127
|
+
* @returns A function that can be used to unsubscribe the subscriber.
|
|
128
|
+
*/
|
|
129
|
+
public subscribe<K extends keyof T>(event: K, subscriber: T[K]): () => void
|
|
16
130
|
{
|
|
17
131
|
if (!(this._subscribers.has(event))) { this._subscribers.set(event, []); }
|
|
18
132
|
|
|
@@ -32,13 +146,34 @@ export default class Publisher<T extends { [K in keyof T]: Callback<any[], any>
|
|
|
32
146
|
};
|
|
33
147
|
}
|
|
34
148
|
|
|
35
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Unsubscribes a subscriber from an event.
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* const onPlayerMove = ({ x, y }: Point) => { [...] };
|
|
154
|
+
*
|
|
155
|
+
* publisher.subscribe("player:spawn", (evt) => publisher.subscribe("player:move", onPlayerMove));
|
|
156
|
+
* publisher.subscribe("player:death", () => publisher.unsubscribe("player:move", onPlayerMove));
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* @template K The key of the map containing the callback signature to unsubscribe.
|
|
160
|
+
*
|
|
161
|
+
* @param event The name of the event to unsubscribe from.
|
|
162
|
+
* @param subscriber The subscriber to remove from the event.
|
|
163
|
+
*/
|
|
164
|
+
public unsubscribe<K extends keyof T>(event: K, subscriber: T[K]): void
|
|
36
165
|
{
|
|
37
166
|
const subscribers = this._subscribers.get(event);
|
|
38
|
-
if (!(subscribers)) { return
|
|
167
|
+
if (!(subscribers)) { return; }
|
|
39
168
|
|
|
40
|
-
|
|
41
|
-
|
|
169
|
+
const index = subscribers.indexOf(subscriber);
|
|
170
|
+
if (index < 0)
|
|
171
|
+
{
|
|
172
|
+
throw new ReferenceException("Unable to unsubscribe the required subscriber. " +
|
|
173
|
+
"The subscription was already unsubscribed or was never subscribed.");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
subscribers.splice(index, 1);
|
|
42
177
|
}
|
|
43
178
|
|
|
44
179
|
public readonly [Symbol.toStringTag]: string = "Publisher";
|
|
@@ -3,20 +3,83 @@ import { KeyException, NotImplementedException, RuntimeException } from "../exce
|
|
|
3
3
|
import CallableObject from "./callable-object.js";
|
|
4
4
|
import type { Callback } from "./types.js";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* A class representing a callback that can be switched between multiple implementations.
|
|
8
|
+
*
|
|
9
|
+
* It can be used to implement different behaviors for the same event handler, allowing
|
|
10
|
+
* it to respond to different states without incurring any overhead during execution.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const onPointerMove = new SwitchableCallback<(evt: PointerEvent) => void>();
|
|
14
|
+
*
|
|
15
|
+
* onPointerMove.register("released", () => { [...] });
|
|
16
|
+
* onPointerMove.register("pressed", () => { [...] });
|
|
17
|
+
*
|
|
18
|
+
* window.addEventListener("pointerdown", () => { onPointerMove.switch("pressed"); });
|
|
19
|
+
* window.addEventListener("pointermove", onPointerMove);
|
|
20
|
+
* window.addEventListener("pointerup", () => { onPointerMove.switch("released"); });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @template T The type signature of the callback. Default is `(...args: any[]) => any`.
|
|
24
|
+
*/
|
|
6
25
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
26
|
export default class SwitchableCallback<T extends Callback<any[], any> = Callback> extends CallableObject<T>
|
|
8
27
|
{
|
|
28
|
+
/**
|
|
29
|
+
* The currently selected implementation of the callback.
|
|
30
|
+
*/
|
|
9
31
|
protected _callback: T;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* All the implementations that have been registered for the callback.
|
|
35
|
+
*
|
|
36
|
+
* The keys are the names of the implementations they were registered with.
|
|
37
|
+
* The values are the implementations themselves.
|
|
38
|
+
*/
|
|
10
39
|
protected _callbacks: Map<string, T>;
|
|
11
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A flag indicating whether the callback is enabled or not.
|
|
43
|
+
*
|
|
44
|
+
* This protected property is the only one that can be modified directly by the derived classes.
|
|
45
|
+
* If you're looking for the public and readonly property, use
|
|
46
|
+
* the {@link SwitchableCallback.isEnabled} getter instead.
|
|
47
|
+
*/
|
|
12
48
|
protected _isEnabled: boolean;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A flag indicating whether the callback is enabled or not.
|
|
52
|
+
*
|
|
53
|
+
* It indicates whether the callback is currently able to execute the currently selected implementation.
|
|
54
|
+
* If it's disabled, the callback will be invoked without executing anything.
|
|
55
|
+
*/
|
|
13
56
|
public get isEnabled(): boolean { return this._isEnabled; }
|
|
14
57
|
|
|
58
|
+
/**
|
|
59
|
+
* The key that is associated with the currently selected implementation.
|
|
60
|
+
*
|
|
61
|
+
* This protected property is the only one that can be modified directly by the derived classes.
|
|
62
|
+
* If you're looking for the public and readonly property, use the {@link SwitchableCallback.key} getter instead.
|
|
63
|
+
*/
|
|
15
64
|
protected _key: string;
|
|
16
|
-
public get key(): string { return this._key; }
|
|
17
65
|
|
|
18
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The key that is associated with the currently selected implementation.
|
|
68
|
+
*/
|
|
69
|
+
public get key(): string { return this._key; }
|
|
19
70
|
|
|
71
|
+
/**
|
|
72
|
+
* The function that will be called by the extended class when the object is invoked as a function.
|
|
73
|
+
*/
|
|
74
|
+
protected readonly _invoke: (...args: Parameters<T>) => ReturnType<T>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initializes a new instance of the {@link SwitchableCallback} class.
|
|
78
|
+
*
|
|
79
|
+
* ```ts
|
|
80
|
+
* const onPointerMove = new SwitchableCallback<(evt: PointerEvent) => void>();
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
20
83
|
public constructor()
|
|
21
84
|
{
|
|
22
85
|
const _default = () =>
|
|
@@ -32,12 +95,24 @@ export default class SwitchableCallback<T extends Callback<any[], any> = Callbac
|
|
|
32
95
|
this._callback = ((_default) as unknown) as T;
|
|
33
96
|
this._callbacks = new Map<string, T>();
|
|
34
97
|
|
|
35
|
-
this._isEnabled =
|
|
98
|
+
this._isEnabled = true;
|
|
36
99
|
this._key = "";
|
|
37
100
|
|
|
38
|
-
this.
|
|
101
|
+
this._invoke = (...args: Parameters<T>): ReturnType<T> => this._callback(...args);
|
|
39
102
|
}
|
|
40
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Enables the callback, allowing it to execute the currently selected implementation.
|
|
106
|
+
*
|
|
107
|
+
* Also note that:
|
|
108
|
+
* - If any implementation has been registered yet, a {@link KeyException} will be thrown.
|
|
109
|
+
* - If the callback is already enabled, a {@link RuntimeException} will be thrown.
|
|
110
|
+
*
|
|
111
|
+
* ```ts
|
|
112
|
+
* window.addEventListener("pointerdown", () => { onPointerMove.enable(); });
|
|
113
|
+
* window.addEventListener("pointermove", onPointerMove);
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
41
116
|
public enable(): void
|
|
42
117
|
{
|
|
43
118
|
if (!(this._key))
|
|
@@ -55,6 +130,17 @@ export default class SwitchableCallback<T extends Callback<any[], any> = Callbac
|
|
|
55
130
|
this._callback = this._callbacks.get(this._key)!;
|
|
56
131
|
this._isEnabled = true;
|
|
57
132
|
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Disables the callback, allowing it to be invoked without executing any implementation.
|
|
136
|
+
*
|
|
137
|
+
* If the callback is already disabled, a {@link RuntimeException} will be thrown.
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* window.addEventListener("pointermove", onPointerMove);
|
|
141
|
+
* window.addEventListener("pointerup", () => { onPointerMove.disable(); });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
58
144
|
public disable(): void
|
|
59
145
|
{
|
|
60
146
|
if (!(this._isEnabled))
|
|
@@ -67,6 +153,21 @@ export default class SwitchableCallback<T extends Callback<any[], any> = Callbac
|
|
|
67
153
|
this._isEnabled = false;
|
|
68
154
|
}
|
|
69
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Registers a new implementation for the callback.
|
|
158
|
+
*
|
|
159
|
+
* Also note that:
|
|
160
|
+
* - If the callback has no other implementation registered yet, this one will be selected as default.
|
|
161
|
+
* - If the key has already been used for another implementation, a {@link KeyException} will be thrown.
|
|
162
|
+
*
|
|
163
|
+
* ```ts
|
|
164
|
+
* onPointerMove.register("pressed", () => { [...] });
|
|
165
|
+
* onPointerMove.register("released", () => { [...] });
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @param key The key that will be associated with the implementation.
|
|
169
|
+
* @param callback The implementation to register.
|
|
170
|
+
*/
|
|
70
171
|
public register(key: string, callback: T): void
|
|
71
172
|
{
|
|
72
173
|
if (this._callbacks.size === 0)
|
|
@@ -81,8 +182,26 @@ export default class SwitchableCallback<T extends Callback<any[], any> = Callbac
|
|
|
81
182
|
|
|
82
183
|
this._callbacks.set(key, callback);
|
|
83
184
|
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Unregisters an implementation for the callback.
|
|
188
|
+
*
|
|
189
|
+
* Also note that:
|
|
190
|
+
* - If the key is the currently selected implementation, a {@link KeyException} will be thrown.
|
|
191
|
+
* - If the key has no associated implementation yet, a {@link KeyException} will be thrown.
|
|
192
|
+
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* onPointerMove.unregister("released");
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* @param key The key that is associated with the implementation to unregister.
|
|
198
|
+
*/
|
|
84
199
|
public unregister(key: string): void
|
|
85
200
|
{
|
|
201
|
+
if (this._key === key)
|
|
202
|
+
{
|
|
203
|
+
throw new KeyException("Unable to unregister the currently selected callback.");
|
|
204
|
+
}
|
|
86
205
|
if (!(this._callbacks.has(key)))
|
|
87
206
|
{
|
|
88
207
|
throw new KeyException(`The key '${key}' doesn't yet have any associated callback.`);
|
|
@@ -91,6 +210,19 @@ export default class SwitchableCallback<T extends Callback<any[], any> = Callbac
|
|
|
91
210
|
this._callbacks.delete(key);
|
|
92
211
|
}
|
|
93
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Switches the callback to the implementation associated with the given key.
|
|
215
|
+
*
|
|
216
|
+
* If the key has no associated implementation yet, a {@link KeyException} will be thrown.
|
|
217
|
+
*
|
|
218
|
+
* ```ts
|
|
219
|
+
* window.addEventListener("pointerdown", () => { onPointerMove.switch("pressed"); });
|
|
220
|
+
* window.addEventListener("pointermove", onPointerMove);
|
|
221
|
+
* window.addEventListener("pointerup", () => { onPointerMove.switch("released"); });
|
|
222
|
+
* ```
|
|
223
|
+
*
|
|
224
|
+
* @param key The key that is associated with the implementation to switch to.
|
|
225
|
+
*/
|
|
94
226
|
public switch(key: string): void
|
|
95
227
|
{
|
|
96
228
|
if (!(this._callbacks.has(key)))
|
|
@@ -99,6 +231,12 @@ export default class SwitchableCallback<T extends Callback<any[], any> = Callbac
|
|
|
99
231
|
}
|
|
100
232
|
|
|
101
233
|
this._key = key;
|
|
102
|
-
|
|
234
|
+
|
|
235
|
+
if (this._isEnabled)
|
|
236
|
+
{
|
|
237
|
+
this._callback = this._callbacks.get(key)!;
|
|
238
|
+
}
|
|
103
239
|
}
|
|
240
|
+
|
|
241
|
+
public override readonly [Symbol.toStringTag]: string = "SwitchableCallback";
|
|
104
242
|
}
|
|
@@ -1 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type that represents a generic function.
|
|
3
|
+
*
|
|
4
|
+
* It can be used to define the signature of a callback, a event handler or any other function.
|
|
5
|
+
* It's simply a shorthand for the `(...args: A) => R` function signature.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const callback: Callback<[PointerEvent]> = (evt: PointerEvent): void => { [...] };
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* @template A
|
|
12
|
+
* The type of the arguments that the function accepts.
|
|
13
|
+
* It must be an array of types, even if it's empty. Default is `[]`.
|
|
14
|
+
*
|
|
15
|
+
* @template R The return type of the function. Default is `void`.
|
|
16
|
+
*/
|
|
1
17
|
export type Callback<A extends unknown[] = [], R = void> = (...args: A) => R;
|