refactor(core): 移除 _activeWorlds 并优化 WorldManager 清理机制 (#220)

* refactor(core): 将 WorldManager 清理机制从定时器改为帧驱动

* refactor(core): 移除 WorldManager _activeWorlds 优化,简化状态管理

* test(core): 补充 WorldManager 测试用例

* docs(core): 更新 WorldManager cleanupFrameInterval 配置说明
This commit is contained in:
LINGYE
2025-11-09 17:35:07 +08:00
committed by GitHub
parent 6242c6daf3
commit b12cfba353
3 changed files with 389 additions and 178 deletions

View File

@@ -435,7 +435,7 @@ const worldManager = Core.services.resolve(WorldManager);
// { // {
// maxWorlds: 50, // maxWorlds: 50,
// autoCleanup: true, // autoCleanup: true,
// cleanupInterval: 30000 // 30 秒 // cleanupFrameInterval: 1800 // 间隔多少帧清理闲置 World
// } // }
``` ```

View File

@@ -19,9 +19,9 @@ export interface IWorldManagerConfig {
autoCleanup?: boolean; autoCleanup?: boolean;
/** /**
* 清理间隔(毫秒 * 清理间隔(帧数
*/ */
cleanupInterval?: number; cleanupFrameInterval?: number;
/** /**
* 是否启用调试模式 * 是否启用调试模式
@@ -63,17 +63,16 @@ export interface IWorldManagerConfig {
* ``` * ```
*/ */
export class WorldManager implements IService { export class WorldManager implements IService {
private readonly _config: IWorldManagerConfig; private readonly _config: Required<IWorldManagerConfig>;
private readonly _worlds: Map<string, World> = new Map(); private readonly _worlds: Map<string, World> = new Map();
private readonly _activeWorlds: Set<string> = new Set();
private _cleanupTimer: ReturnType<typeof setInterval> | null = null;
private _isRunning: boolean = false; private _isRunning: boolean = false;
private _framesSinceCleanup: number = 0;
public constructor(config: IWorldManagerConfig = {}) { public constructor(config: IWorldManagerConfig = {}) {
this._config = { this._config = {
maxWorlds: 50, maxWorlds: 50,
autoCleanup: true, autoCleanup: true,
cleanupInterval: 30000, // 30秒 cleanupFrameInterval: 1800, // 1800帧
debug: false, debug: false,
...config ...config
}; };
@@ -84,10 +83,8 @@ export class WorldManager implements IService {
logger.info('WorldManager已初始化', { logger.info('WorldManager已初始化', {
maxWorlds: this._config.maxWorlds, maxWorlds: this._config.maxWorlds,
autoCleanup: this._config.autoCleanup, autoCleanup: this._config.autoCleanup,
cleanupInterval: this._config.cleanupInterval cleanupFrameInterval: this._config.cleanupFrameInterval
}); });
this.startCleanupTimer();
} }
// ===== World管理 ===== // ===== World管理 =====
@@ -131,11 +128,6 @@ export class WorldManager implements IService {
return false; return false;
} }
// 如果World正在运行先停止它
if (this._activeWorlds.has(worldId)) {
this.setWorldActive(worldId, false);
}
// 销毁World // 销毁World
world.destroy(); world.destroy();
this._worlds.delete(worldId); this._worlds.delete(worldId);
@@ -176,11 +168,9 @@ export class WorldManager implements IService {
} }
if (active) { if (active) {
this._activeWorlds.add(worldId);
world.start(); world.start();
logger.debug(`激活World: ${worldId}`); logger.debug(`激活World: ${worldId}`);
} else { } else {
this._activeWorlds.delete(worldId);
world.stop(); world.stop();
logger.debug(`停用World: ${worldId}`); logger.debug(`停用World: ${worldId}`);
} }
@@ -190,7 +180,8 @@ export class WorldManager implements IService {
* 检查World是否激活 * 检查World是否激活
*/ */
public isWorldActive(worldId: string): boolean { public isWorldActive(worldId: string): boolean {
return this._activeWorlds.has(worldId); const world = this._worlds.get(worldId);
return world?.isActive ?? false;
} }
// ===== 批量操作 ===== // ===== 批量操作 =====
@@ -212,9 +203,8 @@ export class WorldManager implements IService {
public updateAll(): void { public updateAll(): void {
if (!this._isRunning) return; if (!this._isRunning) return;
for (const worldId of this._activeWorlds) { for (const world of this._worlds.values()) {
const world = this._worlds.get(worldId); if (world.isActive) {
if (world && world.isActive) {
// 更新World的全局System // 更新World的全局System
world.updateGlobalSystems(); world.updateGlobalSystems();
@@ -222,6 +212,20 @@ export class WorldManager implements IService {
world.updateScenes(); world.updateScenes();
} }
} }
// 基于帧的自动清理
if (this._config.autoCleanup) {
this._framesSinceCleanup++;
if (this._framesSinceCleanup >= this._config.cleanupFrameInterval) {
this.cleanup();
this._framesSinceCleanup = 0; // 重置计数器
if (this._config.debug) {
logger.debug(`执行定期清理World (间隔: ${this._config.cleanupFrameInterval} 帧)`);
}
}
}
} }
/** /**
@@ -229,9 +233,8 @@ export class WorldManager implements IService {
*/ */
public getActiveWorlds(): World[] { public getActiveWorlds(): World[] {
const activeWorlds: World[] = []; const activeWorlds: World[] = [];
for (const worldId of this._activeWorlds) { for (const world of this._worlds.values()) {
const world = this._worlds.get(worldId); if (world.isActive) {
if (world) {
activeWorlds.push(world); activeWorlds.push(world);
} }
} }
@@ -244,8 +247,8 @@ export class WorldManager implements IService {
public startAll(): void { public startAll(): void {
this._isRunning = true; this._isRunning = true;
for (const worldId of this._worlds.keys()) { for (const world of this._worlds.values()) {
this.setWorldActive(worldId, true); world.start();
} }
logger.info('启动所有World'); logger.info('启动所有World');
@@ -257,8 +260,8 @@ export class WorldManager implements IService {
public stopAll(): void { public stopAll(): void {
this._isRunning = false; this._isRunning = false;
for (const worldId of this._activeWorlds) { for (const world of this._worlds.values()) {
this.setWorldActive(worldId, false); world.stop();
} }
logger.info('停止所有World'); logger.info('停止所有World');
@@ -297,7 +300,7 @@ export class WorldManager implements IService {
public getStats() { public getStats() {
const stats = { const stats = {
totalWorlds: this._worlds.size, totalWorlds: this._worlds.size,
activeWorlds: this._activeWorlds.size, activeWorlds: this.activeWorldCount,
totalScenes: 0, totalScenes: 0,
totalEntities: 0, totalEntities: 0,
totalSystems: 0, totalSystems: 0,
@@ -316,7 +319,7 @@ export class WorldManager implements IService {
stats.worlds.push({ stats.worlds.push({
id: worldId, id: worldId,
name: world.name, name: world.name,
isActive: this._activeWorlds.has(worldId), isActive: world.isActive,
sceneCount: world.sceneCount, sceneCount: world.sceneCount,
...worldStats ...worldStats
}); });
@@ -333,7 +336,7 @@ export class WorldManager implements IService {
...this.getStats(), ...this.getStats(),
worlds: Array.from(this._worlds.entries()).map(([worldId, world]) => ({ worlds: Array.from(this._worlds.entries()).map(([worldId, world]) => ({
id: worldId, id: worldId,
isActive: this._activeWorlds.has(worldId), isActive: world.isActive,
status: world.getStatus() status: world.getStatus()
})) }))
}; };
@@ -370,9 +373,6 @@ export class WorldManager implements IService {
public destroy(): void { public destroy(): void {
logger.info('正在销毁WorldManager...'); logger.info('正在销毁WorldManager...');
// 停止清理定时器
this.stopCleanupTimer();
// 停止所有World // 停止所有World
this.stopAll(); this.stopAll();
@@ -383,7 +383,6 @@ export class WorldManager implements IService {
} }
this._worlds.clear(); this._worlds.clear();
this._activeWorlds.clear();
this._isRunning = false; this._isRunning = false;
logger.info('WorldManager已销毁'); logger.info('WorldManager已销毁');
@@ -399,62 +398,29 @@ export class WorldManager implements IService {
// ===== 私有方法 ===== // ===== 私有方法 =====
/**
* 启动清理定时器
*/
private startCleanupTimer(): void {
if (!this._config.autoCleanup || this._cleanupTimer) {
return;
}
this._cleanupTimer = setInterval(() => {
this.cleanup();
}, this._config.cleanupInterval);
logger.debug(`启动World清理定时器间隔: ${this._config.cleanupInterval}ms`);
}
/**
* 停止清理定时器
*/
private stopCleanupTimer(): void {
if (this._cleanupTimer) {
clearInterval(this._cleanupTimer);
this._cleanupTimer = null;
logger.debug('停止World清理定时器');
}
}
/** /**
* 判断World是否应该被清理 * 判断World是否应该被清理
* 清理策略:
* 1. World未激活
* 2. 没有Scene或所有Scene都是空的
* 3. 创建时间超过10分钟
*/ */
private shouldCleanupWorld(world: World): boolean { private shouldCleanupWorld(world: World): boolean {
// 清理策略:
// 1. World未激活
// 2. 没有Scene或所有Scene都是空的
// 3. 创建时间超过10分钟
if (world.isActive) { if (world.isActive) {
return false; return false;
} }
const age = Date.now() - world.createdAt;
const isOldEnough = age > 10 * 60 * 1000; // 10分钟
if (world.sceneCount === 0) { if (world.sceneCount === 0) {
const age = Date.now() - world.createdAt; return isOldEnough;
return age > 10 * 60 * 1000; // 10分钟
} }
// 检查是否所有Scene都是空的 // 检查是否所有Scene都是空的
const allScenes = world.getAllScenes(); const allScenes = world.getAllScenes();
const hasEntities = allScenes.some((scene) => const hasEntities = allScenes.some((scene) => scene.entities && scene.entities.count > 0);
scene.entities && scene.entities.count > 0 return !hasEntities && isOldEnough;
);
if (!hasEntities) {
const age = Date.now() - world.createdAt;
return age > 10 * 60 * 1000; // 10分钟
}
return false;
} }
// ===== 访问器 ===== // ===== 访问器 =====
@@ -470,7 +436,11 @@ export class WorldManager implements IService {
* 获取激活World数量 * 获取激活World数量
*/ */
public get activeWorldCount(): number { public get activeWorldCount(): number {
return this._activeWorlds.size; let count = 0;
for (const world of this._worlds.values()) {
if (world.isActive) count++;
}
return count;
} }
/** /**

View File

@@ -1,14 +1,11 @@
import { WorldManager, IWorldManagerConfig } from '../../src/ECS/WorldManager'; import { WorldManager, IWorldManagerConfig } from '../../src/ECS/WorldManager';
import { World, IWorldConfig } from '../../src/ECS/World'; import { IWorldConfig, World } from '../../src/ECS/World';
import { Scene } from '../../src/ECS/Scene';
import { EntitySystem } from '../../src/ECS/Systems/EntitySystem';
import { Component } from '../../src/ECS/Component'; import { Component } from '../../src/ECS/Component';
import { Matcher } from '../../src/ECS/Utils/Matcher';
// 测试用组件 // 测试用组件
class TestComponent extends Component { class TestComponent extends Component {
public value: number = 0; public value: number = 0;
constructor(value: number = 0) { constructor(value: number = 0) {
super(); super();
this.value = value; this.value = value;
@@ -19,19 +16,19 @@ class TestComponent extends Component {
class TestGlobalSystem { class TestGlobalSystem {
public readonly name = 'TestGlobalSystem'; public readonly name = 'TestGlobalSystem';
public updateCount: number = 0; public updateCount: number = 0;
public initialize(): void { public initialize(): void {
// 初始化 // 初始化
} }
public update(): void { public update(): void {
this.updateCount++; this.updateCount++;
} }
public reset(): void { public reset(): void {
this.updateCount = 0; this.updateCount = 0;
} }
public destroy(): void { public destroy(): void {
// 销毁 // 销毁
} }
@@ -49,7 +46,7 @@ describe('WorldManager', () => {
// 清理所有World // 清理所有World
if (worldManager) { if (worldManager) {
const worldIds = worldManager.getWorldIds(); const worldIds = worldManager.getWorldIds();
worldIds.forEach(id => { worldIds.forEach((id) => {
worldManager.removeWorld(id); worldManager.removeWorld(id);
}); });
// 清理定时器 // 清理定时器
@@ -92,13 +89,13 @@ describe('WorldManager', () => {
describe('World管理', () => { describe('World管理', () => {
test('创建World应该成功', () => { test('创建World应该成功', () => {
const world = worldManager.createWorld('test-world'); const world = worldManager.createWorld('test-world');
expect(world).toBeDefined(); expect(world).toBeDefined();
expect(world.name).toBe('test-world'); expect(world.name).toBe('test-world');
expect(worldManager.getWorld('test-world')).toBeDefined(); expect(worldManager.getWorld('test-world')).toBeDefined();
expect(worldManager.getWorldIds()).toContain('test-world'); expect(worldManager.getWorldIds()).toContain('test-world');
}); });
test('创建World时传入配置应该正确', () => { test('创建World时传入配置应该正确', () => {
const worldConfig: IWorldConfig = { const worldConfig: IWorldConfig = {
name: 'ConfiguredWorld', name: 'ConfiguredWorld',
@@ -106,20 +103,20 @@ describe('WorldManager', () => {
maxScenes: 5, maxScenes: 5,
autoCleanup: false autoCleanup: false
}; };
const world = worldManager.createWorld('configured-world', worldConfig); const world = worldManager.createWorld('configured-world', worldConfig);
expect(world.name).toBe('configured-world'); expect(world.name).toBe('configured-world');
}); });
test('重复的World ID应该抛出错误', () => { test('重复的World ID应该抛出错误', () => {
worldManager.createWorld('duplicate-world'); worldManager.createWorld('duplicate-world');
expect(() => { expect(() => {
worldManager.createWorld('duplicate-world'); worldManager.createWorld('duplicate-world');
}).toThrow("World ID 'duplicate-world' 已存在"); }).toThrow("World ID 'duplicate-world' 已存在");
}); });
test('超出最大World数量应该抛出错误', () => { test('超出最大World数量应该抛出错误', () => {
const limitedManager = new WorldManager({ maxWorlds: 2 }); const limitedManager = new WorldManager({ maxWorlds: 2 });
@@ -128,301 +125,454 @@ describe('WorldManager', () => {
expect(() => { expect(() => {
limitedManager.createWorld('world3'); limitedManager.createWorld('world3');
}).toThrow("已达到最大World数量限制: 2"); }).toThrow('已达到最大World数量限制: 2');
// 清理 // 清理
limitedManager.destroy(); limitedManager.destroy();
}); });
test('获取World应该正确', () => { test('获取World应该正确', () => {
const world = worldManager.createWorld('get-world'); const world = worldManager.createWorld('get-world');
const retrievedWorld = worldManager.getWorld('get-world'); const retrievedWorld = worldManager.getWorld('get-world');
expect(retrievedWorld).toBe(world); expect(retrievedWorld).toBe(world);
}); });
test('获取不存在的World应该返回null', () => { test('获取不存在的World应该返回null', () => {
const world = worldManager.getWorld('non-existent'); const world = worldManager.getWorld('non-existent');
expect(world).toBeNull(); expect(world).toBeNull();
}); });
test('检查World存在性应该正确', () => { test('检查World存在性应该正确', () => {
expect(worldManager.getWorld('non-existent')).toBeNull(); expect(worldManager.getWorld('non-existent')).toBeNull();
worldManager.createWorld('exists'); worldManager.createWorld('exists');
expect(worldManager.getWorld('exists')).toBeDefined(); expect(worldManager.getWorld('exists')).toBeDefined();
}); });
test('销毁World应该正确清理', () => { test('销毁World应该正确清理', () => {
const world = worldManager.createWorld('destroy-world'); const world = worldManager.createWorld('destroy-world');
world.start(); world.start();
const destroyed = worldManager.removeWorld('destroy-world'); const destroyed = worldManager.removeWorld('destroy-world');
expect(destroyed).toBe(true); expect(destroyed).toBe(true);
expect(worldManager.getWorld('destroy-world')).toBeNull(); expect(worldManager.getWorld('destroy-world')).toBeNull();
}); });
test('销毁不存在的World应该返回false', () => { test('销毁不存在的World应该返回false', () => {
const destroyed = worldManager.removeWorld('non-existent'); const destroyed = worldManager.removeWorld('non-existent');
expect(destroyed).toBe(false); expect(destroyed).toBe(false);
}); });
test('获取所有World ID应该正确', () => { test('获取所有World ID应该正确', () => {
worldManager.createWorld('world1'); worldManager.createWorld('world1');
worldManager.createWorld('world2'); worldManager.createWorld('world2');
worldManager.createWorld('world3'); worldManager.createWorld('world3');
const worldIds = worldManager.getWorldIds(); const worldIds = worldManager.getWorldIds();
expect(worldIds).toHaveLength(3); expect(worldIds).toHaveLength(3);
expect(worldIds).toContain('world1'); expect(worldIds).toContain('world1');
expect(worldIds).toContain('world2'); expect(worldIds).toContain('world2');
expect(worldIds).toContain('world3'); expect(worldIds).toContain('world3');
}); });
}); });
describe('活跃World管理', () => { describe('活跃World管理', () => {
test('启动World应该加入活跃列表', () => { test('启动World应该加入活跃列表', () => {
const world = worldManager.createWorld('active-world'); const world = worldManager.createWorld('active-world');
worldManager.setWorldActive('active-world', true); worldManager.setWorldActive('active-world', true);
const activeWorlds = worldManager.getActiveWorlds(); const activeWorlds = worldManager.getActiveWorlds();
expect(activeWorlds).toHaveLength(1); expect(activeWorlds).toHaveLength(1);
expect(activeWorlds[0]).toBe(world); expect(activeWorlds[0]).toBe(world);
}); });
test('停止World应该从活跃列表移除', () => { test('停止World应该从活跃列表移除', () => {
const world = worldManager.createWorld('inactive-world'); worldManager.createWorld('inactive-world');
worldManager.setWorldActive('inactive-world', true); worldManager.setWorldActive('inactive-world', true);
worldManager.setWorldActive('inactive-world', false); worldManager.setWorldActive('inactive-world', false);
const activeWorlds = worldManager.getActiveWorlds(); const activeWorlds = worldManager.getActiveWorlds();
expect(activeWorlds).toHaveLength(0); expect(activeWorlds).toHaveLength(0);
}); });
test('销毁激活的World应该从活跃列表移除', () => { test('销毁激活的World应该从活跃列表移除', () => {
const world = worldManager.createWorld('destroy-active'); worldManager.createWorld('destroy-active');
worldManager.setWorldActive('destroy-active', true); worldManager.setWorldActive('destroy-active', true);
worldManager.removeWorld('destroy-active'); worldManager.removeWorld('destroy-active');
const activeWorlds = worldManager.getActiveWorlds(); const activeWorlds = worldManager.getActiveWorlds();
expect(activeWorlds).toHaveLength(0); expect(activeWorlds).toHaveLength(0);
}); });
test('多个World的激活状态应该独立管理', () => { test('多个World的激活状态应该独立管理', () => {
const world1 = worldManager.createWorld('world1'); const world1 = worldManager.createWorld('world1');
const world2 = worldManager.createWorld('world2'); const world2 = worldManager.createWorld('world2');
const world3 = worldManager.createWorld('world3'); const world3 = worldManager.createWorld('world3');
worldManager.setWorldActive('world1', true); worldManager.setWorldActive('world1', true);
worldManager.setWorldActive('world3', true); worldManager.setWorldActive('world3', true);
// world2 保持未启动 // world2 保持未启动
const activeWorlds = worldManager.getActiveWorlds(); const activeWorlds = worldManager.getActiveWorlds();
expect(activeWorlds).toHaveLength(2); expect(activeWorlds).toHaveLength(2);
expect(activeWorlds).toContain(world1); expect(activeWorlds).toContain(world1);
expect(activeWorlds).toContain(world3); expect(activeWorlds).toContain(world3);
expect(activeWorlds).not.toContain(world2); expect(activeWorlds).not.toContain(world2);
}); });
}); });
describe('统计和监控', () => { describe('统计和监控', () => {
test('获取WorldManager状态应该正确', () => { test('获取WorldManager状态应该正确', () => {
worldManager.createWorld('status-world1'); worldManager.createWorld('status-world1');
const world2 = worldManager.createWorld('status-world2'); worldManager.createWorld('status-world2');
worldManager.setWorldActive('status-world2', true); worldManager.setWorldActive('status-world2', true);
const status = worldManager.getStats(); const status = worldManager.getStats();
expect(status.totalWorlds).toBe(2); expect(status.totalWorlds).toBe(2);
expect(status.activeWorlds).toBe(1); expect(status.activeWorlds).toBe(1);
expect(status.config.maxWorlds).toBeGreaterThan(0); expect(status.config.maxWorlds).toBeGreaterThan(0);
expect(status.memoryUsage).toBeGreaterThanOrEqual(0); expect(status.memoryUsage).toBeGreaterThanOrEqual(0);
expect(status.isRunning).toBeDefined(); expect(status.isRunning).toBeDefined();
}); });
test('获取所有World统计应该包含详细信息', () => { test('获取所有World统计应该包含详细信息', () => {
const world1 = worldManager.createWorld('stats-world1'); const world1 = worldManager.createWorld('stats-world1');
const world2 = worldManager.createWorld('stats-world2'); worldManager.createWorld('stats-world2');
// 为world1添加一些内容 // 为world1添加一些内容
const scene1 = world1.createScene('scene1'); const scene1 = world1.createScene('scene1');
scene1.createEntity('entity1'); scene1.createEntity('entity1');
worldManager.setWorldActive('stats-world1', true); worldManager.setWorldActive('stats-world1', true);
// world2保持空 // world2保持空
const allStats = worldManager.getDetailedStatus().worlds; const allStats = worldManager.getDetailedStatus().worlds;
expect(allStats).toHaveLength(2); expect(allStats).toHaveLength(2);
const world1Stats = allStats.find(stat => stat.id === 'stats-world1'); const world1Stats = allStats.find((stat) => stat.id === 'stats-world1');
const world2Stats = allStats.find(stat => stat.id === 'stats-world2'); const world2Stats = allStats.find((stat) => stat.id === 'stats-world2');
expect(world1Stats).toBeDefined(); expect(world1Stats).toBeDefined();
expect(world2Stats).toBeDefined(); expect(world2Stats).toBeDefined();
expect(world1Stats?.isActive).toBe(true); expect(world1Stats?.isActive).toBe(true);
expect(world2Stats?.isActive).toBe(false); expect(world2Stats?.isActive).toBe(false);
}); });
test('空WorldManager的统计应该正确', () => { test('空WorldManager的统计应该正确', () => {
const status = worldManager.getStats(); const status = worldManager.getStats();
const allStats = worldManager.getDetailedStatus().worlds; const allStats = worldManager.getDetailedStatus().worlds;
expect(status.totalWorlds).toBe(0); expect(status.totalWorlds).toBe(0);
expect(status.activeWorlds).toBe(0); expect(status.activeWorlds).toBe(0);
expect(allStats).toHaveLength(0); expect(allStats).toHaveLength(0);
}); });
}); });
describe('清理功能', () => { describe('清理功能', () => {
test('清理空闲World应该移除符合条件的World', () => { test('清理空闲World应该移除符合条件的World', () => {
// 创建一个空的World // 创建一个空的World
const emptyWorld = worldManager.createWorld('empty-world'); worldManager.createWorld('empty-world');
// 创建一个有内容的World // 创建一个有内容的World
const fullWorld = worldManager.createWorld('full-world'); const fullWorld = worldManager.createWorld('full-world');
const scene = fullWorld.createScene('scene'); const scene = fullWorld.createScene('scene');
scene.createEntity('entity'); scene.createEntity('entity');
fullWorld.start(); fullWorld.start();
// 执行清理 // 执行清理
const cleanedCount = worldManager.cleanup(); const cleanedCount = worldManager.cleanup();
// 由于清理逻辑可能基于时间或其他条件,这里主要测试不会出错 // 由于清理逻辑可能基于时间或其他条件,这里主要测试不会出错
expect(cleanedCount).toBeGreaterThanOrEqual(0); expect(cleanedCount).toBeGreaterThanOrEqual(0);
expect(() => worldManager.cleanup()).not.toThrow(); expect(() => worldManager.cleanup()).not.toThrow();
}); });
test('应该在指定帧数后自动执行清理', () => {
jest.useFakeTimers();
const manager = new WorldManager({
autoCleanup: true,
cleanupFrameInterval: 10 // 10 帧后清理
});
manager.createWorld('test');
// 模拟一个空的老 World (超过 10 分钟)
jest.advanceTimersByTime(11 * 60 * 1000);
// 执行 9 帧,不应该清理
for (let i = 0; i < 9; i++) {
manager.updateAll();
}
expect(manager.getWorld('test')).not.toBeNull();
// 第 10 帧,应该清理
manager.updateAll();
expect(manager.getWorld('test')).toBeNull();
jest.useRealTimers();
manager.destroy();
});
test('autoCleanup=false 时不应该自动清理', () => {
jest.useFakeTimers();
const manager = new WorldManager({
autoCleanup: false
});
manager.createWorld('test');
jest.advanceTimersByTime(20 * 60 * 1000);
// 执行多帧
for (let i = 0; i < 2000; i++) {
manager.updateAll();
}
// 不应该清理
expect(manager.getWorld('test')).not.toBeNull();
jest.useRealTimers();
manager.destroy();
});
test('清理后计数器应该重置', () => {
jest.useFakeTimers();
const manager = new WorldManager({
autoCleanup: true,
cleanupFrameInterval: 10
});
manager.createWorld('world1');
jest.advanceTimersByTime(11 * 60 * 1000);
// 第 10 帧触发清理
for (let i = 0; i < 10; i++) {
manager.updateAll();
}
// world1 被清理
expect(manager.getWorld('world1')).toBeNull();
// 创建新 World
manager.createWorld('world2');
jest.advanceTimersByTime(11 * 60 * 1000);
// 又要等 10 帧才能清理 (计数器已重置)
for (let i = 0; i < 9; i++) {
manager.updateAll();
}
expect(manager.getWorld('world2')).not.toBeNull();
manager.updateAll();
expect(manager.getWorld('world2')).toBeNull();
jest.useRealTimers();
manager.destroy();
});
test('应该使用配置的 cleanupFrameInterval', () => {
const manager = new WorldManager({
autoCleanup: true,
cleanupFrameInterval: 5
});
expect(manager.config.cleanupFrameInterval).toBe(5);
manager.destroy();
});
}); });
describe('World更新协调', () => { describe('World更新协调', () => {
test('更新所有活跃World应该正确', () => { test('更新所有活跃World应该正确', () => {
const world1 = worldManager.createWorld('update-world1'); const world1 = worldManager.createWorld('update-world1');
const world2 = worldManager.createWorld('update-world2'); const world2 = worldManager.createWorld('update-world2');
const world3 = worldManager.createWorld('update-world3'); worldManager.createWorld('update-world3');
// 添加一些内容到World中 // 添加一些内容到World中
const scene1 = world1.createScene('scene1'); const scene1 = world1.createScene('scene1');
const scene2 = world2.createScene('scene2'); const scene2 = world2.createScene('scene2');
scene1.createEntity('entity1'); scene1.createEntity('entity1');
scene2.createEntity('entity2'); scene2.createEntity('entity2');
// 启动部分World // 启动部分World
worldManager.setWorldActive('update-world1', true); worldManager.setWorldActive('update-world1', true);
worldManager.setWorldActive('update-world2', true); worldManager.setWorldActive('update-world2', true);
// world3保持未启动 // world3保持未启动
// 手动调用更新通常由Core.update()调用) // 手动调用更新通常由Core.update()调用)
const activeWorlds = worldManager.getActiveWorlds(); const activeWorlds = worldManager.getActiveWorlds();
expect(() => { expect(() => {
activeWorlds.forEach(world => { activeWorlds.forEach((world) => {
world.updateGlobalSystems(); world.updateGlobalSystems();
world.updateScenes(); world.updateScenes();
}); });
}).not.toThrow(); }).not.toThrow();
expect(activeWorlds).toHaveLength(2); expect(activeWorlds).toHaveLength(2);
}); });
test('updateAll 应该只更新活跃的 World', () => {
const world1 = worldManager.createWorld('world1');
const world2 = worldManager.createWorld('world2');
const system1 = new TestGlobalSystem();
const system2 = new TestGlobalSystem();
world1.addGlobalSystem(system1);
world2.addGlobalSystem(system2);
worldManager.setWorldActive('world1', true);
// world2 保持未激活
worldManager.updateAll();
expect(system1.updateCount).toBe(1); // world1 被更新
expect(system2.updateCount).toBe(0); // world2 未被更新
});
test('isRunning=false 时 updateAll 不应该更新任何 World', () => {
const world = worldManager.createWorld('world');
const system = new TestGlobalSystem();
world.addGlobalSystem(system);
worldManager.setWorldActive('world', true);
worldManager.stopAll(); // isRunning = false
worldManager.updateAll();
expect(system.updateCount).toBe(0);
});
test('updateAll 应该正确处理多个活跃和非活跃 World', () => {
const worlds: World[] = [];
const systems: TestGlobalSystem[] = [];
// 创建 5 个 World
for (let i = 0; i < 5; i++) {
const world = worldManager.createWorld(`world${i}`);
const system = new TestGlobalSystem();
world.addGlobalSystem(system);
worlds.push(world);
systems.push(system);
}
// 激活第 0, 2, 4 个
worldManager.setWorldActive('world0', true);
worldManager.setWorldActive('world2', true);
worldManager.setWorldActive('world4', true);
worldManager.updateAll();
expect(systems[0].updateCount).toBe(1); // 活跃
expect(systems[1].updateCount).toBe(0); // 非活跃
expect(systems[2].updateCount).toBe(1); // 活跃
expect(systems[3].updateCount).toBe(0); // 非活跃
expect(systems[4].updateCount).toBe(1); // 活跃
});
}); });
describe('边界情况和错误处理', () => { describe('边界情况和错误处理', () => {
test('World ID为空字符串应该抛出错误', () => { test('World ID为空字符串应该抛出错误', () => {
expect(() => { expect(() => {
worldManager.createWorld(''); worldManager.createWorld('');
}).toThrow(); }).toThrow();
}); });
test('World ID为null或undefined应该抛出错误', () => { test('World ID为null或undefined应该抛出错误', () => {
expect(() => { expect(() => {
worldManager.createWorld(null as any); worldManager.createWorld(null as unknown as string);
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
worldManager.createWorld(undefined as any); worldManager.createWorld(undefined as unknown as string);
}).toThrow(); }).toThrow();
}); });
test('极限情况下的大量World管理', () => { test('极限情况下的大量World管理', () => {
const worldCount = 50; const worldCount = 50;
const worldIds: string[] = []; const worldIds: string[] = [];
// 创建大量World // 创建大量World
for (let i = 0; i < worldCount; i++) { for (let i = 0; i < worldCount; i++) {
const worldId = `mass-world-${i}`; const worldId = `mass-world-${i}`;
worldIds.push(worldId); worldIds.push(worldId);
expect(() => { expect(() => {
worldManager.createWorld(worldId); worldManager.createWorld(worldId);
}).not.toThrow(); }).not.toThrow();
} }
expect(worldManager.getWorldIds()).toHaveLength(worldCount); expect(worldManager.getWorldIds()).toHaveLength(worldCount);
// 启动一半的World // 启动一半的World
for (let i = 0; i < worldCount / 2; i++) { for (let i = 0; i < worldCount / 2; i++) {
worldManager.setWorldActive(worldIds[i], true); worldManager.setWorldActive(worldIds[i], true);
} }
expect(worldManager.getActiveWorlds()).toHaveLength(worldCount / 2); expect(worldManager.getActiveWorlds()).toHaveLength(worldCount / 2);
// 批量清理 // 批量清理
worldIds.forEach(id => { worldIds.forEach((id) => {
expect(() => { expect(() => {
worldManager.removeWorld(id); worldManager.removeWorld(id);
}).not.toThrow(); }).not.toThrow();
}); });
expect(worldManager.getWorldIds()).toHaveLength(0); expect(worldManager.getWorldIds()).toHaveLength(0);
}); });
test('销毁后获取World应该返回null', () => { test('销毁后获取World应该返回null', () => {
worldManager.createWorld('temp-world'); worldManager.createWorld('temp-world');
worldManager.removeWorld('temp-world'); worldManager.removeWorld('temp-world');
expect(worldManager.getWorld('temp-world')).toBeNull(); expect(worldManager.getWorld('temp-world')).toBeNull();
}); });
}); });
describe('内存管理', () => { describe('内存管理', () => {
test('销毁所有World后内存应该被释放', () => { test('销毁所有World后内存应该被释放', () => {
// 创建多个World并添加内容 // 创建多个World并添加内容
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const world = worldManager.createWorld(`memory-world-${i}`); const world = worldManager.createWorld(`memory-world-${i}`);
const scene = world.createScene('scene'); const scene = world.createScene('scene');
// 添加一些实体和系统 // 添加一些实体和系统
for (let j = 0; j < 5; j++) { for (let j = 0; j < 5; j++) {
const entity = scene.createEntity(`entity-${j}`); const entity = scene.createEntity(`entity-${j}`);
entity.addComponent(new TestComponent(j)); entity.addComponent(new TestComponent(j));
} }
world.addGlobalSystem(new TestGlobalSystem()); world.addGlobalSystem(new TestGlobalSystem());
worldManager.setWorldActive(`memory-world-${i}`, true); worldManager.setWorldActive(`memory-world-${i}`, true);
} }
const beforeCleanup = worldManager.getStats(); const beforeCleanup = worldManager.getStats();
expect(beforeCleanup.totalWorlds).toBe(10); expect(beforeCleanup.totalWorlds).toBe(10);
expect(beforeCleanup.activeWorlds).toBe(10); expect(beforeCleanup.activeWorlds).toBe(10);
// 清理所有World // 清理所有World
const worldIds = worldManager.getWorldIds(); const worldIds = worldManager.getWorldIds();
worldIds.forEach(id => { worldIds.forEach((id) => {
worldManager.removeWorld(id); worldManager.removeWorld(id);
}); });
const afterCleanup = worldManager.getStats(); const afterCleanup = worldManager.getStats();
expect(afterCleanup.totalWorlds).toBe(0); expect(afterCleanup.totalWorlds).toBe(0);
expect(afterCleanup.activeWorlds).toBe(0); expect(afterCleanup.activeWorlds).toBe(0);
}); });
}); });
describe('配置验证', () => { describe('配置验证', () => {
test('无效的maxWorlds配置应该按传入值使用', () => { test('无效的maxWorlds配置应该按传入值使用', () => {
const invalidConfig: IWorldManagerConfig = { const invalidConfig: IWorldManagerConfig = {
@@ -461,4 +611,95 @@ describe('WorldManager', () => {
manager.destroy(); manager.destroy();
}); });
}); });
});
describe('isWorldActive()', () => {
test('应该正确返回 World 激活状态', () => {
worldManager.createWorld('test');
// 初始未激活
expect(worldManager.isWorldActive('test')).toBe(false);
// 激活后
worldManager.setWorldActive('test', true);
expect(worldManager.isWorldActive('test')).toBe(true);
// 停用后
worldManager.setWorldActive('test', false);
expect(worldManager.isWorldActive('test')).toBe(false);
});
test('不存在的 World 应该返回 false', () => {
expect(worldManager.isWorldActive('non-existent')).toBe(false);
});
test('应该与 world.isActive 保持一致', () => {
const world = worldManager.createWorld('test');
expect(worldManager.isWorldActive('test')).toBe(world.isActive);
worldManager.setWorldActive('test', true);
expect(worldManager.isWorldActive('test')).toBe(world.isActive);
worldManager.setWorldActive('test', false);
expect(worldManager.isWorldActive('test')).toBe(world.isActive);
});
});
describe('activeWorldCount', () => {
test('应该正确返回激活的 World 数量', () => {
expect(worldManager.activeWorldCount).toBe(0);
worldManager.createWorld('world1');
worldManager.createWorld('world2');
worldManager.createWorld('world3');
expect(worldManager.activeWorldCount).toBe(0);
worldManager.setWorldActive('world1', true);
expect(worldManager.activeWorldCount).toBe(1);
worldManager.setWorldActive('world2', true);
expect(worldManager.activeWorldCount).toBe(2);
worldManager.setWorldActive('world1', false);
expect(worldManager.activeWorldCount).toBe(1);
});
test('应该与 getActiveWorlds().length 一致', () => {
worldManager.createWorld('world1');
worldManager.createWorld('world2');
worldManager.setWorldActive('world1', true);
expect(worldManager.activeWorldCount).toBe(worldManager.getActiveWorlds().length);
});
});
describe('getAllWorlds()', () => {
test('应该返回所有 World', () => {
const world1 = worldManager.createWorld('world1');
const world2 = worldManager.createWorld('world2');
const world3 = worldManager.createWorld('world3');
const allWorlds = worldManager.getAllWorlds();
expect(allWorlds).toHaveLength(3);
expect(allWorlds).toContain(world1);
expect(allWorlds).toContain(world2);
expect(allWorlds).toContain(world3);
});
test('空 WorldManager 应该返回空数组', () => {
const allWorlds = worldManager.getAllWorlds();
expect(allWorlds).toHaveLength(0);
});
test('返回的数组修改不应该影响内部状态', () => {
worldManager.createWorld('world1');
const allWorlds = worldManager.getAllWorlds();
allWorlds.push({} as unknown as World);
expect(worldManager.getAllWorlds()).toHaveLength(1);
});
});
});