mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-05-19 22:46:01 +08:00
!30 修复LayerPopUp关闭遮罩时把mask节点的uiSprite关闭了,但是打开遮罩时没有启用,导致只有第一个PopUp能显示mask
Merge pull request !30 from dgflash/develop
This commit is contained in:
@@ -103,7 +103,10 @@ export class LayerPopUp extends LayerUI {
|
||||
this.black.enabled = false;
|
||||
}
|
||||
|
||||
if (config.mask) this.mask.parent = this;
|
||||
if (config.mask) {
|
||||
this.mask.parent = this;
|
||||
this.mask.uiSprite.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭触摸非窗口区域关闭 */
|
||||
|
||||
@@ -1,362 +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后,会回收到ECSModel.entityPool实体对象池中
|
||||
* ECSComp对象从ECSEntity.remove后,数据组件会回收到ECSModel.compPools组件对象池中
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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<E extends Entity = Entity> {
|
||||
entityEnter(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听组件从实体上移除时,在ComblockSystem上实现这个接口 */
|
||||
export interface IEntityRemoveSystem<E extends Entity = Entity> {
|
||||
entityRemove(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统第一次执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemFirstUpdate<E extends Entity = Entity> {
|
||||
firstUpdate(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemUpdate<E extends Entity = Entity> {
|
||||
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<T extends Entity | IComp | ComblockSystem>(name: string, canNew = true) {
|
||||
return function (ctor: EntityCtor<T> | CompCtor<T> | (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<ECSEntity>, name);
|
||||
}
|
||||
// 注册组件
|
||||
else {
|
||||
if (ctorAny.tid === -1) {
|
||||
ctorAny.tid = ECSModel.compTid++;
|
||||
ctorAny.compName = name;
|
||||
ECSModel.compCtors.push(ctor as CompCtor<IComp>);
|
||||
if (canNew) {
|
||||
ECSModel.compPools.set(ctorAny.tid, []);
|
||||
}
|
||||
ECSModel.compAddOrRemove.set(ctorAny.tid, []);
|
||||
}
|
||||
else {
|
||||
throw new Error(`ECS 组件重复注册: ${name}.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的实体对象或从缓存中获取一个实体对象(使用动态池)
|
||||
* @param ctor 实体类
|
||||
*/
|
||||
export function getEntity<T extends Entity>(ctor: EntityCtor<T>): T {
|
||||
// 获取实体对象名
|
||||
const entityName = ECSModel.entityCtors.get(ctor as EntityCtor<ECSEntity>);
|
||||
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<E extends Entity = Entity>(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 {
|
||||
// 清理旧的实体池
|
||||
ECSModel.entityPool.forEach((pool) => {
|
||||
pool.length = 0;
|
||||
});
|
||||
ECSModel.entityPool.clear();
|
||||
|
||||
// 清理旧的组件池
|
||||
ECSModel.compPools.forEach((pool) => {
|
||||
pool.length = 0;
|
||||
});
|
||||
|
||||
// 清理 Mask 对象池
|
||||
ECSMask.clearPool();
|
||||
|
||||
// 清理动态池管理器
|
||||
ecsPoolCoordinator.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过实体唯一编号获得实体对象
|
||||
* @param eid 实体唯一编号
|
||||
*/
|
||||
export function getEntityByEid<E extends Entity = Entity>(eid: number): E | undefined {
|
||||
return ECSModel.eid2Entity.get(eid) as E | undefined;
|
||||
}
|
||||
|
||||
/** 当前活动中的实体数量 */
|
||||
export function activeEntityCount(): number {
|
||||
return ECSModel.eid2Entity.size;
|
||||
}
|
||||
|
||||
/** 创建实体 */
|
||||
function createEntity<E extends Entity = Entity>(): 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<T extends IComp>(ctor: CompCtor<T>): T {
|
||||
const entity = createEntity();
|
||||
return entity.add(ctor);
|
||||
}
|
||||
|
||||
//#region 过滤器
|
||||
/**
|
||||
* 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件
|
||||
* 进行添加操作引发Group重复添加实体。
|
||||
* @param args
|
||||
* @example
|
||||
* ecs.allOf(AComponent, BComponent, CComponent);
|
||||
*/
|
||||
export function allOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().allOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体
|
||||
* @param args 组件类
|
||||
* @example
|
||||
* ecs.anyOf(AComponent, BComponent);
|
||||
*/
|
||||
export function anyOf(...args: CompType<IComp>[]): 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<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().onlyOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含指定的任意一个组件
|
||||
* @param args 组件类
|
||||
* @example
|
||||
// 表示不包含组件A或者组件B
|
||||
ecs.excludeOf(A, B);
|
||||
*/
|
||||
export function excludeOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().excludeOf(...args);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 单例组件
|
||||
/**
|
||||
* 获取单例组件
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
export function getSingleton<T extends IComp>(ctor: CompCtor<T>): 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<IComp>).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<E extends Entity = Entity> {
|
||||
entityEnter(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听组件从实体上移除时,在ComblockSystem上实现这个接口 */
|
||||
export interface IEntityRemoveSystem<E extends Entity = Entity> {
|
||||
entityRemove(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统第一次执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemFirstUpdate<E extends Entity = Entity> {
|
||||
firstUpdate(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemUpdate<E extends Entity = Entity> {
|
||||
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<T extends Entity | IComp | ComblockSystem>(name: string, canNew = true) {
|
||||
return function (ctor: EntityCtor<T> | CompCtor<T> | (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<ECSEntity>, name);
|
||||
}
|
||||
// 注册组件
|
||||
else {
|
||||
if (ctorAny.tid === -1) {
|
||||
ctorAny.tid = ECSModel.compTid++;
|
||||
ctorAny.compName = name;
|
||||
ECSModel.compCtors.push(ctor as CompCtor<IComp>);
|
||||
ECSModel.compAddOrRemove.set(ctorAny.tid, []);
|
||||
}
|
||||
else {
|
||||
throw new Error(`ECS 组件重复注册: ${name}.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的实体对象或从缓存中获取一个实体对象(使用动态池)
|
||||
* @param ctor 实体类
|
||||
*/
|
||||
export function getEntity<T extends Entity>(ctor: EntityCtor<T>): T {
|
||||
// 获取实体对象名
|
||||
const entityName = ECSModel.entityCtors.get(ctor as EntityCtor<ECSEntity>);
|
||||
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<E extends Entity = Entity>(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<E extends Entity = Entity>(eid: number): E | undefined {
|
||||
return ECSModel.eid2Entity.get(eid) as E | undefined;
|
||||
}
|
||||
|
||||
/** 当前活动中的实体数量 */
|
||||
export function activeEntityCount(): number {
|
||||
return ECSModel.eid2Entity.size;
|
||||
}
|
||||
|
||||
/** 创建实体 */
|
||||
function createEntity<E extends Entity = Entity>(): 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<T extends IComp>(ctor: CompCtor<T>): T {
|
||||
const entity = createEntity();
|
||||
return entity.add(ctor);
|
||||
}
|
||||
|
||||
//#region 过滤器
|
||||
/**
|
||||
* 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件
|
||||
* 进行添加操作引发Group重复添加实体。
|
||||
* @param args
|
||||
* @example
|
||||
* ecs.allOf(AComponent, BComponent, CComponent);
|
||||
*/
|
||||
export function allOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().allOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体
|
||||
* @param args 组件类
|
||||
* @example
|
||||
* ecs.anyOf(AComponent, BComponent);
|
||||
*/
|
||||
export function anyOf(...args: CompType<IComp>[]): 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<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().onlyOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含指定的任意一个组件
|
||||
* @param args 组件类
|
||||
* @example
|
||||
// 表示不包含组件A或者组件B
|
||||
ecs.excludeOf(A, B);
|
||||
*/
|
||||
export function excludeOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().excludeOf(...args);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 单例组件
|
||||
/**
|
||||
* 获取单例组件
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
export function getSingleton<T extends IComp>(ctor: CompCtor<T>): 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<IComp>).tid;
|
||||
if (!ECSModel.tid2comp.has(tid)) {
|
||||
ECSModel.tid2comp.set(tid, obj);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@@ -32,4 +32,4 @@ export abstract class ECSComp implements ecs.IComp {
|
||||
* 注:不要偷懒,除非你能确定并保证组件在复用时,里面的数据是先赋值然后再使用
|
||||
*/
|
||||
abstract reset(): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T extends ecs.IComp>(entity: ECSEntity, compName: string, comp: T): void {
|
||||
Reflect.set(entity, compName, comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体上的组件属性(类型安全的动态属性访问)
|
||||
*/
|
||||
function getEntityComp<T extends ecs.IComp>(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<T extends ecs.IComp>(ctor: CompCtor<T>): T {
|
||||
const cct = ECSModel.compCtors[ctor.tid];
|
||||
if (!cct) {
|
||||
throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`);
|
||||
}
|
||||
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (cct as CompCtor<T>)()
|
||||
);
|
||||
|
||||
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<number, CompType<ecs.IComp>> = new Map();
|
||||
/**
|
||||
* 配合 entity.remove(Comp, false), 记录组件实例上的缓存数据,在添加时恢复原数据
|
||||
*
|
||||
* ⚠️ 核心机制:
|
||||
* - isRecycle=true:调用reset(),根据canRecycle决定是否回收到全局池
|
||||
* - isRecycle=false:不调用reset(),缓存在实体上
|
||||
*
|
||||
* ⚠️ 使用场景说明:
|
||||
* 1. UI显示/隐藏 - 保留UI状态数据
|
||||
* 2. 技能冷却 - 保留冷却时间
|
||||
* 3. 临时禁用功能 - 保留配置数据
|
||||
*
|
||||
* 💡 管理建议:
|
||||
* - 无任何限制,完全由用户自行管理
|
||||
* - 使用 ECSMemoryMonitor 监控缓存使用情况
|
||||
* - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理
|
||||
* - 在场景切换、内存警告时主动清理缓存
|
||||
*/
|
||||
private compTid2Obj: Map<number, ecs.IComp> = 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<number, ECSEntity> | null = null;
|
||||
|
||||
/** 获取子实体 */
|
||||
getChild<T extends ECSEntity>(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<number, ECSEntity>();
|
||||
}
|
||||
|
||||
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<T extends ecs.IComp>(obj: T): ECSEntity;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T>, isReAdd?: boolean): T;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T> | 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<T>(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<T>);
|
||||
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<T extends ecs.IComp>(...ctors: CompType<T>[]): this {
|
||||
const len = ctors.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
this.add(ctors[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个组件实例
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
get<T extends ecs.IComp>(ctor: CompCtor<T>): T | undefined {
|
||||
return getEntityComp<T>(this, ctor.compName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件是否在实体存在内
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
has(ctor: CompType<ecs.IComp>): 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<ecs.IComp>, 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<ecs.IComp>(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<ecs.IComp>): 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<number, ecs.IComp> {
|
||||
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<ecs.IComp>): 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<T extends ecs.IComp>(entity: ECSEntity, compName: string, comp: T): void {
|
||||
Reflect.set(entity, compName, comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体上的组件属性(类型安全的动态属性访问)
|
||||
*/
|
||||
function getEntityComp<T extends ecs.IComp>(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<T extends ecs.IComp>(ctor: CompCtor<T>): T {
|
||||
const cct = ECSModel.compCtors[ctor.tid];
|
||||
if (!cct) {
|
||||
throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`);
|
||||
}
|
||||
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (cct as CompCtor<T>)()
|
||||
);
|
||||
|
||||
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<number, CompType<ecs.IComp>> = new Map();
|
||||
/**
|
||||
* 配合 entity.remove(Comp, false), 记录组件实例上的缓存数据,在添加时恢复原数据
|
||||
*
|
||||
* ⚠️ 核心机制:
|
||||
* - isRecycle=true:调用reset(),根据canRecycle决定是否回收到全局池
|
||||
* - isRecycle=false:不调用reset(),缓存在实体上
|
||||
*
|
||||
* ⚠️ 使用场景说明:
|
||||
* 1. UI显示/隐藏 - 保留UI状态数据
|
||||
* 2. 技能冷却 - 保留冷却时间
|
||||
* 3. 临时禁用功能 - 保留配置数据
|
||||
*
|
||||
* 💡 管理建议:
|
||||
* - 无任何限制,完全由用户自行管理
|
||||
* - 使用 ECSMemoryMonitor 监控缓存使用情况
|
||||
* - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理
|
||||
* - 在场景切换、内存警告时主动清理缓存
|
||||
*/
|
||||
private compTid2Obj: Map<number, ecs.IComp> = 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<number, ECSEntity> | null = null;
|
||||
|
||||
/** 获取子实体 */
|
||||
getChild<T extends ECSEntity>(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<number, ECSEntity>();
|
||||
}
|
||||
|
||||
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<T extends ecs.IComp>(obj: T): ECSEntity;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T>, isReAdd?: boolean): T;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T> | 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<T>(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<T>);
|
||||
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<T extends ecs.IComp>(...ctors: CompType<T>[]): this {
|
||||
const len = ctors.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
this.add(ctors[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个组件实例
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
get<T extends ecs.IComp>(ctor: CompCtor<T>): T | undefined {
|
||||
return getEntityComp<T>(this, ctor.compName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件是否在实体存在内
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
has(ctor: CompType<ecs.IComp>): 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<ecs.IComp>, 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<ecs.IComp>(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<ecs.IComp>): 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<number, ecs.IComp> {
|
||||
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<ecs.IComp>): void {
|
||||
this.remove(comp, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,15 @@ export class ECSGroup<E extends ECSEntity = ECSEntity> {
|
||||
if (!this._cacheValid) {
|
||||
const cache = this._entitiesCache;
|
||||
const targetSize = this._matchEntities.size;
|
||||
|
||||
|
||||
// 如果缓存数组过大,重新创建以释放内存
|
||||
if (cache.length > targetSize * 2 && targetSize < 100) {
|
||||
this._entitiesCache = [];
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
cache.length = 0;
|
||||
}
|
||||
|
||||
|
||||
// 直接遍历 Map values 比 Array.from 更高效
|
||||
const iterator = this._matchEntities.values();
|
||||
let result = iterator.next();
|
||||
@@ -38,7 +39,7 @@ export class ECSGroup<E extends ECSEntity = ECSEntity> {
|
||||
this._entitiesCache.push(result.value);
|
||||
result = iterator.next();
|
||||
}
|
||||
|
||||
|
||||
this._cacheValid = true;
|
||||
}
|
||||
return this._entitiesCache;
|
||||
|
||||
@@ -145,7 +145,7 @@ abstract class BaseOf {
|
||||
const len = args.length;
|
||||
// 使用 Set 去重,性能更好
|
||||
const uniqueIds = new Set<number>();
|
||||
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (typeof (args[i]) === 'number') {
|
||||
componentTypeId = args[i] as number;
|
||||
@@ -159,11 +159,11 @@ abstract class BaseOf {
|
||||
this.mask.set(componentTypeId);
|
||||
uniqueIds.add(componentTypeId);
|
||||
}
|
||||
|
||||
|
||||
// 从 Set 转为排序数组
|
||||
this.indices = Array.from(uniqueIds).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
|
||||
/** 清理资源,防止内存泄漏 */
|
||||
destroy(): void {
|
||||
this.mask.destroy();
|
||||
|
||||
@@ -33,15 +33,11 @@ export class ECSModel {
|
||||
static eid = 1;
|
||||
/** 实体构造函数 */
|
||||
static entityCtors: Map<EntityCtor<ECSEntity>, string> = new Map();
|
||||
/** 实体对象缓存池 */
|
||||
static entityPool: Map<string, ECSEntity[]> = new Map();
|
||||
/** 通过实体id查找实体对象 */
|
||||
static eid2Entity: Map<number, ECSEntity> = new Map();
|
||||
|
||||
/** 组件类型id */
|
||||
static compTid = 0;
|
||||
/** 组件缓存池 */
|
||||
static compPools: Map<number, ecs.IComp[]> = new Map();
|
||||
/** 组件构造函数,用于ecs.register注册时,记录不同类型的组件 */
|
||||
static compCtors: CompCtor<ecs.IComp>[] = [];
|
||||
/**
|
||||
@@ -62,7 +58,7 @@ export class ECSModel {
|
||||
|
||||
/** 对象池配置 */
|
||||
static readonly MAX_ENTITY_POOL_SIZE = 200; // 每种实体类型最多缓存数量
|
||||
static readonly MAX_COMP_POOL_SIZE = 500; // 每种组件类型最多缓存数量
|
||||
static readonly MAX_COMP_POOL_SIZE = 500; // 每种组件类型最多缓存数量
|
||||
|
||||
/**
|
||||
* 创建group,每个group只关心对应组件的添加和删除
|
||||
|
||||
287
assets/libs/ecs/ECSMonitorLogger.ts
Normal file
287
assets/libs/ecs/ECSMonitorLogger.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* ECS 对象数量监控日志模块
|
||||
* 独立模块,零入侵现有 ECS 代码
|
||||
* 通过控制台命令触发打印 ECS 对象统计表格
|
||||
*/
|
||||
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { ecsPoolCoordinator } from './pool';
|
||||
|
||||
/** 监控数据项 */
|
||||
interface MonitorItem {
|
||||
/** 对象类型 */
|
||||
type: string;
|
||||
/** 活跃数 */
|
||||
active: number;
|
||||
/** 缓存数 */
|
||||
cached: number;
|
||||
/** 总计 */
|
||||
total: number;
|
||||
}
|
||||
|
||||
/** 池监控数据项 */
|
||||
interface PoolMonitorItem {
|
||||
/** 类型名 */
|
||||
typeName: string;
|
||||
/** 活跃数 */
|
||||
active: number;
|
||||
/** 缓存命中 */
|
||||
hitCount: number;
|
||||
/** 缓存未中 */
|
||||
missCount: number;
|
||||
/** 当前缓存 */
|
||||
currentCache: number;
|
||||
/** 总创建 */
|
||||
totalCreated: number;
|
||||
}
|
||||
|
||||
/** 实体缓存监控项 */
|
||||
interface EntityCacheItem {
|
||||
/** 实体名 */
|
||||
entityName: string;
|
||||
/** 缓存组件数 */
|
||||
cachedCompCount: number;
|
||||
}
|
||||
|
||||
/** ECS 监控日志 */
|
||||
export class ECSMonitorLogger {
|
||||
/**
|
||||
* 打印 ECS 总体统计表格
|
||||
*/
|
||||
printSummary(): void {
|
||||
const data: MonitorItem[] = [];
|
||||
|
||||
// 实体统计
|
||||
const activeEntities = ECSModel.eid2Entity.size;
|
||||
let entityCache = 0;
|
||||
const poolMetrics = ecsPoolCoordinator.getAllMetrics();
|
||||
const entityNames = new Set(ECSModel.entityCtors.values());
|
||||
poolMetrics.forEach((metrics, name) => {
|
||||
// 实体池的名称是通过 entityCtors 注册的名称
|
||||
if (entityNames.has(name)) {
|
||||
entityCache += metrics.currentSize;
|
||||
}
|
||||
});
|
||||
data.push({
|
||||
type: 'ECSEntity',
|
||||
active: activeEntities,
|
||||
cached: entityCache,
|
||||
total: activeEntities + entityCache,
|
||||
});
|
||||
|
||||
// 组件统计
|
||||
let activeComps = 0;
|
||||
ECSModel.eid2Entity.forEach((entity) => {
|
||||
activeComps += entity.getMask().toString().split('1').length - 1;
|
||||
});
|
||||
// 只统计动态池中的组件缓存
|
||||
let compCache = 0;
|
||||
poolMetrics.forEach((metrics, name) => {
|
||||
if (ECSModel.compCtors.some((ctor) => ctor.compName === name)) {
|
||||
compCache += metrics.currentSize;
|
||||
}
|
||||
});
|
||||
data.push({
|
||||
type: 'ECSComp',
|
||||
active: activeComps,
|
||||
cached: compCache,
|
||||
total: activeComps + compCache,
|
||||
});
|
||||
|
||||
// Group 统计
|
||||
data.push({
|
||||
type: 'ECSGroup',
|
||||
active: ECSModel.groups.size,
|
||||
cached: 0,
|
||||
total: ECSModel.groups.size,
|
||||
});
|
||||
|
||||
// System 统计
|
||||
let systemCount = 0;
|
||||
ECSModel.systems.forEach((system) => {
|
||||
systemCount += system.comblockSystems.length;
|
||||
});
|
||||
data.push({
|
||||
type: 'ECSSystem',
|
||||
active: systemCount,
|
||||
cached: 0,
|
||||
total: systemCount,
|
||||
});
|
||||
|
||||
console.log('%c[ECS Monitor] 总体统计', 'color:#fff;background:#3a5fcd;padding:2px 8px;border-radius:4px;font-weight:bold;');
|
||||
console.table(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印实体池明细表格
|
||||
*/
|
||||
printEntityPools(): void {
|
||||
const data: PoolMonitorItem[] = [];
|
||||
const poolMetrics = ecsPoolCoordinator.getAllMetrics();
|
||||
const entityNames = new Set(ECSModel.entityCtors.values());
|
||||
|
||||
poolMetrics.forEach((metrics, name) => {
|
||||
if (entityNames.has(name)) {
|
||||
// 计算活跃数:通过 eid2Entity 中 name 匹配的实体
|
||||
let activeCount = 0;
|
||||
ECSModel.eid2Entity.forEach((entity) => {
|
||||
if (entity.name === name) activeCount++;
|
||||
});
|
||||
data.push({
|
||||
typeName: name,
|
||||
active: activeCount,
|
||||
hitCount: metrics.hitCount,
|
||||
missCount: metrics.missCount,
|
||||
currentCache: metrics.currentSize,
|
||||
totalCreated: metrics.createCount,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log('%c[ECS Monitor] 暂无实体池数据', 'color:#ee7700;');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('%c[ECS Monitor] 实体池明细', 'color:#fff;background:#3a5fcd;padding:2px 8px;border-radius:4px;font-weight:bold;');
|
||||
console.table(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件分类排序权重
|
||||
* @param compName 组件名
|
||||
* @returns 排序权重(越小越靠前)
|
||||
*/
|
||||
private getCompSortWeight(compName: string): number {
|
||||
// M_ 开头 - Model 数据层 (权重 1)
|
||||
if (compName.startsWith('M_')) return 1;
|
||||
// B_ 开头 - Business 业务层 (权重 2)
|
||||
if (compName.startsWith('B_')) return 2;
|
||||
// V_ 开头 - View 视图层 (权重 3)
|
||||
if (compName.startsWith('V_')) return 3;
|
||||
// VC_ 开头 - ViewController 视图控制层 (权重 4)
|
||||
if (compName.startsWith('VC_')) return 4;
|
||||
// 其他 (权重 5)
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印组件池明细表格(按 M/B/V/VC/其他 分类排序)
|
||||
*/
|
||||
printComponentPools(): void {
|
||||
const data: PoolMonitorItem[] = [];
|
||||
const poolMetrics = ecsPoolCoordinator.getAllMetrics();
|
||||
|
||||
// 收集所有组件数据
|
||||
const compDataList: { item: PoolMonitorItem; weight: number }[] = [];
|
||||
|
||||
ECSModel.compCtors.forEach((ctor) => {
|
||||
const metrics = poolMetrics.get(ctor.compName);
|
||||
// 计算活跃组件数
|
||||
let activeCount = 0;
|
||||
ECSModel.eid2Entity.forEach((entity) => {
|
||||
if (entity.has(ctor.tid)) activeCount++;
|
||||
});
|
||||
|
||||
const item: PoolMonitorItem = metrics
|
||||
? {
|
||||
typeName: ctor.compName,
|
||||
active: activeCount,
|
||||
hitCount: metrics.hitCount,
|
||||
missCount: metrics.missCount,
|
||||
currentCache: metrics.currentSize,
|
||||
totalCreated: metrics.createCount,
|
||||
}
|
||||
: {
|
||||
typeName: ctor.compName,
|
||||
active: activeCount,
|
||||
hitCount: 0,
|
||||
missCount: 0,
|
||||
currentCache: 0,
|
||||
totalCreated: 0,
|
||||
};
|
||||
|
||||
compDataList.push({
|
||||
item,
|
||||
weight: this.getCompSortWeight(ctor.compName),
|
||||
});
|
||||
});
|
||||
|
||||
// 按权重排序,同权重按名称排序
|
||||
compDataList.sort((a, b) => {
|
||||
if (a.weight !== b.weight) {
|
||||
return a.weight - b.weight;
|
||||
}
|
||||
return a.item.typeName.localeCompare(b.item.typeName);
|
||||
});
|
||||
|
||||
// 提取排序后的数据
|
||||
compDataList.forEach(({ item }) => data.push(item));
|
||||
|
||||
console.log('%c[ECS Monitor] 组件池明细', 'color:#fff;background:#3a5fcd;padding:2px 8px;border-radius:4px;font-weight:bold;');
|
||||
console.table(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印实体组件缓存明细(isRecycle=false 缓存的组件)
|
||||
*/
|
||||
printEntityCaches(): void {
|
||||
const data: EntityCacheItem[] = [];
|
||||
|
||||
ECSModel.eid2Entity.forEach((entity) => {
|
||||
const count = entity.getCachedComponentCount();
|
||||
if (count > 0) {
|
||||
data.push({
|
||||
entityName: `${entity.name}(eid:${entity.eid})`,
|
||||
cachedCompCount: count,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log('%c[ECS Monitor] 暂无实体组件缓存', 'color:#00aa00;');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('%c[ECS Monitor] 实体组件缓存明细 (isRecycle=false)', 'color:#fff;background:#ee7700;padding:2px 8px;border-radius:4px;font-weight:bold;');
|
||||
console.table(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印所有监控表格
|
||||
*/
|
||||
printAll(): void {
|
||||
console.log('%c══════════════ ECS 对象监控 ══════════════', 'color:#3a5fcd;font-weight:bold;font-size:14px;');
|
||||
this.printSummary();
|
||||
this.printEntityPools();
|
||||
this.printComponentPools();
|
||||
this.printEntityCaches();
|
||||
console.log('%c══════════════════════════════════════════', 'color:#3a5fcd;font-weight:bold;font-size:14px;');
|
||||
}
|
||||
}
|
||||
|
||||
/** ECS 监控日志全局实例 */
|
||||
export const ecsMonitor = new ECSMonitorLogger();
|
||||
|
||||
// 注册全局控制台命令
|
||||
if (typeof window !== 'undefined') {
|
||||
Object.assign(window, {
|
||||
ecsMonitor,
|
||||
ecsLog: () => ecsMonitor.printAll(),
|
||||
ecsSummary: () => ecsMonitor.printSummary(),
|
||||
ecsEntityPools: () => ecsMonitor.printEntityPools(),
|
||||
ecsCompPools: () => ecsMonitor.printComponentPools(),
|
||||
ecsEntityCaches: () => ecsMonitor.printEntityCaches(),
|
||||
ecsHelp: () => {
|
||||
console.log('%c[ECS Monitor] 可用命令:', 'color:#fff;background:#3a5fcd;padding:2px 8px;border-radius:4px;font-weight:bold;');
|
||||
console.table([
|
||||
{ command: 'ecsLog()', description: '打印所有监控表格' },
|
||||
{ command: 'ecsSummary()', description: '打印总体统计(实体/组件/分组/系统数量)' },
|
||||
{ command: 'ecsEntityPools()', description: '打印实体池明细(命中/未命中/缓存统计)' },
|
||||
{ command: 'ecsCompPools()', description: '打印组件池明细(命中/未命中/缓存统计)' },
|
||||
{ command: 'ecsEntityCaches()', description: '打印实体组件缓存明细(isRecycle=false 缓存的组件)' },
|
||||
{ command: 'ecsHelp()', description: '显示此帮助信息' },
|
||||
]);
|
||||
},
|
||||
});
|
||||
}
|
||||
9
assets/libs/ecs/ECSMonitorLogger.ts.meta
Normal file
9
assets/libs/ecs/ECSMonitorLogger.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "4a744dda-576a-4194-bec4-1ad35c91a823",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
/*
|
||||
* 对象池管理系统
|
||||
*/
|
||||
|
||||
//#region 类型定义
|
||||
|
||||
/** 池统计指标 */
|
||||
interface PoolMetrics {
|
||||
createCount: number; // 创建次数
|
||||
recycleCount: number; // 回收次数
|
||||
hitCount: number; // 命中次数(从池中获取)
|
||||
missCount: number; // 未命中次数(需要新建)
|
||||
currentSize: number; // 当前池大小
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 动态对象池
|
||||
|
||||
/**
|
||||
* 动态对象池
|
||||
*/
|
||||
class DynamicPool<T> {
|
||||
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<PoolMetrics> {
|
||||
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<string, DynamicPool<any>> = new Map();
|
||||
|
||||
/**
|
||||
* 获取或创建池
|
||||
* @param typeName 池类型名称
|
||||
* @param factory 对象工厂函数
|
||||
* @returns 动态对象池实例
|
||||
*/
|
||||
getPool<T>(typeName: string, factory: () => T): DynamicPool<T> {
|
||||
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<string, number>): void {
|
||||
hints.forEach((count, typeName) => {
|
||||
const pool = this.pools.get(typeName);
|
||||
if (pool) {
|
||||
pool.preWarm(count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的统计信息
|
||||
* @returns 类型名称到统计指标的映射
|
||||
*/
|
||||
getAllMetrics(): Map<string, PoolMetrics> {
|
||||
const result = new Map<string, PoolMetrics>();
|
||||
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<any> | 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<T>(typeName: string, factory: () => T): DynamicPool<T> {
|
||||
return this.poolManager.getPool(typeName, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池管理器实例
|
||||
* @returns 池管理器
|
||||
*/
|
||||
getPoolManager(): PoolManager {
|
||||
return this.poolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景切换时预热池
|
||||
* @param sceneType 场景类型标识
|
||||
* @param hints 类型名称到预热数量的映射
|
||||
*/
|
||||
onSceneChange(sceneType: string, hints: Map<string, number>): 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<string, PoolMetrics> {
|
||||
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();
|
||||
@@ -51,7 +51,7 @@ export abstract class ECSComblockSystem<E extends ECSEntity = ECSEntity> {
|
||||
this.execute = this.updateOnce;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 清理系统资源 */
|
||||
protected cleanup(): void {
|
||||
// 清理实体映射,防止内存泄漏
|
||||
@@ -234,7 +234,7 @@ export class ECSRootSystem {
|
||||
/** 系统组合器,用于将多个相同功能模块的系统逻辑上放在一起,系统也可以嵌套系统 */
|
||||
export class ECSSystem {
|
||||
private _comblockSystems: ECSComblockSystem[] = [];
|
||||
|
||||
|
||||
get comblockSystems(): ECSComblockSystem[] {
|
||||
return this._comblockSystems;
|
||||
}
|
||||
@@ -249,7 +249,7 @@ export class ECSSystem {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/** 清理系统资源 */
|
||||
clear(): void {
|
||||
this._comblockSystems.length = 0;
|
||||
|
||||
9
assets/libs/ecs/pool.meta
Normal file
9
assets/libs/ecs/pool.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "84525bc8-4095-4145-b04b-621c673eee5d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
76
assets/libs/ecs/pool/ECSDynamicPool.ts
Normal file
76
assets/libs/ecs/pool/ECSDynamicPool.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 动态对象池
|
||||
*/
|
||||
|
||||
import { IECSPoolMetrics } from './IECSPoolMetrics';
|
||||
|
||||
/**
|
||||
* 动态对象池
|
||||
* @template T 池中对象的类型
|
||||
*/
|
||||
export class ECSDynamicPool<T> {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池的统计信息
|
||||
* @returns 只读的统计指标对象
|
||||
*/
|
||||
getMetrics(): Readonly<IECSPoolMetrics> {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空池中的所有对象
|
||||
*/
|
||||
clear(): void {
|
||||
this.pool.length = 0;
|
||||
this.metrics.currentSize = 0;
|
||||
}
|
||||
}
|
||||
9
assets/libs/ecs/pool/ECSDynamicPool.ts.meta
Normal file
9
assets/libs/ecs/pool/ECSDynamicPool.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e18efb47-61df-4094-8457-d330b7847318",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
142
assets/libs/ecs/pool/ECSPoolManager.ts
Normal file
142
assets/libs/ecs/pool/ECSPoolManager.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 池管理器
|
||||
*/
|
||||
|
||||
import { ECSDynamicPool } from './ECSDynamicPool';
|
||||
import type { IECSPoolMetrics } from './IECSPoolMetrics';
|
||||
import type { ECSEntity } from '../ECSEntity';
|
||||
import type { ecs } from '../ECS';
|
||||
import { ECSModel, type CompCtor, type EntityCtor } from '../ECSModel';
|
||||
|
||||
/** 池对象类型 - 实体或组件 */
|
||||
type ECSPoolObject = ECSEntity | ecs.IComp;
|
||||
|
||||
/** 池类型名称 - 实体名或组件名 */
|
||||
type ECSPoolTypeName = string;
|
||||
|
||||
/**
|
||||
* 池管理器 - 统一管理所有对象池
|
||||
*/
|
||||
export class ECSPoolManager {
|
||||
/** 所有对象池的映射 */
|
||||
private pools: Map<ECSPoolTypeName, ECSDynamicPool<ECSPoolObject>> = new Map();
|
||||
|
||||
/**
|
||||
* 从构造函数获取池类型名称
|
||||
* @param ctor 实体或组件的构造函数
|
||||
* @returns 池类型名称
|
||||
*/
|
||||
private getTypeNameFromCtor<T extends ECSEntity>(ctor: EntityCtor<T>): string {
|
||||
// 检查是否是组件构造函数(有 compName 属性)
|
||||
const ctorAny = ctor as unknown as { compName?: string };
|
||||
if (typeof ctorAny.compName === 'string') {
|
||||
return ctorAny.compName;
|
||||
}
|
||||
|
||||
// 检查是否是实体构造函数(从 ECSModel.entityCtors 查找)
|
||||
const entityName = ECSModel.entityCtors.get(ctor as EntityCtor<ECSEntity>);
|
||||
if (entityName) {
|
||||
return entityName;
|
||||
}
|
||||
|
||||
// 如果都找不到,使用构造函数名
|
||||
return ctor.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建池
|
||||
* @param typeName 池类型名称(实体名如 "Account"、"RedDot",或组件名如 "M_Equip_Model")
|
||||
* @param factory 对象工厂函数
|
||||
* @returns 动态对象池实例
|
||||
*/
|
||||
getPool<T extends ECSPoolObject>(typeName: ECSPoolTypeName, factory: () => T): ECSDynamicPool<T> {
|
||||
if (!this.pools.has(typeName)) {
|
||||
const pool = new ECSDynamicPool<ECSPoolObject>(typeName, factory);
|
||||
this.pools.set(typeName, pool);
|
||||
}
|
||||
|
||||
return this.pools.get(typeName)! as ECSDynamicPool<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定池(清空对象并从管理中移除)
|
||||
* @param typeNameOrCtor 池类型名称(实体名或组件名)或构造函数
|
||||
* @returns 是否成功删除
|
||||
*/
|
||||
removePool(typeNameOrCtor: ECSPoolTypeName | EntityCtor<ECSEntity> | CompCtor<ecs.IComp>): boolean {
|
||||
let typeName: string;
|
||||
|
||||
if (typeof typeNameOrCtor === 'string') {
|
||||
typeName = typeNameOrCtor;
|
||||
}
|
||||
else {
|
||||
typeName = this.getTypeNameFromCtor(typeNameOrCtor as EntityCtor<ECSEntity>);
|
||||
}
|
||||
|
||||
const pool = this.pools.get(typeName);
|
||||
if (pool) {
|
||||
pool.clear();
|
||||
this.pools.delete(typeName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有池
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.pools.forEach(pool => pool.clear());
|
||||
this.pools.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的统计信息
|
||||
* @returns 类型名称到统计指标的映射
|
||||
*/
|
||||
getAllMetrics(): Map<ECSPoolTypeName, IECSPoolMetrics> {
|
||||
const result = new Map<ECSPoolTypeName, IECSPoolMetrics>();
|
||||
this.pools.forEach((pool, typeName) => {
|
||||
result.set(typeName, pool.getMetrics());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的池
|
||||
* @param typeName 池类型名称(实体名或组件名)
|
||||
* @returns 池实例,如果不存在则返回undefined
|
||||
*/
|
||||
getPoolByName<T extends ECSPoolObject>(typeName: ECSPoolTypeName): ECSDynamicPool<T> | undefined {
|
||||
return this.pools.get(typeName) as ECSDynamicPool<T> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的名称
|
||||
* @returns 池名称数组(实体名或组件名数组)
|
||||
*/
|
||||
getPoolNames(): ECSPoolTypeName[] {
|
||||
return Array.from(this.pools.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空指定池中的对象
|
||||
* @param typeName 池类型名称(实体名或组件名)
|
||||
*/
|
||||
clearPool(typeName: ECSPoolTypeName): void {
|
||||
const pool = this.pools.get(typeName);
|
||||
if (pool) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定池的统计信息
|
||||
* @param typeName 池类型名称(实体名或组件名)
|
||||
* @returns 统计指标对象,如果池不存在则返回undefined
|
||||
*/
|
||||
getPoolMetrics(typeName: ECSPoolTypeName): IECSPoolMetrics | undefined {
|
||||
const pool = this.pools.get(typeName);
|
||||
return pool ? pool.getMetrics() : undefined;
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
17
assets/libs/ecs/pool/IECSPoolMetrics.ts
Normal file
17
assets/libs/ecs/pool/IECSPoolMetrics.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 池统计指标
|
||||
*/
|
||||
|
||||
/** 池统计指标 */
|
||||
export interface IECSPoolMetrics {
|
||||
/** 创建次数 */
|
||||
createCount: number;
|
||||
/** 回收次数 */
|
||||
recycleCount: number;
|
||||
/** 命中次数(从池中获取) */
|
||||
hitCount: number;
|
||||
/** 未命中次数(需要新建) */
|
||||
missCount: number;
|
||||
/** 当前池大小 */
|
||||
currentSize: number;
|
||||
}
|
||||
9
assets/libs/ecs/pool/IECSPoolMetrics.ts.meta
Normal file
9
assets/libs/ecs/pool/IECSPoolMetrics.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "575be89a-5bfa-4f9e-80f1-88f43edaa1bc",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
25
assets/libs/ecs/pool/index.ts
Normal file
25
assets/libs/ecs/pool/index.ts
Normal file
@@ -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();
|
||||
9
assets/libs/ecs/pool/index.ts.meta
Normal file
9
assets/libs/ecs/pool/index.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "72c10359-0587-429d-957f-743e7f97673d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user