@codehz/ecs 0.0.5 → 0.0.6

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/README.md CHANGED
@@ -120,6 +120,39 @@ world.addComponent(entity, positionRelation, { x: 10, y: 20 });
120
120
  world.flushCommands(); // 通配符钩子会被触发
121
121
  ```
122
122
 
123
+ ### Exclusive Relations
124
+
125
+ ECS 支持 Exclusive Relations,确保实体对于指定的组件类型最多只能有一个关系。当添加新的关系时,会自动移除之前的所有同类型关系:
126
+
127
+ ```typescript
128
+ import { World, component, relation } from "@codehz/ecs";
129
+
130
+ // 定义组件ID
131
+ const ChildOf = component(); // 空组件,用于关系
132
+
133
+ // 创建世界
134
+ const world = new World();
135
+
136
+ // 设置 ChildOf 为独占关系
137
+ world.setExclusive(ChildOf);
138
+
139
+ // 创建实体
140
+ const child = world.createEntity();
141
+ const parent1 = world.createEntity();
142
+ const parent2 = world.createEntity();
143
+
144
+ // 添加第一个关系
145
+ world.addComponent(child, relation(ChildOf, parent1));
146
+ world.flushCommands();
147
+ console.log(world.hasComponent(child, relation(ChildOf, parent1))); // true
148
+
149
+ // 添加第二个关系 - 会自动移除第一个
150
+ world.addComponent(child, relation(ChildOf, parent2));
151
+ world.flushCommands();
152
+ console.log(world.hasComponent(child, relation(ChildOf, parent1))); // false
153
+ console.log(world.hasComponent(child, relation(ChildOf, parent2))); // true
154
+ ```
155
+
123
156
  ### 运行示例
124
157
 
125
158
  ```bash
@@ -139,8 +172,9 @@ bun run examples/simple/demo.ts
139
172
  - `createEntity()`: 创建新实体
140
173
  - `addComponent(entity, componentId, data)`: 向实体添加组件
141
174
  - `removeComponent(entity, componentId)`: 从实体移除组件
175
+ - `setExclusive(componentId)`: 将组件标记为独占关系
142
176
  - `createQuery(componentIds)`: 创建查询
143
- - `registerSystem(system, dependencies?)`: 注册系统,可选指定依赖系统列表
177
+ - `registerSystem(system)`: 注册系统
144
178
  - `registerLifecycleHook(componentId, hook)`: 注册组件或通配符关系生命周期钩子
145
179
  - `unregisterLifecycleHook(componentId, hook)`: 注销组件或通配符关系生命周期钩子
146
180
  - `update(deltaTime)`: 更新世界
package/archetype.d.ts CHANGED
@@ -66,13 +66,13 @@ export declare class Archetype {
66
66
  * @param entityId The entity
67
67
  * @param componentType The wildcard relation type
68
68
  */
69
- getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][] | undefined;
69
+ getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][];
70
70
  /**
71
71
  * Get component data for a specific entity and component type
72
72
  * @param entityId The entity
73
73
  * @param componentType The component type
74
74
  */
