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.
- package/LICENSE +21 -0
- package/README.md +721 -0
- package/dist/actions.d.ts +43 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +35 -0
- package/dist/actions.js.map +1 -0
- package/dist/archetype.d.ts +194 -0
- package/dist/archetype.d.ts.map +1 -0
- package/dist/archetype.js +412 -0
- package/dist/archetype.js.map +1 -0
- package/dist/component.d.ts +89 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/component.js +237 -0
- package/dist/component.js.map +1 -0
- package/dist/encoding.d.ts +204 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +215 -0
- package/dist/encoding.js.map +1 -0
- package/dist/entity.d.ts +129 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +243 -0
- package/dist/entity.js.map +1 -0
- package/dist/event.d.ts +237 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +293 -0
- package/dist/event.js.map +1 -0
- package/dist/filters.d.ts +121 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +202 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/name.d.ts +70 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +172 -0
- package/dist/name.js.map +1 -0
- package/dist/observer.d.ts +83 -0
- package/dist/observer.d.ts.map +1 -0
- package/dist/observer.js +62 -0
- package/dist/observer.js.map +1 -0
- package/dist/query.d.ts +198 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +299 -0
- package/dist/query.js.map +1 -0
- package/dist/registry.d.ts +118 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +112 -0
- package/dist/registry.js.map +1 -0
- package/dist/relation.d.ts +60 -0
- package/dist/relation.d.ts.map +1 -0
- package/dist/relation.js +171 -0
- package/dist/relation.js.map +1 -0
- package/dist/removal.d.ts +27 -0
- package/dist/removal.d.ts.map +1 -0
- package/dist/removal.js +66 -0
- package/dist/removal.js.map +1 -0
- package/dist/resource.d.ts +78 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +86 -0
- package/dist/resource.js.map +1 -0
- package/dist/scheduler.d.ts +106 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +204 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/schema.d.ts +117 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +113 -0
- package/dist/schema.js.map +1 -0
- package/dist/world.d.ts +172 -0
- package/dist/world.d.ts.map +1 -0
- package/dist/world.js +127 -0
- package/dist/world.js.map +1 -0
- 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"}
|
package/dist/actions.js
ADDED
|
@@ -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
|