@inglorious/utils 3.8.0 → 5.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/README.md +83 -3
- package/package.json +104 -12
- package/src/algorithms/decision-tree.js +3 -3
- package/src/algorithms/path-finding.js +6 -6
- package/src/data-structures/array.js +12 -0
- package/src/data-structures/board.js +97 -15
- package/src/data-structures/index.js +1 -2
- package/src/data-structures/object.js +164 -2
- package/src/data-structures/string.js +77 -0
- package/src/data-structures/tree.js +1 -1
- package/src/functions/function.js +190 -0
- package/src/functions/index.js +1 -4
- package/src/math/circle.js +5 -7
- package/src/math/hitmask.js +1 -1
- package/src/math/index.js +2 -6
- package/src/math/line.js +4 -4
- package/src/math/point.js +31 -9
- package/src/math/quaternion.js +19 -2
- package/src/math/rectangle.js +16 -4
- package/src/math/rng.js +5 -2
- package/src/math/segment.js +11 -5
- package/src/math/trigonometry.js +1 -1
- package/src/math/vector.js +288 -37
- package/src/physics/acceleration.js +2 -3
- package/src/physics/friction.js +1 -1
- package/src/physics/gravity.js +1 -1
- package/src/physics/jump.js +0 -4
- package/src/physics/velocity.js +2 -3
- package/src/v.js +1 -1
- package/types/algorithms/decision-tree.d.ts +27 -0
- package/types/algorithms/index.d.ts +2 -0
- package/types/algorithms/path-finding.d.ts +69 -0
- package/types/data-structures/array.d.ts +61 -0
- package/types/data-structures/board.d.ts +121 -0
- package/types/data-structures/boolean.d.ts +4 -0
- package/types/data-structures/heap.d.ts +69 -0
- package/types/data-structures/index.d.ts +8 -0
- package/types/data-structures/object.d.ts +237 -0
- package/types/data-structures/set.d.ts +4 -0
- package/types/data-structures/tree.d.ts +21 -0
- package/types/functions/function.d.ts +57 -0
- package/types/functions/index.d.ts +1 -0
- package/types/index.d.ts +6 -4
- package/types/math/circle.d.ts +22 -0
- package/types/math/hitmask.d.ts +20 -0
- package/types/math/index.d.ts +14 -0
- package/types/math/line.d.ts +11 -0
- package/types/math/linear-interpolation.d.ts +2 -0
- package/types/math/number.d.ts +26 -0
- package/types/math/point.d.ts +50 -0
- package/types/math/quaternion.d.ts +10 -0
- package/types/math/rectangle.d.ts +57 -0
- package/types/math/rng.d.ts +14 -0
- package/types/math/segment.d.ts +23 -0
- package/types/math/statistics.d.ts +11 -0
- package/types/math/triangle.d.ts +5 -0
- package/types/math/trigonometry.d.ts +26 -0
- package/types/math/vector.d.ts +219 -0
- package/types/physics/acceleration.d.ts +24 -0
- package/types/physics/friction.d.ts +11 -0
- package/types/physics/gravity.d.ts +20 -0
- package/types/physics/index.d.ts +5 -0
- package/types/physics/jump.d.ts +10 -0
- package/types/physics/velocity.d.ts +20 -0
- package/types/v.d.ts +7 -3
- package/src/data-structures/objects.js +0 -113
- package/src/functions/functions.js +0 -31
- package/src/math/quaternions.js +0 -23
- package/src/math/vectors.js +0 -212
- package/types/algorithms.d.ts +0 -26
- package/types/data-structures.d.ts +0 -53
- package/types/math.d.ts +0 -90
- /package/src/math/{numbers.js → number.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isFunction } from "../functions/function.js"
|
|
1
2
|
import { v } from "../v.js"
|
|
2
3
|
import { isArray } from "./array.js"
|
|
3
4
|
|
|
@@ -27,6 +28,57 @@ export function clone(obj) {
|
|
|
27
28
|
return deserialize(serialize(obj))
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Recursively merges properties from a source object into a target object.
|
|
33
|
+
* This is a helper function for `merge` and `extend`.
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} target - The target object to merge into.
|
|
36
|
+
* @param {Object} source - The source object to merge from.
|
|
37
|
+
* @param {Function} [merger] - An optional function to customize merging behavior for specific keys.
|
|
38
|
+
* @returns {Object} - The modified target object.
|
|
39
|
+
*/
|
|
40
|
+
function deepMerge(target, source, merger) {
|
|
41
|
+
for (const [key, value] of Object.entries(source)) {
|
|
42
|
+
if (isFunction(merger)) {
|
|
43
|
+
const mergedValue = merger(target[key], value)
|
|
44
|
+
if (mergedValue !== undefined) {
|
|
45
|
+
target[key] = mergedValue
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isArray(value)) {
|
|
51
|
+
target[key] = value
|
|
52
|
+
} else if (isObject(value)) {
|
|
53
|
+
if (!isObject(target[key])) {
|
|
54
|
+
target[key] = {}
|
|
55
|
+
}
|
|
56
|
+
target[key] = deepMerge(target[key], value, merger)
|
|
57
|
+
} else {
|
|
58
|
+
target[key] = value
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return target
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Assigns default properties to a target object from a source object.
|
|
66
|
+
* For each key in `defaultProps`, if `target[key]` is `null` or `undefined`,
|
|
67
|
+
* it is set to `defaultProps[key]`.
|
|
68
|
+
*
|
|
69
|
+
* This function modifies the target object in place.
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} target The object to apply defaults to.
|
|
72
|
+
* @param {Object} defaultProps The object containing the default properties.
|
|
73
|
+
* @returns {Object} The modified target object.
|
|
74
|
+
*/
|
|
75
|
+
export function defaults(target, defaultProps) {
|
|
76
|
+
for (const key in defaultProps) {
|
|
77
|
+
target[key] ??= defaultProps[key]
|
|
78
|
+
}
|
|
79
|
+
return target
|
|
80
|
+
}
|
|
81
|
+
|
|
30
82
|
/**
|
|
31
83
|
* Deserializes an object, converting plain object representations back into
|
|
32
84
|
* their original types, such as vector-like objects. This is the inverse
|
|
@@ -66,6 +118,35 @@ export function deserialize(str) {
|
|
|
66
118
|
}
|
|
67
119
|
}
|
|
68
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Creates a new object by deeply merging a target object with one or more source objects.
|
|
123
|
+
* This function is immutable; it does not modify the original `target` or `sources`.
|
|
124
|
+
* Nested objects are merged recursively.
|
|
125
|
+
* Arrays from a source object will overwrite arrays in the target object.
|
|
126
|
+
*
|
|
127
|
+
* @param {Object} target - The base object.
|
|
128
|
+
* @param {...Object} sources - The source objects to merge into the target object.
|
|
129
|
+
* @returns {Object} - A new object containing the merged properties.
|
|
130
|
+
* @see {@link merge} for a mutable version.
|
|
131
|
+
*/
|
|
132
|
+
export function extend(target, ...sources) {
|
|
133
|
+
return extendWith(undefined, target, ...sources)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a new object by deeply merging a target object with one or more source objects,
|
|
138
|
+
* using a custom merger function to handle specific properties.
|
|
139
|
+
* This function is immutable.
|
|
140
|
+
*
|
|
141
|
+
* @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
|
|
142
|
+
* @param {Object} target - The base object.
|
|
143
|
+
* @param {...Object} sources - The source objects to merge.
|
|
144
|
+
* @returns {Object} A new object with the merged properties.
|
|
145
|
+
*/
|
|
146
|
+
export function extendWith(merger, target, ...sources) {
|
|
147
|
+
return mergeWith(merger, {}, target, ...sources)
|
|
148
|
+
}
|
|
149
|
+
|
|
69
150
|
/**
|
|
70
151
|
* Filters the properties of an object based on a callback function.
|
|
71
152
|
*
|
|
@@ -138,6 +219,57 @@ export function get(obj, path, defaultValue = undefined) {
|
|
|
138
219
|
* @param {*} value - The value to check.
|
|
139
220
|
* @returns {boolean} True if the value is a plain object, false otherwise.
|
|
140
221
|
*/
|
|
222
|
+
/**
|
|
223
|
+
* Whether two values are the same, all the way down.
|
|
224
|
+
*
|
|
225
|
+
* Written here rather than with `JSON.stringify` because stringify cannot tell a key
|
|
226
|
+
* that was left out from one that was set to nothing, and those are different: a key
|
|
227
|
+
* mapped to `undefined` is a key with nothing mapped to it, which is how a mapping is
|
|
228
|
+
* taken off.
|
|
229
|
+
*
|
|
230
|
+
* @param {*} left - The first value.
|
|
231
|
+
* @param {*} right - The second value.
|
|
232
|
+
* @param {WeakMap} [seen] - The pairs already compared, so a structure that contains
|
|
233
|
+
* itself is not walked for ever. Passed along rather than asked for.
|
|
234
|
+
*
|
|
235
|
+
* What it does not settle is whether two structures *share* the same parts. One value
|
|
236
|
+
* used twice against two equal ones written out twice are reported as different, because
|
|
237
|
+
* only the pairs already met are remembered. That errs towards calling things changed
|
|
238
|
+
* when they are not, which is the safe way round for the thing this is asked about.
|
|
239
|
+
*
|
|
240
|
+
* @returns {boolean} Whether the two are the same.
|
|
241
|
+
*/
|
|
242
|
+
export function isDeepEqual(left, right, seen = new WeakMap()) {
|
|
243
|
+
if (Object.is(left, right)) return true
|
|
244
|
+
|
|
245
|
+
// `Object.is` has already said no to these two being the same object, and neither can be
|
|
246
|
+
// walked: a function is equal to itself and nothing else, and a key present in one and
|
|
247
|
+
// not the other is a difference even when both read as undefined.
|
|
248
|
+
if (typeof left !== "object" || typeof right !== "object") return false
|
|
249
|
+
if (left === null || right === null) return false
|
|
250
|
+
|
|
251
|
+
if (Array.isArray(left) !== Array.isArray(right)) return false
|
|
252
|
+
|
|
253
|
+
// A thing that contains itself would otherwise be walked for ever. Meeting the same
|
|
254
|
+
// thing twice means it has already been compared, and it was the same then or the walk
|
|
255
|
+
// would have stopped there -- so the only question left is whether it was matched to
|
|
256
|
+
// this one. A structure that loops back to itself differently is a difference, which is
|
|
257
|
+
// what stops this reporting two equal loops that are not.
|
|
258
|
+
if (seen.has(left)) return seen.get(left) === right
|
|
259
|
+
|
|
260
|
+
seen.set(left, right)
|
|
261
|
+
|
|
262
|
+
const leftKeys = Object.keys(left)
|
|
263
|
+
const rightKeys = Object.keys(right)
|
|
264
|
+
|
|
265
|
+
if (leftKeys.length !== rightKeys.length) return false
|
|
266
|
+
|
|
267
|
+
return leftKeys.every(
|
|
268
|
+
(key) =>
|
|
269
|
+
Object.hasOwn(right, key) && isDeepEqual(left[key], right[key], seen),
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
141
273
|
export function isObject(value) {
|
|
142
274
|
return value != null && value.constructor === Object
|
|
143
275
|
}
|
|
@@ -157,6 +289,36 @@ export function map(obj, callback) {
|
|
|
157
289
|
}, {})
|
|
158
290
|
}
|
|
159
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Merges multiple source objects into a target object.
|
|
294
|
+
* This function is mutable; it modifies the `target` object in place.
|
|
295
|
+
* Nested objects are merged recursively.
|
|
296
|
+
* Arrays from a source object will overwrite arrays in the target object.
|
|
297
|
+
*
|
|
298
|
+
* @param {Object} target - The target object to merge into.
|
|
299
|
+
* @param {...Object} sources - The source objects to merge from.
|
|
300
|
+
* @returns {Object} - The merged target object.
|
|
301
|
+
* @see {@link extend} for an immutable version.
|
|
302
|
+
*/
|
|
303
|
+
export function merge(target, ...sources) {
|
|
304
|
+
return mergeWith(undefined, target, ...sources)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Merges multiple source objects into a target object, using a custom merger function.
|
|
309
|
+
* This function is mutable; it modifies the `target` object in place.
|
|
310
|
+
*
|
|
311
|
+
* @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
|
|
312
|
+
* @param {Object} target - The target object to merge into.
|
|
313
|
+
* @param {...Object} sources - The source objects to merge from.
|
|
314
|
+
* @returns {Object} The merged target object.
|
|
315
|
+
*/
|
|
316
|
+
export function mergeWith(merger, target, ...sources) {
|
|
317
|
+
return sources
|
|
318
|
+
.filter((source) => source != null)
|
|
319
|
+
.reduce((acc, source) => deepMerge(acc, source, merger), target)
|
|
320
|
+
}
|
|
321
|
+
|
|
160
322
|
/**
|
|
161
323
|
* A utility function inspired by Immer's `produce` API. It provides a convenient
|
|
162
324
|
* way to work with immutable data structures by allowing "mutations" on a
|
|
@@ -208,7 +370,7 @@ export function produce(baseState, recipe, ...args) {
|
|
|
208
370
|
*/
|
|
209
371
|
export function serialize(obj) {
|
|
210
372
|
function replacer(key, value) {
|
|
211
|
-
// Handle top-
|
|
373
|
+
// Handle top-stage vector
|
|
212
374
|
if (value?.__isVector__) {
|
|
213
375
|
return {
|
|
214
376
|
_type: "vector",
|
|
@@ -282,7 +444,7 @@ export function set(obj, path, value) {
|
|
|
282
444
|
* Converts an object or array to a formatted string representation.
|
|
283
445
|
*
|
|
284
446
|
* @param {*} obj - The object or array to convert.
|
|
285
|
-
* @param {number} [indentationLevel=INITIAL_LEVEL] - The current indentation
|
|
447
|
+
* @param {number} [indentationLevel=INITIAL_LEVEL] - The current indentation stage (used for nested structures).
|
|
286
448
|
* @returns {string} A string representation of the input object or array.
|
|
287
449
|
*/
|
|
288
450
|
export function toString(obj, indentationLevel = INITIAL_LEVEL) {
|
|
@@ -11,3 +11,80 @@ export function toCamelCase(input) {
|
|
|
11
11
|
|
|
12
12
|
return [firstChar.toLowerCase(), ...rest].join("")
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
// How long a row or column is by one, which is the whole of what the offsets below mean.
|
|
16
|
+
// The grid's own edges are counted by the same one.
|
|
17
|
+
const ONE = 1
|
|
18
|
+
const NONE = 0
|
|
19
|
+
const FIRST = 0
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* How far one string is from another, in single-character edits.
|
|
23
|
+
*
|
|
24
|
+
* The grid is built in full rather than in the one row that would do, because this is
|
|
25
|
+
* short and the short version is where an off-by-one hides.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```js
|
|
29
|
+
* import { editDistance } from "@inglorious/utils/data-structures/string.js"
|
|
30
|
+
*
|
|
31
|
+
* editDistance("audio", "audi") // 1
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @param {string} a - The string that was asked about.
|
|
35
|
+
* @param {string} b - The string to compare it with.
|
|
36
|
+
* @returns {number} How many edits turn one into the other.
|
|
37
|
+
*/
|
|
38
|
+
export function editDistance(a, b) {
|
|
39
|
+
const grid = Array.from({ length: a.length + ONE }, () =>
|
|
40
|
+
Array.from({ length: b.length + ONE }, () => NONE),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
for (let i = FIRST; i <= a.length; i++) grid[i][FIRST] = i
|
|
44
|
+
for (let j = FIRST; j <= b.length; j++) grid[FIRST][j] = j
|
|
45
|
+
|
|
46
|
+
for (let i = ONE; i <= a.length; i++) {
|
|
47
|
+
for (let j = ONE; j <= b.length; j++) {
|
|
48
|
+
grid[i][j] = Math.min(
|
|
49
|
+
grid[i - ONE][j] + ONE,
|
|
50
|
+
grid[i][j - ONE] + ONE,
|
|
51
|
+
grid[i - ONE][j - ONE] + (a[i - ONE] === b[j - ONE] ? NONE : ONE),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return grid[a.length][b.length]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// How far a string may be from the one asked about and still be worth suggesting. A
|
|
60
|
+
// mistyped name is nearly always one or two edits away from the right one; anything
|
|
61
|
+
// further off is a different string rather than a wrong one.
|
|
62
|
+
const NEAR_ENOUGH = 3
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The strings closest to one that was asked for, nearest first.
|
|
66
|
+
*
|
|
67
|
+
* Case is folded, because a name differing from a declared one only by a capital is the
|
|
68
|
+
* commonest kind of mistake and the one where saying nothing is least helpful.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```js
|
|
72
|
+
* import { namesNear } from "@inglorious/utils/data-structures/string.js"
|
|
73
|
+
*
|
|
74
|
+
* namesNear("audio", ["Game", "Audio"]) // ["Audio"]
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param {string} wanted - The string that was asked for.
|
|
78
|
+
* @param {string[]} candidates - Every string that could have been meant.
|
|
79
|
+
* @returns {string[]} The ones worth suggesting, which may be none of them.
|
|
80
|
+
*/
|
|
81
|
+
export function namesNear(wanted, candidates) {
|
|
82
|
+
const target = wanted.toLowerCase()
|
|
83
|
+
const allowed = Math.max(ONE, Math.floor(target.length / NEAR_ENOUGH))
|
|
84
|
+
|
|
85
|
+
return candidates
|
|
86
|
+
.map((name) => [name, editDistance(target, name.toLowerCase())])
|
|
87
|
+
.filter(([, d]) => d <= allowed)
|
|
88
|
+
.sort(([, a], [, b]) => a - b)
|
|
89
|
+
.map(([name]) => name)
|
|
90
|
+
}
|
|
@@ -1,3 +1,89 @@
|
|
|
1
|
+
const DEFAULT_DELAY = 0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Composes multiple functions from right to left, as if every function wraps the next one.
|
|
5
|
+
* The first function (rightmost) can take multiple arguments; the remaining functions must be unary.
|
|
6
|
+
*
|
|
7
|
+
* @param {...Function} fns - Functions to compose.
|
|
8
|
+
* @returns {Function} A function that takes the initial arguments and applies the composed functions.
|
|
9
|
+
*/
|
|
10
|
+
export function compose(...fns) {
|
|
11
|
+
if (!fns.length) {
|
|
12
|
+
return (x) => x
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const [last, ...rest] = fns.reverse()
|
|
16
|
+
return (...args) => rest.reduce((acc, fn) => fn(acc), last(...args))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a debounced function that delays invoking `fn` until after `delay`
|
|
21
|
+
* milliseconds have elapsed since the last call.
|
|
22
|
+
*
|
|
23
|
+
* @param {Function} fn - The function to debounce.
|
|
24
|
+
* @param {number} [delay=0] - The number of milliseconds to delay.
|
|
25
|
+
* @returns {Function & { cancel: Function, flush: Function, pending: Function }} The debounced function.
|
|
26
|
+
*/
|
|
27
|
+
export function debounce(fn, delay = DEFAULT_DELAY) {
|
|
28
|
+
let timeoutId
|
|
29
|
+
let lastArgs
|
|
30
|
+
let lastThis
|
|
31
|
+
let result
|
|
32
|
+
|
|
33
|
+
function clearPending() {
|
|
34
|
+
lastArgs = undefined
|
|
35
|
+
lastThis = undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function invoke() {
|
|
39
|
+
const args = lastArgs
|
|
40
|
+
const context = lastThis
|
|
41
|
+
|
|
42
|
+
clearPending()
|
|
43
|
+
result = fn.apply(context, args)
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function debounced(...args) {
|
|
48
|
+
lastArgs = args
|
|
49
|
+
lastThis = this
|
|
50
|
+
|
|
51
|
+
if (timeoutId !== undefined) {
|
|
52
|
+
clearTimeout(timeoutId)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
timeoutId = setTimeout(() => {
|
|
56
|
+
timeoutId = undefined
|
|
57
|
+
invoke()
|
|
58
|
+
}, delay)
|
|
59
|
+
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
debounced.cancel = () => {
|
|
64
|
+
if (timeoutId !== undefined) {
|
|
65
|
+
clearTimeout(timeoutId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
timeoutId = undefined
|
|
69
|
+
clearPending()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
debounced.flush = () => {
|
|
73
|
+
if (timeoutId === undefined) {
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clearTimeout(timeoutId)
|
|
78
|
+
timeoutId = undefined
|
|
79
|
+
return invoke()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
debounced.pending = () => timeoutId !== undefined
|
|
83
|
+
|
|
84
|
+
return debounced
|
|
85
|
+
}
|
|
86
|
+
|
|
1
87
|
/**
|
|
2
88
|
* Checks if a value is a function.
|
|
3
89
|
*
|
|
@@ -7,3 +93,107 @@
|
|
|
7
93
|
export function isFunction(func) {
|
|
8
94
|
return typeof func === "function"
|
|
9
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Pipes multiple functions from left to right, as if the functions are applied one by one.
|
|
99
|
+
* The first function (leftmost) can take multiple arguments; the remaining functions must be unary.
|
|
100
|
+
*
|
|
101
|
+
* @param {...Function} fns - Functions to pipe.
|
|
102
|
+
* @returns {Function} A function that takes the initial arguments and applies the piped functions.
|
|
103
|
+
*/
|
|
104
|
+
export function pipe(...fns) {
|
|
105
|
+
if (!fns.length) {
|
|
106
|
+
return (x) => x
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const [first, ...rest] = fns
|
|
110
|
+
return (...args) => rest.reduce((acc, fn) => fn(acc), first(...args))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Creates a throttled function that invokes `fn` immediately on the leading
|
|
115
|
+
* edge, then suppresses further calls until `delay` milliseconds have
|
|
116
|
+
* elapsed.
|
|
117
|
+
*
|
|
118
|
+
* By default (`hasTrailing: false`), any calls made while suppressed are
|
|
119
|
+
* dropped entirely — only the leading call's arguments are ever used, and
|
|
120
|
+
* the very last call of a burst may be lost. Pass `{ hasTrailing: true }` to
|
|
121
|
+
* additionally invoke `fn` once more, after `delay` has elapsed, with the
|
|
122
|
+
* arguments from the most recent suppressed call — guaranteeing the final
|
|
123
|
+
* state of a burst is never silently dropped, at the cost of `fn` running
|
|
124
|
+
* again asynchronously after the burst settles.
|
|
125
|
+
*
|
|
126
|
+
* @param {Function} fn - The function to throttle.
|
|
127
|
+
* @param {number} [delay=0] - The number of milliseconds to suppress repeated calls.
|
|
128
|
+
* @param {Object} [options]
|
|
129
|
+
* @param {boolean} [options.hasTrailing=false] - Whether to invoke `fn` once
|
|
130
|
+
* more after `delay`, using the arguments of the last suppressed call.
|
|
131
|
+
* @returns {Function & { cancel: Function, pending: Function }} The throttled function.
|
|
132
|
+
*/
|
|
133
|
+
export function throttle(
|
|
134
|
+
fn,
|
|
135
|
+
delay = DEFAULT_DELAY,
|
|
136
|
+
{ hasTrailing = false } = {},
|
|
137
|
+
) {
|
|
138
|
+
let timeoutId
|
|
139
|
+
let result
|
|
140
|
+
let lastArgs
|
|
141
|
+
let lastThis
|
|
142
|
+
let trailingCallPending = false
|
|
143
|
+
|
|
144
|
+
function clearPending() {
|
|
145
|
+
lastArgs = undefined
|
|
146
|
+
lastThis = undefined
|
|
147
|
+
trailingCallPending = false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function startWindow() {
|
|
151
|
+
timeoutId = setTimeout(() => {
|
|
152
|
+
timeoutId = undefined
|
|
153
|
+
|
|
154
|
+
// NOTE: this runs asynchronously, after `delay` has elapsed — so this
|
|
155
|
+
// is the one path where `fn` is not invoked synchronously with the
|
|
156
|
+
// triggering call. Callers that pass draft/proxy objects through `fn`
|
|
157
|
+
// (e.g. a mutative entity) must treat this like a debounced call: only
|
|
158
|
+
// hold onto primitives across the throttle boundary, and re-fetch any
|
|
159
|
+
// live object when `fn` actually runs here.
|
|
160
|
+
if (trailingCallPending) {
|
|
161
|
+
const args = lastArgs
|
|
162
|
+
const context = lastThis
|
|
163
|
+
clearPending()
|
|
164
|
+
result = fn.apply(context, args)
|
|
165
|
+
}
|
|
166
|
+
}, delay)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function throttled(...args) {
|
|
170
|
+
lastArgs = args
|
|
171
|
+
lastThis = this
|
|
172
|
+
|
|
173
|
+
if (timeoutId !== undefined) {
|
|
174
|
+
if (hasTrailing) {
|
|
175
|
+
trailingCallPending = true
|
|
176
|
+
}
|
|
177
|
+
return result
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
result = fn.apply(this, args)
|
|
181
|
+
lastArgs = undefined
|
|
182
|
+
lastThis = undefined
|
|
183
|
+
startWindow()
|
|
184
|
+
return result
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throttled.cancel = () => {
|
|
188
|
+
if (timeoutId !== undefined) {
|
|
189
|
+
clearTimeout(timeoutId)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
timeoutId = undefined
|
|
193
|
+
clearPending()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
throttled.pending = () => timeoutId !== undefined || trailingCallPending
|
|
197
|
+
|
|
198
|
+
return throttled
|
|
199
|
+
}
|
package/src/functions/index.js
CHANGED
package/src/math/circle.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../../types/math").
|
|
3
|
-
* @typedef {import("../../types/math").Circle} Circle
|
|
4
|
-
* @typedef {import("../../types/math").Rectangle} Rectangle
|
|
5
|
-
* @typedef {import("../../types/math").Platform} Platform
|
|
2
|
+
* @typedef {import("../../types/math/point").PointInput} PointInput
|
|
3
|
+
* @typedef {import("../../types/math/circle").Circle} Circle
|
|
4
|
+
* @typedef {import("../../types/math/rectangle").Rectangle} Rectangle
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import { intersectsCircle as pointIntersectsCircle } from "./point.js"
|
|
9
8
|
import { hypothenuse } from "./triangle.js"
|
|
10
|
-
import { clamp } from "./vector.js"
|
|
11
|
-
import { subtract } from "./vectors.js"
|
|
9
|
+
import { clamp, subtract } from "./vector.js"
|
|
12
10
|
|
|
13
11
|
const HALF = 2
|
|
14
12
|
const SQUARED = 2
|
|
@@ -17,7 +15,7 @@ const INITIAL_SUM = 0
|
|
|
17
15
|
/**
|
|
18
16
|
* Checks if a circle intersects with a point.
|
|
19
17
|
* @param {Circle} circle - The circle to check.
|
|
20
|
-
* @param {
|
|
18
|
+
* @param {PointInput} point - The point to check.
|
|
21
19
|
* @returns {boolean} True if the point intersects the circle, false otherwise.
|
|
22
20
|
*/
|
|
23
21
|
export function intersectsPoint(circle, point) {
|
package/src/math/hitmask.js
CHANGED
package/src/math/index.js
CHANGED
|
@@ -2,10 +2,9 @@ import * as circle from "./circle.js"
|
|
|
2
2
|
import * as hitmask from "./hitmask.js"
|
|
3
3
|
import * as line from "./line.js"
|
|
4
4
|
import * as linearInterpolation from "./linear-interpolation.js"
|
|
5
|
-
import * as numbers from "./
|
|
5
|
+
import * as numbers from "./number.js"
|
|
6
6
|
import * as point from "./point.js"
|
|
7
|
-
import * as
|
|
8
|
-
import * as quaternions from "./quaternions.js"
|
|
7
|
+
import * as quaternions from "./quaternion.js"
|
|
9
8
|
import * as rectangle from "./rectangle.js"
|
|
10
9
|
import * as rng from "./rng.js"
|
|
11
10
|
import * as segment from "./segment.js"
|
|
@@ -13,7 +12,6 @@ import * as statistics from "./statistics.js"
|
|
|
13
12
|
import * as triangle from "./triangle.js"
|
|
14
13
|
import * as trigonometry from "./trigonometry.js"
|
|
15
14
|
import * as vector from "./vector.js"
|
|
16
|
-
import * as vectors from "./vectors.js"
|
|
17
15
|
|
|
18
16
|
export {
|
|
19
17
|
circle,
|
|
@@ -22,7 +20,6 @@ export {
|
|
|
22
20
|
linearInterpolation,
|
|
23
21
|
numbers,
|
|
24
22
|
point,
|
|
25
|
-
quaternion,
|
|
26
23
|
quaternions,
|
|
27
24
|
rectangle,
|
|
28
25
|
rng,
|
|
@@ -31,5 +28,4 @@ export {
|
|
|
31
28
|
triangle,
|
|
32
29
|
trigonometry,
|
|
33
30
|
vector,
|
|
34
|
-
vectors,
|
|
35
31
|
}
|
package/src/math/line.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../../types/math").Line} Line
|
|
3
|
-
* @typedef {import("../../types/math").Point} Point
|
|
4
|
-
* @typedef {import("../../types/math").Circle} Circle
|
|
2
|
+
* @typedef {import("../../types/math/line").Line} Line
|
|
3
|
+
* @typedef {import("../../types/math/point").Point} Point
|
|
4
|
+
* @typedef {import("../../types/math/circle").Circle} Circle
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { abs } from "./
|
|
7
|
+
import { abs } from "./number.js"
|
|
8
8
|
import { hypothenuse } from "./triangle.js"
|
|
9
9
|
|
|
10
10
|
/**
|
package/src/math/point.js
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* A point in the Inglorious coordinate system: `[x, y, z]`.
|
|
3
|
+
*
|
|
4
|
+
* The world is y-up: the origin sits on the floor, and a bigger `y` is always a
|
|
5
|
+
* higher place in the world. Gravity subtracts from it, a jump adds to it, and an
|
|
6
|
+
* object resting on the ground has `y = 0`.
|
|
7
|
+
*
|
|
8
|
+
* `z` is the depth axis, and it is y-up as well — it is not screen-down, so both
|
|
9
|
+
* vertical axes grow upwards. Renderers turn them into screen coordinates:
|
|
10
|
+
* `@inglorious/renderer-2d` projects a point to `canvasY = height - y - z`,
|
|
11
|
+
* which puts `(0, 0, 0)` at the bottom-left of the canvas and turns both axes
|
|
12
|
+
* upside down.
|
|
13
|
+
*
|
|
14
|
+
* @example Reading a position in a 2D game
|
|
15
|
+
* ```js
|
|
16
|
+
* v(0, 0, 0) // the floor
|
|
17
|
+
* v(0, 100, 0) // a hundred pixels up
|
|
18
|
+
*
|
|
19
|
+
* entity.velocity[1] -= GRAVITY * dt // falling loses height
|
|
20
|
+
* entity.position[1] = 0 // keeping a body on the ground
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @typedef {import("../../types/math/point").Point} Point
|
|
24
|
+
* @typedef {import("../../types/math/point").PointInput} PointInput
|
|
25
|
+
* @typedef {import("../../types/math/line").Line} Line
|
|
26
|
+
* @typedef {import("../../types/math/circle").Circle} Circle
|
|
27
|
+
* @typedef {import("../../types/math/rectangle").Rectangle} Rectangle
|
|
6
28
|
*/
|
|
7
29
|
|
|
8
30
|
const SQUARED = 2
|
|
9
31
|
const HALF = 2
|
|
10
32
|
|
|
11
33
|
import { distanceFromPoint } from "./line.js"
|
|
12
|
-
import { isBetween } from "./
|
|
34
|
+
import { isBetween } from "./number.js"
|
|
13
35
|
import { isVector } from "./vector.js"
|
|
14
36
|
|
|
15
37
|
/**
|
|
@@ -24,8 +46,8 @@ export function getDistanceFromLine(point, line) {
|
|
|
24
46
|
|
|
25
47
|
/**
|
|
26
48
|
* Checks if two points intersect.
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {
|
|
49
|
+
* @param {PointInput} point1 - The first point as a 3D coordinate [x, y, z].
|
|
50
|
+
* @param {PointInput} point2 - The second point as a 3D coordinate [x, y, z].
|
|
29
51
|
* @returns {boolean} True if the points intersect, false otherwise.
|
|
30
52
|
*/
|
|
31
53
|
export function intersectsPoint(point1, point2) {
|
|
@@ -36,7 +58,7 @@ export function intersectsPoint(point1, point2) {
|
|
|
36
58
|
|
|
37
59
|
/**
|
|
38
60
|
* Checks if a point intersects with a circle.
|
|
39
|
-
* @param {
|
|
61
|
+
* @param {PointInput} point - The point as a 3D coordinate [x, y, z].
|
|
40
62
|
* @param {Circle} circle - The circle with a position and radius.
|
|
41
63
|
* @returns {boolean} True if the point intersects the circle, false otherwise.
|
|
42
64
|
*/
|
|
@@ -53,7 +75,7 @@ export function intersectsCircle(point, circle) {
|
|
|
53
75
|
|
|
54
76
|
/**
|
|
55
77
|
* Checks if a point intersects with a rectangle.
|
|
56
|
-
* @param {
|
|
78
|
+
* @param {PointInput} point - The point as a 3D coordinate [x, y, z].
|
|
57
79
|
* @param {Rectangle} rectangle - The rectangle with a position and size.
|
|
58
80
|
* @returns {boolean} True if the point intersects the rectangle, false otherwise.
|
|
59
81
|
*/
|