iris-ecs 0.0.1

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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +721 -0
  3. package/dist/actions.d.ts +43 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +35 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/archetype.d.ts +194 -0
  8. package/dist/archetype.d.ts.map +1 -0
  9. package/dist/archetype.js +412 -0
  10. package/dist/archetype.js.map +1 -0
  11. package/dist/component.d.ts +89 -0
  12. package/dist/component.d.ts.map +1 -0
  13. package/dist/component.js +237 -0
  14. package/dist/component.js.map +1 -0
  15. package/dist/encoding.d.ts +204 -0
  16. package/dist/encoding.d.ts.map +1 -0
  17. package/dist/encoding.js +215 -0
  18. package/dist/encoding.js.map +1 -0
  19. package/dist/entity.d.ts +129 -0
  20. package/dist/entity.d.ts.map +1 -0
  21. package/dist/entity.js +243 -0
  22. package/dist/entity.js.map +1 -0
  23. package/dist/event.d.ts +237 -0
  24. package/dist/event.d.ts.map +1 -0
  25. package/dist/event.js +293 -0
  26. package/dist/event.js.map +1 -0
  27. package/dist/filters.d.ts +121 -0
  28. package/dist/filters.d.ts.map +1 -0
  29. package/dist/filters.js +202 -0
  30. package/dist/filters.js.map +1 -0
  31. package/dist/index.d.ts +24 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +54 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/name.d.ts +70 -0
  36. package/dist/name.d.ts.map +1 -0
  37. package/dist/name.js +172 -0
  38. package/dist/name.js.map +1 -0
  39. package/dist/observer.d.ts +83 -0
  40. package/dist/observer.d.ts.map +1 -0
  41. package/dist/observer.js +62 -0
  42. package/dist/observer.js.map +1 -0
  43. package/dist/query.d.ts +198 -0
  44. package/dist/query.d.ts.map +1 -0
  45. package/dist/query.js +299 -0
  46. package/dist/query.js.map +1 -0
  47. package/dist/registry.d.ts +118 -0
  48. package/dist/registry.d.ts.map +1 -0
  49. package/dist/registry.js +112 -0
  50. package/dist/registry.js.map +1 -0
  51. package/dist/relation.d.ts +60 -0
  52. package/dist/relation.d.ts.map +1 -0
  53. package/dist/relation.js +171 -0
  54. package/dist/relation.js.map +1 -0
  55. package/dist/removal.d.ts +27 -0
  56. package/dist/removal.d.ts.map +1 -0
  57. package/dist/removal.js +66 -0
  58. package/dist/removal.js.map +1 -0
  59. package/dist/resource.d.ts +78 -0
  60. package/dist/resource.d.ts.map +1 -0
  61. package/dist/resource.js +86 -0
  62. package/dist/resource.js.map +1 -0
  63. package/dist/scheduler.d.ts +106 -0
  64. package/dist/scheduler.d.ts.map +1 -0
  65. package/dist/scheduler.js +204 -0
  66. package/dist/scheduler.js.map +1 -0
  67. package/dist/schema.d.ts +117 -0
  68. package/dist/schema.d.ts.map +1 -0
  69. package/dist/schema.js +113 -0
  70. package/dist/schema.js.map +1 -0
  71. package/dist/world.d.ts +172 -0
  72. package/dist/world.d.ts.map +1 -0
  73. package/dist/world.js +127 -0
  74. package/dist/world.js.map +1 -0
  75. package/package.json +52 -0
