@codehz/ecs 0.6.10 → 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
@@ -234,6 +234,7 @@ interface LifecycleHookEntry {
234
234
  componentTypes: readonly ComponentType<any>[];
235
235
  requiredComponents: EntityId<any>[];
236
236
  optionalComponents: EntityId<any>[];
237
+ filter: QueryFilter;
237
238
  hook: LifecycleHook<any>;
238
239
  }
239
240
  //#endregion
@@ -662,12 +663,14 @@ declare class World {
662
663
  * @overload hook<const T extends readonly ComponentType<any>[]>(
663
664
  * componentTypes: T,
664
665
  * hook: LifecycleHook<T> | LifecycleCallback<T>,
666
+ * filter?: QueryFilter,
665
667
  * ): () => void
666
668
  * Registers a hook for multiple component types.
667
- * The hook is triggered when all required components change together.
669
+ * The hook is triggered when entities enter/exit the matching set.
668
670
  *
669
671
  * @param componentTypesOrSingle - A single component type or an array of component types
670
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
671
674
  * @returns A function that unsubscribes the hook when called
672
675
  *
673
676
  * @throws {Error} If no required components are specified in array overload
@@ -685,9 +688,18 @@ declare class World {
685
688
  * const unsubscribe = world.hook([Position], (event, entityId, position) => {
686
689
  * if (event === "init") console.log("Initialized");
687
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
+ * );
688
700
  */
689
701
  hook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T> | LegacyLifecycleCallback<T>): () => void;
690
- 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;
691
703
  /** @deprecated use the unsubscribe function returned by hook() instead */
692
704
  unhook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T>): void;
693
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.10",
3
+ "version": "0.6.11",
4
4
  "repository": {
5
5
  "url": "https://github.com/codehz/ecs"
6
6
  },
package/world.mjs CHANGED
@@ -1686,12 +1686,14 @@ function triggerMultiComponentHooks(ctx, entityId, addedComponents, removedCompo
1686
1686
  const anyRequiredAdded = requiredComponents.some((c) => anyComponentMatches(addedComponents, c));
1687
1687
  const anyOptionalAdded = optionalComponents.some((c) => anyComponentMatches(addedComponents, c));
1688
1688
  const anyOptionalRemoved = optionalComponents.some((c) => anyComponentMatches(removedComponents, c));
1689
- if ((anyRequiredAdded || anyOptionalAdded || anyOptionalRemoved) && entityHasAllComponents(ctx, entityId, requiredComponents)) hook.on_set(entityId, ...collectMultiHookComponents(ctx, entityId, componentTypes));
1689
+ if (!oldArchetype.matchingMultiHooks.has(entry) || (anyRequiredAdded || anyOptionalAdded || anyOptionalRemoved) && entityHasAllComponents(ctx, entityId, requiredComponents)) hook.on_set(entityId, ...collectMultiHookComponents(ctx, entityId, componentTypes));
1690
1690
  }
1691
- if (removedComponents.size > 0) for (const entry of oldArchetype.matchingMultiHooks) {
1691
+ for (const entry of oldArchetype.matchingMultiHooks) {
1692
1692
  const { hook, requiredComponents, componentTypes } = entry;
1693
1693
  if (!hook.on_remove) continue;
1694
- if (requiredComponents.some((c) => anyComponentMatches(removedComponents, c)) && entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents) && !entityHasAllComponents(ctx, entityId, requiredComponents)) hook.on_remove(entityId, ...collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, removedComponents));
1694
+ const lostRequiredMatch = requiredComponents.some((c) => anyComponentMatches(removedComponents, c)) && entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents) && !entityHasAllComponents(ctx, entityId, requiredComponents);
1695
+ const exitedMatchingSet = !newArchetype.matchingMultiHooks.has(entry);
1696
+ if (lostRequiredMatch || exitedMatchingSet) hook.on_remove(entityId, ...collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, removedComponents));
1695
1697
  }
1696
1698
  }
1697
1699
  function entityHasAllComponents(ctx, entityId, requiredComponents) {
@@ -2190,7 +2192,7 @@ var World = class {
2190
2192
  }
2191
2193
  return archetype.getOptional(entityId, componentType);
2192
2194
  }
2193
- hook(componentTypesOrSingle, hook) {
2195
+ hook(componentTypesOrSingle, hook, filter) {
2194
2196
  if (typeof hook === "function") if (Array.isArray(componentTypesOrSingle)) {
2195
2197
  const callback = hook;
2196
2198
  hook = {
@@ -2217,14 +2219,15 @@ var World = class {
2217
2219
  componentTypes,
2218
2220
  requiredComponents,
2219
2221
  optionalComponents,
2222
+ filter: filter || {},
2220
2223
  hook
2221
2224
  };
2222
2225
  this.hooks.add(entry);
2223
2226
  for (const archetype of this.archetypes) if (this.archetypeMatchesHook(archetype, entry)) archetype.matchingMultiHooks.add(entry);
2224
2227
  const multiHook = hook;
2225
- if (multiHook.on_init !== void 0) {
2226
- const matchingArchetypes = this.getMatchingArchetypes(requiredComponents);
2227
- for (const archetype of matchingArchetypes) for (const entityId of archetype.getEntities()) {
2228
+ if (multiHook.on_init !== void 0) for (const archetype of this.archetypes) {
2229
+ if (!this.archetypeMatchesHook(archetype, entry)) continue;
2230
+ for (const entityId of archetype.getEntities()) {
2228
2231
  const components = collectMultiHookComponents(this.createHooksContext(), entityId, componentTypes);
2229
2232
  multiHook.on_init(entityId, ...components);
2230
2233
  }
@@ -2567,7 +2570,7 @@ var World = class {
2567
2570
  return componentId !== void 0 && archetype.hasRelationWithComponentId(componentId);
2568
2571
  }
2569
2572
  return archetype.componentTypeSet.has(c) || isDontFragmentRelation(c);
2570
- });
2573
+ }) && matchesFilter(archetype, entry.filter);
2571
2574
  }
2572
2575
  archetypeReferencesEntity(archetype, entityId) {
2573
2576
  return archetype.componentTypes.some((ct) => ct === entityId || isEntityRelation(ct) && getTargetIdFromRelationId(ct) === entityId);