@multiplekex/shallot 0.1.12 → 0.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.
Files changed (62) hide show
  1. package/package.json +3 -4
  2. package/src/core/builder.ts +71 -32
  3. package/src/core/component.ts +25 -11
  4. package/src/core/index.ts +14 -13
  5. package/src/core/math.ts +135 -0
  6. package/src/core/runtime.ts +0 -1
  7. package/src/core/state.ts +9 -68
  8. package/src/core/xml.ts +381 -265
  9. package/src/editor/format.ts +5 -0
  10. package/src/editor/index.ts +101 -0
  11. package/src/extras/arrows/index.ts +28 -69
  12. package/src/extras/gradient/index.ts +36 -52
  13. package/src/extras/lines/index.ts +51 -122
  14. package/src/extras/orbit/index.ts +40 -15
  15. package/src/extras/text/font.ts +546 -0
  16. package/src/extras/text/index.ts +158 -204
  17. package/src/extras/text/sdf.ts +429 -0
  18. package/src/standard/activity/index.ts +172 -0
  19. package/src/standard/compute/graph.ts +23 -23
  20. package/src/standard/compute/index.ts +76 -61
  21. package/src/standard/defaults.ts +8 -5
  22. package/src/standard/index.ts +1 -0
  23. package/src/standard/input/index.ts +30 -19
  24. package/src/standard/loading/index.ts +18 -13
  25. package/src/standard/render/bvh/blas.ts +752 -0
  26. package/src/standard/render/bvh/radix.ts +476 -0
  27. package/src/standard/render/bvh/structs.ts +167 -0
  28. package/src/standard/render/bvh/tlas.ts +886 -0
  29. package/src/standard/render/bvh/traverse.ts +467 -0
  30. package/src/standard/render/camera.ts +302 -27
  31. package/src/standard/render/data.ts +93 -0
  32. package/src/standard/render/depth.ts +117 -0
  33. package/src/standard/render/forward/index.ts +259 -0
  34. package/src/standard/render/forward/raster.ts +228 -0
  35. package/src/standard/render/index.ts +443 -70
  36. package/src/standard/render/indirect.ts +40 -0
  37. package/src/standard/render/instance.ts +214 -0
  38. package/src/standard/render/intersection.ts +72 -0
  39. package/src/standard/render/light.ts +16 -16
  40. package/src/standard/render/mesh/index.ts +67 -75
  41. package/src/standard/render/mesh/unified.ts +96 -0
  42. package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
  43. package/src/standard/render/pass.ts +10 -4
  44. package/src/standard/render/postprocess.ts +142 -64
  45. package/src/standard/render/ray.ts +61 -0
  46. package/src/standard/render/scene.ts +38 -164
  47. package/src/standard/render/shaders.ts +484 -0
  48. package/src/standard/render/surface/compile.ts +3 -10
  49. package/src/standard/render/surface/index.ts +60 -30
  50. package/src/standard/render/surface/noise.ts +45 -0
  51. package/src/standard/render/surface/structs.ts +60 -19
  52. package/src/standard/render/surface/wgsl.ts +573 -0
  53. package/src/standard/render/triangle.ts +84 -0
  54. package/src/standard/transforms/index.ts +4 -6
  55. package/src/standard/tween/index.ts +10 -1
  56. package/src/standard/tween/sequence.ts +24 -16
  57. package/src/standard/tween/tween.ts +67 -16
  58. package/src/core/types.ts +0 -37
  59. package/src/standard/compute/inspect.ts +0 -201
  60. package/src/standard/compute/pass.ts +0 -23
  61. package/src/standard/compute/timing.ts +0 -139
  62. package/src/standard/render/forward.ts +0 -273
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@multiplekex/shallot",
3
- "version": "0.1.12",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
+ "./editor": "./src/editor/index.ts",
9
10
  "./extras": "./src/extras/index.ts"
10
11
  },
11
12
  "files": [
@@ -17,9 +18,7 @@
17
18
  "test": "bun test"
18
19
  },
19
20
  "dependencies": {
20
- "@mapbox/tiny-sdf": "^2.0.7",
21
- "bitecs": "^0.4.0",
22
- "fast-xml-parser": "^5.3.3"
21
+ "bitecs": "^0.4.0"
23
22
  },