@@ -0,0 +1,62 @@
1
+ // ============================================================================
2
+ // Observer API
3
+ // ============================================================================
4
+ /**
5
+ * Registers a callback to be invoked when an event of the specified type is fired.
6
+ *
7
+ * @param world - The world instance containing observer state
8
+ * @param eventType - The event type to listen for
9
+ * @param callback - Function to invoke when the event fires
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * registerObserverCallback(world, "onAdd", (entity, componentId, value) => {
14
+ * console.log(`Component ${componentId} added to entity ${entity}`);
15
+ * });
16
+ * ```
17
+ */
18
+ export function registerObserverCallback(world, eventType, callback) {
19
+ world.observers[eventType].callbacks.push(callback);
20
+ }
21
+ /**
22
+ * Removes a previously registered callback for the specified event type.
23
+ *
24
+ * @param world - The world instance containing observer state
25
+ * @param eventType - The event type to stop listening for
26
+ * @param callback - The exact callback reference to remove
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const handler = (entity, componentId, value) => { ... };
31
+ * registerObserverCallback(world, "onAdd", handler);
32
+ * // Later:
33
+ * unregisterObserverCallback(world, "onAdd", handler);
34
+ * ```
35
+ */
36
+ export function unregisterObserverCallback(world, eventType, callback) {
37
+ const meta = world.observers[eventType];
38
+ const idx = meta.callbacks.indexOf(callback);
39
+ if (idx !== -1) {
40
+ meta.callbacks.splice(idx, 1);
41
+ }
42
+ }
43
+ /**
44
+ * Dispatches an event to all registered callbacks for the specified event type.
45
+ *
46
+ * @param world - The world instance containing observer state
47
+ * @param eventType - The event type to dispatch
48
+ * @param args - Arguments to pass to each callback (varies by event type)
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * fireObserverEvent(world, "onAdd", entity, componentId, componentValue);
53
+ * ```
54
+ */
55
+ export function fireObserverEvent(world, eventType, ...args) {
56
+ const meta = world.observers[eventType];
57
+ // Iterate in reverse so callbacks can safely unregister themselves during dispatch
58
+ for (let i = meta.callbacks.length - 1; i >= 0; i--) {
59
+ meta.callbacks[i](...args);
60
+ }
61
+ }
62
+ //# sourceMappingURL=observer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observer.js","sourceRoot":"","sources":["../src/observer.ts"],"names":[],"mappings":"AA+CA,+EAA+E;AAC/E,eAAe;AACf,+EAA+E;AAE/E;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,wBAAwB,CAAsB,KAAY,EAAE,SAAY,EAAE,QAAqB;IAC7G,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAY,EACZ,SAAY,EACZ,QAAqB;IAErB,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAsB,KAAY,EAAE,SAAY,EAAE,GAAG,IAAsB;IAC1G,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAExC,mFAAmF;IACnF,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACpD,IAAI,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -0,0 +1,198 @@
1
+ import type { EntityId } from "./encoding.js";
2
+ import type { FilterMeta } from "./filters.js";
3
+ import type { Observer } from "./observer.js";
4
+ import type { World } from "./world.js";
5
+ /**
6
+ * Query metadata for registry caching.
7
+ *
8
+ * Stores required and excluded components with reference to underlying filter.
9
+ */
10
+ export type QueryMeta = {
11
+ /**
12
+ * Required components.
13
+ */
14
+ include: EntityId[];
15
+ /**
16
+ * Excluded components.
17
+ */
18
+ exclude: EntityId[];
19
+ /**
20
+ * Direct reference to underlying filter.
21
+ */
22
+ filter: FilterMeta;
23
+ /**
24
+ * Observer callback for filter destruction.
25
+ */
26
+ onFilterDestroy: Observer<"filterDestroyed">;
27
+ /**
28
+ * Components with added() modifier.
29
+ */
30
+ added: EntityId[];
31
+ /**
32
+ * Components with changed() modifier.
33
+ */
34
+ changed: EntityId[];
35
+ /**
36
+ * Execution tick tracking for change detection.
37
+ */
38
+ lastTick: {
39
+ /**
40
+ * Tick when query last executed outside any system.
41
+ */
42
+ self: number;
43
+ /**
44
+ * Per-system execution ticks: systemId -> tick
45
+ */
46
+ bySystemId: Map<string, number>;
47
+ };
48
+ };
49
+ export type ModifierType = "not" | "added" | "changed";
50
+ export type NotModifier = {
51
+ type: "not";
52
+ componentId: EntityId;
53
+ };
54
+ export type AddedModifier = {
55
+ type: "added";
56
+ componentId: EntityId;
57
+ };
58
+ export type ChangedModifier = {
59
+ type: "changed";
60
+ componentId: EntityId;
61
+ };
62
+ export type QueryModifier = NotModifier | AddedModifier | ChangedModifier;
63
+ /**
64
+ * Create exclusion modifier for query.
65
+ *
66
+ * @param componentId - Component to exclude from query results
67
+ * @returns Not modifier
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * fetchEntities(world, Position, not(Dead))
72
+ * ```
73
+ */
74
+ export declare function not(componentId: EntityId): NotModifier;
75
+ /**
76
+ * Create added modifier for change detection.
77
+ *
78
+ * Matches entities where component was added since last query execution.
79
+ *
80
+ * @param componentId - Component to check for addition
81
+ * @returns Added modifier
82
+ *
83
+ * @example
84
+ * for (const entity of fetchEntities(world, added(Enemy))) { ... }
85
+ */
86
+ export declare function added(componentId: EntityId): AddedModifier;
87
+ /**
88
+ * Create changed modifier for change detection.
89
+ *
90
+ * Matches entities where component was modified or added since last query execution.
91
+ *
92
+ * @param componentId - Component to check for changes
93
+ * @returns Changed modifier
94
+ *
95
+ * @example
96
+ * for (const entity of fetchEntities(world, changed(Health))) { ... }
97
+ */
98
+ export declare function changed(componentId: EntityId): ChangedModifier;
99
+ /**
100
+ * Hash query terms to unique string ID for cache lookup.
101
+ *
102
+ * @param include - Component IDs that must be present
103
+ * @param exclude - Component IDs that must not be present
104
+ * @param added - Component IDs to check for recent addition
105
+ * @param changed - Component IDs to check for recent modification
106
+ * @returns Query ID in format "+include|-exclude|~+added|~>changed"
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const id = hashQuery([Position, Velocity], [Dead], [], []);
111
+ * ```
112
+ */
113
+ export declare function hashQuery(include: EntityId[], exclude: EntityId[], added: EntityId[], changed: EntityId[]): string;
114
+ /**
115
+ * Ensure query exists in registry, creating if necessary.
116
+ *
117
+ * @param world - World instance
118
+ * @param terms - Components and modifiers
119
+ * @returns Query metadata
120
+ * @throws {Error} If no included components (query must match something)
121
+ *
122
+ * @example
123
+ * const query = ensureQuery(world, Position, Velocity, not(Dead));
124
+ */
125
+ export declare function ensureQuery(world: World, ...terms: (EntityId | QueryModifier)[]): QueryMeta;
126
+ /**
127
+ * Fetch entities using pre-registered query metadata.
128
+ *
129
+ * Filters by change modifiers (added/changed) when present and updates
130
+ * lastTick after iteration for per-query/per-system change tracking.
131
+ *
132
+ * @param world - World instance
133
+ * @param queryMeta - Query metadata from ensureQuery()
134
+ * @returns Entity IDs in backward order (safe for deletion during iteration)
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const query = ensureQuery(world, Position, Velocity);
139
+ * for (const entity of fetchEntitiesWithQuery(world, query)) {
140
+ * // Process entity
141
+ * }
142
+ * ```
143
+ */
144
+ export declare function fetchEntitiesWithQuery(world: World, queryMeta: QueryMeta): IterableIterator<EntityId>;
145
+ /**
146
+ * Destroy query and clean up associated resources.
147
+ *
148
+ * Unregisters observer callbacks and removes from query registry.
149
+ *
150
+ * @param world - World instance
151
+ * @param queryMeta - Query metadata to destroy
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const query = ensureQuery(world, Position);
156
+ * // ... use query ...
157
+ * destroyQuery(world, query);
158
+ * ```
159
+ */
160
+ export declare function destroyQuery(world: World, queryMeta: QueryMeta): void;
161
+ /**
162
+ * Fetch entities matching components and modifiers.
163
+ *
164
+ * Iterates backward for safe entity destruction during iteration.
165
+ * Creates/reuses cached query internally.
166
+ *
167
+ * @param world - World instance
168
+ * @param terms - Component IDs and query modifiers (not, added, changed)
169
+ * @returns Entity IDs in deletion-safe order
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * for (const entity of fetchEntities(world, Position, Velocity, not(Dead))) {
174
+ * const pos = get(world, entity, Position);
175
+ * // Entity can be safely destroyed here
176
+ * }
177
+ * ```
178
+ */
179
+ export declare function fetchEntities(world: World, ...terms: (EntityId | QueryModifier)[]): IterableIterator<EntityId>;
180
+ /**
181
+ * Fetch first entity matching components and modifiers.
182
+ *
183
+ * Useful for singleton patterns or when only one match is expected.
184
+ *
185
+ * @param world - World instance
186
+ * @param terms - Component IDs and query modifiers (not, added, changed)
187
+ * @returns First matching entity ID, or undefined if no matches
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const player = fetchFirstEntity(world, Player, not(Dead));
192
+ * if (player !== undefined) {
193
+ * const health = get(world, player, Health);
194
+ * }
195
+ * ```
196
+ */
197
+ export declare function fetchFirstEntity(world: World, ...terms: (EntityId | QueryModifier)[]): EntityId | undefined;
198
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMxC;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB;;OAEG;IACH,OAAO,EAAE,QAAQ,EAAE,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,QAAQ,EAAE,CAAC;IAEpB;;OAEG;IACH,MAAM,EAAE,UAAU,CAAC;IAEnB;;OAEG;IACH,eAAe,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAE7C;;OAEG;IACH,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB;;OAEG;IACH,OAAO,EAAE,QAAQ,EAAE,CAAC;IAEpB;;OAEG;IACH,QAAQ,EAAE;QACR;;WAEG;QACH,IAAI,EAAE,MAAM,CAAC;QACb;;WAEG;QACH,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,CAAC;CACH,CAAC;AAMF,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;AACvD,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,WAAW,EAAE,QAAQ,CAAA;CAAE,CAAC;AACjE,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,QAAQ,CAAA;CAAE,CAAC;AACrE,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,WAAW,EAAE,QAAQ,CAAA;CAAE,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,aAAa,GAAG,eAAe,CAAC;AAE1E;;;;;;;;;;GAUG;AACH,wBAAgB,GAAG,CAAC,WAAW,EAAE,QAAQ,GAAG,WAAW,CAEtD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,WAAW,EAAE,QAAQ,GAAG,aAAa,CAE1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,WAAW,EAAE,QAAQ,GAAG,eAAe,CAE9D;AAaD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAKlH;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,QAAQ,GAAG,aAAa,CAAC,EAAE,GAAG,SAAS,CAuE3F;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAiB,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAyEtG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CAMrE;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAiB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,QAAQ,GAAG,aAAa,CAAC,EAAE,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAI/G;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,QAAQ,GAAG,aAAa,CAAC,EAAE,GAAG,QAAQ,GAAG,SAAS,CAQ3G"}
package/dist/query.js ADDED
@@ -0,0 +1,299 @@
1
+ import { ensureFilter, iterateFilterEntities } from "./filters.js";
2
+ import { registerObserverCallback, unregisterObserverCallback } from "./observer.js";
3
+ /**
4
+ * Create exclusion modifier for query.
5
+ *
6
+ * @param componentId - Component to exclude from query results
7
+ * @returns Not modifier
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * fetchEntities(world, Position, not(Dead))
12
+ * ```
13
+ */
14
+ export function not(componentId) {
15
+ return { type: "not", componentId };
16
+ }
17
+ /**
18
+ * Create added modifier for change detection.
19
+ *
20
+ * Matches entities where component was added since last query execution.
21
+ *
22
+ * @param componentId - Component to check for addition
23
+ * @returns Added modifier
24
+ *
25
+ * @example
26
+ * for (const entity of fetchEntities(world, added(Enemy))) { ... }
27
+ */
28
+ export function added(componentId) {
29
+ return { type: "added", componentId };
30
+ }
31
+ /**
32
+ * Create changed modifier for change detection.
33
+ *
34
+ * Matches entities where component was modified or added since last query execution.
35
+ *
36
+ * @param componentId - Component to check for changes
37
+ * @returns Changed modifier
38
+ *
39
+ * @example
40
+ * for (const entity of fetchEntities(world, changed(Health))) { ... }
41
+ */
42
+ export function changed(componentId) {
43
+ return { type: "changed", componentId };
44
+ }
45
+ /**
46
+ * Check if argument is a query modifier (not, added, changed) vs plain component ID.
47
+ */
48
+ function isModifier(arg) {
49
+ return typeof arg === "object" && arg !== null && "type" in arg && "componentId" in arg;
50
+ }
51
+ // ============================================================================
52
+ // Query Hashing
53
+ // ============================================================================
54
+ /**
55
+ * Hash query terms to unique string ID for cache lookup.
56
+ *
57
+ * @param include - Component IDs that must be present
58
+ * @param exclude - Component IDs that must not be present
59
+ * @param added - Component IDs to check for recent addition
60
+ * @param changed - Component IDs to check for recent modification
61
+ * @returns Query ID in format "+include|-exclude|~+added|~>changed"
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const id = hashQuery([Position, Velocity], [Dead], [], []);
66
+ * ```
67
+ */
68
+ export function hashQuery(include, exclude, added, changed) {
69
+ // Sort to ensure consistent hashing regardless of term order
70
+ const join = (arr) => arr.toSorted((a, b) => a - b).join(":");
71
+ return `+${join(include)}|-${join(exclude)}|~+${join(added)}|~>${join(changed)}`;
72
+ }
73
+ // ============================================================================
74
+ // Query Registry Operations
75
+ // ============================================================================
76
+ /**
77
+ * Ensure query exists in registry, creating if necessary.
78
+ *
79
+ * @param world - World instance
80
+ * @param terms - Components and modifiers
81
+ * @returns Query metadata
82
+ * @throws {Error} If no included components (query must match something)
83
+ *
84
+ * @example
85
+ * const query = ensureQuery(world, Position, Velocity, not(Dead));
86
+ */
87
+ export function ensureQuery(world, ...terms) {
88
+ const include = [];
89
+ const exclude = [];
90
+ const added = [];
91
+ const changed = [];
92
+ // Separate terms into categories based on modifier type
93
+ for (const term of terms) {
94
+ if (isModifier(term)) {
95
+ switch (term.type) {
96
+ case "not":
97
+ exclude.push(term.componentId);
98
+ break;
99
+ case "added":
100
+ added.push(term.componentId);
101
+ break;
102
+ case "changed":
103
+ changed.push(term.componentId);
104
+ break;
105
+ }
106
+ }
107
+ else {
108
+ include.push(term);
109
+ }
110
+ }
111
+ // Filter must include added/changed components since they must be present on entity
112
+ const filterInclude = include.concat(added, changed);
113
+ if (filterInclude.length === 0) {
114
+ throw new Error("Query must include at least one component");
115
+ }
116
+ const queryId = hashQuery(include, exclude, added, changed);
117
+ let queryMeta = world.queries.byId.get(queryId);
118
+ if (!queryMeta) {
119
+ const filterMeta = ensureFilter(world, { include: filterInclude, exclude });
120
+ queryMeta = {
121
+ include,
122
+ exclude,
123
+ added,
124
+ changed,
125
+ filter: filterMeta,
126
+ lastTick: {
127
+ self: 0,
128
+ bySystemId: new Map(),
129
+ },
130
+ // Callback to clean up query when its underlying filter is destroyed
131
+ onFilterDestroy: (destroyedFilter) => {
132
+ if (destroyedFilter !== filterMeta) {
133
+ return;
134
+ }
135
+ // Self-cleanup: unregister callback and remove from registry
136
+ unregisterObserverCallback(world, "filterDestroyed", queryMeta.onFilterDestroy);
137
+ world.queries.byId.delete(queryId);
138
+ },
139
+ };
140
+ world.queries.byId.set(queryId, queryMeta);
141
+ // Register for filter destruction events to enable automatic cleanup
142
+ registerObserverCallback(world, "filterDestroyed", queryMeta.onFilterDestroy);
143
+ }
144
+ return queryMeta;
145
+ }
146
+ /**
147
+ * Fetch entities using pre-registered query metadata.
148
+ *
149
+ * Filters by change modifiers (added/changed) when present and updates
150
+ * lastTick after iteration for per-query/per-system change tracking.
151
+ *
152
+ * @param world - World instance
153
+ * @param queryMeta - Query metadata from ensureQuery()
154
+ * @returns Entity IDs in backward order (safe for deletion during iteration)
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * const query = ensureQuery(world, Position, Velocity);
159
+ * for (const entity of fetchEntitiesWithQuery(world, query)) {
160
+ * // Process entity
161
+ * }
162
+ * ```
163
+ */
164
+ export function* fetchEntitiesWithQuery(world, queryMeta) {
165
+ const hasChangeModifiers = queryMeta.added.length > 0 || queryMeta.changed.length > 0;
166
+ // Fast path: no change modifiers
167
+ if (!hasChangeModifiers) {
168
+ yield* iterateFilterEntities(queryMeta.filter);
169
+ return;
170
+ }
171
+ // Slow path: filter by change detection using archetype-local tick arrays.
172
+ // Each component tracks when it was added/changed per-entity via tick timestamps.
173
+ const { systemId, tick } = world.execution;
174
+ // Get lastTick for this execution context (global or per-system)
175
+ const lastTick = systemId === null ? queryMeta.lastTick.self : (queryMeta.lastTick.bySystemId.get(systemId) ?? 0);
176
+ const archetypes = queryMeta.filter.archetypes;
177
+ // Pre-allocated arrays reused across archetypes to avoid allocation in hot loop
178
+ const addedTickArrays = [];
179
+ const changedTickArrays = [];
180
+ // Use try/finally to ensure lastTick updates even on early exit (break/return/throw).
181
+ try {
182
+ for (let a = 0; a < archetypes.length; a++) {
183
+ const archetype = archetypes[a];
184
+ const entities = archetype.entities;
185
+ // Pre-fetch tick arrays for this archetype (one Map lookup per component per archetype)
186
+ addedTickArrays.length = 0;
187
+ for (let j = 0; j < queryMeta.added.length; j++) {
188
+ const ticks = archetype.ticks.get(queryMeta.added[j]);
189
+ if (ticks)
190
+ addedTickArrays.push(ticks.added);
191
+ }
192
+ changedTickArrays.length = 0;
193
+ for (let j = 0; j < queryMeta.changed.length; j++) {
194
+ const ticks = archetype.ticks.get(queryMeta.changed[j]);
195
+ if (ticks)
196
+ changedTickArrays.push(ticks.changed);
197
+ }
198
+ // Iterate entities backward (deletion-safe)
199
+ entityLoop: for (let i = entities.length - 1; i >= 0; i--) {
200
+ const entityId = entities[i];
201
+ // Check added modifiers: skip if component wasn't added in (lastTick, tick] range
202
+ for (let j = 0; j < addedTickArrays.length; j++) {
203
+ const addedTick = addedTickArrays[j][i];
204
+ if (addedTick <= lastTick || addedTick > tick) {
205
+ continue entityLoop;
206
+ }
207
+ }
208
+ // Check changed modifiers: skip if component wasn't modified in (lastTick, tick] range
209
+ for (let j = 0; j < changedTickArrays.length; j++) {
210
+ const changedTick = changedTickArrays[j][i];
211
+ if (changedTick <= lastTick || changedTick > tick) {
212
+ continue entityLoop;
213
+ }
214
+ }
215
+ yield entityId;
216
+ }
217
+ }
218
+ }
219
+ finally {
220
+ // Update lastTick after iteration completes (or on break/return/throw).
221
+ // This ensures subsequent iterations only see changes since this execution.
222
+ if (systemId === null) {
223
+ queryMeta.lastTick.self = tick;
224
+ }
225
+ else {
226
+ queryMeta.lastTick.bySystemId.set(systemId, tick);
227
+ }
228
+ }
229
+ }
230
+ /**
231
+ * Destroy query and clean up associated resources.
232
+ *
233
+ * Unregisters observer callbacks and removes from query registry.
234
+ *
235
+ * @param world - World instance
236
+ * @param queryMeta - Query metadata to destroy
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * const query = ensureQuery(world, Position);
241
+ * // ... use query ...
242
+ * destroyQuery(world, query);
243
+ * ```
244
+ */
245
+ export function destroyQuery(world, queryMeta) {
246
+ const queryId = hashQuery(queryMeta.include, queryMeta.exclude, queryMeta.added, queryMeta.changed);
247
+ unregisterObserverCallback(world, "filterDestroyed", queryMeta.onFilterDestroy);
248
+ world.queries.byId.delete(queryId);
249
+ }
250
+ // ============================================================================
251
+ // Query Iteration
252
+ // ============================================================================
253
+ /**
254
+ * Fetch entities matching components and modifiers.
255
+ *
256
+ * Iterates backward for safe entity destruction during iteration.
257
+ * Creates/reuses cached query internally.
258
+ *
259
+ * @param world - World instance
260
+ * @param terms - Component IDs and query modifiers (not, added, changed)
261
+ * @returns Entity IDs in deletion-safe order
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * for (const entity of fetchEntities(world, Position, Velocity, not(Dead))) {
266
+ * const pos = get(world, entity, Position);
267
+ * // Entity can be safely destroyed here
268
+ * }
269
+ * ```
270
+ */
271
+ export function* fetchEntities(world, ...terms) {
272
+ const queryMeta = ensureQuery(world, ...terms);
273
+ yield* fetchEntitiesWithQuery(world, queryMeta);
274
+ }
275
+ /**
276
+ * Fetch first entity matching components and modifiers.
277
+ *
278
+ * Useful for singleton patterns or when only one match is expected.
279
+ *
280
+ * @param world - World instance
281
+ * @param terms - Component IDs and query modifiers (not, added, changed)
282
+ * @returns First matching entity ID, or undefined if no matches
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * const player = fetchFirstEntity(world, Player, not(Dead));
287
+ * if (player !== undefined) {
288
+ * const health = get(world, player, Health);
289
+ * }
290
+ * ```
291
+ */
292
+ export function fetchFirstEntity(world, ...terms) {
293
+ const queryMeta = ensureQuery(world, ...terms);
294
+ for (const entityId of fetchEntitiesWithQuery(world, queryMeta)) {
295
+ return entityId;
296
+ }
297
+ return undefined;
298
+ }
299
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}