rn-gamekit 0.1.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 +18 -0
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/lib/module/assets/defineAssets.js +48 -0
- package/lib/module/assets/defineAssets.js.map +1 -0
- package/lib/module/assets/errors.js +38 -0
- package/lib/module/assets/errors.js.map +1 -0
- package/lib/module/assets/types.js +2 -0
- package/lib/module/assets/types.js.map +1 -0
- package/lib/module/assets/validation.js +99 -0
- package/lib/module/assets/validation.js.map +1 -0
- package/lib/module/core/frameDriver.js +20 -0
- package/lib/module/core/frameDriver.js.map +1 -0
- package/lib/module/core/input/createInputBuffer.js +296 -0
- package/lib/module/core/input/createInputBuffer.js.map +1 -0
- package/lib/module/core/input/types.js +4 -0
- package/lib/module/core/input/types.js.map +1 -0
- package/lib/module/core/session/createGameSession.js +554 -0
- package/lib/module/core/session/createGameSession.js.map +1 -0
- package/lib/module/core/session/deepFreeze.js +155 -0
- package/lib/module/core/session/deepFreeze.js.map +1 -0
- package/lib/module/core/session/diagnostics.js +2 -0
- package/lib/module/core/session/diagnostics.js.map +1 -0
- package/lib/module/core/session/types.js +43 -0
- package/lib/module/core/session/types.js.map +1 -0
- package/lib/module/definition/defineGame.js +57 -0
- package/lib/module/definition/defineGame.js.map +1 -0
- package/lib/module/definition/types.js +4 -0
- package/lib/module/definition/types.js.map +1 -0
- package/lib/module/geometry/types.js +2 -0
- package/lib/module/geometry/types.js.map +1 -0
- package/lib/module/index.js +12 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/react/GamePointerInput.js +385 -0
- package/lib/module/react/GamePointerInput.js.map +1 -0
- package/lib/module/react/GameView.js +223 -0
- package/lib/module/react/GameView.js.map +1 -0
- package/lib/module/react/alphaClock.js +42 -0
- package/lib/module/react/alphaClock.js.map +1 -0
- package/lib/module/react/assets/createGameAssetStore.js +358 -0
- package/lib/module/react/assets/createGameAssetStore.js.map +1 -0
- package/lib/module/react/assets/decodeSkiaImage.js +54 -0
- package/lib/module/react/assets/decodeSkiaImage.js.map +1 -0
- package/lib/module/react/assets/errors.js +10 -0
- package/lib/module/react/assets/errors.js.map +1 -0
- package/lib/module/react/assets/useGameAssets.js +160 -0
- package/lib/module/react/assets/useGameAssets.js.map +1 -0
- package/lib/module/react/bindAppLifecycle.js +58 -0
- package/lib/module/react/bindAppLifecycle.js.map +1 -0
- package/lib/module/react/bindGameSession.js +37 -0
- package/lib/module/react/bindGameSession.js.map +1 -0
- package/lib/module/react/gestureLifecycle.js +68 -0
- package/lib/module/react/gestureLifecycle.js.map +1 -0
- package/lib/module/react/instrumentation.js +4 -0
- package/lib/module/react/instrumentation.js.map +1 -0
- package/lib/module/react/pointerBinding.js +196 -0
- package/lib/module/react/pointerBinding.js.map +1 -0
- package/lib/module/react/pointerCoalescer.js +224 -0
- package/lib/module/react/pointerCoalescer.js.map +1 -0
- package/lib/module/react/pointerContainment.js +27 -0
- package/lib/module/react/pointerContainment.js.map +1 -0
- package/lib/module/react/sprites/GameSprite.js +116 -0
- package/lib/module/react/sprites/GameSprite.js.map +1 -0
- package/lib/module/react/sprites/GameWorld2D.js +44 -0
- package/lib/module/react/sprites/GameWorld2D.js.map +1 -0
- package/lib/module/react/sprites/Sprite.js +147 -0
- package/lib/module/react/sprites/Sprite.js.map +1 -0
- package/lib/module/react/sprites/SpriteBatch.js +173 -0
- package/lib/module/react/sprites/SpriteBatch.js.map +1 -0
- package/lib/module/react/sprites/spriteBatchPolicy.js +25 -0
- package/lib/module/react/sprites/spriteBatchPolicy.js.map +1 -0
- package/lib/module/react/sprites/spriteTransform.js +135 -0
- package/lib/module/react/sprites/spriteTransform.js.map +1 -0
- package/lib/module/react/viewportBinding.js +101 -0
- package/lib/module/react/viewportBinding.js.map +1 -0
- package/lib/module/react.js +13 -0
- package/lib/module/react.js.map +1 -0
- package/lib/module/scene/defineScene.js +29 -0
- package/lib/module/scene/defineScene.js.map +1 -0
- package/lib/module/scene/types.js +4 -0
- package/lib/module/scene/types.js.map +1 -0
- package/lib/module/sprites/sampleSpriteClip.js +50 -0
- package/lib/module/sprites/sampleSpriteClip.js.map +1 -0
- package/lib/module/sprites/spriteAnimationState.js +165 -0
- package/lib/module/sprites/spriteAnimationState.js.map +1 -0
- package/lib/module/testing.js +57 -0
- package/lib/module/testing.js.map +1 -0
- package/lib/module/viewport2d/index.js +4 -0
- package/lib/module/viewport2d/index.js.map +1 -0
- package/lib/module/viewport2d/math.js +129 -0
- package/lib/module/viewport2d/math.js.map +1 -0
- package/lib/module/viewport2d/types.js +2 -0
- package/lib/module/viewport2d/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/assets/defineAssets.d.ts +20 -0
- package/lib/typescript/src/assets/defineAssets.d.ts.map +1 -0
- package/lib/typescript/src/assets/errors.d.ts +20 -0
- package/lib/typescript/src/assets/errors.d.ts.map +1 -0
- package/lib/typescript/src/assets/types.d.ts +138 -0
- package/lib/typescript/src/assets/types.d.ts.map +1 -0
- package/lib/typescript/src/assets/validation.d.ts +15 -0
- package/lib/typescript/src/assets/validation.d.ts.map +1 -0
- package/lib/typescript/src/core/frameDriver.d.ts +12 -0
- package/lib/typescript/src/core/frameDriver.d.ts.map +1 -0
- package/lib/typescript/src/core/input/createInputBuffer.d.ts +12 -0
- package/lib/typescript/src/core/input/createInputBuffer.d.ts.map +1 -0
- package/lib/typescript/src/core/input/types.d.ts +64 -0
- package/lib/typescript/src/core/input/types.d.ts.map +1 -0
- package/lib/typescript/src/core/session/createGameSession.d.ts +18 -0
- package/lib/typescript/src/core/session/createGameSession.d.ts.map +1 -0
- package/lib/typescript/src/core/session/deepFreeze.d.ts +27 -0
- package/lib/typescript/src/core/session/deepFreeze.d.ts.map +1 -0
- package/lib/typescript/src/core/session/diagnostics.d.ts +36 -0
- package/lib/typescript/src/core/session/diagnostics.d.ts.map +1 -0
- package/lib/typescript/src/core/session/types.d.ts +104 -0
- package/lib/typescript/src/core/session/types.d.ts.map +1 -0
- package/lib/typescript/src/definition/defineGame.d.ts +66 -0
- package/lib/typescript/src/definition/defineGame.d.ts.map +1 -0
- package/lib/typescript/src/definition/types.d.ts +56 -0
- package/lib/typescript/src/definition/types.d.ts.map +1 -0
- package/lib/typescript/src/geometry/types.d.ts +8 -0
- package/lib/typescript/src/geometry/types.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +18 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/react/GamePointerInput.d.ts +37 -0
- package/lib/typescript/src/react/GamePointerInput.d.ts.map +1 -0
- package/lib/typescript/src/react/GameView.d.ts +61 -0
- package/lib/typescript/src/react/GameView.d.ts.map +1 -0
- package/lib/typescript/src/react/alphaClock.d.ts +29 -0
- package/lib/typescript/src/react/alphaClock.d.ts.map +1 -0
- package/lib/typescript/src/react/assets/createGameAssetStore.d.ts +33 -0
- package/lib/typescript/src/react/assets/createGameAssetStore.d.ts.map +1 -0
- package/lib/typescript/src/react/assets/decodeSkiaImage.d.ts +12 -0
- package/lib/typescript/src/react/assets/decodeSkiaImage.d.ts.map +1 -0
- package/lib/typescript/src/react/assets/errors.d.ts +8 -0
- package/lib/typescript/src/react/assets/errors.d.ts.map +1 -0
- package/lib/typescript/src/react/assets/useGameAssets.d.ts +35 -0
- package/lib/typescript/src/react/assets/useGameAssets.d.ts.map +1 -0
- package/lib/typescript/src/react/bindAppLifecycle.d.ts +32 -0
- package/lib/typescript/src/react/bindAppLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/react/bindGameSession.d.ts +14 -0
- package/lib/typescript/src/react/bindGameSession.d.ts.map +1 -0
- package/lib/typescript/src/react/gestureLifecycle.d.ts +37 -0
- package/lib/typescript/src/react/gestureLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/react/instrumentation.d.ts +47 -0
- package/lib/typescript/src/react/instrumentation.d.ts.map +1 -0
- package/lib/typescript/src/react/pointerBinding.d.ts +104 -0
- package/lib/typescript/src/react/pointerBinding.d.ts.map +1 -0
- package/lib/typescript/src/react/pointerCoalescer.d.ts +106 -0
- package/lib/typescript/src/react/pointerCoalescer.d.ts.map +1 -0
- package/lib/typescript/src/react/pointerContainment.d.ts +12 -0
- package/lib/typescript/src/react/pointerContainment.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/GameSprite.d.ts +75 -0
- package/lib/typescript/src/react/sprites/GameSprite.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/GameWorld2D.d.ts +29 -0
- package/lib/typescript/src/react/sprites/GameWorld2D.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/Sprite.d.ts +43 -0
- package/lib/typescript/src/react/sprites/Sprite.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/SpriteBatch.d.ts +41 -0
- package/lib/typescript/src/react/sprites/SpriteBatch.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/spriteBatchPolicy.d.ts +10 -0
- package/lib/typescript/src/react/sprites/spriteBatchPolicy.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/spriteTransform.d.ts +86 -0
- package/lib/typescript/src/react/sprites/spriteTransform.d.ts.map +1 -0
- package/lib/typescript/src/react/viewportBinding.d.ts +42 -0
- package/lib/typescript/src/react/viewportBinding.d.ts.map +1 -0
- package/lib/typescript/src/react.d.ts +20 -0
- package/lib/typescript/src/react.d.ts.map +1 -0
- package/lib/typescript/src/scene/defineScene.d.ts +26 -0
- package/lib/typescript/src/scene/defineScene.d.ts.map +1 -0
- package/lib/typescript/src/scene/types.d.ts +71 -0
- package/lib/typescript/src/scene/types.d.ts.map +1 -0
- package/lib/typescript/src/sprites/sampleSpriteClip.d.ts +24 -0
- package/lib/typescript/src/sprites/sampleSpriteClip.d.ts.map +1 -0
- package/lib/typescript/src/sprites/spriteAnimationState.d.ts +45 -0
- package/lib/typescript/src/sprites/spriteAnimationState.d.ts.map +1 -0
- package/lib/typescript/src/testing.d.ts +28 -0
- package/lib/typescript/src/testing.d.ts.map +1 -0
- package/lib/typescript/src/viewport2d/index.d.ts +3 -0
- package/lib/typescript/src/viewport2d/index.d.ts.map +1 -0
- package/lib/typescript/src/viewport2d/math.d.ts +25 -0
- package/lib/typescript/src/viewport2d/math.d.ts.map +1 -0
- package/lib/typescript/src/viewport2d/types.d.ts +59 -0
- package/lib/typescript/src/viewport2d/types.d.ts.map +1 -0
- package/package.json +120 -0
- package/src/assets/defineAssets.ts +62 -0
- package/src/assets/errors.ts +64 -0
- package/src/assets/types.ts +164 -0
- package/src/assets/validation.ts +158 -0
- package/src/core/frameDriver.ts +31 -0
- package/src/core/input/createInputBuffer.ts +337 -0
- package/src/core/input/types.ts +67 -0
- package/src/core/session/createGameSession.ts +652 -0
- package/src/core/session/deepFreeze.ts +158 -0
- package/src/core/session/diagnostics.ts +35 -0
- package/src/core/session/types.ts +122 -0
- package/src/definition/defineGame.ts +94 -0
- package/src/definition/types.ts +67 -0
- package/src/geometry/types.ts +7 -0
- package/src/index.ts +84 -0
- package/src/react/GamePointerInput.tsx +478 -0
- package/src/react/GameView.tsx +317 -0
- package/src/react/alphaClock.ts +59 -0
- package/src/react/assets/createGameAssetStore.ts +459 -0
- package/src/react/assets/decodeSkiaImage.ts +64 -0
- package/src/react/assets/errors.ts +7 -0
- package/src/react/assets/useGameAssets.ts +171 -0
- package/src/react/bindAppLifecycle.ts +79 -0
- package/src/react/bindGameSession.ts +41 -0
- package/src/react/gestureLifecycle.ts +73 -0
- package/src/react/instrumentation.ts +54 -0
- package/src/react/pointerBinding.ts +226 -0
- package/src/react/pointerCoalescer.ts +234 -0
- package/src/react/pointerContainment.ts +23 -0
- package/src/react/sprites/GameSprite.tsx +192 -0
- package/src/react/sprites/GameWorld2D.tsx +46 -0
- package/src/react/sprites/Sprite.tsx +194 -0
- package/src/react/sprites/SpriteBatch.tsx +219 -0
- package/src/react/sprites/spriteBatchPolicy.ts +21 -0
- package/src/react/sprites/spriteTransform.ts +163 -0
- package/src/react/viewportBinding.ts +104 -0
- package/src/react.ts +37 -0
- package/src/scene/defineScene.ts +38 -0
- package/src/scene/types.ts +79 -0
- package/src/sprites/sampleSpriteClip.ts +51 -0
- package/src/sprites/spriteAnimationState.ts +176 -0
- package/src/testing.ts +61 -0
- package/src/viewport2d/index.ts +7 -0
- package/src/viewport2d/math.ts +134 -0
- package/src/viewport2d/types.ts +63 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trusted deep-freeze cache (T3).
|
|
3
|
+
*
|
|
4
|
+
* Each session owns one freezer. A subtree that has been completely frozen
|
|
5
|
+
* once is recorded in a `WeakSet` and never re-traversed: structurally
|
|
6
|
+
* shared snapshot subtrees (the common case: unchanged bricks, entities,
|
|
7
|
+
* or config objects reused by reference across ticks) are walked exactly
|
|
8
|
+
* once for the lifetime of the session.
|
|
9
|
+
*
|
|
10
|
+
* Correctness rules:
|
|
11
|
+
* - A node is promoted into the trusted set **only after** its whole
|
|
12
|
+
* subtree finished freezing. A child getter that throws mid-traversal
|
|
13
|
+
* leaves every ancestor untrusted, so a later snapshot re-walks it.
|
|
14
|
+
* - Cycle detection uses a separate per-traversal `visiting` set, never the
|
|
15
|
+
* trusted set, so cycles freeze exactly once without infinite recursion.
|
|
16
|
+
* - `Object.isFrozen` is never used as a skip test: an externally
|
|
17
|
+
* shallow-frozen object can contain mutable children and must still be
|
|
18
|
+
* recursed into.
|
|
19
|
+
* - Freezing is never dev-only: the public API promise of deeply immutable
|
|
20
|
+
* snapshots holds in every build.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { DeepReadonly } from './types';
|
|
24
|
+
|
|
25
|
+
/** Whether a string is a canonical array index (`0 <= i < 2^32 - 1`). */
|
|
26
|
+
/** Decimal digit count of a non-negative integer (0 <= value < 1e11). */
|
|
27
|
+
function digitLength(value: number): number {
|
|
28
|
+
'worklet';
|
|
29
|
+
if (value < 10) {
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
if (value < 100) {
|
|
33
|
+
return 2;
|
|
34
|
+
}
|
|
35
|
+
if (value < 1_000) {
|
|
36
|
+
return 3;
|
|
37
|
+
}
|
|
38
|
+
if (value < 10_000) {
|
|
39
|
+
return 4;
|
|
40
|
+
}
|
|
41
|
+
if (value < 100_000) {
|
|
42
|
+
return 5;
|
|
43
|
+
}
|
|
44
|
+
if (value < 1_000_000) {
|
|
45
|
+
return 6;
|
|
46
|
+
}
|
|
47
|
+
if (value < 10_000_000) {
|
|
48
|
+
return 7;
|
|
49
|
+
}
|
|
50
|
+
if (value < 100_000_000) {
|
|
51
|
+
return 8;
|
|
52
|
+
}
|
|
53
|
+
if (value < 1_000_000_000) {
|
|
54
|
+
return 9;
|
|
55
|
+
}
|
|
56
|
+
return 10;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isArrayIndexKey(key: string): boolean {
|
|
60
|
+
'worklet';
|
|
61
|
+
// A canonical array index is the exact decimal form of an integer in
|
|
62
|
+
// [0, 2^32 - 2]; numeric-looking strings such as '1e0', '01', or
|
|
63
|
+
// '4294967295' are ordinary properties, not indices. The check is
|
|
64
|
+
// allocation-free: a digit scan plus the leading-zero rule.
|
|
65
|
+
const length = key.length;
|
|
66
|
+
if (length === 0 || length > 10) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (length > 1 && key.charCodeAt(0) === 48 /* '0' */) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
let value = 0;
|
|
73
|
+
for (let index = 0; index < length; index += 1) {
|
|
74
|
+
const code = key.charCodeAt(index);
|
|
75
|
+
if (code < 48 /* '0' */ || code > 57 /* '9' */) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
value = value * 10 + (code - 48);
|
|
79
|
+
}
|
|
80
|
+
return value <= 0xffffffff - 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface DeepFreezer {
|
|
84
|
+
<T>(value: T): DeepReadonly<T>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createDeepFreeze(): DeepFreezer { const trusted = new WeakSet<object>();
|
|
88
|
+
|
|
89
|
+
return function deepFreeze<T>(value: T): DeepReadonly<T> {
|
|
90
|
+
if (typeof value !== 'object' || value === null) {
|
|
91
|
+
return value as DeepReadonly<T>;
|
|
92
|
+
}
|
|
93
|
+
const visiting = new WeakSet<object>();
|
|
94
|
+
const freeze = (node: unknown): void => {
|
|
95
|
+
if (typeof node !== 'object' || node === null) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (trusted.has(node) || visiting.has(node)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
visiting.add(node);
|
|
102
|
+
if (Array.isArray(node)) {
|
|
103
|
+
// Index fast path: avoids materialising an index-key array per array.
|
|
104
|
+
for (let index = 0; index < node.length; index += 1) {
|
|
105
|
+
freeze(node[index]);
|
|
106
|
+
}
|
|
107
|
+
// F5: legal JavaScript arrays may also carry values on non-index
|
|
108
|
+
// string keys and symbol keys. Only own keys are reachable snapshot
|
|
109
|
+
// values, so the scan uses getOwnPropertyNames (own string keys,
|
|
110
|
+
// enumerable and non-enumerable, no inherited keys) plus
|
|
111
|
+
// getOwnPropertySymbols — `for-in` was rejected because it yields
|
|
112
|
+
// inherited enumerable keys and skips non-enumerable own keys. The
|
|
113
|
+
// name array allocation is the documented cost of full correctness;
|
|
114
|
+
// canonical indices and `length` are skipped with an arithmetic
|
|
115
|
+
// check, and every remaining own value is frozen before the array is
|
|
116
|
+
// promoted into the trusted set.
|
|
117
|
+
const names = Object.getOwnPropertyNames(node);
|
|
118
|
+
// getOwnPropertyNames yields canonical indices first, in ascending
|
|
119
|
+
// order, then 'length', then any extra string keys in insertion
|
|
120
|
+
// order — so a dense array's indices take one Number() compare each
|
|
121
|
+
// and only extras reach the canonical-form check.
|
|
122
|
+
let expectedIndex = 0;
|
|
123
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
124
|
+
const name = names[index];
|
|
125
|
+
if (name === undefined || name === 'length') {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Fast discriminator: a canonical ascending index must match the
|
|
129
|
+
// expected value AND carry its exact decimal length — '01', '1e0',
|
|
130
|
+
// or '4294967295' cannot pass the length guard and fall through
|
|
131
|
+
// to the canonical check.
|
|
132
|
+
if (Number(name) === expectedIndex && name.length === digitLength(expectedIndex)) {
|
|
133
|
+
expectedIndex += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!isArrayIndexKey(name)) {
|
|
137
|
+
freeze((node as unknown as Record<PropertyKey, unknown>)[name]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const symbols = Object.getOwnPropertySymbols(node);
|
|
141
|
+
for (let index = 0; index < symbols.length; index += 1) {
|
|
142
|
+
const symbol = symbols[index];
|
|
143
|
+
if (symbol !== undefined) {
|
|
144
|
+
freeze((node as unknown as Record<PropertyKey, unknown>)[symbol]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
for (const key of Reflect.ownKeys(node)) {
|
|
149
|
+
freeze((node as Record<PropertyKey, unknown>)[key]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
Object.freeze(node);
|
|
153
|
+
trusted.add(node);
|
|
154
|
+
};
|
|
155
|
+
freeze(value as object);
|
|
156
|
+
return value as DeepReadonly<T>;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal performance diagnostics sink.
|
|
3
|
+
*
|
|
4
|
+
* The session calls these hooks only when a sink is supplied through the
|
|
5
|
+
* testing seam. Production sessions pass no sink and pay no timing reads,
|
|
6
|
+
* callbacks, or wrapper allocation on the disabled path (F4). This is
|
|
7
|
+
* intentionally NOT a public diagnostics API: its usefulness is being proven
|
|
8
|
+
* by the playground Performance Lab before any public contract.
|
|
9
|
+
*/
|
|
10
|
+
export interface SessionDiagnostics {
|
|
11
|
+
/** A presentation callback was delivered by the frame driver. */
|
|
12
|
+
onDisplayCallback(): void;
|
|
13
|
+
/** A display callback that ran zero simulation steps. */
|
|
14
|
+
onZeroStepCallback(): void;
|
|
15
|
+
/** A fixed simulation step ran. */
|
|
16
|
+
onFixedStep(): void;
|
|
17
|
+
/** A catch-up step (second or later in the same callback) ran. */
|
|
18
|
+
onCatchUpStep(): void;
|
|
19
|
+
/** Whole-step debt dropped after the catch-up cap, in steps. */
|
|
20
|
+
onDroppedDebt(steps: number): void;
|
|
21
|
+
/** Duration of one scene update, milliseconds. */
|
|
22
|
+
onUpdate(durationMs: number): void;
|
|
23
|
+
/** Duration of one input sample, milliseconds. */
|
|
24
|
+
onInputSample(durationMs: number): void;
|
|
25
|
+
/** Duration of one snapshot extraction, milliseconds. */
|
|
26
|
+
onSnapshot(durationMs: number): void;
|
|
27
|
+
/** Duration of one deep-freeze pass, milliseconds. */
|
|
28
|
+
onDeepFreeze(durationMs: number): void;
|
|
29
|
+
/** Duration of one publish (listener fan-out), milliseconds. */
|
|
30
|
+
onPublish(durationMs: number): void;
|
|
31
|
+
/** A render frame was published to listeners. */
|
|
32
|
+
onCommitNotification(): void;
|
|
33
|
+
/** Listener count at publish time. */
|
|
34
|
+
onListenerCount(count: number): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { InputMap, SceneMap } from '../../definition/types';
|
|
2
|
+
import type { SceneSnapshot } from '../../scene/types';
|
|
3
|
+
import type { Viewport } from '../../viewport2d/types';
|
|
4
|
+
import type { InputController } from '../input/types';
|
|
5
|
+
|
|
6
|
+
/** Recursively readonly view of renderer snapshot data. */
|
|
7
|
+
export type DeepReadonly<T> = T extends (...arguments_: never[]) => unknown
|
|
8
|
+
? T
|
|
9
|
+
: T extends readonly (infer TItem)[]
|
|
10
|
+
? readonly DeepReadonly<TItem>[]
|
|
11
|
+
: T extends object
|
|
12
|
+
? { readonly [TKey in keyof T]: DeepReadonly<T[TKey]> }
|
|
13
|
+
: T;
|
|
14
|
+
|
|
15
|
+
/** Lifecycle states of a headless game session. */
|
|
16
|
+
export type GameSessionStatus = 'idle' | 'running' | 'paused' | 'disposed';
|
|
17
|
+
|
|
18
|
+
/** One renderer-neutral simulation commit produced by a game session. */
|
|
19
|
+
export interface CommitFrameBase<TSnapshot> {
|
|
20
|
+
/** Snapshot immediately before the latest simulation update. */
|
|
21
|
+
readonly previous: DeepReadonly<TSnapshot>;
|
|
22
|
+
/** Snapshot produced by the latest simulation update. */
|
|
23
|
+
readonly current: DeepReadonly<TSnapshot>;
|
|
24
|
+
/** Number of completed global simulation updates. */
|
|
25
|
+
readonly tick: number;
|
|
26
|
+
/** Deterministic elapsed global simulation time in seconds. */
|
|
27
|
+
readonly elapsedSeconds: number;
|
|
28
|
+
/** Monotonic commit counter; increments on every simulation commit. */
|
|
29
|
+
readonly revision: number;
|
|
30
|
+
/** True when this commit is a transition hard cut (previous === current). */
|
|
31
|
+
readonly hardCut: boolean;
|
|
32
|
+
/** Fixed simulation step duration in milliseconds (UI alpha clock input). */
|
|
33
|
+
readonly stepMs: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A simulation commit discriminated by its scene name.
|
|
38
|
+
*
|
|
39
|
+
* Narrowing on `frame.scene` narrows both `previous` and `current` to the
|
|
40
|
+
* snapshot type of that scene. A transition commits a hard cut where both
|
|
41
|
+
* snapshots come from the new scene; frames never interpolate between two
|
|
42
|
+
* scene types.
|
|
43
|
+
*/
|
|
44
|
+
export type CommitFrame<TScenes extends SceneMap> = {
|
|
45
|
+
[TName in keyof TScenes]: CommitFrameBase<SceneSnapshot<TScenes[TName]>> & {
|
|
46
|
+
readonly scene: TName;
|
|
47
|
+
};
|
|
48
|
+
}[keyof TScenes];
|
|
49
|
+
|
|
50
|
+
/** A commit frame plus a presentation fraction computed on demand. */
|
|
51
|
+
export type GameRenderFrame<TScenes extends SceneMap> = CommitFrame<TScenes> & {
|
|
52
|
+
/** Live fixed-step fraction at call time; renderers use the UI clock. */
|
|
53
|
+
readonly alpha: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** An idempotently removable render-frame subscription. */
|
|
57
|
+
export interface GameSubscription {
|
|
58
|
+
/** Stop future notifications. Safe to call repeatedly. */
|
|
59
|
+
remove(): void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A closure-backed, headless running instance of one game definition. */
|
|
63
|
+
export interface GameSession<TScenes extends SceneMap = SceneMap, TInput extends InputMap = InputMap> {
|
|
64
|
+
/** Current lifecycle status. */
|
|
65
|
+
readonly status: GameSessionStatus;
|
|
66
|
+
/** The active scene name, updated by transitions and restarts. */
|
|
67
|
+
readonly scene: keyof TScenes;
|
|
68
|
+
/** The authored viewport configuration of this game. */
|
|
69
|
+
readonly viewport: Viewport;
|
|
70
|
+
/** Semantic input controller constrained to declared action names. */
|
|
71
|
+
readonly input: InputController<Extract<keyof TInput, string>>;
|
|
72
|
+
/** Start an idle session or resume a paused session. */
|
|
73
|
+
start(): void;
|
|
74
|
+
/** Pause simulation and discard suspended wall time. */
|
|
75
|
+
pause(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Transition to a declared scene.
|
|
78
|
+
*
|
|
79
|
+
* While running, the transition commits at the next fixed-step boundary.
|
|
80
|
+
* While idle or paused, it commits synchronously and publishes the new
|
|
81
|
+
* frame. Setting the current scene is an idempotent no-op.
|
|
82
|
+
*/
|
|
83
|
+
setScene(name: keyof TScenes): void;
|
|
84
|
+
/** Recreate the active scene with fresh state. */
|
|
85
|
+
restartScene(): void;
|
|
86
|
+
/** Permanently stop the session and release the active scene. */
|
|
87
|
+
dispose(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Read the latest immutable simulation commit with a live presentation
|
|
90
|
+
* fraction.
|
|
91
|
+
*
|
|
92
|
+
* Freshness contract: `alpha` is computed on demand from the session's
|
|
93
|
+
* accumulated timing fraction at call time and is only meaningful for
|
|
94
|
+
* headless inspection. Rendering uses the UI-owned alpha clock; the
|
|
95
|
+
* envelope fields never change between commits.
|
|
96
|
+
*/
|
|
97
|
+
getRenderFrame(): GameRenderFrame<TScenes>;
|
|
98
|
+
/** Observe simulation commits at commit frequency (never per display frame). */
|
|
99
|
+
addCommitListener(listener: (frame: CommitFrame<TScenes>) => void): GameSubscription;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Error thrown when live work is requested from a disposed session. */
|
|
103
|
+
export class GameSessionDisposedError extends Error {
|
|
104
|
+
override readonly name = 'GameSessionDisposedError';
|
|
105
|
+
|
|
106
|
+
constructor() {
|
|
107
|
+
super('This GameSession has been disposed');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Error thrown for lifecycle violations: conflicting transition requests,
|
|
113
|
+
* transition controllers used outside their update, unknown runtime scene
|
|
114
|
+
* names, and pending transition conflicts.
|
|
115
|
+
*/
|
|
116
|
+
export class GameSessionLifecycleError extends Error {
|
|
117
|
+
override readonly name = 'GameSessionLifecycleError';
|
|
118
|
+
|
|
119
|
+
constructor(message: string) {
|
|
120
|
+
super(message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { AssetGroupMap } from '../assets/types';
|
|
2
|
+
import type { GameDefinition, InputMap, SceneMap } from './types';
|
|
3
|
+
|
|
4
|
+
type SceneActions<TScene> = TScene extends { readonly __actionType?: infer TAction }
|
|
5
|
+
? Exclude<TAction, undefined>
|
|
6
|
+
: never;
|
|
7
|
+
|
|
8
|
+
type SceneTransitions<TScene> = TScene extends { readonly __transitionType?: infer TTransition }
|
|
9
|
+
? Exclude<TTransition, undefined>
|
|
10
|
+
: never;
|
|
11
|
+
|
|
12
|
+
type UndeclaredSceneActions<TScenes extends SceneMap, TInput extends InputMap> = Exclude<
|
|
13
|
+
SceneActions<TScenes[keyof TScenes]>,
|
|
14
|
+
Extract<keyof TInput, string>
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
type UndeclaredSceneTransitions<TScenes extends SceneMap> = Exclude<
|
|
18
|
+
SceneTransitions<TScenes[keyof TScenes]>,
|
|
19
|
+
keyof TScenes
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
type ValidateSceneActions<TScenes extends SceneMap, TInput extends InputMap> =
|
|
23
|
+
UndeclaredSceneActions<TScenes, TInput> extends never
|
|
24
|
+
? unknown
|
|
25
|
+
: { readonly __undeclaredSceneActions: UndeclaredSceneActions<TScenes, TInput> };
|
|
26
|
+
|
|
27
|
+
type ValidateSceneTransitions<TScenes extends SceneMap> =
|
|
28
|
+
UndeclaredSceneTransitions<TScenes> extends never
|
|
29
|
+
? unknown
|
|
30
|
+
: { readonly __undeclaredSceneTransitions: UndeclaredSceneTransitions<TScenes> };
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Declare a game.
|
|
34
|
+
*
|
|
35
|
+
* `defineGame` is the entry point of the provisional GameKit API. It
|
|
36
|
+
* validates the definition at the type level — scene names are inferred so
|
|
37
|
+
* `initialScene` must be one of the keys of `scenes`, every scene action must
|
|
38
|
+
* exist in the game input map, and every declared transition target must be a
|
|
39
|
+
* declared scene — and preserves and returns the supplied definition.
|
|
40
|
+
*
|
|
41
|
+
* This function creates no runtime state: it does not start a scheduler,
|
|
42
|
+
* allocate a session, load assets, or register input. Pass the definition to
|
|
43
|
+
* `createGameSession` to create a live instance. The shape is provisional
|
|
44
|
+
* until reference games validate it.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const game = defineGame({
|
|
49
|
+
* viewport: {
|
|
50
|
+
* logicalSize: { width: 390, height: 844 },
|
|
51
|
+
* mode: 'fit',
|
|
52
|
+
* },
|
|
53
|
+
* assets: [],
|
|
54
|
+
* input: { primary: { type: 'pointer' } },
|
|
55
|
+
* scenes: {
|
|
56
|
+
* ready: defineScene({
|
|
57
|
+
* actions: ['primary'],
|
|
58
|
+
* transitions: ['play'],
|
|
59
|
+
* create: () => ({ ready: true }),
|
|
60
|
+
* update: ({ state, input, transition }) => {
|
|
61
|
+
* if (input.pointer('primary').pressed) {
|
|
62
|
+
* transition.setScene('play');
|
|
63
|
+
* }
|
|
64
|
+
* return state;
|
|
65
|
+
* },
|
|
66
|
+
* snapshot: ({ state }) => state,
|
|
67
|
+
* }),
|
|
68
|
+
* play: defineScene({
|
|
69
|
+
* actions: [],
|
|
70
|
+
* create: () => ({ x: 0 }),
|
|
71
|
+
* update: ({ state }) => ({ x: state.x + 1 }),
|
|
72
|
+
* snapshot: ({ state }) => ({ x: state.x }),
|
|
73
|
+
* }),
|
|
74
|
+
* },
|
|
75
|
+
* initialScene: 'ready',
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function defineGame<
|
|
80
|
+
const TScenes extends SceneMap,
|
|
81
|
+
const TInput extends InputMap = InputMap,
|
|
82
|
+
const TInitialScene extends keyof TScenes = keyof TScenes,
|
|
83
|
+
const TAssets extends AssetGroupMap = AssetGroupMap,
|
|
84
|
+
>(
|
|
85
|
+
definition: GameDefinition<TScenes, TInput, TInitialScene, TAssets> &
|
|
86
|
+
ValidateSceneActions<TScenes, TInput> &
|
|
87
|
+
ValidateSceneTransitions<TScenes>,
|
|
88
|
+
): GameDefinition<TScenes, TInput, TInitialScene, TAssets> {
|
|
89
|
+
// The viewport config is part of the public session surface; freeze it so a
|
|
90
|
+
// caller cannot mutate a live game's coordinate authority.
|
|
91
|
+
Object.freeze(definition.viewport.logicalSize);
|
|
92
|
+
Object.freeze(definition.viewport);
|
|
93
|
+
return definition;
|
|
94
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { AssetGroupMap, GameAssetManifest } from '../assets/types';
|
|
2
|
+
import type { Viewport } from '../viewport2d/types';
|
|
3
|
+
|
|
4
|
+
/** The button action supported by the runtime. */
|
|
5
|
+
export interface ButtonInputAction {
|
|
6
|
+
/** Discriminant for a digital button action. */
|
|
7
|
+
readonly type: 'button';
|
|
8
|
+
/** Optional human-readable description for diagnostics and tooling. */
|
|
9
|
+
readonly description?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A single primary pointer action driven by touch or a mouse pointer. */
|
|
13
|
+
export interface PointerInputAction {
|
|
14
|
+
/** Discriminant for a pointer action. */
|
|
15
|
+
readonly type: 'pointer';
|
|
16
|
+
/** Optional human-readable description for diagnostics and tooling. */
|
|
17
|
+
readonly description?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A named semantic input action. */
|
|
21
|
+
export type InputAction = ButtonInputAction | PointerInputAction;
|
|
22
|
+
|
|
23
|
+
/** The collection of semantic input actions declared by a game. */
|
|
24
|
+
export type InputMap = Readonly<Record<string, InputAction>>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Structural marker shared by all definitions created with `defineScene`.
|
|
28
|
+
*
|
|
29
|
+
* It lets `defineGame` retain each scene's inferred state, snapshot, action,
|
|
30
|
+
* and transition types without erasing them into a universal world or
|
|
31
|
+
* renderer shape.
|
|
32
|
+
*/
|
|
33
|
+
export interface SceneDefinitionMarker {
|
|
34
|
+
/** Internal scene-definition discriminator. */
|
|
35
|
+
readonly kind: 'gamekit.scene';
|
|
36
|
+
/** @internal Type witness for actions consumed by this scene. */
|
|
37
|
+
readonly __actionType?: string;
|
|
38
|
+
/** @internal Type witness for transition targets declared by this scene. */
|
|
39
|
+
readonly __transitionType?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The collection of functional scenes declared by a game. */
|
|
43
|
+
export type SceneMap = Readonly<Record<string, SceneDefinitionMarker>>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A complete static game definition produced by `defineGame`.
|
|
47
|
+
*
|
|
48
|
+
* Creating a definition allocates no runtime state. Pass it to
|
|
49
|
+
* `createGameSession` when a live headless session is required.
|
|
50
|
+
*/
|
|
51
|
+
export interface GameDefinition<
|
|
52
|
+
TScenes extends SceneMap = SceneMap,
|
|
53
|
+
TInput extends InputMap = InputMap,
|
|
54
|
+
TInitialScene extends keyof TScenes = keyof TScenes,
|
|
55
|
+
TAssets extends AssetGroupMap = AssetGroupMap,
|
|
56
|
+
> {
|
|
57
|
+
/** The viewport configuration of the game. */
|
|
58
|
+
readonly viewport: Viewport;
|
|
59
|
+
/** Typed asset manifest; optional for shape-only games. */
|
|
60
|
+
readonly assets?: GameAssetManifest<TAssets>;
|
|
61
|
+
/** Semantic input actions declared by the game. */
|
|
62
|
+
readonly input: TInput;
|
|
63
|
+
/** Functional scenes keyed by stable scene name. */
|
|
64
|
+
readonly scenes: TScenes;
|
|
65
|
+
/** The scene created first when a session starts. */
|
|
66
|
+
readonly initialScene: TInitialScene;
|
|
67
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export { createGameSession } from './core/session/createGameSession';
|
|
2
|
+
export { GameSessionDisposedError, GameSessionLifecycleError } from './core/session/types';
|
|
3
|
+
export type {
|
|
4
|
+
ButtonState,
|
|
5
|
+
InputController,
|
|
6
|
+
InputFrame,
|
|
7
|
+
PointerState,
|
|
8
|
+
} from './core/input/types';
|
|
9
|
+
export type {
|
|
10
|
+
DeepReadonly,
|
|
11
|
+
CommitFrame,
|
|
12
|
+
GameRenderFrame,
|
|
13
|
+
GameSession,
|
|
14
|
+
GameSessionStatus,
|
|
15
|
+
GameSubscription,
|
|
16
|
+
} from './core/session/types';
|
|
17
|
+
export {
|
|
18
|
+
advanceSpriteAnimation,
|
|
19
|
+
pauseSpriteAnimation,
|
|
20
|
+
playSpriteAnimation,
|
|
21
|
+
resetSpriteAnimation,
|
|
22
|
+
resumeSpriteAnimation,
|
|
23
|
+
setSpriteAnimationSpeed,
|
|
24
|
+
startSpriteAnimation,
|
|
25
|
+
} from './sprites/spriteAnimationState';
|
|
26
|
+
export {
|
|
27
|
+
sampleSpriteClipFrame,
|
|
28
|
+
sampleSpriteClipFrameName,
|
|
29
|
+
spriteClipDurationMs,
|
|
30
|
+
} from './sprites/sampleSpriteClip';
|
|
31
|
+
export type { SpriteAnimationState, SpriteClipNames } from './sprites/spriteAnimationState';
|
|
32
|
+
export { defineAssets, image, spriteSheet } from './assets/defineAssets';
|
|
33
|
+
export { GameAssetError } from './assets/errors';
|
|
34
|
+
export type {
|
|
35
|
+
AssetDescriptor,
|
|
36
|
+
AssetGroup,
|
|
37
|
+
AssetGroupMap,
|
|
38
|
+
AssetSourceHandle,
|
|
39
|
+
BrandedAssetDescriptor,
|
|
40
|
+
GameAssetLease,
|
|
41
|
+
GameAssetManifest,
|
|
42
|
+
ImageDescriptor,
|
|
43
|
+
LoadedAssets,
|
|
44
|
+
LoadedImage,
|
|
45
|
+
LoadedSpriteSheet,
|
|
46
|
+
ManifestOf,
|
|
47
|
+
SpriteAnimationMode,
|
|
48
|
+
SpriteClip,
|
|
49
|
+
SpriteFrameRect,
|
|
50
|
+
SpriteSheetDescriptor,
|
|
51
|
+
} from './assets/types';
|
|
52
|
+
export { defineGame } from './definition/defineGame';
|
|
53
|
+
export type {
|
|
54
|
+
ButtonInputAction,
|
|
55
|
+
GameDefinition,
|
|
56
|
+
InputAction,
|
|
57
|
+
InputMap,
|
|
58
|
+
PointerInputAction,
|
|
59
|
+
SceneDefinitionMarker,
|
|
60
|
+
SceneMap,
|
|
61
|
+
} from './definition/types';
|
|
62
|
+
export type { Point2D } from './geometry/types';
|
|
63
|
+
export { defineScene } from './scene/defineScene';
|
|
64
|
+
export type {
|
|
65
|
+
SceneDefinition,
|
|
66
|
+
SceneSnapshot,
|
|
67
|
+
SceneSnapshotContext,
|
|
68
|
+
SceneTransitionController,
|
|
69
|
+
SceneUpdate,
|
|
70
|
+
} from './scene/types';
|
|
71
|
+
export {
|
|
72
|
+
containsSurfacePoint,
|
|
73
|
+
resolveViewport2D,
|
|
74
|
+
surfaceToWorld,
|
|
75
|
+
worldToSurface,
|
|
76
|
+
} from './viewport2d';
|
|
77
|
+
export type {
|
|
78
|
+
LogicalSize,
|
|
79
|
+
Rect,
|
|
80
|
+
ResolvedViewport2D,
|
|
81
|
+
SurfaceSize,
|
|
82
|
+
Viewport,
|
|
83
|
+
ViewportMode,
|
|
84
|
+
} from './viewport2d';
|