Fix/entity system dispose ondestroy (#223)

* fix(core): 修复 EntitySystem dispose 未调用 onDestroy 导致资源泄漏

* fix(core): 修复 Scene.end() 中 unload 调用时机导致用户无法清理资源
This commit is contained in:
YHH
2025-11-14 12:10:59 +08:00
committed by GitHub
parent 3a0544629d
commit e2b316b3cc
3 changed files with 83 additions and 6 deletions

View File

@@ -294,11 +294,24 @@ export class Scene implements IScene {
* 结束场景,清除实体、实体处理器等 * 结束场景,清除实体、实体处理器等
* *
* 这个方法会结束场景。它将移除所有实体结束实体处理器等并调用unload方法。 * 这个方法会结束场景。它将移除所有实体结束实体处理器等并调用unload方法。
*
* 执行顺序:
* 1. 调用 unload() - 用户可以在此访问实体和系统进行清理
* 2. 清理所有实体
* 3. 清空服务容器,触发所有系统的 onDestroy()
*
* 注意:
* - onRemoved 回调不会在 Scene.end() 时触发,因为这是批量销毁场景
* - 用户清理:在 Scene.unload() 中处理(可访问实体和系统)
* - 系统清理:在 System.onDestroy() 中处理(实体已被清理)
*/ */
public end() { public end() {
// 标记场景已结束运行 // 标记场景已结束运行
this._didSceneBegin = false; this._didSceneBegin = false;
// 先调用用户的卸载方法,此时用户可以访问实体和系统进行清理
this.unload();
// 移除所有实体 // 移除所有实体
this.entities.removeAllEntities(); this.entities.removeAllEntities();
@@ -309,14 +322,12 @@ export class Scene implements IScene {
this.componentStorageManager.clear(); this.componentStorageManager.clear();
// 清空服务容器会调用所有服务的dispose方法包括所有EntitySystem // 清空服务容器会调用所有服务的dispose方法包括所有EntitySystem
// 系统的 onDestroy 回调会在这里被触发
this._services.clear(); this._services.clear();
// 清空系统缓存 // 清空系统缓存
this._cachedSystems = null; this._cachedSystems = null;
this._systemsOrderDirty = true; this._systemsOrderDirty = true;
// 调用卸载方法
this.unload();
} }
/** /**

View File

@@ -73,6 +73,7 @@ export abstract class EntitySystem implements ISystemBase, IService {
private _matcher: Matcher; private _matcher: Matcher;
private _eventListeners: EventListenerRecord[]; private _eventListeners: EventListenerRecord[];
private _scene: Scene | null; private _scene: Scene | null;
private _destroyed: boolean;
protected logger: ReturnType<typeof createLogger>; protected logger: ReturnType<typeof createLogger>;
/** /**
@@ -145,6 +146,7 @@ export abstract class EntitySystem implements ISystemBase, IService {
this._matcher = matcher || Matcher.empty(); this._matcher = matcher || Matcher.empty();
this._eventListeners = []; this._eventListeners = [];
this._scene = null; this._scene = null;
this._destroyed = false;
this._entityIdMap = null; this._entityIdMap = null;
this._entityIdMapVersion = -1; this._entityIdMapVersion = -1;
@@ -247,8 +249,17 @@ export abstract class EntitySystem implements ISystemBase, IService {
* 重置系统状态 * 重置系统状态
* *
* 当系统从场景中移除时调用,重置初始化状态以便重新添加时能正确初始化。 * 当系统从场景中移除时调用,重置初始化状态以便重新添加时能正确初始化。
*
* 注意:此方法由 Scene.removeEntityProcessor 调用,在 unregister触发dispose之后调用。
* dispose 已经调用了 onDestroy 并设置了 _destroyed 标志,所以这里不需要重置该标志。
* 重置 _destroyed 会违反服务容器的语义dispose 后不应重用)。
*/ */
public reset(): void { public reset(): void {
// 如果系统已经被销毁不需要再次调用destroy
if (this._destroyed) {
return;
}
this.scene = null; this.scene = null;
this._initialized = false; this._initialized = false;
this._entityCache.clearAll(); this._entityCache.clearAll();
@@ -257,8 +268,7 @@ export abstract class EntitySystem implements ISystemBase, IService {
this._entityIdMap = null; this._entityIdMap = null;
this._entityIdMapVersion = -1; this._entityIdMapVersion = -1;
// 清理所有事件监听器 // 清理所有事件监听器并调用销毁回调
// 调用框架销毁方法
this.destroy(); this.destroy();
} }
@@ -728,15 +738,24 @@ export abstract class EntitySystem implements ISystemBase, IService {
* *
* 默认行为: * 默认行为:
* - 移除所有事件监听器 * - 移除所有事件监听器
* - 调用 onDestroy 回调(仅首次)
* - 清空所有缓存 * - 清空所有缓存
* - 重置初始化状态 * - 重置初始化状态
* *
* 子类可以重写此方法来清理自定义资源但应该调用super.dispose()。 * 子类可以重写此方法来清理自定义资源但应该调用super.dispose()。
*/ */
public dispose(): void { public dispose(): void {
// 防止重复销毁
if (this._destroyed) {
return;
}
// 移除所有事件监听器 // 移除所有事件监听器
this.cleanupManualEventListeners(); this.cleanupManualEventListeners();
// 调用用户销毁回调
this.onDestroy();
// 清空所有缓存 // 清空所有缓存
this._entityCache.clearAll(); this._entityCache.clearAll();
this._entityIdMap = null; this._entityIdMap = null;
@@ -744,6 +763,7 @@ export abstract class EntitySystem implements ISystemBase, IService {
// 重置状态 // 重置状态
this._initialized = false; this._initialized = false;
this._scene = null; this._scene = null;
this._destroyed = true;
this.logger.debug(`System ${this._systemName} disposed`); this.logger.debug(`System ${this._systemName} disposed`);
} }
@@ -827,8 +847,13 @@ export abstract class EntitySystem implements ISystemBase, IService {
* 由框架调用,处理系统的完整销毁流程 * 由框架调用,处理系统的完整销毁流程
*/ */
public destroy(): void { public destroy(): void {
this.cleanupManualEventListeners(); // 防止重复销毁
if (this._destroyed) {
return;
}
this.cleanupManualEventListeners();
this._destroyed = true;
this.onDestroy(); this.onDestroy();
} }

View File

@@ -192,6 +192,47 @@ describe('EntitySystem', () => {
expect(handler).not.toHaveBeenCalled(); expect(handler).not.toHaveBeenCalled();
}); });
it('dispose 方法应该调用 onDestroy 回调', () => {
const plainSystem = new PlainEntitySystem();
scene.addSystem(plainSystem);
// 直接调用 dispose
plainSystem.dispose();
// 验证 onDestroy 被调用
expect(plainSystem.onDestroyCallCount).toBe(1);
});
it('多次调用 dispose 或 destroy 不应该重复调用 onDestroy', () => {
const plainSystem = new PlainEntitySystem();
scene.addSystem(plainSystem);
// 调用 dispose
plainSystem.dispose();
expect(plainSystem.onDestroyCallCount).toBe(1);
// 再次调用 dispose
plainSystem.dispose();
expect(plainSystem.onDestroyCallCount).toBe(1);
// 调用 destroy
plainSystem.destroy();
expect(plainSystem.onDestroyCallCount).toBe(1);
});
it('先调用 destroy 再调用 dispose 不应该重复调用 onDestroy', () => {
const plainSystem = new PlainEntitySystem();
scene.addSystem(plainSystem);
// 先调用 destroy
plainSystem.destroy();
expect(plainSystem.onDestroyCallCount).toBe(1);
// 再调用 dispose
plainSystem.dispose();
expect(plainSystem.onDestroyCallCount).toBe(1);
});
}); });
describe('错误处理', () => { describe('错误处理', () => {