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,43 @@
1
+ import type { World } from "./world.js";
2
+ /**
3
+ * Actions record type.
4
+ *
5
+ * A record of functions that can be bound to a world via closure.
6
+ */
7
+ export type Actions = Record<string, (...args: never[]) => unknown>;
8
+ /**
9
+ * Action initializer function.
10
+ *
11
+ * Takes a world and returns an actions record with world captured in closure.
12
+ */
13
+ export type ActionInitializer<T extends Actions> = (world: World) => T;
14
+ /**
15
+ * Action getter function.
16
+ *
17
+ * Takes a world and returns cached actions (creating on first access).
18
+ */
19
+ export type ActionGetter<T extends Actions> = (world: World) => T;
20
+ /**
21
+ * Define reusable actions bound to a world via closure. Actions are initialized
22
+ * once per world and cached for subsequent access.
23
+ *
24
+ * @param initializer - Function that creates actions with world captured in closure
25
+ * @returns Getter function that returns cached actions for a world
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const transformActions = defineActions((world) => ({
30
+ * spawn(x: number, y: number): Entity {
31
+ * const entity = createEntity(world);
32
+ * addComponent(world, entity, Position, { x, y });
33
+ * return entity;
34
+ * },
35
+ * }));
36
+ *
37
+ * // In systems - getter returns cached actions for the world
38
+ * const transform = transformActions(world);
39
+ * const player = transform.spawn(100, 200);
40
+ * ```
41
+ */
42
+ export declare function defineActions<T extends Actions>(initializer: ActionInitializer<T>): ActionGetter<T>;
43
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMxC;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAanG"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Define reusable actions bound to a world via closure. Actions are initialized
3
+ * once per world and cached for subsequent access.
4
+ *
5
+ * @param initializer - Function that creates actions with world captured in closure
6
+ * @returns Getter function that returns cached actions for a world
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const transformActions = defineActions((world) => ({
11
+ * spawn(x: number, y: number): Entity {
12
+ * const entity = createEntity(world);
13
+ * addComponent(world, entity, Position, { x, y });
14
+ * return entity;
15
+ * },
16
+ * }));
17
+ *
18
+ * // In systems - getter returns cached actions for the world
19
+ * const transform = transformActions(world);
20
+ * const player = transform.spawn(100, 200);
21
+ * ```
22
+ */
23
+ export function defineActions(initializer) {
24
+ return (world) => {
25
+ // Use the initializer function itself as a cache key for identity-based lookup
26
+ let actions = world.actions.byInitializer.get(initializer);
27
+ if (actions === undefined) {
28
+ // First access for this world - initialize and cache
29
+ actions = initializer(world);
30
+ world.actions.byInitializer.set(initializer, actions);
31
+ }
32
+ return actions;
33
+ };
34
+ }
35
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,aAAa,CAAoB,WAAiC;IAChF,OAAO,CAAC,KAAY,EAAK,EAAE;QACzB,+EAA+E;QAC/E,IAAI,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,CAAkB,CAAC;QAE5E,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,qDAAqD;YACrD,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAC7B,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,194 @@
1
+ import type { EntityId } from "./encoding.js";
2
+ import type { SchemaRecord } from "./schema.js";
3
+ import type { World } from "./world.js";
4
+ /**
5
+ * Column storage type.
6
+ *
7
+ * Union of typed arrays (numeric values) and regular arrays (primitives/objects).
8
+ */
9
+ export type Column = Int8Array | Int16Array | Int32Array | Uint32Array | Float32Array | Float64Array | unknown[];
10
+ /**
11
+ * Field columns map.
12
+ *
13
+ * Maps field names to their storage columns for a single component.
14
+ */
15
+ export type FieldColumns = {
16
+ [fieldName: string]: Column;
17
+ };
18
+ /**
19
+ * Component tick storage for change detection.
20
+ *
21
+ * Parallel arrays to entity rows tracking when components were added/changed.
22
+ */
23
+ export type ComponentTicks = {
24
+ added: Uint32Array;
25
+ changed: Uint32Array;
26
+ };
27
+ /**
28
+ * Archetype structure.
29
+ *
30
+ * Groups entities with identical component sets for cache-efficient iteration.
31
+ */
32
+ export type Archetype = {
33
+ types: EntityId[];
34
+ typesSet: Set<EntityId>;
35
+ hash: string;
36
+ entities: EntityId[];
37
+ columns: Map<EntityId, FieldColumns>;
38
+ schemas: Map<EntityId, SchemaRecord>;
39
+ edges: Map<EntityId, Archetype>;
40
+ capacity: number;
41
+ ticks: Map<EntityId, ComponentTicks>;
42
+ };
43
+ /**
44
+ * Hashes a sorted array of type IDs into a unique archetype key.
45
+ *
46
+ * @param types - Sorted type IDs
47
+ * @returns Colon-delimited hash key (e.g., "1:5:12")
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * const hash = hashArchetypeTypes([1, 5, 12]); // "1:5:12"
52
+ * ```
53
+ */
54
+ export declare function hashArchetypeTypes(types: EntityId[]): string;
55
+ /**
56
+ * Creates an archetype from sorted type IDs and their schemas.
57
+ * Columns are allocated lazily on first entity insertion to avoid
58
+ * memory allocation for transitional archetypes (graph traversal nodes).
59
+ *
60
+ * @param sortedTypes - Type IDs in ascending order
61
+ * @param schemas - Map of type ID to field schemas
62
+ * @returns New archetype with empty entity storage
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const archetype = createArchetype([positionId, velocityId], schemas);
67
+ * ```
68
+ */
69
+ export declare function createArchetype(sortedTypes: EntityId[], schemas: Map<EntityId, SchemaRecord>): Archetype;
70
+ /**
71
+ * Registers an archetype in the world's lookup table, updates entity records,
72
+ * and fires the archetypeCreated observer event.
73
+ *
74
+ * @param world - World to register archetype in
75
+ * @param archetype - Archetype to register
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * const archetype = createArchetype(types, schemas);
80
+ * registerArchetype(world, archetype);
81
+ * ```
82
+ */
83
+ export declare function registerArchetype(world: World, archetype: Archetype): void;
84
+ /**
85
+ * Creates an archetype and registers it in the world.
86
+ *
87
+ * @param world - World to register archetype in
88
+ * @param types - Sorted type IDs for the archetype
89
+ * @param schemas - Map of type ID to field schemas
90
+ * @returns Newly created and registered archetype
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const archetype = createAndRegisterArchetype(world, [positionId], schemas);
95
+ * ```
96
+ */
97
+ export declare function createAndRegisterArchetype(world: World, types: EntityId[], schemas: Map<EntityId, SchemaRecord>): Archetype;
98
+ /**
99
+ * Adds an entity to an archetype, initializing tick tracking for change detection.
100
+ *
101
+ * @param archetype - Target archetype
102
+ * @param entityId - Entity to add
103
+ * @param tick - Current world tick for change detection (defaults to 0)
104
+ * @returns Row index where entity was inserted
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const row = addEntityToArchetype(archetype, entityId, world.tick);
109
+ * ```
110
+ */
111
+ export declare function addEntityToArchetype(archetype: Archetype, entityId: EntityId, tick?: number): number;
112
+ /**
113
+ * Removes an entity from an archetype using swap-and-pop for O(1) removal.
114
+ * The last entity in the archetype is moved into the vacated row.
115
+ *
116
+ * @param archetype - Archetype to remove entity from
117
+ * @param row - Row index of entity to remove
118
+ * @returns Entity ID that was swapped into the row, or undefined if row was last
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const swapped = removeEntityFromArchetypeByRow(archetype, row);
123
+ * if (swapped) updateEntityRecord(world, swapped, archetype, row);
124
+ * ```
125
+ */
126
+ export declare function removeEntityFromArchetypeByRow(archetype: Archetype, row: number): EntityId | undefined;
127
+ /**
128
+ * Transfers an entity between archetypes, copying shared component data and ticks.
129
+ * Used when adding/removing components causes an entity to move archetypes.
130
+ *
131
+ * @param fromArchetype - Source archetype
132
+ * @param fromRow - Row index in source archetype
133
+ * @param toArchetype - Target archetype
134
+ * @param tick - Current world tick for new component ticks (defaults to 0)
135
+ * @returns New row index and swapped entity ID (if any was moved during removal)
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const { toRow, swappedEntityId } = transferEntityToArchetypeByRow(
140
+ * fromArchetype, fromRow, toArchetype, world.tick
141
+ * );
142
+ * ```
143
+ */
144
+ export declare function transferEntityToArchetypeByRow(fromArchetype: Archetype, fromRow: number, toArchetype: Archetype, tick?: number): {
145
+ toRow: number;
146
+ swappedEntityId: EntityId | undefined;
147
+ };
148
+ /**
149
+ * Destroys an archetype and cleans up all references.
150
+ * Removes from world lookup, fires observer event, and clears bidirectional edges.
151
+ *
152
+ * @param world - World containing the archetype
153
+ * @param archetype - Archetype to destroy (root archetype is protected)
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * if (archetype.entities.length === 0) {
158
+ * destroyArchetype(world, archetype);
159
+ * }
160
+ * ```
161
+ */
162
+ export declare function destroyArchetype(world: World, archetype: Archetype): void;
163
+ /**
164
+ * Traverses the archetype graph to find or create an archetype with a type added.
165
+ * Uses edge caching for O(1) repeated traversals.
166
+ *
167
+ * @param world - World containing archetype graph
168
+ * @param from - Starting archetype
169
+ * @param typeId - Type ID to add
170
+ * @param schema - Schema for the type (required if type is new to graph)
171
+ * @returns Archetype with the type added, or same archetype if type already present
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * const newArchetype = archetypeTraverseAdd(world, archetype, velocityId, velocitySchema);
176
+ * ```
177
+ */
178
+ export declare function archetypeTraverseAdd(world: World, from: Archetype, typeId: EntityId, schema?: SchemaRecord): Archetype;
179
+ /**
180
+ * Traverses the archetype graph to find or create an archetype with a type removed.
181
+ * Uses edge caching for O(1) repeated traversals.
182
+ *
183
+ * @param world - World containing archetype graph
184
+ * @param from - Starting archetype
185
+ * @param typeId - Type ID to remove
186
+ * @returns Archetype with the type removed, or same archetype if type not present
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const newArchetype = archetypeTraverseRemove(world, archetype, velocityId);
191
+ * ```
192
+ */
193
+ export declare function archetypeTraverseRemove(world: World, from: Archetype, typeId: EntityId): Archetype;
194
+ //# sourceMappingURL=archetype.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archetype.d.ts","sourceRoot":"","sources":["../src/archetype.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,412 @@
1
+ import { addEntityRecord, removeEntityRecord } from "./entity.js";
2
+ import { fireObserverEvent } from "./observer.js";
3
+ import { Type } from "./schema.js";
4
+ // ============================================================================
5
+ // Constants
6
+ // ============================================================================
7
+ /**
8
+ * Initial capacity when first entity is added to an archetype.
9
+ */
10
+ const INITIAL_CAPACITY = 16;
11
+ /**
12
+ * Schema for tick columns (Uint32Array).
13
+ */
14
+ const TICK_SCHEMA = Type.u32();
15
+ // ============================================================================
16
+ // Column Utilities
17
+ // ============================================================================
18
+ /**
19
+ * Allocates a column based on schema type (TypedArray for primitives, Array for objects).
20
+ */
21
+ function allocateColumn(schema, capacity) {
22
+ if (schema.kind === "typed") {
23
+ const TypedArrayCtor = schema.arrayConstructor;
24
+ return new TypedArrayCtor(capacity);
25
+ }
26
+ return [];
27
+ }
28
+ /**
29
+ * Resizes a column to new capacity, preserving existing data. Regular arrays need no resize.
30
+ */
31
+ function resizeColumn(column, newCapacity) {
32
+ if (Array.isArray(column)) {
33
+ return column;
34
+ }
35
+ const TypedArrayCtor = column.constructor;
36
+ const newColumn = new TypedArrayCtor(newCapacity);
37
+ const copyLength = Math.min(column.length, newCapacity);
38
+ newColumn.set(column.subarray(0, copyLength));
39
+ return newColumn;
40
+ }
41
+ /**
42
+ * Clears a column slot (undefined for arrays, 0 for typed arrays).
43
+ */
44
+ function clearColumn(column, index) {
45
+ if (Array.isArray(column)) {
46
+ column[index] = undefined;
47
+ }
48
+ else {
49
+ column[index] = 0;
50
+ }
51
+ }
52
+ // ============================================================================
53
+ // Hashing
54
+ // ============================================================================
55
+ /**
56
+ * Hashes a sorted array of type IDs into a unique archetype key.
57
+ *
58
+ * @param types - Sorted type IDs
59
+ * @returns Colon-delimited hash key (e.g., "1:5:12")
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const hash = hashArchetypeTypes([1, 5, 12]); // "1:5:12"
64
+ * ```
65
+ */
66
+ export function hashArchetypeTypes(types) {
67
+ return types.join(":");
68
+ }
69
+ // ============================================================================
70
+ // Archetype Creation
71
+ // ============================================================================
72
+ /**
73
+ * Creates an archetype from sorted type IDs and their schemas.
74
+ * Columns are allocated lazily on first entity insertion to avoid
75
+ * memory allocation for transitional archetypes (graph traversal nodes).
76
+ *
77
+ * @param sortedTypes - Type IDs in ascending order
78
+ * @param schemas - Map of type ID to field schemas
79
+ * @returns New archetype with empty entity storage
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const archetype = createArchetype([positionId, velocityId], schemas);
84
+ * ```
85
+ */
86
+ export function createArchetype(sortedTypes, schemas) {
87
+ return {
88
+ types: sortedTypes,
89
+ typesSet: new Set(sortedTypes),
90
+ hash: hashArchetypeTypes(sortedTypes),
91
+ entities: [],
92
+ columns: new Map(),
93
+ schemas,
94
+ edges: new Map(),
95
+ capacity: 0,
96
+ ticks: new Map(),
97
+ };
98
+ }
99
+ /**
100
+ * Registers an archetype in the world's lookup table, updates entity records,
101
+ * and fires the archetypeCreated observer event.
102
+ *
103
+ * @param world - World to register archetype in
104
+ * @param archetype - Archetype to register
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const archetype = createArchetype(types, schemas);
109
+ * registerArchetype(world, archetype);
110
+ * ```
111
+ */
112
+ export function registerArchetype(world, archetype) {
113
+ world.archetypes.byId.set(archetype.hash, archetype);
114
+ addEntityRecord(world, archetype);
115
+ fireObserverEvent(world, "archetypeCreated", archetype);
116
+ }
117
+ /**
118
+ * Creates an archetype and registers it in the world.
119
+ *
120
+ * @param world - World to register archetype in
121
+ * @param types - Sorted type IDs for the archetype
122
+ * @param schemas - Map of type ID to field schemas
123
+ * @returns Newly created and registered archetype
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const archetype = createAndRegisterArchetype(world, [positionId], schemas);
128
+ * ```
129
+ */
130
+ export function createAndRegisterArchetype(world, types, schemas) {
131
+ const archetype = createArchetype(types, schemas);
132
+ registerArchetype(world, archetype);
133
+ return archetype;
134
+ }
135
+ // ============================================================================
136
+ // Capacity Management
137
+ // ============================================================================
138
+ /**
139
+ * Ensures archetype has capacity for requiredCapacity entities.
140
+ * Allocates columns and tick arrays on first entity, grows 4x thereafter.
141
+ */
142
+ function ensureArchetypeCapacity(archetype, requiredCapacity) {
143
+ if (archetype.capacity >= requiredCapacity)
144
+ return;
145
+ if (archetype.capacity === 0) {
146
+ const initialCapacity = Math.max(INITIAL_CAPACITY, requiredCapacity);
147
+ for (const [componentId, fieldSchemas] of archetype.schemas.entries()) {
148
+ const fieldColumns = {};
149
+ for (const fieldName in fieldSchemas) {
150
+ fieldColumns[fieldName] = allocateColumn(fieldSchemas[fieldName], initialCapacity);
151
+ }
152
+ archetype.columns.set(componentId, fieldColumns);
153
+ }
154
+ for (const componentId of archetype.types) {
155
+ archetype.ticks.set(componentId, {
156
+ added: allocateColumn(TICK_SCHEMA, initialCapacity),
157
+ changed: allocateColumn(TICK_SCHEMA, initialCapacity),
158
+ });
159
+ }
160
+ archetype.capacity = initialCapacity;
161
+ return;
162
+ }
163
+ let newCapacity = archetype.capacity;
164
+ while (newCapacity < requiredCapacity) {
165
+ newCapacity *= 4;
166
+ }
167
+ for (const fieldColumns of archetype.columns.values()) {
168
+ for (const fieldName in fieldColumns) {
169
+ fieldColumns[fieldName] = resizeColumn(fieldColumns[fieldName], newCapacity);
170
+ }
171
+ }
172
+ for (const componentTicks of archetype.ticks.values()) {
173
+ componentTicks.added = resizeColumn(componentTicks.added, newCapacity);
174
+ componentTicks.changed = resizeColumn(componentTicks.changed, newCapacity);
175
+ }
176
+ archetype.capacity = newCapacity;
177
+ }
178
+ // ============================================================================
179
+ // Entity Movement
180
+ // ============================================================================
181
+ /**
182
+ * Adds an entity to an archetype, initializing tick tracking for change detection.
183
+ *
184
+ * @param archetype - Target archetype
185
+ * @param entityId - Entity to add
186
+ * @param tick - Current world tick for change detection (defaults to 0)
187
+ * @returns Row index where entity was inserted
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * const row = addEntityToArchetype(archetype, entityId, world.tick);
192
+ * ```
193
+ */
194
+ export function addEntityToArchetype(archetype, entityId, tick = 0) {
195
+ const row = archetype.entities.length;
196
+ ensureArchetypeCapacity(archetype, row + 1);
197
+ archetype.entities.push(entityId);
198
+ for (const componentTicks of archetype.ticks.values()) {
199
+ componentTicks.added[row] = tick;
200
+ componentTicks.changed[row] = tick;
201
+ }
202
+ return row;
203
+ }
204
+ /**
205
+ * Removes an entity from an archetype using swap-and-pop for O(1) removal.
206
+ * The last entity in the archetype is moved into the vacated row.
207
+ *
208
+ * @param archetype - Archetype to remove entity from
209
+ * @param row - Row index of entity to remove
210
+ * @returns Entity ID that was swapped into the row, or undefined if row was last
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * const swapped = removeEntityFromArchetypeByRow(archetype, row);
215
+ * if (swapped) updateEntityRecord(world, swapped, archetype, row);
216
+ * ```
217
+ */
218
+ export function removeEntityFromArchetypeByRow(archetype, row) {
219
+ const lastIdx = archetype.entities.length - 1;
220
+ let swappedEntityId;
221
+ if (row !== lastIdx) {
222
+ swappedEntityId = archetype.entities[lastIdx];
223
+ archetype.entities[row] = swappedEntityId;
224
+ for (const fieldColumns of archetype.columns.values()) {
225
+ for (const fieldName in fieldColumns) {
226
+ fieldColumns[fieldName][row] = fieldColumns[fieldName][lastIdx];
227
+ }
228
+ }
229
+ for (const componentTicks of archetype.ticks.values()) {
230
+ componentTicks.added[row] = componentTicks.added[lastIdx];
231
+ componentTicks.changed[row] = componentTicks.changed[lastIdx];
232
+ }
233
+ }
234
+ archetype.entities.pop();
235
+ for (const fieldColumns of archetype.columns.values()) {
236
+ for (const fieldName in fieldColumns) {
237
+ clearColumn(fieldColumns[fieldName], lastIdx);
238
+ }
239
+ }
240
+ for (const componentTicks of archetype.ticks.values()) {
241
+ clearColumn(componentTicks.added, lastIdx);
242
+ clearColumn(componentTicks.changed, lastIdx);
243
+ }
244
+ return swappedEntityId;
245
+ }
246
+ /**
247
+ * Transfers an entity between archetypes, copying shared component data and ticks.
248
+ * Used when adding/removing components causes an entity to move archetypes.
249
+ *
250
+ * @param fromArchetype - Source archetype
251
+ * @param fromRow - Row index in source archetype
252
+ * @param toArchetype - Target archetype
253
+ * @param tick - Current world tick for new component ticks (defaults to 0)
254
+ * @returns New row index and swapped entity ID (if any was moved during removal)
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * const { toRow, swappedEntityId } = transferEntityToArchetypeByRow(
259
+ * fromArchetype, fromRow, toArchetype, world.tick
260
+ * );
261
+ * ```
262
+ */
263
+ export function transferEntityToArchetypeByRow(fromArchetype, fromRow, toArchetype, tick = 0) {
264
+ const entityId = fromArchetype.entities[fromRow];
265
+ const toRow = addEntityToArchetype(toArchetype, entityId, tick);
266
+ for (let t = 0; t < toArchetype.types.length; t++) {
267
+ const type = toArchetype.types[t];
268
+ const destFieldColumns = toArchetype.columns.get(type);
269
+ const sourceFieldColumns = fromArchetype.columns.get(type);
270
+ if (!destFieldColumns || !sourceFieldColumns)
271
+ continue;
272
+ for (const fieldName in destFieldColumns) {
273
+ destFieldColumns[fieldName][toRow] = sourceFieldColumns[fieldName][fromRow];
274
+ }
275
+ }
276
+ for (const componentId of toArchetype.types) {
277
+ const fromTicks = fromArchetype.ticks.get(componentId);
278
+ const toTicks = toArchetype.ticks.get(componentId);
279
+ if (fromTicks && toTicks) {
280
+ toTicks.added[toRow] = fromTicks.added[fromRow];
281
+ toTicks.changed[toRow] = fromTicks.changed[fromRow];
282
+ }
283
+ }
284
+ const swappedEntityId = removeEntityFromArchetypeByRow(fromArchetype, fromRow);
285
+ return { toRow, swappedEntityId };
286
+ }
287
+ // ============================================================================
288
+ // Archetype Graph Traversal
289
+ // ============================================================================
290
+ /**
291
+ * Finds insertion index in sorted array using binary search.
292
+ */
293
+ function findInsertionIndex(types, typeId) {
294
+ let low = 0;
295
+ let high = types.length;
296
+ while (low < high) {
297
+ const mid = Math.floor((low + high) / 2);
298
+ if (types[mid] < typeId) {
299
+ low = mid + 1;
300
+ }
301
+ else {
302
+ high = mid;
303
+ }
304
+ }
305
+ return low;
306
+ }
307
+ /**
308
+ * Finds or creates an archetype with a type added, checking cache first to avoid Map allocation.
309
+ */
310
+ function ensureArchetypeWithType(world, from, typeId, schema) {
311
+ const insertIdx = findInsertionIndex(from.types, typeId);
312
+ const newTypes = from.types.toSpliced(insertIdx, 0, typeId);
313
+ const hashKey = hashArchetypeTypes(newTypes);
314
+ const existing = world.archetypes.byId.get(hashKey);
315
+ if (existing)
316
+ return existing;
317
+ const schemas = new Map(from.schemas);
318
+ if (schema)
319
+ schemas.set(typeId, schema);
320
+ return createAndRegisterArchetype(world, newTypes, schemas);
321
+ }
322
+ /**
323
+ * Finds or creates an archetype with a type removed, checking cache first to avoid Map allocation.
324
+ */
325
+ function ensureArchetypeWithoutType(world, from, typeId) {
326
+ const newTypes = from.types.filter((id) => id !== typeId);
327
+ const hashKey = hashArchetypeTypes(newTypes);
328
+ const existing = world.archetypes.byId.get(hashKey);
329
+ if (existing)
330
+ return existing;
331
+ const schemas = new Map(from.schemas);
332
+ schemas.delete(typeId);
333
+ return createAndRegisterArchetype(world, newTypes, schemas);
334
+ }
335
+ /**
336
+ * Destroys an archetype and cleans up all references.
337
+ * Removes from world lookup, fires observer event, and clears bidirectional edges.
338
+ *
339
+ * @param world - World containing the archetype
340
+ * @param archetype - Archetype to destroy (root archetype is protected)
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * if (archetype.entities.length === 0) {
345
+ * destroyArchetype(world, archetype);
346
+ * }
347
+ * ```
348
+ */
349
+ export function destroyArchetype(world, archetype) {
350
+ if (archetype === world.archetypes.root)
351
+ return;
352
+ removeEntityRecord(world, archetype);
353
+ fireObserverEvent(world, "archetypeDestroyed", archetype);
354
+ world.archetypes.byId.delete(archetype.hash);
355
+ for (const [typeId, targetArchetype] of archetype.edges) {
356
+ targetArchetype.edges.delete(typeId);
357
+ }
358
+ }
359
+ /**
360
+ * Traverses the archetype graph to find or create an archetype with a type added.
361
+ * Uses edge caching for O(1) repeated traversals.
362
+ *
363
+ * @param world - World containing archetype graph
364
+ * @param from - Starting archetype
365
+ * @param typeId - Type ID to add
366
+ * @param schema - Schema for the type (required if type is new to graph)
367
+ * @returns Archetype with the type added, or same archetype if type already present
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * const newArchetype = archetypeTraverseAdd(world, archetype, velocityId, velocitySchema);
372
+ * ```
373
+ */
374
+ export function archetypeTraverseAdd(world, from, typeId, schema) {
375
+ if (from.typesSet.has(typeId))
376
+ return from;
377
+ const cachedArchetype = from.edges.get(typeId);
378
+ if (cachedArchetype)
379
+ return cachedArchetype;
380
+ const to = ensureArchetypeWithType(world, from, typeId, schema);
381
+ // Bidirectional edges enable O(1) traversal in both add and remove directions
382
+ from.edges.set(typeId, to);
383
+ to.edges.set(typeId, from);
384
+ return to;
385
+ }
386
+ /**
387
+ * Traverses the archetype graph to find or create an archetype with a type removed.
388
+ * Uses edge caching for O(1) repeated traversals.
389
+ *
390
+ * @param world - World containing archetype graph
391
+ * @param from - Starting archetype
392
+ * @param typeId - Type ID to remove
393
+ * @returns Archetype with the type removed, or same archetype if type not present
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * const newArchetype = archetypeTraverseRemove(world, archetype, velocityId);
398
+ * ```
399
+ */
400
+ export function archetypeTraverseRemove(world, from, typeId) {
401
+ if (!from.typesSet.has(typeId))
402
+ return from;
403
+ const cachedArchetype = from.edges.get(typeId);
404
+ if (cachedArchetype)
405
+ return cachedArchetype;
406
+ const to = ensureArchetypeWithoutType(world, from, typeId);
407
+ // Bidirectional edges enable O(1) traversal in both add and remove directions
408
+ from.edges.set(typeId, to);
409
+ to.edges.set(typeId, from);
410
+ return to;
411
+ }
412
+ //# sourceMappingURL=archetype.js.map