修复了QuerySystem在销毁实体时的内存泄漏问题
实现了完整的onAdded/onRemoved回调系统 修复了override修饰符和类型兼容性问题
This commit is contained in:
@@ -1,121 +0,0 @@
|
|||||||
/**
|
|
||||||
* 简化后的Matcher使用示例
|
|
||||||
* 展示框架自动处理QuerySystem的优雅设计
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Scene } from '../src/ECS/Scene';
|
|
||||||
import { Component } from '../src/ECS/Component';
|
|
||||||
import { Matcher } from '../src/ECS/Utils/Matcher';
|
|
||||||
|
|
||||||
// 示例组件
|
|
||||||
class Position extends Component {
|
|
||||||
constructor(public x: number = 0, public y: number = 0) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Velocity extends Component {
|
|
||||||
constructor(public vx: number = 0, public vy: number = 0) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Health extends Component {
|
|
||||||
constructor(public hp: number = 100) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Dead extends Component {}
|
|
||||||
|
|
||||||
function demonstrateSimplifiedMatcher() {
|
|
||||||
console.log('=== 简化的Matcher API示例 ===\n');
|
|
||||||
|
|
||||||
// 创建场景 - QuerySystem自动创建
|
|
||||||
const scene = new Scene();
|
|
||||||
scene.begin();
|
|
||||||
|
|
||||||
// 创建测试实体
|
|
||||||
const player = scene.createEntity('Player');
|
|
||||||
player.addComponent(new Position(100, 200));
|
|
||||||
player.addComponent(new Velocity(5, 0));
|
|
||||||
player.addComponent(new Health(100));
|
|
||||||
|
|
||||||
const enemy = scene.createEntity('Enemy');
|
|
||||||
enemy.addComponent(new Position(300, 200));
|
|
||||||
enemy.addComponent(new Health(50));
|
|
||||||
|
|
||||||
const corpse = scene.createEntity('Corpse');
|
|
||||||
corpse.addComponent(new Position(150, 150));
|
|
||||||
corpse.addComponent(new Dead());
|
|
||||||
|
|
||||||
// ===== 推荐的新API =====
|
|
||||||
|
|
||||||
// 1. 直接使用scene.querySystem创建Matcher
|
|
||||||
const movingEntities = Matcher.create(scene.querySystem)
|
|
||||||
.all(Position, Velocity)
|
|
||||||
.query();
|
|
||||||
|
|
||||||
console.log('移动实体:', movingEntities.map(e => e.name));
|
|
||||||
|
|
||||||
// 2. 复合查询 - 活着的实体
|
|
||||||
const aliveEntities = Matcher.create(scene.querySystem)
|
|
||||||
.all(Health)
|
|
||||||
.none(Dead)
|
|
||||||
.query();
|
|
||||||
|
|
||||||
console.log('活着的实体:', aliveEntities.map(e => e.name));
|
|
||||||
|
|
||||||
// 3. 实用方法
|
|
||||||
const healthMatcher = Matcher.create(scene.querySystem).all(Health);
|
|
||||||
console.log(`有血量的实体数量: ${healthMatcher.count()}`);
|
|
||||||
console.log(`玩家是否有血量: ${healthMatcher.matches(player)}`);
|
|
||||||
|
|
||||||
// 4. 系统中使用Matcher的典型模式
|
|
||||||
class MovementSystem {
|
|
||||||
private movementMatcher: Matcher;
|
|
||||||
|
|
||||||
constructor(scene: Scene) {
|
|
||||||
// 在系统初始化时创建Matcher
|
|
||||||
this.movementMatcher = Matcher.create(scene.querySystem)
|
|
||||||
.all(Position, Velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
// 高效的批量查询
|
|
||||||
const movableEntities = this.movementMatcher.query();
|
|
||||||
|
|
||||||
for (const entity of movableEntities) {
|
|
||||||
const pos = entity.getComponent(Position)!;
|
|
||||||
const vel = entity.getComponent(Velocity)!;
|
|
||||||
|
|
||||||
pos.x += vel.vx;
|
|
||||||
pos.y += vel.vy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 创建并使用系统
|
|
||||||
const movementSystem = new MovementSystem(scene);
|
|
||||||
movementSystem.update();
|
|
||||||
|
|
||||||
console.log('玩家更新后位置:', player.getComponent(Position));
|
|
||||||
|
|
||||||
scene.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 展示设计哲学 =====
|
|
||||||
function designPhilosophy() {
|
|
||||||
console.log('\n=== 设计哲学 ===');
|
|
||||||
console.log('✅ QuerySystem是框架核心,总是存在');
|
|
||||||
console.log('✅ Matcher强制要求QuerySystem,避免回退复杂性');
|
|
||||||
console.log('✅ 清晰的错误提示,引导正确使用');
|
|
||||||
console.log('✅ 旧API保持兼容,但明确标记deprecated');
|
|
||||||
console.log('✅ 新API简洁明了,符合现代设计原则');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 运行示例
|
|
||||||
if (require.main === module) {
|
|
||||||
demonstrateSimplifiedMatcher();
|
|
||||||
designPhilosophy();
|
|
||||||
}
|
|
||||||
@@ -933,10 +933,18 @@ export class Entity {
|
|||||||
this.removeAllComponents();
|
this.removeAllComponents();
|
||||||
|
|
||||||
// 从场景中移除
|
// 从场景中移除
|
||||||
if (this.scene && this.scene.entities) {
|
if (this.scene) {
|
||||||
|
// 从查询系统中移除
|
||||||
|
if (this.scene.querySystem) {
|
||||||
|
this.scene.querySystem.removeEntity(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从实体列表中移除
|
||||||
|
if (this.scene.entities) {
|
||||||
this.scene.entities.remove(this);
|
this.scene.entities.remove(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 比较实体
|
* 比较实体
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { IdentifierPool } from './Utils/IdentifierPool';
|
|||||||
import { EntitySystem } from './Systems/EntitySystem';
|
import { EntitySystem } from './Systems/EntitySystem';
|
||||||
import { ComponentStorageManager } from './Core/ComponentStorage';
|
import { ComponentStorageManager } from './Core/ComponentStorage';
|
||||||
import { QuerySystem } from './Core/QuerySystem';
|
import { QuerySystem } from './Core/QuerySystem';
|
||||||
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
|
import { TypeSafeEventSystem } from './Core/EventSystem';
|
||||||
import { EventBus } from './Core/EventBus';
|
import { EventBus } from './Core/EventBus';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,6 +164,9 @@ export class Scene {
|
|||||||
// 移除所有实体
|
// 移除所有实体
|
||||||
this.entities.removeAllEntities();
|
this.entities.removeAllEntities();
|
||||||
|
|
||||||
|
// 清理查询系统中的实体引用和缓存
|
||||||
|
this.querySystem.setEntities([]);
|
||||||
|
|
||||||
// 清空组件存储
|
// 清空组件存储
|
||||||
this.componentStorageManager.clear();
|
this.componentStorageManager.clear();
|
||||||
|
|
||||||
@@ -281,6 +284,9 @@ export class Scene {
|
|||||||
*/
|
*/
|
||||||
public destroyAllEntities() {
|
public destroyAllEntities() {
|
||||||
this.entities.removeAllEntities();
|
this.entities.removeAllEntities();
|
||||||
|
|
||||||
|
// 清理查询系统中的实体引用和缓存
|
||||||
|
this.querySystem.setEntities([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
private _systemName: string;
|
private _systemName: string;
|
||||||
private _initialized: boolean = false;
|
private _initialized: boolean = false;
|
||||||
private _matcher: Matcher;
|
private _matcher: Matcher;
|
||||||
|
private _trackedEntities: Set<Entity> = new Set();
|
||||||
|
private _lastQueryResult: Entity[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统处理的实体列表(动态查询)
|
* 获取系统处理的实体列表(动态查询)
|
||||||
@@ -134,6 +136,10 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
* 子类可以重写此方法进行初始化操作。
|
* 子类可以重写此方法进行初始化操作。
|
||||||
*/
|
*/
|
||||||
protected onInitialize(): void {
|
protected onInitialize(): void {
|
||||||
|
// 初始化时触发一次实体查询,以便正确跟踪现有实体
|
||||||
|
if (this.scene) {
|
||||||
|
this.queryEntities();
|
||||||
|
}
|
||||||
// 子类可以重写此方法进行初始化
|
// 子类可以重写此方法进行初始化
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +150,8 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
*/
|
*/
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
this._initialized = false;
|
this._initialized = false;
|
||||||
|
this._trackedEntities.clear();
|
||||||
|
this._lastQueryResult = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,24 +159,30 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
*/
|
*/
|
||||||
private queryEntities(): Entity[] {
|
private queryEntities(): Entity[] {
|
||||||
if (!this.scene?.querySystem || !this._matcher) {
|
if (!this.scene?.querySystem || !this._matcher) {
|
||||||
|
this._lastQueryResult = [];
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const condition = this._matcher.getCondition();
|
const condition = this._matcher.getCondition();
|
||||||
const querySystem = this.scene.querySystem;
|
const querySystem = this.scene.querySystem;
|
||||||
|
let currentEntities: Entity[] = [];
|
||||||
|
|
||||||
// 空条件返回所有实体
|
// 空条件返回所有实体
|
||||||
if (this._matcher.isEmpty()) {
|
if (this._matcher.isEmpty()) {
|
||||||
return querySystem.getAllEntities();
|
currentEntities = querySystem.getAllEntities();
|
||||||
}
|
} else if (this.isSingleCondition(condition)) {
|
||||||
|
|
||||||
// 单一条件优化查询
|
// 单一条件优化查询
|
||||||
if (this.isSingleCondition(condition)) {
|
currentEntities = this.executeSingleConditionQuery(condition, querySystem);
|
||||||
return this.executeSingleConditionQuery(condition, querySystem);
|
} else {
|
||||||
|
// 复合查询
|
||||||
|
currentEntities = this.executeComplexQuery(condition, querySystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复合查询
|
// 检查实体变化并触发回调
|
||||||
return this.executeComplexQuery(condition, querySystem);
|
this.updateEntityTracking(currentEntities);
|
||||||
|
|
||||||
|
this._lastQueryResult = currentEntities;
|
||||||
|
return currentEntities;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -436,4 +450,49 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
|
|
||||||
return `${this._systemName}[${entityCount} entities]${perfInfo}`;
|
return `${this._systemName}[${entityCount} entities]${perfInfo}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新实体跟踪,检查新增和移除的实体
|
||||||
|
*/
|
||||||
|
private updateEntityTracking(currentEntities: Entity[]): void {
|
||||||
|
const currentSet = new Set(currentEntities);
|
||||||
|
|
||||||
|
// 检查新增的实体
|
||||||
|
for (const entity of currentEntities) {
|
||||||
|
if (!this._trackedEntities.has(entity)) {
|
||||||
|
this._trackedEntities.add(entity);
|
||||||
|
this.onAdded(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查移除的实体
|
||||||
|
for (const entity of this._trackedEntities) {
|
||||||
|
if (!currentSet.has(entity)) {
|
||||||
|
this._trackedEntities.delete(entity);
|
||||||
|
this.onRemoved(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当实体被添加到系统时调用
|
||||||
|
*
|
||||||
|
* 子类可以重写此方法来处理实体添加事件。
|
||||||
|
*
|
||||||
|
* @param entity 被添加的实体
|
||||||
|
*/
|
||||||
|
protected onAdded(_entity: Entity): void {
|
||||||
|
// 子类可以重写此方法
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当实体从系统中移除时调用
|
||||||
|
*
|
||||||
|
* 子类可以重写此方法来处理实体移除事件。
|
||||||
|
*
|
||||||
|
* @param entity 被移除的实体
|
||||||
|
*/
|
||||||
|
protected onRemoved(_entity: Entity): void {
|
||||||
|
// 子类可以重写此方法
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ export class TypeUtils {
|
|||||||
* @param obj 对象
|
* @param obj 对象
|
||||||
* @returns 对象的构造函数
|
* @returns 对象的构造函数
|
||||||
*/
|
*/
|
||||||
public static getType(obj: Record<string, unknown> & { constructor: Function }) {
|
public static getType(obj: any) {
|
||||||
return obj.constructor;
|
return obj.constructor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,8 +9,9 @@ class TestPositionComponent extends Component {
|
|||||||
public y: number = 0;
|
public y: number = 0;
|
||||||
public z: number = 0;
|
public z: number = 0;
|
||||||
|
|
||||||
constructor(x = 0, y = 0, z = 0) {
|
constructor(...args: unknown[]) {
|
||||||
super();
|
super();
|
||||||
|
const [x = 0, y = 0, z = 0] = args as [number?, number?, number?];
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.z = z;
|
this.z = z;
|
||||||
@@ -24,8 +25,9 @@ class TestVelocityComponent extends Component {
|
|||||||
public vz: number = 0;
|
public vz: number = 0;
|
||||||
public maxSpeed: number = 100;
|
public maxSpeed: number = 100;
|
||||||
|
|
||||||
constructor(vx = 0, vy = 0, vz = 0) {
|
constructor(...args: unknown[]) {
|
||||||
super();
|
super();
|
||||||
|
const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?];
|
||||||
this.vx = vx;
|
this.vx = vx;
|
||||||
this.vy = vy;
|
this.vy = vy;
|
||||||
this.vz = vz;
|
this.vz = vz;
|
||||||
@@ -38,8 +40,9 @@ class TestHealthComponent extends Component {
|
|||||||
public max: number = 100;
|
public max: number = 100;
|
||||||
public regeneration: number = 1;
|
public regeneration: number = 1;
|
||||||
|
|
||||||
constructor(current = 100, max = 100) {
|
constructor(...args: unknown[]) {
|
||||||
super();
|
super();
|
||||||
|
const [current = 100, max = 100] = args as [number?, number?];
|
||||||
this.current = current;
|
this.current = current;
|
||||||
this.max = max;
|
this.max = max;
|
||||||
}
|
}
|
||||||
@@ -51,8 +54,9 @@ class OriginalPositionComponent extends Component {
|
|||||||
public y: number = 0;
|
public y: number = 0;
|
||||||
public z: number = 0;
|
public z: number = 0;
|
||||||
|
|
||||||
constructor(x = 0, y = 0, z = 0) {
|
constructor(...args: unknown[]) {
|
||||||
super();
|
super();
|
||||||
|
const [x = 0, y = 0, z = 0] = args as [number?, number?, number?];
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.z = z;
|
this.z = z;
|
||||||
@@ -65,8 +69,9 @@ class OriginalVelocityComponent extends Component {
|
|||||||
public vz: number = 0;
|
public vz: number = 0;
|
||||||
public maxSpeed: number = 100;
|
public maxSpeed: number = 100;
|
||||||
|
|
||||||
constructor(vx = 0, vy = 0, vz = 0) {
|
constructor(...args: unknown[]) {
|
||||||
super();
|
super();
|
||||||
|
const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?];
|
||||||
this.vx = vx;
|
this.vx = vx;
|
||||||
this.vy = vy;
|
this.vy = vy;
|
||||||
this.vz = vz;
|
this.vz = vz;
|
||||||
@@ -78,8 +83,9 @@ class OriginalHealthComponent extends Component {
|
|||||||
public max: number = 100;
|
public max: number = 100;
|
||||||
public regeneration: number = 1;
|
public regeneration: number = 1;
|
||||||
|
|
||||||
constructor(current = 100, max = 100) {
|
constructor(...args: unknown[]) {
|
||||||
super();
|
super();
|
||||||
|
const [current = 100, max = 100] = args as [number?, number?];
|
||||||
this.current = current;
|
this.current = current;
|
||||||
this.max = max;
|
this.max = max;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,16 @@ class TrackingSystem extends EntitySystem {
|
|||||||
const wasInitialized = (this as any)._initialized;
|
const wasInitialized = (this as any)._initialized;
|
||||||
super.initialize();
|
super.initialize();
|
||||||
|
|
||||||
// 只有在真正执行初始化时才增加计数
|
// 只有在真正执行初始化时才增加计数和处理实体
|
||||||
if (!wasInitialized) {
|
if (!wasInitialized) {
|
||||||
this.initializeCallCount++;
|
this.initializeCallCount++;
|
||||||
|
|
||||||
|
// 处理所有现有实体
|
||||||
|
if (this.scene) {
|
||||||
|
for (const entity of this.scene.entities.buffer) {
|
||||||
|
this.onChanged(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user