@codehz/ecs 0.6.9 → 0.6.11

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
@@ -124,6 +124,29 @@ const unhook = world.hook([PositionId, { optional: VelocityId }], {
124
124
  });
125
125
  ```
126
126
 
127
+ 多组件 `hook()` 还支持第三个可选参数 `filter`(与 `createQuery()` 的过滤语义一致),可用于排除带有某些负面组件的实体:
128
+
129
+ ```typescript
130
+ const DisabledId = component<void>();
131
+
132
+ const unhook = world.hook(
133
+ [PositionId, VelocityId],
134
+ {
135
+ on_set: (entityId, position, velocity) => {
136
+ // 实体进入匹配集合时触发(包括移除 Disabled 后重新进入)
137
+ console.log("active", entityId, position, velocity);
138
+ },
139
+ on_remove: (entityId, position, velocity) => {
140
+ // 实体退出匹配集合时触发(包括新增 Disabled 后退出)
141
+ console.log("inactive", entityId, position, velocity);
142
+ },
143
+ },
144
+ {
145
+ negativeComponentTypes: [DisabledId],
146
+ },
147
+ );
148
+ ```
149
+
127
150
  ### 通配符关系钩子
128
151
 
129
152
  ECS 支持通配符关系钩子,可以监听特定组件的所有关系变化:
@@ -226,7 +249,7 @@ bun run examples/simple/demo.ts
226
249
  - `delete(entity)`: 销毁实体及其所有组件
227
250
  - `query(componentIds)`: 快速查询具有指定组件的实体
228
251
  - `createQuery(componentIds)`: 创建可重用的查询对象
229
- - `hook(componentIds, hook)`: 注册生命周期钩子,返回卸载函数
252
+ - `hook(componentIds, hook, filter?)`: 注册生命周期钩子,返回卸载函数(数组形式支持可选 filter)
230
253
  - `serialize()`: 序列化世界状态为快照对象
231
254
  - `sync()`: 执行所有延迟命令
232
255
 
package/builder.d.mts CHANGED
@@ -64,10 +64,11 @@ declare function decodeRelationId(relationId: RelationId<any>): {
64
64
  };
65
65
  //#endregion
66
66
  //#region src/core/component-registry.d.ts
67
+ type ComponentMerge<T = any> = (prev: T, next: T) => T;
67
68
  /**
68
69
  * Component options that define intrinsic properties
69
70
  */
70
- interface ComponentOptions {
71
+ interface ComponentOptions<T = any> {
71
72
  /**
72
73
  * Optional name for the component (for serialization/debugging)
73
74
  */
@@ -93,6 +94,10 @@ interface ComponentOptions {
93
94
  * Inspired by Flecs' DontFragment trait.
94
95
  */
95
96
  dontFragment?: boolean;
97
+ /**
98
+ * Custom merge behavior for repeated set() of the same componentType in a single sync batch.
99
+ */
100
+ merge?: ComponentMerge<T>;
96
101
  }
97
102
  /**
98
103
  * Allocate a new component ID from the global allocator.
@@ -108,7 +113,7 @@ interface ComponentOptions {
108
113
  * // With name and options
109
114
  * const ChildOf = component({ name: "ChildOf", exclusive: true });
110
115
  */
111
- declare function component<T = void>(nameOrOptions?: string | ComponentOptions): ComponentId<T>;
116
+ declare function component<T = void>(nameOrOptions?: string | ComponentOptions<T>): ComponentId<T>;
112
117
  /**
113
118
  * Get a component ID by its registered name
114
119
  * @param name The component name
@@ -229,6 +234,7 @@ interface LifecycleHookEntry {
229
234
  componentTypes: readonly ComponentType<any>[];
230
235
  requiredComponents: EntityId<any>[];
231
236
  optionalComponents: EntityId<any>[];
237
+ filter: QueryFilter;
232
238
  hook: LifecycleHook<any>;
233
239
  }
234
240
  //#endregion
@@ -286,6 +292,7 @@ declare class Archetype {
286
292
  addEntity(entityId: EntityId, componentData: Map<EntityId<any>, any>): void;
287
293
  private addDontFragmentRelations;
288
294
  getEntity(entityId: EntityId): Map<EntityId<any>, any> | undefined;
295
+ getEntityDontFragmentRelations(entityId: EntityId): Map<EntityId<any>, any> | undefined;
289
296
  dump(): Array<{
290
297
  entity: EntityId;
291
298
  components: Map<EntityId<any>, any>;
@@ -457,9 +464,14 @@ declare class World {
457
464
  private relationEntityIdsByTarget;
458
465
  private queries;
459
466
  private queryCache;
460
- private commandBuffer;
461
467
  private legacyHooks;
462
468
  private hooks;
469
+ private commandBuffer;
470
+ private readonly _changeset;
471
+ /** Cached command processor context to avoid per-entity object allocation */
472
+ private readonly _commandCtx;
473
+ /** Cached hooks context to avoid per-entity object allocation */
474
+ private readonly _hooksCtx;
463
475
  constructor(snapshot?: SerializedWorld);
464
476
  private deserializeSnapshot;
465
477
  private createArchetypeSignature;
@@ -495,6 +507,12 @@ declare class World {
495
507
  * }
496
508
  */
497
509
  exists(entityId: EntityId): boolean;
510
+ private assertEntityExists;
511
+ private assertComponentTypeValid;
512
+ private assertSetComponentTypeValid;
513
+ private resolveSetOperation;
514
+ private resolveRemoveOperation;
515
+ private getComponentEntityWildcardRelations;
498
516
  /**
499
517
  * Adds or updates a component on an entity (or marks void component as present).
500
518
  * The change is buffered and takes effect after calling `world.sync()`.
@@ -645,12 +663,14 @@ declare class World {
645
663
  * @overload hook<const T extends readonly ComponentType<any>[]>(
646
664
  * componentTypes: T,
647
665
  * hook: LifecycleHook<T> | LifecycleCallback<T>,
666
+ * filter?: QueryFilter,
648
667
  * ): () => void
649
668
  * Registers a hook for multiple component types.
650
- * The hook is triggered when all required components change together.
669
+ * The hook is triggered when entities enter/exit the matching set.
651
670
  *
652
671
  * @param componentTypesOrSingle - A single component type or an array of component types
653
672
  * @param hook - Either a hook object with on_init/on_set/on_remove handlers, or a callback function
673
+ * @param filter - Optional filter, only applied to array overload
654
674
  * @returns A function that unsubscribes the hook when called
655
675
  *
656
676
  * @throws {Error} If no required components are specified in array overload
@@ -668,9 +688,18 @@ declare class World {
668
688
  * const unsubscribe = world.hook([Position], (event, entityId, position) => {
669
689
  * if (event === "init") console.log("Initialized");
670
690
  * });
691
+ *
692
+ * // With filter
693
+ * const unsubscribe2 = world.hook(
694
+ * [Position, Velocity],
695
+ * {
696
+ * on_set: (entityId, position, velocity) => console.log(entityId, position, velocity),
697
+ * },
698
+ * { negativeComponentTypes: [Disabled] },
699
+ * );
671
700
  */
672
701
  hook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T> | LegacyLifecycleCallback<T>): () => void;
673
- hook<const T extends readonly ComponentType<any>[]>(componentTypes: T, hook: LifecycleHook<T> | LifecycleCallback<T>): () => void;
702
+ hook<const T extends readonly ComponentType<any>[]>(componentTypes: T, hook: LifecycleHook<T> | LifecycleCallback<T>, filter?: QueryFilter): () => void;
674
703
  /** @deprecated use the unsubscribe function returned by hook() instead */
675
704
  unhook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T>): void;
676
705
  /** @deprecated use the unsubscribe function returned by hook() instead */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ecs",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
4
4
  "repository": {
5
5
  "url": "https://github.com/codehz/ecs"
6
6
  },