Files
esengine/docs/query-system-usage.md
YHH 6ea366cfed 优化matcher内部实现改为querysystem
完善type类型
更新文档
2025-07-31 11:56:04 +08:00

20 KiB
Raw Blame History

QuerySystem 使用指南

QuerySystem 是 ECS Framework 中的高性能实体查询系统,支持多级索引、智能缓存和类型安全的查询操作。

基本用法

1. 获取查询系统

import { Scene, Entity } from '@esengine/ecs-framework';

// 创建场景,查询系统会自动创建
const scene = new Scene();
const querySystem = scene.querySystem;

// 或者从Core获取当前场景的查询系统
import { Core } from '@esengine/ecs-framework';
const currentQuerySystem = Core.scene?.querySystem;

2. 基本查询操作

// 查询包含所有指定组件的实体
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
console.log(`找到 ${result.count} 个实体`);

// 查询包含任意指定组件的实体
const anyResult = querySystem.queryAny(HealthComponent, ManaComponent);

// 查询不包含指定组件的实体
const noneResult = querySystem.queryNone(DeadComponent);

3. 类型安全查询

// 类型安全的查询,返回实体和对应的组件
const typedResult = querySystem.queryAll(PositionComponent, VelocityComponent);
for (let i = 0; i < typedResult.entities.length; i++) {
    const entity = typedResult.entities[i];
    const [position, velocity] = typedResult.components[i];
    // position 和 velocity 都是类型安全的
}

// 查询单个组件类型
const healthResult = querySystem.queryAll(HealthComponent);
for (const entity of healthResult.entities) {
    const health = entity.getComponent(HealthComponent);
    if (health) {
        console.log(`实体 ${entity.name} 的生命值: ${health.value}`);
    }
}

// 查询两个组件类型
const movableResult = querySystem.queryAll(PositionComponent, VelocityComponent);
for (const entity of movableResult.entities) {
    const position = entity.getComponent(PositionComponent);
    const velocity = entity.getComponent(VelocityComponent);
    if (position && velocity) {
        // 更新位置
        position.x += velocity.x;
        position.y += velocity.y;
    }
}

4. 使用查询构建器

// QuerySystem不提供查询构建器请使用Matcher进行复杂查询
// 推荐使用Matcher配合EntitySystem

import { Matcher } from '@esengine/ecs-framework';

// 创建复杂查询条件
const visibleMatcher = Matcher.all(PositionComponent, RenderComponent)
    .none(HiddenComponent);

// 通过QuerySystem执行查询
const visibleEntities = querySystem.query(visibleMatcher.getCondition());

// 过滤和排序需要手动处理
const sortedEntities = visibleEntities.entities
    .filter(entity => entity.name.startsWith('Boss'))
    .sort((a, b) => a.id - b.id)
    .slice(0, 10); // 限制数量

// 迭代结果
sortedEntities.forEach((entity, index) => {
    console.log(`敌人 ${index}: ${entity.name}`);
});

5. 高级查询功能

// QuerySystem主要提供基础查询方法
// 复杂查询推荐使用Matcher和EntitySystem

// 基本查询
const positionResult = querySystem.queryAll(PositionComponent, VelocityComponent);
const healthResult = querySystem.queryAll(HealthComponent);
const manaResult = querySystem.queryAll(ManaComponent);

// 排除死亡实体的移动实体
const aliveMovingEntities = positionResult.entities.filter(entity => 
    !entity.hasComponent(DeadComponent)
);

// 如果需要复杂查询推荐在EntitySystem中使用Matcher
class ComplexQuerySystem extends EntitySystem {
    constructor() {
        super(Matcher.all(PositionComponent, VelocityComponent).none(DeadComponent));
    }
    
    protected process(entities: Entity[]): void {
        // entities已经是过滤后的结果
        for (const entity of entities) {
            // 处理逻辑
        }
    }
}

场景级别的实体查询

