@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.
Files changed (47) hide show
  1. package/dist/core.js +3372 -609
  2. package/dist/core.js.map +1 -1
  3. package/dist/core.umd.cjs +2 -2
  4. package/dist/core.umd.cjs.map +1 -1
  5. package/package.json +13 -10
  6. package/src/core/types.ts +41 -0
  7. package/src/helpers.ts +11 -2
  8. package/src/index.ts +12 -9
  9. package/src/models/aggregators/aggregated-async-iterator.ts +765 -21
  10. package/src/models/aggregators/aggregated-iterator.ts +698 -22
  11. package/src/models/aggregators/reduced-iterator.ts +699 -10
  12. package/src/models/aggregators/types.ts +153 -10
  13. package/src/models/callbacks/callable-object.ts +42 -6
  14. package/src/models/callbacks/index.ts +2 -2
  15. package/src/models/callbacks/publisher.ts +140 -5
  16. package/src/models/callbacks/switchable-callback.ts +143 -5
  17. package/src/models/callbacks/types.ts +16 -0
  18. package/src/models/exceptions/core.ts +112 -3
  19. package/src/models/exceptions/index.ts +340 -13
  20. package/src/models/index.ts +4 -8
  21. package/src/models/iterators/smart-async-iterator.ts +687 -22
  22. package/src/models/iterators/smart-iterator.ts +631 -21
  23. package/src/models/iterators/types.ts +268 -9
  24. package/src/models/json/json-storage.ts +388 -110
  25. package/src/models/json/types.ts +10 -1
  26. package/src/models/promises/deferred-promise.ts +75 -5
  27. package/src/models/promises/index.ts +1 -3
  28. package/src/models/promises/smart-promise.ts +232 -4
  29. package/src/models/promises/timed-promise.ts +38 -1
  30. package/src/models/promises/types.ts +84 -2
  31. package/src/models/timers/clock.ts +91 -19
  32. package/src/models/timers/countdown.ts +152 -22
  33. package/src/models/timers/game-loop.ts +243 -0
  34. package/src/models/timers/index.ts +2 -1
  35. package/src/models/types.ts +6 -5
  36. package/src/utils/async.ts +43 -0
  37. package/src/utils/curve.ts +75 -0
  38. package/src/utils/date.ts +204 -10
  39. package/src/utils/dom.ts +16 -2
  40. package/src/utils/index.ts +3 -2
  41. package/src/utils/iterator.ts +200 -17
  42. package/src/utils/math.ts +55 -3
  43. package/src/utils/random.ts +109 -2
  44. package/src/utils/string.ts +11 -0
  45. package/src/models/game-loop.ts +0 -83
  46. package/src/models/promises/long-running-task.ts +0 -294
  47. 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
- export type KeyedTypeGuardIteratee<K extends PropertyKey, T, R extends T> = (key: K, value: T, index: number) => value is R;
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
- // @ts-expect-error - This is an asyncronous type guard keyed-iteratee that guarantees the return value is a promise.
12
- export type AsyncKeyedTypeGuardIteratee<K extends PropertyKey, T, R extends T> = (key: K, value: T, index: number) => value is Promise<R>;
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
- // @ts-expect-error - This may be an asyncronous type guard keyed-iteratee that guarantees the return value may be a promise.
15
- export type MaybeAsyncKeyedTypeGuardIteratee<K extends PropertyKey, T, R extends T> = (key: K, value: T, index: number) => value is MaybePromise<R>;
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
- export type AsyncKeyedReducer<K extends PropertyKey, T, A> = (key: K, accumulator: A, value: T, index: number) => Promise<A>;
19
- export type MaybeAsyncKeyedReducer<K extends PropertyKey, T, A> = (key: K, accumulator: A, value: T, index: number) => MaybePromise<A>;
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
- export const SmartFunction = (Function as unknown) as
6
- new<T extends Callback<any[], any> = () => void>(...args: string[]) =>
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.invoke(...arguments);`);
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
- public abstract invoke(...args: Parameters<T>): ReturnType<T>;
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, { SmartFunction } from "./callable-object.js";
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, SmartFunction, SwitchableCallback };
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
- public subscribe<K extends keyof T, S extends T[K]>(event: K, subscriber: S): () => void
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
- public publish<K extends keyof T, A extends Parameters<T[K]>, R extends ReturnType<T[K]>>(event: K, ...args: A): R[]
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
- return subscribers.slice()
41
- .map((subscriber) => subscriber(...args)) as R[];
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
- public readonly invoke: (...args: Parameters<T>) => ReturnType<T>;
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 = false;
98
+ this._isEnabled = true;
36
99
  this._key = "";
37
100
 
38
- this.invoke = (...args: Parameters<T>): ReturnType<T> => this._callback(...args);
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
- this._callback = this._callbacks.get(key)!;
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;