75
- getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T | undefined;
75
+ getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T;
76
76
  /**
77
77
  * Set component data for a specific entity and component type
78
78
  * @param entityId The entity
package/changeset.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { EntityId } from "./entity";
2
+ /**
3
+ * @internal Represents a set of component changes to be applied to an entity
4
+ */
5
+ export declare class ComponentChangeset {
6
+ readonly adds: Map<EntityId<any>, any>;
7
+ readonly removes: Set<EntityId<any>>;
8
+ /**
9
+ * Add a component to the changeset
10
+ */
11
+ addComponent<T>(componentType: EntityId<T>, component: T): void;
12
+ /**
13
+ * Remove a component from the changeset
14
+ */
15
+ removeComponent<T>(componentType: EntityId<T>): void;
16
+ /**
17
+ * Check if the changeset has any changes
18
+ */
19
+ hasChanges(): boolean;
20
+ /**
21
+ * Clear all changes
22
+ */
23
+ clear(): void;
24
+ /**
25
+ * Merge another changeset into this one
26
+ */
27
+ merge(other: ComponentChangeset): void;
28
+ /**
29
+ * Apply the changeset to existing components and return the final state
30
+ */
31
+ applyTo(existingComponents: Map<EntityId<any>, any>): Map<EntityId<any>, any>;
32
+ /**
33
+ * Get the final component types after applying changes
34
+ */
35
+ getFinalComponentTypes(existingComponents: Map<EntityId<any>, any>): EntityId<any>[];
36
+ }
@@ -21,7 +21,8 @@ export declare class CommandBuffer {
21
21
  /**
22
22
  * Add a component to an entity (deferred)
23
23
  */
24
- addComponent<T>(entityId: EntityId, componentType: EntityId<T>, component: T): void;
24
+ addComponent(entityId: EntityId, componentType: EntityId<void>): void;
25
+ addComponent<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
25
26
  /**
26
27
  * Remove a component from an entity (deferred)
27
28
  */
package/entity.d.ts CHANGED
@@ -153,5 +153,5 @@ export declare class ComponentIdAllocator {
153
153
  /**
154
154
  * Allocate a new component ID from the global allocator
155
155
  */
156
- export declare function component<T>(): ComponentId<T>;
156
+ export declare function component<T = void>(): ComponentId<T>;
157
157
  export {};
package/index.js CHANGED
@@ -226,6 +226,8 @@ function getOrCreateWithSideEffect(cache, key, create) {
226
226
  }
227
227
 
228
228
  // src/archetype.ts
229
+ var MISSING_COMPONENT = Symbol("missing component");
230
+
229
231
  class Archetype {
230
232
  componentTypes;
231
233
  entities = [];
@@ -257,10 +259,7 @@ class Archetype {
257
259
  this.entityToIndex.set(entityId, index);
258
260
  for (const componentType of this.componentTypes) {
259
261
  const data = componentData.get(componentType);
260
- if (data === undefined) {
261
- throw new Error(`Missing component data for type ${componentType}`);
262
- }
263
- this.componentData.get(componentType).push(data);
262
+ this.componentData.get(componentType).push(data === undefined ? MISSING_COMPONENT : data);
264
263
  }
265
264
  }
266
265
  removeEntity(entityId) {
@@ -287,11 +286,7 @@ class Archetype {
287
286
  getComponent(entityId, componentType) {
288
287
  const index = this.entityToIndex.get(entityId);
289
288
  if (index === undefined) {
290
- if (getIdType(componentType) === "wildcard-relation") {
291
- return [];
292
- } else {
293
- return;
294
- }
289
+ throw new Error(`Entity ${entityId} is not in this archetype`);
295
290
  }
296
291
  if (isWildcardRelationId(componentType)) {
297
292
  const decoded = decodeRelationId(componentType);
@@ -302,24 +297,26 @@ class Archetype {
302
297
  if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) {
303
298
  const dataArray = this.componentData.get(relType);
304
299
  if (dataArray && dataArray[index] !== undefined) {
305
- relations.push([relDetailed.targetId, dataArray[index]]);
300
+ const data = dataArray[index];
301
+ relations.push([relDetailed.targetId, data === MISSING_COMPONENT ? undefined : data]);
306
302
  }
307
303
  }
308
304
  }
309
305
  return relations;
310
306
  } else {
311
- return this.componentData.get(componentType)?.[index];
307
+ const data = this.componentData.get(componentType)?.[index];
308
+ return data === MISSING_COMPONENT ? undefined : data;
312
309
  }
313
310
  }
314
311
  setComponent(entityId, componentType, data) {
312
+ if (!this.componentData.has(componentType)) {
313
+ throw new Error(`Component type ${componentType} is not in this archetype`);
314
+ }
315
315
  const index = this.entityToIndex.get(entityId);
316
316
  if (index === undefined) {
317
317
  throw new Error(`Entity ${entityId} is not in this archetype`);
318
318
  }
319
319
  const dataArray = this.componentData.get(componentType);
320
- if (!dataArray) {
321
- throw new Error(`Component type ${componentType} is not in this archetype`);
322
- }
323
320
  dataArray[index] = data;
324
321
  }
325
322
  getEntities() {
@@ -364,14 +361,16 @@ class Archetype {
364
361
  for (const relType of matchingRelations) {
365
362
  const dataArray = this.componentData.get(relType);
366
363
  if (dataArray && dataArray[entityIndex] !== undefined) {
364
+ const data = dataArray[entityIndex];
367
365
  const decodedRel = decodeRelationId(relType);
368
- relations.push([decodedRel.targetId, dataArray[entityIndex]]);
366
+ relations.push([decodedRel.targetId, data === MISSING_COMPONENT ? undefined : data]);
369
367
  }
370
368
  }
371
369
  return relations;
372
370
  } else {
373
371
  const dataArray = dataSource;
374
- return dataArray ? dataArray[entityIndex] : undefined;
372
+ const data = dataArray ? dataArray[entityIndex] : undefined;
373
+ return data === MISSING_COMPONENT ? undefined : data;
375
374
  }
376
375
  });
377
376
  callback(entity, ...components);
@@ -381,13 +380,59 @@ class Archetype {
381
380
  for (let i = 0;i < this.entities.length; i++) {
382
381
  const components = new Map;
383
382
  for (const componentType of this.componentTypes) {
384
- components.set(componentType, this.componentData.get(componentType)[i]);
383
+ const data = this.componentData.get(componentType)[i];
384
+ components.set(componentType, data === MISSING_COMPONENT ? undefined : data);
385
385
  }
386
386
  callback(this.entities[i], components);
387
387
  }
388
388
  }
389
389
  }
390
390
 
391
+ // src/changeset.ts
392
+ class ComponentChangeset {
393
+ adds = new Map;
394
+ removes = new Set;
395
+ addComponent(componentType, component2) {
396
+ this.adds.set(componentType, component2);
397
+ this.removes.delete(componentType);
398
+ }
399
+ removeComponent(componentType) {
400
+ this.removes.add(componentType);
401
+ this.adds.delete(componentType);
402
+ }
403
+ hasChanges() {
404
+ return this.adds.size > 0 || this.removes.size > 0;
405
+ }
406
+ clear() {
407
+ this.adds.clear();
408
+ this.removes.clear();
409
+ }
410
+ merge(other) {
411
+ for (const [componentType, component2] of other.adds) {
412
+ this.adds.set(componentType, component2);
413
+ this.removes.delete(componentType);
414
+ }
415
+ for (const componentType of other.removes) {
416
+ this.removes.add(componentType);
417
+ this.adds.delete(componentType);
418
+ }
419
+ }
420
+ applyTo(existingComponents) {
421
+ const finalComponents = new Map(existingComponents);
422
+ for (const componentType of this.removes) {
423
+ finalComponents.delete(componentType);
424
+ }
425
+ for (const [componentType, component2] of this.adds) {
426
+ finalComponents.set(componentType, component2);
427
+ }
428
+ return finalComponents;
429
+ }
430
+ getFinalComponentTypes(existingComponents) {
431
+ const finalComponents = this.applyTo(existingComponents);
432
+ return Array.from(finalComponents.keys()).sort((a, b) => a - b);
433
+ }
434
+ }
435
+
391
436
  // src/command-buffer.ts
392
437
  class CommandBuffer {
393
438
  commands = [];
@@ -555,26 +600,19 @@ class Query {
555
600
 
556
601
  // src/system-scheduler.ts
557
602
  class SystemScheduler {
558
- systems = new Map;
559
- allSystems = new Set;
560
- addSystem(system, dependencies = []) {
561
- this.systems.set(system, dependencies);
562
- this.allSystems.add(system);
563
- for (const dep of dependencies) {
564
- this.allSystems.add(dep);
565
- }
566
- }
567
- removeSystem(system) {
568
- this.systems.delete(system);
569
- this.allSystems.delete(system);
570
- for (const [sys, deps] of this.systems) {
571
- const index = deps.indexOf(system);
572
- if (index !== -1) {
573
- deps.splice(index, 1);
574
- }
603
+ systems = new Set;
604
+ cachedExecutionOrder = null;
605
+ addSystem(system) {
606
+ this.systems.add(system);
607
+ for (const dep of system.dependencies || []) {
608
+ this.systems.add(dep);
575
609
  }
610
+ this.cachedExecutionOrder = null;
576
611
  }
577
612
  getExecutionOrder() {
613
+ if (this.cachedExecutionOrder !== null) {
614
+ return this.cachedExecutionOrder;
615
+ }
578
616
  const result = [];
579
617
  const visited = new Set;
580
618
  const visiting = new Set;
@@ -585,24 +623,24 @@ class SystemScheduler {
585
623
  throw new Error("Circular dependency detected in system scheduling");
586
624
  }
587
625
  visiting.add(system);
588
- const dependencies = this.systems.get(system) || [];
589
- for (const dep of dependencies) {
626
+ for (const dep of system.dependencies || []) {
590
627
  visit(dep);
591
628
  }
592
629
  visiting.delete(system);
593
630
  visited.add(system);
594
631
  result.push(system);
595
632
  };
596
- for (const system of this.allSystems) {
633
+ for (const system of this.systems) {
597
634
  if (!visited.has(system)) {
598
635
  visit(system);
599
636
  }
600
637
  }
638
+ this.cachedExecutionOrder = result;
601
639
  return result;
602
640
  }
603
641
  clear() {
604
642
  this.systems.clear();
605
- this.allSystems.clear();
643
+ this.cachedExecutionOrder = null;
606
644
  }
607
645
  }
608
646
 
@@ -619,6 +657,7 @@ class World {
619
657
  componentToArchetypes = new Map;
620
658
  lifecycleHooks = new Map;
621
659
  entityReverseIndex = new Map;
660
+ exclusiveComponents = new Set;
622
661
  constructor() {
623
662
  this.commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
624
663
  }
@@ -706,19 +745,12 @@ class World {
706
745
  getComponent(entityId, componentType) {
707
746
  const archetype = this.entityToArchetype.get(entityId);
708
747
  if (!archetype) {
709
- if (getIdType(componentType) === "wildcard-relation") {
710
- return [];
711
- } else {
712
- return;
713
- }
748
+ throw new Error(`Entity ${entityId} does not exist`);
714
749
  }
715
750
  return archetype.getComponent(entityId, componentType);
716
751
  }
717
- registerSystem(system, dependencies = []) {
718
- this.systemScheduler.addSystem(system, dependencies);
719
- }
720
- unregisterSystem(system) {
721
- this.systemScheduler.removeSystem(system);
752
+ registerSystem(system) {
753
+ this.systemScheduler.addSystem(system);
722
754
  }
723
755
  registerLifecycleHook(componentType, hook) {
724
756
  if (!this.lifecycleHooks.has(componentType)) {
@@ -735,6 +767,9 @@ class World {
735
767
  }
736
768
  }
737
769
  }
770
+ setExclusive(componentId) {
771
+ this.exclusiveComponents.add(componentId);
772
+ }
738
773
  update(...params) {
739
774
  const systems = this.systemScheduler.getExecutionOrder();
740
775
  for (const system of systems) {
@@ -859,30 +894,35 @@ class World {
859
894
  }
860
895
  }
861
896
  executeEntityCommands(entityId, commands) {
897
+ const changeset = new ComponentChangeset;
862
898
  const hasDestroy = commands.some((cmd) => cmd.type === "destroyEntity");
863
899
  if (hasDestroy) {
864
900
  this._destroyEntity(entityId);
865
- return;
901
+ return changeset;
866
902
  }
867
903
  const currentArchetype = this.entityToArchetype.get(entityId);
868
904
  if (!currentArchetype) {
869
- return;
905
+ return changeset;
870
906
  }
871
907
  const currentComponents = new Map;
872
908
  for (const componentType of currentArchetype.componentTypes) {
873
909
  const data = currentArchetype.getComponent(entityId, componentType);
874
- if (data !== undefined) {
875
- currentComponents.set(componentType, data);
876
- }
910
+ currentComponents.set(componentType, data);
877
911
  }
878
- const adds = new Map;
879
- const removes = new Set;
880
912
  for (const cmd of commands) {
881
913
  switch (cmd.type) {
882
914
  case "addComponent":
883
- if (cmd.componentType && cmd.component !== undefined) {
884
- adds.set(cmd.componentType, cmd.component);
885
- removes.delete(cmd.componentType);
915
+ if (cmd.componentType) {
916
+ const detailedType = getDetailedIdType(cmd.componentType);
917
+ if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && this.exclusiveComponents.has(detailedType.componentId)) {
918
+ for (const componentType of currentArchetype.componentTypes) {
919
+ const componentDetailedType = getDetailedIdType(componentType);
920
+ if ((componentDetailedType.type === "entity-relation" || componentDetailedType.type === "component-relation") && componentDetailedType.componentId === detailedType.componentId) {
921
+ changeset.removeComponent(componentType);
922
+ }
923
+ }
924
+ }
925
+ changeset.addComponent(cmd.componentType, cmd.component);
886
926
  }
887
927
  break;
888
928
  case "removeComponent":
@@ -894,27 +934,19 @@ class World {
894
934
  const componentDetailedType = getDetailedIdType(componentType);
895
935
  if (componentDetailedType.type === "entity-relation" || componentDetailedType.type === "component-relation") {
896
936
  if (componentDetailedType.componentId === baseComponentId) {
897
- removes.add(componentType);
898
- adds.delete(componentType);
937
+ changeset.removeComponent(componentType);
899
938
  }
900
939
  }
901
940
  }
902
941
  } else {
903
- removes.add(cmd.componentType);
904
- adds.delete(cmd.componentType);
942
+ changeset.removeComponent(cmd.componentType);
905
943
  }
906
944
  }
907
945
  break;
908
946
  }
909
947
  }
910
- const finalComponents = new Map(currentComponents);
911
- for (const componentType of removes) {
912
- finalComponents.delete(componentType);
913
- }
914
- for (const [componentType, component2] of adds) {
915
- finalComponents.set(componentType, component2);
916
- }
917
- const finalComponentTypes = Array.from(finalComponents.keys()).sort((a, b) => a - b);
948
+ const finalComponents = changeset.applyTo(currentComponents);
949
+ const finalComponentTypes = changeset.getFinalComponentTypes(currentComponents);
918
950
  const currentComponentTypes = currentArchetype.componentTypes.sort((a, b) => a - b);
919
951
  const needsArchetypeChange = finalComponentTypes.length !== currentComponentTypes.length || !finalComponentTypes.every((type, index) => type === currentComponentTypes[index]);
920
952
  if (needsArchetypeChange) {
@@ -923,11 +955,11 @@ class World {
923
955
  newArchetype.addEntity(entityId, finalComponents);
924
956
  this.entityToArchetype.set(entityId, newArchetype);
925
957
  } else {
926
- for (const [componentType, component2] of adds) {
958
+ for (const [componentType, component2] of changeset.adds) {
927
959
  currentArchetype.setComponent(entityId, componentType, component2);
928
960
  }
929
961
  }
930
- for (const componentType of removes) {
962
+ for (const componentType of changeset.removes) {
931
963
  const detailedType = getDetailedIdType(componentType);
932
964
  if (detailedType.type === "entity-relation") {
933
965
  const targetEntityId = detailedType.targetId;
@@ -936,7 +968,7 @@ class World {
936
968
  this.removeComponentReference(entityId, componentType, componentType);
937
969
  }
938
970
  }
939
- for (const [componentType, component2] of adds) {
971
+ for (const [componentType, component2] of changeset.adds) {
940
972
  const detailedType = getDetailedIdType(componentType);
941
973
  if (detailedType.type === "entity-relation") {
942
974
  const targetEntityId = detailedType.targetId;
@@ -945,7 +977,8 @@ class World {
945
977
  this.addComponentReference(entityId, componentType, componentType);
946
978
  }
947
979
  }
948
- this.executeComponentLifecycleHooks(entityId, adds, removes);
980
+ this.executeComponentLifecycleHooks(entityId, changeset.adds, changeset.removes);
981
+ return changeset;
949
982
  }
950
983
  getOrCreateArchetype(componentTypes) {
951
984
  const sortedTypes = [...componentTypes].sort((a, b) => a - b);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ecs",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -4,18 +4,12 @@ import type { System } from "./system";
4
4
  */
5
5
  export declare class SystemScheduler<ExtraParams extends any[] = [deltaTime: number]> {
6
6
  private systems;
7
- private allSystems;
7
+ private cachedExecutionOrder;
8
8
  /**
9
9
  * Add a system with optional dependencies
10
10
  * @param system The system to add
11
- * @param dependencies Systems that this system depends on (must run before this system)
12
11
  */
13
- addSystem(system: System<ExtraParams>, dependencies?: System<ExtraParams>[]): void;
14
- /**
15
- * Remove a system
16
- * @param system The system to remove
17
- */
18
- removeSystem(system: System<ExtraParams>): void;
12
+ addSystem(system: System<ExtraParams>): void;
19
13
  /**
20
14
  * Get the execution order of systems based on dependencies
21
15
  * Uses topological sort
package/system.d.ts CHANGED
@@ -7,4 +7,8 @@ export interface System<ExtraParams extends any[] = [deltaTime: number]> {
7
7
  * Update the system
8
8
  */
9
9
  update(world: World<ExtraParams>, ...params: ExtraParams): void;
10
+ /**
11
+ * Dependencies of this system (systems that must run before this one)
12
+ */
13
+ readonly dependencies?: readonly System<ExtraParams>[];
10
14
  }
package/world.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Archetype } from "./archetype";
2
+ import { ComponentChangeset } from "./changeset";
2
3
  import { type Command } from "./command-buffer";
3
4
  import type { EntityId, WildcardRelationId } from "./entity";
4
5
  import { Query } from "./query";
@@ -29,6 +30,11 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
29
30
  * This includes both relation components and direct usage of entities as component types
30
31
  */
31
32
  private entityReverseIndex;
33
+ /**
34
+ * Set of component IDs that are marked as exclusive relations
35
+ * For exclusive relations, an entity can have at most one relation per base component
36
+ */
37
+ private exclusiveComponents;
32
38
  constructor();
33
39
  /**
34
40
  * Generate a hash key for component types array
@@ -49,7 +55,8 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
49
55
  /**
50
56
  * Add a component to an entity (deferred)
51
57
  */
52
- addComponent<T>(entityId: EntityId, componentType: EntityId<T>, component: T): void;
58
+ addComponent(entityId: EntityId, componentType: EntityId<void>): void;
59
+ addComponent<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
53
60
  /**
54
61
  * Remove a component from an entity (deferred)
55
62
  */
@@ -65,19 +72,15 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
65
72
  /**
66
73
  * Get wildcard relations from an entity
67
74
  */
68
- getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][] | undefined;
75
+ getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][];
69
76
  /**
70
77
  * Get a component from an entity
71
78
  */
72
- getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T | undefined;
79
+ getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T;
73
80
  /**
74
81
  * Register a system with optional dependencies
75
82
  */
76
- registerSystem(system: System<ExtraParams>, dependencies?: System<ExtraParams>[]): void;
77
- /**
78
- * Unregister a system
79
- */
80
- unregisterSystem(system: System<ExtraParams>): void;
83
+ registerSystem(system: System<ExtraParams>): void;
81
84
  /**
82
85
  * Register a lifecycle hook for component or wildcard relation events
83
86
  */
@@ -86,6 +89,11 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
86
89
  * Unregister a lifecycle hook for component or wildcard relation events
87
90
  */
88
91
  unregisterLifecycleHook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
92
+ /**
93
+ * Mark a component as exclusive relation
94
+ * For exclusive relations, an entity can have at most one relation per base component
95
+ */
96
+ setExclusive(componentId: EntityId): void;
89
97
  /**
90
98
  * Update the world (run all systems in dependency order)
91
99
  */
@@ -126,7 +134,7 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
126
134
  /**
127
135
  * @internal Execute commands for a single entity (for internal use by CommandBuffer)
128
136
  */
129
- executeEntityCommands(entityId: EntityId, commands: Command[]): void;
137
+ executeEntityCommands(entityId: EntityId, commands: Command[]): ComponentChangeset;
130
138
  /**
131
139
  * Get or create an archetype for the given component types
132
140
  */