diff --git a/README.md b/README.md index a69c5624..b66f0173 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ ## 特性 - **高性能** - 针对大规模实体优化,支持SoA存储和批量处理 +- **多线程计算** - Worker系统支持真正的并行处理,充分利用多核CPU性能 - **类型安全** - 完整的TypeScript支持,编译时类型检查 - **现代架构** - 支持多World、多Scene的分层架构设计 - **开发友好** - 内置调试工具和性能监控 @@ -86,6 +87,7 @@ function gameLoop(deltaTime: number) { - **实体查询** - 使用 Matcher API 进行高效的实体过滤 - **事件系统** - 类型安全的事件发布/订阅机制 - **性能优化** - SoA 存储优化,支持大规模实体处理 +- **多线程支持** - Worker系统实现真正的并行计算,充分利用多核CPU - **多场景** - 支持 World/Scene 分层架构 - **时间管理** - 内置定时器和时间控制系统 @@ -101,6 +103,7 @@ function gameLoop(deltaTime: number) { ## 示例项目 +- [Worker系统演示](https://esengine.github.io/ecs-framework/demos/worker-system/) - 多线程物理系统演示,展示高性能并行计算 - [割草机演示](https://github.com/esengine/lawn-mower-demo) - 完整的游戏示例 ## 文档 diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 033785b8..fe83f0cf 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -66,7 +66,13 @@ export default defineConfig({ items: [ { text: '实体类 (Entity)', link: '/guide/entity' }, { text: '组件系统 (Component)', link: '/guide/component' }, - { text: '系统架构 (System)', link: '/guide/system' }, + { + text: '系统架构 (System)', + link: '/guide/system', + items: [ + { text: 'Worker系统 (多线程)', link: '/guide/worker-system' } + ] + }, { text: '场景管理 (Scene)', link: '/guide/scene' }, { text: '事件系统 (Event)', link: '/guide/event-system' }, { text: '时间和定时器 (Time)', link: '/guide/time-and-timers' }, diff --git a/docs/guide/system.md b/docs/guide/system.md index 2d51e96b..b7845cc7 100644 --- a/docs/guide/system.md +++ b/docs/guide/system.md @@ -105,6 +105,14 @@ class AutoSaveSystem extends IntervalSystem { } ``` +### WorkerEntitySystem - 多线程系统 + +基于Web Worker的多线程处理系统,适用于计算密集型任务,能够充分利用多核CPU性能。 + +Worker系统提供了真正的并行计算能力,支持SharedArrayBuffer优化,并具有自动降级支持。特别适合物理模拟、粒子系统、AI计算等场景。 + +**详细内容请参考:[Worker系统](/guide/worker-system)** + ## 实体匹配器 (Matcher) Matcher 用于定义系统需要处理哪些实体。它提供了灵活的条件组合: diff --git a/docs/guide/worker-system.md b/docs/guide/worker-system.md new file mode 100644 index 00000000..d5650c45 --- /dev/null +++ b/docs/guide/worker-system.md @@ -0,0 +1,579 @@ +# Worker系统 + +Worker系统(WorkerEntitySystem)是ECS框架中基于Web Worker的多线程处理系统,专为计算密集型任务设计,能够充分利用多核CPU性能,实现真正的并行计算。 + +## 核心特性 + +- **真正的并行计算**:利用Web Worker在后台线程执行计算密集型任务 +- **自动负载均衡**:根据CPU核心数自动分配工作负载 +- **SharedArrayBuffer优化**:零拷贝数据共享,提升大规模计算性能 +- **降级支持**:不支持Worker时自动回退到主线程处理 +- **类型安全**:完整的TypeScript支持和类型检查 + +## 基本用法 + +### 简单的物理系统示例 + +```typescript +interface PhysicsData { + id: number; + x: number; + y: number; + vx: number; + vy: number; + mass: number; + radius: number; +} + +@ECSSystem('Physics') +class PhysicsWorkerSystem extends WorkerEntitySystem { + constructor() { + super(Matcher.all(Position, Velocity, Physics), { + enableWorker: true, // 启用Worker并行处理 + workerCount: 4, // Worker数量 + useSharedArrayBuffer: true, // 启用SharedArrayBuffer优化 + entityDataSize: 7, // 每个实体数据大小 + maxEntities: 10000, // 最大实体数量 + systemConfig: { // 传递给Worker的配置 + gravity: 100, + friction: 0.95 + } + }); + } + + // 数据提取:将Entity转换为可序列化的数据 + protected extractEntityData(entity: Entity): PhysicsData { + const position = entity.getComponent(Position); + const velocity = entity.getComponent(Velocity); + const physics = entity.getComponent(Physics); + + return { + id: entity.id, + x: position.x, + y: position.y, + vx: velocity.x, + vy: velocity.y, + mass: physics.mass, + radius: physics.radius + }; + } + + // Worker处理函数:纯函数,在Worker中执行 + protected workerProcess( + entities: PhysicsData[], + deltaTime: number, + config: any + ): PhysicsData[] { + return entities.map(entity => { + // 应用重力 + entity.vy += config.gravity * deltaTime; + + // 更新位置 + entity.x += entity.vx * deltaTime; + entity.y += entity.vy * deltaTime; + + // 应用摩擦力 + entity.vx *= config.friction; + entity.vy *= config.friction; + + return entity; + }); + } + + // 结果应用:将Worker处理结果应用回Entity + protected applyResult(entity: Entity, result: PhysicsData): void { + const position = entity.getComponent(Position); + const velocity = entity.getComponent(Velocity); + + position.x = result.x; + position.y = result.y; + velocity.x = result.vx; + velocity.y = result.vy; + } + + // SharedArrayBuffer优化支持 + protected getDefaultEntityDataSize(): number { + return 7; // id, x, y, vx, vy, mass, radius + } + + protected writeEntityToBuffer(entityData: PhysicsData, offset: number): void { + if (!this.sharedFloatArray) return; + + this.sharedFloatArray[offset + 0] = entityData.id; + this.sharedFloatArray[offset + 1] = entityData.x; + this.sharedFloatArray[offset + 2] = entityData.y; + this.sharedFloatArray[offset + 3] = entityData.vx; + this.sharedFloatArray[offset + 4] = entityData.vy; + this.sharedFloatArray[offset + 5] = entityData.mass; + this.sharedFloatArray[offset + 6] = entityData.radius; + } + + protected readEntityFromBuffer(offset: number): PhysicsData | null { + if (!this.sharedFloatArray) return null; + + return { + id: this.sharedFloatArray[offset + 0], + x: this.sharedFloatArray[offset + 1], + y: this.sharedFloatArray[offset + 2], + vx: this.sharedFloatArray[offset + 3], + vy: this.sharedFloatArray[offset + 4], + mass: this.sharedFloatArray[offset + 5], + radius: this.sharedFloatArray[offset + 6] + }; + } +} +``` + +## 配置选项 + +Worker系统支持丰富的配置选项: + +```typescript +interface WorkerSystemConfig { + /** 是否启用Worker并行处理 */ + enableWorker?: boolean; + /** Worker数量,默认为CPU核心数 */ + workerCount?: number; + /** 系统配置数据,会传递给Worker */ + systemConfig?: any; + /** 是否使用SharedArrayBuffer优化 */ + useSharedArrayBuffer?: boolean; + /** 每个实体在SharedArrayBuffer中占用的Float32数量 */ + entityDataSize?: number; + /** 最大实体数量(用于预分配SharedArrayBuffer) */ + maxEntities?: number; +} +``` + +### 配置建议 + +```typescript +constructor() { + super(matcher, { + // 根据任务复杂度决定是否启用 + enableWorker: this.shouldUseWorker(), + + // 限制Worker数量,避免创建过多线程 + workerCount: Math.min(navigator.hardwareConcurrency || 2, 4), + + // 大量简单计算时启用SharedArrayBuffer + useSharedArrayBuffer: this.entityCount > 1000, + + // 根据实际数据结构设置 + entityDataSize: 8, // 确保与数据结构匹配 + + // 预估最大实体数量 + maxEntities: 10000, + + // 传递给Worker的全局配置 + systemConfig: { + gravity: 9.8, + friction: 0.95, + worldBounds: { width: 1920, height: 1080 } + } + }); +} + +private shouldUseWorker(): boolean { + // 根据实体数量和计算复杂度决定 + return this.expectedEntityCount > 100; +} +``` + +## 处理模式 + +Worker系统支持两种处理模式: + +### 1. 传统Worker模式 + +数据通过序列化在主线程和Worker间传递: + +```typescript +// 适用于:复杂计算逻辑,实体数量适中 +constructor() { + super(matcher, { + enableWorker: true, + useSharedArrayBuffer: false, // 使用传统模式 + workerCount: 2 + }); +} + +protected workerProcess(entities: EntityData[], deltaTime: number): EntityData[] { + // 复杂的算法逻辑 + return entities.map(entity => { + // AI决策、路径规划等复杂计算 + return this.complexAILogic(entity, deltaTime); + }); +} +``` + +### 2. SharedArrayBuffer模式 + +零拷贝数据共享,适合大量简单计算: + +```typescript +// 适用于:大量实体的简单计算 +constructor() { + super(matcher, { + enableWorker: true, + useSharedArrayBuffer: true, // 启用共享内存 + entityDataSize: 6, + maxEntities: 10000 + }); +} + +protected getSharedArrayBufferProcessFunction(): SharedArrayBufferProcessFunction { + return function(sharedFloatArray: Float32Array, startIndex: number, endIndex: number, deltaTime: number, config: any) { + const entitySize = 6; + for (let i = startIndex; i < endIndex; i++) { + const offset = i * entitySize; + + // 读取数据 + let x = sharedFloatArray[offset]; + let y = sharedFloatArray[offset + 1]; + let vx = sharedFloatArray[offset + 2]; + let vy = sharedFloatArray[offset + 3]; + + // 物理计算 + vy += config.gravity * deltaTime; + x += vx * deltaTime; + y += vy * deltaTime; + + // 写回数据 + sharedFloatArray[offset] = x; + sharedFloatArray[offset + 1] = y; + sharedFloatArray[offset + 2] = vx; + sharedFloatArray[offset + 3] = vy; + } + }; +} +``` + +## 完整示例:粒子物理系统 + +一个包含碰撞检测的完整粒子物理系统: + +```typescript +interface ParticleData { + id: number; + x: number; + y: number; + dx: number; + dy: number; + mass: number; + radius: number; + bounce: number; + friction: number; +} + +@ECSSystem('ParticlePhysics') +class ParticlePhysicsWorkerSystem extends WorkerEntitySystem { + constructor() { + super(Matcher.all(Position, Velocity, Physics, Renderable), { + enableWorker: true, + workerCount: navigator.hardwareConcurrency || 2, + useSharedArrayBuffer: true, + entityDataSize: 9, + maxEntities: 5000, + systemConfig: { + gravity: 100, + canvasWidth: 800, + canvasHeight: 600, + groundFriction: 0.98 + } + }); + } + + protected extractEntityData(entity: Entity): ParticleData { + const position = entity.getComponent(Position); + const velocity = entity.getComponent(Velocity); + const physics = entity.getComponent(Physics); + const renderable = entity.getComponent(Renderable); + + return { + id: entity.id, + x: position.x, + y: position.y, + dx: velocity.dx, + dy: velocity.dy, + mass: physics.mass, + radius: renderable.size, + bounce: physics.bounce, + friction: physics.friction + }; + } + + protected workerProcess( + entities: ParticleData[], + deltaTime: number, + config: any + ): ParticleData[] { + const result = entities.map(e => ({ ...e })); + + // 基础物理更新 + for (const particle of result) { + // 应用重力 + particle.dy += config.gravity * deltaTime; + + // 更新位置 + particle.x += particle.dx * deltaTime; + particle.y += particle.dy * deltaTime; + + // 边界碰撞 + if (particle.x <= particle.radius) { + particle.x = particle.radius; + particle.dx = -particle.dx * particle.bounce; + } else if (particle.x >= config.canvasWidth - particle.radius) { + particle.x = config.canvasWidth - particle.radius; + particle.dx = -particle.dx * particle.bounce; + } + + if (particle.y <= particle.radius) { + particle.y = particle.radius; + particle.dy = -particle.dy * particle.bounce; + } else if (particle.y >= config.canvasHeight - particle.radius) { + particle.y = config.canvasHeight - particle.radius; + particle.dy = -particle.dy * particle.bounce; + particle.dx *= config.groundFriction; + } + + // 空气阻力 + particle.dx *= particle.friction; + particle.dy *= particle.friction; + } + + // 粒子间碰撞检测(O(n²)算法) + for (let i = 0; i < result.length; i++) { + for (let j = i + 1; j < result.length; j++) { + const p1 = result[i]; + const p2 = result[j]; + + const dx = p2.x - p1.x; + const dy = p2.y - p1.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const minDistance = p1.radius + p2.radius; + + if (distance < minDistance && distance > 0) { + // 分离粒子 + const nx = dx / distance; + const ny = dy / distance; + const overlap = minDistance - distance; + + p1.x -= nx * overlap * 0.5; + p1.y -= ny * overlap * 0.5; + p2.x += nx * overlap * 0.5; + p2.y += ny * overlap * 0.5; + + // 弹性碰撞 + const relativeVelocityX = p2.dx - p1.dx; + const relativeVelocityY = p2.dy - p1.dy; + const velocityAlongNormal = relativeVelocityX * nx + relativeVelocityY * ny; + + if (velocityAlongNormal > 0) continue; + + const restitution = (p1.bounce + p2.bounce) * 0.5; + const impulseScalar = -(1 + restitution) * velocityAlongNormal / (1/p1.mass + 1/p2.mass); + + p1.dx -= impulseScalar * nx / p1.mass; + p1.dy -= impulseScalar * ny / p1.mass; + p2.dx += impulseScalar * nx / p2.mass; + p2.dy += impulseScalar * ny / p2.mass; + } + } + } + + return result; + } + + protected applyResult(entity: Entity, result: ParticleData): void { + if (!entity?.enabled) return; + + const position = entity.getComponent(Position); + const velocity = entity.getComponent(Velocity); + + if (position && velocity) { + position.set(result.x, result.y); + velocity.set(result.dx, result.dy); + } + } + + protected getDefaultEntityDataSize(): number { + return 9; + } + + protected writeEntityToBuffer(data: ParticleData, offset: number): void { + if (!this.sharedFloatArray) return; + + this.sharedFloatArray[offset + 0] = data.id; + this.sharedFloatArray[offset + 1] = data.x; + this.sharedFloatArray[offset + 2] = data.y; + this.sharedFloatArray[offset + 3] = data.dx; + this.sharedFloatArray[offset + 4] = data.dy; + this.sharedFloatArray[offset + 5] = data.mass; + this.sharedFloatArray[offset + 6] = data.radius; + this.sharedFloatArray[offset + 7] = data.bounce; + this.sharedFloatArray[offset + 8] = data.friction; + } + + protected readEntityFromBuffer(offset: number): ParticleData | null { + if (!this.sharedFloatArray) return null; + + return { + id: this.sharedFloatArray[offset + 0], + x: this.sharedFloatArray[offset + 1], + y: this.sharedFloatArray[offset + 2], + dx: this.sharedFloatArray[offset + 3], + dy: this.sharedFloatArray[offset + 4], + mass: this.sharedFloatArray[offset + 5], + radius: this.sharedFloatArray[offset + 6], + bounce: this.sharedFloatArray[offset + 7], + friction: this.sharedFloatArray[offset + 8] + }; + } + + // 性能监控 + public getPerformanceInfo(): { + enabled: boolean; + workerCount: number; + entityCount: number; + isProcessing: boolean; + } { + const workerInfo = this.getWorkerInfo(); + return { + ...workerInfo, + entityCount: this.entities.length + }; + } +} +``` + +## 适用场景 + +Worker系统特别适合以下场景: + +### 1. 物理模拟 +- **重力系统**:大量实体的重力计算 +- **碰撞检测**:复杂的碰撞算法 +- **流体模拟**:粒子流体系统 +- **布料模拟**:顶点物理计算 + +### 2. AI计算 +- **路径寻找**:A*、Dijkstra等算法 +- **行为树**:复杂的AI决策逻辑 +- **群体智能**:鸟群、鱼群算法 +- **神经网络**:简单的AI推理 + +### 3. 数据处理 +- **大量实体更新**:状态机、生命周期管理 +- **统计计算**:游戏数据分析 +- **图像处理**:纹理生成、效果计算 +- **音频处理**:音效合成、频谱分析 + +## 最佳实践 + +### 1. Worker函数要求 + +```typescript +// ✅ 推荐:Worker处理函数是纯函数 +protected workerProcess(entities: PhysicsData[], deltaTime: number, config: any): PhysicsData[] { + // 只使用参数和标准JavaScript API + return entities.map(entity => { + // 纯计算逻辑,不依赖外部状态 + entity.y += entity.velocity * deltaTime; + return entity; + }); +} + +// ❌ 避免:在Worker函数中使用外部引用 +protected workerProcess(entities: PhysicsData[], deltaTime: number): PhysicsData[] { + // this 和外部变量在Worker中不可用 + return entities.map(entity => { + entity.y += this.someProperty; // ❌ 错误 + return entity; + }); +} +``` + +### 2. 数据设计 + +```typescript +// ✅ 推荐:合理的数据设计 +interface SimplePhysicsData { + x: number; + y: number; + vx: number; + vy: number; + // 保持数据结构简单,便于序列化 +} + +// ❌ 避免:复杂的嵌套对象 +interface ComplexData { + transform: { + position: { x: number; y: number }; + rotation: { angle: number }; + }; + // 复杂嵌套结构增加序列化开销 +} +``` + +### 3. Worker数量控制 + +```typescript +// ✅ 推荐:适当的Worker数量 +constructor() { + super(matcher, { + workerCount: Math.min(navigator.hardwareConcurrency || 2, 4), // 限制最大数量 + enableWorker: this.shouldUseWorker(), // 条件启用 + }); +} + +private shouldUseWorker(): boolean { + // 根据实体数量和复杂度决定是否使用Worker + return this.expectedEntityCount > 100; +} +``` + +### 4. 性能监控 + +```typescript +// ✅ 推荐:性能监控 +public getPerformanceMetrics(): WorkerPerformanceMetrics { + return { + ...this.getWorkerInfo(), + entityCount: this.entities.length, + averageProcessTime: this.getAverageProcessTime(), + workerUtilization: this.getWorkerUtilization() + }; +} +``` + +## 性能优化建议 + +### 1. 计算密集度评估 +只对计算密集型任务使用Worker,避免在简单计算上增加线程开销。 + +### 2. 数据传输优化 +- 使用SharedArrayBuffer减少序列化开销 +- 保持数据结构简单和扁平 +- 避免频繁的大数据传输 + +### 3. 批处理大小 +根据实体数量和Worker数量调整批处理大小,平衡负载和开销。 + +### 4. 降级策略 +始终提供主线程回退方案,确保在不支持Worker的环境中正常运行。 + +### 5. 内存管理 +及时清理Worker池和共享缓冲区,避免内存泄漏。 + +## 在线演示 + +查看完整的Worker系统演示:[Worker系统演示](https://esengine.github.io/ecs-framework/demos/worker-system/) + +该演示展示了: +- 多线程物理计算 +- 实时性能对比 +- SharedArrayBuffer优化 +- 大量实体的并行处理 + +Worker系统为ECS框架提供了强大的并行计算能力,让你能够充分利用现代多核处理器的性能,为复杂的游戏逻辑和计算密集型任务提供了高效的解决方案。 \ No newline at end of file diff --git a/docs/public/demos/worker-system/assets/ecs-framework.umd.js b/docs/public/demos/worker-system/assets/ecs-framework.umd.js deleted file mode 100644 index 8abc622f..00000000 --- a/docs/public/demos/worker-system/assets/ecs-framework.umd.js +++ /dev/null @@ -1,4 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ECS={})}(this,(function(e){"use strict";function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function c(){return c=Object.assign?Object.assign.bind():function(e){for(var t=1;t3?(r=p===i)&&(c=o[(a=o[4])?5:(a=3,3)],o[4]=o[5]=e):o[0]<=f&&((r=n<2&&fi||i>p)&&(o[4]=n,o[5]=i,d.n=p,a=0))}if(r||n>1)return s;throw l=!0,i}return function(r,h,p){if(u>1)throw TypeError("Generator is already running");for(l&&1===h&&f(h,p),a=h,c=p;(t=a<2?e:c)||!l;){o||(a?a<3?(a>1&&(d.n=-1),f(a,c)):d.n=c:d.v=c);try{if(u=2,o){if(a||(r="next"),t=o[r]){if(!(t=t.call(o,c)))throw TypeError("iterator result is not an object");if(!t.done)return t;c=t.value,a<2&&(a=0)}else 1===a&&(t=o.return)&&t.call(o),a<2&&(c=TypeError("The iterator does not provide a '"+r+"' method"),a=1);o=e}else if((t=(l=d.n<0)?c:n.call(i,d))!==s)break}catch(t){o=e,a=1,c=t}finally{u=1}}return{value:t,done:l}}}(n,r,o),!0),u}var s={};function a(){}function c(){}function u(){}t=Object.getPrototypeOf;var h=[][i]?t(t([][i]())):(f(t={},i,(function(){return this})),t),l=u.prototype=a.prototype=Object.create(h);function p(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,u):(e.__proto__=u,f(e,r,"GeneratorFunction")),e.prototype=Object.create(l),e}return c.prototype=u,f(l,"constructor",u),f(u,"constructor",c),c.displayName="GeneratorFunction",f(u,r,"GeneratorFunction"),f(l),f(l,r,"Generator"),f(l,i,(function(){return this})),f(l,"toString",(function(){return"[object Generator]"})),(d=function(){return{w:o,m:p}})()}function f(e,t,n,i){var r=Object.defineProperty;try{r({},"",{})}catch(e){r=0}f=function(e,t,n,i){function o(t,n){f(e,t,(function(e){return this._invoke(t,n,e)}))}t?r?r(e,t,{value:n,enumerable:!i,configurable:!i,writable:!i}):e[t]=n:(o("next",0),o("throw",1),o("return",2))},f(e,t,n,i)}function p(e,t){return p=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},p(e,t)}function m(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var i=n.call(e,t);if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e,"string");return"symbol"==typeof t?t:t+""}function y(e){var t="function"==typeof Map?new Map:void 0;return y=function(e){if(null===e||!function(e){try{return-1!==Function.toString.call(e).indexOf("[native code]")}catch(t){return"function"==typeof e}}(e))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return r(e,arguments,u(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),p(n,e)},y(e)}var v=function(){function e(){this._enabled=!1}var t=e.prototype;return t.setEnabled=function(e){this._enabled!=e&&(this._enabled=e,this._enabled?this.onEnabled():this.onDisabled())},t.onEnabled=function(){},t.onDisabled=function(){},t.update=function(){},s(e,[{key:"enabled",get:function(){return this._enabled},set:function(e){this.setEnabled(e)}}])}(),g=function(){function e(){}return e.update=function(e){this.unscaledDeltaTime=e,this.deltaTime=e*this.timeScale,this.unscaledTotalTime+=this.unscaledDeltaTime,this.totalTime+=this.deltaTime,this.frameCount++},e.sceneChanged=function(){this.frameCount=0,this.totalTime=0,this.unscaledTotalTime=0,this.deltaTime=0,this.unscaledDeltaTime=0},e.checkEvery=function(e,t){return this.totalTime-t>=e},e}();g.deltaTime=0,g.unscaledDeltaTime=0,g.totalTime=0,g.unscaledTotalTime=0,g.timeScale=1,g.frameCount=0;var _,S=function(){function e(){this._timeInSeconds=0,this._repeats=!1,this._isDone=!1,this._elapsedTime=0}var t=e.prototype;return t.getContext=function(){return this.context},t.reset=function(){this._elapsedTime=0},t.stop=function(){this._isDone=!0},t.tick=function(){return!this._isDone&&this._elapsedTime>this._timeInSeconds&&(this._elapsedTime-=this._timeInSeconds,this._onTime(this),this._isDone||this._repeats||(this._isDone=!0)),this._elapsedTime+=g.deltaTime,this._isDone},t.initialize=function(e,t,n,i){this._timeInSeconds=e,this._repeats=t,this.context=n,this._onTime=i.bind(n)},t.unload=function(){this.context=null,this._onTime=null},s(e,[{key:"isDone",get:function(){return this._isDone}},{key:"elapsedTime",get:function(){return this._elapsedTime}}])}(),E=function(e){function t(){var t;return(t=e.apply(this,arguments)||this)._timers=[],t}h(t,e);var n=t.prototype;return n.update=function(){for(var e=this._timers.length-1;e>=0;e--)this._timers[e].tick()&&(this._timers[e].unload(),this._timers.splice(e,1))},n.schedule=function(e,t,n,i){var r=new S;return r.initialize(e,t,n,i),this._timers.push(r),r},t}(v);e.PerformanceWarningType=void 0,(_=e.PerformanceWarningType||(e.PerformanceWarningType={})).HIGH_EXECUTION_TIME="high_execution_time",_.HIGH_MEMORY_USAGE="high_memory_usage",_.HIGH_CPU_USAGE="high_cpu_usage",_.FREQUENT_GC="frequent_gc",_.LOW_FPS="low_fps",_.HIGH_ENTITY_COUNT="high_entity_count";var C=function(){function e(){this._systemData=new Map,this._systemStats=new Map,this._warnings=[],this._isEnabled=!1,this._maxRecentSamples=60,this._maxWarnings=100,this._thresholds={executionTime:{warning:16.67,critical:33.33},memoryUsage:{warning:100,critical:200},cpuUsage:{warning:70,critical:90},fps:{warning:45,critical:30},entityCount:{warning:1e3,critical:5e3}},this._fpsHistory=[],this._lastFrameTime=0,this._frameCount=0,this._fpsUpdateInterval=1e3,this._lastFpsUpdate=0,this._currentFps=60,this._memoryCheckInterval=5e3,this._lastMemoryCheck=0,this._memoryHistory=[],this._gcCount=0,this._lastGcCheck=0,this._gcCheckInterval=1e3}var t=e.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.startMonitoring=function(e){return this._isEnabled?performance.now():0},t.endMonitoring=function(e,t,n){if(void 0===n&&(n=0),this._isEnabled&&0!==t){var i=performance.now(),r=i-t,o={name:e,executionTime:r,entityCount:n,averageTimePerEntity:n>0?r/n:0,lastUpdateTime:i};this._systemData.set(e,o),this.updateStats(e,r)}},t.updateStats=function(e,t){var n=this._systemStats.get(e);n||(n={totalTime:0,averageTime:0,minTime:Number.MAX_VALUE,maxTime:0,executionCount:0,recentTimes:[],standardDeviation:0,percentile95:0,percentile99:0},this._systemStats.set(e,n)),n.totalTime+=t,n.executionCount++,n.averageTime=n.totalTime/n.executionCount,n.minTime=Math.min(n.minTime,t),n.maxTime=Math.max(n.maxTime,t),n.recentTimes.push(t),n.recentTimes.length>this._maxRecentSamples&&n.recentTimes.shift(),this.calculateAdvancedStats(n)},t.calculateAdvancedStats=function(e){if(0!==e.recentTimes.length){var t=e.recentTimes.reduce((function(e,t){return e+t}),0)/e.recentTimes.length,n=e.recentTimes.reduce((function(e,n){return e+Math.pow(n-t,2)}),0)/e.recentTimes.length;e.standardDeviation=Math.sqrt(n);var i=[].concat(e.recentTimes).sort((function(e,t){return e-t})),r=i.length;e.percentile95=i[Math.floor(.95*r)]||0,e.percentile99=i[Math.floor(.99*r)]||0}},t.getSystemData=function(e){return this._systemData.get(e)},t.getSystemStats=function(e){return this._systemStats.get(e)},t.getAllSystemData=function(){return new Map(this._systemData)},t.getAllSystemStats=function(){return new Map(this._systemStats)},t.getPerformanceReport=function(){if(!this._isEnabled)return"Performance monitoring is disabled.";var e=[];e.push("=== ECS Performance Report ==="),e.push("");for(var t,n=a(Array.from(this._systemStats.entries()).sort((function(e,t){return t[1].averageTime-e[1].averageTime})));!(t=n()).done;){var i=t.value,r=i[0],o=i[1],s=this._systemData.get(r);e.push("System: "+r),e.push(" Current: "+(null==s?void 0:s.executionTime.toFixed(2))+"ms ("+(null==s?void 0:s.entityCount)+" entities)"),e.push(" Average: "+o.averageTime.toFixed(2)+"ms"),e.push(" Min/Max: "+o.minTime.toFixed(2)+"ms / "+o.maxTime.toFixed(2)+"ms"),e.push(" Total: "+o.totalTime.toFixed(2)+"ms ("+o.executionCount+" calls)"),null!=s&&s.averageTimePerEntity&&s.averageTimePerEntity>0&&e.push(" Per Entity: "+s.averageTimePerEntity.toFixed(4)+"ms"),e.push("")}var c=Array.from(this._systemData.values()).reduce((function(e,t){return e+t.executionTime}),0);return e.push("Total Frame Time: "+c.toFixed(2)+"ms"),e.push("Systems Count: "+this._systemData.size),e.join("\n")},t.reset=function(){this._systemData.clear(),this._systemStats.clear()},t.resetSystem=function(e){this._systemData.delete(e),this._systemStats.delete(e)},t.getPerformanceWarnings=function(e){void 0===e&&(e=16.67);for(var t,n=[],i=a(this._systemData.entries());!(t=i()).done;){var r=t.value,o=r[0],s=r[1];s.executionTime>e&&n.push(o+": "+s.executionTime.toFixed(2)+"ms (>"+e+"ms)")}return n},t.setMaxRecentSamples=function(e){this._maxRecentSamples=e;for(var t,n=a(this._systemStats.values());!(t=n()).done;)for(var i=t.value;i.recentTimes.length>e;)i.recentTimes.shift()},s(e,[{key:"isEnabled",get:function(){return this._isEnabled}}],[{key:"instance",get:function(){return e._instance||(e._instance=new e),e._instance}}])}(),T=function(){function e(e,t,n){void 0===t&&(t=100),void 0===n&&(n=1024),this._objects=[],this._createFn=e,this._maxSize=t,this._objectSize=n,this._stats={size:0,maxSize:t,totalCreated:0,totalObtained:0,totalReleased:0,hitRate:0,estimatedMemoryUsage:0}}e.getPool=function(t,n,i){void 0===n&&(n=100),void 0===i&&(i=1024);var r=this._pools.get(t);return r||(r=new e((function(){return new t}),n,i),this._pools.set(t,r)),r};var t=e.prototype;return t.obtain=function(){if(this._stats.totalObtained++,this._objects.length>0){var e=this._objects.pop();return this._stats.size--,this._updateHitRate(),this._updateMemoryUsage(),e}return this._stats.totalCreated++,this._updateHitRate(),this._createFn()},t.release=function(e){e&&(this._stats.totalReleased++,this._stats.sizet;){var n=this._objects.pop();n&&(n.reset(),this._stats.size--)}this._updateMemoryUsage()},t.prewarm=function(e){for(var t=Math.min(e,this._maxSize-this._objects.length),n=0;ne&&this.compact(e)},t.getAvailableCount=function(){return this._objects.length},t.isEmpty=function(){return 0===this._objects.length},t.isFull=function(){return this._objects.length>=this._maxSize},e.getAllPoolTypes=function(){return Array.from(this._pools.keys())},e.getAllPoolStats=function(){for(var e,t={},n=a(this._pools);!(e=n()).done;){var i=e.value,r=i[0],o=i[1];t[r.name||r.toString()]=o.getStats()}return t},e.compactAllPools=function(){for(var e,t=a(this._pools.values());!(e=t()).done;){e.value.compact()}},e.clearAllPools=function(){for(var e,t=a(this._pools.values());!(e=t()).done;){e.value.clear()}this._pools.clear()},e.getGlobalStatsString=function(){var e=this.getAllPoolStats(),t=["=== Object Pool Global Statistics ===",""];if(0===Object.keys(e).length)return t.push("No pools registered"),t.join("\n");for(var n=0,i=Object.entries(e);nthis.autoCompactInterval&&(this.compactAllPools(),this.lastCompactTime=e)},t.createPool=function(e,t,n,i){void 0===n&&(n=100),void 0===i&&(i=1024);var r=this.pools.get(e);return r||(r=new T(t,n,i),this.pools.set(e,r)),r},t.removePool=function(e){var t=this.pools.get(e);return!!t&&(t.clear(),this.pools.delete(e),!0)},t.getPoolNames=function(){return Array.from(this.pools.keys())},t.getPoolCount=function(){return this.pools.size},t.compactAllPools=function(){for(var e,t=a(this.pools.values());!(e=t()).done;){e.value.compact()}},t.clearAllPools=function(){for(var e,t=a(this.pools.values());!(e=t()).done;){e.value.clear()}},t.getAllStats=function(){for(var e,t=new Map,n=a(this.pools);!(e=n()).done;){var i=e.value,r=i[0],o=i[1];t.set(r,o.getStats())}return t},t.getGlobalStats=function(){for(var e,t=0,n=0,i=0,r=0,o=0,s=0,c=a(this.pools.values());!(e=c()).done;){var u=e.value.getStats();t+=u.size,n+=u.maxSize,i+=u.totalCreated,r+=u.totalObtained,o+=u.totalReleased,s+=u.estimatedMemoryUsage}return{size:t,maxSize:n,totalCreated:i,totalObtained:r,totalReleased:o,hitRate:0===r?0:(r-i)/r,estimatedMemoryUsage:s}},t.getStatsString=function(){var e=["=== Pool Manager Statistics ===",""];if(0===this.pools.size)return e.push("No pools registered"),e.join("\n");var t=this.getGlobalStats();e.push("Total Pools: "+this.pools.size),e.push("Global Hit Rate: "+(100*t.hitRate).toFixed(1)+"%"),e.push("Global Memory Usage: "+(t.estimatedMemoryUsage/1024).toFixed(1)+" KB"),e.push("");for(var n,i=a(this.pools);!(n=i()).done;){var r=n.value,o=r[0],s=r[1].getStats();e.push(o+":"),e.push(" Size: "+s.size+"/"+s.maxSize),e.push(" Hit Rate: "+(100*s.hitRate).toFixed(1)+"%"),e.push(" Memory: "+(s.estimatedMemoryUsage/1024).toFixed(1)+" KB"),e.push("")}return e.join("\n")},t.setAutoCompactInterval=function(e){this.autoCompactInterval=e},t.prewarmAllPools=function(){for(var e,t=a(this.pools.values());!(e=t()).done;){var n=e.value,i=n.getStats(),r=Math.floor(.2*i.maxSize);n.prewarm(r)}},t.reset=function(){this.clearAllPools(),this.pools.clear(),this.lastCompactTime=0},e}(),w=function(){function e(){}return e.create=function(e){if(e<0||e>=64)throw new Error("Bit index "+e+" out of range [0, 63]");return e<32?{lo:1<>>0,hi:0}},e.hasAny=function(e,t){return 0!==(e.lo&t.lo)||0!==(e.hi&t.hi)},e.hasAll=function(e,t){return(e.lo&t.lo)===t.lo&&(e.hi&t.hi)===t.hi},e.hasNone=function(e,t){return 0===(e.lo&t.lo)&&0===(e.hi&t.hi)},e.isZero=function(e){return 0===e.lo&&0===e.hi},e.equals=function(e,t){return e.lo===t.lo&&e.hi===t.hi},e.setBit=function(e,t){if(t<0||t>=64)throw new Error("Bit index "+t+" out of range [0, 63]");t<32?e.lo|=1<=64)throw new Error("Bit index "+t+" out of range [0, 63]");t<32?e.lo&=~(1<1?n-1:0),r=1;r1?n-1:0),r=1;r1?n-1:0),r=1;r1?n-1:0),r=1;r1?n-1:0),r=1;r2?r-2:0),s=2;s2?r-2:0),s=2;s0?i=this.freeIndices.pop():(i=this._size)>=this._capacity&&this.resize(2*this._capacity),this.entityToIndex.set(e,i),this.indexToEntity[i]=e,this.updateComponentAtIndex(i,t),this._size++}},t.updateComponentAtIndex=function(e,t){var n=this.indexToEntity[e],i=new Map,r=this.type.__highPrecisionFields||new Set,o=this.type.__serializeMapFields||new Set,s=this.type.__serializeSetFields||new Set,a=this.type.__serializeArrayFields||new Set,c=this.type.__deepCopyFields||new Set;for(var u in t)if(t.hasOwnProperty(u)&&"id"!==u){var h=t[u],l=typeof h;if("number"===l)if(r.has(u)||!this.fields.has(u))i.set(u,h);else this.fields.get(u)[e]=h;else if("boolean"===l&&this.fields.has(u)){this.fields.get(u)[e]=h?1:0}else if(this.stringFields.has(u)){this.stringFields.get(u)[e]=String(h)}else if(this.serializedFields.has(u)){this.serializedFields.get(u)[e]=this.serializeValue(h,u,o,s,a)}else c.has(u)?i.set(u,this.deepClone(h)):i.set(u,h)}i.size>0&&this.complexFields.set(n,i)},t.serializeValue=function(t,n,i,r,o){try{return i.has(n)&&t instanceof Map?JSON.stringify(Array.from(t.entries())):r.has(n)&&t instanceof Set?JSON.stringify(Array.from(t)):(o.has(n)&&Array.isArray(t),JSON.stringify(t))}catch(t){return e._logger.warn("SoA序列化字段 "+n+" 失败:",t),"{}"}},t.deserializeValue=function(t,n,i,r,o){try{var s=JSON.parse(t);return i.has(n)?new Map(s):r.has(n)?new Set(s):(o.has(n),s)}catch(t){return e._logger.warn("SoA反序列化字段 "+n+" 失败:",t),null}},t.deepClone=function(e){var t=this;if(null===e||"object"!=typeof e)return e;if(e instanceof Date)return new Date(e.getTime());if(e instanceof Array)return e.map((function(e){return t.deepClone(e)}));if(e instanceof Map){for(var n,i=new Map,r=a(e.entries());!(n=r()).done;){var o=n.value,s=o[0],c=o[1];i.set(s,this.deepClone(c))}return i}if(e instanceof Set){for(var u,h=new Set,l=a(e.values());!(u=l()).done;){var d=u.value;h.add(this.deepClone(d))}return h}var f={};for(var p in e)e.hasOwnProperty(p)&&(f[p]=this.deepClone(e[p]));return f},t.getComponent=function(e){var t=this.entityToIndex.get(e);if(void 0===t)return null;for(var n,i=new this.type,r=this.type.__serializeMapFields||new Set,o=this.type.__serializeSetFields||new Set,s=this.type.__serializeArrayFields||new Set,c=a(this.fields.entries());!(n=c()).done;){var u=n.value,h=u[0],l=u[1][t],d=this.getFieldType(h);i[h]="boolean"===d?1===l:l}for(var f,p=a(this.stringFields.entries());!(f=p()).done;){var m=f.value,y=m[0],v=m[1];i[y]=v[t]}for(var g,_=a(this.serializedFields.entries());!(g=_()).done;){var S=g.value,E=S[0],C=S[1][t];C&&(i[E]=this.deserializeValue(C,E,r,o,s))}var T=this.complexFields.get(e);if(T)for(var b,M=a(T.entries());!(b=M()).done;){var w=b.value,A=w[0],I=w[1];i[A]=I}return i},t.getFieldType=function(e){return typeof(new this.type)[e]},t.hasComponent=function(e){return this.entityToIndex.has(e)},t.removeComponent=function(e){var t=this.entityToIndex.get(e);if(void 0===t)return null;var n=this.getComponent(e);return this.complexFields.delete(e),this.entityToIndex.delete(e),this.freeIndices.push(t),this._size--,n},t.resize=function(e){for(var t,n=a(this.fields.entries());!(t=n()).done;){var i=t.value,r=i[0],o=i[1],s=void 0;(s=o instanceof Float32Array?new Float32Array(e):o instanceof Float64Array?new Float64Array(e):new Int32Array(e)).set(o),this.fields.set(r,s)}for(var c,u=a(this.stringFields.entries());!(c=u()).done;){for(var h=c.value,l=h[0],d=h[1],f=new Array(e),p=0;p=this.maxComponents)throw new Error("Maximum number of component types ("+this.maxComponents+") exceeded");var n=this.nextBitIndex++;return this.componentTypes.set(e,n),this.componentNameToType.set(t,e),this.componentNameToId.set(t,n),n},e.getBitMask=function(e){var t=this.componentTypes.get(e);if(void 0===t){var n=O(e);throw new Error("Component type "+n+" is not registered")}return w.create(t)},e.getBitIndex=function(e){var t=this.componentTypes.get(e);if(void 0===t){var n=O(e);throw new Error("Component type "+n+" is not registered")}return t},e.isRegistered=function(e){return this.componentTypes.has(e)},e.getComponentType=function(e){return this.componentNameToType.get(e)||null},e.getAllRegisteredTypes=function(){return new Map(this.componentTypes)},e.getAllComponentNames=function(){return new Map(this.componentNameToType)},e.getComponentId=function(e){return this.componentNameToId.get(e)},e.registerComponentByName=function(e){if(this.componentNameToId.has(e))return this.componentNameToId.get(e);if(this.nextBitIndex>=this.maxComponents)throw new Error("Maximum number of component types ("+this.maxComponents+") exceeded");var t=this.nextBitIndex++;return this.componentNameToId.set(e,t),t},e.createSingleComponentMask=function(e){var t="single:"+e;if(this.maskCache.has(t))return this.maskCache.get(t);var n=this.getComponentId(e);if(void 0===n)throw new Error("Component type "+e+" is not registered");var i=w.create(n);return this.maskCache.set(t,i),i},e.createComponentMask=function(e){var t="multi:"+[].concat(e).sort().join(",");if(this.maskCache.has(t))return this.maskCache.get(t);for(var n,i=w.clone(w.ZERO),r=a(e);!(n=r()).done;){var o=n.value,s=this.getComponentId(o);if(void 0!==s){var c=w.create(s);w.orInPlace(i,c)}}return this.maskCache.set(t,i),i},e.clearMaskCache=function(){this.maskCache.clear()},e.reset=function(){this.componentTypes.clear(),this.componentNameToType.clear(),this.componentNameToId.clear(),this.maskCache.clear(),this.nextBitIndex=0},e}();F._logger=k("ComponentStorage"),F.componentTypes=new Map,F.componentNameToType=new Map,F.componentNameToId=new Map,F.maskCache=new Map,F.nextBitIndex=0,F.maxComponents=64;var W=function(){function e(e){this.dense=[],this.entityIds=[],this.entityToIndex=new Map,this.componentType=e,F.isRegistered(e)||F.register(e)}var t=e.prototype;return t.addComponent=function(e,t){if(this.entityToIndex.has(e))throw new Error("Entity "+e+" already has component "+O(this.componentType));var n=this.dense.length;this.dense.push(t),this.entityIds.push(e),this.entityToIndex.set(e,n)},t.getComponent=function(e){var t=this.entityToIndex.get(e);return void 0!==t?this.dense[t]:null},t.hasComponent=function(e){return this.entityToIndex.has(e)},t.removeComponent=function(e){var t=this.entityToIndex.get(e);if(void 0===t)return null;var n=this.dense[t],i=this.dense.length-1;if(t!==i){var r=this.dense[i],o=this.entityIds[i];this.dense[t]=r,this.entityIds[t]=o,this.entityToIndex.set(o,t)}return this.dense.pop(),this.entityIds.pop(),this.entityToIndex.delete(e),n},t.forEach=function(e){for(var t=0;t1?t-1:0),i=1;i1?n-1:0),r=1;r=0;t--)e.push(this.buffer[t].id),this.buffer[t].destroy();if(this._scene&&this._scene.identifierPool)for(var n,i=a(e);!(n=i()).done;){var r=n.value;this._scene.identifierPool.checkIn(r)}this.buffer.length=0,this._idToEntity.clear(),this._nameToEntities.clear(),this._entitiesToAdd.length=0,this._entitiesToRemove.length=0},t.updateLists=function(){if(this._entitiesToAdd.length>0){for(var e,t=a(this._entitiesToAdd);!(e=t()).done;){var n=e.value;this.addImmediate(n)}this._entitiesToAdd.length=0}if(this._entitiesToRemove.length>0){for(var i,r=a(this._entitiesToRemove);!(i=r()).done;){var o=i.value;this.removeImmediate(o)}this._entitiesToRemove.length=0}},t.update=function(){this._isUpdating=!0;try{if(this._enableEntityDirectUpdate)for(var e=0;e0?t[0]:null},t.findEntitiesByName=function(e){return this._nameToEntities.get(e)||[]},t.findEntityById=function(e){return this._idToEntity.get(e)||null},t.findEntitiesByTag=function(e){for(var t,n=[],i=a(this.buffer);!(t=i()).done;){var r=t.value;r.tag===e&&n.push(r)}return n},t.findEntitiesWithComponent=function(e){for(var t,n=[],i=a(this.buffer);!(t=i()).done;){var r=t.value;r.hasComponent(e)&&n.push(r)}return n},t.forEach=function(e){for(var t,n=a(this.buffer);!(t=n()).done;){e(t.value)}},t.forEachWhere=function(e,t){for(var n,i=a(this.buffer);!(n=i()).done;){var r=n.value;e(r)&&t(r)}},t.updateNameIndex=function(e,t){if(e.name)if(t){var n=this._nameToEntities.get(e.name);n||(n=[],this._nameToEntities.set(e.name,n)),n.push(e)}else{var i=this._nameToEntities.get(e.name);if(i){var r=i.indexOf(e);-1!==r&&(i.splice(r,1),0===i.length&&this._nameToEntities.delete(e.name))}}},t.getStats=function(){for(var e,t=0,n=a(this.buffer);!(e=n()).done;){var i=e.value;i.enabled&&!i.isDestroyed&&t++}return{totalEntities:this.buffer.length,activeEntities:t,pendingAdd:this._entitiesToAdd.length,pendingRemove:this._entitiesToRemove.length,nameIndexSize:this._nameToEntities.size}},s(e,[{key:"count",get:function(){return this.buffer.length}}])}(),Y=function(){function e(){this._processors=[],this._isDirty=!1}var t=e.prototype;return t.setDirty=function(){this._isDirty=!0},t.add=function(e){this._processors.push(e),this.setDirty()},t.remove=function(e){var t=this._processors.indexOf(e);-1!==t&&this._processors.splice(t,1)},t.getProcessor=function(e){for(var t,n=a(this._processors);!(t=n()).done;){var i=t.value;if(i instanceof e)return i}return null},t.begin=function(){this.sortProcessors()},t.end=function(){for(var t,n=a(this._processors);!(t=n()).done;){var i=t.value;try{i.reset()}catch(t){e._logger.error("Error in processor "+L(i)+":",t)}}this._isDirty=!1,this._processors.length=0},t.update=function(){this.sortProcessors();for(var t,n=a(this._processors);!(t=n()).done;){var i=t.value;try{i.update()}catch(t){e._logger.error("Error in processor "+L(i)+":",t)}}},t.lateUpdate=function(){for(var e,t=a(this._processors);!(e=t()).done;){e.value.lateUpdate()}},t.sortProcessors=function(){this._isDirty&&(this._processors.sort((function(e,t){return e.updateOrder-t.updateOrder})),this._isDirty=!1)},s(e,[{key:"processors",get:function(){return this._processors}},{key:"count",get:function(){return this._processors.length}}])}();Y._logger=k("EntityProcessorList");var Q=function(){function e(e,t){void 0===e&&(e=100),void 0===t&&(t=1024),this._nextAvailableIndex=0,this._freeIndices=[],this._generations=new Map,this._pendingRecycle=[],this._recycleDelay=100,this._stats={totalAllocated:0,totalRecycled:0,currentActive:0,memoryExpansions:0},this._recycleDelay=e,this._expansionBlockSize=t,this._preAllocateGenerations(0,this._expansionBlockSize)}var t=e.prototype;return t.checkOut=function(){var t;if(this._processDelayedRecycle(),this._freeIndices.length>0)t=this._freeIndices.pop();else{if(this._nextAvailableIndex>e.MAX_INDEX)throw new Error("实体索引已达到框架设计限制 ("+e.MAX_INDEX+")。这意味着您已经分配了超过65535个不同的实体索引。这是16位索引设计的限制,考虑优化实体回收策略或升级到64位ID设计。");t=this._nextAvailableIndex++,this._ensureGenerationCapacity(t)}var n=this._generations.get(t)||1;return this._stats.totalAllocated++,this._stats.currentActive++,this._packId(t,n)},t.checkIn=function(e){var t=this._unpackIndex(e),n=this._unpackGeneration(e);return!!this._isValidId(t,n)&&(!this._pendingRecycle.some((function(e){return e.index===t&&e.generation===n}))&&(this._pendingRecycle.push({index:t,generation:n,timestamp:Date.now()}),this._stats.currentActive--,this._stats.totalRecycled++,!0))},t.isValid=function(e){var t=this._unpackIndex(e),n=this._unpackGeneration(e);return this._isValidId(t,n)},t.getStats=function(){for(var t,n=0,i=0,r=a(this._generations);!(t=r()).done;){var o=t.value,s=o[0],c=o[1];s0?n/i:1;return{totalAllocated:this._stats.totalAllocated,totalRecycled:this._stats.totalRecycled,currentActive:this._stats.currentActive,currentlyFree:this._freeIndices.length,pendingRecycle:this._pendingRecycle.length,maxPossibleEntities:e.MAX_INDEX+1,maxUsedIndex:this._nextAvailableIndex-1,memoryUsage:this._calculateMemoryUsage(),memoryExpansions:this._stats.memoryExpansions,averageGeneration:Math.round(100*u)/100,generationStorageSize:this._generations.size}},t.forceProcessDelayedRecycle=function(){this._processDelayedRecycle(!0)},t._processDelayedRecycle=function(t){if(void 0===t&&(t=!1),0!==this._pendingRecycle.length){for(var n,i=Date.now(),r=[],o=[],s=a(this._pendingRecycle);!(n=s()).done;){var c=n.value;t||i-c.timestamp>=this._recycleDelay?r.push(c):o.push(c)}for(var u=0,h=r;ue.MAX_GENERATION&&(d=1),this._generations.set(l.index,d),this._freeIndices.push(l.index)}}this._pendingRecycle=o}},t._preAllocateGenerations=function(t,n){for(var i=0;i>>16&65535},t._isValidId=function(e,t){if(e<0||e>=this._nextAvailableIndex)return!1;var n=this._generations.get(e);return void 0!==n&&n===t},e}();Q.MAX_INDEX=65535,Q.MAX_GENERATION=65535;var V=function(){function e(){this._dense=[],this._sparse=new Map}var t=e.prototype;return t.add=function(e){if(this._sparse.has(e))return!1;var t=this._dense.length;return this._dense.push(e),this._sparse.set(e,t),!0},t.remove=function(e){var t=this._sparse.get(e);if(void 0===t)return!1;var n=this._dense.length-1;if(t!==n){var i=this._dense[n];this._dense[t]=i,this._sparse.set(i,t)}return this._dense.pop(),this._sparse.delete(e),!0},t.has=function(e){return this._sparse.has(e)},t.getIndex=function(e){return this._sparse.get(e)},t.getByIndex=function(e){return this._dense[e]},t.forEach=function(e){for(var t=0;t=this._dense.length||this._dense[s]!==o)return!1}return!0},s(e,[{key:"size",get:function(){return this._dense.length}},{key:"isEmpty",get:function(){return 0===this._dense.length}}])}(),X=function(e){function t(){return e.call(this)||this}return h(t,e),t.prototype.reset=function(){this.clear()},t}(y(Set)),Z=function(){function e(){this._componentMasks=[],this._componentToEntities=new Map,this._entities=new V}var t=e.prototype;return t.addEntity=function(e){this._entities.has(e)&&this.removeEntity(e);for(var t,n=w.clone(w.ZERO),i=new Set,r=a(e.components);!(t=r()).done;){var o=t.value.constructor;i.add(o),F.isRegistered(o)||F.register(o);var s=F.getBitMask(o);w.orInPlace(n,s)}this._entities.add(e);for(var c=this._entities.getIndex(e);this._componentMasks.length<=c;)this._componentMasks.push(w.clone(w.ZERO));this._componentMasks[c]=n,this.updateComponentMappings(e,i,!0)},t.removeEntity=function(e){var t=this._entities.getIndex(e);if(void 0!==t){var n=this.getEntityComponentTypes(e);this.updateComponentMappings(e,n,!1),this._entities.remove(e);var i=this._componentMasks.length-1;t!==i&&(this._componentMasks[t]=this._componentMasks[i]),this._componentMasks.pop()}},t.queryByComponent=function(e){var t=this._componentToEntities.get(e);return t?new Set(t):new Set},t.queryMultipleAnd=function(t){var n=this;if(0===t.length)return new Set;if(1===t.length)return this.queryByComponent(t[0]);for(var i,r=w.clone(w.ZERO),o=a(t);!(i=o()).done;){var s=i.value;if(!F.isRegistered(s))return new Set;var c=F.getBitMask(s);w.orInPlace(r,c)}var u=e._entitySetPool.obtain();return this._entities.forEach((function(e,t){var i=n._componentMasks[t];w.hasAll(i,r)&&u.add(e)})),u},t.queryMultipleOr=function(t){var n=this;if(0===t.length)return new Set;if(1===t.length)return this.queryByComponent(t[0]);for(var i,r=w.clone(w.ZERO),o=a(t);!(i=o()).done;){var s=i.value;if(F.isRegistered(s)){var c=F.getBitMask(s);w.orInPlace(r,c)}}if(w.equals(r,w.ZERO))return new Set;var u=e._entitySetPool.obtain();return this._entities.forEach((function(e,t){var i=n._componentMasks[t];w.hasAny(i,r)&&u.add(e)})),u},t.hasComponent=function(e,t){var n=this._entities.getIndex(e);if(void 0===n)return!1;if(!F.isRegistered(t))return!1;var i=this._componentMasks[n],r=F.getBitMask(t);return w.hasAny(i,r)},t.getEntityMask=function(e){var t=this._entities.getIndex(e);if(void 0!==t)return this._componentMasks[t]},t.getAllEntities=function(){return this._entities.toArray()},t.forEach=function(e){var t=this;this._entities.forEach((function(n,i){e(n,t._componentMasks[i],i)}))},t.clear=function(){this._entities.clear(),this._componentMasks.length=0;for(var t,n=a(this._componentToEntities.values());!(t=n()).done;){var i=t.value;e._entitySetPool.release(i)}this._componentToEntities.clear()},t.getMemoryStats=function(){for(var e,t=this._entities.getMemoryStats(),n=16*this._componentMasks.length,i=16*this._componentToEntities.size,r=a(this._componentToEntities.values());!(e=r()).done;){i+=8*e.value.size}return{entitiesMemory:t.totalMemory,masksMemory:n,mappingsMemory:i,totalMemory:t.totalMemory+n+i}},t.validate=function(){if(!this._entities.validate())return!1;if(this._componentMasks.length!==this._entities.size)return!1;for(var e,t=new Set,n=a(this._componentToEntities.values());!(e=n()).done;)for(var i,r=a(e.value);!(i=r()).done;){var o=i.value;t.add(o)}for(var s,c=a(t);!(s=c()).done;){var u=s.value;if(!this._entities.has(u))return!1}return!0},t.getEntityComponentTypes=function(e){for(var t,n=new Set,i=a(e.components);!(t=i()).done;){var r=t.value;n.add(r.constructor)}return n},t.updateComponentMappings=function(t,n,i){for(var r,o=a(n);!(r=o()).done;){var s=r.value,c=this._componentToEntities.get(s);i?(c||(c=e._entitySetPool.obtain(),this._componentToEntities.set(s,c)),c.add(t)):c&&(c.delete(t),0===c.size&&(this._componentToEntities.delete(s),e._entitySetPool.release(c)))}},s(e,[{key:"size",get:function(){return this._entities.size}},{key:"isEmpty",get:function(){return this._entities.isEmpty}}])}();Z._entitySetPool=T.getPool(X,50,512);var K,J=function(){function e(){this._queryCount=0,this._totalQueryTime=0,this._lastUpdated=Date.now(),this._sparseSet=new Z}var t=e.prototype;return t.addEntity=function(e){this._sparseSet.addEntity(e),this._lastUpdated=Date.now()},t.removeEntity=function(e){this._sparseSet.removeEntity(e),this._lastUpdated=Date.now()},t.query=function(e){var t=performance.now(),n=this._sparseSet.queryByComponent(e);return this._queryCount++,this._totalQueryTime+=performance.now()-t,n},t.queryMultiple=function(e,t){var n,i=performance.now();return n=0===e.length?new Set:1===e.length?this.query(e[0]):"AND"===t?this._sparseSet.queryMultipleAnd(e):this._sparseSet.queryMultipleOr(e),this._queryCount++,this._totalQueryTime+=performance.now()-i,n},t.clear=function(){this._sparseSet.clear(),this._lastUpdated=Date.now()},t.getStats=function(){var e=this._sparseSet.getMemoryStats();return{size:this._sparseSet.size,memoryUsage:e.totalMemory,queryCount:this._queryCount,avgQueryTime:this._queryCount>0?this._totalQueryTime/this._queryCount:0,lastUpdated:this._lastUpdated}},e}(),$=function(){function e(){this._index=new J}var t=e.prototype;return t.addEntity=function(e){this._index.addEntity(e)},t.removeEntity=function(e){this._index.removeEntity(e)},t.query=function(e){return this._index.query(e)},t.queryMultiple=function(e,t){return this._index.queryMultiple(e,t)},t.getStats=function(){return this._index.getStats()},t.clear=function(){this._index.clear()},e}(),ee=function(){function e(){this._archetypes=new Map,this._entityToArchetype=new Map,this._componentToArchetypes=new Map,this._queryCache=new Map,this._cacheTimeout=5e3,this._maxCacheSize=100}var t=e.prototype;return t.addEntity=function(e){var t=this.getEntityComponentTypes(e),n=this.generateArchetypeId(t),i=this._archetypes.get(n);i||(i=this.createArchetype(t)),i.entities.push(e),i.updatedAt=Date.now(),this._entityToArchetype.set(e,i),this.updateComponentIndexes(i,t,!0),this.invalidateQueryCache()},t.removeEntity=function(e){var t=this._entityToArchetype.get(e);if(t){var n=t.entities.indexOf(e);-1!==n&&(t.entities.splice(n,1),t.updatedAt=Date.now()),this._entityToArchetype.delete(e),this.invalidateQueryCache()}},t.updateEntity=function(e){var t=this._entityToArchetype.get(e),n=this.getEntityComponentTypes(e),i=this.generateArchetypeId(n);if(!t||t.id!==i){if(t){var r=t.entities.indexOf(e);-1!==r&&(t.entities.splice(r,1),t.updatedAt=Date.now())}var o=this._archetypes.get(i);o||(o=this.createArchetype(n)),o.entities.push(e),o.updatedAt=Date.now(),this._entityToArchetype.set(e,o),t&&this.updateComponentIndexes(t,t.componentTypes,!1),this.updateComponentIndexes(o,n,!0),this.invalidateQueryCache()}},t.queryArchetypes=function(e,t){void 0===t&&(t="AND");var n=performance.now(),i=t+":"+e.map((function(e){return O(e)})).sort().join(","),r=this._queryCache.get(i);if(r&&Date.now()-r.timestamp0&&this.clearQueryCache()}},t.addEntitiesUnchecked=function(e){if(0!==e.length){for(var t,n=a(e);!(t=n()).done;){var i=t.value;this.entities.push(i)}for(var r,o=a(e);!(r=o()).done;){var s=r.value;this.addEntityToIndexes(s),this.componentIndexManager.addEntity(s),this.archetypeSystem.addEntity(s)}this.clearQueryCache()}},t.removeEntity=function(e){var t=this.entities.indexOf(e);-1!==t&&(this.entities.splice(t,1),this.removeEntityFromIndexes(e),this.componentIndexManager.removeEntity(e),this.archetypeSystem.removeEntity(e),this.clearQueryCache(),this._version++)},t.updateEntity=function(e){this.entities.includes(e)?(this.removeEntityFromIndexes(e),this.archetypeSystem.updateEntity(e),this.componentIndexManager.removeEntity(e),this.componentIndexManager.addEntity(e),this.addEntityToIndexes(e),this.clearQueryCache(),this._version++):this.addEntity(e)},t.addEntityToIndexes=function(e){var t=e.componentMask.toString();(this.entityIndex.byMask.get(t)||this.createAndSetMaskIndex(t)).add(e);for(var n=e.components,i=0;i0){this.queryStats.archetypeHits++;for(var u,h=a(c.archetypes);!(u=h()).done;){var l,d=u.value;(l=s).push.apply(l,d.entities)}}else try{if(1===n.length){this.queryStats.indexHits++;var f=this.componentIndexManager.query(n[0]);s=Array.from(f)}else{var p=this.componentIndexManager.queryMultiple(n,"AND");s=Array.from(p)}}catch(e){s=[]}return this.addToCache(r,s),{entities:s,count:s.length,executionTime:performance.now()-e,fromCache:!1}},t.queryMultipleComponents=function(e){for(var t,n=null,i=1/0,r=a(e);!(t=r()).done;){var o=t.value,s=this.entityIndex.byComponentType.get(o);if(!s||0===s.size)return[];s.size0){this.queryStats.archetypeHits++,s=[];for(var u,h=a(c.archetypes);!(u=h()).done;){var l,d=u.value;(l=s).push.apply(l,d.entities)}}else{var f=this.componentIndexManager.queryMultiple(n,"OR");s=Array.from(f)}return this.addToCache(r,s),{entities:s,count:s.length,executionTime:performance.now()-e,fromCache:!1}},t.queryNone=function(){var e=performance.now();this.queryStats.totalQueries++;for(var t=arguments.length,n=new Array(t),i=0;ithis.cacheTimeout||t.version!==this._version?(this.queryCache.delete(e),null):(t.hitCount++,t.entities):null},t.addToCache=function(e,t){this.queryCache.size>=this.cacheMaxSize&&this.cleanupCache(),this.queryCache.set(e,{entities:t,timestamp:Date.now(),hitCount:0,version:this._version})},t.cleanupCache=function(){for(var e,t=Date.now(),n=a(this.queryCache.entries());!(e=n()).done;){var i=e.value,r=i[0];t-i[1].timestamp>this.cacheTimeout&&this.queryCache.delete(r)}if(this.queryCache.size>=this.cacheMaxSize){for(var o,s=1/0,c="",u=1/0,h=a(this.queryCache.entries());!(o=h()).done;){var l=o.value,d=l[0],f=l[1];(f.hitCount0?(this.queryStats.cacheHits/this.queryStats.totalQueries*100).toFixed(2)+"%":"0%"}),optimizationStats:{componentIndex:this.componentIndexManager.getStats(),archetypeSystem:this.archetypeSystem.getAllArchetypes().map((function(e){return{id:e.id,componentTypes:e.componentTypes.map((function(e){return O(e)})),entityCount:e.entities.length}}))},cacheStats:{size:this.queryCache.size,hitRate:this.queryStats.totalQueries>0?(this.queryStats.cacheHits/this.queryStats.totalQueries*100).toFixed(2)+"%":"0%"}}},t.getEntityArchetype=function(e){return this.archetypeSystem.getEntityArchetype(e)},s(e,[{key:"version",get:function(){return this._version}}])}(),re=function(){function e(e){this._logger=k("QueryBuilder"),this.conditions=[],this.querySystem=e}var t=e.prototype;return t.withAll=function(){for(var e=arguments.length,t=new Array(e),n=0;n0},t.getListenerCount=function(e){var t=this.listeners.get(e);return t?t.length:0},t.clear=function(){this.listeners.clear(),this.stats.clear(),this.clearAllBatches()},t.setMaxListeners=function(e){this.maxListeners=e},t.addListener=function(t,n,i){var r=this.listeners.get(t);if(r||(r=[],this.listeners.set(t,r)),r.length>=this.maxListeners)return e._logger.warn("事件类型 "+t+" 的监听器数量超过最大限制 ("+this.maxListeners+")"),"";var o="listener_"+this.nextListenerId++,s={handler:n,config:c({priority:0},i),id:o};return r.push(s),this.stats.has(t)||this.stats.set(t,this.createEmptyStats(t)),o},t.executeEvent=function(){var t=i(d().m((function t(n,r){var o,s,c,u,h,l,f,p,m,y;return d().w((function(t){for(;;)switch(t.n){case 0:if((o=this.listeners.get(n))&&0!==o.length){t.n=1;break}return t.a(2);case 1:for(s=performance.now(),c=[],u=this.sortListenersByPriority(o),h=u.filter((function(e){return!e.config.async})),l=u.filter((function(e){return e.config.async})),f=a(h);!(p=f()).done;){m=p.value;try{m.config.context?m.handler.call(m.config.context,r):m.handler(r),m.config.once&&c.push(m.id)}catch(t){e._logger.error("同步事件处理器执行错误 "+n+":",t)}}return y=l.map(function(){var t=i(d().m((function t(i){var o;return d().w((function(t){for(;;)switch(t.p=t.n){case 0:if(t.p=0,!i.config.context){t.n=2;break}return t.n=1,i.handler.call(i.config.context,r);case 1:t.n=3;break;case 2:return t.n=3,i.handler(r);case 3:i.config.once&&c.push(i.id),t.n=5;break;case 4:t.p=4,o=t.v,e._logger.error("异步事件处理器执行错误 "+n+":",o);case 5:return t.a(2)}}),t,null,[[0,4]])})));return function(e){return t.apply(this,arguments)}}()),t.n=2,Promise.all(y);case 2:this.removeListeners(n,c),this.updateStats(n,performance.now()-s);case 3:return t.a(2)}}),t,this)})));return function(e,n){return t.apply(this,arguments)}}(),t.sortListenersByPriority=function(e){return e.slice().sort((function(e,t){return(t.config.priority||0)-(e.config.priority||0)}))},t.removeListeners=function(e,t){if(0!==t.length){var n=this.listeners.get(e);if(n){for(var i,r=function(){var e=i.value,t=n.findIndex((function(t){return t.id===e}));-1!==t&&n.splice(t,1)},o=a(t);!(i=o()).done;)r();0===n.length&&(this.listeners.delete(e),this.stats.delete(e))}}},t.addToBatch=function(e,t){var n=this,i=this.batchQueue.get(e);i||(i=[],this.batchQueue.set(e,i)),i.push(t);var r=this.batchConfigs.get(e);if(i.length>=r.batchSize)this.flushBatch(e);else if(!this.batchTimers.has(e)){var o=setTimeout((function(){n.flushBatch(e)}),r.delay);this.batchTimers.set(e,o)}},t.processBatch=function(){var e=i(d().m((function e(t,n){var i;return d().w((function(e){for(;;)switch(e.n){case 0:return i={type:t,events:n,count:n.length,timestamp:Date.now()},e.n=1,this.executeEvent(t+":batch",i);case 1:return e.a(2)}}),e,this)})));return function(t,n){return e.apply(this,arguments)}}(),t.clearBatch=function(e){this.batchQueue.delete(e);var t=this.batchTimers.get(e);t&&(clearTimeout(t),this.batchTimers.delete(e))},t.clearAllBatches=function(){this.batchQueue.clear();for(var e,t=a(this.batchTimers.values());!(e=t()).done;){var n=e.value;clearTimeout(n)}this.batchTimers.clear(),this.batchConfigs.clear()},t.updateStats=function(e,t){var n=this.stats.get(e);n||(n=this.createEmptyStats(e),this.stats.set(e,n)),n.triggerCount++,n.totalExecutionTime+=t,n.averageExecutionTime=n.totalExecutionTime/n.triggerCount,n.lastTriggerTime=Date.now(),n.listenerCount=this.getListenerCount(e)},t.createEmptyStats=function(e){return{eventType:e,listenerCount:0,triggerCount:0,totalExecutionTime:0,averageExecutionTime:0,lastTriggerTime:0}},e}();oe._logger=k("EventSystem"),e.ECSEventType=void 0,(te=e.ECSEventType||(e.ECSEventType={})).ENTITY_CREATED="entity:created",te.ENTITY_DESTROYED="entity:destroyed",te.ENTITY_ENABLED="entity:enabled",te.ENTITY_DISABLED="entity:disabled",te.ENTITY_TAG_ADDED="entity:tag:added",te.ENTITY_TAG_REMOVED="entity:tag:removed",te.ENTITY_NAME_CHANGED="entity:name:changed",te.COMPONENT_ADDED="component:added",te.COMPONENT_REMOVED="component:removed",te.COMPONENT_MODIFIED="component:modified",te.COMPONENT_ENABLED="component:enabled",te.COMPONENT_DISABLED="component:disabled",te.SYSTEM_ADDED="system:added",te.SYSTEM_REMOVED="system:removed",te.SYSTEM_ENABLED="system:enabled",te.SYSTEM_DISABLED="system:disabled",te.SYSTEM_PROCESSING_START="system:processing:start",te.SYSTEM_PROCESSING_END="system:processing:end",te.SYSTEM_ERROR="system:error",te.SCENE_CREATED="scene:created",te.SCENE_DESTROYED="scene:destroyed",te.SCENE_ACTIVATED="scene:activated",te.SCENE_DEACTIVATED="scene:deactivated",te.SCENE_PAUSED="scene:paused",te.SCENE_RESUMED="scene:resumed",te.QUERY_EXECUTED="query:executed",te.QUERY_CACHE_HIT="query:cache:hit",te.QUERY_CACHE_MISS="query:cache:miss",te.QUERY_OPTIMIZED="query:optimized",te.PERFORMANCE_WARNING="performance:warning",te.PERFORMANCE_CRITICAL="performance:critical",te.MEMORY_USAGE_HIGH="memory:usage:high",te.FRAME_RATE_DROP="frame:rate:drop",te.INDEX_CREATED="index:created",te.INDEX_UPDATED="index:updated",te.INDEX_OPTIMIZED="index:optimized",te.ARCHETYPE_CREATED="archetype:created",te.ARCHETYPE_ENTITY_ADDED="archetype:entity:added",te.ARCHETYPE_ENTITY_REMOVED="archetype:entity:removed",te.DIRTY_MARK_ADDED="dirty:mark:added",te.DIRTY_BATCH_PROCESSED="dirty:batch:processed",te.ERROR_OCCURRED="error:occurred",te.WARNING_ISSUED="warning:issued",te.FRAMEWORK_INITIALIZED="framework:initialized",te.FRAMEWORK_SHUTDOWN="framework:shutdown",te.DEBUG_INFO="debug:info",te.DEBUG_STATS_UPDATED="debug:stats:updated",e.EventPriority=void 0,(ne=e.EventPriority||(e.EventPriority={}))[ne.LOWEST=0]="LOWEST",ne[ne.LOW=25]="LOW",ne[ne.NORMAL=50]="NORMAL",ne[ne.HIGH=75]="HIGH",ne[ne.HIGHEST=100]="HIGHEST",ne[ne.CRITICAL=200]="CRITICAL";var se={ENTITY:{CREATED:e.ECSEventType.ENTITY_CREATED,DESTROYED:e.ECSEventType.ENTITY_DESTROYED,ENABLED:e.ECSEventType.ENTITY_ENABLED,DISABLED:e.ECSEventType.ENTITY_DISABLED,TAG_ADDED:e.ECSEventType.ENTITY_TAG_ADDED,TAG_REMOVED:e.ECSEventType.ENTITY_TAG_REMOVED,NAME_CHANGED:e.ECSEventType.ENTITY_NAME_CHANGED},COMPONENT:{ADDED:e.ECSEventType.COMPONENT_ADDED,REMOVED:e.ECSEventType.COMPONENT_REMOVED,MODIFIED:e.ECSEventType.COMPONENT_MODIFIED,ENABLED:e.ECSEventType.COMPONENT_ENABLED,DISABLED:e.ECSEventType.COMPONENT_DISABLED},SYSTEM:{ADDED:e.ECSEventType.SYSTEM_ADDED,REMOVED:e.ECSEventType.SYSTEM_REMOVED,ENABLED:e.ECSEventType.SYSTEM_ENABLED,DISABLED:e.ECSEventType.SYSTEM_DISABLED,PROCESSING_START:e.ECSEventType.SYSTEM_PROCESSING_START,PROCESSING_END:e.ECSEventType.SYSTEM_PROCESSING_END,ERROR:e.ECSEventType.SYSTEM_ERROR},PERFORMANCE:{WARNING:e.ECSEventType.PERFORMANCE_WARNING,CRITICAL:e.ECSEventType.PERFORMANCE_CRITICAL,MEMORY_HIGH:e.ECSEventType.MEMORY_USAGE_HIGH,FRAME_DROP:e.ECSEventType.FRAME_RATE_DROP}},ae=function(){function e(){}return e.isValid=function(e){return this.validTypes.has(e)},e.getAllValidTypes=function(){return Array.from(this.validTypes)},e.addCustomType=function(e){this.validTypes.add(e)},e.removeCustomType=function(e){this.validTypes.delete(e)},e}();ae.validTypes=new Set([].concat(Object.values(e.ECSEventType),Object.values(se.ENTITY),Object.values(se.COMPONENT),Object.values(se.SYSTEM),Object.values(se.PERFORMANCE)));var ce=function(){function t(e){void 0===e&&(e=!1),this.eventIdCounter=0,this.isDebugMode=!1,this.eventSystem=new oe,this.isDebugMode=e}var n=t.prototype;return n.emit=function(e,n,i){void 0===i&&(i=!1),this.validateEventType(e);var r=i?this.enhanceEventData(e,n):n;this.isDebugMode&&t._logger.info("发射事件: "+e,r),this.eventSystem.emitSync(e,r)},n.emitAsync=function(){var e=i(d().m((function e(n,i,r){var o;return d().w((function(e){for(;;)switch(e.n){case 0:return void 0===r&&(r=!1),this.validateEventType(n),o=r?this.enhanceEventData(n,i):i,this.isDebugMode&&t._logger.info("发射异步事件: "+n,o),e.n=1,this.eventSystem.emit(n,o);case 1:return e.a(2)}}),e,this)})));return function(t,n,i){return e.apply(this,arguments)}}(),n.on=function(n,i,r){void 0===r&&(r={}),this.validateEventType(n);var o={once:r.once||!1,priority:r.priority||e.EventPriority.NORMAL,async:r.async||!1,context:r.context};return this.isDebugMode&&t._logger.info("添加监听器: "+n,o),this.eventSystem.on(n,i,o)},n.once=function(e,t,n){return void 0===n&&(n={}),this.on(e,t,c({},n,{once:!0}))},n.onAsync=function(e,t,n){return void 0===n&&(n={}),this.on(e,t,c({},n,{async:!0}))},n.off=function(e,n){return this.isDebugMode&&t._logger.info("移除监听器: "+n+" 事件: "+e),this.eventSystem.off(e,n)},n.offAll=function(e){this.isDebugMode&&t._logger.info("移除所有监听器: "+e),this.eventSystem.offAll(e)},n.hasListeners=function(e){return this.eventSystem.hasListeners(e)},n.getStats=function(e){var t=this,n=this.eventSystem.getStats(e);if(n instanceof Map){var i=new Map;return n.forEach((function(e,n){i.set(n,t.convertEventStats(e))})),i}return this.convertEventStats(n)},n.clear=function(){this.isDebugMode&&t._logger.info("清空所有监听器"),this.eventSystem.clear()},n.setEnabled=function(e){this.eventSystem.setEnabled(e)},n.setDebugMode=function(e){this.isDebugMode=e},n.setMaxListeners=function(e){this.eventSystem.setMaxListeners(e)},n.getListenerCount=function(e){return this.eventSystem.getListenerCount(e)},n.setBatchConfig=function(e,t,n){this.eventSystem.setBatchConfig(e,{batchSize:t,delay:n,enabled:!0})},n.flushBatch=function(e){this.eventSystem.flushBatch(e)},n.resetStats=function(e){this.eventSystem.resetStats(e)},n.emitEntityCreated=function(t){this.emit(e.ECSEventType.ENTITY_CREATED,t)},n.emitEntityDestroyed=function(t){this.emit(e.ECSEventType.ENTITY_DESTROYED,t)},n.emitComponentAdded=function(t){this.emit(e.ECSEventType.COMPONENT_ADDED,t)},n.emitComponentRemoved=function(t){this.emit(e.ECSEventType.COMPONENT_REMOVED,t)},n.emitSystemAdded=function(t){this.emit(e.ECSEventType.SYSTEM_ADDED,t)},n.emitSystemRemoved=function(t){this.emit(e.ECSEventType.SYSTEM_REMOVED,t)},n.emitSceneChanged=function(t){this.emit(e.ECSEventType.SCENE_ACTIVATED,t)},n.emitPerformanceWarning=function(t){this.emit(e.ECSEventType.PERFORMANCE_WARNING,t)},n.onEntityCreated=function(t,n){return this.on(e.ECSEventType.ENTITY_CREATED,t,n)},n.onComponentAdded=function(t,n){return this.on(e.ECSEventType.COMPONENT_ADDED,t,n)},n.onSystemError=function(t,n){return this.on(e.ECSEventType.SYSTEM_ERROR,t,n)},n.onPerformanceWarning=function(t,n){return this.on(e.ECSEventType.PERFORMANCE_WARNING,t,n)},n.validateEventType=function(e){this.isDebugMode&&(ae.isValid(e)||(t._logger.warn("未知事件类型: "+e),ae.addCustomType(e)))},n.enhanceEventData=function(e,t){if(null==t)return{timestamp:Date.now(),eventId:e+"_"+ ++this.eventIdCounter,source:"EventBus"};var n=t;return n.timestamp||(n.timestamp=Date.now()),n.eventId||(n.eventId=e+"_"+ ++this.eventIdCounter),n.source||(n.source="EventBus"),n},n.convertEventStats=function(e){return{eventType:e.eventType,listenerCount:e.listenerCount,triggerCount:e.triggerCount,totalExecutionTime:e.totalExecutionTime,averageExecutionTime:e.averageExecutionTime,lastTriggerTime:e.lastTriggerTime}},t}();ce._logger=k("EventBus");var ue=function(){function e(){}return e.getInstance=function(e){return void 0===e&&(e=!1),this.instance||(this.instance=new ce(e)),this.instance},e.reset=function(e){return void 0===e&&(e=!1),this.instance&&this.instance.clear(),this.instance=new ce(e),this.instance},e}();var he=function(){function e(e){var t=this;this.name="",this._didSceneBegin=!1,this.entities=new G(this),this.entityProcessors=new Y,this.identifierPool=new Q,this.componentStorageManager=new q,this.querySystem=new ie,this.eventSystem=new oe,null!=e&&e.name&&(this.name=e.name),void 0!==(null==e?void 0:e.enableEntityDirectUpdate)&&this.entities.setEnableEntityDirectUpdate(e.enableEntityDirectUpdate),U.eventBus||(U.eventBus=new ce(!1)),U.eventBus&&U.eventBus.onComponentAdded((function(e){t.eventSystem.emitSync("component:added",e)}))}var t=e.prototype;return t.initialize=function(){},t.onStart=function(){},t.unload=function(){},t.begin=function(){null!=this.entityProcessors&&this.entityProcessors.begin(),this._didSceneBegin=!0,this.onStart()},t.end=function(){this._didSceneBegin=!1,this.entities.removeAllEntities(),this.querySystem.setEntities([]),this.componentStorageManager.clear(),this.entityProcessors&&this.entityProcessors.end(),this.unload()},t.update=function(){this.entities.updateLists(),null!=this.entityProcessors&&this.entityProcessors.update(),this.entities.update(),null!=this.entityProcessors&&this.entityProcessors.lateUpdate()},t.createEntity=function(e){var t=new U(e,this.identifierPool.checkOut());return this.eventSystem.emitSync("entity:created",{entityName:e,entity:t,scene:this}),this.addEntity(t)},t.clearSystemEntityCaches=function(){for(var e,t=a(this.entityProcessors.processors);!(e=t()).done;){e.value.clearEntityCache()}},t.addEntity=function(e,t){return void 0===t&&(t=!1),this.entities.add(e),e.scene=this,this.querySystem.addEntity(e,t),t||this.clearSystemEntityCaches(),this.eventSystem.emitSync("entity:added",{entity:e,scene:this}),e},t.createEntities=function(e,t){void 0===t&&(t="Entity");for(var n=[],i=0;i1?t-1:0),i=1;i1?t-1:0),i=1;i0?t.entities[0]:null},t.findByName=function(e){return this.scene.findEntity(e)},t.findByTag=function(e){return this.scene.findEntitiesByTag(e)},t.emit=function(e,t){this.eventSystem.emitSync(e,t)},t.emitAsync=function(){var e=i(d().m((function e(t,n){return d().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,this.eventSystem.emit(t,n);case 1:return e.a(2)}}),e,this)})));return function(t,n){return e.apply(this,arguments)}}(),t.on=function(e,t){return this.eventSystem.on(e,t)},t.once=function(e,t){return this.eventSystem.once(e,t)},t.off=function(e,t){this.eventSystem.off(e,t)},t.batch=function(e){return new fe(e)},t.getStats=function(){return{entityCount:this.scene.entities.count,systemCount:this.scene.systems.length,componentStats:this.scene.componentStorageManager.getAllStats(),queryStats:this.querySystem.getStats(),eventStats:this.eventSystem.getStats()}},e}();function me(e,t,n){return new pe(e,t,n)}var ye=k("World"),ve=function(){function e(e){void 0===e&&(e={}),this._scenes=new Map,this._activeScenes=new Set,this._globalSystems=[],this._isActive=!1,this._config=c({name:"World",debug:!1,maxScenes:10,autoCleanup:!0},e),this.name=this._config.name,this._createdAt=Date.now(),ye.info("创建World: "+this.name)}var t=e.prototype;return t.createScene=function(e,t){if(this._scenes.has(e))throw new Error("Scene ID '"+e+"' 已存在于World '"+this.name+"' 中");if(this._scenes.size>=this._config.maxScenes)throw new Error("World '"+this.name+"' 已达到最大Scene数量限制: "+this._config.maxScenes);var n=t||new he;return"id"in n&&(n.id=e),"name"in n&&!n.name&&(n.name=e),this._scenes.set(e,n),n.initialize(),ye.info("在World '"+this.name+"' 中创建Scene: "+e),n},t.removeScene=function(e){var t=this._scenes.get(e);return!!t&&(this._activeScenes.has(e)&&this.setSceneActive(e,!1),t.end(),this._scenes.delete(e),ye.info("从World '"+this.name+"' 中移除Scene: "+e),!0)},t.getScene=function(e){return this._scenes.get(e)||null},t.getSceneIds=function(){return Array.from(this._scenes.keys())},t.getAllScenes=function(){return Array.from(this._scenes.values())},t.setSceneActive=function(e,t){var n=this._scenes.get(e);n?t?(this._activeScenes.add(e),n.begin&&n.begin(),ye.debug("在World '"+this.name+"' 中激活Scene: "+e)):(this._activeScenes.delete(e),ye.debug("在World '"+this.name+"' 中停用Scene: "+e)):ye.warn("Scene '"+e+"' 不存在于World '"+this.name+"' 中")},t.isSceneActive=function(e){return this._activeScenes.has(e)},t.getActiveSceneCount=function(){return this._activeScenes.size},t.addGlobalSystem=function(e){return this._globalSystems.includes(e)||(this._globalSystems.push(e),e.initialize&&e.initialize(),ye.debug("在World '"+this.name+"' 中添加全局System: "+e.name)),e},t.removeGlobalSystem=function(e){var t=this._globalSystems.indexOf(e);return-1!==t&&(this._globalSystems.splice(t,1),e.reset&&e.reset(),ye.debug("从World '"+this.name+"' 中移除全局System: "+e.name),!0)},t.getGlobalSystem=function(e){for(var t,n=a(this._globalSystems);!(t=n()).done;){var i=t.value;if(i instanceof e)return i}return null},t.start=function(){if(!this._isActive){this._isActive=!0;for(var e,t=a(this._globalSystems);!(e=t()).done;){var n=e.value;n.initialize&&n.initialize()}ye.info("启动World: "+this.name)}},t.stop=function(){if(this._isActive){for(var e,t=a(this._activeScenes);!(e=t()).done;){var n=e.value;this.setSceneActive(n,!1)}for(var i,r=a(this._globalSystems);!(i=r()).done;){var o=i.value;o.reset&&o.reset()}this._isActive=!1,ye.info("停止World: "+this.name)}},t.updateGlobalSystems=function(){if(this._isActive)for(var e,t=a(this._globalSystems);!(e=t()).done;){var n=e.value;n.update&&n.update()}},t.updateScenes=function(){if(this._isActive){for(var e,t=a(this._activeScenes);!(e=t()).done;){var n=e.value,i=this._scenes.get(n);i&&i.update&&i.update()}this._config.autoCleanup&&this.shouldAutoCleanup()&&this.cleanup()}},t.destroy=function(){ye.info("销毁World: "+this.name),this.stop();for(var e=0,t=Array.from(this._scenes.keys());e3e5)return!0}return!1},t.cleanup=function(){for(var e=Array.from(this._scenes.keys()),t=Date.now(),n=0,i=e;n3e5&&(this.removeScene(r),ye.debug("自动清理空Scene: "+r+" from World "+this.name))}},s(e,[{key:"isActive",get:function(){return this._isActive}},{key:"sceneCount",get:function(){return this._scenes.size}},{key:"createdAt",get:function(){return this._createdAt}}])}(),ge=k("WorldManager"),_e=function(){function e(e){void 0===e&&(e={}),this._worlds=new Map,this._activeWorlds=new Set,this._cleanupTimer=null,this._isRunning=!1,this._config=c({maxWorlds:50,autoCleanup:!0,cleanupInterval:3e4,debug:!1},e),ge.info("WorldManager已初始化",{maxWorlds:this._config.maxWorlds,autoCleanup:this._config.autoCleanup,cleanupInterval:this._config.cleanupInterval}),this.startCleanupTimer()}e.getInstance=function(t){return this._instance||(this._instance=new e(t)),this._instance},e.reset=function(){this._instance&&(this._instance.destroy(),this._instance=null)};var t=e.prototype;return t.createWorld=function(e,t){if(!e||"string"!=typeof e||""===e.trim())throw new Error("World ID不能为空");if(this._worlds.has(e))throw new Error("World ID '"+e+"' 已存在");if(this._worlds.size>=this._config.maxWorlds)throw new Error("已达到最大World数量限制: "+this._config.maxWorlds);var n=c({name:e,debug:this._config.debug},t),i=new ve(n);return this._worlds.set(e,i),ge.info("创建World: "+e,{config:n}),i},t.removeWorld=function(e){var t=this._worlds.get(e);return!!t&&(this._activeWorlds.has(e)&&this.setWorldActive(e,!1),t.destroy(),this._worlds.delete(e),ge.info("移除World: "+e),!0)},t.getWorld=function(e){return this._worlds.get(e)||null},t.getWorldIds=function(){return Array.from(this._worlds.keys())},t.getAllWorlds=function(){return Array.from(this._worlds.values())},t.setWorldActive=function(e,t){var n=this._worlds.get(e);n?t?(this._activeWorlds.add(e),n.start(),ge.debug("激活World: "+e)):(this._activeWorlds.delete(e),n.stop(),ge.debug("停用World: "+e)):ge.warn("World '"+e+"' 不存在")},t.isWorldActive=function(e){return this._activeWorlds.has(e)},t.getActiveWorlds=function(){for(var e,t=[],n=a(this._activeWorlds);!(e=n()).done;){var i=e.value,r=this._worlds.get(i);r&&t.push(r)}return t},t.startAll=function(){this._isRunning=!0;for(var e,t=a(this._worlds.keys());!(e=t()).done;){var n=e.value;this.setWorldActive(n,!0)}ge.info("启动所有World")},t.stopAll=function(){this._isRunning=!1;for(var e,t=a(this._activeWorlds);!(e=t()).done;){var n=e.value;this.setWorldActive(n,!1)}ge.info("停止所有World")},t.findWorlds=function(e){for(var t,n=[],i=a(this._worlds.values());!(t=i()).done;){var r=t.value;e(r)&&n.push(r)}return n},t.findWorldByName=function(e){for(var t,n=a(this._worlds.values());!(t=n()).done;){var i=t.value;if(i.name===e)return i}return null},t.getStats=function(){for(var e,t={totalWorlds:this._worlds.size,activeWorlds:this._activeWorlds.size,totalScenes:0,totalEntities:0,totalSystems:0,memoryUsage:0,isRunning:this._isRunning,config:c({},this._config),worlds:[]},n=a(this._worlds);!(e=n()).done;){var i=e.value,r=i[0],o=i[1],s=o.getStats();t.totalScenes+=s.totalSystems,t.totalEntities+=s.totalEntities,t.totalSystems+=s.totalSystems,t.worlds.push(c({id:r,name:o.name,isActive:this._activeWorlds.has(r),sceneCount:o.sceneCount},s))}return t},t.getDetailedStatus=function(){var e=this;return c({},this.getStats(),{worlds:Array.from(this._worlds.entries()).map((function(t){var n=t[0],i=t[1];return{id:n,isActive:e._activeWorlds.has(n),status:i.getStatus()}}))})},t.cleanup=function(){for(var e,t=[],n=a(this._worlds);!(e=n()).done;){var i=e.value,r=i[0],o=i[1];this.shouldCleanupWorld(o)&&t.push(r)}for(var s=0,c=t;s0&&ge.debug("清理了 "+t.length+" 个World"),t.length},t.destroy=function(){ge.info("正在销毁WorldManager..."),this.stopCleanupTimer(),this.stopAll();for(var e=0,t=Array.from(this._worlds.keys());e0})))&&Date.now()-e.createdAt>6e5)},s(e,[{key:"worldCount",get:function(){return this._worlds.size}},{key:"activeWorldCount",get:function(){return this._activeWorlds.size}},{key:"isRunning",get:function(){return this._isRunning}},{key:"config",get:function(){return c({},this._config)}}])}();_e._instance=null;var Se=function(){function e(e){if(e&&"object"==typeof e)this._value=w.clone(e);else if("number"==typeof e)this._value=w.fromNumber(e);else if("string"==typeof e){var t=parseInt(e,10);this._value=w.fromNumber(t)}else this._value=w.clone(w.ZERO)}var t=e.prototype;return t.set=function(e){if(e<0)throw new Error("Bit index cannot be negative");if(e>=64)throw new Error("Bit index exceeds 64-bit limit. ECS framework supports max 64 component types.");w.setBit(this._value,e)},t.clear=function(e){if(e<0)throw new Error("Bit index cannot be negative");e>=64||w.clearBit(this._value,e)},t.get=function(e){if(e<0||e>=64)return!1;var t=w.create(e);return w.hasAny(this._value,t)},t.containsAll=function(e){return w.hasAll(this._value,e._value)},t.intersects=function(e){return w.hasAny(this._value,e._value)},t.excludes=function(e){return w.hasNone(this._value,e._value)},t.clearAll=function(){w.clear(this._value)},t.isEmpty=function(){return w.isZero(this._value)},t.cardinality=function(){return w.popCount(this._value)},t.and=function(t){var n=new e;return w.copy(this._value,n._value),w.andInPlace(n._value,t._value),n},t.or=function(t){var n=new e;return w.copy(this._value,n._value),w.orInPlace(n._value,t._value),n},t.xor=function(t){var n=new e;return w.copy(this._value,n._value),w.xorInPlace(n._value,t._value),n},t.not=function(t){void 0===t&&(t=64),t>64&&(t=64);var n=new e;if(w.copy(this._value,n._value),t<=32){var i=(1<64&&(e=64);for(var t="",n=e-1;n>=0;n--)t+=this.get(n)?"1":"0",n%8==0&&n>0&&(t+=" ");return t},t.toHexString=function(){return w.toString(this._value,16)},e.fromBinaryString=function(t){var n,i=t.replace(/\s/g,"");if(i.length<=32){n={lo:parseInt(i,2)>>>0,hi:0}}else{var r=i.substring(i.length-32),o=i.substring(0,i.length-32);n={lo:parseInt(r,2)>>>0,hi:parseInt(o,2)>>>0}}return new e(n)},e.fromHexString=function(t){var n,i=t.replace(/^0x/i,"");if(i.length<=8){n={lo:parseInt(i,16)>>>0,hi:0}}else{var r=i.substring(i.length-8),o=i.substring(0,i.length-8);n={lo:parseInt(r,16)>>>0,hi:parseInt(o,16)>>>0}}return new e(n)},t.equals=function(e){return w.equals(this._value,e._value)},t.getHighestBitIndex=function(){if(w.isZero(this._value))return-1;if(0!==this._value.hi)for(var e=31;e>=0;e--)if(this._value.hi&1<=0;t--)if(this._value.lo&1<0?i.sort().join(", "):"无组件",o=t.get(r);o?o.count++:t.set(r,{count:1,componentTypes:i})})),Array.from(t.entries()).map((function(e){return{signature:e[0],count:e[1].count,memory:0}})).sort((function(e,t){return t.count-e.count})).slice(0,20)},t.getTopEntitiesByComponentsFast=function(e){return e&&e.entities?e.entities.map((function(e){var t;return{id:e.id.toString(),name:e.name||"Entity_"+e.id,componentCount:(null===(t=e.components)||void 0===t?void 0:t.length)||0,memory:0}})).sort((function(e,t){return t.componentCount-e.componentCount})).slice(0,10):[]},t.collectArchetypeDataWithMemory=function(e){var t;if(e&&e.archetypeSystem&&"function"==typeof e.archetypeSystem.getAllArchetypes)return this.extractArchetypeStatisticsWithMemory(e.archetypeSystem);var n={entities:(null===(t=e.entities)||void 0===t?void 0:t.buffer)||[]};return{distribution:this.getArchetypeDistributionWithMemory(n),topEntities:this.getTopEntitiesByComponentsWithMemory(n)}},t.extractArchetypeStatistics=function(e){var t=e.getAllArchetypes(),n=[],i=[];return t.forEach((function(e){var t,r,o=(null===(t=e.componentTypes)||void 0===t?void 0:t.map((function(e){return e.name})).join(","))||"Unknown",s=(null===(r=e.entities)||void 0===r?void 0:r.length)||0;n.push({signature:o,count:s,memory:0}),e.entities&&e.entities.slice(0,5).forEach((function(e){var t;i.push({id:e.id.toString(),name:e.name||"Entity_"+e.id,componentCount:(null===(t=e.components)||void 0===t?void 0:t.length)||0,memory:0})}))})),n.sort((function(e,t){return t.count-e.count})),i.sort((function(e,t){return t.componentCount-e.componentCount})),{distribution:n,topEntities:i}},t.extractArchetypeStatisticsWithMemory=function(e){var t=this,n=e.getAllArchetypes(),i=[],r=[];return n.forEach((function(e){var n,o,s=(null===(n=e.componentTypes)||void 0===n?void 0:n.map((function(e){return e.name})).join(","))||"Unknown",a=(null===(o=e.entities)||void 0===o?void 0:o.length)||0,c=0;if(e.entities&&e.entities.length>0){for(var u=Math.min(5,e.entities.length),h=0,l=0;l0?r.sort().join(", "):"无组件",s=n.get(o),a=t.estimateEntityMemoryUsage(e);(isNaN(a)||a<0)&&(a=0),s?(s.count++,s.memory+=a):n.set(o,{count:1,memory:a,componentTypes:r})})),Array.from(n.entries()).map((function(e){var t=e[0],n=e[1];return{signature:t,count:n.count,memory:isNaN(n.memory)?0:n.memory}})).sort((function(e,t){return t.count-e.count}))},t.getTopEntitiesByComponents=function(e){return e&&e.entities?e.entities.map((function(e){var t;return{id:e.id.toString(),name:e.name||"Entity_"+e.id,componentCount:(null===(t=e.components)||void 0===t?void 0:t.length)||0,memory:0}})).sort((function(e,t){return t.componentCount-e.componentCount})):[]},t.getTopEntitiesByComponentsWithMemory=function(e){var t=this;return e&&e.entities?e.entities.map((function(e){var n;return{id:e.id.toString(),name:e.name||"Entity_"+e.id,componentCount:(null===(n=e.components)||void 0===n?void 0:n.length)||0,memory:t.estimateEntityMemoryUsage(e)}})).sort((function(e,t){return t.componentCount-e.componentCount})):[]},t.getEmptyEntityDebugData=function(){return{totalEntities:0,activeEntities:0,pendingAdd:0,pendingRemove:0,entitiesPerArchetype:[],topEntitiesByComponents:[],entityHierarchy:[],entityDetailsMap:{}}},t.calculateFallbackEntityStats=function(e){var t=e.buffer||[],n=t.filter((function(e){return e.enabled&&!e._isDestroyed}));return{totalEntities:t.length,activeEntities:n.length,pendingAdd:0,pendingRemove:0,averageComponentsPerEntity:n.length>0?t.reduce((function(e,t){var n;return e+((null===(n=t.components)||void 0===n?void 0:n.length)||0)}),0)/n.length:0}},t.estimateEntityMemoryUsage=function(e){var t=this;try{var n=0,i=this.calculateObjectSize(e,["components","children","parent"]);return!isNaN(i)&&i>0&&(n+=i),e.components&&Array.isArray(e.components)&&e.components.forEach((function(e){var i=t.calculateObjectSize(e,["entity"]);!isNaN(i)&&i>0&&(n+=i)})),isNaN(n)||n<0?0:n}catch(e){return 0}},t.calculateObjectSize=function(e,t){if(void 0===t&&(t=[]),!e||"object"!=typeof e)return 0;var n=new WeakSet,i=function(e,r){if(void 0===r&&(r=0),!e||"object"!=typeof e||r>=2)return 0;if(n.has(e))return 0;n.add(e);var o=32;try{for(var s=Object.keys(e),a=Math.min(s.length,20),c=0;ct.name?1:e.id-t.id})),n},t.buildEntityHierarchyNode=function(e){var t,n=this,i={id:e.id,name:e.name||"Entity_"+e.id,active:!1!==e.active,enabled:!1!==e.enabled,activeInHierarchy:!1!==e.activeInHierarchy,componentCount:e.components.length,componentTypes:e.components.map((function(e){return B(e)})),parentId:(null===(t=e.parent)||void 0===t?void 0:t.id)||null,children:[],depth:e.getDepth?e.getDepth():0,tag:e.tag||0,updateOrder:e.updateOrder||0};(e.children&&e.children.length>0&&(i.children=e.children.map((function(e){return n.buildEntityHierarchyNode(e)}))),"function"==typeof e.getDebugInfo)&&(i=c({},i,e.getDebugInfo()));return e.components&&e.components.length>0&&(i.componentDetails=this.extractComponentDetails(e.components)),i},t.buildEntityDetailsMap=function(e,t){var n=this;if(null==e||!e.buffer)return{};for(var i={},r=e.buffer,o=0;o=o.components.length)return{};var s=o.components[t],a={};return Object.keys(s).forEach((function(e){if(!e.startsWith("_")&&"entity"!==e){var t=s[e];null!=t&&(a[e]=i.formatPropertyValue(t))}})),a}catch(e){return{_error:"属性提取失败"}}},t.formatPropertyValue=function(e,t){return void 0===t&&(t=0),null==e?e:"object"!=typeof e?"string"==typeof e&&e.length>200?"[长字符串: "+e.length+"字符] "+e.substring(0,100)+"...":e:0===t?this.formatObjectFirstLevel(e):this.createLazyLoadPlaceholder(e)},t.formatObjectFirstLevel=function(e){var t=this;try{if(Array.isArray(e)){if(0===e.length)return[];if(e.length>10){var n=e.slice(0,3).map((function(e){return t.formatPropertyValue(e,1)}));return{_isLazyArray:!0,_arrayLength:e.length,_sample:n,_summary:"数组["+e.length+"个元素]"}}return e.map((function(e){return t.formatPropertyValue(e,1)}))}var i=Object.keys(e);if(0===i.length)return{};for(var r={},o=0,s=0,a=i;s=15){r._hasMoreProperties=!0,r._totalProperties=i.length,r._hiddenCount=i.length-o;break}if(!c.startsWith("_")&&!c.startsWith("$")&&"function"!=typeof e[c])try{var u=e[c];null!=u&&(r[c]=this.formatPropertyValue(u,1),o++)}catch(e){r[c]="[访问失败: "+(e instanceof Error?e.message:String(e))+"]",o++}}return r}catch(e){return"[对象解析失败: "+(e instanceof Error?e.message:String(e))+"]"}},t.createLazyLoadPlaceholder=function(e){try{var t,n=(null===(t=e.constructor)||void 0===t?void 0:t.name)||"Object";return{_isLazyObject:!0,_typeName:n,_summary:this.getObjectSummary(e,n),_objectId:this.generateObjectId(e)}}catch(e){return{_isLazyObject:!0,_typeName:"Unknown",_summary:"无法分析的对象: "+(e instanceof Error?e.message:String(e)),_objectId:Math.random().toString(36).substr(2,9)}}},t.getObjectSummary=function(e,t){try{if((t.toLowerCase().includes("vec")||t.toLowerCase().includes("vector"))&&void 0!==e.x&&void 0!==e.y){var n=void 0!==e.z?e.z:"";return t+"("+e.x+", "+e.y+(n?", "+n:"")+")"}if(t.toLowerCase().includes("color")&&void 0!==e.r&&void 0!==e.g&&void 0!==e.b){var i=void 0!==e.a?e.a:1;return t+"("+e.r+", "+e.g+", "+e.b+", "+i+")"}if(t.toLowerCase().includes("node"))return t+": "+(e.name||e._name||"未命名");if(t.toLowerCase().includes("component")){var r,o,s=(null===(r=e.node)||void 0===r?void 0:r.name)||(null===(o=e.node)||void 0===o?void 0:o._name)||"";return t+(s?" on "+s:"")}var a=Object.keys(e);return 0===a.length?t+" (空对象)":t+" ("+a.length+"个属性)"}catch(e){return t+" (无法分析)"}},t.generateObjectId=function(e){try{return void 0!==e.id?"obj_"+e.id:void 0!==e._id?"obj_"+e._id:void 0!==e.uuid?"obj_"+e.uuid:void 0!==e._uuid?"obj_"+e._uuid:"obj_"+Math.random().toString(36).substr(2,9)}catch(e){return"obj_"+Math.random().toString(36).substr(2,9)}},t.expandLazyObject=function(e,t,n,i){try{if(!i)return null;var r=i.entities;if(null==r||!r.buffer)return null;var o=r.buffer.find((function(t){return t.id===e}));if(!o)return null;if(t>=o.components.length)return null;var s=o.components[t],a=this.getObjectByPath(s,n);return a?this.formatObjectFirstLevel(a):null}catch(e){return{error:"展开失败: "+(e instanceof Error?e.message:String(e))}}},t.getObjectByPath=function(e,t){if(!t)return e;for(var n,i=e,r=a(t.split("."));!(n=r()).done;){var o=n.value;if(null==i)return null;if(o.includes("[")&&o.includes("]")){var s=o.substring(0,o.indexOf("[")),c=parseInt(o.substring(o.indexOf("[")+1,o.indexOf("]")));if(s&&(i=i[s]),!(Array.isArray(i)&&c>=0&&c0?Math.round(1/t):0,r=this.getECSPerformanceData(e),o=r.totalExecutionTime,s=n>0?o/n*100:0,a=0;performance.memory&&(a=performance.memory.usedJSHeapSize/1024/1024),this.frameTimeHistory.push(o),this.frameTimeHistory.length>this.maxHistoryLength&&this.frameTimeHistory.shift();var c=this.frameTimeHistory.filter((function(e){return e>=0}));return{frameTime:o,engineFrameTime:n,ecsPercentage:s,memoryUsage:a,fps:i,averageFrameTime:c.length>0?c.reduce((function(e,t){return e+t}),0)/c.length:o,minFrameTime:c.length>0?Math.min.apply(Math,c):o,maxFrameTime:c.length>0?Math.max.apply(Math,c):o,frameTimeHistory:[].concat(this.frameTimeHistory),systemPerformance:this.getSystemPerformance(e),systemBreakdown:r.systemBreakdown,memoryDetails:this.getMemoryDetails()}},t.getECSPerformanceData=function(e){if(!e)return{totalExecutionTime:0,systemBreakdown:[]};if(!e.enabled){try{e.enabled=!0}catch(e){}return{totalExecutionTime:0,systemBreakdown:[]}}try{var t=0,n=[],i=e.getAllSystemStats();if(0===i.size)return{totalExecutionTime:0,systemBreakdown:[]};for(var r,o=a(i.entries());!(r=o()).done;){var s=r.value,c=s[0],u=s[1],h=u.recentTimes&&u.recentTimes.length>0?u.recentTimes[u.recentTimes.length-1]:u.averageTime||0;t+=h,n.push({systemName:c,executionTime:h,percentage:0})}return n.forEach((function(e){e.percentage=t>0?e.executionTime/t*100:0})),n.sort((function(e,t){return t.executionTime-e.executionTime})),{totalExecutionTime:t,systemBreakdown:n}}catch(e){return{totalExecutionTime:0,systemBreakdown:[]}}},t.getSystemPerformance=function(e){if(!e)return[];try{var t=e.getAllSystemStats(),n=e.getAllSystemData();return Array.from(t.entries()).map((function(e){var t=e[0],i=e[1],r=n.get(t);return{systemName:t,averageTime:i.averageTime||0,maxTime:i.maxTime||0,minTime:i.minTime===Number.MAX_VALUE?0:i.minTime||0,samples:i.executionCount||0,percentage:0,entityCount:(null==r?void 0:r.entityCount)||0,lastExecutionTime:(null==r?void 0:r.executionTime)||0}}))}catch(e){return[]}},t.getMemoryDetails=function(){var e={entities:0,components:0,systems:0,pooled:0,totalMemory:0,usedMemory:0,freeMemory:0,gcCollections:this.updateGCCount()};try{if(performance.memory){var t=performance.memory;if(e.totalMemory=t.jsHeapSizeLimit||536870912,e.usedMemory=t.usedJSHeapSize||0,e.freeMemory=e.totalMemory-e.usedMemory,this.lastMemoryCheck>0)this.lastMemoryCheck-e.usedMemory>1048576&&this.gcCollections++;this.lastMemoryCheck=e.usedMemory}else e.totalMemory=536870912,e.freeMemory=536870912}catch(e){return{totalMemory:0,usedMemory:0,freeMemory:0,entityMemory:0,componentMemory:0,systemMemory:0,pooledMemory:0,gcCollections:this.gcCollections}}return e},t.updateGCCount=function(){try{return"undefined"!=typeof PerformanceObserver||performance.measureUserAgentSpecificMemory,this.gcCollections}catch(e){return this.gcCollections}},e}(),Me=function(){function e(e,t,n){void 0===n&&(n=1e3),this.pool=[],this.createFn=e,this.resetFn=t,this.maxSize=n}var t=e.prototype;return t.acquire=function(){return this.pool.length>0?this.pool.pop():this.createFn()},t.release=function(e){this.pool.length0?u/c*100:0;t.set(r,{used:u,total:c,utilization:h})}return t},t.getComponentUtilization=function(e){var t=this.pools.get(e);if(!t)return 0;var n=t.getAvailableCount(),i=t.getMaxSize();return i>0?(i-n)/i*100:0},e}(),Ae=function(){function e(){}var t=e.prototype;return t.collectComponentData=function(e){var t=this;if(!e)return{componentTypes:0,componentInstances:0,componentStats:[]};var n=e.entities;if(null==n||!n.buffer)return{componentTypes:0,componentInstances:0,componentStats:[]};var i=new Map,r=0;n.buffer.forEach((function(e){e.components&&e.components.forEach((function(e){var t=B(e),n=i.get(t)||{count:0,entities:0};n.count++,r++,i.set(t,n)}))}));var o=new Map,s=new Map;try{for(var c,u=we.getInstance(),h=u.getPoolStats(),l=u.getPoolUtilization(),d=a(h.entries());!(c=d()).done;){var f=c.value,p=f[0],m=f[1];s.set(p,m.maxSize)}for(var y,v=a(l.entries());!(y=v()).done;){var g=y.value,_=g[0],S=g[1];o.set(_,S.utilization)}}catch(e){}return{componentTypes:i.size,componentInstances:r,componentStats:Array.from(i.entries()).map((function(i){var r=i[0],a=i[1],c=s.get(r)||0,u=o.get(r)||0,h=t.getEstimatedComponentSize(r,e);return{typeName:r,instanceCount:a.count,memoryPerInstance:h,totalMemory:a.count*h,poolSize:c,poolUtilization:u,averagePerEntity:a.count/n.buffer.length}}))}},t.getEstimatedComponentSize=function(t,n){if(e.componentSizeCache.has(t))return e.componentSizeCache.get(t);if(!n)return 64;var i=n.entities;if(null==i||!i.buffer)return 64;var r=64;try{for(var o,s=a(i.buffer);!(o=s()).done;){var c=o.value;if(c.components){var u=c.components.find((function(e){return B(e)===t}));if(u){r=this.calculateQuickObjectSize(u);break}}}}catch(e){r=64}return e.componentSizeCache.set(t,r),r},t.calculateQuickObjectSize=function(e){if(!e||"object"!=typeof e)return 8;var t=32,n=new WeakSet,i=function(e,t){if(void 0===t&&(t=0),!e||"object"!=typeof e||n.has(e)||t>3)return 0;n.add(e);var r=0;try{for(var o=Object.keys(e),s=0;s10)return 0;if(t.has(e))return 0;var i=0;switch(typeof e){case"boolean":i=4;break;case"number":default:i=8;break;case"string":i=24+Math.min(2*e.length,1e3);break;case"object":if(t.add(e),Array.isArray(e)){i=40+8*e.length;for(var r=Math.min(e.length,50),o=0;o=this.sendInterval&&(this.sendDebugData(),this.lastSendTime=t)}},t.onSceneChanged=function(){this.isRunning&&this.config.enabled&&this.sendDebugData()},t.handleMessage=function(e){try{switch(e.type){case"capture_memory_snapshot":this.handleMemorySnapshotRequest();break;case"config_update":e.config&&this.updateConfig(c({},this.config,e.config));break;case"expand_lazy_object":this.handleExpandLazyObjectRequest(e);break;case"get_component_properties":this.handleGetComponentPropertiesRequest(e);break;case"get_raw_entity_list":this.handleGetRawEntityListRequest(e);break;case"get_entity_details":this.handleGetEntityDetailsRequest(e);break;case"ping":this.webSocketManager.send({type:"pong",timestamp:Date.now()})}}catch(t){e.requestId&&this.webSocketManager.send({type:"error_response",requestId:e.requestId,error:t instanceof Error?t.message:String(t)})}},t.handleExpandLazyObjectRequest=function(e){try{var t=e.entityId,n=e.componentIndex,i=e.propertyPath,r=e.requestId;if(void 0===t||void 0===n||!i)return void this.webSocketManager.send({type:"expand_lazy_object_response",requestId:r,error:"缺少必要参数"});var o=this.entityCollector.expandLazyObject(t,n,i);this.webSocketManager.send({type:"expand_lazy_object_response",requestId:r,data:o})}catch(t){this.webSocketManager.send({type:"expand_lazy_object_response",requestId:e.requestId,error:t instanceof Error?t.message:String(t)})}},t.handleGetComponentPropertiesRequest=function(e){try{var t=e.entityId,n=e.componentIndex,i=e.requestId;if(void 0===t||void 0===n)return void this.webSocketManager.send({type:"get_component_properties_response",requestId:i,error:"缺少必要参数"});var r=this.entityCollector.getComponentProperties(t,n);this.webSocketManager.send({type:"get_component_properties_response",requestId:i,data:r})}catch(t){this.webSocketManager.send({type:"get_component_properties_response",requestId:e.requestId,error:t instanceof Error?t.message:String(t)})}},t.handleGetRawEntityListRequest=function(e){try{var t=e.requestId,n=this.entityCollector.getRawEntityList();this.webSocketManager.send({type:"get_raw_entity_list_response",requestId:t,data:n})}catch(t){this.webSocketManager.send({type:"get_raw_entity_list_response",requestId:e.requestId,error:t instanceof Error?t.message:String(t)})}},t.handleGetEntityDetailsRequest=function(e){try{var t=e.entityId,n=e.requestId;if(void 0===t)return void this.webSocketManager.send({type:"get_entity_details_response",requestId:n,error:"缺少实体ID参数"});var i=this.entityCollector.getEntityDetails(t);this.webSocketManager.send({type:"get_entity_details_response",requestId:n,data:i})}catch(t){this.webSocketManager.send({type:"get_entity_details_response",requestId:e.requestId,error:t instanceof Error?t.message:String(t)})}},t.handleMemorySnapshotRequest=function(){try{var e=this.captureMemorySnapshot();this.webSocketManager.send({type:"memory_snapshot_response",data:e})}catch(e){this.webSocketManager.send({type:"memory_snapshot_error",error:e instanceof Error?e.message:"内存快照捕获失败"})}},t.captureMemorySnapshot=function(){var e=Date.now(),t=this.collectBaseMemoryInfo(),n=this.sceneProvider(),i=this.entityCollector.collectEntityDataWithMemory(n),r=null!=n&&n.entities?this.collectComponentMemoryStats(n.entities):{totalMemory:0,componentTypes:0,totalInstances:0,breakdown:[]},o=this.collectSystemMemoryStats(),s=this.collectPoolMemoryStats(),a=this.collectPerformanceStats(),c=i.entitiesPerArchetype.reduce((function(e,t){return e+t.memory}),0);return{timestamp:e,version:"2.0",summary:{totalEntities:i.totalEntities,totalMemoryUsage:t.usedMemory,totalMemoryLimit:t.totalMemory,memoryUtilization:t.usedMemory/t.totalMemory*100,gcCollections:t.gcCollections,entityMemory:c,componentMemory:r.totalMemory,systemMemory:o.totalMemory,poolMemory:s.totalMemory},baseMemory:t,entities:{totalMemory:c,entityCount:i.totalEntities,archetypes:i.entitiesPerArchetype,largestEntities:i.topEntitiesByComponents},components:r,systems:o,pools:s,performance:a}},t.collectBaseMemoryInfo=function(){var e={totalMemory:0,usedMemory:0,freeMemory:0,gcCollections:0,heapInfo:null,detailedMemory:void 0};try{var t=performance;if(t.memory){var n=t.memory;e.totalMemory=n.jsHeapSizeLimit||536870912,e.usedMemory=n.usedJSHeapSize||0,e.freeMemory=e.totalMemory-e.usedMemory,e.heapInfo={totalJSHeapSize:n.totalJSHeapSize||0,usedJSHeapSize:n.usedJSHeapSize||0,jsHeapSizeLimit:n.jsHeapSizeLimit||0}}else e.totalMemory=536870912,e.freeMemory=536870912;t.measureUserAgentSpecificMemory&&t.measureUserAgentSpecificMemory().then((function(t){e.detailedMemory=t})).catch((function(){}))}catch(e){}return e},t.collectComponentMemoryStats=function(e){for(var t,n=new Map,i=0,r=new Map,o=a(e.buffer);!(t=o()).done;){var s=t.value;if(s&&!s.destroyed&&s.components)for(var c,u=a(s.components);!(c=u()).done;){var h=B(c.value);r.set(h,(r.get(h)||0)+1)}}for(var l,d=a(r.entries());!(l=d()).done;){var f=l.value,p=f[0],m=f[1],y=this.componentCollector.calculateDetailedComponentMemory(p),v=y*m;i+=v;for(var g,_=[],S=0,E=a(e.buffer);!(g=E()).done;){var C=g.value;if(C&&!C.destroyed&&C.components){for(var T,b=a(C.components);!(T=b()).done;){if(B(T.value)===p&&(_.push({entityId:C.id,entityName:C.name||"Entity_"+C.id,memory:y}),++S>=100))break}if(S>=100)break}}n.set(p,{count:m,totalMemory:v,instances:_.slice(0,10)})}var M=Array.from(n.entries()).map((function(e){var t=e[0],n=e[1];return{typeName:t,instanceCount:n.count,totalMemory:n.totalMemory,averageMemory:n.totalMemory/n.count,percentage:i>0?n.totalMemory/i*100:0,largestInstances:n.instances.sort((function(e,t){return t.memory-e.memory})).slice(0,3)}})).sort((function(e,t){return t.totalMemory-e.totalMemory}));return{totalMemory:i,componentTypes:n.size,totalInstances:Array.from(n.values()).reduce((function(e,t){return e+t.count}),0),breakdown:M}},t.collectSystemMemoryStats=function(){var e=this.sceneProvider(),t=0,n=[];try{var i=null==e?void 0:e.entityProcessors;if(i&&i.processors)for(var r,o=new Map,s=a(i.processors);!(r=s()).done;){var c=r.value,u=L(c),h=void 0;o.has(u)?h=o.get(u):(h=this.calculateQuickSystemSize(c),o.set(u,h)),t+=h,n.push({name:u,memory:h,enabled:!1!==c.enabled,updateOrder:c.updateOrder||0})}}catch(e){}return{totalMemory:t,systemCount:n.length,breakdown:n.sort((function(e,t){return t.memory-e.memory}))}},t.calculateQuickSystemSize=function(e){if(!e||"object"!=typeof e)return 64;var t=128;try{for(var n=Object.keys(e),i=0;i1?n-1:0),r=1;r0&&e.push("all("+this.condition.all.map((function(e){return O(e)})).join(", ")+")"),this.condition.any.length>0&&e.push("any("+this.condition.any.map((function(e){return O(e)})).join(", ")+")"),this.condition.none.length>0&&e.push("none("+this.condition.none.map((function(e){return O(e)})).join(", ")+")"),void 0!==this.condition.tag&&e.push("tag("+this.condition.tag+")"),void 0!==this.condition.name&&e.push("name("+this.condition.name+")"),void 0!==this.condition.component&&e.push("component("+O(this.condition.component)+")"),"Matcher["+e.join(" & ")+"]"},e}(),ze=function(){function e(e){this._updateOrder=0,this._enabled=!0,this._performanceMonitor=C.instance,this._systemName=L(this),this._initialized=!1,this._matcher=e||Oe.empty(),this._eventListeners=[],this._scene=null,this._entityIdMap=null,this._entityIdMapVersion=-1,this._entityIdMapSize=0,this._entityCache={frame:null,persistent:null,tracked:new Set,invalidate:function(){this.persistent=null},clearFrame:function(){this.frame=null},clearAll:function(){this.frame=null,this.persistent=null,this.tracked.clear()}}}var t=e.prototype;return t.setUpdateOrder=function(e){this._updateOrder=e,this.scene&&this.scene.entityProcessors&&this.scene.entityProcessors.setDirty()},t.initialize=function(){this._initialized||(this._initialized=!0,this.scene&&(this._entityCache.invalidate(),this.queryEntities()),this.onInitialize())},t.onInitialize=function(){},t.clearEntityCache=function(){this._entityCache.invalidate()},t.reset=function(){this.scene=null,this._initialized=!1,this._entityCache.clearAll(),this._entityIdMap=null,this._entityIdMapVersion=-1,this._entityIdMapSize=0,this.cleanupEventListeners(),this.onDestroy()},t.queryEntities=function(){var e;if(null===(e=this.scene)||void 0===e||!e.querySystem||!this._matcher)return[];var t=this._matcher.getCondition(),n=this.scene.querySystem,i=[];return i=this._matcher.isEmpty()?n.getAllEntities():this.isSingleCondition(t)?this.executeSingleConditionQuery(t,n):this.executeComplexQuery(t,n),this.updateEntityTracking(i),i},t.isSingleCondition=function(e){var t=(e.all.length>0?1:0)|(e.any.length>0?2:0)|(e.none.length>0?4:0)|(void 0!==e.tag?8:0)|(void 0!==e.name?16:0)|(void 0!==e.component?32:0);return 0!==t&&!(t&t-1)},t.executeSingleConditionQuery=function(e,t){return void 0!==e.tag?t.queryByTag(e.tag).entities:void 0!==e.name?t.queryByName(e.name).entities:void 0!==e.component?t.queryByComponent(e.component).entities:e.all.length>0&&0===e.any.length&&0===e.none.length?t.queryAll.apply(t,e.all).entities:0===e.all.length&&e.any.length>0&&0===e.none.length?t.queryAny.apply(t,e.any).entities:0===e.all.length&&0===e.any.length&&e.none.length>0?t.queryNone.apply(t,e.none).entities:[]},t.executeComplexQueryWithIdSets=function(e,t){var n=null;if(void 0!==e.tag){var i=t.queryByTag(e.tag);n=this.extractEntityIds(i.entities)}if(void 0!==e.name){var r=this.extractEntityIds(t.queryByName(e.name).entities);n=n?this.intersectIdSets(n,r):r}if(void 0!==e.component){var o=this.extractEntityIds(t.queryByComponent(e.component).entities);n=n?this.intersectIdSets(n,o):o}if(e.all.length>0){var s=this.extractEntityIds(t.queryAll.apply(t,e.all).entities);n=n?this.intersectIdSets(n,s):s}if(e.any.length>0){var a=this.extractEntityIds(t.queryAny.apply(t,e.any).entities);n=n?this.intersectIdSets(n,a):a}if(e.none.length>0){n||(n=this.extractEntityIds(t.getAllEntities()));var c=t.queryAny.apply(t,e.none),u=this.extractEntityIds(c.entities);n=this.differenceIdSets(n,u)}return n?this.idSetToEntityArray(n,t.getAllEntities()):[]},t.extractEntityIds=function(e){for(var t=e.length,n=new Set,i=0;i=0){var i=this._eventListeners[n];i.eventSystem.off(e,i.listenerRef),this._eventListeners.splice(n,1)}},t.cleanupEventListeners=function(){for(var e,t=a(this._eventListeners);!(e=t()).done;){var n=e.value;try{n.eventSystem.off(n.eventType,n.listenerRef)}catch(e){console.warn("["+this.systemName+'] Failed to remove event listener for "'+n.eventType+'":',e)}}this._eventListeners.length=0},t.onDestroy=function(){},s(e,[{key:"entities",get:function(){return null!==this._entityCache.frame?this._entityCache.frame:(null===this._entityCache.persistent&&(this._entityCache.persistent=this.queryEntities()),this._entityCache.persistent)}},{key:"updateOrder",get:function(){return this._updateOrder},set:function(e){this.setUpdateOrder(e)}},{key:"enabled",get:function(){return this._enabled},set:function(e){this._enabled=e}},{key:"systemName",get:function(){return this._systemName}},{key:"scene",get:function(){return this._scene},set:function(e){this._scene=e}},{key:"matcher",get:function(){return this._matcher}}])}(),Be=function(e){function t(t){return e.call(this,t)||this}return h(t,e),t.prototype.process=function(e){this.processSystem()},t}(ze),Le=function(e){function t(t){return e.call(this,t)||this}return h(t,e),t.prototype.process=function(e){},t}(ze),Fe=function(e){function t(t,n){var i;return(i=e.call(this,n)||this).acc=0,i.intervalRemainder=0,i.interval=t,i}h(t,e);var n=t.prototype;return n.onCheckProcessing=function(){return this.acc+=g.deltaTime,this.acc>=this.interval&&(this.intervalRemainder=this.acc-this.interval,this.acc=0,!0)},n.getIntervalDelta=function(){return this.interval+this.intervalRemainder},t}(ze),We=function(e){function t(t,n){var i,r,o,s,a,c;return void 0===n&&(n={}),(c=e.call(this,t)||this).workerPool=null,c.isProcessing=!1,c.sharedBuffer=null,c.sharedFloatArray=null,c.config={enableWorker:null===(i=n.enableWorker)||void 0===i||i,workerCount:null!==(r=n.workerCount)&&void 0!==r?r:c.getOptimalWorkerCount(),systemConfig:n.systemConfig,useSharedArrayBuffer:null!==(o=n.useSharedArrayBuffer)&&void 0!==o?o:c.isSharedArrayBufferSupported(),entityDataSize:null!==(s=n.entityDataSize)&&void 0!==s?s:c.getDefaultEntityDataSize(),maxEntities:null!==(a=n.maxEntities)&&void 0!==a?a:1e4},c.config.enableWorker&&c.isWorkerSupported()&&(c.config.useSharedArrayBuffer&&c.initializeSharedArrayBuffer(),c.initializeWorkerPool()),c}h(t,e);var n=t.prototype;return n.isWorkerSupported=function(){return"undefined"!=typeof Worker&&"undefined"!=typeof Blob},n.isSharedArrayBufferSupported=function(){return"undefined"!=typeof SharedArrayBuffer&&self.crossOriginIsolated},n.getOptimalWorkerCount=function(){return"undefined"!=typeof navigator&&navigator.hardwareConcurrency?Math.min(navigator.hardwareConcurrency,4):2},n.initializeSharedArrayBuffer=function(){try{var e=this.config.maxEntities*this.config.entityDataSize*4;this.sharedBuffer=new SharedArrayBuffer(e),this.sharedFloatArray=new Float32Array(this.sharedBuffer)}catch(e){console.warn("["+this.systemName+"] SharedArrayBuffer init failed:",e),this.config.useSharedArrayBuffer=!1}},n.initializeWorkerPool=function(){try{var e=this.createWorkerScript();this.workerPool=new qe(this.config.workerCount,e,this.sharedBuffer)}catch(e){console.error("["+this.systemName+"] Failed to initialize worker pool:",e),this.config.enableWorker=!1}},n.createWorkerScript=function(){var e,t=this.workerProcess.toString().match(/\{([\s\S]*)\}/);if(!t)throw new Error("无法解析workerProcess方法");var n=t[1],i=this.config.entityDataSize,r=(null===(e=this.getSharedArrayBufferProcessFunction)||void 0===e?void 0:e.call(this))||null,o="";if(r){var s=r.toString().match(/\{([\s\S]*)\}/);s&&(o=s[1])}return"\n // Worker脚本 - 支持SharedArrayBuffer\n let sharedFloatArray = null;\n const ENTITY_DATA_SIZE = "+i+";\n\n self.onmessage = function(e) {\n const { type, id, entities, deltaTime, systemConfig, startIndex, endIndex, sharedBuffer } = e.data;\n\n\n try {\n // 处理SharedArrayBuffer初始化\n if (type === 'init' && sharedBuffer) {\n sharedFloatArray = new Float32Array(sharedBuffer);\n self.postMessage({ type: 'init', success: true });\n return;\n }\n\n // 处理SharedArrayBuffer数据\n if (type === 'shared' && sharedFloatArray) {\n processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig);\n self.postMessage({ id, result: null }); // SharedArrayBuffer不需要返回数据\n return;\n }\n\n // 传统处理方式\n if (entities) {\n // 定义处理函数\n function workerProcess(entities, deltaTime, systemConfig) {\n "+n+"\n }\n\n // 执行处理\n const result = workerProcess(entities, deltaTime, systemConfig);\n\n // 处理Promise返回值\n if (result && typeof result.then === 'function') {\n result.then(finalResult => {\n self.postMessage({ id, result: finalResult });\n }).catch(error => {\n self.postMessage({ id, error: error.message });\n });\n } else {\n self.postMessage({ id, result });\n }\n }\n } catch (error) {\n self.postMessage({ id, error: error.message });\n }\n };\n\n // SharedArrayBuffer处理函数 - 由子类定义\n function processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig) {\n if (!sharedFloatArray) return;\n\n "+(o?"\n // 用户定义的处理函数\n const userProcessFunction = function(sharedFloatArray, startIndex, endIndex, deltaTime, systemConfig) {\n "+o+"\n };\n userProcessFunction(sharedFloatArray, startIndex, endIndex, deltaTime, systemConfig);\n ":"")+"\n }\n "},n.process=function(e){var t=this;if(!this.isProcessing){this.isProcessing=!0;try{this.config.enableWorker&&this.workerPool?this.config.useSharedArrayBuffer&&this.sharedFloatArray?this.processWithSharedArrayBuffer(e).finally((function(){t.isProcessing=!1})):this.processWithWorker(e).finally((function(){t.isProcessing=!1})):(this.processSynchronously(e),this.isProcessing=!1)}catch(e){throw this.isProcessing=!1,e}}},n.processWithSharedArrayBuffer=function(){var e=i(d().m((function e(t){var n;return d().w((function(e){for(;;)switch(e.n){case 0:if(this.sharedFloatArray){e.n=1;break}throw new Error("SharedArrayBuffer not initialized");case 1:return this.writeEntitiesToSharedBuffer(t),n=this.createSharedArrayBufferTasks(t.length),e.n=2,Promise.all(n);case 2:this.readResultsFromSharedBuffer(t);case 3:return e.a(2)}}),e,this)})));return function(t){return e.apply(this,arguments)}}(),n.processWithWorker=function(){var e=i(d().m((function e(t){var n,i,r,o,s,c,u,h,l,f,p,m,y,v,_=this;return d().w((function(e){for(;;)switch(e.n){case 0:for(n=[],i=0;i0){var t=this.taskQueue.shift();this.busyWorkers.add(e),this.workers[e].postMessage(c({id:t.id},t.data)),this.workers[e]._currentTask=t}},t.handleWorkerMessage=function(e,t){var n=this.workers[e],i=n._currentTask;i&&(this.busyWorkers.delete(e),n._currentTask=null,t.error?i.reject(new Error(t.error)):i.resolve(t.result),this.processQueue())},t.handleWorkerError=function(e,t){var n=this.workers[e],i=n._currentTask;i&&(this.busyWorkers.delete(e),n._currentTask=null,i.reject(new Error(t.message))),this.processQueue()},t.destroy=function(){for(var e,t=a(this.workers);!(e=t()).done;){e.value.terminate()}this.workers.length=0,this.taskQueue.length=0,this.busyWorkers.clear()},e}(),je=function(){function e(){}return e.getType=function(e){return e.constructor},e}(),Ue=function(){function e(){}return e.toNumber=function(e){return null==e?0:Number(e)},e}();e.AsyncEventHandler=function(e,t){return void 0===t&&(t={}),function(n,i,r){var o=r.value,s=n.constructor.prototype.initEventListeners||function(){};return n.constructor.prototype.initEventListeners=function(){s.call(this),ue.getInstance().onAsync(e,o.bind(this),t)},r}},e.BitMask64Utils=w,e.Bits=Se,e.COMPONENT_TYPE_NAME=R,e.Colors=A,e.Component=Ne,e.ComponentDataCollector=Ae,e.ComponentPool=Me,e.ComponentPoolManager=we,e.ComponentRegistry=F,e.ComponentSparseSet=Z,e.ComponentStorage=W,e.ComponentTypeManager=Ee,e.ConsoleLogger=I,e.Core=ke,e.DebugManager=De,e.ECSComponent=function(e){return function(t){if(!e||"string"!=typeof e)throw new Error("ECSComponent装饰器必须提供有效的类型名称");return t[R]=e,t}},e.ECSFluentAPI=pe,e.ECSSystem=function(e){return function(t){if(!e||"string"!=typeof e)throw new Error("ECSSystem装饰器必须提供有效的类型名称");return t[N]=e,t}},e.EVENT_TYPES=se,e.Emitter=Re,e.EnableSoA=function(e){return e.__enableSoA=!0,e},e.Entity=U,e.EntityDataCollector=Ce,e.EntityList=G,e.EntityProcessorList=Y,e.EntitySystem=ze,e.EventBus=ce,e.EventHandler=function(e,t){return void 0===t&&(t={}),function(n,i,r){var o=r.value,s=n.constructor.prototype.initEventListeners||function(){};return n.constructor.prototype.initEventListeners=function(){s.call(this),ue.getInstance().on(e,o.bind(this),t)},r}},e.EventTypeValidator=ae,e.Float32=function(e,t){var n=String(t);e.constructor.__float32Fields||(e.constructor.__float32Fields=new Set),e.constructor.__float32Fields.add(n)},e.Float64=function(e,t){var n=String(t);e.constructor.__float64Fields||(e.constructor.__float64Fields=new Set),e.constructor.__float64Fields.add(n)},e.FuncPack=Pe,e.GlobalEventBus=ue,e.GlobalManager=v,e.HighPrecision=function(e,t){var n=String(t);e.constructor.__highPrecisionFields||(e.constructor.__highPrecisionFields=new Set),e.constructor.__highPrecisionFields.add(n)},e.IdentifierPool=Q,e.Int32=function(e,t){var n=String(t);e.constructor.__int32Fields||(e.constructor.__int32Fields=new Set),e.constructor.__int32Fields.add(n)},e.IntervalSystem=Fe,e.Logger=D,e.LoggerManager=x,e.Matcher=Oe,e.NumberExtension=Ue,e.PassiveSystem=Le,e.PerformanceDataCollector=be,e.PerformanceMonitor=C,e.Pool=T,e.PoolManager=M,e.ProcessingSystem=Be,e.QuerySystem=ie,e.SYSTEM_TYPE_NAME=N,e.Scene=he,e.SceneDataCollector=Ie,e.SerializeMap=function(e,t){var n=String(t);e.constructor.__serializeMapFields||(e.constructor.__serializeMapFields=new Set),e.constructor.__serializeMapFields.add(n)},e.SoAStorage=P,e.SparseSet=V,e.SystemDataCollector=Te,e.Time=g,e.Timer=S,e.TimerManager=E,e.TypeSafeEventSystem=oe,e.TypeUtils=je,e.WebSocketManager=xe,e.WorkerEntitySystem=We,e.World=ve,e.WorldManager=_e,e.createECSAPI=me,e.createLogger=k,e.getComponentInstanceTypeName=B,e.getComponentTypeName=O,e.getSystemInstanceTypeName=L,e.getSystemTypeName=z,e.resetLoggerColors=function(){x.getInstance().resetColors()},e.setGlobalLogLevel=function(e){x.getInstance().setGlobalLevel(e)},e.setLoggerColors=function(e){x.getInstance().setGlobalColors(e)}})); -//# sourceMappingURL=index.umd.js.map diff --git a/docs/public/demos/worker-system/assets/index-a4a166b2.js b/docs/public/demos/worker-system/assets/index-83126548.js similarity index 99% rename from docs/public/demos/worker-system/assets/index-a4a166b2.js rename to docs/public/demos/worker-system/assets/index-83126548.js index b004eca1..0412b770 100644 --- a/docs/public/demos/worker-system/assets/index-a4a166b2.js +++ b/docs/public/demos/worker-system/assets/index-83126548.js @@ -11914,9 +11914,10 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { { enableWorker, workerCount: navigator.hardwareConcurrency || 2, + // 恢复多Worker systemConfig: defaultConfig, useSharedArrayBuffer: true - // 启用SharedArrayBuffer优化 + // 使用SharedArrayBuffer进行全局碰撞检测 } ); this.physicsConfig = { @@ -11948,7 +11949,8 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { /** * Worker处理函数 - 纯函数,会被序列化到Worker中执行 * 注意:这个函数内部不能访问外部变量,必须是纯函数 - * 添加了小球间碰撞检测,大大增加计算复杂度 + * 非SharedArrayBuffer模式:每个Worker只能看到分配给它的实体批次 + * 这会导致跨批次的碰撞检测缺失,但单批次内的碰撞是正确的 */ workerProcess(entities, deltaTime, systemConfig) { const config = systemConfig || { @@ -12023,7 +12025,7 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { return result; } /** - * 应用处理结果 - 将Worker计算结果应用回组件 + * 应用处理结果 */ applyResult(entity, result) { if (!entity || !entity.enabled) { @@ -12051,7 +12053,7 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { return { ...this.physicsConfig }; } /** - * 性能监控 - 重写onEnd来计算执行时间 + * 性能监控 */ onEnd() { super.onEnd(); @@ -12060,7 +12062,7 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { window.physicsExecutionTime = executionTime; } /** - * 获取实体数据大小 - 物理系统使用9个Float32值 + * 获取实体数据大小 */ getDefaultEntityDataSize() { return 9; @@ -12086,7 +12088,7 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { sharedArray[dataOffset + 8] = entityData.radius; } /** - * 性能监控 - 重写onBegin来记录开始时间 + * 性能监控开始 */ onBegin() { super.onBegin(); @@ -12113,8 +12115,7 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { }; } /** - * 提供SharedArrayBuffer处理函数 - 物理系统的具体实现 - * 包含小球间碰撞检测的复杂计算 + * SharedArrayBuffer处理函数 */ getSharedArrayBufferProcessFunction() { return function(sharedFloatArray, startIndex, endIndex, deltaTime, systemConfig) { @@ -12214,11 +12215,9 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { const impulseY = impulseScalar * ny; dx1 -= impulseX / mass1; dy1 -= impulseY / mass1; - if (i < j) { - const energyLoss = 0.98; - dx1 *= energyLoss; - dy1 *= energyLoss; - } + const energyLoss = 0.98; + dx1 *= energyLoss; + dy1 *= energyLoss; } } sharedFloatArray[offset1 + 1] = x1; diff --git a/docs/public/demos/worker-system/index.html b/docs/public/demos/worker-system/index.html index 0d59412c..cc07b219 100644 --- a/docs/public/demos/worker-system/index.html +++ b/docs/public/demos/worker-system/index.html @@ -118,7 +118,7 @@ color: #ff4a4a; } - +
diff --git a/examples/worker-system-demo/index.html b/examples/worker-system-demo/index.html new file mode 100644 index 00000000..b1f78a73 --- /dev/null +++ b/examples/worker-system-demo/index.html @@ -0,0 +1,175 @@ + + + + + + ECS Framework Worker System Demo + + + +
+

