diff --git a/docs/guide/serialization.md b/docs/guide/serialization.md index 4b8c7781..8be3066b 100644 --- a/docs/guide/serialization.md +++ b/docs/guide/serialization.md @@ -14,6 +14,18 @@ - **JSON格式**:人类可读,便于调试和编辑 - **Binary格式**:使用MessagePack,体积更小,性能更高 +> **📢 v2.2.2 重要变更** +> +> 从 v2.2.2 开始,二进制序列化格式返回 `Uint8Array` 而非 Node.js 的 `Buffer`,以确保浏览器兼容性: +> - `serialize({ format: 'binary' })` 返回 `string | Uint8Array`(原为 `string | Buffer`) +> - `deserialize(data)` 接收 `string | Uint8Array`(原为 `string | Buffer`) +> - `applyIncremental(data)` 接收 `IncrementalSnapshot | string | Uint8Array`(原为包含 `Buffer`) +> +> **迁移影响**: +> - ✅ **运行时兼容**:Node.js 的 `Buffer` 继承自 `Uint8Array`,现有代码可直接运行 +> - ⚠️ **类型检查**:如果你的 TypeScript 代码中显式使用了 `Buffer` 类型,需要改为 `Uint8Array` +> - ✅ **浏览器支持**:`Uint8Array` 是标准 JavaScript 类型,所有现代浏览器都支持 + ## 全量序列化 ### 基础用法 @@ -63,6 +75,7 @@ const binaryData = scene.serialize({ }); // 保存为文件(Node.js环境) +// 注意:binaryData 是 Uint8Array 类型,Node.js 的 fs 可以直接写入 fs.writeFileSync('save.bin', binaryData); ``` @@ -285,7 +298,7 @@ otherScene.applyIncremental(incremental); const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' }); otherScene.applyIncremental(jsonData); -// 从二进制Buffer应用 +// 从二进制Uint8Array应用 const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' }); otherScene.applyIncremental(binaryData); ``` @@ -552,9 +565,9 @@ class NetworkSync { } private receiveIncremental(data: ArrayBuffer): void { - // 直接应用二进制数据 - const buffer = Buffer.from(data); - this.scene.applyIncremental(buffer); + // 直接应用二进制数据(ArrayBuffer 转 Uint8Array) + const uint8Array = new Uint8Array(data); + this.scene.applyIncremental(uint8Array); } } ``` @@ -790,7 +803,7 @@ class LargeDataComponent extends Component { - [`Scene.createIncrementalSnapshot()`](/api/classes/Scene#createincrementalsnapshot) - 创建基础快照 - [`Scene.serializeIncremental()`](/api/classes/Scene#serializeincremental) - 获取增量变更 -- [`Scene.applyIncremental()`](/api/classes/Scene#applyincremental) - 应用增量变更(支持IncrementalSnapshot对象、JSON字符串或二进制Buffer) +- [`Scene.applyIncremental()`](/api/classes/Scene#applyincremental) - 应用增量变更(支持IncrementalSnapshot对象、JSON字符串或二进制Uint8Array) - [`Scene.updateIncrementalSnapshot()`](/api/classes/Scene#updateincrementalsnapshot) - 更新快照基准 - [`Scene.clearIncrementalSnapshot()`](/api/classes/Scene#clearincrementalsnapshot) - 清除快照 - [`Scene.hasIncrementalSnapshot()`](/api/classes/Scene#hasincrementalsnapshot) - 检查是否有快照 diff --git a/package-lock.json b/package-lock.json index a962d4a5..80fa67c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3559,6 +3559,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@msgpack/msgpack": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.2.tgz", + "integrity": "sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==", + "license": "ISC", + "engines": { + "node": ">= 18" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", @@ -5220,16 +5229,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/msgpack-lite": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@types/msgpack-lite/-/msgpack-lite-0.1.11.tgz", - "integrity": "sha512-cdCZS/gw+jIN22I4SUZUFf1ZZfVv5JM1//Br/MuZcI373sxiy3eSSoiyLu0oz+BPatTbGGGBO5jrcvd0siCdTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/multer": { "version": "1.4.13", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", @@ -7715,12 +7714,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-lite": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.3.tgz", - "integrity": "sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==", - "license": "MIT" - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -8672,6 +8665,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -8861,12 +8855,6 @@ "node": ">=8" } }, - "node_modules/int64-buffer": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz", - "integrity": "sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==", - "license": "MIT" - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -9138,6 +9126,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -11543,21 +11532,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/msgpack-lite": { - "version": "0.1.26", - "resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz", - "integrity": "sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==", - "license": "MIT", - "dependencies": { - "event-lite": "^0.1.1", - "ieee754": "^1.1.8", - "int64-buffer": "^0.1.9", - "isarray": "^1.0.0" - }, - "bin": { - "msgpack": "bin/msgpack" - } - }, "node_modules/multimatch": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", @@ -15556,10 +15530,10 @@ }, "packages/core": { "name": "@esengine/ecs-framework", - "version": "2.2.1", + "version": "2.2.3", "license": "MIT", "dependencies": { - "msgpack-lite": "^0.1.26", + "@msgpack/msgpack": "^3.0.0", "tslib": "^2.8.1" }, "devDependencies": { @@ -15572,7 +15546,6 @@ "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", "@types/jest": "^29.5.14", - "@types/msgpack-lite": "^0.1.11", "@types/node": "^20.19.17", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", diff --git a/packages/core/package.json b/packages/core/package.json index 57308612..903a56b3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/ecs-framework", - "version": "2.2.1", + "version": "2.2.3", "description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架", "main": "bin/index.js", "types": "bin/index.d.ts", @@ -58,7 +58,6 @@ "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", "@types/jest": "^29.5.14", - "@types/msgpack-lite": "^0.1.11", "@types/node": "^20.19.17", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -78,7 +77,7 @@ "directory": "packages/core" }, "dependencies": { - "msgpack-lite": "^0.1.26", + "@msgpack/msgpack": "^3.0.0", "tslib": "^2.8.1" } } diff --git a/packages/core/src/Core.ts b/packages/core/src/Core.ts index f04e2202..d240c4f3 100644 --- a/packages/core/src/Core.ts +++ b/packages/core/src/Core.ts @@ -13,7 +13,6 @@ import { ServiceContainer } from './Core/ServiceContainer'; import { PluginManager } from './Core/PluginManager'; import { IPlugin } from './Core/Plugin'; import { WorldManager } from './ECS/WorldManager'; -import { registerInjectable } from './Core/DI'; /** * 游戏引擎核心类 @@ -178,14 +177,9 @@ export class Core { this._poolManager = new PoolManager(); this._serviceContainer.registerInstance(PoolManager, this._poolManager); - // 使用依赖注入自动注册WorldManager和SceneManager - // WorldManager会在构造时创建默认World - registerInjectable(this._serviceContainer, WorldManager); - this._worldManager = this._serviceContainer.resolve(WorldManager); - - // SceneManager会通过@Inject自动获取WorldManager - registerInjectable(this._serviceContainer, SceneManager); - this._sceneManager = this._serviceContainer.resolve(SceneManager); + // 初始化场景管理器 + this._sceneManager = new SceneManager(); + this._serviceContainer.registerInstance(SceneManager, this._sceneManager); // 设置场景切换回调,通知调试管理器 this._sceneManager.setSceneChangedCallback(() => { @@ -194,6 +188,10 @@ export class Core { } }); + // 初始化World管理器 + this._worldManager = new WorldManager(); + this._serviceContainer.registerInstance(WorldManager, this._worldManager); + // 初始化插件管理器 this._pluginManager = new PluginManager(); this._pluginManager.initialize(this, this._serviceContainer); @@ -647,7 +645,7 @@ export class Core { this._performanceMonitor.updateFPS(Time.deltaTime); } - // 更新所有@Updatable服务(包括TimerManager, WorldManager, SceneManager等) + // 更新所有可更新的服务 const servicesStartTime = this._performanceMonitor.startMonitoring('Services.update'); this._serviceContainer.updateAll(deltaTime); this._performanceMonitor.endMonitoring('Services.update', servicesStartTime, this._serviceContainer.getUpdatableCount()); @@ -655,6 +653,12 @@ export class Core { // 更新对象池管理器 this._poolManager.update(); + // 更新默认场景(通过 SceneManager) + this._sceneManager.update(); + + // 更新额外的 WorldManager + this._worldManager.updateAll(); + // 更新调试管理器(基于FPS的数据发送) if (this._debugManager) { this._debugManager.onFrameUpdate(deltaTime); diff --git a/packages/core/src/Core/DI/Decorators.ts b/packages/core/src/Core/DI/Decorators.ts index 838d787a..7225ee9c 100644 --- a/packages/core/src/Core/DI/Decorators.ts +++ b/packages/core/src/Core/DI/Decorators.ts @@ -35,6 +35,12 @@ export interface InjectableMetadata { * 依赖列表 */ dependencies: Array | string | symbol>; + + /** + * 属性注入映射 + * key: 属性名, value: 服务类型 + */ + properties?: Map>; } /** @@ -77,10 +83,12 @@ export interface UpdatableMetadata { */ export function Injectable(): ClassDecorator { return function (target: T): T { - // 标记为可注入 + const existing = injectableMetadata.get(target); + injectableMetadata.set(target, { injectable: true, - dependencies: [] + dependencies: [], + properties: existing?.properties }); return target; @@ -142,20 +150,7 @@ export function Updatable(priority: number = 0): ClassDecorator { * * 标记构造函数参数需要注入的服务类型 * - * @param serviceType 服务类型标识符(类、字符串或Symbol) - * - * @example - * ```typescript - * @Injectable() - * class MySystem extends EntitySystem { - * constructor( - * @Inject(TimeService) private timeService: TimeService, - * @Inject(PhysicsService) private physics: PhysicsService - * ) { - * super(); - * } - * } - * ``` + * @param serviceType 服务类型标识符 */ export function Inject(serviceType: ServiceType | string | symbol): ParameterDecorator { return function (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) { @@ -171,6 +166,35 @@ export function Inject(serviceType: ServiceType | string | symbol): Paramet }; } +/** + * @InjectProperty() 装饰器 + * + * 通过属性装饰器注入依赖 + * + * 注入时机:在构造函数执行后、onInitialize() 调用前完成 + * + * @param serviceType 服务类型 + */ +export function InjectProperty(serviceType: ServiceType): PropertyDecorator { + return function (target: any, propertyKey: string | symbol) { + let metadata = injectableMetadata.get(target.constructor); + + if (!metadata) { + metadata = { + injectable: true, + dependencies: [] + }; + injectableMetadata.set(target.constructor, metadata); + } + + if (!metadata.properties) { + metadata.properties = new Map(); + } + + metadata.properties.set(propertyKey, serviceType); + }; +} + /** * 检查类是否标记为可注入 * @@ -252,6 +276,29 @@ export function createInstance( return new constructor(...dependencies); } +/** + * 为实例注入属性依赖 + * + * @param instance 目标实例 + * @param container 服务容器 + */ +export function injectProperties(instance: T, container: ServiceContainer): void { + const constructor = (instance as any).constructor; + const metadata = getInjectableMetadata(constructor); + + if (!metadata?.properties || metadata.properties.size === 0) { + return; + } + + for (const [propertyKey, serviceType] of metadata.properties) { + const service = container.resolve(serviceType); + + if (service !== null) { + (instance as any)[propertyKey] = service; + } + } +} + /** * 检查类是否标记为可更新 * diff --git a/packages/core/src/Core/DI/index.ts b/packages/core/src/Core/DI/index.ts index 4dd71639..43803bd9 100644 --- a/packages/core/src/Core/DI/index.ts +++ b/packages/core/src/Core/DI/index.ts @@ -7,6 +7,7 @@ export { Injectable, Inject, + InjectProperty, Updatable, isInjectable, getInjectableMetadata, @@ -14,6 +15,7 @@ export { isUpdatable, getUpdatableMetadata, createInstance, + injectProperties, registerInjectable } from './Decorators'; diff --git a/packages/core/src/ECS/Scene.ts b/packages/core/src/ECS/Scene.ts index f57c5818..5f0fd46e 100644 --- a/packages/core/src/ECS/Scene.ts +++ b/packages/core/src/ECS/Scene.ts @@ -15,7 +15,7 @@ import { IncrementalSerializer, IncrementalSnapshot, IncrementalSerializationOpt import { ComponentPoolManager } from './Core/ComponentPool'; import { PerformanceMonitor } from '../Utils/PerformanceMonitor'; import { ServiceContainer, type ServiceType } from '../Core/ServiceContainer'; -import { createInstance, isInjectable } from '../Core/DI'; +import { createInstance, isInjectable, injectProperties } from '../Core/DI'; import { isUpdatable, getUpdatableMetadata } from '../Core/DI/Decorators'; import { createLogger } from '../Utils/Logger'; @@ -578,6 +578,8 @@ export class Scene implements IScene { this._services.registerInstance(constructor, system); + injectProperties(system, this._services); + system.initialize(); return system; @@ -742,10 +744,10 @@ export class Scene implements IScene { /** * 序列化场景 * - * 将场景及其所有实体、组件序列化为JSON字符串或二进制Buffer + * 将场景及其所有实体、组件序列化为JSON字符串或二进制Uint8Array * * @param options 序列化选项 - * @returns 序列化后的数据(JSON字符串或二进制Buffer) + * @returns 序列化后的数据(JSON字符串或二进制Uint8Array) * * @example * ```typescript @@ -761,7 +763,7 @@ export class Scene implements IScene { * }); * ``` */ - public serialize(options?: SceneSerializationOptions): string | Buffer { + public serialize(options?: SceneSerializationOptions): string | Uint8Array { return SceneSerializer.serialize(this, options); } @@ -770,7 +772,7 @@ export class Scene implements IScene { * * 从序列化数据恢复场景状态 * - * @param saveData 序列化的数据(JSON字符串或二进制Buffer) + * @param saveData 序列化的数据(JSON字符串或二进制Uint8Array) * @param options 反序列化选项 * * @example @@ -786,7 +788,7 @@ export class Scene implements IScene { * }); * ``` */ - public deserialize(saveData: string | Buffer, options?: SceneDeserializationOptions): void { + public deserialize(saveData: string | Uint8Array, options?: SceneDeserializationOptions): void { SceneSerializer.deserialize(this, saveData, options); } @@ -858,7 +860,7 @@ export class Scene implements IScene { /** * 应用增量变更到场景 * - * @param incremental 增量快照数据(IncrementalSnapshot对象、JSON字符串或二进制Buffer) + * @param incremental 增量快照数据(IncrementalSnapshot对象、JSON字符串或二进制Uint8Array) * @param componentRegistry 组件类型注册表(可选,默认使用全局注册表) * * @example @@ -870,21 +872,20 @@ export class Scene implements IScene { * const jsonData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'json' }); * scene.applyIncremental(jsonData); * - * // 从二进制Buffer应用 + * // 从二进制Uint8Array应用 * const binaryData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'binary' }); * scene.applyIncremental(binaryData); * ``` */ public applyIncremental( - incremental: IncrementalSnapshot | string | Buffer, + incremental: IncrementalSnapshot | string | Uint8Array, componentRegistry?: Map ): void { const isSerializedData = typeof incremental === 'string' || - (typeof Buffer !== 'undefined' && Buffer.isBuffer(incremental)) || incremental instanceof Uint8Array; const snapshot = isSerializedData - ? IncrementalSerializer.deserializeIncremental(incremental as string | Buffer) + ? IncrementalSerializer.deserializeIncremental(incremental as string | Uint8Array) : incremental as IncrementalSnapshot; const registry = componentRegistry || ComponentRegistry.getAllComponentNames() as Map; diff --git a/packages/core/src/ECS/SceneManager.ts b/packages/core/src/ECS/SceneManager.ts index 62b3876d..8d1b2b50 100644 --- a/packages/core/src/ECS/SceneManager.ts +++ b/packages/core/src/ECS/SceneManager.ts @@ -3,10 +3,7 @@ import { ECSFluentAPI, createECSAPI } from './Core/FluentAPI'; import { Time } from '../Utils/Time'; import { createLogger } from '../Utils/Logger'; import type { IService } from '../Core/ServiceContainer'; -import type { IUpdatable } from '../Types/IUpdatable'; import { World } from './World'; -import { WorldManager } from './WorldManager'; -import { Injectable, Inject, Updatable } from '../Core/DI'; /** * 单场景管理器 @@ -21,13 +18,15 @@ import { Injectable, Inject, Updatable } from '../Core/DI'; * - 简单直观的API * - 支持延迟场景切换 * - 自动管理ECS API - * - 通过依赖注入获取WorldManager的默认World * * @example * ```typescript * // 初始化Core * Core.create({ debug: true }); * + * // 创建场景管理器 + * const sceneManager = new SceneManager(); + * * // 设置场景 * class GameScene extends Scene { * initialize() { @@ -36,22 +35,21 @@ import { Injectable, Inject, Updatable } from '../Core/DI'; * } * } * - * Core.setScene(new GameScene()); + * sceneManager.setScene(new GameScene()); * * // 游戏循环 * function gameLoop(deltaTime: number) { - * Core.update(deltaTime); // 自动更新所有服务(包括SceneManager) + * Core.update(deltaTime); // 更新全局服务 + * sceneManager.update(); // 更新场景 * } * * // 延迟切换场景(下一帧生效) - * Core.loadScene(new MenuScene()); + * sceneManager.loadScene(new MenuScene()); * ``` */ -@Injectable() -@Updatable(10) -export class SceneManager implements IService, IUpdatable { +export class SceneManager implements IService { /** - * 内部默认World(从WorldManager获取) + * 内部默认World */ private _defaultWorld: World; @@ -80,18 +78,9 @@ export class SceneManager implements IService, IUpdatable { */ private static readonly DEFAULT_SCENE_ID = '__main__'; - /** - * 构造函数 - * - * WorldManager通过依赖注入自动传入 - * - * @param worldManager WorldManager实例,用于获取默认World - */ - constructor( - @Inject(WorldManager) worldManager: WorldManager - ) { - // 使用WorldManager管理的默认World - this._defaultWorld = worldManager.getDefaultWorld(); + constructor() { + this._defaultWorld = new World({ name: '__default__' }); + this._defaultWorld.start(); } /** diff --git a/packages/core/src/ECS/Serialization/IncrementalSerializer.ts b/packages/core/src/ECS/Serialization/IncrementalSerializer.ts index 63f40f48..0299a26f 100644 --- a/packages/core/src/ECS/Serialization/IncrementalSerializer.ts +++ b/packages/core/src/ECS/Serialization/IncrementalSerializer.ts @@ -11,7 +11,7 @@ import { Component } from '../Component'; import { ComponentSerializer, SerializedComponent } from './ComponentSerializer'; import { SerializedEntity } from './EntitySerializer'; import { ComponentType } from '../Core/ComponentStorage'; -import * as msgpack from 'msgpack-lite'; +import { encode, decode } from '@msgpack/msgpack'; /** * 变更操作类型 @@ -609,7 +609,7 @@ export class IncrementalSerializer { * * @param incremental 增量快照 * @param options 序列化选项 - * @returns 序列化后的数据(JSON字符串或二进制Buffer) + * @returns 序列化后的数据(JSON字符串或二进制Uint8Array) * * @example * ```typescript @@ -631,7 +631,7 @@ export class IncrementalSerializer { public static serializeIncremental( incremental: IncrementalSnapshot, options?: { format?: IncrementalSerializationFormat; pretty?: boolean } - ): string | Buffer { + ): string | Uint8Array { const opts = { format: 'json' as IncrementalSerializationFormat, pretty: false, @@ -639,7 +639,7 @@ export class IncrementalSerializer { }; if (opts.format === 'binary') { - return msgpack.encode(incremental); + return encode(incremental); } else { return opts.pretty ? JSON.stringify(incremental, null, 2) @@ -650,7 +650,7 @@ export class IncrementalSerializer { /** * 反序列化增量快照 * - * @param data 序列化的数据(JSON字符串或二进制Buffer) + * @param data 序列化的数据(JSON字符串或二进制Uint8Array) * @returns 增量快照 * * @example @@ -662,13 +662,13 @@ export class IncrementalSerializer { * const snapshot = IncrementalSerializer.deserializeIncremental(buffer); * ``` */ - public static deserializeIncremental(data: string | Buffer): IncrementalSnapshot { + public static deserializeIncremental(data: string | Uint8Array): IncrementalSnapshot { if (typeof data === 'string') { // JSON格式 return JSON.parse(data); } else { // 二进制格式(MessagePack) - return msgpack.decode(data); + return decode(data) as IncrementalSnapshot; } } diff --git a/packages/core/src/ECS/Serialization/SceneSerializer.ts b/packages/core/src/ECS/Serialization/SceneSerializer.ts index 985be925..605f2fc2 100644 --- a/packages/core/src/ECS/Serialization/SceneSerializer.ts +++ b/packages/core/src/ECS/Serialization/SceneSerializer.ts @@ -11,7 +11,7 @@ import { ComponentType, ComponentRegistry } from '../Core/ComponentStorage'; import { EntitySerializer, SerializedEntity } from './EntitySerializer'; import { getComponentTypeName } from '../Decorators'; import { getSerializationMetadata } from './SerializationDecorators'; -import * as msgpack from 'msgpack-lite'; +import { encode, decode } from '@msgpack/msgpack'; /** * 场景序列化格式 @@ -154,9 +154,9 @@ export class SceneSerializer { * * @param scene 要序列化的场景 * @param options 序列化选项 - * @returns 序列化后的数据(JSON字符串或二进制Buffer) + * @returns 序列化后的数据(JSON字符串或二进制Uint8Array) */ - public static serialize(scene: IScene, options?: SceneSerializationOptions): string | Buffer { + public static serialize(scene: IScene, options?: SceneSerializationOptions): string | Uint8Array { const opts: SceneSerializationOptions = { systems: false, format: 'json', @@ -207,7 +207,7 @@ export class SceneSerializer { : JSON.stringify(serializedScene); } else { // 二进制格式(使用 MessagePack) - return msgpack.encode(serializedScene); + return encode(serializedScene); } } @@ -215,12 +215,12 @@ export class SceneSerializer { * 反序列化场景 * * @param scene 目标场景 - * @param saveData 序列化的数据(JSON字符串或二进制Buffer) + * @param saveData 序列化的数据(JSON字符串或二进制Uint8Array) * @param options 反序列化选项 */ public static deserialize( scene: IScene, - saveData: string | Buffer, + saveData: string | Uint8Array, options?: SceneDeserializationOptions ): void { const opts: SceneDeserializationOptions = { @@ -237,7 +237,7 @@ export class SceneSerializer { serializedScene = JSON.parse(saveData); } else { // 二进制格式(MessagePack) - serializedScene = msgpack.decode(saveData); + serializedScene = decode(saveData) as SerializedScene; } } catch (error) { throw new Error(`Failed to parse save data: ${error}`); diff --git a/packages/core/src/ECS/WorldManager.ts b/packages/core/src/ECS/WorldManager.ts index d04c6151..5296e8a9 100644 --- a/packages/core/src/ECS/WorldManager.ts +++ b/packages/core/src/ECS/WorldManager.ts @@ -1,8 +1,6 @@ import { World, IWorldConfig } from './World'; import { createLogger } from '../Utils/Logger'; import type { IService } from '../Core/ServiceContainer'; -import type { IUpdatable } from '../Types/IUpdatable'; -import { Injectable, Updatable } from '../Core/DI'; const logger = createLogger('WorldManager'); @@ -14,28 +12,21 @@ export interface IWorldManagerConfig { * 最大World数量 */ maxWorlds?: number; - + /** * 是否自动清理空World */ autoCleanup?: boolean; - + /** * 清理间隔(毫秒) */ cleanupInterval?: number; - + /** * 是否启用调试模式 */ debug?: boolean; - - /** - * 是否创建默认World(默认true) - * - * 当通过Core使用时应该为true,直接使用WorldManager时可设为false - */ - createDefaultWorld?: boolean; } /** @@ -66,18 +57,12 @@ export interface IWorldManagerConfig { * * // 游戏循环 * function gameLoop(deltaTime: number) { - * Core.update(deltaTime); // 自动更新所有@Updatable服务(包括WorldManager) + * Core.update(deltaTime); + * worldManager.updateAll(); // 更新所有活跃World * } * ``` */ -@Injectable() -@Updatable(5) -export class WorldManager implements IService, IUpdatable { - /** - * 默认World的ID - */ - public static readonly DEFAULT_WORLD_ID = '__default__'; - +export class WorldManager implements IService { private readonly _config: IWorldManagerConfig; private readonly _worlds: Map = new Map(); private readonly _activeWorlds: Set = new Set(); @@ -90,26 +75,16 @@ export class WorldManager implements IService, IUpdatable { autoCleanup: true, cleanupInterval: 30000, // 30秒 debug: false, - createDefaultWorld: true, // 默认创建 ...config }; // 默认启动运行状态 this._isRunning = true; - // 如果配置要求,创建并注册默认World - if (this._config.createDefaultWorld) { - const defaultWorld = new World({ name: WorldManager.DEFAULT_WORLD_ID }); - this._worlds.set(WorldManager.DEFAULT_WORLD_ID, defaultWorld); - this._activeWorlds.add(WorldManager.DEFAULT_WORLD_ID); - defaultWorld.start(); - } - logger.info('WorldManager已初始化', { maxWorlds: this._config.maxWorlds, autoCleanup: this._config.autoCleanup, - cleanupInterval: this._config.cleanupInterval, - defaultWorldCreated: this._config.createDefaultWorld + cleanupInterval: this._config.cleanupInterval }); this.startCleanupTimer(); @@ -117,22 +92,6 @@ export class WorldManager implements IService, IUpdatable { // ===== World管理 ===== - /** - * 获取默认World - * - * 默认World由WorldManager自动创建,供SceneManager使用。 - * 此方法主要供SceneManager内部使用。 - * - * @returns 默认World实例 - */ - public getDefaultWorld(): World { - const defaultWorld = this._worlds.get(WorldManager.DEFAULT_WORLD_ID); - if (!defaultWorld) { - throw new Error('默认World不存在,这不应该发生'); - } - return defaultWorld; - } - /** * 创建新World */ @@ -164,16 +123,8 @@ export class WorldManager implements IService, IUpdatable { /** * 移除World - * - * 注意:默认World不能被删除 */ public removeWorld(worldId: string): boolean { - // 防止删除默认World - if (worldId === WorldManager.DEFAULT_WORLD_ID) { - logger.warn('无法删除默认World'); - return false; - } - const world = this._worlds.get(worldId); if (!world) { return false; @@ -246,12 +197,18 @@ export class WorldManager implements IService, IUpdatable { /** * 更新所有活跃的World * - * 此方法由ServiceContainer自动调用(@Updatable装饰器) + * 应该在每帧的游戏循环中调用。 * 会自动更新所有活跃World的全局系统和场景。 * - * @param deltaTime 帧时间间隔(未使用,保留用于接口兼容) + * @example + * ```typescript + * function gameLoop(deltaTime: number) { + * Core.update(deltaTime); // 更新全局服务 + * worldManager.updateAll(); // 更新所有World + * } + * ``` */ - public update(deltaTime?: number): void { + public updateAll(): void { if (!this._isRunning) return; for (const worldId of this._activeWorlds) { diff --git a/packages/core/tests/ECS/Core/WorldCoreIntegration.test.ts b/packages/core/tests/ECS/Core/WorldCoreIntegration.test.ts index 6ea14e3e..969a8851 100644 --- a/packages/core/tests/ECS/Core/WorldCoreIntegration.test.ts +++ b/packages/core/tests/ECS/Core/WorldCoreIntegration.test.ts @@ -47,6 +47,13 @@ class NetworkGlobalSystem implements IGlobalSystem { /** * World与Core集成测试 + * + * 注意:v3.0重构后,Core不再直接管理Scene/World + * - 场景管理由 SceneManager 负责 + * - 多世界管理由 WorldManager 负责 + * - Core 仅负责全局服务(Timer、Performance等) + * + * 大部分旧的集成测试已移至 SceneManager.test.ts 和 WorldManager.test.ts */ describe('World与Core集成测试', () => { let worldManager: WorldManager; @@ -60,10 +67,8 @@ describe('World与Core集成测试', () => { Core.create({ debug: false }); // WorldManager和SceneManager不再是单例 - // SceneManager需要WorldManager的默认World,所以必须创建 - worldManager = new WorldManager({ createDefaultWorld: true }); - // SceneManager需要WorldManager实例 - sceneManager = new SceneManager(worldManager); + worldManager = new WorldManager(); + sceneManager = new SceneManager(); }); afterEach(() => { @@ -104,8 +109,7 @@ describe('World与Core集成测试', () => { const world1 = worldManager.createWorld('world1'); const world2 = worldManager.createWorld('world2'); - // worldManager已包含默认World,所以总数是3 - expect(worldManager.worldCount).toBe(3); + expect(worldManager.worldCount).toBe(2); expect(worldManager.getWorld('world1')).toBe(world1); expect(worldManager.getWorld('world2')).toBe(world2); }); @@ -133,7 +137,7 @@ describe('World与Core集成测试', () => { // 游戏循环 Core.update(0.016); // 更新全局服务 - worldManager.update(); // 更新所有World + worldManager.updateAll(); // 更新所有World expect(world.isActive).toBe(true); }); @@ -146,7 +150,7 @@ describe('World与Core集成测试', () => { worldManager.setWorldActive('test-world', true); // 更新World - worldManager.update(); + worldManager.updateAll(); expect(globalSystem.syncCount).toBeGreaterThan(0); }); @@ -154,8 +158,8 @@ describe('World与Core集成测试', () => { describe('隔离性测试', () => { test('多个WorldManager实例应该完全隔离', () => { - const manager1 = new WorldManager({ createDefaultWorld: false }); - const manager2 = new WorldManager({ createDefaultWorld: false }); + const manager1 = new WorldManager(); + const manager2 = new WorldManager(); manager1.createWorld('world1'); manager2.createWorld('world2'); @@ -171,10 +175,8 @@ describe('World与Core集成测试', () => { }); test('多个SceneManager实例应该完全隔离', () => { - const wm1 = new WorldManager({ createDefaultWorld: true }); - const wm2 = new WorldManager({ createDefaultWorld: true }); - const sm1 = new SceneManager(wm1); - const sm2 = new SceneManager(wm2); + const sm1 = new SceneManager(); + const sm2 = new SceneManager(); const scene1 = new Scene(); const scene2 = new Scene(); @@ -188,8 +190,6 @@ describe('World与Core集成测试', () => { // 清理 sm1.destroy(); sm2.destroy(); - wm1.destroy(); - wm2.destroy(); }); }); }); diff --git a/packages/core/tests/ECS/EntitySystemDI.test.ts b/packages/core/tests/ECS/EntitySystemDI.test.ts index 087b9374..f02802e5 100644 --- a/packages/core/tests/ECS/EntitySystemDI.test.ts +++ b/packages/core/tests/ECS/EntitySystemDI.test.ts @@ -3,7 +3,7 @@ import { EntitySystem } from '../../src/ECS/Systems/EntitySystem'; import { Entity } from '../../src/ECS/Entity'; import { Component } from '../../src/ECS/Component'; import { Matcher } from '../../src/ECS/Utils/Matcher'; -import { Injectable, Inject } from '../../src/Core/DI'; +import { Injectable, Inject, InjectProperty } from '../../src/Core/DI'; import { Core } from '../../src/Core'; import type { IService } from '../../src/Core/ServiceContainer'; import { ECSSystem } from '../../src/ECS/Decorators'; @@ -423,4 +423,170 @@ describe('EntitySystem - 依赖注入测试', () => { expect(transform.y).toBeCloseTo(0.8, 1); }); }); + + describe('属性注入 @InjectProperty', () => { + test('应该支持单个属性注入', () => { + @Injectable() + @ECSSystem('Config') + class GameConfig extends EntitySystem implements IService { + public bulletDamage = 10; + + constructor() { + super(Matcher.empty()); + } + override dispose() {} + } + + @Injectable() + @ECSSystem('Combat') + class CombatSystem extends EntitySystem implements IService { + @InjectProperty(GameConfig) + gameConfig!: GameConfig; + + constructor() { + super(Matcher.empty().all(Health)); + } + + protected override onInitialize(): void { + expect(this.gameConfig).toBeInstanceOf(GameConfig); + expect(this.gameConfig.bulletDamage).toBe(10); + } + + override dispose() {} + } + + scene.addEntityProcessor(GameConfig); + scene.addEntityProcessor(CombatSystem); + }); + + test('应该支持多个属性注入', () => { + @Injectable() + @ECSSystem('Time') + class TimeService extends EntitySystem implements IService { + public deltaTime = 0.016; + constructor() { + super(Matcher.empty()); + } + override dispose() {} + } + + @Injectable() + @ECSSystem('Collision') + class CollisionSystem extends EntitySystem implements IService { + public checkCount = 0; + constructor() { + super(Matcher.empty()); + } + override dispose() {} + } + + @Injectable() + @ECSSystem('Physics') + class PhysicsSystem extends EntitySystem implements IService { + @InjectProperty(TimeService) + time!: TimeService; + + @InjectProperty(CollisionSystem) + collision!: CollisionSystem; + + constructor() { + super(Matcher.empty()); + } + + protected override onInitialize(): void { + expect(this.time).toBeInstanceOf(TimeService); + expect(this.collision).toBeInstanceOf(CollisionSystem); + expect(this.time.deltaTime).toBe(0.016); + } + + override dispose() {} + } + + scene.registerSystems([TimeService, CollisionSystem, PhysicsSystem]); + }); + + test('属性注入应该在onInitialize之前完成', () => { + @Injectable() + @ECSSystem('Service') + class TestService extends EntitySystem implements IService { + public value = 42; + constructor() { + super(Matcher.empty()); + } + override dispose() {} + } + + @Injectable() + @ECSSystem('Consumer') + class ConsumerSystem extends EntitySystem implements IService { + @InjectProperty(TestService) + service!: TestService; + + private initializeValue = 0; + + constructor() { + super(Matcher.empty()); + } + + protected override onInitialize(): void { + this.initializeValue = this.service.value; + } + + public getInitializeValue(): number { + return this.initializeValue; + } + + override dispose() {} + } + + scene.addEntityProcessor(TestService); + const consumer = scene.addEntityProcessor(ConsumerSystem); + + expect(consumer.getInitializeValue()).toBe(42); + }); + + test('属性注入可以与构造函数注入混合使用', () => { + @Injectable() + @ECSSystem('A') + class ServiceA extends EntitySystem implements IService { + public valueA = 'A'; + constructor() { + super(Matcher.empty()); + } + override dispose() {} + } + + @Injectable() + @ECSSystem('B') + class ServiceB extends EntitySystem implements IService { + public valueB = 'B'; + constructor() { + super(Matcher.empty()); + } + override dispose() {} + } + + @Injectable() + @ECSSystem('Mixed') + class MixedSystem extends EntitySystem implements IService { + @InjectProperty(ServiceB) + serviceB!: ServiceB; + + constructor(@Inject(ServiceA) public serviceA: ServiceA) { + super(Matcher.empty()); + } + + protected override onInitialize(): void { + expect(this.serviceA).toBeInstanceOf(ServiceA); + expect(this.serviceB).toBeInstanceOf(ServiceB); + expect(this.serviceA.valueA).toBe('A'); + expect(this.serviceB.valueB).toBe('B'); + } + + override dispose() {} + } + + scene.registerSystems([ServiceA, ServiceB, MixedSystem]); + }); + }); }); diff --git a/packages/core/tests/ECS/Serialization/IncrementalSerialization.test.ts b/packages/core/tests/ECS/Serialization/IncrementalSerialization.test.ts index 53ae0fdf..5296647b 100644 --- a/packages/core/tests/ECS/Serialization/IncrementalSerialization.test.ts +++ b/packages/core/tests/ECS/Serialization/IncrementalSerialization.test.ts @@ -464,7 +464,7 @@ describe('Incremental Serialization System', () => { const incremental = scene.serializeIncremental(); const binary = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' }); - expect(Buffer.isBuffer(binary)).toBe(true); + expect(binary instanceof Uint8Array).toBe(true); const deserialized = IncrementalSerializer.deserializeIncremental(binary); expect(deserialized.version).toBe(incremental.version); diff --git a/packages/core/tests/ECS/Serialization/Serialization.test.ts b/packages/core/tests/ECS/Serialization/Serialization.test.ts index d9f7652b..e4d829d7 100644 --- a/packages/core/tests/ECS/Serialization/Serialization.test.ts +++ b/packages/core/tests/ECS/Serialization/Serialization.test.ts @@ -557,14 +557,14 @@ describe('ECS Serialization System', () => { // 二进制序列化 const binaryData = scene1.serialize({ format: 'binary' }); - // 验证是Buffer类型 - expect(Buffer.isBuffer(binaryData)).toBe(true); + // 验证是Uint8Array类型 + expect(binaryData instanceof Uint8Array).toBe(true); // JSON序列化对比 const jsonData = scene1.serialize({ format: 'json', pretty: false }); // 二进制应该更小 - const binarySize = (binaryData as Buffer).length; + const binarySize = (binaryData as Uint8Array).length; const jsonSize = (jsonData as string).length; console.log(`Binary size: ${binarySize} bytes, JSON size: ${jsonSize} bytes`); expect(binarySize).toBeLessThan(jsonSize); diff --git a/packages/core/tests/ECS/WorldManager.test.ts b/packages/core/tests/ECS/WorldManager.test.ts index 64ffbbad..0285fd2c 100644 --- a/packages/core/tests/ECS/WorldManager.test.ts +++ b/packages/core/tests/ECS/WorldManager.test.ts @@ -42,8 +42,7 @@ describe('WorldManager', () => { beforeEach(() => { // WorldManager不再是单例,直接创建新实例 - // 测试时不创建默认World - worldManager = new WorldManager({ createDefaultWorld: false }); + worldManager = new WorldManager(); }); afterEach(() => { @@ -60,8 +59,8 @@ describe('WorldManager', () => { describe('实例化', () => { test('可以创建多个独立的WorldManager实例', () => { - const manager1 = new WorldManager({ createDefaultWorld: false }); - const manager2 = new WorldManager({ createDefaultWorld: false }); + const manager1 = new WorldManager(); + const manager2 = new WorldManager(); expect(manager1).not.toBe(manager2); @@ -77,8 +76,7 @@ describe('WorldManager', () => { const config: IWorldManagerConfig = { maxWorlds: 10, autoCleanup: true, - debug: false, - createDefaultWorld: false + debug: false }; const instance = new WorldManager(config); @@ -123,7 +121,7 @@ describe('WorldManager', () => { }); test('超出最大World数量应该抛出错误', () => { - const limitedManager = new WorldManager({ maxWorlds: 2, createDefaultWorld: false }); + const limitedManager = new WorldManager({ maxWorlds: 2 }); limitedManager.createWorld('world1'); limitedManager.createWorld('world2'); @@ -430,8 +428,7 @@ describe('WorldManager', () => { const invalidConfig: IWorldManagerConfig = { maxWorlds: -1, autoCleanup: true, - debug: true, - createDefaultWorld: false + debug: true }; const manager = new WorldManager(invalidConfig); @@ -445,8 +442,7 @@ describe('WorldManager', () => { const config: IWorldManagerConfig = { maxWorlds: 3, autoCleanup: true, - debug: true, - createDefaultWorld: false + debug: true }; const manager = new WorldManager(config);