Files
oops-plugin-framework/assets/libs/ecs/ECSMonitorLogger.ts
dgflash 3a2db77647 1. 核心入口 — ECS.ts
ecs 命名空间:统一导出所有 API,业务只需 import { ecs } from './ECS'
核心类型别名:ecs.Entity / ecs.Comp / ecs.RootSystem / ecs.ComblockSystem
接口再导出:ecs.IComp / ecs.IMatcher / ecs.IEntityEnterSystem / ecs.IEntityRemoveSystem / ecs.ISystemFirstUpdate / ecs.ISystemUpdate
实体工厂:ecs.getEntity(ctor, world?) — 创建或从对象池获取实体
动态查询:ecs.query(matcher, world?) — 按匹配器查询实体
过滤器工厂:ecs.allOf() / ecs.anyOf() / ecs.onlyOf() / ecs.excludeOf() — 组合匹配规则
单例组件:ecs.getSingleton() / ecs.addSingleton() — 全局唯一组件访问
子模块门面:ecs.world / ecs.pool / ecs.system / ecs.storage / ecs.network / ecs.serialize / ecs.entityRef
2. 驱动器 — ECSDriver.ts
封装默认世界的 init / execute / destroy 生命周期
add(system) — 向默认世界添加业务系统
init() — 初始化 ECS(并入 @ecs.register 注册的系统、拓扑排序、init)
execute(dt) — 驱动默认世界一帧
destroy() — 清理所有子系统
3. 实体 — ECSEntity.ts
组件容器:位掩码 mask + 按 tid 索引的密集数组,get/has 为 O(1)
双重重载 add():
add(组件类) — 从对象池取实例或恢复软移除缓存
add(组件实例) — 挂载外部实例(如 cc.Component),设 canRecycle=false
remove(ctor, isRecycle?):
isRecycle=true(默认):reset + 回池/释放 SoA 槽位
isRecycle=false:软移除,数据暂存 compTid2Obj,下次 add 同 tid 恢复
父子层级:addChild / removeChild,带循环引用检测
destroy():断开父子 → 移除全部组件 → 清理软移除缓存 → 回收实体 + 释放 eid
forEachComponent(cb):遍历当前挂载的所有组件
4. 组件 — ECSComp.ts
抽象基类,子类须实现 reset() 方法
canRecycle — 是否可回收(外部创建的组件设 false)
ent — 拥有该组件的实体引用
变更检测:markDirty() / isChangedSince(sinceEpoch) / lastWriteEpoch — 基于 epoch 的帧级脏标记
5. 位掩码 — ECSMask.ts
基于 Uint32Array 的位运算,支持 set / has / delete / and / or / bitCount
对象池复用,clearPool() 清理
6. 注册系统 — ECSRegister.ts + ECSTypeRegistry.ts
@ecs.register('Name') 类装饰器:自动识别注册类型
组件:分配 tid,写入 compCtors 表
实体:记录 ctor → name 映射
系统:注册到全局表或指定世界
ECSTypeRegistry:全局类型注册表(跨世界共享),记录组件/实体/系统元数据
7. 查询 — ECSMatcher.ts + ECSGroup.ts
四种规则:AllOf / AnyOf / OnlyOf / ExcludeOf,规则间为"与"关系
flyweight 缓存:相同组合共享同一 Matcher 实例
小规则优化:tid 数 ≤ 4 时直接 mask.has,否则分配 ECSMask
ECSGroup:SparseSet 实现,O(1) 增删(swap-pop),稳定快照遍历
进入/离开追踪:watchEntityEnterAndRemove 供系统帧内查询变化
8. 系统 — ECSComblockSystem.ts + ECSRootSystem.ts
ComblockSystem:业务系统基类
生命周期:init → entityEnter → firstUpdate → update → entityRemove → onDestroy
filter() — 声明实体匹配规则
interval — 执行间隔(0=每帧,>0=固定间隔)
被动系统:isPassiveSystem() 返回 true 时不参与每帧 update
构造时探测子类钩子,选定 execute 变体(零开销分支)
RootSystem:根系统,一世界一个
init() — 并入 @ecs.register 系统 + 拓扑排序 + 绑定世界 + init
execute(dt) — 切换当前世界 → 递增 epoch → tick 各系统 → flush 命令缓冲
9. 系统调度 — SystemScheduler.ts
声明式执行顺序:@ecs.system.executeBefore / @ecs.system.executeAfter / @ecs.system.inSet
拓扑排序(Kahn 算法):无约束系统保持原始顺序,循环依赖抛 CycleDependencyError
集合机制:inSet('名') 把系统加入虚拟分组,其他系统用 executeAfter('set:名') 批量依赖
10. 世界 — ECSWorld.ts + ECSWorldManager.ts
ECSWorld:运行期数据容器
entities — 活动实体表(eid → 实体)
groups — 响应式查询分组
singletons — 单例组件表
refs — @entityRef 引用追踪
commands — 延迟结构变更命令队列
epoch — 世代号(每帧递增,变更检测用)
getEntity(ctor) — 创建/从池获取实体
assignEid(entity, eid) — 反序列化时保持 eid 一致
ECSWorldManager(ecs.world):多世界管理
get(name?) / default() / current — 获取/切换世界
use(world) — 切换当前世界,返回切换前的世界
inWorld(world, fn) — 在指定世界中执行,自动还原
createSystems(world, ...ctors) — 批量装配系统并 init
defer(fn) / flushCommands() — 延迟结构变更
11. 命令缓冲 — ECSCommandBuffer.ts
延迟结构变更队列,帧末由 RootSystem.execute 统一 flush
push(fn) 入队,flush() 先快照再执行(避免本帧新入队命令在本帧执行)
12. 对象池 — ECSPoolManager.ts + ECSDynamicPool.ts
ecs.pool:统一管理实体/组件动态对象池
getPool(name, factory) — 获取或创建池
clearAll() / clearPools() — 清空池缓存(不触碰存活数据)
getAllMetrics() — 获取各池统计信息(命中/未命中/缓存/创建数)
13. 实体引用 — ECSEntityRef.ts + ECSReferenceTracker.ts
@ecs.entityRef() 属性装饰器:标记组件属性为实体引用
目标实体销毁时自动置 null,避免悬空引用
组件回收时 clearComponentEntityRefs 清理所有引用
14. SoA 列存储 — StorageSoA.ts + StorageDecorators.ts
@ecs.storage.enableSoA 类装饰器:为组件启用 SoA 列存储(默认 AoS,opt-in)
字段装饰器:float64 / float32 / int32 / uint32 / int16 / uint16 / int8 / uint8
数值字段按列存入 TypedArray,按 eid 分配槽位
acquire 返回 Proxy 视图:SoA 字段读写直接落到 TypedArray,非 SoA 字段走后备实例
槽位超出容量时 2 倍扩容,释放时入空闲栈
自注册机制:模块加载时注册到核心,删除 storage/ 目录后基础 ECS 仍可运行
15. 网络同步 — NetworkSync.ts + SyncDecorators.ts + SyncCodec.ts + ByteBuffer.ts
@ecs.network.sync(类型) 字段装饰器:标记组件字段参与网络同步
ecs.network.net 门面:
encodeWorld(op?) — 编码当前世界为紧凑二进制(Full 全量 / Delta 增量)
applyToWorld(bytes, onMissing?) — 将二进制同步数据应用到世界
track(entity, markAll?) — 为实体初始化变更追踪器
clearDirty() — 清除所有脏标记
SyncCodec:实体/组件级编解码器
ByteWriter / ByteReader:二进制读写缓冲(变长整数等)
ChangeTracker:字段级脏标记追踪
16. 序列化 — Serialization.ts + Incremental.ts
@ecs.serialize() 字段装饰器:标记组件需要持久化的字段
全量序列化:
ecs.serialize.serializeWorld(pretty?) — 序列化当前世界为 JSON
ecs.serialize.deserializeWorld(json) — 从 JSON 反序列化到当前世界
增量序列化:
ecs.serialize.snapshot() — 对当前世界拍快照(基线)
ecs.serialize.computeDelta(base) — 计算相对基线的增量(added / removed / changed)
ecs.serialize.applyDelta(delta) — 将增量应用到当前世界
保留实体层级(parentEid),两阶段重建(先建实体+组件,再重建父子)
17. 监控日志 — ECSMonitorLogger.ts
零入侵监控模块,通过控制台命令触发
注册全局命令:
ecsLog() — 打印所有监控表格
ecsWorldSummary() — 各世界实体/系统/组件缓存统计
ecsSummary() — 总体统计(所有世界合计)
ecsEntityPools() — 实体池明细(命中/未命中/缓存)
ecsCompPools() — 组件池明细(按 M/B/V/VC 分类排序)
ecsEntityCaches() — 实体软移除组件缓存明细
ecsHelp() — 显示帮助信息
2026-06-12 21:52:47 +08:00