除了使用QuerySystem您还可以直接使用Scene提供的便捷查询方法

基本场景查询

// 按名称查找实体
const player = scene.findEntity("Player");
const playerAlt = scene.getEntityByName("Player"); // 别名方法

// 按ID查找实体
const entity = scene.findEntityById(123);

// 按标签查找实体
const enemies = scene.findEntitiesByTag(2);
const enemiesAlt = scene.getEntitiesByTag(2); // 别名方法

// 获取所有实体
const allEntities = scene.entities.buffer;

性能优化

1. 缓存管理

// 设置缓存配置
querySystem.setCacheConfig(200, 2000); // 最大200个缓存项2秒超时

// 清空缓存
querySystem.clearCache();

// 预热常用查询(使用基础查询方法)
querySystem.queryAll(PositionComponent); // 预热Position查询
querySystem.queryAll(VelocityComponent); // 预热Velocity查询

2. 索引优化

// 获取性能统计
const stats = querySystem.getStats();
console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`);
console.log(`实体数量: ${stats.entityCount}`);

// 获取详细性能报告
const report = querySystem.getPerformanceReport();
console.log(report);

3. 查询监听和快照

// QuerySystem主要用于基础查询高级功能请使用EntitySystem和事件系统

// 如需监听实体变化,使用事件系统
scene.entityManager.eventBus.on('entity:added', (entity) => {
    if (entity.hasComponent(EnemyComponent)) {
        console.log('新增敌人实体');
    }
});

scene.entityManager.eventBus.on('entity:removed', (entity) => {
    if (entity.hasComponent(EnemyComponent)) {
        console.log('移除敌人实体');
    }
});

// 手动创建快照进行比较
const snapshot1 = querySystem.queryAll(PlayerComponent).entities.map(e => e.id);
// 稍后
const snapshot2 = querySystem.queryAll(PlayerComponent).entities.map(e => e.id);

// 比较快照
const added = snapshot2.filter(id => !snapshot1.includes(id));
const removed = snapshot1.filter(id => !snapshot2.includes(id));
console.log(`新增: ${added.length}, 移除: ${removed.length}`);

使用Matcher进行高级查询

Matcher是一个优雅的查询封装器提供流畅的API和强大的缓存机制。

基本Matcher用法

import { Matcher } from '@esengine/ecs-framework';

// 创建Matcher查询条件
const movingMatcher = Matcher.all(PositionComponent, VelocityComponent);
// 在QuerySystem中需要使用基础查询方法
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent).entities;

// 复合查询需要使用EntitySystem或手动过滤
const aliveEnemiesMatcher = Matcher.all(EnemyComponent, HealthComponent)
    .any(WeaponComponent, MagicComponent)
    .none(DeadComponent, StunnedComponent);

// 在EntitySystem中使用
class AliveEnemySystem extends EntitySystem {
    constructor() {
        super(aliveEnemiesMatcher);
    }
    
    protected process(entities: Entity[]): void {
        // entities已经是匹配的实体
        for (const entity of entities) {
            // 处理存活的敌人
        }
    }
}

// 或者手动过滤
const enemyResult = scene.querySystem.queryAll(EnemyComponent, HealthComponent);
const aliveEnemies = enemyResult.entities.filter(entity => {
    const hasWeaponOrMagic = entity.hasComponent(WeaponComponent) || entity.hasComponent(MagicComponent);
    const isAlive = !entity.hasComponent(DeadComponent) && !entity.hasComponent(StunnedComponent);
    return hasWeaponOrMagic && isAlive;
});

// 单个实体检查
const playerResult = scene.querySystem.queryAll(PlayerComponent);
if (playerResult.entities.includes(someEntity)) {
    console.log('这是玩家实体');
}

// 统计信息
console.log(`玩家数量: ${playerResult.count}`);
console.log(`是否有玩家: ${playerResult.count > 0}`);

系统中使用Matcher

import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';

class MovementSystem extends EntitySystem {
    constructor() {
        // 在构造函数中直接传入Matcher
        super(Matcher.all(PositionComponent, VelocityComponent));
    }
    
    protected process(entities: Entity[]): void {
        // entities参数已经是系统自动过滤后的实体
        for (const entity of entities) {
            const position = entity.getComponent(PositionComponent)!;
            const velocity = entity.getComponent(VelocityComponent)!;
            
            // 更新位置
            position.x += velocity.x * Time.deltaTime;
            position.y += velocity.y * Time.deltaTime;

            // 边界检查
            if (position.x < 0 || position.x > 800) {
                velocity.x = -velocity.x;
            }
            if (position.y < 0 || position.y > 600) {
                velocity.y = -velocity.y;
            }
        }
    }
}

碰撞检测示例

class CollisionSystem extends EntitySystem {
    constructor() {
        // 在构造函数中传入Matcher
        super(Matcher.all(PositionComponent, ColliderComponent));
    }
    
    protected process(entities: Entity[]): void {
        // entities已经是匹配的实体
        const collidableEntities = entities;

        // 检测碰撞
        for (let i = 0; i < collidableEntities.length; i++) {
            for (let j = i + 1; j < collidableEntities.length; j++) {
                const entityA = collidableEntities[i];
                const entityB = collidableEntities[j];

                if (this.checkCollision(entityA, entityB)) {
                    this.handleCollision(entityA, entityB);
                }
            }
        }
    }

    private checkCollision(entityA: Entity, entityB: Entity): boolean {
        const posA = entityA.getComponent(PositionComponent)!;
        const posB = entityB.getComponent(PositionComponent)!;
        const distance = Math.sqrt(
            Math.pow(posA.x - posB.x, 2) + Math.pow(posA.y - posB.y, 2)
        );
        return distance < 50;
    }

    private handleCollision(entityA: Entity, entityB: Entity): void {
        console.log(`碰撞检测: ${entityA.name}${entityB.name}`);
    }
}

生命值管理示例

class HealthSystem extends EntitySystem {
    constructor() {
        // 在构造函数中传入Matcher
        super(Matcher.all(HealthComponent));
    }
    
    protected process(entities: Entity[]): void {
        // entities已经是匹配的实体
        const healthEntities = entities;
        const deadEntities: Entity[] = [];

        for (const entity of healthEntities) {
            const health = entity.getComponent(HealthComponent)!;
            
            // 检查死亡
            if (health.currentHealth <= 0) {
                deadEntities.push(entity);
            }
        }

        // 移除死亡实体
        deadEntities.forEach(entity => {
            console.log(`实体 ${entity.name} 已死亡`);
            entity.destroy();
        });
    }
}

Matcher完整API参考

静态创建方法

// 基础静态方法
const allMatcher = Matcher.all(PositionComponent, VelocityComponent);     // 必须包含所有组件
const anyMatcher = Matcher.any(WeaponComponent, MagicComponent);          // 必须包含任意一个组件  
const noneMatcher = Matcher.none(DeadComponent, DisabledComponent);       // 不能包含任何指定组件

// 特殊查询静态方法
const tagMatcher = Matcher.byTag(1);                                      // 按标签查询
const nameMatcher = Matcher.byName("Player");                             // 按名称查询
const componentMatcher = Matcher.byComponent(HealthComponent);            // 单组件查询

// 构建器方法
const complexMatcher = Matcher.complex();                                 // 创建复杂查询构建器
const emptyMatcher = Matcher.empty();                                     // 创建空匹配器

实例方法 - 条件构建

// 基础条件方法
const matcher = Matcher.empty()
    .all(PositionComponent, VelocityComponent)      // 必须包含所有组件
    .any(WeaponComponent, MagicComponent)           // 必须包含任意一个组件
    .none(DeadComponent, StunnedComponent);         // 不能包含任何指定组件

// 别名方法提供更语义化的API
const semanticMatcher = Matcher.empty()
    .all(PositionComponent)
    .exclude(DeadComponent)                         // exclude() 等同于 none()
    .one(WeaponComponent, MagicComponent);          // one() 等同于 any()

// 特殊条件方法
const advancedMatcher = Matcher.empty()
    .all(EnemyComponent)
    .withTag(2)                                     // 指定标签
    .withName("Boss")                               // 指定名称
    .withComponent(HealthComponent);                // 单组件条件

条件移除方法

// 移除特殊条件
const matcher = Matcher.byTag(1)
    .withName("Player")
    .withComponent(HealthComponent);

// 移除各种条件
matcher.withoutTag();          // 移除标签条件
matcher.withoutName();         // 移除名称条件
matcher.withoutComponent();    // 移除单组件条件

实用工具方法

// 检查和调试
const matcher = Matcher.all(PositionComponent, VelocityComponent)
    .none(DeadComponent);

// 检查是否为空条件
if (matcher.isEmpty()) {
    console.log('匹配器没有设置任何条件');
}

// 获取条件信息(只读)
const condition = matcher.getCondition();
console.log('必须组件:', condition.all.map(c => c.name));
console.log('任选组件:', condition.any.map(c => c.name));
console.log('排除组件:', condition.none.map(c => c.name));
console.log('标签:', condition.tag);
console.log('名称:', condition.name);
console.log('单组件:', condition.component?.name);

// 调试输出
console.log('匹配器描述:', matcher.toString());
// 输出: "Matcher[all(PositionComponent, VelocityComponent) & none(DeadComponent)]"

克隆和重置

// 克隆匹配器
const baseMatcher = Matcher.all(PositionComponent);
const livingMatcher = baseMatcher.clone().all(HealthComponent).none(DeadComponent);
const deadMatcher = baseMatcher.clone().all(DeadComponent);

// 重置匹配器
const reusableMatcher = Matcher.all(PositionComponent);
console.log(reusableMatcher.toString()); // "Matcher[all(PositionComponent)]"

reusableMatcher.reset();                 // 清空所有条件
console.log(reusableMatcher.toString()); // "Matcher[]"

reusableMatcher.all(PlayerComponent);    // 重新设置条件
console.log(reusableMatcher.toString()); // "Matcher[all(PlayerComponent)]"

链式调用示例

// 复杂的链式调用
const complexMatcher = Matcher.empty()
    .all(PositionComponent, RenderComponent)        // 必须有位置和渲染组件  
    .any(PlayerComponent, NPCComponent)             // 必须是玩家或NPC
    .none(DeadComponent, HiddenComponent)           // 不能死亡或隐藏
    .withTag(1)                                     // 标签为1
    .exclude(DisabledComponent);                    // 不能被禁用

// 在EntitySystem中使用
class VisibleCharacterSystem extends EntitySystem {
    constructor() {
        super(complexMatcher);
    }
    
    protected process(entities: Entity[]): void {
        // entities已经是符合所有条件的实体
        for (const entity of entities) {
            // 处理可见角色的逻辑
        }
    }
}

最佳实践

1. Matcher使用建议

// 推荐的用法:
const matcher = Matcher.all(Position, Velocity);

// 在系统中使用
class MySystem extends EntitySystem {
    constructor() {
        super(Matcher.all(RequiredComponent));
    }
    
    protected process(entities: Entity[]): void {
        // entities已经是系统自动过滤的结果
        for (const entity of entities) {
            // 处理逻辑...
        }
    }
}

// 避免在process方法中重复查询
class InefficientSystem extends EntitySystem {
    protected process(entities: Entity[]): void {
        // 不必要的额外查询,性能差
        const condition = Matcher.all(RequiredComponent).getCondition();
        const result = this.scene.querySystem.query(condition);
    }
}

2. Matcher API最佳实践

选择合适的创建方式

// ✅ 推荐:单一条件使用静态方法
const movingEntities = Matcher.all(PositionComponent, VelocityComponent);
const playerEntities = Matcher.byTag(PLAYER_TAG);
const specificEntity = Matcher.byName("Boss");

// ✅ 推荐:复杂条件使用链式调用
const complexMatcher = Matcher.empty()
    .all(PositionComponent, HealthComponent)
    .any(WeaponComponent, MagicComponent)
    .none(DeadComponent);

// ❌ 不推荐:简单条件使用复杂语法
const simpleButBad = Matcher.empty().all(PositionComponent);
// 应该用: Matcher.all(PositionComponent)

合理使用别名方法

// 使用语义化的别名提高可读性
const combatUnits = Matcher.all(PositionComponent, HealthComponent)
    .one(WeaponComponent, MagicComponent)           // one() 比 any() 更语义化
    .exclude(DeadComponent, PacifistComponent);     // exclude() 比 none() 更直观

合理的克隆和重用

// ✅ 推荐:基础匹配器重用
const livingEntityMatcher = Matcher.all(HealthComponent).none(DeadComponent);
const livingPlayerMatcher = livingEntityMatcher.clone().all(PlayerComponent);
const livingEnemyMatcher = livingEntityMatcher.clone().all(EnemyComponent);

// ✅ 推荐:重置匹配器重用
const reusableMatcher = Matcher.empty();

// 用于玩家系统
reusableMatcher.reset().all(PlayerComponent);
const playerSystem = new PlayerSystem(reusableMatcher.clone());

// 用于敌人系统  
reusableMatcher.reset().all(EnemyComponent);
const enemySystem = new EnemySystem(reusableMatcher.clone());

调试和维护

// 在开发阶段添加调试信息
const debugMatcher = Matcher.all(ComplexComponent)
    .any(VariantA, VariantB)
    .none(DisabledComponent);

if (DEBUG_MODE) {
    console.log('系统匹配条件:', debugMatcher.toString());
    const condition = debugMatcher.getCondition();
    console.log('预期匹配实体数:', 
        scene.querySystem.queryAll(...condition.all).count);
}

3. 查询优化

  • 使用Matcher封装复杂查询:提供更好的可读性和缓存
  • 避免频繁创建查询:在系统初始化时创建,重复使用
  • 合理使用any()和none()条件:减少不必要的实体遍历
  • 利用Matcher的缓存机制:自动优化重复查询性能
  • 使用克隆方法复用基础条件:避免重复定义相似的匹配条件
  • 选择合适的静态方法:单一条件优先使用对应的静态方法

3. 性能监控

  • 定期检查查询性能报告
  • 监控缓存命中率
  • 优化频繁使用的查询
  • 使用性能测试验证优化效果

4. 内存管理

  • 及时清理不需要的查询监听器
  • 合理设置缓存大小
  • 避免创建过多的查询快照
  • 适当使用Matcher的clone()和reset()方法

5. 代码组织

  • 系统级别的Matcher在系统中创建和管理Matcher
  • 查询逻辑封装:将复杂查询封装到专门的方法中
  • 条件复用使用clone()方法复用基础查询条件
  • 清晰的命名给Matcher变量使用描述性的名称

6. 迁移指南

系统中Matcher的推荐用法

// 在EntitySystem中使用Matcher
class MySystem extends EntitySystem {
    constructor() {
        super(Matcher.all(ComponentA, ComponentB).none(ComponentC));
    }
    
    protected process(entities: Entity[]): void {
        // entities已经是系统自动过滤的结果
        for (const entity of entities) {
            // 处理逻辑
        }
    }
}

使用最佳实践

  • 在EntitySystem构造函数中传入Matcher
  • 使用none()来排除组件
  • 使用any()来匹配任意组件
  • 直接使用EntitySystem的entities参数避免额外查询
  • 定期检查查询性能和缓存命中率