@typeonce/ritmo-math 0.11.0 → 0.13.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/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/dist/Aabb.d.ts +15 -15
- package/dist/Aabb.d.ts.map +1 -1
- package/dist/Aabb.js +13 -5
- package/dist/Aabb.js.map +1 -1
- package/dist/Random.d.ts +16 -6
- package/dist/Random.d.ts.map +1 -1
- package/dist/Random.js +13 -3
- package/dist/Random.js.map +1 -1
- package/dist/Scalar.d.ts +26 -7
- package/dist/Scalar.d.ts.map +1 -1
- package/dist/Scalar.js +6 -3
- package/dist/Scalar.js.map +1 -1
- package/dist/Size2.d.ts +15 -15
- package/dist/Size2.d.ts.map +1 -1
- package/dist/Size2.js +13 -5
- package/dist/Size2.js.map +1 -1
- package/dist/Vector2.d.ts +15 -15
- package/dist/Vector2.d.ts.map +1 -1
- package/dist/Vector2.js +13 -5
- package/dist/Vector2.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/internal/aabb.d.ts +9 -2
- package/dist/internal/aabb.d.ts.map +1 -1
- package/dist/internal/aabb.js +3 -1
- package/dist/internal/aabb.js.map +1 -1
- package/dist/internal/codec.d.ts +9 -0
- package/dist/internal/codec.d.ts.map +1 -0
- package/dist/internal/codec.js +16 -0
- package/dist/internal/codec.js.map +1 -0
- package/dist/internal/scalar.d.ts +3 -4
- package/dist/internal/scalar.d.ts.map +1 -1
- package/dist/internal/scalar.js +19 -12
- package/dist/internal/scalar.js.map +1 -1
- package/dist/internal/size2.d.ts +9 -2
- package/dist/internal/size2.d.ts.map +1 -1
- package/dist/internal/size2.js +3 -1
- package/dist/internal/size2.js.map +1 -1
- package/dist/internal/vector2.d.ts +9 -2
- package/dist/internal/vector2.d.ts.map +1 -1
- package/dist/internal/vector2.js +3 -1
- package/dist/internal/vector2.js.map +1 -1
- package/package.json +2 -2
- package/src/Aabb.ts +15 -16
- package/src/Random.ts +21 -7
- package/src/Scalar.ts +27 -7
- package/src/Size2.ts +15 -16
- package/src/Vector2.ts +15 -16
- package/src/index.ts +3 -3
- package/src/internal/aabb.ts +8 -2
- package/src/internal/codec.ts +21 -0
- package/src/internal/scalar.ts +24 -13
- package/src/internal/size2.ts +8 -2
- package/src/internal/vector2.ts +8 -2
package/src/Scalar.ts
CHANGED
|
@@ -36,13 +36,14 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import type * as Brand from "@typeonce/ritmo/Brand"
|
|
38
38
|
import * as internal from "./internal/scalar.ts"
|
|
39
|
+
import type { StandardSchema } from "@typeonce/ritmo/Descriptor"
|
|
39
40
|
import type * as Result from "@typeonce/ritmo/Result"
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* Finite number brand.
|
|
43
44
|
*
|
|
44
45
|
* Values of this type are guaranteed not to be `NaN`, `Infinity`, or
|
|
45
|
-
* `-Infinity` when they come from a
|
|
46
|
+
* `-Infinity` when they come from a constructor (`result`, `option`) or a `codec`. The
|
|
46
47
|
* arithmetic helpers here and in `Vector2` keep the brand without
|
|
47
48
|
* re-validating, so their results are finite only while they stay within
|
|
48
49
|
* `±Number.MAX_VALUE`: game-sized values never come close, but validate
|
|
@@ -85,27 +86,46 @@ export type InterpolationError = {
|
|
|
85
86
|
readonly end: number
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* The constructors, guard, and schema of one scalar brand.
|
|
91
|
+
*
|
|
92
|
+
* @category Constructors
|
|
93
|
+
*/
|
|
94
|
+
export interface Constructor<Value> {
|
|
95
|
+
/** Validates one number and returns an explicit success/failure result. */
|
|
96
|
+
readonly result: (raw: number) => Result.Result<Value, Error>
|
|
97
|
+
/** Validates one number and returns `null` on failure. */
|
|
98
|
+
readonly option: (raw: number) => Value | null
|
|
99
|
+
/** Whether any value is a number of this brand. */
|
|
100
|
+
readonly is: (raw: unknown) => raw is Value
|
|
101
|
+
/** The Standard Schema for `Descriptor.ConstructedComponent(...)`: the saved form is the number itself. */
|
|
102
|
+
readonly codec: StandardSchema<number, Value>
|
|
103
|
+
}
|
|
104
|
+
|
|
88
105
|
/**
|
|
89
106
|
* Constructor for {@link Finite}: `result` (explicit failure), `option`
|
|
90
|
-
* (`null` on failure),
|
|
107
|
+
* (`null` on failure), `is` (a type guard), and `codec`, for
|
|
108
|
+
* `Descriptor.ConstructedComponent(Scalar.Finite.codec)`.
|
|
91
109
|
*
|
|
92
110
|
* @category Constructors
|
|
93
111
|
*/
|
|
94
|
-
export const Finite = internal.Finite
|
|
112
|
+
export const Finite: Constructor<Finite> = internal.Finite
|
|
95
113
|
/**
|
|
96
114
|
* Constructor for {@link NonNegative}: `result` (explicit failure), `option`
|
|
97
|
-
* (`null` on failure),
|
|
115
|
+
* (`null` on failure), `is` (a type guard), and `codec`, for
|
|
116
|
+
* `Descriptor.ConstructedComponent(Scalar.NonNegative.codec)`.
|
|
98
117
|
*
|
|
99
118
|
* @category Constructors
|
|
100
119
|
*/
|
|
101
|
-
export const NonNegative = internal.NonNegative
|
|
120
|
+
export const NonNegative: Constructor<NonNegative> = internal.NonNegative
|
|
102
121
|
/**
|
|
103
122
|
* Constructor for {@link Positive}: `result` (explicit failure), `option`
|
|
104
|
-
* (`null` on failure),
|
|
123
|
+
* (`null` on failure), `is` (a type guard), and `codec`, for
|
|
124
|
+
* `Descriptor.ConstructedComponent(Scalar.Positive.codec)`.
|
|
105
125
|
*
|
|
106
126
|
* @category Constructors
|
|
107
127
|
*/
|
|
108
|
-
export const Positive = internal.Positive
|
|
128
|
+
export const Positive: Constructor<Positive> = internal.Positive
|
|
109
129
|
|
|
110
130
|
/**
|
|
111
131
|
* Clamps one finite value between two finite bounds.
|
package/src/Size2.ts
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* Pure readers and raw views over already-validated size values.
|
|
32
32
|
*/
|
|
33
33
|
import type * as Brand from "@typeonce/ritmo/Brand"
|
|
34
|
+
import type { StandardSchema } from "@typeonce/ritmo/Descriptor"
|
|
34
35
|
import * as internal from "./internal/size2.ts"
|
|
35
36
|
import type * as Result from "@typeonce/ritmo/Result"
|
|
36
37
|
import type * as Scalar from "./Scalar.ts"
|
|
@@ -67,16 +68,6 @@ export type Error = {
|
|
|
67
68
|
readonly input: Raw
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
* Failure produced by `decode` when the input does not have the raw shape.
|
|
72
|
-
*
|
|
73
|
-
* @category Size Types
|
|
74
|
-
*/
|
|
75
|
-
export type DecodeError = {
|
|
76
|
-
readonly tag: "Size2/Malformed"
|
|
77
|
-
readonly input: unknown
|
|
78
|
-
}
|
|
79
|
-
|
|
80
71
|
/**
|
|
81
72
|
* Validates one raw size and returns an explicit result.
|
|
82
73
|
*
|
|
@@ -84,13 +75,20 @@ export type DecodeError = {
|
|
|
84
75
|
*/
|
|
85
76
|
export const result: (raw: Raw) => Result.Result<Size2, Error> = internal.result
|
|
86
77
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
78
|
+
* The codec of `Size2` (a Standard Schema, see `Descriptor.Codec`): give it to
|
|
79
|
+
* `Descriptor.ConstructedComponent(...)` so raw input and saves are validated.
|
|
80
|
+
* Its saved form is the raw shape, so it needs no `encode`. To put a `Size2`
|
|
81
|
+
* inside your own object schema, use {@link is} with your library's guard
|
|
82
|
+
* schema (`Schema.declare`, `z.custom`).
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const Position = Descriptor.ConstructedComponent(Size2.codec)("Game/Position")
|
|
87
|
+
* ```
|
|
90
88
|
*
|
|
91
89
|
* @category Construction
|
|
92
90
|
*/
|
|
93
|
-
export const
|
|
91
|
+
export const codec: StandardSchema<Raw, Size2> = internal.codec
|
|
94
92
|
/**
|
|
95
93
|
* Validates one raw size and returns `null` on failure.
|
|
96
94
|
*
|
|
@@ -98,11 +96,12 @@ export const decode: (raw: unknown) => Result.Result<Size2, Error | DecodeError>
|
|
|
98
96
|
*/
|
|
99
97
|
export const option: (raw: Raw) => Size2 | null = internal.option
|
|
100
98
|
/**
|
|
101
|
-
*
|
|
99
|
+
* Whether any value is a valid `Size2`: a guard, also for building a
|
|
100
|
+
* `Size2` field in your own schema (`Schema.declare(Size2.is)`).
|
|
102
101
|
*
|
|
103
102
|
* @category Construction
|
|
104
103
|
*/
|
|
105
|
-
export const is: (raw:
|
|
104
|
+
export const is: (raw: unknown) => raw is Size2 = internal.is
|
|
106
105
|
/**
|
|
107
106
|
* Converts one branded size back to a fresh raw object.
|
|
108
107
|
*
|
package/src/Vector2.ts
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* Immutable vector math and normalization helpers on already-validated values.
|
|
37
37
|
*/
|
|
38
38
|
import type * as Brand from "@typeonce/ritmo/Brand"
|
|
39
|
+
import type { StandardSchema } from "@typeonce/ritmo/Descriptor"
|
|
39
40
|
import * as internal from "./internal/vector2.ts"
|
|
40
41
|
import type * as Result from "@typeonce/ritmo/Result"
|
|
41
42
|
import type * as Scalar from "./Scalar.ts"
|
|
@@ -72,16 +73,6 @@ export type Error = {
|
|
|
72
73
|
readonly input: Raw
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
/**
|
|
76
|
-
* Failure produced by `decode` when the input does not have the raw shape.
|
|
77
|
-
*
|
|
78
|
-
* @category Vector Types
|
|
79
|
-
*/
|
|
80
|
-
export type DecodeError = {
|
|
81
|
-
readonly tag: "Vector2/Malformed"
|
|
82
|
-
readonly input: unknown
|
|
83
|
-
}
|
|
84
|
-
|
|
85
76
|
/**
|
|
86
77
|
* Explicit failure produced when a zero-length vector cannot be normalized.
|
|
87
78
|
*
|
|
@@ -103,13 +94,20 @@ export type NormalizeError = {
|
|
|
103
94
|
*/
|
|
104
95
|
export const result: (raw: Raw) => Result.Result<Vector2, Error> = internal.result
|
|
105
96
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
97
|
+
* The codec of `Vector2` (a Standard Schema, see `Descriptor.Codec`): give it to
|
|
98
|
+
* `Descriptor.ConstructedComponent(...)` so raw input and saves are validated.
|
|
99
|
+
* Its saved form is the raw shape, so it needs no `encode`. To put a `Vector2`
|
|
100
|
+
* inside your own object schema, use {@link is} with your library's guard
|
|
101
|
+
* schema (`Schema.declare`, `z.custom`).
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const Position = Descriptor.ConstructedComponent(Vector2.codec)("Game/Position")
|
|
106
|
+
* ```
|
|
109
107
|
*
|
|
110
108
|
* @category Construction
|
|
111
109
|
*/
|
|
112
|
-
export const
|
|
110
|
+
export const codec: StandardSchema<Raw, Vector2> = internal.codec
|
|
113
111
|
/**
|
|
114
112
|
* Validates one raw vector and returns `null` on failure.
|
|
115
113
|
*
|
|
@@ -117,11 +115,12 @@ export const decode: (raw: unknown) => Result.Result<Vector2, Error | DecodeErro
|
|
|
117
115
|
*/
|
|
118
116
|
export const option: (raw: Raw) => Vector2 | null = internal.option
|
|
119
117
|
/**
|
|
120
|
-
*
|
|
118
|
+
* Whether any value is a valid `Vector2`: a guard, also for building a
|
|
119
|
+
* `Vector2` field in your own schema (`Schema.declare(Vector2.is)`).
|
|
121
120
|
*
|
|
122
121
|
* @category Construction
|
|
123
122
|
*/
|
|
124
|
-
export const is: (raw:
|
|
123
|
+
export const is: (raw: unknown) => raw is Vector2 = internal.is
|
|
125
124
|
/**
|
|
126
125
|
* Converts one branded vector back to a fresh raw object.
|
|
127
126
|
*
|
package/src/index.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Math for game code.
|
|
3
3
|
*
|
|
4
4
|
* Validated values (`Scalar`, `Vector2`, `Size2`, `Aabb`) expose
|
|
5
|
-
* Result-returning constructors,
|
|
6
|
-
* `Descriptor.ConstructedComponent(
|
|
7
|
-
* validated once at the boundary. `InputAxis` normalizes directional input.
|
|
5
|
+
* Result-returning constructors, a guard (`is`), and one `codec` each, so
|
|
6
|
+
* they plug into `Descriptor.ConstructedComponent(Vector2.codec)` /
|
|
7
|
+
* `ConstructedResource(Random.codec)` and are validated once at the boundary. `InputAxis` normalizes directional input.
|
|
8
8
|
*
|
|
9
9
|
* Hot-path helpers work on plain numbers every frame: `Vec2` arithmetic,
|
|
10
10
|
* `Box2` edges, `Collide2` swept circle collision, a `SpatialGrid` broad
|
package/src/internal/aabb.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { codecOf } from "./codec.ts"
|
|
1
2
|
import * as Result from "@typeonce/ritmo/Result"
|
|
2
3
|
import * as Size2 from "../Size2.ts"
|
|
3
4
|
import * as Vector2 from "../Vector2.ts"
|
|
@@ -36,7 +37,7 @@ export const option = (raw: Aabb.Raw): Aabb.Aabb | null => {
|
|
|
36
37
|
return next.ok ? next.value : null
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
export const is = (raw:
|
|
40
|
+
export const is = (raw: unknown): raw is Aabb.Aabb => decode(raw).ok
|
|
40
41
|
|
|
41
42
|
export const position = (aabb: Aabb.Aabb): Vector2.Vector2 => aabb.position
|
|
42
43
|
export const size = (aabb: Aabb.Aabb): Size2.Size2 => aabb.size
|
|
@@ -66,10 +67,15 @@ export const overlapsVertically = (first: Aabb.Aabb, second: Aabb.Aabb): boolean
|
|
|
66
67
|
export const translate = (aabb: Aabb.Aabb, delta: Vector2.Vector2): Aabb.Aabb =>
|
|
67
68
|
make(Vector2.add(aabb.position, delta), aabb.size)
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
/** Untrusted input without the raw shape. */
|
|
71
|
+
type Malformed = { readonly tag: "Aabb/Malformed"; readonly input: unknown }
|
|
72
|
+
|
|
73
|
+
export const decode = (raw: unknown): Result.Result<Aabb.Aabb, Aabb.Error | Malformed> => {
|
|
70
74
|
if (typeof raw !== "object" || raw === null) return Result.failure({ tag: "Aabb/Malformed", input: raw })
|
|
71
75
|
const { position, size } = raw as Record<string, unknown>
|
|
72
76
|
return isVector2Shape(position) && isSize2Shape(size)
|
|
73
77
|
? result({ position, size })
|
|
74
78
|
: Result.failure({ tag: "Aabb/Malformed", input: raw })
|
|
75
79
|
}
|
|
80
|
+
|
|
81
|
+
export const codec = codecOf<Aabb.Raw, Aabb.Aabb>(decode, "a box { x, y, width, height } of finite numbers with a non-negative size")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StandardSchema } from "@typeonce/ritmo/Descriptor"
|
|
2
|
+
import type * as Result from "@typeonce/ritmo/Result"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The codec of one math type, from its decode: a Standard Schema whose saved
|
|
6
|
+
* form is the raw shape. The value is already plain data, so it needs no
|
|
7
|
+
* `encode`.
|
|
8
|
+
*/
|
|
9
|
+
export const codecOf = <Raw, Value>(
|
|
10
|
+
decode: (raw: unknown) => Result.Result<Value, unknown>,
|
|
11
|
+
expected: string
|
|
12
|
+
): StandardSchema<Raw, Value> => ({
|
|
13
|
+
"~standard": {
|
|
14
|
+
version: 1,
|
|
15
|
+
vendor: "ritmo-math",
|
|
16
|
+
validate: (raw) => {
|
|
17
|
+
const value = decode(raw)
|
|
18
|
+
return value.ok ? { value: value.value } : { issues: [{ message: `Expected ${expected}` }] }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
package/src/internal/scalar.ts
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
|
-
import * as Brand from "@typeonce/ritmo/Brand"
|
|
2
1
|
import * as Result from "@typeonce/ritmo/Result"
|
|
3
2
|
import type * as Scalar from "../Scalar.ts"
|
|
3
|
+
import { codecOf } from "./codec.ts"
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// A branded number's constructors, guard, and codec, from its check.
|
|
6
|
+
const refine = <Value extends number>(
|
|
7
|
+
validate: (raw: number) => Result.Result<Value, Scalar.Error>,
|
|
8
|
+
expected: string
|
|
9
|
+
): Scalar.Constructor<Value> => ({
|
|
10
|
+
result: validate,
|
|
11
|
+
option: (raw) => {
|
|
12
|
+
const result = validate(raw)
|
|
13
|
+
return result.ok ? result.value : null
|
|
14
|
+
},
|
|
15
|
+
is: (raw): raw is Value => typeof raw === "number" && validate(raw).ok,
|
|
16
|
+
codec: codecOf<number, Value>((raw) => typeof raw === "number" ? validate(raw) : Result.failure(raw), expected)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const Finite = refine<Scalar.Finite>((value) =>
|
|
6
20
|
Number.isFinite(value)
|
|
7
21
|
? Result.success(value as Scalar.Finite)
|
|
8
22
|
: Result.failure({
|
|
9
23
|
tag: "Scalar/NotFinite",
|
|
10
24
|
value
|
|
11
|
-
})
|
|
25
|
+
}),
|
|
26
|
+
"a finite number"
|
|
12
27
|
)
|
|
13
28
|
|
|
14
|
-
const
|
|
15
|
-
const finite =
|
|
29
|
+
export const NonNegative = refine<Scalar.NonNegative>((value) => {
|
|
30
|
+
const finite = Finite.result(value)
|
|
16
31
|
if (!finite.ok) {
|
|
17
32
|
return finite
|
|
18
33
|
}
|
|
@@ -23,10 +38,10 @@ const nonNegativeBrand = Brand.refine<Scalar.NonNegative, number, Scalar.Error>(
|
|
|
23
38
|
tag: "Scalar/Negative",
|
|
24
39
|
value
|
|
25
40
|
})
|
|
26
|
-
})
|
|
41
|
+
}, "a finite number, 0 or more")
|
|
27
42
|
|
|
28
|
-
const
|
|
29
|
-
const finite =
|
|
43
|
+
export const Positive = refine<Scalar.Positive>((value) => {
|
|
44
|
+
const finite = Finite.result(value)
|
|
30
45
|
if (!finite.ok) {
|
|
31
46
|
return finite
|
|
32
47
|
}
|
|
@@ -37,11 +52,7 @@ const positiveBrand = Brand.refine<Scalar.Positive, number, Scalar.Error>((value
|
|
|
37
52
|
tag: "Scalar/NonPositive",
|
|
38
53
|
value
|
|
39
54
|
})
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
export const Finite = finiteBrand
|
|
43
|
-
export const NonNegative = nonNegativeBrand
|
|
44
|
-
export const Positive = positiveBrand
|
|
55
|
+
}, "a finite number above 0")
|
|
45
56
|
|
|
46
57
|
export const clamp = (value: number, min: number, max: number): Scalar.Finite =>
|
|
47
58
|
Math.min(Math.max(value, min), max) as Scalar.Finite
|
package/src/internal/size2.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { codecOf } from "./codec.ts"
|
|
1
2
|
import * as Result from "@typeonce/ritmo/Result"
|
|
2
3
|
import * as Scalar from "../Scalar.ts"
|
|
3
4
|
import type * as Size2 from "../Size2.ts"
|
|
@@ -28,7 +29,7 @@ export const option = (raw: Size2.Raw): Size2.Size2 | null => {
|
|
|
28
29
|
return next.ok ? next.value : null
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export const is = (raw:
|
|
32
|
+
export const is = (raw: unknown): raw is Size2.Size2 => decode(raw).ok
|
|
32
33
|
|
|
33
34
|
export const toRaw = (size: Size2.Size2): Size2.Raw => ({
|
|
34
35
|
width: size.width,
|
|
@@ -44,5 +45,10 @@ export const height = (size: Size2.Size2): Scalar.NonNegative => size.height
|
|
|
44
45
|
export const isRawShape = (raw: unknown): raw is Size2.Raw =>
|
|
45
46
|
typeof raw === "object" && raw !== null && typeof (raw as Record<string, unknown>)["width"] === "number" && typeof (raw as Record<string, unknown>)["height"] === "number"
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
/** Untrusted input without the raw shape. */
|
|
49
|
+
type Malformed = { readonly tag: "Size2/Malformed"; readonly input: unknown }
|
|
50
|
+
|
|
51
|
+
export const decode = (raw: unknown): Result.Result<Size2.Size2, Size2.Error | Malformed> =>
|
|
48
52
|
isRawShape(raw) ? result(raw) : Result.failure({ tag: "Size2/Malformed", input: raw })
|
|
53
|
+
|
|
54
|
+
export const codec = codecOf<Size2.Raw, Size2.Size2>(decode, "a size { width, height } of non-negative finite numbers")
|
package/src/internal/vector2.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { codecOf } from "./codec.ts"
|
|
1
2
|
import * as Result from "@typeonce/ritmo/Result"
|
|
2
3
|
import * as Scalar from "../Scalar.ts"
|
|
3
4
|
import type * as Vector2 from "../Vector2.ts"
|
|
@@ -28,7 +29,7 @@ export const option = (raw: Vector2.Raw): Vector2.Vector2 | null => {
|
|
|
28
29
|
return next.ok ? next.value : null
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export const is = (raw:
|
|
32
|
+
export const is = (raw: unknown): raw is Vector2.Vector2 => decode(raw).ok
|
|
32
33
|
|
|
33
34
|
export const toRaw = (vector: Vector2.Vector2): Vector2.Raw => ({
|
|
34
35
|
x: vector.x,
|
|
@@ -111,5 +112,10 @@ export const normalizeXYOrZero = (x: number, y: number): {
|
|
|
111
112
|
export const isRawShape = (raw: unknown): raw is Vector2.Raw =>
|
|
112
113
|
typeof raw === "object" && raw !== null && typeof (raw as Record<string, unknown>)["x"] === "number" && typeof (raw as Record<string, unknown>)["y"] === "number"
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
/** Untrusted input without the raw shape. */
|
|
116
|
+
type Malformed = { readonly tag: "Vector2/Malformed"; readonly input: unknown }
|
|
117
|
+
|
|
118
|
+
export const decode = (raw: unknown): Result.Result<Vector2.Vector2, Vector2.Error | Malformed> =>
|
|
115
119
|
isRawShape(raw) ? result(raw) : Result.failure({ tag: "Vector2/Malformed", input: raw })
|
|
120
|
+
|
|
121
|
+
export const codec = codecOf<Vector2.Raw, Vector2.Vector2>(decode, "a vector { x, y } of finite numbers")
|