@inglorious/utils 5.1.0 → 5.2.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 +20 -1
- package/package.json +11 -3
- package/src/data-structures/array.js +0 -1
- package/src/data-structures/heap.js +10 -13
- package/src/data-structures/object.js +0 -8
- package/src/data-structures/string.js +0 -5
- package/src/math/hitmask.js +0 -2
- package/src/math/line.js +0 -1
- package/src/math/number.js +3 -3
- package/src/math/quaternion.js +2 -2
- package/src/math/rectangle.js +0 -1
- package/src/math/rng.js +0 -4
- package/src/math/trigonometry.js +2 -2
- package/src/math/vector.js +0 -3
- package/src/physics/acceleration.js +3 -6
- package/src/physics/friction.js +2 -2
- package/src/physics/gravity.js +5 -5
- package/src/physics/jump.js +3 -3
- package/src/physics/velocity.js +2 -2
- package/src/storage.js +52 -0
- package/types/storage.d.ts +29 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ This package provides a collection of pure, composable helper functions to strea
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
19
|
+
pnpm add @inglorious/utils
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
---
|
|
@@ -118,6 +118,21 @@ Vectors are mutable arrays, so copy a shared vector rather than storing it where
|
|
|
118
118
|
to one, such as an entity position. Copy it with `v(...ZERO_VECTOR)`: a plain spread
|
|
119
119
|
(`[...ZERO_VECTOR]`) gives you a new array but drops the tag that `isVector` looks for.
|
|
120
120
|
|
|
121
|
+
### Saving and loading JSON
|
|
122
|
+
|
|
123
|
+
Keep a value in `localStorage` between visits, such as a game's high scores or an app's
|
|
124
|
+
saved state. Neither function throws: absent or blocked storage, and a value that cannot be
|
|
125
|
+
parsed, read as the fallback, and a write that did not happen answers `false`.
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import { readJSON, writeJSON } from "@inglorious/utils/storage.js"
|
|
129
|
+
|
|
130
|
+
const scores = readJSON("high-scores", [])
|
|
131
|
+
writeJSON("high-scores", [...scores, 1200]) // => true
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Pass another `Storage` as the last argument, such as `sessionStorage` or a fake in tests.
|
|
135
|
+
|
|
121
136
|
---
|
|
122
137
|
|
|
123
138
|
## API
|
|
@@ -139,6 +154,10 @@ know whether a helper lives under `data-structures` or `math`:
|
|
|
139
154
|
- **`@inglorious/utils/quaternion`**: `quaternion` and `combine`.
|
|
140
155
|
- **`@inglorious/utils/vector`**: Single-vector helpers (`magnitude`, `normalize`, `rotate`, ...) alongside multi-vector helpers (`cross`, `dot`, `sum`, ...).
|
|
141
156
|
|
|
157
|
+
One module stands on its own:
|
|
158
|
+
|
|
159
|
+
- **`@inglorious/utils/storage`**: `readJSON` and `writeJSON`, for keeping JSON in `localStorage`.
|
|
160
|
+
|
|
142
161
|
`divide`, `mod`, `multiply` and `power` accept either a scalar or one or more vectors:
|
|
143
162
|
a scalar is applied to every component, while vectors are combined component-wise.
|
|
144
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "A set of general-purpose utility functions designed with functional programming principles in mind.",
|
|
5
5
|
"author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
"types": "./types/math/vector.d.ts",
|
|
119
119
|
"import": "./src/math/vector.js"
|
|
120
120
|
},
|
|
121
|
+
"./storage.js": {
|
|
122
|
+
"types": "./types/storage.d.ts",
|
|
123
|
+
"import": "./src/storage.js"
|
|
124
|
+
},
|
|
125
|
+
"./storage": {
|
|
126
|
+
"types": "./types/storage.d.ts",
|
|
127
|
+
"import": "./src/storage.js"
|
|
128
|
+
},
|
|
121
129
|
"./v.js": {
|
|
122
130
|
"types": "./types/v.d.ts",
|
|
123
131
|
"import": "./src/v.js"
|
|
@@ -137,13 +145,13 @@
|
|
|
137
145
|
},
|
|
138
146
|
"sideEffects": false,
|
|
139
147
|
"devDependencies": {
|
|
140
|
-
"@inglorious/eslint-config": "1.
|
|
148
|
+
"@inglorious/eslint-config": "1.2.0",
|
|
141
149
|
"prettier": "^3.9.6",
|
|
142
150
|
"vite": "^8.2.2",
|
|
143
151
|
"vitest": "^5.0.0"
|
|
144
152
|
},
|
|
145
153
|
"engines": {
|
|
146
|
-
"node": ">=
|
|
154
|
+
"node": ">= 26"
|
|
147
155
|
},
|
|
148
156
|
"scripts": {
|
|
149
157
|
"format": "prettier --write .",
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @see https://stackfull.dev/heaps-in-javascript
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Default comparator for a max-heap.
|
|
5
3
|
* @param {*} a - First element.
|
|
@@ -8,17 +6,16 @@
|
|
|
8
6
|
*/
|
|
9
7
|
const DEFAULT_COMPARATOR = (a, b) => b - a
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const COMPARATOR_THRESHOLD = 0 // Threshold for comparator results.
|
|
9
|
+
const ROOT_INDEX = 0
|
|
10
|
+
const SINGLE_ELEMENT = 1
|
|
11
|
+
const LEFT_CHILD_MULTIPLIER = 2
|
|
12
|
+
const RIGHT_CHILD_MULTIPLIER = 2
|
|
13
|
+
const PARENT_DIVISOR = 2
|
|
14
|
+
const LEFT_CHILD_OFFSET = 1
|
|
15
|
+
const RIGHT_CHILD_OFFSET = 2
|
|
16
|
+
const LAST_ELEMENT_OFFSET = 1
|
|
17
|
+
const INDEX_ADJUSTMENT = 1
|
|
18
|
+
const COMPARATOR_THRESHOLD = 0
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Checks if a heap contains a specific item.
|
|
@@ -28,8 +28,6 @@ const NEXT_LEVEL = 2
|
|
|
28
28
|
* @returns {T} A deep clone of the input value.
|
|
29
29
|
*/
|
|
30
30
|
export function clone(value) {
|
|
31
|
-
// A vector holds numbers and nothing else, so it needs no copying below the top -- and
|
|
32
|
-
// building it in one go is noticeably cheaper than copying it and tagging the copy.
|
|
33
31
|
if (value?.__isVector__) {
|
|
34
32
|
return v(...value)
|
|
35
33
|
}
|
|
@@ -244,9 +242,6 @@ export function get(obj, path, defaultValue = undefined) {
|
|
|
244
242
|
export function isDeepEqual(left, right, seen = new WeakMap()) {
|
|
245
243
|
if (Object.is(left, right)) return true
|
|
246
244
|
|
|
247
|
-
// `Object.is` has already said no to these two being the same object, and neither can be
|
|
248
|
-
// walked: a function is equal to itself and nothing else, and a key present in one and
|
|
249
|
-
// not the other is a difference even when both read as undefined.
|
|
250
245
|
if (typeof left !== "object" || typeof right !== "object") return false
|
|
251
246
|
if (left === null || right === null) return false
|
|
252
247
|
|
|
@@ -372,7 +367,6 @@ export function produce(baseState, recipe, ...args) {
|
|
|
372
367
|
*/
|
|
373
368
|
export function serialize(obj) {
|
|
374
369
|
function replacer(key, value) {
|
|
375
|
-
// Handle top-stage vector
|
|
376
370
|
if (value?.__isVector__) {
|
|
377
371
|
return {
|
|
378
372
|
_type: "vector",
|
|
@@ -429,9 +423,7 @@ export function set(obj, path, value) {
|
|
|
429
423
|
const key = keys[i]
|
|
430
424
|
const nextKey = keys[i + NEXT_KEY_OFFSET]
|
|
431
425
|
|
|
432
|
-
// Create missing intermediate objects
|
|
433
426
|
if (!(key in current) || typeof current[key] !== "object") {
|
|
434
|
-
// Create array if next key is a number, object otherwise
|
|
435
427
|
current[key] = /^\d+$/.test(nextKey) ? [] : {}
|
|
436
428
|
}
|
|
437
429
|
|
|
@@ -12,8 +12,6 @@ export function toCamelCase(input) {
|
|
|
12
12
|
return [firstChar.toLowerCase(), ...rest].join("")
|
|
13
13
|
}
|
|
14
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
15
|
const ONE = 1
|
|
18
16
|
const NONE = 0
|
|
19
17
|
const FIRST = 0
|
|
@@ -56,9 +54,6 @@ export function editDistance(a, b) {
|
|
|
56
54
|
return grid[a.length][b.length]
|
|
57
55
|
}
|
|
58
56
|
|
|
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
57
|
const NEAR_ENOUGH = 3
|
|
63
58
|
|
|
64
59
|
/**
|
package/src/math/hitmask.js
CHANGED
package/src/math/line.js
CHANGED
|
@@ -20,7 +20,6 @@ export function distanceFromPoint(line, point) {
|
|
|
20
20
|
return abs(a * x + b * z + c) / hypothenuse(a, b)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// @see https://math.stackexchange.com/questions/275529/check-if-line-intersects-with-circles-perimeter
|
|
24
23
|
/**
|
|
25
24
|
* Determines whether a line intersects with the perimeter of a circle.
|
|
26
25
|
* @param {Line} line - The line represented by the equation ax + bz + c = 0, where `line` is [a, b, c].
|
package/src/math/number.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const DEFAULT_PRECISION = 1
|
|
2
|
-
const DEFAULT_TOLERANCE = 0.1
|
|
3
|
-
const SQUARE_ROOT = 0.5
|
|
1
|
+
const DEFAULT_PRECISION = 1
|
|
2
|
+
const DEFAULT_TOLERANCE = 0.1
|
|
3
|
+
const SQUARE_ROOT = 0.5
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the absolute value of a number.
|
package/src/math/quaternion.js
CHANGED
|
@@ -8,9 +8,9 @@ import { cos, sin } from "./trigonometry.js"
|
|
|
8
8
|
import { cross, dot, multiply, sum } from "./vector.js"
|
|
9
9
|
|
|
10
10
|
const DEFAULT_ANGLE = 0
|
|
11
|
-
const HALF = 2
|
|
11
|
+
const HALF = 2
|
|
12
12
|
// eslint-disable-next-line no-magic-numbers
|
|
13
|
-
const Y_AXIS = v(0, 1, 0)
|
|
13
|
+
const Y_AXIS = v(0, 1, 0)
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Combines two quaternions using the Hamilton product.
|
package/src/math/rectangle.js
CHANGED
package/src/math/rng.js
CHANGED
|
@@ -31,10 +31,6 @@ export function random(...args) {
|
|
|
31
31
|
return Math.random()
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// A range with a fractional bound cannot be stepped through as integers, so it
|
|
35
|
-
// is read as a continuous range. Requiring both bounds to be floats used to
|
|
36
|
-
// send `random(0.5, 1)` down the integer path, where it could only ever answer
|
|
37
|
-
// 0 or 1.
|
|
38
34
|
if (
|
|
39
35
|
args.length > 1 &&
|
|
40
36
|
(!Number.isInteger(args[0]) || !Number.isInteger(args[1]))
|
package/src/math/trigonometry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mod } from "./number.js"
|
|
2
2
|
|
|
3
|
-
const HALF_CIRCLE_IN_DEGRESS = 180
|
|
4
|
-
const FULL_CIRCLE = 2
|
|
3
|
+
const HALF_CIRCLE_IN_DEGRESS = 180
|
|
4
|
+
const FULL_CIRCLE = 2
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Calculates the arctangent of the quotient of its arguments.
|
package/src/math/vector.js
CHANGED
|
@@ -499,9 +499,7 @@ export function toSpherical(vector) {
|
|
|
499
499
|
return zero()
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
// In a Y-up system, inclination is the angle with the Y axis.
|
|
503
502
|
const inclination = Math.acos(vector[Y] / r)
|
|
504
|
-
// Azimuth is the angle in the XZ plane, which `angle()` calculates.
|
|
505
503
|
const azimuth = angle(vector)
|
|
506
504
|
|
|
507
505
|
return v(r, inclination, azimuth)
|
|
@@ -666,7 +664,6 @@ function rotateWithQuaternion(vector, angle) {
|
|
|
666
664
|
return vector
|
|
667
665
|
}
|
|
668
666
|
|
|
669
|
-
// @see https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Performance_comparisons
|
|
670
667
|
const [w, ...r] = quaternion(angle)
|
|
671
668
|
const result = sum(
|
|
672
669
|
vector,
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { clamp, multiply, sum, zero } from "../math/vector.js"
|
|
6
6
|
import { applyFriction } from "./friction.js"
|
|
7
7
|
|
|
8
|
-
const DEFAULT_DT = 1
|
|
9
|
-
const NO_FRICTION = 0
|
|
10
|
-
const HALF_ACCELERATION = 0.5
|
|
8
|
+
const DEFAULT_DT = 1
|
|
9
|
+
const NO_FRICTION = 0
|
|
10
|
+
const HALF_ACCELERATION = 0.5
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Applies acceleration to an object using Euler's integration method.
|
|
@@ -36,15 +36,12 @@ export function applyAcceleration(params, dt = DEFAULT_DT) {
|
|
|
36
36
|
friction = NO_FRICTION,
|
|
37
37
|
} = params
|
|
38
38
|
|
|
39
|
-
// Clamp acceleration to the maximum allowed range
|
|
40
39
|
acceleration = clamp(acceleration, -maxAcceleration, maxAcceleration)
|
|
41
40
|
|
|
42
|
-
// Update velocity with acceleration and clamp to the maximum allowed speed
|
|
43
41
|
velocity = sum(velocity, multiply(acceleration, dt))
|
|
44
42
|
velocity = clamp(velocity, -maxSpeed, maxSpeed)
|
|
45
43
|
velocity = applyFriction({ velocity, friction }, dt)
|
|
46
44
|
|
|
47
|
-
// Update position with velocity and acceleration
|
|
48
45
|
position = sum(
|
|
49
46
|
position,
|
|
50
47
|
multiply(velocity, dt),
|
package/src/physics/friction.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { magnitude, setMagnitude, zero } from "../math/vector.js"
|
|
6
6
|
|
|
7
|
-
const DEFAULT_DT = 1
|
|
8
|
-
const NO_FRICTION = 0
|
|
7
|
+
const DEFAULT_DT = 1
|
|
8
|
+
const NO_FRICTION = 0
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Applies friction to a given velocity vector.
|
package/src/physics/gravity.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
import { zero } from "../math/vector.js"
|
|
8
8
|
|
|
9
|
-
const DEFAULT_DT = 1
|
|
10
|
-
const DEFAULT_JUMP = 0
|
|
11
|
-
const DEFAULT_LEAP = 0
|
|
12
|
-
const DEFAULT_SPEED = 0
|
|
13
|
-
const NO_VELOCITY = 0
|
|
9
|
+
const DEFAULT_DT = 1
|
|
10
|
+
const DEFAULT_JUMP = 0
|
|
11
|
+
const DEFAULT_LEAP = 0
|
|
12
|
+
const DEFAULT_SPEED = 0
|
|
13
|
+
const NO_VELOCITY = 0
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Applies gravity to an object based on its current velocity and position.
|
package/src/physics/jump.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-magic-numbers */
|
|
2
2
|
|
|
3
|
-
const DEFAULT_MAX_JUMP = 0
|
|
4
|
-
const DEFAULT_MAX_LEAP = 0
|
|
5
|
-
const DEFAULT_MAX_SPEED = 0
|
|
3
|
+
const DEFAULT_MAX_JUMP = 0
|
|
4
|
+
const DEFAULT_MAX_LEAP = 0
|
|
5
|
+
const DEFAULT_MAX_SPEED = 0
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Calculates the vertical velocity (vy) of a jump
|
package/src/physics/velocity.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { clamp, multiply, sum, zero } from "../math/vector.js"
|
|
6
6
|
|
|
7
|
-
const DEFAULT_DT = 1
|
|
8
|
-
const DEFAULT_MAX_SPEED = 0
|
|
7
|
+
const DEFAULT_DT = 1
|
|
8
|
+
const DEFAULT_MAX_SPEED = 0
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Applies velocity to a position based on the given parameters and options.
|
package/src/storage.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const DEFAULT_STORAGE = () => globalThis.localStorage
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads a value back as JSON.
|
|
5
|
+
*
|
|
6
|
+
* Storage that is absent -- a server render, a private window, a test -- reads as the
|
|
7
|
+
* fallback rather than throwing, so a caller does not have to know whether it is running
|
|
8
|
+
* somewhere that has any. A value that cannot be parsed is treated the same way: what is
|
|
9
|
+
* stored is not this function's to defend, and refusing to start over it would be worse
|
|
10
|
+
* than starting again.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} key - Where the value is kept.
|
|
13
|
+
* @param {*} fallback - What to answer when there is nothing usable to read.
|
|
14
|
+
* @param {Storage} [storage] - Where to read from. Defaults to the browser's own.
|
|
15
|
+
* @returns {*} The stored value, or the fallback.
|
|
16
|
+
*/
|
|
17
|
+
export function readJSON(key, fallback, storage) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = store(storage)?.getItem(key)
|
|
20
|
+
if (raw === null || raw === undefined) return fallback
|
|
21
|
+
return JSON.parse(raw)
|
|
22
|
+
} catch {
|
|
23
|
+
return fallback
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Writes a value down as JSON, and answers whether it took.
|
|
29
|
+
*
|
|
30
|
+
* Storage can be full, absent, or refusing writes for its own reasons, and none of those
|
|
31
|
+
* is worth throwing over: a game that cannot remember a score can still be played. The
|
|
32
|
+
* answer is returned so that a caller which does care -- a test, mostly -- can say so.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} key - Where to keep the value.
|
|
35
|
+
* @param {*} value - The value to keep.
|
|
36
|
+
* @param {Storage} [storage] - Where to write to. Defaults to the browser's own.
|
|
37
|
+
* @returns {boolean} `true` if the value was written.
|
|
38
|
+
*/
|
|
39
|
+
export function writeJSON(key, value, storage) {
|
|
40
|
+
try {
|
|
41
|
+
const target = store(storage)
|
|
42
|
+
if (!target) return false
|
|
43
|
+
target.setItem(key, JSON.stringify(value))
|
|
44
|
+
return true
|
|
45
|
+
} catch {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function store(storage) {
|
|
51
|
+
return storage ?? DEFAULT_STORAGE()
|
|
52
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a value back as JSON. Absent storage, a missing key or a value that
|
|
3
|
+
* cannot be parsed all read as the fallback, rather than throwing.
|
|
4
|
+
*
|
|
5
|
+
* @param key - Where the value is kept.
|
|
6
|
+
* @param fallback - What to answer when there is nothing usable to read.
|
|
7
|
+
* @param storage - Where to read from. Defaults to the browser's own.
|
|
8
|
+
* @returns The stored value, or the fallback.
|
|
9
|
+
*/
|
|
10
|
+
export function readJSON<T>(
|
|
11
|
+
key: string,
|
|
12
|
+
fallback: T,
|
|
13
|
+
storage?: Storage | null,
|
|
14
|
+
): T
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Writes a value down as JSON, and answers whether it took. Absent, full or
|
|
18
|
+
* refusing storage answers `false` rather than throwing.
|
|
19
|
+
*
|
|
20
|
+
* @param key - Where to keep the value.
|
|
21
|
+
* @param value - The value to keep.
|
|
22
|
+
* @param storage - Where to write to. Defaults to the browser's own.
|
|
23
|
+
* @returns `true` if the value was written.
|
|
24
|
+
*/
|
|
25
|
+
export function writeJSON(
|
|
26
|
+
key: string,
|
|
27
|
+
value: unknown,
|
|
28
|
+
storage?: Storage | null,
|
|
29
|
+
): boolean
|