@multiplekex/shallot 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.
Files changed (196) hide show
  1. package/dist/core/builder.d.ts +25 -0
  2. package/dist/core/builder.d.ts.map +1 -0
  3. package/dist/core/builder.js +88 -0
  4. package/dist/core/builder.js.map +1 -0
  5. package/dist/core/component.d.ts +29 -0
  6. package/dist/core/component.d.ts.map +1 -0
  7. package/dist/core/component.js +36 -0
  8. package/dist/core/component.js.map +1 -0
  9. package/dist/core/index.d.ts +13 -0
  10. package/dist/core/index.d.ts.map +1 -0
  11. package/dist/core/math.d.ts +32 -0
  12. package/dist/core/math.d.ts.map +1 -0
  13. package/dist/core/math.js +39 -0
  14. package/dist/core/math.js.map +1 -0
  15. package/dist/core/relation.d.ts +16 -0
  16. package/dist/core/relation.d.ts.map +1 -0
  17. package/dist/core/relation.js +32 -0
  18. package/dist/core/relation.js.map +1 -0
  19. package/dist/core/resource.d.ts +9 -0
  20. package/dist/core/resource.d.ts.map +1 -0
  21. package/dist/core/resource.js +12 -0
  22. package/dist/core/resource.js.map +1 -0
  23. package/dist/core/runtime.d.ts +13 -0
  24. package/dist/core/runtime.d.ts.map +1 -0
  25. package/dist/core/runtime.js +118 -0
  26. package/dist/core/runtime.js.map +1 -0
  27. package/dist/core/scheduler.d.ts +47 -0
  28. package/dist/core/scheduler.d.ts.map +1 -0
  29. package/dist/core/scheduler.js +138 -0
  30. package/dist/core/scheduler.js.map +1 -0
  31. package/dist/core/state.d.ts +62 -0
  32. package/dist/core/state.d.ts.map +1 -0
  33. package/dist/core/state.js +185 -0
  34. package/dist/core/state.js.map +1 -0
  35. package/dist/core/strings.d.ts +3 -0
  36. package/dist/core/strings.d.ts.map +1 -0
  37. package/dist/core/strings.js +11 -0
  38. package/dist/core/strings.js.map +1 -0
  39. package/dist/core/types.d.ts +33 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/xml.d.ts +42 -0
  42. package/dist/core/xml.d.ts.map +1 -0
  43. package/dist/core/xml.js +349 -0
  44. package/dist/core/xml.js.map +1 -0
  45. package/dist/extras/arrows/index.d.ts +33 -0
  46. package/dist/extras/arrows/index.d.ts.map +1 -0
  47. package/dist/extras/arrows/index.js +288 -0
  48. package/dist/extras/arrows/index.js.map +1 -0
  49. package/dist/extras/index.d.ts +5 -0
  50. package/dist/extras/index.d.ts.map +1 -0
  51. package/dist/extras/index.js +31 -0
  52. package/dist/extras/index.js.map +1 -0
  53. package/dist/extras/lines/index.d.ts +36 -0
  54. package/dist/extras/lines/index.d.ts.map +1 -0
  55. package/dist/extras/lines/index.js +288 -0
  56. package/dist/extras/lines/index.js.map +1 -0
  57. package/dist/extras/orbit/index.d.ts +20 -0
  58. package/dist/extras/orbit/index.d.ts.map +1 -0
  59. package/dist/extras/orbit/index.js +93 -0
  60. package/dist/extras/orbit/index.js.map +1 -0
  61. package/dist/extras/text/index.d.ts +64 -0
  62. package/dist/extras/text/index.d.ts.map +1 -0
  63. package/dist/extras/text/index.js +423 -0
  64. package/dist/extras/text/index.js.map +1 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +187 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/rust/transforms/pkg/shallot_transforms.js +107 -0
  70. package/dist/rust/transforms/pkg/shallot_transforms.js.map +1 -0
  71. package/dist/standard/compute/graph.d.ts +37 -0
  72. package/dist/standard/compute/graph.d.ts.map +1 -0
  73. package/dist/standard/compute/graph.js +85 -0
  74. package/dist/standard/compute/graph.js.map +1 -0
  75. package/dist/standard/compute/index.d.ts +21 -0
  76. package/dist/standard/compute/index.d.ts.map +1 -0
  77. package/dist/standard/compute/index.js +81 -0
  78. package/dist/standard/compute/index.js.map +1 -0
  79. package/dist/standard/defaults.d.ts +3 -0
  80. package/dist/standard/defaults.d.ts.map +1 -0
  81. package/dist/standard/defaults.js +18 -0
  82. package/dist/standard/defaults.js.map +1 -0
  83. package/dist/standard/index.d.ts +8 -0
  84. package/dist/standard/index.d.ts.map +1 -0
  85. package/dist/standard/input/index.d.ts +5 -0
  86. package/dist/standard/input/index.d.ts.map +1 -0
  87. package/dist/standard/input/index.js +70 -0
  88. package/dist/standard/input/index.js.map +1 -0
  89. package/dist/standard/loading/index.d.ts +7 -0
  90. package/dist/standard/loading/index.d.ts.map +1 -0
  91. package/dist/standard/loading/index.js +91 -0
  92. package/dist/standard/loading/index.js.map +1 -0
  93. package/dist/standard/render/camera.d.ts +36 -0
  94. package/dist/standard/render/camera.d.ts.map +1 -0
  95. package/dist/standard/render/camera.js +71 -0
  96. package/dist/standard/render/camera.js.map +1 -0
  97. package/dist/standard/render/forward.d.ts +30 -0
  98. package/dist/standard/render/forward.d.ts.map +1 -0
  99. package/dist/standard/render/forward.js +158 -0
  100. package/dist/standard/render/forward.js.map +1 -0
  101. package/dist/standard/render/index.d.ts +22 -0
  102. package/dist/standard/render/index.d.ts.map +1 -0
  103. package/dist/standard/render/index.js +153 -0
  104. package/dist/standard/render/index.js.map +1 -0
  105. package/dist/standard/render/light.d.ts +25 -0
  106. package/dist/standard/render/light.d.ts.map +1 -0
  107. package/dist/standard/render/light.js +48 -0
  108. package/dist/standard/render/light.js.map +1 -0
  109. package/dist/standard/render/mesh/box.d.ts +3 -0
  110. package/dist/standard/render/mesh/box.d.ts.map +1 -0
  111. package/dist/standard/render/mesh/box.js +190 -0
  112. package/dist/standard/render/mesh/box.js.map +1 -0
  113. package/dist/standard/render/mesh/index.d.ts +52 -0
  114. package/dist/standard/render/mesh/index.d.ts.map +1 -0
  115. package/dist/standard/render/mesh/index.js +158 -0
  116. package/dist/standard/render/mesh/index.js.map +1 -0
  117. package/dist/standard/render/mesh/plane.d.ts +3 -0
  118. package/dist/standard/render/mesh/plane.d.ts.map +1 -0
  119. package/dist/standard/render/mesh/plane.js +33 -0
  120. package/dist/standard/render/mesh/plane.js.map +1 -0
  121. package/dist/standard/render/mesh/sphere.d.ts +3 -0
  122. package/dist/standard/render/mesh/sphere.d.ts.map +1 -0
  123. package/dist/standard/render/mesh/sphere.js +25 -0
  124. package/dist/standard/render/mesh/sphere.js.map +1 -0
  125. package/dist/standard/render/postprocess.d.ts +11 -0
  126. package/dist/standard/render/postprocess.d.ts.map +1 -0
  127. package/dist/standard/render/postprocess.js +190 -0
  128. package/dist/standard/render/postprocess.js.map +1 -0
  129. package/dist/standard/render/scene.d.ts +8 -0
  130. package/dist/standard/render/scene.d.ts.map +1 -0
  131. package/dist/standard/render/scene.js +67 -0
  132. package/dist/standard/render/scene.js.map +1 -0
  133. package/dist/standard/transforms/index.d.ts +27 -0
  134. package/dist/standard/transforms/index.d.ts.map +1 -0
  135. package/dist/standard/transforms/index.js +122 -0
  136. package/dist/standard/transforms/index.js.map +1 -0
  137. package/dist/standard/transforms/wasm.d.ts +17 -0
  138. package/dist/standard/transforms/wasm.d.ts.map +1 -0
  139. package/dist/standard/transforms/wasm.js +31 -0
  140. package/dist/standard/transforms/wasm.js.map +1 -0
  141. package/dist/standard/tween/easing.d.ts +5 -0
  142. package/dist/standard/tween/easing.d.ts.map +1 -0
  143. package/dist/standard/tween/easing.js +80 -0
  144. package/dist/standard/tween/easing.js.map +1 -0
  145. package/dist/standard/tween/index.d.ts +4 -0
  146. package/dist/standard/tween/index.d.ts.map +1 -0
  147. package/dist/standard/tween/sequence.d.ts +20 -0
  148. package/dist/standard/tween/sequence.d.ts.map +1 -0
  149. package/dist/standard/tween/sequence.js +95 -0
  150. package/dist/standard/tween/sequence.js.map +1 -0
  151. package/dist/standard/tween/tween.d.ts +28 -0
  152. package/dist/standard/tween/tween.d.ts.map +1 -0
  153. package/dist/standard/tween/tween.js +136 -0
  154. package/dist/standard/tween/tween.js.map +1 -0
  155. package/package.json +63 -0
  156. package/src/core/builder.ts +148 -0
  157. package/src/core/component.ts +71 -0
  158. package/src/core/index.ts +92 -0
  159. package/src/core/math.ts +128 -0
  160. package/src/core/relation.ts +46 -0
  161. package/src/core/resource.ts +18 -0
  162. package/src/core/runtime.ts +185 -0
  163. package/src/core/scheduler.ts +238 -0
  164. package/src/core/state.ts +295 -0
  165. package/src/core/strings.ts +10 -0
  166. package/src/core/types.ts +37 -0
  167. package/src/core/xml.ts +676 -0
  168. package/src/extras/arrows/index.ts +363 -0
  169. package/src/extras/index.ts +4 -0
  170. package/src/extras/lines/index.ts +368 -0
  171. package/src/extras/orbit/index.ts +133 -0
  172. package/src/extras/text/index.ts +641 -0
  173. package/src/index.ts +3 -0
  174. package/src/standard/compute/graph.ts +165 -0
  175. package/src/standard/compute/index.ts +116 -0
  176. package/src/standard/defaults.ts +17 -0
  177. package/src/standard/index.ts +7 -0
  178. package/src/standard/input/index.ts +142 -0
  179. package/src/standard/loading/index.ts +136 -0
  180. package/src/standard/render/camera.ts +87 -0
  181. package/src/standard/render/forward.ts +212 -0
  182. package/src/standard/render/index.ts +175 -0
  183. package/src/standard/render/light.ts +81 -0
  184. package/src/standard/render/mesh/box.ts +20 -0
  185. package/src/standard/render/mesh/index.ts +227 -0
  186. package/src/standard/render/mesh/plane.ts +11 -0
  187. package/src/standard/render/mesh/sphere.ts +40 -0
  188. package/src/standard/render/postprocess.ts +235 -0
  189. package/src/standard/render/scene.ts +116 -0
  190. package/src/standard/transforms/index.ts +184 -0
  191. package/src/standard/transforms/wasm.ts +61 -0
  192. package/src/standard/tween/easing.ts +169 -0
  193. package/src/standard/tween/index.ts +13 -0
  194. package/src/standard/tween/sequence.ts +142 -0
  195. package/src/standard/tween/tween.ts +265 -0
  196. package/src/vite-env.d.ts +6 -0
