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,237 @@
1
+ import type { Schema, SchemaRecord } from "./schema.js";
2
+ import type { World } from "./world.js";
3
+ /**
4
+ * Event ID brand for nominal typing.
5
+ */
6
+ declare const EVENT_BRAND: unique symbol;
7
+ /**
8
+ * Event schema brand for carrying schema type in Event.
9
+ */
10
+ declare const EVENT_SCHEMA_BRAND: unique symbol;
11
+ /**
12
+ * Event schema type.
13
+ *
14
+ * Maps field names to their schema definitions (same as component schema).
15
+ */
16
+ export type EventSchema = SchemaRecord;
17
+ /**
18
+ * Event data type inference.
19
+ *
20
+ * - Empty schema {} -> undefined (tag event)
21
+ * - Non-empty schema -> resolved data object
22
+ */
23
+ export type EventData<T extends EventSchema> = keyof T extends never ? undefined : {
24
+ [K in keyof T]: T[K] extends Schema<infer U> ? U : never;
25
+ };
26
+ /**
27
+ * Event ID (branded type).
28
+ *
29
+ * Nominal type for events defined via defineEvent().
30
+ */
31
+ export type EventId<S extends EventSchema = EventSchema> = number & {
32
+ [EVENT_BRAND]: true;
33
+ [EVENT_SCHEMA_BRAND]: S;
34
+ };
35
+ /**
36
+ * Event definition.
37
+ *
38
+ * Global event definition with schema for type-safe event data.
39
+ */
40
+ export type Event<S extends EventSchema = EventSchema> = {
41
+ /**
42
+ * Unique event ID.
43
+ */
44
+ readonly id: EventId<S>;
45
+ /**
46
+ * Event name (user-defined).
47
+ */
48
+ readonly name: string;
49
+ /**
50
+ * Field schemas for event data (empty for tag events).
51
+ */
52
+ readonly schema: S;
53
+ };
54
+ /**
55
+ * Internal event entry with tick.
56
+ *
57
+ * Stores event data along with the tick it was emitted at.
58
+ */
59
+ export type EventEntry<T extends EventSchema = EventSchema> = {
60
+ /**
61
+ * Event data (undefined for tag events).
62
+ */
63
+ data: EventData<T>;
64
+ /**
65
+ * Tick when event was emitted.
66
+ */
67
+ tick: number;
68
+ };
69
+ /**
70
+ * Per-world event queue metadata.
71
+ */
72
+ export type EventQueueMeta<T extends EventSchema = EventSchema> = {
73
+ /**
74
+ * Event definition reference.
75
+ */
76
+ event: Event<T>;
77
+ /**
78
+ * Queue of event entries.
79
+ */
80
+ events: EventEntry<T>[];
81
+ /**
82
+ * Execution tick tracking for event consumption.
83
+ */
84
+ lastTick: {
85
+ /**
86
+ * Tick when events last consumed outside any system.
87
+ */
88
+ self: number;
89
+ /**
90
+ * Per-system consumption ticks: systemId -> tick
91
+ */
92
+ bySystemId: Map<string, number>;
93
+ };
94
+ };
95
+ /**
96
+ * Number of ticks before events expire.
97
+ *
98
+ * Events persist for this many ticks to ensure systems can read regardless
99
+ * of execution order.
100
+ */
101
+ export declare const EVENT_EXPIRY_TICKS = 2;
102
+ /**
103
+ * Define event type.
104
+ *
105
+ * Allocates unique event ID with optional schema for type-safe event data.
106
+ * Tag events (no schema) use void for data type - emit() requires no data argument.
107
+ *
108
+ * @param name - Event name for debugging
109
+ * @param schema - Optional field schema record (omit for tag events)
110
+ * @returns Event definition
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * // Tag event (no data)
115
+ * const GameStarted = defineEvent("GameStarted");
116
+ * emit(world, GameStarted); // No data argument
117
+ *
118
+ * // Data event
119
+ * const DamageDealt = defineEvent("DamageDealt", {
120
+ * target: Type.u32(),
121
+ * amount: Type.f32(),
122
+ * });
123
+ * emit(world, DamageDealt, { target: enemy, amount: 25 });
124
+ * ```
125
+ */
126
+ export declare function defineEvent<S extends EventSchema = Record<never, never>>(name: string, schema?: S): Event<S>;
127
+ /**
128
+ * Ensure event queue exists for given event in world.
129
+ *
130
+ * Creates queue lazily on first access (emit or fetch).
131
+ *
132
+ * @param world - World instance
133
+ * @param event - Event definition
134
+ * @returns Event queue metadata
135
+ */
136
+ export declare function ensureEventQueue<S extends EventSchema>(world: World, event: Event<S>): EventQueueMeta<S>;
137
+ /**
138
+ * Emit event to world.
139
+ *
140
+ * Tag events (empty schema) require no data argument.
141
+ * Data events require data matching the schema.
142
+ *
143
+ * @param world - World instance
144
+ * @param event - Event definition
145
+ * @param args - Event data (only for data events)
146
+ */
147
+ export declare function emitEvent<S extends EventSchema>(world: World, event: Event<S>, ...args: keyof S extends never ? [] : [data: EventData<S>]): void;
148
+ /**
149
+ * Fetch events emitted since last call.
150
+ *
151
+ * Per-system isolated: each system has independent tracking of which events
152
+ * it has consumed. Multiple systems can consume the same events independently.
153
+ *
154
+ * @param world - World instance
155
+ * @param event - Event definition
156
+ * @returns Generator yielding event data
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * for (const event of fetchEvents(world, DamageDealt)) {
161
+ * applyDamage(event.target, event.amount);
162
+ * }
163
+ * ```
164
+ */
165
+ export declare function fetchEvents<S extends EventSchema>(world: World, event: Event<S>): Generator<EventData<S>>;
166
+ /**
167
+ * Check if there are unread events for current context.
168
+ *
169
+ * Does not mark events as read or trigger cleanup.
170
+ *
171
+ * @param world - World instance
172
+ * @param event - Event definition
173
+ * @returns True if unread events exist
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * if (hasEvents(world, DamageDealt)) {
178
+ * // Process damage events
179
+ * }
180
+ * ```
181
+ */
182
+ export declare function hasEvents<S extends EventSchema>(world: World, event: Event<S>): boolean;
183
+ /**
184
+ * Count unread events for current context.
185
+ *
186
+ * Does not mark events as read or trigger cleanup.
187
+ *
188
+ * @param world - World instance
189
+ * @param event - Event definition
190
+ * @returns Number of unread events
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const damageCount = countEvents(world, DamageDealt);
195
+ * console.log(`${damageCount} damage events this tick`);
196
+ * ```
197
+ */
198
+ export declare function countEvents<S extends EventSchema>(world: World, event: Event<S>): number;
199
+ /**
200
+ * Fetch only the most recent event, marking all as read.
201
+ *
202
+ * Useful when only the latest state matters (e.g., input, config changes).
203
+ *
204
+ * @param world - World instance
205
+ * @param event - Event definition
206
+ * @returns Most recent event data, or undefined if no unread events
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * // Only care about the latest input state
211
+ * const input = fetchLastEvent(world, InputChanged);
212
+ * if (input) {
213
+ * updatePlayerDirection(input.direction);
214
+ * }
215
+ * ```
216
+ */
217
+ export declare function fetchLastEvent<S extends EventSchema>(world: World, event: Event<S>): EventData<S> | undefined;
218
+ /**
219
+ * Clear events (mark as read without processing).
220
+ *
221
+ * Useful when a system needs to skip events under certain conditions.
222
+ *
223
+ * @param world - World instance
224
+ * @param event - Event definition
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * if (isPaused) {
229
+ * // Skip damage events while paused
230
+ * clearEvents(world, DamageDealt);
231
+ * return;
232
+ * }
233
+ * ```
234
+ */
235
+ export declare function clearEvents<S extends EventSchema>(world: World, event: Event<S>): void;
236
+ export {};
237
+ //# sourceMappingURL=event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"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"}
package/dist/event.js ADDED
@@ -0,0 +1,293 @@
1
+ // ============================================================================
2
+ // Constants
3
+ // ============================================================================
4
+ /**
5
+ * Number of ticks before events expire.
6
+ *
7
+ * Events persist for this many ticks to ensure systems can read regardless
8
+ * of execution order.
9
+ */
10
+ export const EVENT_EXPIRY_TICKS = 2;
11
+ /**
12
+ * Global event registry singleton.
13
+ */
14
+ const EVENT_REGISTRY = {
15
+ byId: new Map(),
16
+ nextId: 0,
17
+ };
18
+ // ============================================================================
19
+ // Event Definition
20
+ // ============================================================================
21
+ /**
22
+ * Define event type.
23
+ *
24
+ * Allocates unique event ID with optional schema for type-safe event data.
25
+ * Tag events (no schema) use void for data type - emit() requires no data argument.
26
+ *
27
+ * @param name - Event name for debugging
28
+ * @param schema - Optional field schema record (omit for tag events)
29
+ * @returns Event definition
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Tag event (no data)
34
+ * const GameStarted = defineEvent("GameStarted");
35
+ * emit(world, GameStarted); // No data argument
36
+ *
37
+ * // Data event
38
+ * const DamageDealt = defineEvent("DamageDealt", {
39
+ * target: Type.u32(),
40
+ * amount: Type.f32(),
41
+ * });
42
+ * emit(world, DamageDealt, { target: enemy, amount: 25 });
43
+ * ```
44
+ */
45
+ export function defineEvent(name, schema) {
46
+ const id = EVENT_REGISTRY.nextId++;
47
+ const event = {
48
+ id,
49
+ name,
50
+ schema: schema ?? {},
51
+ };
52
+ EVENT_REGISTRY.byId.set(id, event);
53
+ return event;
54
+ }
55
+ // ============================================================================
56
+ // Per-World Event Queue Management
57
+ // ============================================================================
58
+ /**
59
+ * Ensure event queue exists for given event in world.
60
+ *
61
+ * Creates queue lazily on first access (emit or fetch).
62
+ *
63
+ * @param world - World instance
64
+ * @param event - Event definition
65
+ * @returns Event queue metadata
66
+ */
67
+ export function ensureEventQueue(world, event) {
68
+ let queue = world.events.byId.get(event.id);
69
+ if (!queue) {
70
+ queue = {
71
+ event: event,
72
+ events: [],
73
+ lastTick: {
74
+ self: 0,
75
+ bySystemId: new Map(),
76
+ },
77
+ };
78
+ world.events.byId.set(event.id, queue);
79
+ }
80
+ return queue;
81
+ }
82
+ // ============================================================================
83
+ // Event Emission
84
+ // ============================================================================
85
+ /**
86
+ * Emit event to world.
87
+ *
88
+ * Tag events (empty schema) require no data argument.
89
+ * Data events require data matching the schema.
90
+ *
91
+ * @param world - World instance
92
+ * @param event - Event definition
93
+ * @param args - Event data (only for data events)
94
+ */
95
+ export function emitEvent(world, event, ...args) {
96
+ const queue = ensureEventQueue(world, event);
97
+ const data = args[0];
98
+ const tick = world.execution.tick;
99
+ queue.events.push({ data, tick });
100
+ }
101
+ // ============================================================================
102
+ // Event Reading
103
+ // ============================================================================
104
+ /**
105
+ * Update lastTick for current execution context.
106
+ *
107
+ * Internal helper shared by fetchEvents, fetchLastEvent, and clearEvents.
108
+ *
109
+ * @param world - World instance
110
+ * @param queue - Event queue metadata
111
+ */
112
+ function markEventsRead(world, queue) {
113
+ const { systemId, tick } = world.execution;
114
+ if (systemId === null) {
115
+ queue.lastTick.self = tick;
116
+ }
117
+ else {
118
+ queue.lastTick.bySystemId.set(systemId, tick);
119
+ }
120
+ }
121
+ /**
122
+ * Remove expired events from queue.
123
+ *
124
+ * Events older than EVENT_EXPIRY_TICKS are removed to prevent unbounded growth.
125
+ *
126
+ * @param world - World instance
127
+ * @param queue - Event queue metadata
128
+ */
129
+ function cleanupExpiredEvents(world, queue) {
130
+ const expiryTick = world.execution.tick - EVENT_EXPIRY_TICKS;
131
+ // Find first non-expired index
132
+ let firstValidIndex = 0;
133
+ while (firstValidIndex < queue.events.length && queue.events[firstValidIndex].tick <= expiryTick) {
134
+ firstValidIndex++;
135
+ }
136
+ // Remove expired events from front
137
+ if (firstValidIndex > 0) {
138
+ queue.events.splice(0, firstValidIndex);
139
+ }
140
+ }
141
+ /**
142
+ * Fetch events emitted since last call.
143
+ *
144
+ * Per-system isolated: each system has independent tracking of which events
145
+ * it has consumed. Multiple systems can consume the same events independently.
146
+ *
147
+ * @param world - World instance
148
+ * @param event - Event definition
149
+ * @returns Generator yielding event data
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * for (const event of fetchEvents(world, DamageDealt)) {
154
+ * applyDamage(event.target, event.amount);
155
+ * }
156
+ * ```
157
+ */
158
+ export function* fetchEvents(world, event) {
159
+ const queue = ensureEventQueue(world, event);
160
+ const { systemId, tick } = world.execution;
161
+ // Get lastTick for this execution context
162
+ const lastTick = systemId === null ? queue.lastTick.self : (queue.lastTick.bySystemId.get(systemId) ?? 0);
163
+ try {
164
+ for (let i = 0; i < queue.events.length; i++) {
165
+ const entry = queue.events[i];
166
+ // Event must have been emitted AFTER lastTick but AT or BEFORE current tick
167
+ if (entry.tick > lastTick && entry.tick <= tick) {
168
+ yield entry.data;
169
+ }
170
+ }
171
+ }
172
+ finally {
173
+ markEventsRead(world, queue);
174
+ cleanupExpiredEvents(world, queue);
175
+ }
176
+ }
177
+ // ============================================================================
178
+ // Utility Functions
179
+ // ============================================================================
180
+ /**
181
+ * Check if there are unread events for current context.
182
+ *
183
+ * Does not mark events as read or trigger cleanup.
184
+ *
185
+ * @param world - World instance
186
+ * @param event - Event definition
187
+ * @returns True if unread events exist
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * if (hasEvents(world, DamageDealt)) {
192
+ * // Process damage events
193
+ * }
194
+ * ```
195
+ */
196
+ export function hasEvents(world, event) {
197
+ const queue = ensureEventQueue(world, event);
198
+ const { systemId, tick } = world.execution;
199
+ const lastTick = systemId === null ? queue.lastTick.self : (queue.lastTick.bySystemId.get(systemId) ?? 0);
200
+ for (let i = 0; i < queue.events.length; i++) {
201
+ const entry = queue.events[i];
202
+ if (entry.tick > lastTick && entry.tick <= tick) {
203
+ return true;
204
+ }
205
+ }
206
+ return false;
207
+ }
208
+ /**
209
+ * Count unread events for current context.
210
+ *
211
+ * Does not mark events as read or trigger cleanup.
212
+ *
213
+ * @param world - World instance
214
+ * @param event - Event definition
215
+ * @returns Number of unread events
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const damageCount = countEvents(world, DamageDealt);
220
+ * console.log(`${damageCount} damage events this tick`);
221
+ * ```
222
+ */
223
+ export function countEvents(world, event) {
224
+ const queue = ensureEventQueue(world, event);
225
+ const { systemId, tick } = world.execution;
226
+ const lastTick = systemId === null ? queue.lastTick.self : (queue.lastTick.bySystemId.get(systemId) ?? 0);
227
+ let count = 0;
228
+ for (let i = 0; i < queue.events.length; i++) {
229
+ const entry = queue.events[i];
230
+ if (entry.tick > lastTick && entry.tick <= tick) {
231
+ count++;
232
+ }
233
+ }
234
+ return count;
235
+ }
236
+ /**
237
+ * Fetch only the most recent event, marking all as read.
238
+ *
239
+ * Useful when only the latest state matters (e.g., input, config changes).
240
+ *
241
+ * @param world - World instance
242
+ * @param event - Event definition
243
+ * @returns Most recent event data, or undefined if no unread events
244
+ *
245
+ * @example
246
+ * ```typescript
247
+ * // Only care about the latest input state
248
+ * const input = fetchLastEvent(world, InputChanged);
249
+ * if (input) {
250
+ * updatePlayerDirection(input.direction);
251
+ * }
252
+ * ```
253
+ */
254
+ export function fetchLastEvent(world, event) {
255
+ const queue = ensureEventQueue(world, event);
256
+ const { systemId, tick } = world.execution;
257
+ const lastTick = systemId === null ? queue.lastTick.self : (queue.lastTick.bySystemId.get(systemId) ?? 0);
258
+ // Find last matching event (iterate backwards, break early)
259
+ let result;
260
+ for (let i = queue.events.length - 1; i >= 0; i--) {
261
+ const entry = queue.events[i];
262
+ if (entry.tick > lastTick && entry.tick <= tick) {
263
+ result = entry.data;
264
+ break;
265
+ }
266
+ }
267
+ markEventsRead(world, queue);
268
+ cleanupExpiredEvents(world, queue);
269
+ return result;
270
+ }
271
+ /**
272
+ * Clear events (mark as read without processing).
273
+ *
274
+ * Useful when a system needs to skip events under certain conditions.
275
+ *
276
+ * @param world - World instance
277
+ * @param event - Event definition
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * if (isPaused) {
282
+ * // Skip damage events while paused
283
+ * clearEvents(world, DamageDealt);
284
+ * return;
285
+ * }
286
+ * ```
287
+ */
288
+ export function clearEvents(world, event) {
289
+ const queue = ensureEventQueue(world, event);
290
+ markEventsRead(world, queue);
291
+ cleanupExpiredEvents(world, queue);
292
+ }
293
+ //# sourceMappingURL=event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event.js","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import type { Archetype } from "./archetype.js";
2
+ import type { EntityId } from "./encoding.js";
3
+ import type { Observer } from "./observer.js";
4
+ import type { World } from "./world.js";
5
+ /**
6
+ * Filter terms for archetype matching.
7
+ *
8
+ * Specifies inclusion and exclusion constraints for archetype selection.
9
+ */
10
+ export type FilterTerms = {
11
+ /**
12
+ * Required component IDs (all must be present).
13
+ */
14
+ include: EntityId[];
15
+ /**
16
+ * Excluded component IDs (none must be present).
17
+ */
18
+ exclude: EntityId[];
19
+ };
20
+ /**
21
+ * Filter metadata for registry caching.
22
+ *
23
+ * Stores filter terms, matched archetypes, and observer callbacks.
24
+ */
25
+ export type FilterMeta = {
26
+ /**
27
+ * Filter terms (include/exclude constraints).
28
+ */
29
+ terms: FilterTerms;
30
+ /**
31
+ * Matched archetypes (cached result of findMatchingArchetypes).
32
+ */
33
+ archetypes: Archetype[];
34
+ /**
35
+ * Observer callback for archetype creation.
36
+ */
37
+ onArchetypeCreate: Observer<"archetypeCreated">;
38
+ /**
39
+ * Observer callback for archetype destruction.
40
+ */
41
+ onArchetypeDelete: Observer<"archetypeDestroyed">;
42
+ };
43
+ /**
44
+ * Generates a unique hash string for filter terms.
45
+ *
46
+ * @param terms - Filter terms containing include/exclude type arrays
47
+ * @returns Deterministic hash string (e.g., "+1:5:12|-3:7")
48
+ *
49
+ * @example
50
+ * const hash = hashFilterTerms({ include: [5, 1, 12], exclude: [7, 3] });
51
+ * // Returns "+1:5:12|-3:7" (sorted for consistency)
52
+ */
53
+ export declare function hashFilterTerms(terms: FilterTerms): string;
54
+ /**
55
+ * Tests whether an archetype satisfies the given filter terms.
56
+ *
57
+ * @param archetype - Archetype to test against filter
58
+ * @param terms - Filter terms with include/exclude type constraints
59
+ * @returns True if archetype contains ALL included types and NONE of excluded types
60
+ *
61
+ * @example
62
+ * const matches = matchesFilterTerms(archetype, {
63
+ * include: [PositionType, VelocityType],
64
+ * exclude: [DisabledType]
65
+ * });
66
+ */
67
+ export declare function matchesFilterTerms(archetype: Archetype, terms: FilterTerms): boolean;
68
+ /**
69
+ * Finds all archetypes matching filter terms using rarest-type optimization.
70
+ *
71
+ * Uses the "rarest type first" strategy: starts with the type that appears in
72
+ * the fewest archetypes, then filters that smaller set. This minimizes the
73
+ * number of archetypes we need to check.
74
+ *
75
+ * @param world - World instance containing archetype registry
76
+ * @param terms - Filter terms with include/exclude type constraints
77
+ * @returns Array of archetypes that match all filter criteria
78
+ *
79
+ * @example
80
+ * const archetypes = findMatchingArchetypes(world, {
81
+ * include: [PositionType, VelocityType],
82
+ * exclude: []
83
+ * });
84
+ */
85
+ export declare function findMatchingArchetypes(world: World, terms: FilterTerms): Archetype[];
86
+ /**
87
+ * Gets or creates a filter with observer-based cache invalidation.
88
+ *
89
+ * Filters are cached by their terms hash. When created, observers are registered
90
+ * to automatically update the cached archetype list as archetypes are created
91
+ * or destroyed.
92
+ *
93
+ * @param world - World instance containing filter registry
94
+ * @param terms - Filter terms defining which archetypes to match
95
+ * @returns FilterMeta with cached matching archetypes
96
+ *
97
+ * @example
98
+ * const filter = ensureFilter(world, {
99
+ * include: [PositionType, VelocityType],
100
+ * exclude: [DisabledType]
101
+ * });
102
+ * // filter.archetypes contains all matching archetypes
103
+ */
104
+ export declare function ensureFilter(world: World, terms: FilterTerms): FilterMeta;
105
+ /**
106
+ * Iterates all entities matching a filter in reverse order.
107
+ *
108
+ * Reverse iteration allows safe entity deletion during iteration without
109
+ * skipping entities or invalidating indices.
110
+ *
111
+ * @param filter - Filter metadata containing cached matching archetypes
112
+ * @returns Generator yielding entity IDs from all matching archetypes
113
+ *
114
+ * @example
115
+ * for (const entity of iterateFilterEntities(filter)) {
116
+ * // Safe to delete entity here due to reverse iteration
117
+ * destroyEntity(world, entity);
118
+ * }
119
+ */
120
+ export declare function iterateFilterEntities(filter: FilterMeta): IterableIterator<EntityId>;
121
+ //# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMxC;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB;;OAEG;IACH,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB;;OAEG;IACH,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB;;OAEG;IACH,KAAK,EAAE,WAAW,CAAC;IACnB;;OAEG;IACH,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB;;OAEG;IACH,iBAAiB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAChD;;OAEG;IACH,iBAAiB,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;CACnD,CAAC;AAMF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAK1D;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAkBpF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,CAwCpF;AAqBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG,UAAU,CAyCzE;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAiB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAYrF"}