402 lines
14 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ECS 对象数量监控日志模块
* 独立模块,零入侵现有 ECS 代码
* 通过控制台命令触发打印 ECS 对象统计表格
*/
import type { ECSWorld } from './world/ECSWorld';
import { registry } from './registry/ECSTypeRegistry';
import { ecsWorldManager } from './world/ECSWorldManager';
import { ecsPoolCoordinator } from './pool/ECSPoolManager';
/** 监控数据项 */
interface MonitorItem {
/** 对象类型 */
type: string;
/** 活跃数 */
active: number;
/** 缓存数 */
cached: number;
/** 总计 */
total: number;
}
/** 世界监控数据项 */
interface WorldMonitorItem {
/** 世界名称 */
worldName: string;
/** 活跃实体数 */
entities: number;
/** 活跃组件数(实体上已挂载) */
activeComponents: number;
/** 软移除缓存组件数isRecycle=false */
softCachedComponents: number;
/** 本世界 @ecs.register(world) 注册的系统类数 */
worldSystems: number;
}
/** 对象池缓存汇总 */
interface PoolCacheSummaryItem {
/** 缓存类别 */
category: string;
/** 池中闲置对象数 */
cached: number;
}
/** 池监控数据项 */
interface PoolMonitorItem {
/** 类型名 */
typeName: string;
/** 活跃数 */
active: number;
/** 缓存命中 */
hitCount: number;
/** 缓存未中 */
missCount: number;
/** 当前缓存 */
currentCache: number;
/** 总创建 */
totalCreated: number;
}
/** 实体缓存监控项 */
interface EntityCacheItem {
/** 所属世界 */
worldName: string;
/** 实体名 */
entityName: string;
/** 缓存组件数 */
cachedCompCount: number;
}
/**
* ECS 监控日志
* 通过控制台命令输出实体/组件/系统/对象池的统计表格,零入侵现有 ECS 逻辑
*/
export class ECSMonitorLogger {
/** 统计全局 registry.systems 中注册的系统类总数 */
private countGlobalSystemClasses(): number {
let count = 0;
registry.systems.forEach((ctors) => { count += ctors.length; });
return count;
}
/** 统计某世界 world.systems 中注册的系统类总数 */
private countWorldSystemClasses(world: ECSWorld): number {
return world.systems.count;
}
/** 收集单个世界的实体/组件/系统统计 */
private collectWorldStats(world: ECSWorld): WorldMonitorItem {
let activeComponents = 0;
let softCachedComponents = 0;
world.entities.forEach((entity) => {
activeComponents += entity.getMask().bitCount();
softCachedComponents += entity.getCachedComponentCount();
});
return {
worldName: world.name,
entities: world.entities.size,
activeComponents,
softCachedComponents,
worldSystems: this.countWorldSystemClasses(world),
};
}
/** 汇总对象池中实体与组件的闲置缓存数量 */
private collectPoolCacheStats(): PoolCacheSummaryItem[] {
const poolMetrics = ecsPoolCoordinator.getAllMetrics();
const entityNames = new Set(registry.entityCtors.values());
let entityPoolCached = 0;
let compPoolCached = 0;
poolMetrics.forEach((metrics, name) => {
if (entityNames.has(name)) {
entityPoolCached += metrics.currentSize;
}
else if (registry.compCtors.some((ctor) => ctor?.compName === name)) {
compPoolCached += metrics.currentSize;
}
});
return [
{ category: '实体池', cached: entityPoolCached },
{ category: '组件池', cached: compPoolCached },
{ category: '合计', cached: entityPoolCached + compPoolCached },
];
}
/** 遍历所有世界 */
private forEachWorld(fn: (world: ECSWorld) => void): void {
ecsWorldManager.worlds.forEach(fn);
}
/**
* 打印各世界实体/系统/组件统计,以及全局对象池组件缓存汇总
*/
printWorldSummary(): void {
const worldData: WorldMonitorItem[] = [];
this.forEachWorld((world) => {
worldData.push(this.collectWorldStats(world));
});
const globalSystems = this.countGlobalSystemClasses();
const poolCache = this.collectPoolCacheStats();
const totalSoftCache = worldData.reduce((sum, row) => sum + row.softCachedComponents, 0);
const totalPoolCompCache = poolCache.find((row) => row.category === '组件池')?.cached ?? 0;
console.log('%c[ECS Monitor] 各世界统计', 'color:#fff;background:#3a5fcd;padding:2px 8px;border-radius:4px;font-weight:bold;');
console.table(worldData);
console.log('%c[ECS Monitor] 全局注册系统(@ecs.register 未指定 worldRootSystem.init 时按世界实例化)', 'color:#888;');
console.table([{ globalSystemClasses: globalSystems }]);
console.log('%c[ECS Monitor] 对象池闲置缓存', 'color:#fff;background:#3a5fcd;padding:2px 8px;border-radius:4px;font-weight:bold;');
console.table(poolCache);
console.log(
`%c[ECS Monitor] 组件缓存合计:对象池 ${totalPoolCompCache} + 实体软移除 ${totalSoftCache} = ${totalPoolCompCache + totalSoftCache}`,
'color:#00aa00;font-weight:bold;'
);
}
/**
* 打印 ECS 总体统计表格(汇总所有世界)
*/
printSummary(): void {
const data: MonitorItem[] = [];
let totalEntities = 0;
let totalActiveComps = 0;
let totalSoftCompCache = 0;
let totalGroups = 0;
this.forEachWorld((world) => {
totalEntities += world.entities.size;
totalGroups += world.groups.size;
world.entities.forEach((entity) => {
totalActiveComps += entity.getMask().bitCount();
totalSoftCompCache += entity.getCachedComponentCount();
});
});
const poolCache = this.collectPoolCacheStats();
const entityPoolCached = poolCache.find((row) => row.category === '实体池')?.cached ?? 0;
const compPoolCached = poolCache.find((row) => row.category === '组件池')?.cached ?? 0;
data.push({
type: 'ECSEntity',
active: totalEntities,
cached: entityPoolCached,
total: totalEntities + entityPoolCached,
});
data.push({
type: 'ECSComp',
active: totalActiveComps,
cached: compPoolCached + totalSoftCompCache,
total: totalActiveComps + compPoolCached + totalSoftCompCache,
});
data.push({
type: 'ECSGroup',
active: totalGroups,
cached: 0,
total: totalGroups,
});
let worldSystemClasses = 0;
this.forEachWorld((world) => {
worldSystemClasses += this.countWorldSystemClasses(world);
});
const globalSystemClasses = this.countGlobalSystemClasses();
data.push({
type: 'ECSSystem(世界注册)',
active: worldSystemClasses,
cached: 0,
total: worldSystemClasses,
});
data.push({
type: 'ECSSystem(全局注册)',
active: globalSystemClasses,
cached: 0,
total: globalSystemClasses,
});
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(registry.entityCtors.values());
poolMetrics.forEach((metrics, name) => {
if (entityNames.has(name)) {
let activeCount = 0;
this.forEachWorld((world) => {
world.entities.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 {
if (compName.startsWith('M_')) return 1;
if (compName.startsWith('B_')) return 2;
if (compName.startsWith('V_')) return 3;
if (compName.startsWith('VC_')) return 4;
return 5;
}
/**
* 打印组件池明细表格(按 M/B/V/VC/其他 分类排序)
*/
printComponentPools(): void {
const data: PoolMonitorItem[] = [];
const poolMetrics = ecsPoolCoordinator.getAllMetrics();
const compDataList: { item: PoolMonitorItem; weight: number }[] = [];
registry.compCtors.forEach((ctor) => {
if (!ctor) return;
const metrics = poolMetrics.get(ctor.compName);
let activeCount = 0;
this.forEachWorld((world) => {
world.entities.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[] = [];
this.forEachWorld((world) => {
world.entities.forEach((entity) => {
const count = entity.getCachedComponentCount();
if (count > 0) {
data.push({
worldName: world.name,
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.printWorldSummary();
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(),
ecsWorldSummary: () => ecsMonitor.printWorldSummary(),
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: 'ecsWorldSummary()', description: '打印各世界实体/系统/组件缓存统计' },
{ command: 'ecsSummary()', description: '打印总体统计(所有世界合计)' },
{ command: 'ecsEntityPools()', description: '打印实体池明细(命中/未命中/缓存统计)' },
{ command: 'ecsCompPools()', description: '打印组件池明细(命中/未命中/缓存统计)' },
{ command: 'ecsEntityCaches()', description: '打印实体软移除组件缓存明细' },
{ command: 'ecsHelp()', description: '显示此帮助信息' },
]);
},
});
}