@@ -0,0 +1,92 @@
1
+ export {
2
+ observe,
3
+ onAdd,
4
+ onRemove,
5
+ onGet,
6
+ onSet,
7
+ And,
8
+ Or,
9
+ Not,
10
+ Any,
11
+ All,
12
+ None,
13
+ Hierarchy,
14
+ Cascade,
15
+ createRelation,
16
+ getRelationTargets,
17
+ Wildcard,
18
+ IsA,
19
+ Pair,
20
+ type World,
21
+ type EntityId,
22
+ type ComponentRef,
23
+ type Relation,
24
+ } from "bitecs";
25
+
26
+ export { State, MAX_ENTITIES } from "./state";
27
+ export { StateBuilder } from "./builder";
28
+ export { resource, type ResourceKey } from "./resource";
29
+ export {
30
+ Scheduler,
31
+ Time,
32
+ CycleError,
33
+ OrderingError,
34
+ type System,
35
+ type SystemGroup,
36
+ type GameTime,
37
+ } from "./scheduler";
38
+
39
+ export {
40
+ registerComponent,
41
+ getRegisteredComponent,
42
+ clearRegistry,
43
+ getTraits,
44
+ setTraits,
45
+ directAccessor,
46
+ type ParseContext,
47
+ type ComponentTraits,
48
+ type ComponentData,
49
+ type RegisteredComponent,
50
+ type FieldAccessor,
51
+ } from "./component";
52
+
53
+ export {
54
+ defineRelation,
55
+ getRelationDef,
56
+ clearRelationDefs,
57
+ ChildOf,
58
+ type RelationDef,
59
+ type RelationOptions,
60
+ } from "./relation";
61
+
62
+ export type { Plugin, Loading, InputState, MouseState, RenderContext } from "./types";
63
+
64
+ export {
65
+ clamp,
66
+ lerp,
67
+ slerp,
68
+ rotateQuaternion,
69
+ eulerToQuaternion,
70
+ quaternionToEuler,
71
+ lookAt,
72
+ } from "./math";
73
+
74
+ export { toKebabCase, toCamelCase } from "./strings";
75
+
76
+ export { initRuntime, getRuntime, resetRuntime, type Runtime, type RuntimeTarget } from "./runtime";
77
+
78
+ export {
79
+ parseXml,
80
+ loadScene,
81
+ loadSceneFile,
82
+ registerPostLoadHook,
83
+ unregisterPostLoadHook,
84
+ type ParsedElement,
85
+ type EntityDef,
86
+ type EntityRef,
87
+ type ComponentDef,
88
+ type ParseResult,
89
+ type ParseError,
90
+ type LoadResult,
91
+ type PostLoadHook,
92
+ } from "./xml";
@@ -0,0 +1,128 @@
1
+ import { quat, utils, mat4 } from "wgpu-matrix";
2
+
3
+ const tempQuatA = quat.create();
4
+ const tempQuatB = quat.create();
5
+ const tempQuatOut = quat.create();
6
+ const tempMat4 = mat4.create();
7
+ const tempVec3A = new Float32Array(3);
8
+ const tempVec3B = new Float32Array(3);
9
+ const tempVec3C = new Float32Array(3);
10
+
11
+ export function clamp(value: number, min: number, max: number): number {
12
+ return Math.max(min, Math.min(max, value));
13
+ }
14
+
15
+ export function lerp(a: number, b: number, t: number): number {
16
+ return a + (b - a) * t;
17
+ }
18
+
19
+ export function slerp(
20
+ fromX: number,
21
+ fromY: number,
22
+ fromZ: number,
23
+ fromW: number,
24
+ toX: number,
25
+ toY: number,
26
+ toZ: number,
27
+ toW: number,
28
+ t: number
29
+ ): { x: number; y: number; z: number; w: number } {
30
+ quat.set(fromX, fromY, fromZ, fromW, tempQuatA);
31
+ quat.set(toX, toY, toZ, toW, tempQuatB);
32
+ quat.slerp(tempQuatA, tempQuatB, t, tempQuatOut);
33
+ return { x: tempQuatOut[0], y: tempQuatOut[1], z: tempQuatOut[2], w: tempQuatOut[3] };
34
+ }
35
+
36
+ export function rotateQuaternion(
37
+ quatX: number,
38
+ quatY: number,
39
+ quatZ: number,
40
+ quatW: number,
41
+ dx: number,
42
+ dy: number,
43
+ dz: number
44
+ ): { x: number; y: number; z: number; w: number } {
45
+ quat.fromEuler(utils.degToRad(dx), utils.degToRad(dy), utils.degToRad(dz), "xyz", tempQuatA);
46
+ quat.set(quatX, quatY, quatZ, quatW, tempQuatB);
47
+ quat.multiply(tempQuatB, tempQuatA, tempQuatOut);
48
+ return { x: tempQuatOut[0], y: tempQuatOut[1], z: tempQuatOut[2], w: tempQuatOut[3] };
49
+ }
50
+
51
+ export function eulerToQuaternion(
52
+ x: number,
53
+ y: number,
54
+ z: number
55
+ ): { x: number; y: number; z: number; w: number } {
56
+ quat.fromEuler(utils.degToRad(x), utils.degToRad(y), utils.degToRad(z), "xyz", tempQuatOut);
57
+ return { x: tempQuatOut[0], y: tempQuatOut[1], z: tempQuatOut[2], w: tempQuatOut[3] };
58
+ }
59
+
60
+ export function quaternionToEuler(
61
+ x: number,
62
+ y: number,
63
+ z: number,
64
+ w: number
65
+ ): { x: number; y: number; z: number } {
66
+ const x2 = x + x,
67
+ y2 = y + y,
68
+ z2 = z + z;
69
+ const xx = x * x2,
70
+ xy = x * y2,
71
+ xz = x * z2;
72
+ const yy = y * y2,
73
+ yz = y * z2,
74
+ zz = z * z2;
75
+ const wx = w * x2,
76
+ wy = w * y2,
77
+ wz = w * z2;
78
+
79
+ const m11 = 1 - (yy + zz);
80
+ const m12 = xy - wz;
81
+ const m13 = xz + wy;
82
+ const m22 = 1 - (xx + zz);
83
+ const m23 = yz - wx;
84
+ const m32 = yz + wx;
85
+ const m33 = 1 - (xx + yy);
86
+
87
+ const ey = Math.asin(Math.max(-1, Math.min(1, m13)));
88
+ let ex: number, ez: number;
89
+
90
+ if (Math.abs(m13) < 0.9999999) {
91
+ ex = Math.atan2(-m23, m33);
92
+ ez = Math.atan2(-m12, m11);
93
+ } else {
94
+ ex = Math.atan2(m32, m22);
95
+ ez = 0;
96
+ }
97
+
98
+ return {
99
+ x: utils.radToDeg(ex),
100
+ y: utils.radToDeg(ey),
101
+ z: utils.radToDeg(ez),
102
+ };
103
+ }
104
+
105
+ export function lookAt(
106
+ eyeX: number,
107
+ eyeY: number,
108
+ eyeZ: number,
109
+ targetX: number,
110
+ targetY: number,
111
+ targetZ: number,
112
+ upX = 0,
113
+ upY = 1,
114
+ upZ = 0
115
+ ): { x: number; y: number; z: number; w: number } {
116
+ tempVec3A[0] = eyeX;
117
+ tempVec3A[1] = eyeY;
118
+ tempVec3A[2] = eyeZ;
119
+ tempVec3B[0] = targetX;
120
+ tempVec3B[1] = targetY;
121
+ tempVec3B[2] = targetZ;
122
+ tempVec3C[0] = upX;
123
+ tempVec3C[1] = upY;
124
+ tempVec3C[2] = upZ;
125
+ mat4.cameraAim(tempVec3A, tempVec3B, tempVec3C, tempMat4);
126
+ quat.fromMat(tempMat4, tempQuatOut);
127
+ return { x: tempQuatOut[0], y: tempQuatOut[1], z: tempQuatOut[2], w: tempQuatOut[3] };
128
+ }
@@ -0,0 +1,46 @@
1
+ import { createRelation as bitecsCreateRelation, type Relation } from "bitecs";
2
+ import { toKebabCase } from "./strings";
3
+
4
+ export interface RelationOptions {
5
+ readonly exclusive?: boolean;
6
+ readonly autoRemoveSubject?: boolean;
7
+ }
8
+
9
+ export interface RelationDef {
10
+ readonly name: string;
11
+ readonly relation: Relation<unknown>;
12
+ readonly exclusive?: boolean;
13
+ readonly autoRemoveSubject?: boolean;
14
+ }
15
+
16
+ const registry = new Map<string, RelationDef>();
17
+
18
+ export function defineRelation(name: string, options?: RelationOptions): RelationDef {
19
+ const relation = bitecsCreateRelation({
20
+ exclusive: options?.exclusive,
21
+ autoRemoveSubject: options?.autoRemoveSubject,
22
+ });
23
+
24
+ const def: RelationDef = {
25
+ name: toKebabCase(name),
26
+ relation,
27
+ exclusive: options?.exclusive,
28
+ autoRemoveSubject: options?.autoRemoveSubject,
29
+ };
30
+
31
+ registry.set(def.name, def);
32
+ return def;
33
+ }
34
+
35
+ export function getRelationDef(name: string): RelationDef | undefined {
36
+ return registry.get(toKebabCase(name));
37
+ }
38
+
39
+ export function clearRelationDefs(): void {
40
+ registry.clear();
41
+ }
42
+
43
+ export const ChildOf = defineRelation("child-of", {
44
+ exclusive: true,
45
+ autoRemoveSubject: true,
46
+ });
@@ -0,0 +1,18 @@
1
+ export interface ResourceContainer {
2
+ getResource<T>(key: ResourceKey<T>): T | undefined;
3
+ }
4
+
5
+ export type ResourceKey<T> = symbol & {
6
+ __type?: T;
7
+ from(state: ResourceContainer): T | undefined;
8
+ };
9
+
10
+ export function resource<T>(name: string): ResourceKey<T> {
11
+ const key = Symbol(name) as symbol;
12
+ const resourceKey = Object.assign(key, {
13
+ from(state: ResourceContainer): T | undefined {
14
+ return state.getResource(resourceKey);
15
+ },
16
+ });
17
+ return resourceKey as ResourceKey<T>;
18
+ }
@@ -0,0 +1,185 @@
1
+ export type RuntimeTarget = "headless" | "standalone" | "web";
2
+
3
+ export interface Runtime {
4
+ readonly target: RuntimeTarget;
5
+ readFile(path: string): Promise<string>;
6
+ readBinary(path: string): Promise<ArrayBuffer>;
7
+ requestFrame(callback: () => void): void;
8
+ now(): number;
9
+ }
10
+
11
+ declare const __TAURI_INTERNALS__: unknown;
12
+ declare const Bun: unknown;
13
+
14
+ export function detectTarget(): RuntimeTarget {
15
+ if (typeof __TAURI_INTERNALS__ !== "undefined") {
16
+ return "standalone";
17
+ }
18
+
19
+ if (typeof Bun !== "undefined") {
20
+ return "headless";
21
+ }
22
+
23
+ if (typeof window !== "undefined" && typeof fetch === "function") {
24
+ return "web";
25
+ }
26
+
27
+ throw new Error("Unknown runtime environment");
28
+ }
29
+
30
+ function createWebRuntime(): Runtime {
31
+ return {
32
+ target: "web",
33
+
34
+ async readFile(path: string): Promise<string> {
35
+ const response = await fetch(path);
36
+ if (!response.ok) {
37
+ throw new Error(`Failed to load ${path}: ${response.status}`);
38
+ }
39
+ return response.text();
40
+ },
41
+
42
+ async readBinary(path: string): Promise<ArrayBuffer> {
43
+ const response = await fetch(path);
44
+ if (!response.ok) {
45
+ throw new Error(`Failed to load ${path}: ${response.status}`);
46
+ }
47
+ return response.arrayBuffer();
48
+ },
49
+
50
+ requestFrame(callback: () => void): void {
51
+ requestAnimationFrame(callback);
52
+ },
53
+
54
+ now(): number {
55
+ return performance.now();
56
+ },
57
+ };
58
+ }
59
+
60
+ function createHeadlessRuntime(): Runtime {
61
+ return {
62
+ target: "headless",
63
+
64
+ async readFile(path: string): Promise<string> {
65
+ const file = (
66
+ Bun as {
67
+ file: (path: string) => {
68
+ text: () => Promise<string>;
69
+ arrayBuffer: () => Promise<ArrayBuffer>;
70
+ };
71
+ }
72
+ ).file(path);
73
+ return file.text();
74
+ },
75
+
76
+ async readBinary(path: string): Promise<ArrayBuffer> {
77
+ const file = (
78
+ Bun as {
79
+ file: (path: string) => {
80
+ text: () => Promise<string>;
81
+ arrayBuffer: () => Promise<ArrayBuffer>;
82
+ };
83
+ }
84
+ ).file(path);
85
+ return file.arrayBuffer();
86
+ },
87
+
88
+ requestFrame(callback: () => void): void {
89
+ setTimeout(callback, 0);
90
+ },
91
+
92
+ now(): number {
93
+ return performance.now();
94
+ },
95
+ };
96
+ }
97
+
98
+ function isWebviewResource(path: string): boolean {
99
+ return path.startsWith("/") || path.startsWith("./") || path.startsWith("../");
100
+ }
101
+
102
+ function createStandaloneRuntime(): Runtime {
103
+ return {
104
+ target: "standalone",
105
+
106
+ async readFile(path: string): Promise<string> {
107
+ if (isWebviewResource(path)) {
108
+ const response = await fetch(path);
109
+ if (!response.ok) {
110
+ throw new Error(`Failed to load ${path}: ${response.status}`);
111
+ }
112
+ return response.text();
113
+ }
114
+ const { readTextFile } = await import(/* @vite-ignore */ "@tauri-apps/plugin-fs");
115
+ return readTextFile(path);
116
+ },
117
+
118
+ async readBinary(path: string): Promise<ArrayBuffer> {
119
+ if (isWebviewResource(path)) {
120
+ const response = await fetch(path);
121
+ if (!response.ok) {
122
+ throw new Error(`Failed to load ${path}: ${response.status}`);
123
+ }
124
+ return response.arrayBuffer();
125
+ }
126
+ const { readFile } = await import(/* @vite-ignore */ "@tauri-apps/plugin-fs");
127
+ const bytes = await readFile(path);
128
+ return bytes.buffer;
129
+ },
130
+
131
+ requestFrame(callback: () => void): void {
132
+ requestAnimationFrame(callback);
133
+ },
134
+
135
+ now(): number {
136
+ return performance.now();
137
+ },
138
+ };
139
+ }
140
+
141
+ let _runtime: Runtime | undefined;
142
+
143
+ async function createRuntime(): Promise<Runtime> {
144
+ const target = detectTarget();
145
+
146
+ switch (target) {
147
+ case "headless":
148
+ return createHeadlessRuntime();
149
+ case "standalone":
150
+ return createStandaloneRuntime();
151
+ case "web":
152
+ return createWebRuntime();
153
+ }
154
+ }
155
+
156
+ let _initPromise: Promise<Runtime> | undefined;
157
+
158
+ export function getRuntime(): Runtime {
159
+ if (_runtime) return _runtime;
160
+
161
+ if (!_initPromise) {
162
+ _initPromise = createRuntime().then((r) => {
163
+ _runtime = r;
164
+ return r;
165
+ });
166
+ }
167
+
168
+ throw new Error("Runtime not initialized. Call initRuntime() first.");
169
+ }
170
+
171
+ export async function initRuntime(): Promise<Runtime> {
172
+ if (_runtime) return _runtime;
173
+
174
+ if (!_initPromise) {
175
+ _initPromise = createRuntime();
176
+ }
177
+
178
+ _runtime = await _initPromise;
179
+ return _runtime;
180
+ }
181
+
182
+ export function resetRuntime(): void {
183
+ _runtime = undefined;
184
+ _initPromise = undefined;
185
+ }
@@ -0,0 +1,238 @@
1
+ import type { State } from "./state";
2
+
3
+ export class CycleError extends Error {
4
+ constructor(message = "Circular dependency detected") {
5
+ super(message);
6
+ this.name = "CycleError";
7
+ }
8
+ }
9
+
10
+ export function toposort<T>(nodes: T[], edges: [T, T][]): T[] {
11
+ if (nodes.length === 0) return [];
12
+
13
+ const graph = new Map<T, Set<T>>();
14
+ const inDegree = new Map<T, number>();
15
+
16
+ for (const node of nodes) {
17
+ graph.set(node, new Set());
18
+ inDegree.set(node, 0);
19
+ }
20
+
21
+ for (const [from, to] of edges) {
22
+ if (!graph.has(from) || !graph.has(to)) continue;
23
+ graph.get(from)!.add(to);
24
+ inDegree.set(to, inDegree.get(to)! + 1);
25
+ }
26
+
27
+ detectCycle(nodes, graph);
28
+
29
+ const queue: T[] = [];
30
+ const sorted: T[] = [];
31
+
32
+ for (const node of nodes) {
33
+ if (inDegree.get(node) === 0) queue.push(node);
34
+ }
35
+
36
+ while (queue.length > 0) {
37
+ const node = queue.shift()!;
38
+ sorted.push(node);
39
+
40
+ for (const dep of graph.get(node)!) {
41
+ const newDegree = inDegree.get(dep)! - 1;
42
+ inDegree.set(dep, newDegree);
43
+ if (newDegree === 0) queue.push(dep);
44
+ }
45
+ }
46
+
47
+ return sorted;
48
+ }
49
+
50
+ function detectCycle<T>(nodes: T[], graph: Map<T, Set<T>>): void {
51
+ const visited = new Set<T>();
52
+ const stack = new Set<T>();
53
+
54
+ function hasCycle(node: T): boolean {
55
+ if (stack.has(node)) return true;
56
+ if (visited.has(node)) return false;
57
+
58
+ visited.add(node);
59
+ stack.add(node);
60
+
61
+ for (const dep of graph.get(node)!) {
62
+ if (hasCycle(dep)) return true;
63
+ }
64
+
65
+ stack.delete(node);
66
+ return false;
67
+ }
68
+
69
+ for (const node of nodes) {
70
+ if (hasCycle(node)) throw new CycleError();
71
+ }
72
+ }
73
+
74
+ export const Time = {
75
+ FIXED_DT: 1 / 50,
76
+ DEFAULT_DT: 1 / 60,
77
+ } as const;
78
+
79
+ export interface GameTime {
80
+ deltaTime: number;
81
+ fixedDeltaTime: number;
82
+ elapsed: number;
83
+ }
84
+
85
+ export type SystemGroup = "setup" | "fixed" | "simulation" | "draw";
86
+
87
+ export interface System {
88
+ readonly update?: (state: State) => void;
89
+ readonly setup?: (state: State) => void;
90
+ readonly dispose?: (state: State) => void;
91
+ readonly group?: SystemGroup;
92
+ readonly first?: boolean;
93
+ readonly last?: boolean;
94
+ readonly before?: readonly System[];
95
+ readonly after?: readonly System[];
96
+ }
97
+
98
+ export class OrderingError extends Error {
99
+ constructor(message: string) {
100
+ super(message);
101
+ this.name = "OrderingError";
102
+ }
103
+ }
104
+
105
+ export class Scheduler {
106
+ private readonly _systems = new Set<System>();
107
+ private _systemsVersion = 0;
108
+ private _accumulator = 0;
109
+ private readonly _initialized = new WeakSet<System>();
110
+ private _cache = new Map<SystemGroup, System[]>();
111
+ private _cacheVersion = -1;
112
+ private _time: GameTime = {
113
+ deltaTime: 0,
114
+ fixedDeltaTime: Time.FIXED_DT,
115
+ elapsed: 0,
116
+ };
117
+
118
+ get systems(): ReadonlySet<System> {
119
+ return this._systems;
120
+ }
121
+
122
+ get systemsVersion(): number {
123
+ return this._systemsVersion;
124
+ }
125
+
126
+ get accumulator(): number {
127
+ return this._accumulator;
128
+ }
129
+
130
+ get time(): Readonly<GameTime> {
131
+ return this._time;
132
+ }
133
+
134
+ register(system: System): void {
135
+ this._systems.add(system);
136
+ this._systemsVersion++;
137
+ }
138
+
139
+ unregister(system: System): void {
140
+ if (this._systems.delete(system)) {
141
+ this._systemsVersion++;
142
+ }
143
+ }
144
+
145
+ step(state: State, deltaTime = Time.DEFAULT_DT): void {
146
+ const fixedDt = Time.FIXED_DT;
147
+
148
+ this._time.deltaTime = deltaTime;
149
+ this._time.elapsed += deltaTime;
150
+ this._accumulator += deltaTime;
151
+
152
+ this.runGroup(state, "setup");
153
+
154
+ while (this._accumulator >= fixedDt) {
155
+ this._time.deltaTime = fixedDt;
156
+ this.runGroup(state, "fixed");
157
+ this._accumulator -= fixedDt;
158
+ }
159
+
160
+ this._time.deltaTime = deltaTime;
161
+ this.runGroup(state, "simulation");
162
+ this.runGroup(state, "draw");
163
+ }
164
+
165
+ private runGroup(state: State, group: SystemGroup): void {
166
+ for (const system of this.getSorted(group)) {
167
+ if (!this._initialized.has(system)) {
168
+ system.setup?.(state);
169
+ this._initialized.add(system);
170
+ }
171
+ system.update?.(state);
172
+ }
173
+ }
174
+
175
+ private getSorted(group: SystemGroup): System[] {
176
+ if (this._systemsVersion !== this._cacheVersion) {
177
+ this._cache.clear();
178
+ this._cacheVersion = this._systemsVersion;
179
+ }
180
+
181
+ const cached = this._cache.get(group);
182
+ if (cached) return cached;
183
+
184
+ const all = Array.from(this._systems);
185
+ const filtered = all.filter((s) => (s.group ?? "simulation") === group);
186
+ const sorted = sortSystems(filtered, all);
187
+ this._cache.set(group, sorted);
188
+ return sorted;
189
+ }
190
+ }
191
+
192
+ function sortSystems(systems: System[], allSystems?: System[]): System[] {
193
+ const all = allSystems ?? systems;
194
+ validateSystems(systems, all);
195
+
196
+ const first = systems.filter((s) => s.first);
197
+ const last = systems.filter((s) => s.last);
198
+ const normal = systems.filter((s) => !s.first && !s.last);
199
+
200
+ return [
201
+ ...toposort(first, buildEdges(first)),
202
+ ...toposort(normal, buildEdges(normal)),
203
+ ...toposort(last, buildEdges(last)),
204
+ ];
205
+ }
206
+
207
+ function buildEdges(systems: System[]): [System, System][] {
208
+ const edges: [System, System][] = [];
209
+ for (const system of systems) {
210
+ for (const target of system.before ?? []) {
211
+ if (systems.includes(target)) edges.push([system, target]);
212
+ }
213
+ for (const target of system.after ?? []) {
214
+ if (systems.includes(target)) edges.push([target, system]);
215
+ }
216
+ }
217
+ return edges;
218
+ }
219
+
220
+ function validateSystems(systems: System[], all: System[]): void {
221
+ for (const s of systems) {
222
+ if (s.first && s.last) {
223
+ throw new OrderingError("System cannot have both first and last constraints");
224
+ }
225
+
226
+ const group = s.group ?? "simulation";
227
+ for (const ref of s.before ?? []) checkGroup(ref, group, all);
228
+ for (const ref of s.after ?? []) checkGroup(ref, group, all);
229
+ }
230
+ }
231
+
232
+ function checkGroup(ref: System, group: string, all: System[]): void {
233
+ if (!all.includes(ref)) return;
234
+ const refGroup = ref.group ?? "simulation";
235
+ if (refGroup !== group) {
236
+ throw new OrderingError(`Cross-group constraint: ${group} references ${refGroup}`);
237
+ }
238
+ }