diff --git a/packages/core/src/Core.ts b/packages/core/src/Core.ts index d240c4f3..2f5931b5 100644 --- a/packages/core/src/Core.ts +++ b/packages/core/src/Core.ts @@ -13,6 +13,8 @@ import { ServiceContainer } from './Core/ServiceContainer'; import { PluginManager } from './Core/PluginManager'; import { IPlugin } from './Core/Plugin'; import { WorldManager } from './ECS/WorldManager'; +import { DebugConfigService } from './Utils/Debug/DebugConfigService'; +import { createInstance } from './Core/DI/Decorators'; /** * 游戏引擎核心类 @@ -202,14 +204,15 @@ export class Core { // 初始化调试管理器 if (this._config.debugConfig?.enabled) { - // 使用DI容器创建DebugManager(前两个参数从容器解析,config手动传入) - const config = this._config.debugConfig; - this._debugManager = new DebugManager( - this._serviceContainer.resolve(SceneManager), - this._serviceContainer.resolve(PerformanceMonitor), - config + const configService = new DebugConfigService(); + configService.setConfig(this._config.debugConfig); + this._serviceContainer.registerInstance(DebugConfigService, configService); + + this._serviceContainer.registerSingleton(DebugManager, (c) => + createInstance(DebugManager, c) ); - this._serviceContainer.registerInstance(DebugManager, this._debugManager); + + this._debugManager = this._serviceContainer.resolve(DebugManager); } this.initialize(); @@ -476,13 +479,15 @@ export class Core { if (this._instance._debugManager) { this._instance._debugManager.updateConfig(config); } else { - // 使用DI容器创建DebugManager - this._instance._debugManager = new DebugManager( - this._instance._serviceContainer.resolve(SceneManager), - this._instance._serviceContainer.resolve(PerformanceMonitor), - config + const configService = new DebugConfigService(); + configService.setConfig(config); + this._instance._serviceContainer.registerInstance(DebugConfigService, configService); + + this._instance._serviceContainer.registerSingleton(DebugManager, (c) => + createInstance(DebugManager, c) ); - this._instance._serviceContainer.registerInstance(DebugManager, this._instance._debugManager); + + this._instance._debugManager = this._instance._serviceContainer.resolve(DebugManager); } // 更新Core配置 @@ -659,11 +664,6 @@ export class Core { // 更新额外的 WorldManager this._worldManager.updateAll(); - // 更新调试管理器(基于FPS的数据发送) - if (this._debugManager) { - this._debugManager.onFrameUpdate(deltaTime); - } - // 结束性能监控 this._performanceMonitor.endMonitoring('Core.update', frameStartTime); } diff --git a/packages/core/src/Utils/Debug/DebugConfigService.ts b/packages/core/src/Utils/Debug/DebugConfigService.ts new file mode 100644 index 00000000..a15d8d6f --- /dev/null +++ b/packages/core/src/Utils/Debug/DebugConfigService.ts @@ -0,0 +1,45 @@ +import { IECSDebugConfig } from '../../Types'; +import { Injectable } from '../../Core/DI/Decorators'; +import type { IService } from '../../Core/ServiceContainer'; + +/** + * 调试配置服务 + * + * 管理调试系统的配置信息 + */ +@Injectable() +export class DebugConfigService implements IService { + private _config: IECSDebugConfig; + + constructor() { + this._config = { + enabled: false, + websocketUrl: '', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + } + + public setConfig(config: IECSDebugConfig): void { + this._config = config; + } + + public getConfig(): IECSDebugConfig { + return this._config; + } + + public isEnabled(): boolean { + return this._config.enabled; + } + + dispose(): void { + // 清理资源 + } +} diff --git a/packages/core/src/Utils/Debug/DebugManager.ts b/packages/core/src/Utils/Debug/DebugManager.ts index d4b04666..4cd36a0b 100644 --- a/packages/core/src/Utils/Debug/DebugManager.ts +++ b/packages/core/src/Utils/Debug/DebugManager.ts @@ -10,17 +10,20 @@ import { ComponentPoolManager } from '../../ECS/Core/ComponentPool'; import { Pool } from '../../Utils/Pool'; import { getComponentInstanceTypeName, getSystemInstanceTypeName } from '../../ECS/Decorators'; import type { IService } from '../../Core/ServiceContainer'; +import type { IUpdatable } from '../../Types/IUpdatable'; import { SceneManager } from '../../ECS/SceneManager'; import { PerformanceMonitor } from '../PerformanceMonitor'; +import { Injectable, Inject, Updatable } from '../../Core/DI/Decorators'; +import { DebugConfigService } from './DebugConfigService'; /** * 调试管理器 * * 整合所有调试数据收集器,负责收集和发送调试数据 - * - * 通过构造函数接收SceneManager和PerformanceMonitor,避免直接依赖Core实例 */ -export class DebugManager implements IService { +@Injectable() +@Updatable() +export class DebugManager implements IService, IUpdatable { private config: IECSDebugConfig; private webSocketManager: WebSocketManager; private entityCollector: EntityDataCollector; @@ -36,18 +39,12 @@ export class DebugManager implements IService { private sendInterval: number; private isRunning: boolean = false; - /** - * 构造调试管理器 - * @param sceneManager 场景管理器 - * @param performanceMonitor 性能监控器 - * @param config 调试配置 - */ constructor( - sceneManager: SceneManager, - performanceMonitor: PerformanceMonitor, - config: IECSDebugConfig + @Inject(SceneManager) sceneManager: SceneManager, + @Inject(PerformanceMonitor) performanceMonitor: PerformanceMonitor, + @Inject(DebugConfigService) configService: DebugConfigService ) { - this.config = config; + this.config = configService.getConfig(); this.sceneManager = sceneManager; this.performanceMonitor = performanceMonitor; @@ -60,15 +57,15 @@ export class DebugManager implements IService { // 初始化WebSocket管理器 this.webSocketManager = new WebSocketManager( - config.websocketUrl, - config.autoReconnect !== false + this.config.websocketUrl, + this.config.autoReconnect !== false ); // 设置消息处理回调 this.webSocketManager.setMessageHandler(this.handleMessage.bind(this)); // 计算发送间隔(基于帧率) - const debugFrameRate = config.debugFrameRate || 30; + const debugFrameRate = this.config.debugFrameRate || 30; this.sendInterval = 1000 / debugFrameRate; this.start(); @@ -116,16 +113,12 @@ export class DebugManager implements IService { } } - /** - * 帧更新回调 - */ - public onFrameUpdate(deltaTime: number): void { + public update(deltaTime?: number): void { if (!this.isRunning || !this.config.enabled) return; this.frameCounter++; const currentTime = Date.now(); - // 基于配置的帧率发送数据 if (currentTime - this.lastSendTime >= this.sendInterval) { this.sendDebugData(); this.lastSendTime = currentTime; diff --git a/packages/core/src/Utils/Debug/index.ts b/packages/core/src/Utils/Debug/index.ts index 6ecae5d1..637cf40f 100644 --- a/packages/core/src/Utils/Debug/index.ts +++ b/packages/core/src/Utils/Debug/index.ts @@ -4,4 +4,5 @@ export { PerformanceDataCollector } from './PerformanceDataCollector'; export { ComponentDataCollector } from './ComponentDataCollector'; export { SceneDataCollector } from './SceneDataCollector'; export { WebSocketManager } from './WebSocketManager'; -export { DebugManager } from './DebugManager'; \ No newline at end of file +export { DebugManager } from './DebugManager'; +export { DebugConfigService } from './DebugConfigService'; \ No newline at end of file diff --git a/packages/core/tests/DebugManager.test.ts b/packages/core/tests/DebugManager.test.ts new file mode 100644 index 00000000..38a84adb --- /dev/null +++ b/packages/core/tests/DebugManager.test.ts @@ -0,0 +1,668 @@ +import { Core } from '../src/Core'; +import { Scene } from '../src/ECS/Scene'; +import { DebugManager } from '../src/Utils/Debug/DebugManager'; +import { DebugConfigService } from '../src/Utils/Debug/DebugConfigService'; +import { IECSDebugConfig } from '../src/Types'; +import { createLogger } from '../src/Utils/Logger'; + +const logger = createLogger('DebugManagerTest'); + +class TestScene extends Scene { + public initializeCalled = false; + + public override initialize(): void { + this.initializeCalled = true; + } + + public override begin(): void { + } + + public override end(): void { + } +} + +describe('DebugManager DI Architecture Tests', () => { + let originalConsoleWarn: typeof console.warn; + let originalConsoleError: typeof console.error; + + beforeEach(() => { + (Core as any)._instance = null; + originalConsoleWarn = console.warn; + originalConsoleError = console.error; + console.warn = jest.fn(); + console.error = jest.fn(); + }); + + afterEach(() => { + console.warn = originalConsoleWarn; + console.error = originalConsoleError; + if (Core.Instance) { + Core.destroy(); + } + }); + + describe('DebugConfigService - Configuration Service', () => { + test('should create with default configuration', () => { + const configService = new DebugConfigService(); + const config = configService.getConfig(); + + expect(config).toBeDefined(); + expect(config.enabled).toBe(false); + expect(config.websocketUrl).toBe(''); + expect(config.debugFrameRate).toBe(30); + expect(config.autoReconnect).toBe(true); + expect(config.channels).toBeDefined(); + }); + + test('should set and get configuration', () => { + const configService = new DebugConfigService(); + const newConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 60, + autoReconnect: false, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + configService.setConfig(newConfig); + const retrievedConfig = configService.getConfig(); + + expect(retrievedConfig).toEqual(newConfig); + expect(retrievedConfig.enabled).toBe(true); + expect(retrievedConfig.websocketUrl).toBe('ws://localhost:9229'); + expect(retrievedConfig.debugFrameRate).toBe(60); + }); + + test('should return correct enabled status', () => { + const configService = new DebugConfigService(); + expect(configService.isEnabled()).toBe(false); + + configService.setConfig({ + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }); + + expect(configService.isEnabled()).toBe(true); + }); + + test('should implement dispose method', () => { + const configService = new DebugConfigService(); + expect(() => configService.dispose()).not.toThrow(); + }); + }); + + describe('DebugManager - DI Initialization', () => { + test('should initialize DebugManager through DI when debug config is enabled', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + const core = Core.create({ + debug: true, + debugConfig: debugConfig + }); + + expect((core as any)._debugManager).toBeDefined(); + expect((core as any)._debugManager).toBeInstanceOf(DebugManager); + }); + + test('should not create DebugManager when debug config is disabled', () => { + const core = Core.create({ + debug: true, + debugConfig: { + enabled: false, + websocketUrl: '', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + } + }); + + expect((core as any)._debugManager).toBeUndefined(); + }); + + test('should not create DebugManager when no debug config provided', () => { + const core = Core.create({ debug: true }); + expect((core as any)._debugManager).toBeUndefined(); + }); + + test('should register DebugConfigService in ServiceContainer', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ + debug: true, + debugConfig: debugConfig + }); + + const configService = Core.services.resolve(DebugConfigService); + expect(configService).toBeDefined(); + expect(configService).toBeInstanceOf(DebugConfigService); + }); + + test('should inject all dependencies correctly', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + const core = Core.create({ + debug: true, + debugConfig: debugConfig + }); + + const debugManager = (core as any)._debugManager as DebugManager; + expect(debugManager).toBeDefined(); + + const sceneManager = (debugManager as any).sceneManager; + const performanceMonitor = (debugManager as any).performanceMonitor; + const config = (debugManager as any).config; + + expect(sceneManager).toBeDefined(); + expect(performanceMonitor).toBeDefined(); + expect(config).toBeDefined(); + expect(config.enabled).toBe(true); + expect(config.websocketUrl).toBe('ws://localhost:9229'); + }); + }); + + describe('Core.enableDebug - Runtime Activation', () => { + test('should enable debug at runtime', () => { + const core = Core.create({ debug: true }); + expect(Core.isDebugEnabled).toBe(false); + + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.enableDebug(debugConfig); + + expect(Core.isDebugEnabled).toBe(true); + expect((core as any)._debugManager).toBeDefined(); + }); + + test('should create DebugConfigService when enabling debug at runtime', () => { + Core.create({ debug: true }); + + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.enableDebug(debugConfig); + + const configService = Core.services.resolve(DebugConfigService); + expect(configService).toBeDefined(); + expect(configService.getConfig()).toEqual(debugConfig); + }); + + test('should update existing DebugManager config when already enabled', () => { + const initialConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: initialConfig }); + + const updatedConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:8080', + debugFrameRate: 60, + autoReconnect: false, + channels: { + entities: false, + systems: true, + performance: true, + components: false, + scenes: true + } + }; + + Core.enableDebug(updatedConfig); + + expect(Core.isDebugEnabled).toBe(true); + const debugManager = (Core.Instance as any)._debugManager; + expect(debugManager).toBeDefined(); + }); + + test('should show warning when enabling debug without Core instance', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.enableDebug(debugConfig); + + expect(console.warn).toHaveBeenCalledWith( + expect.stringContaining('Core实例未创建,请先调用Core.create()') + ); + }); + }); + + describe('Core.disableDebug', () => { + test('should disable debug functionality', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + expect(Core.isDebugEnabled).toBe(true); + + Core.disableDebug(); + + expect(Core.isDebugEnabled).toBe(false); + expect((Core.Instance as any)._debugManager).toBeUndefined(); + }); + + test('should handle disabling when Core instance does not exist', () => { + expect(() => Core.disableDebug()).not.toThrow(); + }); + + test('should handle disabling when debug was never enabled', () => { + Core.create({ debug: true }); + expect(() => Core.disableDebug()).not.toThrow(); + }); + }); + + describe('DebugManager - Auto Update Integration', () => { + test('should be registered as updatable service', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + + const serviceContainer = (Core.Instance as any)._serviceContainer; + const updatableCount = serviceContainer.getUpdatableCount(); + + expect(updatableCount).toBeGreaterThan(0); + }); + + test('should be updated during Core.update cycle', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + const core = Core.create({ debug: true, debugConfig: debugConfig }); + const debugManager = (core as any)._debugManager as DebugManager; + const updateSpy = jest.spyOn(debugManager, 'update'); + + Core.update(0.016); + + expect(updateSpy).toHaveBeenCalledWith(0.016); + }); + }); + + describe('DebugManager - Scene Integration', () => { + test('should respond to scene changes', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + const core = Core.create({ debug: true, debugConfig: debugConfig }); + const debugManager = (core as any)._debugManager as DebugManager; + const sceneChangeSpy = jest.spyOn(debugManager, 'onSceneChanged'); + + const testScene = new TestScene(); + Core.setScene(testScene); + + expect(sceneChangeSpy).toHaveBeenCalled(); + }); + + test('should collect debug data from current scene', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + const testScene = new TestScene(); + Core.setScene(testScene); + + const debugData = Core.getDebugData(); + + expect(debugData).toBeDefined(); + expect(debugData).toHaveProperty('timestamp'); + expect(debugData).toHaveProperty('frameworkVersion'); + expect(debugData).toHaveProperty('currentScene'); + }); + }); + + describe('DebugManager - Configuration Management', () => { + test('should use correct debug frame rate', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 60, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + const core = Core.create({ debug: true, debugConfig: debugConfig }); + const debugManager = (core as any)._debugManager as DebugManager; + const sendInterval = (debugManager as any).sendInterval; + + expect(sendInterval).toBe(1000 / 60); + }); + + test('should handle channel configuration correctly', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: false, + performance: false, + components: true, + scenes: false + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + const testScene = new TestScene(); + Core.setScene(testScene); + + const debugData = Core.getDebugData() as any; + + expect(debugData.entities).toBeDefined(); + expect(debugData.components).toBeDefined(); + expect(debugData.systems).toBeUndefined(); + expect(debugData.performance).toBeUndefined(); + expect(debugData.scenes).toBeUndefined(); + }); + }); + + describe('DebugManager - Lifecycle', () => { + test('should start automatically on creation', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + const core = Core.create({ debug: true, debugConfig: debugConfig }); + const debugManager = (core as any)._debugManager as DebugManager; + const isRunning = (debugManager as any).isRunning; + + expect(isRunning).toBe(true); + }); + + test('should stop when disabling debug', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + const debugManager = (Core.Instance as any)._debugManager as DebugManager; + const stopSpy = jest.spyOn(debugManager, 'stop'); + + Core.disableDebug(); + + expect(stopSpy).toHaveBeenCalled(); + }); + + test('should dispose properly on Core.destroy', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + const debugManager = (Core.Instance as any)._debugManager as DebugManager; + const stopSpy = jest.spyOn(debugManager, 'stop'); + + Core.destroy(); + + expect(stopSpy).toHaveBeenCalled(); + }); + }); + + describe('DebugManager - Pure DI Architecture Validation', () => { + test('should resolve all dependencies through ServiceContainer', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + + const debugConfigService = Core.services.resolve(DebugConfigService); + expect(debugConfigService).toBeDefined(); + + const config = debugConfigService.getConfig(); + expect(config).toEqual(debugConfig); + }); + + test('should not have mixed DI patterns', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + const core = Core.create({ debug: true, debugConfig: debugConfig }); + const debugManager = (core as any)._debugManager as DebugManager; + + const sceneManager = (debugManager as any).sceneManager; + const performanceMonitor = (debugManager as any).performanceMonitor; + const config = (debugManager as any).config; + + expect(sceneManager).toBeDefined(); + expect(performanceMonitor).toBeDefined(); + expect(config).toBeDefined(); + expect(config.enabled).toBe(true); + }); + + test('should use factory pattern for registration', () => { + const debugConfig: IECSDebugConfig = { + enabled: true, + websocketUrl: 'ws://localhost:9229', + debugFrameRate: 30, + autoReconnect: true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + + Core.create({ debug: true, debugConfig: debugConfig }); + + const debugManager1 = (Core.Instance as any)._debugManager; + const debugManager2 = Core.services.resolve(DebugManager); + + expect(debugManager1).toBe(debugManager2); + }); + }); +});