24
23
  "devDependencies": {
25
24
  "@napi-rs/canvas": "^0.1.88",
@@ -1,12 +1,32 @@
1
1
  import { State } from "./state";
2
- import type { Plugin, Loading } from "./types";
3
2
  import { toposort, type System } from "./scheduler";
4
- import { registerComponent } from "./component";
3
+ import { registerComponent, type ComponentLike } from "./component";
5
4
  import { initRuntime } from "./runtime";
5
+ import type { RelationDef } from "./relation";
6
+
7
+ export interface Plugin {
8
+ readonly systems?: readonly System[];
9
+ readonly components?: Record<string, ComponentLike>;
10
+ readonly relations?: readonly RelationDef[];
11
+ readonly dependencies?: readonly Plugin[];
12
+ readonly initialize?: (
13
+ state: State,
14
+ onProgress?: (progress: number) => void
15
+ ) => void | Promise<void>;
16
+ readonly warm?: (state: State, onProgress?: (progress: number) => void) => void | Promise<void>;
17
+ }
18
+
19
+ export interface Loading {
20
+ show(): (() => void) | void;
21
+ update(progress: number): void;
22
+ }
23
+
24
+ export const NoLoading: Loading = { show: () => {}, update: () => {} };
6
25
 
7
26
  export class StateBuilder {
8
27
  static defaultPlugins: readonly Plugin[] = [];
9
- static loading: ((canvas: HTMLCanvasElement) => Loading) | null = null;
28
+ static initCanvas: ((state: State, canvas: HTMLCanvasElement) => void) | null = null;
29
+ static defaultLoading: ((canvas: HTMLCanvasElement) => Loading) | null = null;
10
30
 
11
31
  private readonly _plugins: Plugin[] = [];
12
32
  private readonly _systems: System[] = [];
@@ -14,13 +34,18 @@ export class StateBuilder {
14
34
  private readonly _excludedPlugins = new Set<Plugin>();
15
35
  private _useDefaultPlugins = true;
16
36
  private _canvas: HTMLCanvasElement | null = null;
17
- private _loading: Loading | null | undefined = undefined;
37
+ private _loading: Loading | null = null;
18
38
 
19
39
  withCanvas(canvas: HTMLCanvasElement): this {
20
40
  this._canvas = canvas;
21
41
  return this;
22
42
  }
23
43
 
44
+ withLoading(loading: Loading): this {
45
+ this._loading = loading;
46
+ return this;
47
+ }
48
+
24
49
  withPlugins(...plugins: Plugin[]): this {
25
50
  this._plugins.push(...plugins);
26
51
  return this;
@@ -51,20 +76,16 @@ export class StateBuilder {
51
76
  return this;
52
77
  }
53
78
 
54
- withLoading(loading: Loading | null): this {
55
- this._loading = loading;
56
- return this;
57
- }
58
-
59
79
  async build(): Promise<State> {
60
- const loading =
61
- this._loading === undefined && this._canvas
62
- ? (StateBuilder.loading?.(this._canvas) ?? null)
63
- : this._loading;
80
+ const state = new State();
64
81
 
65
- const cleanup = loading?.show();
82
+ if (this._canvas && StateBuilder.initCanvas) {
83
+ StateBuilder.initCanvas(state, this._canvas);
84
+ }
66
85
 
67
- const state = new State(this._canvas);
86
+ const loading =
87
+ this._loading ?? (this._canvas && StateBuilder.defaultLoading?.(this._canvas));
88
+ const cleanup = loading?.show();
68
89
 
69
90
  const pluginSet = new Set<Plugin>();
70
91
 
@@ -105,13 +126,15 @@ export class StateBuilder {
105
126
  }
106
127
  const sorted = toposort(allPlugins, edges);
107
128
 
108
- let completed = 0;
109
- const total = sorted.length;
129
+ const total = sorted.length * 2 + this._scenes.length;
110
130
 
111
- for (const plugin of sorted) {
112
- await plugin.initialize?.(state);
113
- completed++;
114
- loading?.update(completed / total);
131
+ for (let i = 0; i < sorted.length; i++) {
132
+ const plugin = sorted[i];
133
+ const onProgress = loading
134
+ ? (progress: number) => loading.update((i + progress) / total)
135
+ : undefined;
136
+ await plugin.initialize?.(state, onProgress);
137
+ loading?.update((i + 1) / total);
115
138
  }
116
139
 
117
140
  for (const system of this._systems) {
@@ -119,21 +142,37 @@ export class StateBuilder {
119
142
  }
120
143
 
121
144
  if (this._scenes.length > 0) {
122
- const { loadSceneFile } = await import("./xml");
145
+ const { parse, load } = await import("./xml");
123
146
  const runtime = await initRuntime();
124
- for (const scenePath of this._scenes) {
125
- const result = await loadSceneFile(
126
- state,
127
- scenePath,
128
- runtime.readFile.bind(runtime)
129
- );
130
- if (result.errors.length > 0) {
131
- console.warn(`Scene "${scenePath}" loaded with errors:`, result.errors);
132
- }
147
+ for (let i = 0; i < this._scenes.length; i++) {
148
+ const xml = await runtime.readFile(this._scenes[i]);
149
+ const nodes = parse(xml);
150
+ load(nodes, state);
151
+ loading?.update((sorted.length + i + 1) / total);
133
152
  }
134
153
  }
135
154
 
136
- cleanup?.();
155
+ const warmable = sorted.filter((p) => p.warm);
156
+ let warmDone = 0;
157
+ const warmBase = sorted.length + this._scenes.length;
158
+
159
+ const warmPromises = warmable.map(async (plugin) => {
160
+ await plugin.warm!(state, (progress: number) => {
161
+ if (loading) {
162
+ loading.update((warmBase + warmDone + progress) / total);
163
+ }
164
+ });
165
+ warmDone++;
166
+ loading?.update((warmBase + warmDone) / total);
167
+ });
168
+
169
+ await Promise.all(warmPromises);
170
+
171
+ if (cleanup) {
172
+ loading?.update(1);
173
+ await new Promise((r) => setTimeout(r, 200));
174
+ cleanup();
175
+ }
137
176
 
138
177
  return state;
139
178
  }
@@ -12,7 +12,6 @@ export interface FieldAccessor {
12
12
  export interface ComponentTraits {
13
13
  defaults?: () => Record<string, number>;
14
14
  adapter?: (attrs: Record<string, string>, eid: number) => Record<string, number>;
15
- accessors?: Record<string, FieldAccessor>;
16
15
  }
17
16
 
18
17
  const traitsMap = new WeakMap<ComponentLike, ComponentTraits>();
@@ -47,15 +46,30 @@ export function clearRegistry(): void {
47
46
  registry.clear();
48
47
  }
49
48
 
50
- export function directAccessor(
51
- data: ComponentArray,
52
- stride: number,
53
- offset: number
54
- ): FieldAccessor {
55
- return {
56
- get: (eid) => data[eid * stride + offset],
57
- set: (eid, v) => {
58
- data[eid * stride + offset] = v;
49
+ export interface FieldProxy extends Array<number>, FieldAccessor {}
50
+
51
+ export function createFieldProxy(data: Float32Array, stride: number, offset: number): FieldProxy {
52
+ function getValue(eid: number): number {
53
+ return data[eid * stride + offset];
54
+ }
55
+
56
+ function setValue(eid: number, value: number): void {
57
+ data[eid * stride + offset] = value;
58
+ }
59
+
60
+ return new Proxy([] as unknown as FieldProxy, {
61
+ get(_, prop) {
62
+ if (prop === "get") return getValue;
63
+ if (prop === "set") return setValue;
64
+ const eid = Number(prop);
65
+ if (Number.isNaN(eid)) return undefined;
66
+ return getValue(eid);
67
+ },
68
+ set(_, prop, value) {
69
+ const eid = Number(prop);
70
+ if (Number.isNaN(eid)) return false;
71
+ setValue(eid, value);
72
+ return true;
59
73
  },
60
- };
74
+ });
61
75
  }
package/src/core/index.ts CHANGED
@@ -24,7 +24,7 @@ export {
24
24
  } from "bitecs";
25
25
 
26
26
  export { State, MAX_ENTITIES } from "./state";
27
- export { StateBuilder } from "./builder";
27
+ export { StateBuilder, NoLoading, type Plugin, type Loading } from "./builder";
28
28
  export { resource, type ResourceKey } from "./resource";
29
29
  export {
30
30
  Scheduler,
@@ -42,12 +42,13 @@ export {
42
42
  clearRegistry,
43
43
  getTraits,
44
44
  setTraits,
45
- directAccessor,
45
+ createFieldProxy,
46
46
  type ComponentTraits,
47
47
  type ComponentData,
48
48
  type ComponentLike,
49
49
  type RegisteredComponent,
50
50
  type FieldAccessor,
51
+ type FieldProxy,
51
52
  } from "./component";
52
53
 
53
54
  export {
@@ -59,8 +60,6 @@ export {
59
60
  type RelationOptions,
60
61
  } from "./relation";
61
62
 
62
- export type { Plugin, Loading, InputState, MouseState, RenderContext } from "./types";
63
-
64
63
  export {
65
64
  clamp,
66
65
  lerp,
@@ -69,6 +68,11 @@ export {
69
68
  eulerToQuaternion,
70
69
  quaternionToEuler,
71
70
  lookAt,
71
+ perspective,
72
+ orthographic,
73
+ multiply,
74
+ invert,
75
+ extractFrustumPlanes,
72
76
  } from "./math";
73
77
 
74
78
  export { toKebabCase, toCamelCase } from "./strings";
@@ -76,18 +80,15 @@ export { toKebabCase, toCamelCase } from "./strings";
76
80
  export { initRuntime, getRuntime, resetRuntime, type Runtime, type RuntimeTarget } from "./runtime";
77
81
 
78
82
  export {
79
- parseXml,
80
- loadScene,
81
- loadSceneFile,
83
+ parse,
84
+ serialize,
85
+ load,
82
86
  registerPostLoadHook,
83
87
  unregisterPostLoadHook,
84
- type ParsedElement,
85
- type EntityDef,
86
- type EntityRef,
87
- type ComponentDef,
88
- type ParseResult,
88
+ type Node,
89
+ type Attr,
90
+ type Ref,
89
91
  type ParseError,
90
- type LoadResult,
91
92
  type PostLoadHook,
92
93
  type PostLoadContext,
93
94
  } from "./xml";
package/src/core/math.ts CHANGED
@@ -149,6 +149,141 @@ export function quaternionToEuler(
149
149
  }
150
150
  }
151
151
 
152
+ export function perspective(fov: number, aspect: number, near: number, far: number): Float32Array {
153
+ if (fov <= 0) throw new Error(`Invalid FOV: ${fov} (must be > 0)`);
154
+ if (aspect <= 0) throw new Error(`Invalid aspect ratio: ${aspect} (must be > 0)`);
155
+ if (near === far) throw new Error(`Invalid depth planes: near === far (${near})`);
156
+ const f = 1 / Math.tan((fov * Math.PI) / 360);
157
+ const nf = 1 / (near - far);
158
+ return new Float32Array([
159
+ f / aspect,
160
+ 0,
161
+ 0,
162
+ 0,
163
+ 0,
164
+ f,
165
+ 0,
166
+ 0,
167
+ 0,
168
+ 0,
169
+ far * nf,
170
+ -1,
171
+ 0,
172
+ 0,
173
+ far * near * nf,
174
+ 0,
175
+ ]);
176
+ }
177
+
178
+ export function orthographic(
179
+ size: number,
180
+ aspect: number,
181
+ near: number,
182
+ far: number
183
+ ): Float32Array {
184
+ if (size <= 0) throw new Error(`Invalid orthographic size: ${size} (must be > 0)`);
185
+ if (aspect <= 0) throw new Error(`Invalid aspect ratio: ${aspect} (must be > 0)`);
186
+ if (near === far) throw new Error(`Invalid depth planes: near === far (${near})`);
187
+ const lr = 1 / (size * aspect);
188
+ const bt = 1 / size;
189
+ const nf = 1 / (near - far);
190
+ return new Float32Array([lr, 0, 0, 0, 0, bt, 0, 0, 0, 0, nf, 0, 0, 0, near * nf, 1]);
191
+ }
192
+
193
+ export function multiply(a: Float32Array, b: Float32Array): Float32Array {
194
+ const out = new Float32Array(16);
195
+ for (let i = 0; i < 4; i++) {
196
+ for (let j = 0; j < 4; j++) {
197
+ out[j * 4 + i] =
198
+ a[i] * b[j * 4] +
199
+ a[i + 4] * b[j * 4 + 1] +
200
+ a[i + 8] * b[j * 4 + 2] +
201
+ a[i + 12] * b[j * 4 + 3];
202
+ }
203
+ }
204
+ return out;
205
+ }
206
+
207
+ export function invert(m: Float32Array): Float32Array {
208
+ const out = new Float32Array(16);
209
+ const r00 = m[0],
210
+ r01 = m[1],
211
+ r02 = m[2];
212
+ const r10 = m[4],
213
+ r11 = m[5],
214
+ r12 = m[6];
215
+ const r20 = m[8],
216
+ r21 = m[9],
217
+ r22 = m[10];
218
+ const tx = m[12],
219
+ ty = m[13],
220
+ tz = m[14];
221
+
222
+ out[0] = r00;
223
+ out[1] = r10;
224
+ out[2] = r20;
225
+ out[3] = 0;
226
+ out[4] = r01;
227
+ out[5] = r11;
228
+ out[6] = r21;
229
+ out[7] = 0;
230
+ out[8] = r02;
231
+ out[9] = r12;
232
+ out[10] = r22;
233
+ out[11] = 0;
234
+ out[12] = -(r00 * tx + r01 * ty + r02 * tz);
235
+ out[13] = -(r10 * tx + r11 * ty + r12 * tz);
236
+ out[14] = -(r20 * tx + r21 * ty + r22 * tz);
237
+ out[15] = 1;
238
+
239
+ return out;
240
+ }
241
+
242
+ export function extractFrustumPlanes(viewProj: Float32Array): Float32Array {
243
+ const planes = new Float32Array(24);
244
+ const m = viewProj;
245
+
246
+ planes[0] = m[3] + m[0];
247
+ planes[1] = m[7] + m[4];
248
+ planes[2] = m[11] + m[8];
249
+ planes[3] = m[15] + m[12];
250
+
251
+ planes[4] = m[3] - m[0];
252
+ planes[5] = m[7] - m[4];
253
+ planes[6] = m[11] - m[8];
254
+ planes[7] = m[15] - m[12];
255
+
256
+ planes[8] = m[3] + m[1];
257
+ planes[9] = m[7] + m[5];
258
+ planes[10] = m[11] + m[9];
259
+ planes[11] = m[15] + m[13];
260
+
261
+ planes[12] = m[3] - m[1];
262
+ planes[13] = m[7] - m[5];
263
+ planes[14] = m[11] - m[9];
264
+ planes[15] = m[15] - m[13];
265
+
266
+ planes[16] = m[2];
267
+ planes[17] = m[6];
268
+ planes[18] = m[10];
269
+ planes[19] = m[14];
270
+
271
+ planes[20] = m[3] - m[2];
272
+ planes[21] = m[7] - m[6];
273
+ planes[22] = m[11] - m[10];
274
+ planes[23] = m[15] - m[14];
275
+ for (let i = 0; i < 6; i++) {
276
+ const len = Math.hypot(planes[i * 4], planes[i * 4 + 1], planes[i * 4 + 2]);
277
+ if (len > 0) {
278
+ planes[i * 4] /= len;
279
+ planes[i * 4 + 1] /= len;
280
+ planes[i * 4 + 2] /= len;
281
+ planes[i * 4 + 3] /= len;
282
+ }
283
+ }
284
+ return planes;
285
+ }
286
+
152
287
  export function lookAt(
153
288
  eyeX: number,
154
289
  eyeY: number,
@@ -99,7 +99,6 @@ function isWebviewResource(path: string): boolean {
99
99
  return path.startsWith("/") || path.startsWith("./") || path.startsWith("../");
100
100
  }
101
101
 
102
- // Dynamic import path to prevent bundler static analysis
103
102
  const TAURI_FS_MODULE = "@tauri-apps/" + "plugin-fs";
104
103
 
105
104
  function createStandaloneRuntime(): Runtime {
package/src/core/state.ts CHANGED
@@ -17,34 +17,23 @@ import {
17
17
  } from "bitecs";
18
18
  import { Scheduler, Time, type GameTime, type System } from "./scheduler";
19
19
  import type { RelationDef } from "./relation";
20
- import type { Plugin } from "./types";
21
- import type { StateBuilder } from "./builder";
20
+ import type { Plugin, StateBuilder } from "./builder";
22
21
  import { initRuntime, type Runtime } from "./runtime";
23
- import {
24
- registerComponent,
25
- getTraits,
26
- getRegisteredComponent,
27
- directAccessor,
28
- type ComponentData,
29
- type FieldAccessor,
30
- } from "./component";
22
+ import { registerComponent, getTraits, type ComponentData } from "./component";
31
23
  import { type ResourceKey } from "./resource";
32
- import { toCamelCase } from "./strings";
33
24
 
34
25
  export const MAX_ENTITIES = 65536;
35
26
 
36
27
  export class State {
37
28
  readonly world: World;
38
29
  readonly scheduler = new Scheduler();
39
- readonly canvas: HTMLCanvasElement | null;
40
- maxEid = 0;
41
30
 
42
31
  private _resources = new Map<symbol, unknown>();
43
32
  private _disposed = false;
44
33
  private _running = false;
45
34
  private _runtime: Runtime | null = null;
46
35
  private _lastTime = 0;
47
- private _fieldAccessors: Map<number, FieldAccessor> | null = null;
36
+ private _maxEid = 0;
48
37
 
49
38
  get time(): Readonly<GameTime> {
50
39
  return this.scheduler.time;
@@ -54,6 +43,10 @@ export class State {
54
43
  return this._running;
55
44
  }
56
45
 
46
+ get maxEid(): number {
47
+ return this._maxEid;
48
+ }
49
+
57
50
  static Builder: (new () => StateBuilder) | null = null;
58
51
 
59
52
  static new(): StateBuilder {
@@ -63,9 +56,8 @@ export class State {
63
56
  return new State.Builder();
64
57
  }
65
58
 
66
- constructor(canvas: HTMLCanvasElement | null = null) {
59
+ constructor() {
67
60
  this.world = createWorld();
68
- this.canvas = canvas;
69
61
  }
70
62
 
71
63
  setResource<T>(key: ResourceKey<T>, value: T): void {
@@ -141,12 +133,11 @@ export class State {
141
133
  if (eid >= MAX_ENTITIES) {
142
134
  throw new Error(`Entity limit exceeded: ${eid} >= ${MAX_ENTITIES}`);
143
135
  }
144
- if (eid > this.maxEid) this.maxEid = eid;
136
+ if (eid > this._maxEid) this._maxEid = eid;
145
137
  return eid;
146
138
  }
147
139
 
148
140
  removeEntity(eid: number): void {
149
- this._fieldAccessors?.delete(eid);
150
141
  removeEntity(this.world, eid);
151
142
  }
152
143
 
@@ -236,59 +227,9 @@ export class State {
236
227
  return targets.length > 0 ? targets[0] : -1;
237
228
  }
238
229
 
239
- private ensureFieldAccessors(): Map<number, FieldAccessor> {
240
- if (!this._fieldAccessors) {
241
- this._fieldAccessors = new Map();
242
- }
243
- return this._fieldAccessors;
244
- }
245
-
246
- resolveFieldPath(path: string): { component: string; field: string } | null {
247
- const dotIndex = path.lastIndexOf(".");
248
- if (dotIndex === -1) return null;
249
- return {
250
- component: path.slice(0, dotIndex),
251
- field: path.slice(dotIndex + 1),
252
- };
253
- }
254
-
255
- bindFieldAccessor(
256
- bindingId: number,
257
- componentName: string,
258
- fieldPath: string
259
- ): FieldAccessor | null {
260
- const registered = getRegisteredComponent(componentName);
261
- if (!registered) return null;
262
-
263
- const camelPath = toCamelCase(fieldPath);
264
-
265
- const traits = getTraits(registered.component);
266
- if (traits?.accessors?.[camelPath]) {
267
- const accessor = traits.accessors[camelPath];
268
- this.ensureFieldAccessors().set(bindingId, accessor);
269
- return accessor;
270
- }
271
-
272
- const array = registered.component[camelPath];
273
- if (array == null || !(ArrayBuffer.isView(array) || Array.isArray(array))) return null;
274
-
275
- const accessor = directAccessor(array as number[], 1, 0);
276
- this.ensureFieldAccessors().set(bindingId, accessor);
277
- return accessor;
278
- }
279
-
280
- getFieldAccessor(bindingId: number): FieldAccessor | undefined {
281
- return this._fieldAccessors?.get(bindingId);
282
- }
283
-
284
- removeFieldAccessor(bindingId: number): boolean {
285
- return this._fieldAccessors?.delete(bindingId) ?? false;
286
- }
287
-
288
230
  dispose(): void {
289
231
  if (this._disposed) return;
290
232
  this.stop();
291
- this._fieldAccessors = null;
292
233
  for (const system of this.scheduler.systems) {
293
234
  system.dispose?.(this);
294
235
  }