diff --git a/assets/libs/ecs/ECS.ts b/assets/libs/ecs/ECS.ts index 023a009..2472d1b 100644 --- a/assets/libs/ecs/ECS.ts +++ b/assets/libs/ecs/ECS.ts @@ -1,348 +1,348 @@ -import { ECSComp } from './ECSComp'; -import { ECSEntity } from './ECSEntity'; -import { ECSMask } from './ECSMask'; -import { ECSMatcher } from './ECSMatcher'; -import type { CompCtor, CompType, EntityCtor } from './ECSModel'; -import { ECSModel } from './ECSModel'; -import { ECSComblockSystem, ECSRootSystem, ECSSystem } from './ECSSystem'; -import { ecsPoolCoordinator } from './ECSPoolManager'; - -/** - * ECSEntity对象在destroy后,会回收到ECSPoolManager动态对象池中 - * ECSComp对象从ECSEntity.remove后,数据组件会回收到ECSPoolManager动态对象池中 - */ - -/** - * Entity-Component-System(实体-组件-系统)框架 - * 文档:https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12033388&doc_id=2873565 - */ -export namespace ecs { - /** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */ - export type Entity = ECSEntity; - /** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */ - export type Comp = ECSComp; - /** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */ - export type System = ECSSystem; - /** 根系统 - 驱动游戏中所有系统工作 */ - export type RootSystem = ECSRootSystem; - /** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */ - export type ComblockSystem = ECSComblockSystem; - - /** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */ - export const Entity = ECSEntity; - /** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */ - export const Comp = ECSComp; - /** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */ - export const System = ECSSystem; - /** 根系统 - 驱动游戏中所有系统工作 */ - export const RootSystem = ECSRootSystem; - /** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */ - export const ComblockSystem = ECSComblockSystem; - - //#region 接口 - - /** 组件接口 */ - export interface IComp { - canRecycle: boolean; - ent: Entity; - tid: number; - - reset(): void; - } - - /** 实体匹配器接口 */ - export interface IMatcher { - mid: number; - indices: number[]; - key: string; - isMatch(entity: Entity): boolean; - } - - /** - * 监听组件首次添加到实体上时,在ComblockSystem上实现这个接口 - * 1. entityEnter会在update方法之前执行,实体进入后,不会再次进入entityEnter方法中 - * 2. 当实体从当前System移除,下次再次符合条件进入System也会执行上述流程 - * @example - export class RoleUpgradeSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { - filter(): ecs.IMatcher { - return ecs.allOf(RoleUpgradeComp, RoleModelLevelComp); - } - - entityEnter(e: Role): void { - e.remove(RoleUpgradeComp); - } - } - */ - export interface IEntityEnterSystem { - entityEnter(entity: E): void; - } - - /** 监听组件从实体上移除时,在ComblockSystem上实现这个接口 */ - export interface IEntityRemoveSystem { - entityRemove(entity: E): void; - } - - /** 监听系统第一次执行update处理实体时,在ComblockSystem上实现这个接口 */ - export interface ISystemFirstUpdate { - firstUpdate(entity: E): void; - } - - /** 监听系统执行update处理实体时,在ComblockSystem上实现这个接口 */ - export interface ISystemUpdate { - update(entity: E): void; - } - //#endregion - - /** - * 注册组件到ecs系统中 - * @param name 由于js打包会改变类名,所以这里必须手动传入组件的名称 - * @param canNew 标识是否可以new对象。想继承自Cocos Creator的组件就不能去new,需要写成@ecs.register('name', false) - * @example - // 注册实体 - @ecs.register('Role') - export class Role extends ecs.Entity { - - } - - // 注册数据组件 - @ecs.register('RoleModel') - export class RoleModelComp extends ecs.Comp { - id: number = -1; - - reset() { - this.id = -1; - } - } - - // 注册系统组件 - @ecs.register('Initialize') - export class InitResSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { - - } - - // 注册显示对象组件 - @ccclass('RoleViewComp') - @ecs.register('RoleView', false) - export class RoleViewComp extends CCComp { - onLoad(){ - - } - } - */ - export function register(name: string, canNew = true) { - return function (ctor: EntityCtor | CompCtor | (new () => T)) { - const ctorAny = ctor as unknown as { - s?: boolean; - tid?: number; - compName?: string; - }; - - // 注册系统 - if (ctorAny.s) { - let system = ECSModel.systems.get(name); - if (system == null) { - system = new ecs.System(); - ECSModel.systems.set(name, system); - } - system.add(new (ctor as new () => ComblockSystem)()); - } - // 注册实体 - else if (ctorAny.tid === undefined) { - ECSModel.entityCtors.set(ctor as EntityCtor, name); - } - // 注册组件 - else { - if (ctorAny.tid === -1) { - ctorAny.tid = ECSModel.compTid++; - ctorAny.compName = name; - ECSModel.compCtors.push(ctor as CompCtor); - ECSModel.compAddOrRemove.set(ctorAny.tid, []); - } - else { - throw new Error(`ECS 组件重复注册: ${name}.`); - } - } - }; - } - - /** - * 创建一个新的实体对象或从缓存中获取一个实体对象(使用动态池) - * @param ctor 实体类 - */ - export function getEntity(ctor: EntityCtor): T { - // 获取实体对象名 - const entityName = ECSModel.entityCtors.get(ctor as EntityCtor); - if (entityName === undefined) { - console.error(`${ctor.name} 实体没有注册`); - throw new Error(`${ctor.name} 实体没有注册`); - } - - // 使用动态池管理器 - const pool = ecsPoolCoordinator.getPool( - entityName, - () => { - const entity = new ctor(); - entity.eid = ECSModel.eid++; // 实体唯一编号 - entity.name = entityName; - return entity; - } - ); - - const entity = pool.get(); - - // 触发实体初始化逻辑 - const entityWithInit = entity as ECSEntity & { init?: () => void }; - entity.isValid = true; // 无论新建还是复用,都标记为有效 - if (entityWithInit.init) entityWithInit.init(); - - ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); - return entity; - } - - /** - * 动态查询实体 - * @param matcher 匹配器 - * @example - * ecs.query(ecs.allOf(Comp1, Comp2)); - */ - export function query(matcher: IMatcher): E[] { - let group = ECSModel.groups.get(matcher.mid); - if (!group) { - group = ECSModel.createGroup(matcher); - ECSModel.eid2Entity.forEach(group.onComponentAddOrRemove, group); - } - return group.matchEntities as E[]; - } - - /** 清理所有的实体 */ - export function clear(): void { - ECSModel.eid2Entity.forEach((entity) => { - entity.destroy(); - }); - ECSModel.groups.forEach((group) => { - group.clear(); - }); - ECSModel.compAddOrRemove.forEach((callbackLst) => { - callbackLst.length = 0; - }); - ECSModel.eid2Entity.clear(); - ECSModel.groups.clear(); - } - - /** - * 清理所有对象池 - 用于释放不再使用的缓存内存 - * 注意:此操作会清空所有实体池、组件池和 Mask 池,请在确保不再需要这些缓存时调用 - */ - export function clearPools(): void { - // 清理 Mask 对象池 - ECSMask.clearPool(); - - // 清理动态池管理器 - ecsPoolCoordinator.clearAll(); - } - - /** - * 通过实体唯一编号获得实体对象 - * @param eid 实体唯一编号 - */ - export function getEntityByEid(eid: number): E | undefined { - return ECSModel.eid2Entity.get(eid) as E | undefined; - } - - /** 当前活动中的实体数量 */ - export function activeEntityCount(): number { - return ECSModel.eid2Entity.size; - } - - /** 创建实体 */ - function createEntity(): E { - const entity = new Entity(); - entity.eid = ECSModel.eid++; // 实体id也是有限的资源 - ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); - return entity as E; - } - - /** - * 指定一个组件创建实体,返回组件对象。 - * @param ctor - */ - function createEntityWithComp(ctor: CompCtor): T { - const entity = createEntity(); - return entity.add(ctor); - } - - //#region 过滤器 - /** - * 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件 - * 进行添加操作引发Group重复添加实体。 - * @param args - * @example - * ecs.allOf(AComponent, BComponent, CComponent); - */ - export function allOf(...args: CompType[]): IMatcher { - return new ECSMatcher().allOf(...args); - } - - /** - * 组件间是或的关系,表示关注拥有任意一个这些组件的实体 - * @param args 组件类 - * @example - * ecs.anyOf(AComponent, BComponent); - */ - export function anyOf(...args: CompType[]): IMatcher { - return new ECSMatcher().anyOf(...args); - } - - /** - * 表示关注只拥有这些组件的实体 - * 注:不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件 - * @param args 组件类 - * @example - // 不包含CComponent或者DComponent - ecs.allOf(AComponent, BComponent).excludeOf(CComponent, DComponent); - - // 不同时包含CComponent和DComponent - ecs.allOf(AComponent, BComponent).excludeOf(CComponent).excludeOf(DComponent); - */ - export function onlyOf(...args: CompType[]): IMatcher { - return new ECSMatcher().onlyOf(...args); - } - - /** - * 不包含指定的任意一个组件 - * @param args 组件类 - * @example - // 表示不包含组件A或者组件B - ecs.excludeOf(A, B); - */ - export function excludeOf(...args: CompType[]): IMatcher { - return new ECSMatcher().excludeOf(...args); - } - //#endregion - - //#region 单例组件 - /** - * 获取单例组件 - * @param ctor 组件类 - */ - export function getSingleton(ctor: CompCtor): T { - if (!ECSModel.tid2comp.has(ctor.tid)) { - const comp = createEntityWithComp(ctor); - ECSModel.tid2comp.set(ctor.tid, comp); - } - return ECSModel.tid2comp.get(ctor.tid) as T; - } - - /** - * 注册单例组件 - 主要用于那些不能手动创建对象的组件 - * @param obj - */ - export function addSingleton(obj: IComp): void { - const tid = (obj.constructor as CompCtor).tid; - if (!ECSModel.tid2comp.has(tid)) { - ECSModel.tid2comp.set(tid, obj); - } - } - - //#endregion -} +import { ECSComp } from './ECSComp'; +import { ECSEntity } from './ECSEntity'; +import { ECSMask } from './ECSMask'; +import { ECSMatcher } from './ECSMatcher'; +import type { CompCtor, CompType, EntityCtor } from './ECSModel'; +import { ECSModel } from './ECSModel'; +import { ECSComblockSystem, ECSRootSystem, ECSSystem } from './ECSSystem'; +import { ecsPoolCoordinator } from './pool'; + +/** + * ECSEntity对象在destroy后,会回收到ECSPoolManager动态对象池中 + * ECSComp对象从ECSEntity.remove后,数据组件会回收到ECSPoolManager动态对象池中 + */ + +/** + * Entity-Component-System(实体-组件-系统)框架 + * 文档:https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12033388&doc_id=2873565 + */ +export namespace ecs { + /** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */ + export type Entity = ECSEntity; + /** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */ + export type Comp = ECSComp; + /** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */ + export type System = ECSSystem; + /** 根系统 - 驱动游戏中所有系统工作 */ + export type RootSystem = ECSRootSystem; + /** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */ + export type ComblockSystem = ECSComblockSystem; + + /** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */ + export const Entity = ECSEntity; + /** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */ + export const Comp = ECSComp; + /** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */ + export const System = ECSSystem; + /** 根系统 - 驱动游戏中所有系统工作 */ + export const RootSystem = ECSRootSystem; + /** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */ + export const ComblockSystem = ECSComblockSystem; + + //#region 接口 + + /** 组件接口 */ + export interface IComp { + canRecycle: boolean; + ent: Entity; + tid: number; + + reset(): void; + } + + /** 实体匹配器接口 */ + export interface IMatcher { + mid: number; + indices: number[]; + key: string; + isMatch(entity: Entity): boolean; + } + + /** + * 监听组件首次添加到实体上时,在ComblockSystem上实现这个接口 + * 1. entityEnter会在update方法之前执行,实体进入后,不会再次进入entityEnter方法中 + * 2. 当实体从当前System移除,下次再次符合条件进入System也会执行上述流程 + * @example + export class RoleUpgradeSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { + filter(): ecs.IMatcher { + return ecs.allOf(RoleUpgradeComp, RoleModelLevelComp); + } + + entityEnter(e: Role): void { + e.remove(RoleUpgradeComp); + } + } + */ + export interface IEntityEnterSystem { + entityEnter(entity: E): void; + } + + /** 监听组件从实体上移除时,在ComblockSystem上实现这个接口 */ + export interface IEntityRemoveSystem { + entityRemove(entity: E): void; + } + + /** 监听系统第一次执行update处理实体时,在ComblockSystem上实现这个接口 */ + export interface ISystemFirstUpdate { + firstUpdate(entity: E): void; + } + + /** 监听系统执行update处理实体时,在ComblockSystem上实现这个接口 */ + export interface ISystemUpdate { + update(entity: E): void; + } + //#endregion + + /** + * 注册组件到ecs系统中 + * @param name 由于js打包会改变类名,所以这里必须手动传入组件的名称 + * @param canNew 标识是否可以new对象。想继承自Cocos Creator的组件就不能去new,需要写成@ecs.register('name', false) + * @example + // 注册实体 + @ecs.register('Role') + export class Role extends ecs.Entity { + + } + + // 注册数据组件 + @ecs.register('RoleModel') + export class RoleModelComp extends ecs.Comp { + id: number = -1; + + reset() { + this.id = -1; + } + } + + // 注册系统组件 + @ecs.register('Initialize') + export class InitResSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { + + } + + // 注册显示对象组件 + @ccclass('RoleViewComp') + @ecs.register('RoleView', false) + export class RoleViewComp extends CCComp { + onLoad(){ + + } + } + */ + export function register(name: string, canNew = true) { + return function (ctor: EntityCtor | CompCtor | (new () => T)) { + const ctorAny = ctor as unknown as { + s?: boolean; + tid?: number; + compName?: string; + }; + + // 注册系统 + if (ctorAny.s) { + let system = ECSModel.systems.get(name); + if (system == null) { + system = new ecs.System(); + ECSModel.systems.set(name, system); + } + system.add(new (ctor as new () => ComblockSystem)()); + } + // 注册实体 + else if (ctorAny.tid === undefined) { + ECSModel.entityCtors.set(ctor as EntityCtor, name); + } + // 注册组件 + else { + if (ctorAny.tid === -1) { + ctorAny.tid = ECSModel.compTid++; + ctorAny.compName = name; + ECSModel.compCtors.push(ctor as CompCtor); + ECSModel.compAddOrRemove.set(ctorAny.tid, []); + } + else { + throw new Error(`ECS 组件重复注册: ${name}.`); + } + } + }; + } + + /** + * 创建一个新的实体对象或从缓存中获取一个实体对象(使用动态池) + * @param ctor 实体类 + */ + export function getEntity(ctor: EntityCtor): T { + // 获取实体对象名 + const entityName = ECSModel.entityCtors.get(ctor as EntityCtor); + if (entityName === undefined) { + console.error(`${ctor.name} 实体没有注册`); + throw new Error(`${ctor.name} 实体没有注册`); + } + + // 使用动态池管理器 + const pool = ecsPoolCoordinator.getPool( + entityName, + () => { + const entity = new ctor(); + entity.eid = ECSModel.eid++; // 实体唯一编号 + entity.name = entityName; + return entity; + } + ); + + const entity = pool.get(); + + // 触发实体初始化逻辑 + const entityWithInit = entity as ECSEntity & { init?: () => void }; + entity.isValid = true; // 无论新建还是复用,都标记为有效 + if (entityWithInit.init) entityWithInit.init(); + + ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); + return entity; + } + + /** + * 动态查询实体 + * @param matcher 匹配器 + * @example + * ecs.query(ecs.allOf(Comp1, Comp2)); + */ + export function query(matcher: IMatcher): E[] { + let group = ECSModel.groups.get(matcher.mid); + if (!group) { + group = ECSModel.createGroup(matcher); + ECSModel.eid2Entity.forEach(group.onComponentAddOrRemove, group); + } + return group.matchEntities as E[]; + } + + /** 清理所有的实体 */ + export function clear(): void { + ECSModel.eid2Entity.forEach((entity) => { + entity.destroy(); + }); + ECSModel.groups.forEach((group) => { + group.clear(); + }); + ECSModel.compAddOrRemove.forEach((callbackLst) => { + callbackLst.length = 0; + }); + ECSModel.eid2Entity.clear(); + ECSModel.groups.clear(); + } + + /** + * 清理所有对象池 - 用于释放不再使用的缓存内存 + * 注意:此操作会清空所有实体池、组件池和 Mask 池,请在确保不再需要这些缓存时调用 + */ + export function clearPools(): void { + // 清理 Mask 对象池 + ECSMask.clearPool(); + + // 清理动态池管理器 + ecsPoolCoordinator.clearAll(); + } + + /** + * 通过实体唯一编号获得实体对象 + * @param eid 实体唯一编号 + */ + export function getEntityByEid(eid: number): E | undefined { + return ECSModel.eid2Entity.get(eid) as E | undefined; + } + + /** 当前活动中的实体数量 */ + export function activeEntityCount(): number { + return ECSModel.eid2Entity.size; + } + + /** 创建实体 */ + function createEntity(): E { + const entity = new Entity(); + entity.eid = ECSModel.eid++; // 实体id也是有限的资源 + ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); + return entity as E; + } + + /** + * 指定一个组件创建实体,返回组件对象。 + * @param ctor + */ + function createEntityWithComp(ctor: CompCtor): T { + const entity = createEntity(); + return entity.add(ctor); + } + + //#region 过滤器 + /** + * 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件 + * 进行添加操作引发Group重复添加实体。 + * @param args + * @example + * ecs.allOf(AComponent, BComponent, CComponent); + */ + export function allOf(...args: CompType[]): IMatcher { + return new ECSMatcher().allOf(...args); + } + + /** + * 组件间是或的关系,表示关注拥有任意一个这些组件的实体 + * @param args 组件类 + * @example + * ecs.anyOf(AComponent, BComponent); + */ + export function anyOf(...args: CompType[]): IMatcher { + return new ECSMatcher().anyOf(...args); + } + + /** + * 表示关注只拥有这些组件的实体 + * 注:不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件 + * @param args 组件类 + * @example + // 不包含CComponent或者DComponent + ecs.allOf(AComponent, BComponent).excludeOf(CComponent, DComponent); + + // 不同时包含CComponent和DComponent + ecs.allOf(AComponent, BComponent).excludeOf(CComponent).excludeOf(DComponent); + */ + export function onlyOf(...args: CompType[]): IMatcher { + return new ECSMatcher().onlyOf(...args); + } + + /** + * 不包含指定的任意一个组件 + * @param args 组件类 + * @example + // 表示不包含组件A或者组件B + ecs.excludeOf(A, B); + */ + export function excludeOf(...args: CompType[]): IMatcher { + return new ECSMatcher().excludeOf(...args); + } + //#endregion + + //#region 单例组件 + /** + * 获取单例组件 + * @param ctor 组件类 + */ + export function getSingleton(ctor: CompCtor): T { + if (!ECSModel.tid2comp.has(ctor.tid)) { + const comp = createEntityWithComp(ctor); + ECSModel.tid2comp.set(ctor.tid, comp); + } + return ECSModel.tid2comp.get(ctor.tid) as T; + } + + /** + * 注册单例组件 - 主要用于那些不能手动创建对象的组件 + * @param obj + */ + export function addSingleton(obj: IComp): void { + const tid = (obj.constructor as CompCtor).tid; + if (!ECSModel.tid2comp.has(tid)) { + ECSModel.tid2comp.set(tid, obj); + } + } + + //#endregion +} diff --git a/assets/libs/ecs/ECSEntity.ts b/assets/libs/ecs/ECSEntity.ts index 29a71cc..d77e795 100644 --- a/assets/libs/ecs/ECSEntity.ts +++ b/assets/libs/ecs/ECSEntity.ts @@ -1,470 +1,470 @@ -import type { ecs } from './ECS'; -import { ECSMask } from './ECSMask'; -import type { CompCtor, CompType } from './ECSModel'; -import { ECSModel } from './ECSModel'; -import { ecsPoolCoordinator } from './ECSPoolManager'; - -//#region 辅助方法 - -/** - * 实体身上组件有增删操作,广播通知对应的观察者 - * @param entity 实体对象 - * @param componentTypeId 组件类型id - */ -function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number): void { - const events = ECSModel.compAddOrRemove.get(componentTypeId); - if (events) { - for (let i = events.length - 1; i >= 0; i--) { - events[i](entity); - } - } - // 判断是不是删了单例组件 - if (ECSModel.tid2comp.has(componentTypeId)) { - ECSModel.tid2comp.delete(componentTypeId); - } -} - -/** - * 设置实体上的组件属性(类型安全的动态属性访问) - */ -function setEntityComp(entity: ECSEntity, compName: string, comp: T): void { - Reflect.set(entity, compName, comp); -} - -/** - * 获取实体上的组件属性(类型安全的动态属性访问) - */ -function getEntityComp(entity: ECSEntity, compName: string): T | undefined { - return Reflect.get(entity, compName) as T | undefined; -} - -/** - * 删除实体上的组件属性(类型安全的动态属性访问) - */ -function deleteEntityComp(entity: ECSEntity, compName: string): void { - Reflect.set(entity, compName, null); -} - -/** - * 创建组件对象(使用动态池管理) - * @param ctor - */ -function createComp(ctor: CompCtor): T { - const cct = ECSModel.compCtors[ctor.tid]; - if (!cct) { - throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`); - } - - // 使用动态池管理器 - const pool = ecsPoolCoordinator.getPool( - ctor.compName, - () => new (cct as CompCtor)() - ); - - const component = pool.get(); - return component as T; -} - -/** - * 销毁实体(使用动态池管理) - * - * 缓存销毁的实体,下次新建实体时会优先从缓存中拿 - * @param entity - */ -function destroyEntity(entity: ECSEntity): void { - if (ECSModel.eid2Entity.has(entity.eid)) { - // 使用动态池管理器 - const pool = ecsPoolCoordinator.getPool( - entity.name, - () => new ECSEntity() - ); - - // 清空mask并回收 - entity.getMask().clear(); - pool.recycle(entity); - - ECSModel.eid2Entity.delete(entity.eid); - } - else { - console.warn('试图销毁不存在的实体'); - } -} - -//#endregion - -/** ECS实体对象 */ -export class ECSEntity { - /** 实体唯一标识,不要手动修改 */ - eid = -1; - /** 实体对象名 */ - name = ''; - /** 实体是否有效 */ - isValid = true; - /** 组件过滤数据 */ - private mask: ECSMask = new ECSMask(); - /** 当前实体身上附加的组件构造函数 */ - private compTid2Ctor: Map> = new Map(); - /** - * 配合 entity.remove(Comp, false), 记录组件实例上的缓存数据,在添加时恢复原数据 - * - * ⚠️ 核心机制: - * - isRecycle=true:调用reset(),根据canRecycle决定是否回收到全局池 - * - isRecycle=false:不调用reset(),缓存在实体上 - * - * ⚠️ 使用场景说明: - * 1. UI显示/隐藏 - 保留UI状态数据 - * 2. 技能冷却 - 保留冷却时间 - * 3. 临时禁用功能 - 保留配置数据 - * - * 💡 管理建议: - * - 无任何限制,完全由用户自行管理 - * - 使用 ECSMemoryMonitor 监控缓存使用情况 - * - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理 - * - 在场景切换、内存警告时主动清理缓存 - */ - private compTid2Obj: Map = new Map(); - - /** 获取 mask 对象(内部使用) */ - getMask(): ECSMask { - return this.mask; - } - - private _parent: ECSEntity | null = null; - /** 父实体 */ - get parent(): ECSEntity | null { - return this._parent; - } - set parent(value: ECSEntity | null) { - this._parent = value; - } - - /** 子实体 */ - private childs: Map | null = null; - - /** 获取子实体 */ - getChild(eid: number): T | undefined { - return this.childs?.get(eid) as T | undefined; - } - - /** - * 添加子实体(带循环引用检测) - * @param entity 被添加的实体对象 - * @returns 子实体的唯一编号, -1表示添加失败 - */ - addChild(entity: ECSEntity): number { - if (this.childs === null) { - this.childs = new Map(); - } - - if (this.childs.has(entity.eid)) { - console.warn(`子实体${entity.name}已存在`); - return -1; - } - - // 检测循环引用 - if (this.hasAncestor(entity)) { - console.error(`检测到循环引用: ${this.name} -> ${entity.name}`); - return -1; - } - - entity._parent = this; - this.childs.set(entity.eid, entity); - return entity.eid; - } - - /** - * 检测是否存在祖先关系(防止循环引用) - */ - private hasAncestor(entity: ECSEntity): boolean { - let current: ECSEntity | null = this; - while (current) { - if (current === entity) return true; - current = current.parent; - } - return false; - } - - /** - * 移除子实体 - * @param entity 被移除的实体对象 - * @param isDestroy 被移除的实体是否释放,默认为释放 - * @returns - */ - removeChild(entity: ECSEntity, isDestroy = true): void { - if (this.childs === null) return; - - entity.parent = null; - this.childs.delete(entity.eid); - if (isDestroy) entity.destroy(); - } - - /** - * 根据组件类动态创建组件,并通知关心的系统。如果实体存在了这个组件,那么会先删除之前的组件然后添加新的 - * - * 注意:不要直接new Component,new来的Component不会从Component的缓存池拿缓存的数据 - * @param componentTypeId 组件类 - * @param isReAdd true-表示用户指定这个实体可能已经存在了该组件,那么再次add组件的时候会先移除该组件然后再添加一遍。false-表示不重复添加组件 - */ - add(obj: T): ECSEntity; - add(ctor: CompType, isReAdd?: boolean): T; - add(ctor: CompType | T, isReAdd = false): T | ECSEntity { - if (typeof ctor === 'function') { - const compTid = ctor.tid; - if (ctor.tid === -1) { - throw Error(`【${this.name}】实体【${ctor.compName}】组件未注册`); - } - if (this.compTid2Ctor.has(compTid)) { // 判断是否有该组件,如果有则先移除 - if (isReAdd) { - this.remove(ctor); - } - else { - console.log(`【${this.name}】实体【${ctor.compName}】组件已存在`); - const existingComp = getEntityComp(this, ctor.compName); - if (existingComp) { - return existingComp; - } - } - } - this.mask.set(compTid); - - let comp: T; - if (this.compTid2Obj.has(compTid)) { - comp = this.compTid2Obj.get(compTid) as T; - this.compTid2Obj.delete(compTid); - } - else { - // 创建组件对象 - comp = createComp(ctor); - } - - // 将组件对象直接附加到实体对象身上,方便直接获取 - setEntityComp(this, ctor.compName, comp); - this.compTid2Ctor.set(compTid, ctor); - comp.tid = compTid; - comp.ent = this; - // 广播实体添加组件的消息 - broadcastCompAddOrRemove(this, compTid); - - return comp; - } - else { - // 此时 ctor 是组件实例 - const compInstance = ctor as T; - const tmpCtor = (compInstance.constructor as CompCtor); - const compTid = tmpCtor.tid; - if (compTid === -1 || compTid == null) throw Error(`【${this.name}】实体【${tmpCtor.name}】组件未注册`); - if (this.compTid2Ctor.has(compTid)) throw Error(`【${this.name}】实体【${tmpCtor.name}】组件已经存在`); - - this.mask.set(compTid); - setEntityComp(this, tmpCtor.compName, compInstance); - this.compTid2Ctor.set(compTid, tmpCtor); - compInstance.tid = compTid; - compInstance.canRecycle = false; - compInstance.ent = this; - broadcastCompAddOrRemove(this, compTid); - - return this; - } - } - - /** - * 批量添加组件 - * @param ctors 组件类 - * @returns - */ - addComponents(...ctors: CompType[]): this { - const len = ctors.length; - for (let i = 0; i < len; i++) { - this.add(ctors[i]); - } - return this; - } - - /** - * 获取一个组件实例 - * @param ctor 组件类 - */ - get(ctor: CompCtor): T | undefined { - return getEntityComp(this, ctor.compName); - } - - /** - * 组件是否在实体存在内 - * @param ctor 组件类 - */ - has(ctor: CompType): boolean { - const tid = typeof ctor === 'number' ? ctor : ctor.tid; - return this.compTid2Ctor.has(tid); - } - - /** - * 从实体上删除指定组件 - * @param ctor 组件构造函数或者组件Tag - * @param isRecycle 是否回收该组件对象 - * - * **isRecycle=true(默认):** - * - 调用组件的 reset() 方法清理数据 - * - 如果 canRecycle=true,回收到全局对象池供复用 - * - 如果 canRecycle=false,直接销毁 - * - 适用于大部分场景 - * - * **isRecycle=false:** - * - 不调用 reset(),数据完整保留 - * - 组件缓存在当前实体上,下次 add() 时恢复 - * - 无任何限制和检查,完全由用户自行管理 - * - 适用于需要保留状态的场景(UI切换、技能冷却等) - * - * ⚠️ isRecycle=false 使用场景: - * - UI显示/隐藏:保留UI状态(如滚动位置、选中项) - * - 技能冷却:保留冷却剩余时间 - * - 临时禁用:保留配置数据,稍后恢复 - * - * 💡 管理建议: - * - 使用 ECSMemoryMonitor 监控缓存使用情况 - * - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理 - * - 在场景切换、内存警告时主动清理缓存 - */ - remove(ctor: CompType, isRecycle = true): void { - const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid; - const compName = typeof ctor === 'number' ? '' : ctor.compName; - - if (!this.mask.has(componentTypeId)) { - return; - } - - const comp = getEntityComp(this, compName); - if (!comp) { - return; - } - - comp.ent = null!; - - if (isRecycle) { - comp.reset(); - - // 回收到全局池 - if (comp.canRecycle) { - const ctor = ECSModel.compCtors[componentTypeId]; - if (ctor) { - const pool = ecsPoolCoordinator.getPool( - ctor.compName, - () => new (ctor as any)() - ); - pool.recycle(comp); - } - } - } - else { - // isRecycle=false:用户明确要求缓存,完全尊重用户意图 - // 不做任何检查和限制,由用户通过 ECSMemoryMonitor 自行管理 - this.compTid2Obj.set(componentTypeId, comp); - } - - // 删除实体上的组件逻辑 - deleteEntityComp(this, compName); - this.mask.delete(componentTypeId); - this.compTid2Ctor.delete(componentTypeId); - broadcastCompAddOrRemove(this, componentTypeId); - } - - /** - * 清理指定组件的缓存 - * @param ctor 组件构造函数 - */ - clearComponentCache(ctor: CompType): void { - const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid; - const comp = this.compTid2Obj.get(componentTypeId); - - if (comp) { - this.compTid2Obj.delete(componentTypeId); - - const ctorObj = ECSModel.compCtors[componentTypeId]; - const compName = ctorObj?.compName || `tid:${componentTypeId}`; - - comp.reset(); - if (comp.canRecycle) { - if (ctorObj) { - const pool = ecsPoolCoordinator.getPool( - ctorObj.compName, - () => new (ctorObj as any)() - ); - pool.recycle(comp); - } - } - - console.log(`[ECS] 实体 ${this.name} 清理组件缓存: ${compName}`); - } - } - - /** - * 清理所有组件缓存 - */ - clearAllComponentCache(): void { - if (this.compTid2Obj.size === 0) return; - - const count = this.compTid2Obj.size; - - this.compTid2Obj.forEach((comp, tid) => { - comp.reset(); - if (comp.canRecycle) { - const ctor = ECSModel.compCtors[tid]; - if (ctor) { - const pool = ecsPoolCoordinator.getPool( - ctor.compName, - () => new (ctor as any)() - ); - pool.recycle(comp); - } - } - }); - - this.compTid2Obj.clear(); - - console.log(`[ECS] 实体 ${this.name} 清理所有缓存,共 ${count} 个组件`); - } - - /** - * 获取缓存的组件数量 - */ - getCachedComponentCount(): number { - return this.compTid2Obj.size; - } - - /** - * 获取缓存的组件列表(用于监控) - */ - getCachedComponents(): Map { - return new Map(this.compTid2Obj); - } - - /** 销毁实体,实体会被回收到实体缓存池中 */ - destroy(): void { - this.isValid = false; - - // 如果有父模块,则移除父模块上记录的子模块 - if (this._parent) { - this._parent.removeChild(this, false); - this._parent = null; - } - - // 移除模块上所有子模块 - if (this.childs) { - this.childs.forEach((e) => { - this.removeChild(e); - }); - this.childs.clear(); - this.childs = null; - } - - // 移除实体上所有组件 - this.compTid2Ctor.forEach(this._remove, this); - destroyEntity(this); - - // 清理缓存的组件对象,防止内存泄漏 - this.clearAllComponentCache(); - } - - private _remove(comp: CompType): void { - this.remove(comp, true); - } -} +import type { ecs } from './ECS'; +import { ECSMask } from './ECSMask'; +import type { CompCtor, CompType } from './ECSModel'; +import { ECSModel } from './ECSModel'; +import { ecsPoolCoordinator } from './pool'; + +//#region 辅助方法 + +/** + * 实体身上组件有增删操作,广播通知对应的观察者 + * @param entity 实体对象 + * @param componentTypeId 组件类型id + */ +function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number): void { + const events = ECSModel.compAddOrRemove.get(componentTypeId); + if (events) { + for (let i = events.length - 1; i >= 0; i--) { + events[i](entity); + } + } + // 判断是不是删了单例组件 + if (ECSModel.tid2comp.has(componentTypeId)) { + ECSModel.tid2comp.delete(componentTypeId); + } +} + +/** + * 设置实体上的组件属性(类型安全的动态属性访问) + */ +function setEntityComp(entity: ECSEntity, compName: string, comp: T): void { + Reflect.set(entity, compName, comp); +} + +/** + * 获取实体上的组件属性(类型安全的动态属性访问) + */ +function getEntityComp(entity: ECSEntity, compName: string): T | undefined { + return Reflect.get(entity, compName) as T | undefined; +} + +/** + * 删除实体上的组件属性(类型安全的动态属性访问) + */ +function deleteEntityComp(entity: ECSEntity, compName: string): void { + Reflect.set(entity, compName, null); +} + +/** + * 创建组件对象(使用动态池管理) + * @param ctor + */ +function createComp(ctor: CompCtor): T { + const cct = ECSModel.compCtors[ctor.tid]; + if (!cct) { + throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`); + } + + // 使用动态池管理器 + const pool = ecsPoolCoordinator.getPool( + ctor.compName, + () => new (cct as CompCtor)() + ); + + const component = pool.get(); + return component as T; +} + +/** + * 销毁实体(使用动态池管理) + * + * 缓存销毁的实体,下次新建实体时会优先从缓存中拿 + * @param entity + */ +function destroyEntity(entity: ECSEntity): void { + if (ECSModel.eid2Entity.has(entity.eid)) { + // 使用动态池管理器 + const pool = ecsPoolCoordinator.getPool( + entity.name, + () => new ECSEntity() + ); + + // 清空mask并回收 + entity.getMask().clear(); + pool.recycle(entity); + + ECSModel.eid2Entity.delete(entity.eid); + } + else { + console.warn('试图销毁不存在的实体'); + } +} + +//#endregion + +/** ECS实体对象 */ +export class ECSEntity { + /** 实体唯一标识,不要手动修改 */ + eid = -1; + /** 实体对象名 */ + name = ''; + /** 实体是否有效 */ + isValid = true; + /** 组件过滤数据 */ + private mask: ECSMask = new ECSMask(); + /** 当前实体身上附加的组件构造函数 */ + private compTid2Ctor: Map> = new Map(); + /** + * 配合 entity.remove(Comp, false), 记录组件实例上的缓存数据,在添加时恢复原数据 + * + * ⚠️ 核心机制: + * - isRecycle=true:调用reset(),根据canRecycle决定是否回收到全局池 + * - isRecycle=false:不调用reset(),缓存在实体上 + * + * ⚠️ 使用场景说明: + * 1. UI显示/隐藏 - 保留UI状态数据 + * 2. 技能冷却 - 保留冷却时间 + * 3. 临时禁用功能 - 保留配置数据 + * + * 💡 管理建议: + * - 无任何限制,完全由用户自行管理 + * - 使用 ECSMemoryMonitor 监控缓存使用情况 + * - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理 + * - 在场景切换、内存警告时主动清理缓存 + */ + private compTid2Obj: Map = new Map(); + + /** 获取 mask 对象(内部使用) */ + getMask(): ECSMask { + return this.mask; + } + + private _parent: ECSEntity | null = null; + /** 父实体 */ + get parent(): ECSEntity | null { + return this._parent; + } + set parent(value: ECSEntity | null) { + this._parent = value; + } + + /** 子实体 */ + private childs: Map | null = null; + + /** 获取子实体 */ + getChild(eid: number): T | undefined { + return this.childs?.get(eid) as T | undefined; + } + + /** + * 添加子实体(带循环引用检测) + * @param entity 被添加的实体对象 + * @returns 子实体的唯一编号, -1表示添加失败 + */ + addChild(entity: ECSEntity): number { + if (this.childs === null) { + this.childs = new Map(); + } + + if (this.childs.has(entity.eid)) { + console.warn(`子实体${entity.name}已存在`); + return -1; + } + + // 检测循环引用 + if (this.hasAncestor(entity)) { + console.error(`检测到循环引用: ${this.name} -> ${entity.name}`); + return -1; + } + + entity._parent = this; + this.childs.set(entity.eid, entity); + return entity.eid; + } + + /** + * 检测是否存在祖先关系(防止循环引用) + */ + private hasAncestor(entity: ECSEntity): boolean { + let current: ECSEntity | null = this; + while (current) { + if (current === entity) return true; + current = current.parent; + } + return false; + } + + /** + * 移除子实体 + * @param entity 被移除的实体对象 + * @param isDestroy 被移除的实体是否释放,默认为释放 + * @returns + */ + removeChild(entity: ECSEntity, isDestroy = true): void { + if (this.childs === null) return; + + entity.parent = null; + this.childs.delete(entity.eid); + if (isDestroy) entity.destroy(); + } + + /** + * 根据组件类动态创建组件,并通知关心的系统。如果实体存在了这个组件,那么会先删除之前的组件然后添加新的 + * + * 注意:不要直接new Component,new来的Component不会从Component的缓存池拿缓存的数据 + * @param componentTypeId 组件类 + * @param isReAdd true-表示用户指定这个实体可能已经存在了该组件,那么再次add组件的时候会先移除该组件然后再添加一遍。false-表示不重复添加组件 + */ + add(obj: T): ECSEntity; + add(ctor: CompType, isReAdd?: boolean): T; + add(ctor: CompType | T, isReAdd = false): T | ECSEntity { + if (typeof ctor === 'function') { + const compTid = ctor.tid; + if (ctor.tid === -1) { + throw Error(`【${this.name}】实体【${ctor.compName}】组件未注册`); + } + if (this.compTid2Ctor.has(compTid)) { // 判断是否有该组件,如果有则先移除 + if (isReAdd) { + this.remove(ctor); + } + else { + console.log(`【${this.name}】实体【${ctor.compName}】组件已存在`); + const existingComp = getEntityComp(this, ctor.compName); + if (existingComp) { + return existingComp; + } + } + } + this.mask.set(compTid); + + let comp: T; + if (this.compTid2Obj.has(compTid)) { + comp = this.compTid2Obj.get(compTid) as T; + this.compTid2Obj.delete(compTid); + } + else { + // 创建组件对象 + comp = createComp(ctor); + } + + // 将组件对象直接附加到实体对象身上,方便直接获取 + setEntityComp(this, ctor.compName, comp); + this.compTid2Ctor.set(compTid, ctor); + comp.tid = compTid; + comp.ent = this; + // 广播实体添加组件的消息 + broadcastCompAddOrRemove(this, compTid); + + return comp; + } + else { + // 此时 ctor 是组件实例 + const compInstance = ctor as T; + const tmpCtor = (compInstance.constructor as CompCtor); + const compTid = tmpCtor.tid; + if (compTid === -1 || compTid == null) throw Error(`【${this.name}】实体【${tmpCtor.name}】组件未注册`); + if (this.compTid2Ctor.has(compTid)) throw Error(`【${this.name}】实体【${tmpCtor.name}】组件已经存在`); + + this.mask.set(compTid); + setEntityComp(this, tmpCtor.compName, compInstance); + this.compTid2Ctor.set(compTid, tmpCtor); + compInstance.tid = compTid; + compInstance.canRecycle = false; + compInstance.ent = this; + broadcastCompAddOrRemove(this, compTid); + + return this; + } + } + + /** + * 批量添加组件 + * @param ctors 组件类 + * @returns + */ + addComponents(...ctors: CompType[]): this { + const len = ctors.length; + for (let i = 0; i < len; i++) { + this.add(ctors[i]); + } + return this; + } + + /** + * 获取一个组件实例 + * @param ctor 组件类 + */ + get(ctor: CompCtor): T | undefined { + return getEntityComp(this, ctor.compName); + } + + /** + * 组件是否在实体存在内 + * @param ctor 组件类 + */ + has(ctor: CompType): boolean { + const tid = typeof ctor === 'number' ? ctor : ctor.tid; + return this.compTid2Ctor.has(tid); + } + + /** + * 从实体上删除指定组件 + * @param ctor 组件构造函数或者组件Tag + * @param isRecycle 是否回收该组件对象 + * + * **isRecycle=true(默认):** + * - 调用组件的 reset() 方法清理数据 + * - 如果 canRecycle=true,回收到全局对象池供复用 + * - 如果 canRecycle=false,直接销毁 + * - 适用于大部分场景 + * + * **isRecycle=false:** + * - 不调用 reset(),数据完整保留 + * - 组件缓存在当前实体上,下次 add() 时恢复 + * - 无任何限制和检查,完全由用户自行管理 + * - 适用于需要保留状态的场景(UI切换、技能冷却等) + * + * ⚠️ isRecycle=false 使用场景: + * - UI显示/隐藏:保留UI状态(如滚动位置、选中项) + * - 技能冷却:保留冷却剩余时间 + * - 临时禁用:保留配置数据,稍后恢复 + * + * 💡 管理建议: + * - 使用 ECSMemoryMonitor 监控缓存使用情况 + * - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理 + * - 在场景切换、内存警告时主动清理缓存 + */ + remove(ctor: CompType, isRecycle = true): void { + const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid; + const compName = typeof ctor === 'number' ? '' : ctor.compName; + + if (!this.mask.has(componentTypeId)) { + return; + } + + const comp = getEntityComp(this, compName); + if (!comp) { + return; + } + + comp.ent = null!; + + if (isRecycle) { + comp.reset(); + + // 回收到全局池 + if (comp.canRecycle) { + const ctor = ECSModel.compCtors[componentTypeId]; + if (ctor) { + const pool = ecsPoolCoordinator.getPool( + ctor.compName, + () => new (ctor as any)() + ); + pool.recycle(comp); + } + } + } + else { + // isRecycle=false:用户明确要求缓存,完全尊重用户意图 + // 不做任何检查和限制,由用户通过 ECSMemoryMonitor 自行管理 + this.compTid2Obj.set(componentTypeId, comp); + } + + // 删除实体上的组件逻辑 + deleteEntityComp(this, compName); + this.mask.delete(componentTypeId); + this.compTid2Ctor.delete(componentTypeId); + broadcastCompAddOrRemove(this, componentTypeId); + } + + /** + * 清理指定组件的缓存 + * @param ctor 组件构造函数 + */ + clearComponentCache(ctor: CompType): void { + const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid; + const comp = this.compTid2Obj.get(componentTypeId); + + if (comp) { + this.compTid2Obj.delete(componentTypeId); + + const ctorObj = ECSModel.compCtors[componentTypeId]; + const compName = ctorObj?.compName || `tid:${componentTypeId}`; + + comp.reset(); + if (comp.canRecycle) { + if (ctorObj) { + const pool = ecsPoolCoordinator.getPool( + ctorObj.compName, + () => new (ctorObj as any)() + ); + pool.recycle(comp); + } + } + + console.log(`[ECS] 实体 ${this.name} 清理组件缓存: ${compName}`); + } + } + + /** + * 清理所有组件缓存 + */ + clearAllComponentCache(): void { + if (this.compTid2Obj.size === 0) return; + + const count = this.compTid2Obj.size; + + this.compTid2Obj.forEach((comp, tid) => { + comp.reset(); + if (comp.canRecycle) { + const ctor = ECSModel.compCtors[tid]; + if (ctor) { + const pool = ecsPoolCoordinator.getPool( + ctor.compName, + () => new (ctor as any)() + ); + pool.recycle(comp); + } + } + }); + + this.compTid2Obj.clear(); + + console.log(`[ECS] 实体 ${this.name} 清理所有缓存,共 ${count} 个组件`); + } + + /** + * 获取缓存的组件数量 + */ + getCachedComponentCount(): number { + return this.compTid2Obj.size; + } + + /** + * 获取缓存的组件列表(用于监控) + */ + getCachedComponents(): Map { + return new Map(this.compTid2Obj); + } + + /** 销毁实体,实体会被回收到实体缓存池中 */ + destroy(): void { + this.isValid = false; + + // 如果有父模块,则移除父模块上记录的子模块 + if (this._parent) { + this._parent.removeChild(this, false); + this._parent = null; + } + + // 移除模块上所有子模块 + if (this.childs) { + this.childs.forEach((e) => { + this.removeChild(e); + }); + this.childs.clear(); + this.childs = null; + } + + // 移除实体上所有组件 + this.compTid2Ctor.forEach(this._remove, this); + destroyEntity(this); + + // 清理缓存的组件对象,防止内存泄漏 + this.clearAllComponentCache(); + } + + private _remove(comp: CompType): void { + this.remove(comp, true); + } +} diff --git a/assets/libs/ecs/ECSMonitorLogger.ts b/assets/libs/ecs/ECSMonitorLogger.ts index e3e55ab..d897431 100644 --- a/assets/libs/ecs/ECSMonitorLogger.ts +++ b/assets/libs/ecs/ECSMonitorLogger.ts @@ -5,7 +5,7 @@ */ import { ECSModel } from './ECSModel'; -import { ecsPoolCoordinator } from './ECSPoolManager'; +import { ecsPoolCoordinator } from './pool'; /** 监控数据项 */ interface MonitorItem { diff --git a/assets/libs/ecs/ECSPoolManager.ts b/assets/libs/ecs/ECSPoolManager.ts deleted file mode 100644 index 4170bb0..0000000 --- a/assets/libs/ecs/ECSPoolManager.ts +++ /dev/null @@ -1,324 +0,0 @@ -/* - * 对象池管理系统 - */ - -//#region 类型定义 - -/** 池统计指标 */ -interface PoolMetrics { - /** 创建次数 */ - createCount: number; - /** 回收次数 */ - recycleCount: number; - /** 命中次数(从池中获取) */ - hitCount: number; - /** 未命中次数(需要新建) */ - missCount: number; - /** 当前池大小 */ - currentSize: number; -} - -//#endregion - -//#region 动态对象池 - -/** - * 动态对象池 - */ -class DynamicPool { - private pool: T[] = []; - private metrics: PoolMetrics; - - /** - * 构造函数 - * @param typeName 池类型名称 - * @param factory 对象工厂函数 - */ - constructor( - private typeName: string, - private factory: () => T - ) { - this.metrics = { - createCount: 0, - recycleCount: 0, - hitCount: 0, - missCount: 0, - currentSize: 0 - }; - } - - /** - * 从池中获取对象 - * @returns 池中的对象或新创建的对象 - */ - get(): T { - if (this.pool.length > 0) { - this.metrics.hitCount++; - const item = this.pool.pop()!; - this.metrics.currentSize = this.pool.length; - return item; - } - - this.metrics.missCount++; - this.metrics.createCount++; - - return this.factory(); - } - - /** - * 回收对象到池中 - * @param item 要回收的对象 - */ - recycle(item: T): void { - this.pool.push(item); - this.metrics.currentSize = this.pool.length; - } - - /** - * 预热池,提前创建指定数量的对象 - * @param count 要预热的对象数量 - */ - preWarm(count: number): void { - while (this.pool.length < count) { - this.pool.push(this.factory()); - this.metrics.createCount++; - } - this.metrics.currentSize = this.pool.length; - } - - /** - * 获取池的统计信息 - * @returns 只读的统计指标对象 - */ - getMetrics(): Readonly { - return { ...this.metrics }; - } - - /** - * 清空池中的所有对象 - */ - clear(): void { - this.pool.length = 0; - this.metrics.currentSize = 0; - } - - /** - * 手动缩减池大小到指定容量 - * @param targetSize 目标池大小 - * @returns 移除的对象数量 - */ - shrinkTo(targetSize: number): number { - let removed = 0; - - while (this.pool.length > targetSize) { - this.pool.pop(); - removed++; - } - - if (removed > 0) { - this.metrics.currentSize = this.pool.length; - } - - return removed; - } -} - -//#endregion - -//#region 池管理器 - -/** - * 池管理器 - */ -class PoolManager { - private pools: Map> = new Map(); - - /** - * 获取或创建池 - * @param typeName 池类型名称 - * @param factory 对象工厂函数 - * @returns 动态对象池实例 - */ - getPool(typeName: string, factory: () => T): DynamicPool { - if (!this.pools.has(typeName)) { - const pool = new DynamicPool(typeName, factory); - this.pools.set(typeName, pool); - } - - return this.pools.get(typeName)!; - } - - /** - * 清空所有池 - */ - clearAll(): void { - this.pools.forEach(pool => pool.clear()); - this.pools.clear(); - } - - /** - * 场景切换时预热池 - * @param sceneType 场景类型标识 - * @param hints 类型名称到预热数量的映射 - */ - onSceneChange(sceneType: string, hints: Map): void { - hints.forEach((count, typeName) => { - const pool = this.pools.get(typeName); - if (pool) { - pool.preWarm(count); - } - }); - } - - /** - * 获取所有池的统计信息 - * @returns 类型名称到统计指标的映射 - */ - getAllMetrics(): Map { - const result = new Map(); - this.pools.forEach((pool, typeName) => { - result.set(typeName, pool.getMetrics()); - }); - return result; - } - - /** - * 手动缩减所有池到指定百分比 - * @param percent 目标百分比(0-1之间) - * @returns 总共移除的对象数量 - */ - shrinkAllTo(percent: number): number { - let totalRemoved = 0; - this.pools.forEach(pool => { - const metrics = pool.getMetrics(); - const targetSize = Math.floor(metrics.currentSize * percent); - totalRemoved += pool.shrinkTo(targetSize); - }); - return totalRemoved; - } - - /** - * 获取指定类型的池 - * @param typeName 池类型名称 - * @returns 池实例,如果不存在则返回undefined - */ - getPoolByName(typeName: string): DynamicPool | undefined { - return this.pools.get(typeName); - } -} - -//#endregion - -//#region 全局协调器 - -/** - * 全局池协调器 - 提供手动管理API - */ -class GlobalPoolCoordinator { - private poolManager: PoolManager; - - constructor() { - this.poolManager = new PoolManager(); - } - - /** - * 获取或创建池 - * @param typeName 池类型名称 - * @param factory 对象工厂函数 - * @returns 动态对象池实例 - */ - getPool(typeName: string, factory: () => T): DynamicPool { - return this.poolManager.getPool(typeName, factory); - } - - /** - * 获取池管理器实例 - * @returns 池管理器 - */ - getPoolManager(): PoolManager { - return this.poolManager; - } - - /** - * 场景切换时预热池 - * @param sceneType 场景类型标识 - * @param hints 类型名称到预热数量的映射 - */ - onSceneChange(sceneType: string, hints: Map): void { - this.poolManager.onSceneChange(sceneType, hints); - } - - /** - * 手动缩减所有池到指定百分比 - * @param percent 目标百分比(0-1之间) - * @returns 总共移除的对象数量 - */ - shrinkAll(percent: number): number { - return this.poolManager.shrinkAllTo(percent); - } - - /** - * 清空所有池中的对象 - */ - clearAll(): void { - this.poolManager.clearAll(); - } - - /** - * 清空指定池中的对象 - * @param typeName 池类型名称 - */ - clearPool(typeName: string): void { - const pool = this.poolManager.getPoolByName(typeName); - if (pool) { - pool.clear(); - } - } - - /** - * 手动缩减指定池到目标大小 - * @param typeName 池类型名称 - * @param targetSize 目标池大小 - * @returns 移除的对象数量 - */ - shrinkPool(typeName: string, targetSize: number): number { - const pool = this.poolManager.getPoolByName(typeName); - return pool ? pool.shrinkTo(targetSize) : 0; - } - - /** - * 获取所有池的统计信息 - * @returns 类型名称到统计指标的映射 - */ - getAllMetrics(): Map { - return this.poolManager.getAllMetrics(); - } - - /** - * 获取指定池的统计信息 - * @param typeName 池类型名称 - * @returns 统计指标对象,如果池不存在则返回undefined - */ - getPoolMetrics(typeName: string): PoolMetrics | undefined { - const pool = this.poolManager.getPoolByName(typeName); - return pool ? pool.getMetrics() : undefined; - } -} - -//#endregion - -/** - * 全局池协调器实例 - * - * 用于管理 ECS 框架中的对象池,主要包括: - * 1. ECS 实体对象 (ECSEntity) - 实体销毁后回收复用 - * 2. ECS 组件对象 (IComp) - 组件移除后回收复用 - * 3. 其他自定义对象 - 支持任意类型的对象池化 - * - * 核心功能: - * - 对象复用:减少频繁创建销毁带来的性能开销 - * - 统计监控:跟踪命中率、创建次数等指标 - * - 池管理:支持预热、缩减、清空等操作 - * - 场景优化:场景切换时自动预热相关对象池 - */ -export const ecsPoolCoordinator = new GlobalPoolCoordinator(); diff --git a/assets/libs/ecs/pool.meta b/assets/libs/ecs/pool.meta new file mode 100644 index 0000000..8cd240e --- /dev/null +++ b/assets/libs/ecs/pool.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "84525bc8-4095-4145-b04b-621c673eee5d", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/libs/ecs/pool/ECSDynamicPool.ts b/assets/libs/ecs/pool/ECSDynamicPool.ts new file mode 100644 index 0000000..b9fba95 --- /dev/null +++ b/assets/libs/ecs/pool/ECSDynamicPool.ts @@ -0,0 +1,108 @@ +/** + * 动态对象池 + */ + +import { IECSPoolMetrics } from './IECSPoolMetrics'; + +/** + * 动态对象池 + * @template T 池中对象的类型 + */ +export class ECSDynamicPool { + private pool: T[] = []; + private metrics: IECSPoolMetrics; + + /** + * 构造函数 + * @param typeName 池类型名称 + * @param factory 对象工厂函数 + */ + constructor( + private typeName: string, + private factory: () => T + ) { + this.metrics = { + createCount: 0, + recycleCount: 0, + hitCount: 0, + missCount: 0, + currentSize: 0 + }; + } + + /** + * 从池中获取对象 + * @returns 池中的对象或新创建的对象 + */ + get(): T { + if (this.pool.length > 0) { + this.metrics.hitCount++; + const item = this.pool.pop()!; + this.metrics.currentSize = this.pool.length; + return item; + } + + this.metrics.missCount++; + this.metrics.createCount++; + + return this.factory(); + } + + /** + * 回收对象到池中 + * @param item 要回收的对象 + */ + recycle(item: T): void { + this.pool.push(item); + this.metrics.recycleCount++; + this.metrics.currentSize = this.pool.length; + } + + /** + * 预热池,提前创建指定数量的对象 + * @param count 要预热的对象数量 + */ + preWarm(count: number): void { + while (this.pool.length < count) { + this.pool.push(this.factory()); + this.metrics.createCount++; + } + this.metrics.currentSize = this.pool.length; + } + + /** + * 获取池的统计信息 + * @returns 只读的统计指标对象 + */ + getMetrics(): Readonly { + return { ...this.metrics }; + } + + /** + * 清空池中的所有对象 + */ + clear(): void { + this.pool.length = 0; + this.metrics.currentSize = 0; + } + + /** + * 手动缩减池大小到指定容量 + * @param targetSize 目标池大小 + * @returns 移除的对象数量 + */ + shrinkTo(targetSize: number): number { + let removed = 0; + + while (this.pool.length > targetSize) { + this.pool.pop(); + removed++; + } + + if (removed > 0) { + this.metrics.currentSize = this.pool.length; + } + + return removed; + } +} diff --git a/assets/libs/ecs/pool/ECSDynamicPool.ts.meta b/assets/libs/ecs/pool/ECSDynamicPool.ts.meta new file mode 100644 index 0000000..69fcb19 --- /dev/null +++ b/assets/libs/ecs/pool/ECSDynamicPool.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "e18efb47-61df-4094-8457-d330b7847318", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/libs/ecs/pool/ECSPoolManager.ts b/assets/libs/ecs/pool/ECSPoolManager.ts new file mode 100644 index 0000000..623aba0 --- /dev/null +++ b/assets/libs/ecs/pool/ECSPoolManager.ts @@ -0,0 +1,127 @@ +/** + * 池管理器 + */ + +import { ECSDynamicPool } from './ECSDynamicPool'; +import type { IECSPoolMetrics } from './IECSPoolMetrics'; + +/** + * 池管理器 - 统一管理所有对象池 + */ +export class ECSPoolManager { + /** 所有对象池的映射 */ + private pools: Map> = new Map(); + + /** + * 获取或创建池 + * @param typeName 池类型名称 + * @param factory 对象工厂函数 + * @returns 动态对象池实例 + */ + getPool(typeName: string, factory: () => T): ECSDynamicPool { + if (!this.pools.has(typeName)) { + const pool = new ECSDynamicPool(typeName, factory); + this.pools.set(typeName, pool); + } + + return this.pools.get(typeName)! as ECSDynamicPool; + } + + /** + * 清空所有池 + */ + clearAll(): void { + this.pools.forEach(pool => pool.clear()); + this.pools.clear(); + } + + /** + * 场景切换时预热池 + * @param sceneType 场景类型标识 + * @param hints 类型名称到预热数量的映射 + */ + onSceneChange(sceneType: string, hints: Map): void { + hints.forEach((count, typeName) => { + const pool = this.pools.get(typeName); + if (pool) { + pool.preWarm(count); + } + }); + } + + /** + * 获取所有池的统计信息 + * @returns 类型名称到统计指标的映射 + */ + getAllMetrics(): Map { + const result = new Map(); + this.pools.forEach((pool, typeName) => { + result.set(typeName, pool.getMetrics()); + }); + return result; + } + + /** + * 手动缩减所有池到指定百分比 + * @param percent 目标百分比(0-1之间) + * @returns 总共移除的对象数量 + */ + shrinkAllTo(percent: number): number { + let totalRemoved = 0; + this.pools.forEach(pool => { + const metrics = pool.getMetrics(); + const targetSize = Math.floor(metrics.currentSize * percent); + totalRemoved += pool.shrinkTo(targetSize); + }); + return totalRemoved; + } + + /** + * 获取指定类型的池 + * @param typeName 池类型名称 + * @returns 池实例,如果不存在则返回undefined + */ + getPoolByName(typeName: string): ECSDynamicPool | undefined { + return this.pools.get(typeName) as ECSDynamicPool | undefined; + } + + /** + * 获取所有池的名称 + * @returns 池名称数组 + */ + getPoolNames(): string[] { + return Array.from(this.pools.keys()); + } + + /** + * 清空指定池中的对象 + * @param typeName 池类型名称 + */ + clearPool(typeName: string): void { + const pool = this.pools.get(typeName); + if (pool) { + pool.clear(); + } + } + + /** + * 手动缩减指定池到目标大小 + * @param typeName 池类型名称 + * @param targetSize 目标池大小 + * @returns 移除的对象数量 + */ + shrinkPool(typeName: string, targetSize: number): number { + const pool = this.pools.get(typeName); + return pool ? pool.shrinkTo(targetSize) : 0; + } + + /** + * 获取指定池的统计信息 + * @param typeName 池类型名称 + * @returns 统计指标对象,如果池不存在则返回undefined + */ + getPoolMetrics(typeName: string): IECSPoolMetrics | undefined { + const pool = this.pools.get(typeName); + return pool ? pool.getMetrics() : undefined; + } +} diff --git a/assets/libs/ecs/ECSPoolManager.ts.meta b/assets/libs/ecs/pool/ECSPoolManager.ts.meta similarity index 52% rename from assets/libs/ecs/ECSPoolManager.ts.meta rename to assets/libs/ecs/pool/ECSPoolManager.ts.meta index 41490ae..a9c6372 100644 --- a/assets/libs/ecs/ECSPoolManager.ts.meta +++ b/assets/libs/ecs/pool/ECSPoolManager.ts.meta @@ -2,10 +2,8 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "88db857b-ee25-442e-aef3-64a3b1e474de", + "uuid": "7ee37e6e-4033-4bb0-99c6-ba2cde97e5bf", "files": [], "subMetas": {}, - "userData": { - "simulateGlobals": [] - } + "userData": {} } diff --git a/assets/libs/ecs/pool/IECSPoolMetrics.ts b/assets/libs/ecs/pool/IECSPoolMetrics.ts new file mode 100644 index 0000000..a55ce05 --- /dev/null +++ b/assets/libs/ecs/pool/IECSPoolMetrics.ts @@ -0,0 +1,17 @@ +/** + * 池统计指标 + */ + +/** 池统计指标 */ +export interface IECSPoolMetrics { + /** 创建次数 */ + createCount: number; + /** 回收次数 */ + recycleCount: number; + /** 命中次数(从池中获取) */ + hitCount: number; + /** 未命中次数(需要新建) */ + missCount: number; + /** 当前池大小 */ + currentSize: number; +} diff --git a/assets/libs/ecs/pool/IECSPoolMetrics.ts.meta b/assets/libs/ecs/pool/IECSPoolMetrics.ts.meta new file mode 100644 index 0000000..8f3e540 --- /dev/null +++ b/assets/libs/ecs/pool/IECSPoolMetrics.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "575be89a-5bfa-4f9e-80f1-88f43edaa1bc", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/libs/ecs/pool/index.ts b/assets/libs/ecs/pool/index.ts new file mode 100644 index 0000000..19cbd69 --- /dev/null +++ b/assets/libs/ecs/pool/index.ts @@ -0,0 +1,25 @@ +/** + * 对象池管理系统 + * + * 用于管理 ECS 框架中的对象池,主要包括: + * 1. ECS 实体对象 (ECSEntity) - 实体销毁后回收复用 + * 2. ECS 组件对象 (IComp) - 组件移除后回收复用 + * 3. 其他自定义对象 - 支持任意类型的对象池化 + * + * 核心功能: + * - 对象复用:减少频繁创建销毁带来的性能开销 + * - 统计监控:跟踪命中率、创建次数等指标 + * - 池管理:支持预热、缩减、清空等操作 + * - 场景优化:场景切换时自动预热相关对象池 + */ + +export { IECSPoolMetrics } from './IECSPoolMetrics'; +export { ECSDynamicPool } from './ECSDynamicPool'; +export { ECSPoolManager } from './ECSPoolManager'; + +import { ECSPoolManager } from './ECSPoolManager'; + +/** + * 全局池协调器实例 + */ +export const ecsPoolCoordinator = new ECSPoolManager(); diff --git a/assets/libs/ecs/pool/index.ts.meta b/assets/libs/ecs/pool/index.ts.meta new file mode 100644 index 0000000..57695df --- /dev/null +++ b/assets/libs/ecs/pool/index.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "72c10359-0587-429d-957f-743e7f97673d", + "files": [], + "subMetas": {}, + "userData": {} +}