ECS Framework Worker System 演示

+ +
+
+ +
+ +
+
+ + + 1000 +
+ +
+ + +
+ +
+ + + +
+ +
+ + + + + + +
+
+
+ +
+

性能统计

+
FPS: 0
+
实体数量: 0
+
Worker状态: 未启用
+
Worker负载: N/A
+
物理系统耗时: 0ms
+
渲染系统耗时: 0ms
+
总帧时间: 0ms
+
内存使用: 0MB
+
+
+ + + + \ No newline at end of file diff --git a/examples/worker-system-demo/package-lock.json b/examples/worker-system-demo/package-lock.json new file mode 100644 index 00000000..f86f0445 --- /dev/null +++ b/examples/worker-system-demo/package-lock.json @@ -0,0 +1,626 @@ +{ + "name": "ecs-worker-system-demo", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ecs-worker-system-demo", + "version": "1.0.0", + "dependencies": { + "@esengine/ecs-framework": "file:../../packages/core" + }, + "devDependencies": { + "typescript": "^5.0.0", + "vite": "^4.0.0" + } + }, + "../../packages/core": { + "name": "@esengine/ecs-framework", + "version": "2.1.49", + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.28.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/preset-env": "^7.28.3", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.3", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-terser": "^0.4.4", + "@types/jest": "^29.5.14", + "@types/node": "^20.19.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "rimraf": "^5.0.0", + "rollup": "^4.42.0", + "rollup-plugin-dts": "^6.2.1", + "ts-jest": "^29.4.0", + "typescript": "^5.8.3" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esengine/ecs-framework": { + "resolved": "../../packages/core", + "link": true + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/examples/worker-system-demo/package.json b/examples/worker-system-demo/package.json new file mode 100644 index 00000000..143af1cf --- /dev/null +++ b/examples/worker-system-demo/package.json @@ -0,0 +1,18 @@ +{ + "name": "ecs-worker-system-demo", + "version": "1.0.0", + "description": "ECS Framework Worker System Demo", + "main": "src/main.ts", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^5.0.0", + "vite": "^4.0.0" + }, + "dependencies": { + "@esengine/ecs-framework": "file:../../packages/core" + } +} \ No newline at end of file diff --git a/examples/worker-system-demo/src/GameScene.ts b/examples/worker-system-demo/src/GameScene.ts new file mode 100644 index 00000000..62bfceeb --- /dev/null +++ b/examples/worker-system-demo/src/GameScene.ts @@ -0,0 +1,173 @@ +import { Scene } from '@esengine/ecs-framework'; +import { PhysicsWorkerSystem, RenderSystem, LifetimeSystem } from './systems'; +import { Position, Velocity, Physics, Renderable, Lifetime } from './components'; + +export class GameScene extends Scene { + private canvas: HTMLCanvasElement; + private physicsSystem!: PhysicsWorkerSystem; + private renderSystem!: RenderSystem; + private lifetimeSystem!: LifetimeSystem; + + constructor(canvas: HTMLCanvasElement) { + super(); + this.canvas = canvas; + } + + override initialize(): void { + this.name = "WorkerDemoScene"; + + // 创建系统 + this.physicsSystem = new PhysicsWorkerSystem(true); // 默认启用Worker + this.renderSystem = new RenderSystem(this.canvas); + this.lifetimeSystem = new LifetimeSystem(); + + // 设置系统执行顺序 + this.physicsSystem.updateOrder = 1; + this.lifetimeSystem.updateOrder = 2; + this.renderSystem.updateOrder = 3; + + // 添加系统到场景 + this.addSystem(this.physicsSystem); + this.addSystem(this.lifetimeSystem); + this.addSystem(this.renderSystem); + } + + override onStart(): void { + console.log("Worker演示场景已启动"); + this.spawnInitialEntities(); + } + + override unload(): void { + console.log("Worker演示场景已卸载"); + } + + /** + * 生成初始实体 + */ + public spawnInitialEntities(count: number = 1000): void { + this.clearAllEntities(); + + for (let i = 0; i < count; i++) { + this.createParticle(); + } + } + + /** + * 创建一个粒子实体 + */ + public createParticle(): void { + const entity = this.createEntity(`Particle_${Date.now()}_${Math.random()}`); + + // 随机位置 + const x = Math.random() * (this.canvas.width - 20) + 10; + const y = Math.random() * (this.canvas.height - 20) + 10; + + // 随机速度 + const dx = (Math.random() - 0.5) * 200; + const dy = (Math.random() - 0.5) * 200; + + const mass = Math.random() * 3 + 2; + const bounce = 0.85 + Math.random() * 0.15; + const friction = 0.998 + Math.random() * 0.002; + + // 随机颜色和大小 - 增加更多颜色提高多样性 + const colors = [ + '#ff4444', '#44ff44', '#4444ff', '#ffff44', '#ff44ff', '#44ffff', '#ffffff', + '#ff8844', '#88ff44', '#4488ff', '#ff4488', '#88ff88', '#8888ff', '#ffaa44', + '#aaff44', '#44aaff', '#ff44aa', '#aa44ff', '#44ffaa', '#cccccc' + ]; + const color = colors[Math.floor(Math.random() * colors.length)]; + const size = Math.random() * 6 + 3; + + // 添加组件 + entity.addComponent(new Position(x, y)); + entity.addComponent(new Velocity(dx, dy)); + entity.addComponent(new Physics(mass, bounce, friction)); + entity.addComponent(new Renderable(color, size, 'circle')); + entity.addComponent(new Lifetime(5 + Math.random() * 10)); // 5-15秒生命周期 + } + + /** + * 生成粒子爆发效果 + */ + public spawnParticleExplosion(centerX: number, centerY: number, count: number = 50): void { + for (let i = 0; i < count; i++) { + const entity = this.createEntity(`Explosion_${Date.now()}_${i}`); + + // 在中心点周围随机分布 + const angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.5; + const distance = Math.random() * 30; + const x = centerX + Math.cos(angle) * distance; + const y = centerY + Math.sin(angle) * distance; + + // 爆炸速度 + const speed = 100 + Math.random() * 150; + const dx = Math.cos(angle) * speed; + const dy = Math.sin(angle) * speed; + + const mass = 0.5 + Math.random() * 1; + const bounce = 0.8 + Math.random() * 0.2; + + // 亮色 + const colors = ['#ffaa00', '#ff6600', '#ff0066', '#ff3300', '#ffff00']; + const color = colors[Math.floor(Math.random() * colors.length)]; + const size = Math.random() * 4 + 2; + + entity.addComponent(new Position(x, y)); + entity.addComponent(new Velocity(dx, dy)); + entity.addComponent(new Physics(mass, bounce, 0.999)); + entity.addComponent(new Renderable(color, size, 'circle')); + entity.addComponent(new Lifetime(2 + Math.random() * 3)); // 短生命周期 + } + } + + /** + * 清空所有实体 + */ + public clearAllEntities(): void { + const entities = [...this.entities.buffer]; // 复制数组避免修改原数组 + for (const entity of entities) { + entity.destroy(); + } + } + + /** + * 切换Worker启用状态 + */ + public toggleWorker(): boolean { + const workerInfo = this.physicsSystem.getWorkerInfo(); + const newWorkerEnabled = !workerInfo.enabled; + + // 重新创建物理系统 + this.removeSystem(this.physicsSystem); + this.physicsSystem = new PhysicsWorkerSystem(newWorkerEnabled); + this.physicsSystem.updateOrder = 1; + this.addSystem(this.physicsSystem); + + return newWorkerEnabled; + } + + /** + * 更新Worker配置 + */ + public updateWorkerConfig(config: { gravity?: number; friction?: number }): void { + if (config.gravity !== undefined || config.friction !== undefined) { + const physicsConfig = this.physicsSystem.getPhysicsConfig(); + this.physicsSystem.updatePhysicsConfig({ + gravity: config.gravity ?? physicsConfig.gravity, + groundFriction: config.friction ?? physicsConfig.groundFriction + }); + } + } + + /** + * 获取系统信息 + */ + public getSystemInfo() { + return { + physics: this.physicsSystem.getWorkerInfo(), + entityCount: this.entities.count, + physicsConfig: this.physicsSystem.getPhysicsConfig() + }; + } +} \ No newline at end of file diff --git a/examples/worker-system-demo/src/components/index.ts b/examples/worker-system-demo/src/components/index.ts new file mode 100644 index 00000000..b128bc23 --- /dev/null +++ b/examples/worker-system-demo/src/components/index.ts @@ -0,0 +1,89 @@ +import { Component, ECSComponent } from '@esengine/ecs-framework'; + +// 位置组件 +@ECSComponent('Position') +export class Position extends Component { + x: number = 0; + y: number = 0; + + constructor(x: number = 0, y: number = 0) { + super(); + this.x = x; + this.y = y; + } + + set(x: number, y: number): void { + this.x = x; + this.y = y; + } +} + +// 速度组件 +@ECSComponent('Velocity') +export class Velocity extends Component { + dx: number = 0; + dy: number = 0; + + constructor(dx: number = 0, dy: number = 0) { + super(); + this.dx = dx; + this.dy = dy; + } + + set(dx: number, dy: number): void { + this.dx = dx; + this.dy = dy; + } + + scale(factor: number): void { + this.dx *= factor; + this.dy *= factor; + } +} + +// 物理组件 +@ECSComponent('Physics') +export class Physics extends Component { + mass: number = 1; + bounce: number = 0.8; + friction: number = 0.95; + + constructor(mass: number = 1, bounce: number = 0.8, friction: number = 0.95) { + super(); + this.mass = mass; + this.bounce = bounce; + this.friction = friction; + } +} + +// 渲染组件 +@ECSComponent('Renderable') +export class Renderable extends Component { + color: string = '#ffffff'; + size: number = 5; + shape: 'circle' | 'square' = 'circle'; + + constructor(color: string = '#ffffff', size: number = 5, shape: 'circle' | 'square' = 'circle') { + super(); + this.color = color; + this.size = size; + this.shape = shape; + } +} + +// 生命周期组件 +@ECSComponent('Lifetime') +export class Lifetime extends Component { + maxAge: number = 5; + currentAge: number = 0; + + constructor(maxAge: number = 5) { + super(); + this.maxAge = maxAge; + this.currentAge = 0; + } + + isDead(): boolean { + return this.currentAge >= this.maxAge; + } +} \ No newline at end of file diff --git a/examples/worker-system-demo/src/main.ts b/examples/worker-system-demo/src/main.ts new file mode 100644 index 00000000..40fb1455 --- /dev/null +++ b/examples/worker-system-demo/src/main.ts @@ -0,0 +1,339 @@ +import { Core } from '@esengine/ecs-framework'; +import { GameScene } from './GameScene'; + +// 性能监控 +interface PerformanceStats { + fps: number; + frameTime: number; + physicsTime: number; + renderTime: number; + memoryUsage: number; +} + +class WorkerDemo { + private gameScene: GameScene; + private canvas: HTMLCanvasElement; + private isRunning = false; + private lastTime = 0; + private frameCount = 0; + private fpsUpdateTime = 0; + private currentFPS = 0; + private lastWorkerStatusUpdate = 0; + + // UI元素 + private elements: { [key: string]: HTMLElement } = {}; + + constructor() { + // 获取canvas + this.canvas = document.getElementById('gameCanvas') as HTMLCanvasElement; + if (!this.canvas) { + throw new Error('Canvas element not found'); + } + + // 初始化UI元素引用 + this.initializeUIElements(); + + // 初始化ECS Core + Core.create({ + debug: true, + enableEntitySystems: true + }); + + // 创建游戏场景 + this.gameScene = new GameScene(this.canvas); + + // 设置场景 + Core.setScene(this.gameScene); + + // 绑定事件 + this.bindEvents(); + + // 启动演示 + this.start(); + } + + private initializeUIElements(): void { + const elementIds = [ + 'entityCount', 'entityCountValue', 'toggleWorker', + 'gravity', 'gravityValue', 'friction', 'frictionValue', 'spawnParticles', + 'clearEntities', 'resetDemo', 'fps', 'entityCountStat', 'workerStatus', 'workerLoad', + 'physicsTime', 'renderTime', 'frameTime', 'memoryUsage' + ]; + + for (const id of elementIds) { + const element = document.getElementById(id); + if (element) { + this.elements[id] = element; + } else { + console.warn(`Element with id '${id}' not found`); + } + } + } + + private bindEvents(): void { + // 实体数量滑块 + if (this.elements.entityCount && this.elements.entityCountValue) { + const slider = this.elements.entityCount as HTMLInputElement; + slider.addEventListener('input', () => { + this.elements.entityCountValue.textContent = slider.value; + }); + + slider.addEventListener('change', () => { + const count = parseInt(slider.value); + this.gameScene.spawnInitialEntities(count); + }); + } + + // Worker切换按钮 + if (this.elements.toggleWorker) { + this.elements.toggleWorker.addEventListener('click', () => { + const workerEnabled = this.gameScene.toggleWorker(); + this.elements.toggleWorker.textContent = workerEnabled ? '禁用 Worker' : '启用 Worker'; + this.updateWorkerStatus(); + }); + } + + + // 重力滑块 + if (this.elements.gravity && this.elements.gravityValue) { + const slider = this.elements.gravity as HTMLInputElement; + slider.addEventListener('input', () => { + this.elements.gravityValue.textContent = slider.value; + }); + + slider.addEventListener('change', () => { + const gravity = parseInt(slider.value); + this.gameScene.updateWorkerConfig({ gravity }); + }); + } + + // 摩擦力滑块 + if (this.elements.friction && this.elements.frictionValue) { + const slider = this.elements.friction as HTMLInputElement; + slider.addEventListener('input', () => { + const value = parseInt(slider.value); + this.elements.frictionValue.textContent = `${value}%`; + }); + + slider.addEventListener('change', () => { + const friction = parseInt(slider.value) / 100; + this.gameScene.updateWorkerConfig({ friction }); + }); + } + + // 生成粒子按钮 + if (this.elements.spawnParticles) { + this.elements.spawnParticles.addEventListener('click', () => { + const centerX = this.canvas.width / 2; + const centerY = this.canvas.height / 2; + this.gameScene.spawnParticleExplosion(centerX, centerY, 100); + }); + } + + // 清空实体按钮 + if (this.elements.clearEntities) { + this.elements.clearEntities.addEventListener('click', () => { + this.gameScene.clearAllEntities(); + }); + } + + // 重置演示按钮 + if (this.elements.resetDemo) { + this.elements.resetDemo.addEventListener('click', () => { + this.resetDemo(); + }); + } + + // Canvas点击事件 - 在点击位置生成粒子爆发 + this.canvas.addEventListener('click', (event) => { + const rect = this.canvas.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + this.gameScene.spawnParticleExplosion(x, y, 30); + }); + } + + private start(): void { + this.isRunning = true; + this.lastTime = performance.now(); + this.gameLoop(); + console.log('Worker演示已启动'); + } + + private gameLoop = (): void => { + if (!this.isRunning) return; + + const currentTime = performance.now(); + const deltaTime = (currentTime - this.lastTime) / 1000; // 转换为秒 + this.lastTime = currentTime; + + // 更新ECS框架 + const frameStartTime = performance.now(); + Core.update(deltaTime); + const frameEndTime = performance.now(); + + // 更新性能统计 + this.updatePerformanceStats({ + fps: this.currentFPS, + frameTime: frameEndTime - frameStartTime, + physicsTime: (window as any).physicsExecutionTime || 0, + renderTime: (window as any).renderExecutionTime || 0, + memoryUsage: this.getMemoryUsage() + }); + + // 更新FPS计算 + this.frameCount++; + if (currentTime - this.fpsUpdateTime >= 1000) { + this.currentFPS = this.frameCount; + this.frameCount = 0; + this.fpsUpdateTime = currentTime; + } + + // 更新UI + this.updateUI(); + + // 继续循环 + requestAnimationFrame(this.gameLoop); + }; + + private updatePerformanceStats(stats: PerformanceStats): void { + if (this.elements.fps) { + this.elements.fps.textContent = stats.fps.toString(); + this.elements.fps.className = stats.fps >= 55 ? 'performance-high' : + stats.fps >= 30 ? 'performance-medium' : 'performance-low'; + } + + if (this.elements.frameTime) { + this.elements.frameTime.textContent = stats.frameTime.toFixed(2); + this.elements.frameTime.className = stats.frameTime <= 16 ? 'performance-high' : + stats.frameTime <= 33 ? 'performance-medium' : 'performance-low'; + } + + if (this.elements.physicsTime) { + this.elements.physicsTime.textContent = stats.physicsTime.toFixed(2); + this.elements.physicsTime.className = stats.physicsTime <= 8 ? 'performance-high' : + stats.physicsTime <= 16 ? 'performance-medium' : 'performance-low'; + } + + if (this.elements.renderTime) { + this.elements.renderTime.textContent = stats.renderTime.toFixed(2); + this.elements.renderTime.className = stats.renderTime <= 8 ? 'performance-high' : + stats.renderTime <= 16 ? 'performance-medium' : 'performance-low'; + } + + if (this.elements.memoryUsage) { + this.elements.memoryUsage.textContent = stats.memoryUsage.toFixed(1); + } + } + + private updateUI(): void { + const currentTime = performance.now(); + const systemInfo = this.gameScene.getSystemInfo(); + + // 更新实体数量(每帧更新) + if (this.elements.entityCountStat) { + this.elements.entityCountStat.textContent = systemInfo.entityCount.toString(); + } + + // 更新Worker状态(每500ms更新一次即可) + if (currentTime - this.lastWorkerStatusUpdate >= 500) { + this.updateWorkerStatus(); + this.lastWorkerStatusUpdate = currentTime; + } + + // 更新全局Worker信息供其他系统使用 + (window as any).workerInfo = systemInfo.physics; + } + + private updateWorkerStatus(): void { + const systemInfo = this.gameScene.getSystemInfo(); + const workerInfo = systemInfo.physics; + const entityCount = systemInfo.entityCount; + + if (this.elements.workerStatus) { + if (workerInfo.enabled) { + this.elements.workerStatus.textContent = `启用 (${workerInfo.workerCount} Workers)`; + this.elements.workerStatus.className = 'worker-enabled'; + } else { + this.elements.workerStatus.textContent = '禁用'; + this.elements.workerStatus.className = 'worker-disabled'; + } + } + + if (this.elements.workerLoad) { + if (workerInfo.enabled && entityCount > 0) { + const entitiesPerWorker = Math.ceil(entityCount / workerInfo.workerCount); + this.elements.workerLoad.textContent = `${entitiesPerWorker}/Worker (共${workerInfo.workerCount}个)`; + } else { + this.elements.workerLoad.textContent = 'N/A'; + } + } + } + + private getMemoryUsage(): number { + if ('memory' in performance) { + const memory = (performance as any).memory; + return memory.usedJSHeapSize / (1024 * 1024); // MB + } + return 0; + } + + private resetDemo(): void { + // 重置所有控件到默认值 + if (this.elements.entityCount) { + (this.elements.entityCount as HTMLInputElement).value = '1000'; + this.elements.entityCountValue.textContent = '1000'; + } + + + if (this.elements.gravity) { + (this.elements.gravity as HTMLInputElement).value = '100'; + this.elements.gravityValue.textContent = '100'; + } + + if (this.elements.friction) { + (this.elements.friction as HTMLInputElement).value = '95'; + this.elements.frictionValue.textContent = '95%'; + } + + // 确保Worker被启用 + const workerInfo = this.gameScene.getSystemInfo().physics; + if (!workerInfo.enabled) { + this.gameScene.toggleWorker(); // 只有在禁用时才切换 + } + if (this.elements.toggleWorker) { + this.elements.toggleWorker.textContent = '禁用 Worker'; + } + + // 重新生成实体 + this.gameScene.spawnInitialEntities(1000); + + // 重置配置 + this.gameScene.updateWorkerConfig({ + gravity: 100, + friction: 0.95 + }); + + console.log('演示已重置'); + } + + public stop(): void { + this.isRunning = false; + } +} + +// 启动演示 +document.addEventListener('DOMContentLoaded', () => { + try { + new WorkerDemo(); + } catch (error) { + console.error('启动演示失败:', error); + document.body.innerHTML = ` +
+

启动失败

+

错误: ${error}

+

请确保浏览器支持Web Workers和Canvas API

+
+ `; + } +}); \ No newline at end of file diff --git a/examples/worker-system-demo/src/systems/LifetimeSystem.ts b/examples/worker-system-demo/src/systems/LifetimeSystem.ts new file mode 100644 index 00000000..399387b2 --- /dev/null +++ b/examples/worker-system-demo/src/systems/LifetimeSystem.ts @@ -0,0 +1,30 @@ +import { EntitySystem, Matcher, Entity, ECSSystem, Time } from '@esengine/ecs-framework'; +import { Lifetime } from '../components'; + +@ECSSystem('LifetimeSystem') +export class LifetimeSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(Lifetime)); + } + + protected override process(entities: readonly Entity[]): void { + const entitiesToRemove: Entity[] = []; + + for (const entity of entities) { + const lifetime = entity.getComponent(Lifetime)!; + + // 更新年龄 + lifetime.currentAge += Time.deltaTime; + + // 检查是否需要销毁 + if (lifetime.isDead()) { + entitiesToRemove.push(entity); + } + } + + // 销毁过期的实体 + for (const entity of entitiesToRemove) { + entity.destroy(); + } + } +} \ No newline at end of file diff --git a/examples/worker-system-demo/src/systems/PhysicsWorkerSystem.ts b/examples/worker-system-demo/src/systems/PhysicsWorkerSystem.ts new file mode 100644 index 00000000..3f1ad533 --- /dev/null +++ b/examples/worker-system-demo/src/systems/PhysicsWorkerSystem.ts @@ -0,0 +1,466 @@ +import { WorkerEntitySystem, Matcher, Entity, ECSSystem, SharedArrayBufferProcessFunction } from '@esengine/ecs-framework'; +import { Position, Velocity, Physics, Renderable } from '../components'; + +interface PhysicsEntityData { + id: number; + x: number; + y: number; + dx: number; + dy: number; + mass: number; + bounce: number; + friction: number; + radius: number; +} + +interface PhysicsConfig { + gravity: number; + canvasWidth: number; + canvasHeight: number; + groundFriction: number; +} + +@ECSSystem('PhysicsWorkerSystem') +export class PhysicsWorkerSystem extends WorkerEntitySystem { + private physicsConfig: PhysicsConfig = { + gravity: 100, + canvasWidth: 800, + canvasHeight: 600, + groundFriction: 0.98 // 减少地面摩擦 + }; + + constructor(enableWorker: boolean = true) { + const defaultConfig = { + gravity: 100, + canvasWidth: 800, + canvasHeight: 600, + groundFriction: 0.98 + }; + + super( + Matcher.empty().all(Position, Velocity, Physics), + { + enableWorker, + workerCount: navigator.hardwareConcurrency || 2, // 恢复多Worker + systemConfig: defaultConfig, + useSharedArrayBuffer: true // 使用SharedArrayBuffer进行全局碰撞检测 + } + ); + } + + protected extractEntityData(entity: Entity): PhysicsEntityData { + const position = entity.getComponent(Position)!; + const velocity = entity.getComponent(Velocity)!; + const physics = entity.getComponent(Physics)!; + const renderable = entity.getComponent(Renderable)!; + + return { + id: entity.id, + x: position.x, + y: position.y, + dx: velocity.dx, + dy: velocity.dy, + mass: physics.mass, + bounce: physics.bounce, + friction: physics.friction, + radius: renderable.size + }; + } + + /** + * Worker处理函数 - 纯函数,会被序列化到Worker中执行 + * 注意:这个函数内部不能访问外部变量,必须是纯函数 + * 非SharedArrayBuffer模式:每个Worker只能看到分配给它的实体批次 + * 这会导致跨批次的碰撞检测缺失,但单批次内的碰撞是正确的 + */ + protected workerProcess( + entities: PhysicsEntityData[], + deltaTime: number, + systemConfig?: PhysicsConfig + ): PhysicsEntityData[] { + const config = systemConfig || { + gravity: 100, + canvasWidth: 800, + canvasHeight: 600, + groundFriction: 0.98 + }; + + // 创建实体副本以避免修改原始数据 + const result = entities.map(e => ({ ...e })); + + // 应用重力和基础物理 + for (let i = 0; i < result.length; i++) { + const entity = result[i]; + + // 应用重力 + entity.dy += config.gravity * deltaTime; + + // 更新位置 + entity.x += entity.dx * deltaTime; + entity.y += entity.dy * deltaTime; + + // 边界碰撞检测和处理 + if (entity.x <= entity.radius) { + entity.x = entity.radius; + entity.dx = -entity.dx * entity.bounce; + } else if (entity.x >= config.canvasWidth - entity.radius) { + entity.x = config.canvasWidth - entity.radius; + entity.dx = -entity.dx * entity.bounce; + } + + if (entity.y <= entity.radius) { + entity.y = entity.radius; + entity.dy = -entity.dy * entity.bounce; + } else if (entity.y >= config.canvasHeight - entity.radius) { + entity.y = config.canvasHeight - entity.radius; + entity.dy = -entity.dy * entity.bounce; + + // 地面摩擦力 + entity.dx *= config.groundFriction; + } + + // 空气阻力 + entity.dx *= entity.friction; + entity.dy *= entity.friction; + } + + // 小球间碰撞检测 + for (let i = 0; i < result.length; i++) { + for (let j = i + 1; j < result.length; j++) { + const ball1 = result[i]; + const ball2 = result[j]; + + // 计算距离 + const dx = ball2.x - ball1.x; + const dy = ball2.y - ball1.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const minDistance = ball1.radius + ball2.radius; + + // 检测碰撞 + if (distance < minDistance && distance > 0) { + // 碰撞法线 + const nx = dx / distance; + const ny = dy / distance; + + // 分离小球以避免重叠 + const overlap = minDistance - distance; + const separationX = nx * overlap * 0.5; + const separationY = ny * overlap * 0.5; + + ball1.x -= separationX; + ball1.y -= separationY; + ball2.x += separationX; + ball2.y += separationY; + + // 相对速度 + const relativeVelocityX = ball2.dx - ball1.dx; + const relativeVelocityY = ball2.dy - ball1.dy; + + // 沿碰撞法线的速度分量 + const velocityAlongNormal = relativeVelocityX * nx + relativeVelocityY * ny; + + // 如果速度分量为正,小球正在分离,不需要处理 + if (velocityAlongNormal > 0) continue; + + // 计算弹性系数(两球弹性的平均值) + const restitution = (ball1.bounce + ball2.bounce) * 0.5; + + // 计算冲量大小 + const impulseScalar = -(1 + restitution) * velocityAlongNormal / (1/ball1.mass + 1/ball2.mass); + + // 应用冲量 + const impulseX = impulseScalar * nx; + const impulseY = impulseScalar * ny; + + ball1.dx -= impulseX / ball1.mass; + ball1.dy -= impulseY / ball1.mass; + ball2.dx += impulseX / ball2.mass; + ball2.dy += impulseY / ball2.mass; + + // 轻微的能量损失,保持活力 + const energyLoss = 0.98; + ball1.dx *= energyLoss; + ball1.dy *= energyLoss; + ball2.dx *= energyLoss; + ball2.dy *= energyLoss; + } + } + } + + return result; + } + + /** + * 应用处理结果 + */ + protected applyResult(entity: Entity, result: PhysicsEntityData): void { + // 检查实体是否仍然存在且有效 + if (!entity || !entity.enabled) { + return; + } + + const position = entity.getComponent(Position); + const velocity = entity.getComponent(Velocity); + + // 检查组件是否仍然存在(实体可能在Worker处理期间被修改) + if (!position || !velocity) { + return; + } + + position.set(result.x, result.y); + velocity.set(result.dx, result.dy); + } + + /** + * 更新物理配置 + */ + public updatePhysicsConfig(newConfig: Partial): void { + Object.assign(this.physicsConfig, newConfig); + this.updateConfig({ systemConfig: this.physicsConfig }); + } + + /** + * 获取物理配置 + */ + public getPhysicsConfig(): PhysicsConfig { + return { ...this.physicsConfig }; + } + + private startTime: number = 0; + + + /** + * 性能监控 + */ + protected override onEnd(): void { + super.onEnd(); + const endTime = performance.now(); + const executionTime = endTime - this.startTime; + + // 发送性能数据到UI + (window as any).physicsExecutionTime = executionTime; + } + + /** + * 获取实体数据大小 + */ + protected getDefaultEntityDataSize(): number { + return 9; // id, x, y, dx, dy, mass, bounce, friction, radius + } + + /** + * 将实体数据写入SharedArrayBuffer + */ + protected writeEntityToBuffer(entityData: PhysicsEntityData, offset: number): void { + const sharedArray = (this as any).sharedFloatArray as Float32Array; + if (!sharedArray) return; + + // 在第一个位置存储当前实体数量,用于Worker函数判断实际有效数据范围 + const currentEntityCount = Math.floor(offset / 9) + 1; + sharedArray[0] = currentEntityCount; // 元数据:实际实体数量 + + // 数据从索引9开始存储(第一个9个位置用作元数据区域) + const dataOffset = offset + 9; + sharedArray[dataOffset + 0] = entityData.id; + sharedArray[dataOffset + 1] = entityData.x; + sharedArray[dataOffset + 2] = entityData.y; + sharedArray[dataOffset + 3] = entityData.dx; + sharedArray[dataOffset + 4] = entityData.dy; + sharedArray[dataOffset + 5] = entityData.mass; + sharedArray[dataOffset + 6] = entityData.bounce; + sharedArray[dataOffset + 7] = entityData.friction; + sharedArray[dataOffset + 8] = entityData.radius; + } + + /** + * 性能监控开始 + */ + protected override onBegin(): void { + super.onBegin(); + this.startTime = performance.now(); + } + + /** + * 从SharedArrayBuffer读取实体数据 + */ + protected readEntityFromBuffer(offset: number): PhysicsEntityData | null { + const sharedArray = (this as any).sharedFloatArray as Float32Array; + if (!sharedArray) return null; + + // 数据从索引9开始存储(第一个9个位置用作元数据区域) + const dataOffset = offset + 9; + return { + id: sharedArray[dataOffset + 0], + x: sharedArray[dataOffset + 1], + y: sharedArray[dataOffset + 2], + dx: sharedArray[dataOffset + 3], + dy: sharedArray[dataOffset + 4], + mass: sharedArray[dataOffset + 5], + bounce: sharedArray[dataOffset + 6], + friction: sharedArray[dataOffset + 7], + radius: sharedArray[dataOffset + 8] + }; + } + + /** + * SharedArrayBuffer处理函数 + */ + protected getSharedArrayBufferProcessFunction(): SharedArrayBufferProcessFunction { + return function(sharedFloatArray: Float32Array, startIndex: number, endIndex: number, deltaTime: number, systemConfig?: any) { + const config = systemConfig || { + gravity: 100, + canvasWidth: 800, + canvasHeight: 600, + groundFriction: 0.98 + }; + + // 读取实际实体数量(存储在第一个位置) + const actualEntityCount = sharedFloatArray[0]; + + // 基础物理更新 + for (let i = startIndex; i < endIndex && i < actualEntityCount; i++) { + const offset = i * 9 + 9; // 数据从索引9开始,加上元数据偏移 + + // 读取实体数据 + const id = sharedFloatArray[offset + 0]; + if (id === 0) continue; // 跳过无效实体 + + let x = sharedFloatArray[offset + 1]; + let y = sharedFloatArray[offset + 2]; + let dx = sharedFloatArray[offset + 3]; + let dy = sharedFloatArray[offset + 4]; + const mass = sharedFloatArray[offset + 5]; + const bounce = sharedFloatArray[offset + 6]; + const friction = sharedFloatArray[offset + 7]; + const radius = sharedFloatArray[offset + 8]; + + // 应用重力 + dy += config.gravity * deltaTime; + + // 更新位置 + x += dx * deltaTime; + y += dy * deltaTime; + + // 边界碰撞检测和处理 + if (x <= radius) { + x = radius; + dx = -dx * bounce; + } else if (x >= config.canvasWidth - radius) { + x = config.canvasWidth - radius; + dx = -dx * bounce; + } + + if (y <= radius) { + y = radius; + dy = -dy * bounce; + } else if (y >= config.canvasHeight - radius) { + y = config.canvasHeight - radius; + dy = -dy * bounce; + + // 地面摩擦力 + dx *= config.groundFriction; + } + + // 空气阻力 + dx *= friction; + dy *= friction; + + // 写回数据 + sharedFloatArray[offset + 1] = x; + sharedFloatArray[offset + 2] = y; + sharedFloatArray[offset + 3] = dx; + sharedFloatArray[offset + 4] = dy; + } + + // 小球间碰撞检测 + for (let i = startIndex; i < endIndex && i < actualEntityCount; i++) { + const offset1 = i * 9 + 9; // 数据从索引9开始,加上元数据偏移 + const id1 = sharedFloatArray[offset1 + 0]; + if (id1 === 0) continue; + + let x1 = sharedFloatArray[offset1 + 1]; + let y1 = sharedFloatArray[offset1 + 2]; + let dx1 = sharedFloatArray[offset1 + 3]; + let dy1 = sharedFloatArray[offset1 + 4]; + const mass1 = sharedFloatArray[offset1 + 5]; + const bounce1 = sharedFloatArray[offset1 + 6]; + const radius1 = sharedFloatArray[offset1 + 8]; + + // 检测与所有其他小球的碰撞(能看到所有实体,实现完整碰撞检测) + for (let j = 0; j < actualEntityCount; j++) { + if (i === j) continue; + + const offset2 = j * 9 + 9; // 数据从索引9开始,加上元数据偏移 + const id2 = sharedFloatArray[offset2 + 0]; + if (id2 === 0) continue; + + const x2 = sharedFloatArray[offset2 + 1]; + const y2 = sharedFloatArray[offset2 + 2]; + const dx2 = sharedFloatArray[offset2 + 3]; + const dy2 = sharedFloatArray[offset2 + 4]; + const mass2 = sharedFloatArray[offset2 + 5]; + const bounce2 = sharedFloatArray[offset2 + 6]; + const radius2 = sharedFloatArray[offset2 + 8]; + + // 额外检查:确保位置和半径都是有效值 + if (isNaN(x2) || isNaN(y2) || isNaN(radius2) || radius2 <= 0) continue; + + // 计算距离 + const deltaX = x2 - x1; + const deltaY = y2 - y1; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + const minDistance = radius1 + radius2; + + // 检测碰撞 + if (distance < minDistance && distance > 0) { + // 碰撞法线 + const nx = deltaX / distance; + const ny = deltaY / distance; + + // 分离小球 - 只调整当前Worker负责的球 + const overlap = minDistance - distance; + const separationX = nx * overlap * 0.5; + const separationY = ny * overlap * 0.5; + + x1 -= separationX; + y1 -= separationY; + + // 相对速度 + const relativeVelocityX = dx2 - dx1; + const relativeVelocityY = dy2 - dy1; + + // 沿碰撞法线的速度分量 + const velocityAlongNormal = relativeVelocityX * nx + relativeVelocityY * ny; + + // 如果速度分量为正,小球正在分离 + if (velocityAlongNormal > 0) continue; + + // 弹性系数 + const restitution = (bounce1 + bounce2) * 0.5; + + // 冲量计算 + const impulseScalar = -(1 + restitution) * velocityAlongNormal / (1/mass1 + 1/mass2); + + // 应用冲量到当前小球(只更新当前Worker负责的球) + const impulseX = impulseScalar * nx; + const impulseY = impulseScalar * ny; + + dx1 -= impulseX / mass1; + dy1 -= impulseY / mass1; + + // 能量损失 + const energyLoss = 0.98; + dx1 *= energyLoss; + dy1 *= energyLoss; + } + } + + // 只更新当前Worker负责的实体 + sharedFloatArray[offset1 + 1] = x1; + sharedFloatArray[offset1 + 2] = y1; + sharedFloatArray[offset1 + 3] = dx1; + sharedFloatArray[offset1 + 4] = dy1; + } + }; + } +} \ No newline at end of file diff --git a/examples/worker-system-demo/src/systems/RenderSystem.ts b/examples/worker-system-demo/src/systems/RenderSystem.ts new file mode 100644 index 00000000..9088d825 --- /dev/null +++ b/examples/worker-system-demo/src/systems/RenderSystem.ts @@ -0,0 +1,107 @@ +import { EntitySystem, Matcher, Entity, ECSSystem } from '@esengine/ecs-framework'; +import { Position, Renderable } from '../components'; + +@ECSSystem('RenderSystem') +export class RenderSystem extends EntitySystem { + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + private startTime: number = 0; + private batchCount: number = 0; + private drawCallCount: number = 0; + + constructor(canvas: HTMLCanvasElement) { + super(Matcher.empty().all(Position, Renderable)); + this.canvas = canvas; + this.ctx = canvas.getContext('2d')!; + } + + protected override onBegin(): void { + super.onBegin(); + this.startTime = performance.now(); + + // 清空画布 + this.ctx.fillStyle = '#000000'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + } + + protected override process(entities: readonly Entity[]): void { + // 保持原始绘制顺序,但优化连续相同颜色的绘制 + let lastColor = ''; + this.drawCallCount = 0; + + for (const entity of entities) { + const position = entity.getComponent(Position)!; + const renderable = entity.getComponent(Renderable)!; + + // 只在颜色变化时设置fillStyle,减少状态切换 + if (renderable.color !== lastColor) { + this.ctx.fillStyle = renderable.color; + lastColor = renderable.color; + } + + if (renderable.shape === 'circle') { + this.ctx.beginPath(); + this.ctx.arc(position.x, position.y, renderable.size, 0, Math.PI * 2); + this.ctx.fill(); + this.drawCallCount++; + } else if (renderable.shape === 'square') { + this.ctx.fillRect( + position.x - renderable.size / 2, + position.y - renderable.size / 2, + renderable.size, + renderable.size + ); + this.drawCallCount++; + } + } + + // 计算颜色多样性用于显示 + const uniqueColors = new Set(entities.map(e => e.getComponent(Renderable)!.color)); + this.batchCount = uniqueColors.size; + } + + protected override onEnd(): void { + super.onEnd(); + const endTime = performance.now(); + const executionTime = endTime - this.startTime; + + // 发送性能数据到UI + (window as any).renderExecutionTime = executionTime; + + // 绘制调试信息 + this.drawDebugInfo(); + } + + private drawDebugInfo(): void { + const entities = this.entities; + + this.ctx.fillStyle = '#00ff00'; + this.ctx.font = '14px Arial'; + this.ctx.fillText(`实体数量: ${entities.length}`, 10, 20); + this.ctx.fillText(`渲染批次: ${this.batchCount}`, 10, 140); + this.ctx.fillText(`绘制调用: ${this.drawCallCount}`, 10, 160); + + const workerInfo = (window as any).workerInfo; + if (workerInfo) { + this.ctx.fillStyle = workerInfo.enabled ? '#00ff00' : '#ff0000'; + this.ctx.fillText(`Worker: ${workerInfo.enabled ? '启用' : '禁用'}`, 10, 40); + + if (workerInfo.enabled) { + this.ctx.fillStyle = '#ffff00'; + const entitiesPerWorker = Math.ceil(entities.length / workerInfo.workerCount); + this.ctx.fillText(`每个Worker实体: ${entitiesPerWorker}`, 10, 60); + this.ctx.fillText(`Worker数量: ${workerInfo.workerCount}`, 10, 80); + } + } + + // 显示性能信息 + const physicsTime = (window as any).physicsExecutionTime || 0; + const renderTime = (window as any).renderExecutionTime || 0; + + this.ctx.fillStyle = physicsTime > 16 ? '#ff0000' : physicsTime > 8 ? '#ffff00' : '#00ff00'; + this.ctx.fillText(`物理: ${physicsTime.toFixed(2)}ms`, 10, 100); + + this.ctx.fillStyle = renderTime > 16 ? '#ff0000' : renderTime > 8 ? '#ffff00' : '#00ff00'; + this.ctx.fillText(`渲染: ${renderTime.toFixed(2)}ms`, 10, 120); + } +} \ No newline at end of file diff --git a/examples/worker-system-demo/src/systems/index.ts b/examples/worker-system-demo/src/systems/index.ts new file mode 100644 index 00000000..3279b8f3 --- /dev/null +++ b/examples/worker-system-demo/src/systems/index.ts @@ -0,0 +1,3 @@ +export { PhysicsWorkerSystem } from './PhysicsWorkerSystem'; +export { RenderSystem } from './RenderSystem'; +export { LifetimeSystem } from './LifetimeSystem'; \ No newline at end of file diff --git a/examples/worker-system-demo/tsconfig.json b/examples/worker-system-demo/tsconfig.json new file mode 100644 index 00000000..23255742 --- /dev/null +++ b/examples/worker-system-demo/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/examples/worker-system-demo/tsconfig.node.json b/examples/worker-system-demo/tsconfig.node.json new file mode 100644 index 00000000..099658cf --- /dev/null +++ b/examples/worker-system-demo/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/examples/worker-system-demo/vite.config.ts b/examples/worker-system-demo/vite.config.ts new file mode 100644 index 00000000..ca96ff59 --- /dev/null +++ b/examples/worker-system-demo/vite.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 3000, + open: true, + headers: { + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin' + } + }, + build: { + target: 'es2020', + outDir: 'dist', + minify: false, + rollupOptions: { + output: { + format: 'es', + manualChunks: undefined + } + } + }, + esbuild: { + target: 'es2020' + } +}); \ No newline at end of file