@vworlds/vecs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +12 -0
- package/.devcontainer/devcontainer.json +22 -0
- package/.github/workflows/publish.yml +32 -0
- package/README.md +464 -0
- package/dist/component.d.ts +135 -0
- package/dist/component.js +101 -0
- package/dist/component.js.map +1 -0
- package/dist/entity.d.ts +157 -0
- package/dist/entity.js +199 -0
- package/dist/entity.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +25 -0
- package/dist/phase.d.ts +47 -0
- package/dist/phase.js +23 -0
- package/dist/phase.js.map +1 -0
- package/dist/system.d.ts +361 -0
- package/dist/system.js +396 -0
- package/dist/system.js.map +1 -0
- package/dist/util/array_map.d.ts +58 -0
- package/dist/util/array_map.js +84 -0
- package/dist/util/array_map.js.map +1 -0
- package/dist/util/bitset.d.ts +117 -0
- package/dist/util/bitset.js +177 -0
- package/dist/util/bitset.js.map +1 -0
- package/dist/util/events.d.ts +27 -0
- package/dist/util/events.js +43 -0
- package/dist/util/events.js.map +1 -0
- package/dist/util/ordered_set.d.ts +17 -0
- package/dist/util/ordered_set.js +69 -0
- package/dist/util/ordered_set.js.map +1 -0
- package/dist/world.d.ts +279 -0
- package/dist/world.js +453 -0
- package/dist/world.js.map +1 -0
- package/package.json +25 -0
- package/src/component.ts +180 -0
- package/src/entity.ts +276 -0
- package/src/index.ts +6 -0
- package/src/phase.ts +49 -0
- package/src/system.ts +693 -0
- package/src/util/array_map.ts +93 -0
- package/src/util/bitset.ts +199 -0
- package/src/util/events.ts +95 -0
- package/src/util/ordered_set.ts +82 -0
- package/src/world.ts +534 -0
- package/tests/_helpers.ts +30 -0
- package/tests/array_map.test.ts +68 -0
- package/tests/bitset.test.ts +127 -0
- package/tests/component.test.ts +104 -0
- package/tests/entity.test.ts +179 -0
- package/tests/events.test.ts +48 -0
- package/tests/ordered_set.test.ts +153 -0
- package/tests/setup.ts +6 -0
- package/tests/system.test.ts +800 -0
- package/tests/world.test.ts +174 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +9 -0
package/dist/world.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { ComponentMeta, } from "./component.js";
|
|
2
|
+
import { Entity } from "./entity.js";
|
|
3
|
+
import { System } from "./system.js";
|
|
4
|
+
import { ArrayMap } from "./util/array_map.js";
|
|
5
|
+
import { Phase } from "./phase.js";
|
|
6
|
+
const LOCAL_COMPONENT_MIN = 256;
|
|
7
|
+
/**
|
|
8
|
+
* The central ECS container.
|
|
9
|
+
*
|
|
10
|
+
* A `World` owns all entities, components, systems, and the update pipeline.
|
|
11
|
+
* Typical lifecycle:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Register components** — call {@link registerComponent} (and optionally
|
|
14
|
+
* {@link registerComponentType}) for every component class.
|
|
15
|
+
* 2. **Register systems** — call {@link system} (or {@link addSystem}) to
|
|
16
|
+
* create and configure {@link System | systems}.
|
|
17
|
+
* 3. **Start** — call {@link start} to freeze registration and sort systems
|
|
18
|
+
* into their phases.
|
|
19
|
+
* 4. **Run loop** — call {@link runPhase} once per frame for each phase.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* const world = new World();
|
|
23
|
+
*
|
|
24
|
+
* world.registerComponent(Position);
|
|
25
|
+
* world.registerComponent(Velocity);
|
|
26
|
+
*
|
|
27
|
+
* world.system("Move")
|
|
28
|
+
* .requires(Position, Velocity)
|
|
29
|
+
* .update(Position, (pos) => { pos.x += vel.x; });
|
|
30
|
+
*
|
|
31
|
+
* world.start();
|
|
32
|
+
*
|
|
33
|
+
* // game loop:
|
|
34
|
+
* world.runPhase(updatePhase, Date.now(), 16);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class World {
|
|
38
|
+
constructor() {
|
|
39
|
+
this.entities = new Map(); // maps entity Id to Entity
|
|
40
|
+
this.componentNameTypeMap = new Map();
|
|
41
|
+
this.archChangeQueue = [];
|
|
42
|
+
this.destroyedEntities = [];
|
|
43
|
+
this.pendingSystems = [];
|
|
44
|
+
this.allSystems = [];
|
|
45
|
+
this.Class2Meta = new Map();
|
|
46
|
+
this.Type2Meta = new ArrayMap();
|
|
47
|
+
this.updatedComponents = [];
|
|
48
|
+
this.localComponentCounter = LOCAL_COMPONENT_MIN;
|
|
49
|
+
this.componentRegistrationDisabled = false;
|
|
50
|
+
this.systemRegistrationDisabled = false;
|
|
51
|
+
this.pipeline = new Map();
|
|
52
|
+
this.eidCounter = 0;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Return the entity with id `eid`, creating it if it does not yet exist.
|
|
56
|
+
*
|
|
57
|
+
* Used by networking code to materialise server-assigned entities:
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* const e = world.getOrCreateEntity(snapshot.eid, (e) => {
|
|
61
|
+
* networkEntities.add(e);
|
|
62
|
+
* });
|
|
63
|
+
* const c = e.add(snapshot.type, false);
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @param eid - The entity id to look up or create.
|
|
67
|
+
* @param onCreateCallback - Optional callback invoked only when a **new**
|
|
68
|
+
* entity is created, before it is returned. Use this to initialise
|
|
69
|
+
* bookkeeping (e.g. tracking it in a local set).
|
|
70
|
+
* @returns The existing or newly created entity.
|
|
71
|
+
*/
|
|
72
|
+
getOrCreateEntity(eid, onCreateCallback) {
|
|
73
|
+
let e = this.entities.get(eid);
|
|
74
|
+
if (!e) {
|
|
75
|
+
e = new Entity(this, eid);
|
|
76
|
+
this.entities.set(eid, e);
|
|
77
|
+
if (onCreateCallback)
|
|
78
|
+
onCreateCallback(e);
|
|
79
|
+
}
|
|
80
|
+
return e;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Look up an entity by id.
|
|
84
|
+
*
|
|
85
|
+
* @param id - Numeric entity id.
|
|
86
|
+
* @returns The entity, or `undefined` if no entity with that id exists.
|
|
87
|
+
*/
|
|
88
|
+
entity(id) {
|
|
89
|
+
return this.entities.get(id);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a new entity with an auto-assigned id and register it in the world.
|
|
93
|
+
*
|
|
94
|
+
* The id counter starts at 0 (or at the value set by
|
|
95
|
+
* {@link setEntityIdRange}) and increments by one for each call.
|
|
96
|
+
*
|
|
97
|
+
* @returns The new entity.
|
|
98
|
+
*/
|
|
99
|
+
createEntity() {
|
|
100
|
+
const eid = this.eidCounter++;
|
|
101
|
+
const e = new Entity(this, eid);
|
|
102
|
+
this.entities.set(eid, e);
|
|
103
|
+
return e;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Set the starting value for the auto-incrementing entity id counter.
|
|
107
|
+
*
|
|
108
|
+
* Must be called **before** {@link start} (or
|
|
109
|
+
* {@link disableComponentRegistration}). Useful when the world runs alongside
|
|
110
|
+
* a server that owns a different id range — for example, locally-created
|
|
111
|
+
* client entities can start at a high offset to avoid collisions with
|
|
112
|
+
* server-assigned ids.
|
|
113
|
+
*
|
|
114
|
+
* @param min - The first id that will be assigned by {@link createEntity}.
|
|
115
|
+
* @throws If called after registration has been disabled.
|
|
116
|
+
*/
|
|
117
|
+
setEntityIdRange(min) {
|
|
118
|
+
if (this.componentRegistrationDisabled)
|
|
119
|
+
throw "setEntityIdRange must be called before component registration is disabled";
|
|
120
|
+
this.eidCounter = min;
|
|
121
|
+
}
|
|
122
|
+
getComponentInstance(typeOrClass, entity) {
|
|
123
|
+
const meta = this.getComponentMeta(typeOrClass);
|
|
124
|
+
const c = new meta.Class(entity, meta);
|
|
125
|
+
const hook = meta["onAddHandler"];
|
|
126
|
+
if (hook)
|
|
127
|
+
hook(c);
|
|
128
|
+
return c;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Retrieve the {@link ComponentMeta} record for a registered component.
|
|
132
|
+
*
|
|
133
|
+
* @param typeOrClass - A component class constructor or a numeric type id.
|
|
134
|
+
* @returns The corresponding `ComponentMeta`.
|
|
135
|
+
* @throws If no component with that class or type id has been registered.
|
|
136
|
+
*/
|
|
137
|
+
getComponentMeta(typeOrClass) {
|
|
138
|
+
let meta;
|
|
139
|
+
if (typeof typeOrClass === "function") {
|
|
140
|
+
meta = this.Class2Meta.get(typeOrClass);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
meta = this.Type2Meta.get(typeOrClass);
|
|
144
|
+
}
|
|
145
|
+
if (!meta)
|
|
146
|
+
throw `unregistered component meta for component type or class '${typeOrClass}'`;
|
|
147
|
+
return meta;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resolve a component class or type id to its numeric type id.
|
|
151
|
+
*
|
|
152
|
+
* @param typeOrClass - A component class constructor or a numeric type id.
|
|
153
|
+
* @returns The numeric type id.
|
|
154
|
+
*/
|
|
155
|
+
getComponentType(typeOrClass) {
|
|
156
|
+
if (typeof typeOrClass === "function") {
|
|
157
|
+
return this.getComponentMeta(typeOrClass).type;
|
|
158
|
+
}
|
|
159
|
+
return typeOrClass;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Mark an entity's archetype as changed, queuing it for re-evaluation
|
|
163
|
+
* against all system queries at the end of the current system run.
|
|
164
|
+
*
|
|
165
|
+
* Also recursively marks all children as changed so that `{ PARENT: ... }`
|
|
166
|
+
* queries are re-evaluated.
|
|
167
|
+
*
|
|
168
|
+
* @internal Called automatically by {@link Entity.add} and
|
|
169
|
+
* {@link Entity.remove}.
|
|
170
|
+
*/
|
|
171
|
+
archetypeChanged(e) {
|
|
172
|
+
if (e._archetypeChanged)
|
|
173
|
+
return;
|
|
174
|
+
e._archetypeChanged = true;
|
|
175
|
+
this.archChangeQueue.push(e);
|
|
176
|
+
e.children.forEach((child) => this.archetypeChanged(child));
|
|
177
|
+
}
|
|
178
|
+
/** @internal */
|
|
179
|
+
_notifyComponentAdded(e, c) {
|
|
180
|
+
this.archetypeChanged(e);
|
|
181
|
+
}
|
|
182
|
+
/** @internal */
|
|
183
|
+
_notifyComponentRemoved(e, c) {
|
|
184
|
+
const hook = c.meta["onRemoveHandler"];
|
|
185
|
+
if (hook)
|
|
186
|
+
hook(c);
|
|
187
|
+
this.archetypeChanged(e);
|
|
188
|
+
}
|
|
189
|
+
/** @internal */
|
|
190
|
+
_notifyEntityDestroyed(e) {
|
|
191
|
+
if (!this.entities.delete(e.eid))
|
|
192
|
+
return;
|
|
193
|
+
e.forEachComponent((c) => {
|
|
194
|
+
e.remove(c.type);
|
|
195
|
+
});
|
|
196
|
+
this.destroyedEntities.push(e);
|
|
197
|
+
}
|
|
198
|
+
updateArchetypes() {
|
|
199
|
+
if (this.archChangeQueue.length > 0) {
|
|
200
|
+
this.allSystems.forEach((s) => {
|
|
201
|
+
this.archChangeQueue.forEach((e) => {
|
|
202
|
+
if (s.belongs(e)) {
|
|
203
|
+
e._addSystem(s);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
e._removeSystem(s);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
this.archChangeQueue.forEach((e) => {
|
|
211
|
+
e.clearDeletedComponents();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
this.destroyedEntities.forEach((e) => {
|
|
215
|
+
e["_destroy"]();
|
|
216
|
+
});
|
|
217
|
+
this.destroyedEntities.length = 0;
|
|
218
|
+
this.updatedComponents.forEach((c) => {
|
|
219
|
+
const hook = c.meta["onSetHandler"];
|
|
220
|
+
if (hook)
|
|
221
|
+
hook(c);
|
|
222
|
+
c.entity._notifyModified(c);
|
|
223
|
+
c["dirty"] = false;
|
|
224
|
+
});
|
|
225
|
+
this.archChangeQueue.forEach((e) => {
|
|
226
|
+
e._updateSystems();
|
|
227
|
+
e._archetypeChanged = false;
|
|
228
|
+
});
|
|
229
|
+
this.archChangeQueue.length = 0;
|
|
230
|
+
this.updatedComponents.length = 0;
|
|
231
|
+
}
|
|
232
|
+
/** @internal Queues a component for onSet / update delivery. */
|
|
233
|
+
_queueUpdatedComponent(c) {
|
|
234
|
+
if (c["dirty"])
|
|
235
|
+
return;
|
|
236
|
+
c["dirty"] = true;
|
|
237
|
+
this.updatedComponents.push(c);
|
|
238
|
+
}
|
|
239
|
+
registerComponent(ComponentClass, typeOrComponentName, componentName) {
|
|
240
|
+
if (this.componentRegistrationDisabled) {
|
|
241
|
+
throw "World component registartion is disabled";
|
|
242
|
+
}
|
|
243
|
+
let type = undefined;
|
|
244
|
+
// Determine if the second argument is type or componentName based on its type
|
|
245
|
+
if (typeof typeOrComponentName === "number") {
|
|
246
|
+
type = typeOrComponentName;
|
|
247
|
+
}
|
|
248
|
+
else if (typeof typeOrComponentName === "string") {
|
|
249
|
+
componentName = typeOrComponentName;
|
|
250
|
+
}
|
|
251
|
+
componentName = componentName || ComponentClass.name;
|
|
252
|
+
let local = false;
|
|
253
|
+
if (type === undefined) {
|
|
254
|
+
// attempt to get type id from name->type map
|
|
255
|
+
type = this.componentNameTypeMap.get(componentName);
|
|
256
|
+
if (type === undefined) {
|
|
257
|
+
type = this.localComponentCounter++;
|
|
258
|
+
local = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
let meta = this.Class2Meta.get(ComponentClass);
|
|
262
|
+
if (meta) {
|
|
263
|
+
if (local)
|
|
264
|
+
this.localComponentCounter--;
|
|
265
|
+
throw `Trying to register ${componentName} with type=${type} which is already registered to ${meta.componentName}`;
|
|
266
|
+
}
|
|
267
|
+
this.registerComponentType(componentName, type);
|
|
268
|
+
meta = new ComponentMeta(ComponentClass, type, componentName);
|
|
269
|
+
this.Class2Meta.set(ComponentClass, meta);
|
|
270
|
+
this.Type2Meta.set(type, meta);
|
|
271
|
+
console.log("Registered component %s with type=%d as %s component", componentName, type, local ? "local" : "networked");
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Pre-register a component name → type id mapping without associating a
|
|
275
|
+
* class.
|
|
276
|
+
*
|
|
277
|
+
* Useful when network messages refer to components by type id and the
|
|
278
|
+
* corresponding class may be registered later. Call this before
|
|
279
|
+
* {@link registerComponent} to ensure the class picks up the server-assigned
|
|
280
|
+
* id rather than a locally generated one.
|
|
281
|
+
*
|
|
282
|
+
* @param componentName - The string name used in network payloads.
|
|
283
|
+
* @param type - The numeric type id assigned by the server.
|
|
284
|
+
*/
|
|
285
|
+
registerComponentType(componentName, type) {
|
|
286
|
+
this.componentNameTypeMap.set(componentName, type);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Register a pre-built {@link System} with the world.
|
|
290
|
+
*
|
|
291
|
+
* In most cases it is more convenient to use {@link system} which both
|
|
292
|
+
* creates and registers in one call. Use `addSystem` when you need to
|
|
293
|
+
* subclass `System` directly.
|
|
294
|
+
*
|
|
295
|
+
* @param s - The system to add.
|
|
296
|
+
* @throws If system registration is disabled (after {@link start}).
|
|
297
|
+
*/
|
|
298
|
+
addSystem(s) {
|
|
299
|
+
if (this.systemRegistrationDisabled)
|
|
300
|
+
throw "System registration is disabled";
|
|
301
|
+
this.pendingSystems.push(s);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Create a new {@link System}, register it, and return it for configuration.
|
|
305
|
+
*
|
|
306
|
+
* This is the primary way to define systems:
|
|
307
|
+
*
|
|
308
|
+
* ```ts
|
|
309
|
+
* world.system("Render")
|
|
310
|
+
* .phase("update")
|
|
311
|
+
* .requires(Position, Sprite)
|
|
312
|
+
* .enter([Sprite], (e, [sprite]) => sprite.initialize(scene))
|
|
313
|
+
* .update(Position, (pos) => { ... });
|
|
314
|
+
* ```
|
|
315
|
+
*
|
|
316
|
+
* @param name - A unique display name for the system.
|
|
317
|
+
* @returns The new `System` instance.
|
|
318
|
+
*/
|
|
319
|
+
system(name) {
|
|
320
|
+
const system = new System(name, this);
|
|
321
|
+
this.addSystem(system);
|
|
322
|
+
return system;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Prevent any further calls to {@link registerComponent}.
|
|
326
|
+
*
|
|
327
|
+
* Called automatically by {@link start}. Can be called early if you want to
|
|
328
|
+
* lock component registration before systems are fully configured.
|
|
329
|
+
*/
|
|
330
|
+
disableComponentRegistration() {
|
|
331
|
+
this.componentRegistrationDisabled = true;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Freeze registration and prepare the world for running.
|
|
335
|
+
*
|
|
336
|
+
* Disables both component and system registration, then distributes all
|
|
337
|
+
* pending systems into their assigned pipeline phases (defaulting to
|
|
338
|
+
* `"update"`). Logs the resulting phase → system order to the console.
|
|
339
|
+
*
|
|
340
|
+
* Call this once, after all components and systems are registered but before
|
|
341
|
+
* the first {@link runPhase} call.
|
|
342
|
+
*/
|
|
343
|
+
start() {
|
|
344
|
+
this.componentRegistrationDisabled = true;
|
|
345
|
+
this.systemRegistrationDisabled = true;
|
|
346
|
+
this.reindexSystems();
|
|
347
|
+
}
|
|
348
|
+
reindexSystems() {
|
|
349
|
+
let _defaultPhase = this.pipeline.get("update");
|
|
350
|
+
if (!_defaultPhase) {
|
|
351
|
+
_defaultPhase = new Phase("update", this);
|
|
352
|
+
this.pipeline.set(_defaultPhase.name, _defaultPhase);
|
|
353
|
+
}
|
|
354
|
+
const defaultPhase = _defaultPhase;
|
|
355
|
+
this.pendingSystems.forEach((s) => {
|
|
356
|
+
let phase = s._phase;
|
|
357
|
+
if (typeof phase === "string") {
|
|
358
|
+
phase = this.pipeline.get(phase);
|
|
359
|
+
}
|
|
360
|
+
phase = phase || defaultPhase;
|
|
361
|
+
phase.systems.push(s);
|
|
362
|
+
});
|
|
363
|
+
this.pendingSystems.length = 0;
|
|
364
|
+
this.allSystems.length = 0;
|
|
365
|
+
this.pipeline.forEach((phase) => {
|
|
366
|
+
this.allSystems.push(...phase.systems);
|
|
367
|
+
console.log("Phase %s : %s", phase.name, phase.systems.map((s) => s.name).join(" -> "));
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Return the {@link Hook} for a component class.
|
|
372
|
+
*
|
|
373
|
+
* Hooks let you react to component lifecycle events (add / remove / set)
|
|
374
|
+
* without building a full {@link System}. The hook is backed by the
|
|
375
|
+
* component's {@link ComponentMeta} and the same object is returned on every
|
|
376
|
+
* call.
|
|
377
|
+
*
|
|
378
|
+
* ```ts
|
|
379
|
+
* world.hook(Sprite)
|
|
380
|
+
* .onAdd(c => c.initialize(scene))
|
|
381
|
+
* .onRemove(c => c.destroy());
|
|
382
|
+
* ```
|
|
383
|
+
*
|
|
384
|
+
* @param C - The component class.
|
|
385
|
+
* @returns The `Hook` for that component type.
|
|
386
|
+
*/
|
|
387
|
+
hook(C) {
|
|
388
|
+
return this.getComponentMeta(C);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Add a named phase to the update pipeline and return it.
|
|
392
|
+
*
|
|
393
|
+
* Phases are executed in insertion order when you call {@link runPhase} for
|
|
394
|
+
* each one. Systems are assigned to a phase via {@link System.phase}.
|
|
395
|
+
*
|
|
396
|
+
* ```ts
|
|
397
|
+
* const preUpdate = world.addPhase("preupdate");
|
|
398
|
+
* const update = world.addPhase("update");
|
|
399
|
+
* const send = world.addPhase("send");
|
|
400
|
+
* ```
|
|
401
|
+
*
|
|
402
|
+
* @param name - Unique phase name. Systems can reference it by this string.
|
|
403
|
+
* @returns The new {@link IPhase}.
|
|
404
|
+
*/
|
|
405
|
+
addPhase(name) {
|
|
406
|
+
const phase = new Phase(name, this);
|
|
407
|
+
this.pipeline.set(name, phase);
|
|
408
|
+
return phase;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Execute all systems in the given phase for one tick.
|
|
412
|
+
*
|
|
413
|
+
* After each system runs, pending archetype changes (entity add/remove
|
|
414
|
+
* component events) are flushed so that `enter` / `exit` callbacks are
|
|
415
|
+
* delivered before the next system in the same phase executes.
|
|
416
|
+
*
|
|
417
|
+
* @param phase - The {@link IPhase} to run (returned by {@link addPhase}).
|
|
418
|
+
* @param now - Absolute timestamp in milliseconds (e.g. `Date.now()`).
|
|
419
|
+
* @param delta - Milliseconds elapsed since the previous tick.
|
|
420
|
+
*/
|
|
421
|
+
runPhase(phase, now, delta) {
|
|
422
|
+
phase.systems.forEach((s) => {
|
|
423
|
+
s._run(now, delta);
|
|
424
|
+
this.updateArchetypes();
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Run every phase in the pipeline in insertion order (the order phases were
|
|
429
|
+
* registered via {@link addPhase}). Equivalent to calling
|
|
430
|
+
* {@link runPhase} for each phase manually.
|
|
431
|
+
*
|
|
432
|
+
* @param now - Absolute timestamp in milliseconds (e.g. `Date.now()`).
|
|
433
|
+
* @param delta - Milliseconds elapsed since the previous tick.
|
|
434
|
+
*/
|
|
435
|
+
progress(now, delta) {
|
|
436
|
+
this.pipeline.forEach((phase) => {
|
|
437
|
+
this.runPhase(phase, now, delta);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Destroy every entity currently tracked by the world.
|
|
442
|
+
*
|
|
443
|
+
* Triggers all `onRemove` hooks and `exit` callbacks. Useful when
|
|
444
|
+
* transitioning between game sessions or resetting to a clean state.
|
|
445
|
+
*/
|
|
446
|
+
clearAllEntities() {
|
|
447
|
+
this.entities.forEach((e) => {
|
|
448
|
+
e.destroy();
|
|
449
|
+
});
|
|
450
|
+
this.entities.clear();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=world.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.js","sourceRoot":"","sources":["../src/world.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vworlds/vecs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"clean": "rimraf dist && rimraf tsconfig.tsbuildinfo",
|
|
9
|
+
"prepack": "yarn build",
|
|
10
|
+
"build": "yarn clean && yarn compile",
|
|
11
|
+
"compile": "tsc --build && cp \"./package.json\" ./dist/",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^20.0.0",
|
|
17
|
+
"rimraf": "^5.0.0",
|
|
18
|
+
"typescript": "^4.8.4",
|
|
19
|
+
"vitest": "^1.6.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"eventemitter3": "^4.0.7"
|
|
23
|
+
},
|
|
24
|
+
"license": "UNLICENSED"
|
|
25
|
+
}
|
package/src/component.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { BitPtr, Bitset } from "./util/bitset.js";
|
|
2
|
+
import type { Entity } from "./entity.js";
|
|
3
|
+
import { type World } from "./world.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lifecycle hook for a component type. Obtained via {@link World.hook}.
|
|
7
|
+
*
|
|
8
|
+
* Hooks let you react to component lifecycle events without building a full
|
|
9
|
+
* {@link System}. Each call returns the same `Hook` so the methods can be
|
|
10
|
+
* chained:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* world.hook(Sprite)
|
|
14
|
+
* .onAdd(c => initSprite(c))
|
|
15
|
+
* .onRemove(c => destroySprite(c))
|
|
16
|
+
* .onSet(c => syncSprite(c));
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Callbacks are invoked synchronously during {@link World.runPhase} when
|
|
20
|
+
* archetype changes are flushed.
|
|
21
|
+
*
|
|
22
|
+
* @typeParam C - The `Component` subclass this hook is bound to.
|
|
23
|
+
*/
|
|
24
|
+
export interface Hook<C extends Component = Component> {
|
|
25
|
+
/**
|
|
26
|
+
* Register a callback that fires when a component of this type is added to
|
|
27
|
+
* an entity.
|
|
28
|
+
*
|
|
29
|
+
* @param handler - Receives the newly created component instance.
|
|
30
|
+
* @returns `this` for chaining.
|
|
31
|
+
*/
|
|
32
|
+
onAdd(handler: (c: C) => void): Hook<C>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register a callback that fires when a component of this type is removed
|
|
36
|
+
* from an entity (including when the entity is destroyed).
|
|
37
|
+
*
|
|
38
|
+
* @param handler - Receives the component instance being removed.
|
|
39
|
+
* @returns `this` for chaining.
|
|
40
|
+
*/
|
|
41
|
+
onRemove(handler: (c: C) => void): Hook<C>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register a callback that fires when {@link Component.modified} is called
|
|
45
|
+
* on a component of this type.
|
|
46
|
+
*
|
|
47
|
+
* @param handler - Receives the component instance that changed.
|
|
48
|
+
* @returns `this` for chaining.
|
|
49
|
+
*/
|
|
50
|
+
onSet(handler: (c: C) => void): Hook<C>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Internal bookkeeping record for a registered component class.
|
|
55
|
+
*
|
|
56
|
+
* Every component class that is passed to {@link World.registerComponent} gets
|
|
57
|
+
* a `ComponentMeta` that maps it to a numeric type id, a string name, and a
|
|
58
|
+
* pre-computed {@link BitPtr} used for fast archetype checks.
|
|
59
|
+
*
|
|
60
|
+
* `ComponentMeta` also implements {@link Hook}, so you can attach lifecycle
|
|
61
|
+
* callbacks directly on the meta object (as `World.hook()` returns it).
|
|
62
|
+
*/
|
|
63
|
+
export class ComponentMeta implements Hook<Component> {
|
|
64
|
+
/** The component class constructor. */
|
|
65
|
+
public readonly Class: typeof Component;
|
|
66
|
+
/** Numeric type id assigned at registration time. */
|
|
67
|
+
public readonly type: number;
|
|
68
|
+
/** Human-readable name used in logs and serialization lookups. */
|
|
69
|
+
public readonly componentName: string;
|
|
70
|
+
/** Pre-computed bit-pointer into the entity archetype {@link Bitset}. */
|
|
71
|
+
public readonly bitPtr: BitPtr;
|
|
72
|
+
private onAddHandler: ((c: Component) => void) | undefined;
|
|
73
|
+
private onRemoveHandler: ((c: Component) => void) | undefined;
|
|
74
|
+
private onSetHandler: ((c: Component) => void) | undefined;
|
|
75
|
+
|
|
76
|
+
constructor(Class: typeof Component, type: number, componentName: string) {
|
|
77
|
+
this.Class = Class;
|
|
78
|
+
this.type = type;
|
|
79
|
+
this.componentName = componentName;
|
|
80
|
+
this.bitPtr = new BitPtr(type);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @inheritdoc */
|
|
84
|
+
public onAdd(handler: (c: Component) => void): ComponentMeta {
|
|
85
|
+
this.onAddHandler = handler;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @inheritdoc */
|
|
90
|
+
public onRemove(handler: (c: Component) => void): ComponentMeta {
|
|
91
|
+
this.onRemoveHandler = handler;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @inheritdoc */
|
|
96
|
+
public onSet(handler: (c: Component) => void): ComponentMeta {
|
|
97
|
+
this.onSetHandler = handler;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A component class constructor or its numeric type id. */
|
|
103
|
+
export type ComponentClassOrType = number | typeof Component;
|
|
104
|
+
|
|
105
|
+
/** An array of component class constructors or type ids. */
|
|
106
|
+
export type ComponentClassArray = ComponentClassOrType[];
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Base class for all ECS components.
|
|
110
|
+
*
|
|
111
|
+
* Extend this class to define data that can be attached to an {@link Entity}:
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* class Position extends Component {
|
|
115
|
+
* x = 0;
|
|
116
|
+
* y = 0;
|
|
117
|
+
* }
|
|
118
|
+
*
|
|
119
|
+
* world.registerComponent(Position);
|
|
120
|
+
* const pos = entity.add(Position);
|
|
121
|
+
* pos.x = 100;
|
|
122
|
+
* pos.modified(); // notify watching systems
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* A component instance is always bound to a single entity and is created by
|
|
126
|
+
* the world when {@link Entity.add} is called.
|
|
127
|
+
*/
|
|
128
|
+
export class Component {
|
|
129
|
+
private dirty: boolean = false;
|
|
130
|
+
|
|
131
|
+
constructor(
|
|
132
|
+
/** The entity this component belongs to. */
|
|
133
|
+
public readonly entity: Entity,
|
|
134
|
+
/** Registration metadata (type id, name, bit-pointer). */
|
|
135
|
+
public readonly meta: ComponentMeta
|
|
136
|
+
) {}
|
|
137
|
+
|
|
138
|
+
/** Numeric type id — shorthand for `this.meta.type`. */
|
|
139
|
+
public get type(): number {
|
|
140
|
+
return this.meta.type;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Pre-computed bit-pointer — shorthand for `this.meta.bitPtr`. */
|
|
144
|
+
public get bitPtr(): BitPtr {
|
|
145
|
+
return this.meta.bitPtr;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Notify the world that this component's data has changed.
|
|
150
|
+
*
|
|
151
|
+
* Queues the component for delivery to all {@link System.update} callbacks
|
|
152
|
+
* that watch this component type. Call this after mutating the component's
|
|
153
|
+
* fields to ensure systems react to the new values.
|
|
154
|
+
*/
|
|
155
|
+
public modified() {
|
|
156
|
+
this.entity.world._queueUpdatedComponent(this);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Returns the component's registered name, e.g. `"Position"`. */
|
|
160
|
+
public toString(): string {
|
|
161
|
+
return this.meta.componentName;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Compute a {@link Bitset} that has a bit set for every component class or
|
|
167
|
+
* type id in `classes`.
|
|
168
|
+
*
|
|
169
|
+
* @internal Used internally to build archetype masks for system queries.
|
|
170
|
+
*/
|
|
171
|
+
export function calculateComponentBitmask(
|
|
172
|
+
classes: ComponentClassArray,
|
|
173
|
+
world: World
|
|
174
|
+
) {
|
|
175
|
+
const bitmask = new Bitset();
|
|
176
|
+
classes.forEach((C) => {
|
|
177
|
+
bitmask.add(world.getComponentType(C));
|
|
178
|
+
});
|
|
179
|
+
return bitmask;
|
|
180
|
+
}
|