移除废弃的文件
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* 可更新接口
|
||||
* @deprecated 不符合ECS架构规范,建议使用EntitySystem来处理更新逻辑而非在组件中实现
|
||||
*/
|
||||
export interface IUpdatable {
|
||||
enabled: boolean;
|
||||
updateOrder: number;
|
||||
update(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于比较组件更新排序的比较器
|
||||
*/
|
||||
export class IUpdatableComparer {
|
||||
public compare(a: IUpdatable, b: IUpdatable): number {
|
||||
return a.updateOrder - b.updateOrder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否实现了IUpdatable接口
|
||||
* @param props 要检查的对象
|
||||
* @returns 如果实现了IUpdatable接口返回true,否则返回false
|
||||
*/
|
||||
export function isIUpdatable(props: any): props is IUpdatable {
|
||||
return typeof (props as IUpdatable)['update'] !== 'undefined';
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import type { Scene } from '../Scene';
|
||||
|
||||
/**
|
||||
* 场景组件基类
|
||||
* 附加到场景的组件,用于实现场景级别的功能
|
||||
*/
|
||||
export class SceneComponent {
|
||||
/** 组件所属的场景 */
|
||||
public scene!: Scene;
|
||||
/** 更新顺序 */
|
||||
public updateOrder: number = 0;
|
||||
/** 是否启用 */
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/** 获取是否启用 */
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/** 设置是否启用 */
|
||||
public set enabled(value: boolean) {
|
||||
if (this._enabled !== value) {
|
||||
this._enabled = value;
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
} else {
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当组件启用时调用
|
||||
*/
|
||||
public onEnabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当组件禁用时调用
|
||||
*/
|
||||
public onDisabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当组件从场景中移除时调用
|
||||
*/
|
||||
public onRemovedFromScene(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧更新
|
||||
* @deprecated 不符合ECS架构规范,建议使用EntitySystem来处理场景级更新逻辑
|
||||
*/
|
||||
public update(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较组件的更新顺序
|
||||
* @param other 其他组件
|
||||
* @returns 比较结果
|
||||
*/
|
||||
public compare(other: SceneComponent): number {
|
||||
return this.updateOrder - other.updateOrder;
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { ComponentType } from './ComponentStorage';
|
||||
import { ComponentSparseSet } from '../Utils/ComponentSparseSet';
|
||||
|
||||
|
||||
/**
|
||||
* 索引统计信息
|
||||
*/
|
||||
export interface IndexStats {
|
||||
/** 索引大小 */
|
||||
size: number;
|
||||
/** 内存使用量(字节) */
|
||||
memoryUsage: number;
|
||||
/** 查询次数 */
|
||||
queryCount: number;
|
||||
/** 平均查询时间(毫秒) */
|
||||
avgQueryTime: number;
|
||||
/** 最后更新时间 */
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件索引接口
|
||||
*/
|
||||
export interface IComponentIndex {
|
||||
/** 添加实体到索引 */
|
||||
addEntity(entity: Entity): void;
|
||||
/** 从索引中移除实体 */
|
||||
removeEntity(entity: Entity): void;
|
||||
/** 查询包含指定组件的实体 */
|
||||
query(componentType: ComponentType): Set<Entity>;
|
||||
/** 批量查询多个组件 */
|
||||
queryMultiple(componentTypes: ComponentType[], operation: 'AND' | 'OR'): Set<Entity>;
|
||||
/** 清空索引 */
|
||||
clear(): void;
|
||||
/** 获取索引统计信息 */
|
||||
getStats(): IndexStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用组件索引实现
|
||||
*
|
||||
* 基于Sparse Set算法:
|
||||
* - O(1)的实体添加、删除、查找
|
||||
* - 高效的位运算查询
|
||||
* - 内存紧凑的存储结构
|
||||
* - 缓存友好的遍历性能
|
||||
*/
|
||||
export class ComponentIndex implements IComponentIndex {
|
||||
|
||||
/**
|
||||
* 组件稀疏集合
|
||||
*
|
||||
* 核心存储结构,处理所有实体和组件的索引操作。
|
||||
*/
|
||||
private _sparseSet: ComponentSparseSet;
|
||||
|
||||
// 性能统计
|
||||
private _queryCount = 0;
|
||||
private _totalQueryTime = 0;
|
||||
private _lastUpdated = Date.now();
|
||||
|
||||
constructor() {
|
||||
this._sparseSet = new ComponentSparseSet();
|
||||
}
|
||||
|
||||
public addEntity(entity: Entity): void {
|
||||
this._sparseSet.addEntity(entity);
|
||||
this._lastUpdated = Date.now();
|
||||
}
|
||||
|
||||
public removeEntity(entity: Entity): void {
|
||||
this._sparseSet.removeEntity(entity);
|
||||
this._lastUpdated = Date.now();
|
||||
}
|
||||
|
||||
public query(componentType: ComponentType): Set<Entity> {
|
||||
const startTime = performance.now();
|
||||
const result = this._sparseSet.queryByComponent(componentType);
|
||||
|
||||
this._queryCount++;
|
||||
this._totalQueryTime += performance.now() - startTime;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public queryMultiple(componentTypes: ComponentType[], operation: 'AND' | 'OR'): Set<Entity> {
|
||||
const startTime = performance.now();
|
||||
|
||||
let result: Set<Entity>;
|
||||
|
||||
if (componentTypes.length === 0) {
|
||||
result = new Set();
|
||||
} else if (componentTypes.length === 1) {
|
||||
result = this.query(componentTypes[0]);
|
||||
} else if (operation === 'AND') {
|
||||
result = this._sparseSet.queryMultipleAnd(componentTypes);
|
||||
} else {
|
||||
result = this._sparseSet.queryMultipleOr(componentTypes);
|
||||
}
|
||||
|
||||
this._queryCount++;
|
||||
this._totalQueryTime += performance.now() - startTime;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public clear(): void {
|
||||
this._sparseSet.clear();
|
||||
this._lastUpdated = Date.now();
|
||||
}
|
||||
|
||||
public getStats(): IndexStats {
|
||||
const memoryStats = this._sparseSet.getMemoryStats();
|
||||
|
||||
return {
|
||||
size: this._sparseSet.size,
|
||||
memoryUsage: memoryStats.totalMemory,
|
||||
queryCount: this._queryCount,
|
||||
avgQueryTime: this._queryCount > 0 ? this._totalQueryTime / this._queryCount : 0,
|
||||
lastUpdated: this._lastUpdated
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 组件索引管理器
|
||||
*
|
||||
* 使用统一的组件索引实现,自动优化查询性能。
|
||||
*/
|
||||
export class ComponentIndexManager {
|
||||
private _index: ComponentIndex;
|
||||
|
||||
constructor() {
|
||||
this._index = new ComponentIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体到索引
|
||||
*/
|
||||
public addEntity(entity: Entity): void {
|
||||
this._index.addEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从索引中移除实体
|
||||
*/
|
||||
public removeEntity(entity: Entity): void {
|
||||
this._index.removeEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询包含指定组件的实体
|
||||
*/
|
||||
public query(componentType: ComponentType): Set<Entity> {
|
||||
return this._index.query(componentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询多个组件
|
||||
*/
|
||||
public queryMultiple(componentTypes: ComponentType[], operation: 'AND' | 'OR'): Set<Entity> {
|
||||
return this._index.queryMultiple(componentTypes, operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取索引统计信息
|
||||
*/
|
||||
public getStats(): IndexStats {
|
||||
return this._index.getStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空索引
|
||||
*/
|
||||
public clear(): void {
|
||||
this._index.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
import { ComponentType } from './ComponentStorage';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
|
||||
/**
|
||||
* 脏标记类型
|
||||
*/
|
||||
export enum DirtyFlag {
|
||||
/** 组件数据已修改 */
|
||||
COMPONENT_MODIFIED = 1 << 0,
|
||||
/** 组件已添加 */
|
||||
COMPONENT_ADDED = 1 << 1,
|
||||
/** 组件已移除 */
|
||||
COMPONENT_REMOVED = 1 << 2,
|
||||
/** 实体位置已改变 */
|
||||
TRANSFORM_CHANGED = 1 << 3,
|
||||
/** 实体状态已改变 */
|
||||
STATE_CHANGED = 1 << 4,
|
||||
/** 自定义标记1 */
|
||||
CUSTOM_1 = 1 << 8,
|
||||
/** 自定义标记2 */
|
||||
CUSTOM_2 = 1 << 9,
|
||||
/** 自定义标记3 */
|
||||
CUSTOM_3 = 1 << 10,
|
||||
/** 所有标记 */
|
||||
ALL = 0xFFFFFFFF
|
||||
}
|
||||
|
||||
/**
|
||||
* 脏标记数据
|
||||
*/
|
||||
export interface DirtyData {
|
||||
/** 实体引用 */
|
||||
entity: Entity;
|
||||
/** 脏标记位 */
|
||||
flags: number;
|
||||
/** 修改的组件类型列表 */
|
||||
modifiedComponents: Set<ComponentType>;
|
||||
/** 标记时间戳 */
|
||||
timestamp: number;
|
||||
/** 帧编号 */
|
||||
frameNumber: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脏标记监听器
|
||||
*/
|
||||
export interface DirtyListener {
|
||||
/** 感兴趣的标记类型 */
|
||||
flags: number;
|
||||
/** 回调函数 */
|
||||
callback: (dirtyData: DirtyData) => void;
|
||||
/** 监听器优先级(数字越小优先级越高) */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脏标记统计信息
|
||||
*/
|
||||
export interface DirtyStats {
|
||||
/** 当前脏实体数量 */
|
||||
dirtyEntityCount: number;
|
||||
/** 总标记次数 */
|
||||
totalMarkings: number;
|
||||
/** 总清理次数 */
|
||||
totalCleanups: number;
|
||||
/** 监听器数量 */
|
||||
listenerCount: number;
|
||||
/** 平均每帧脏实体数量 */
|
||||
avgDirtyPerFrame: number;
|
||||
/** 内存使用量估算 */
|
||||
estimatedMemoryUsage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脏标记追踪系统
|
||||
*
|
||||
* 提供高效的组件和实体变更追踪,避免不必要的计算和更新。
|
||||
* 支持细粒度的脏标记和批量处理机制。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const dirtySystem = new DirtyTrackingSystem();
|
||||
*
|
||||
* // 标记实体的位置组件已修改
|
||||
* dirtySystem.markDirty(entity, DirtyFlag.TRANSFORM_CHANGED, [PositionComponent]);
|
||||
*
|
||||
* // 监听位置变化
|
||||
* dirtySystem.addListener({
|
||||
* flags: DirtyFlag.TRANSFORM_CHANGED,
|
||||
* callback: (data) => {
|
||||
* logger.debug('Entity position changed:', data.entity.name);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // 处理所有脏标记
|
||||
* dirtySystem.processDirtyEntities();
|
||||
* ```
|
||||
*/
|
||||
export class DirtyTrackingSystem {
|
||||
private static readonly _logger = createLogger('DirtyTrackingSystem');
|
||||
/** 脏实体映射表 */
|
||||
private _dirtyEntities = new Map<Entity, DirtyData>();
|
||||
|
||||
/** 脏标记监听器 */
|
||||
private _listeners: DirtyListener[] = [];
|
||||
|
||||
/** 性能统计 */
|
||||
private _stats = {
|
||||
totalMarkings: 0,
|
||||
totalCleanups: 0,
|
||||
frameCount: 0,
|
||||
totalDirtyPerFrame: 0
|
||||
};
|
||||
|
||||
/** 当前帧编号 */
|
||||
private _currentFrame = 0;
|
||||
|
||||
private _batchSize = 100;
|
||||
private _maxProcessingTime = 16;
|
||||
|
||||
/** 延迟处理队列 */
|
||||
private _processingQueue: DirtyData[] = [];
|
||||
private _isProcessing = false;
|
||||
|
||||
/**
|
||||
* 标记实体为脏状态
|
||||
*
|
||||
* @param entity 要标记的实体
|
||||
* @param flags 脏标记位
|
||||
* @param modifiedComponents 修改的组件类型列表
|
||||
*/
|
||||
public markDirty(entity: Entity, flags: DirtyFlag, modifiedComponents: ComponentType[] = []): void {
|
||||
this._stats.totalMarkings++;
|
||||
|
||||
let dirtyData = this._dirtyEntities.get(entity);
|
||||
if (!dirtyData) {
|
||||
dirtyData = {
|
||||
entity,
|
||||
flags: 0,
|
||||
modifiedComponents: new Set(),
|
||||
timestamp: performance.now(),
|
||||
frameNumber: this._currentFrame
|
||||
};
|
||||
this._dirtyEntities.set(entity, dirtyData);
|
||||
}
|
||||
|
||||
dirtyData.flags |= flags;
|
||||
dirtyData.timestamp = performance.now();
|
||||
dirtyData.frameNumber = this._currentFrame;
|
||||
|
||||
for (const componentType of modifiedComponents) {
|
||||
dirtyData.modifiedComponents.add(componentType);
|
||||
}
|
||||
|
||||
this.notifyListeners(dirtyData, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否有指定的脏标记
|
||||
*
|
||||
* @param entity 要检查的实体
|
||||
* @param flags 要检查的标记位
|
||||
* @returns 是否有指定的脏标记
|
||||
*/
|
||||
public isDirty(entity: Entity, flags: DirtyFlag = DirtyFlag.ALL): boolean {
|
||||
const dirtyData = this._dirtyEntities.get(entity);
|
||||
return dirtyData ? (dirtyData.flags & flags) !== 0 : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除实体的脏标记
|
||||
*
|
||||
* @param entity 要清除的实体
|
||||
* @param flags 要清除的标记位,默认清除所有
|
||||
*/
|
||||
public clearDirty(entity: Entity, flags: DirtyFlag = DirtyFlag.ALL): void {
|
||||
const dirtyData = this._dirtyEntities.get(entity);
|
||||
if (!dirtyData) return;
|
||||
|
||||
if (flags === DirtyFlag.ALL) {
|
||||
this._dirtyEntities.delete(entity);
|
||||
} else {
|
||||
dirtyData.flags &= ~flags;
|
||||
if (dirtyData.flags === 0) {
|
||||
this._dirtyEntities.delete(entity);
|
||||
}
|
||||
}
|
||||
|
||||
this._stats.totalCleanups++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有脏实体
|
||||
*
|
||||
* @param flags 过滤标记位,只返回包含指定标记的实体
|
||||
* @returns 脏实体数据数组
|
||||
*/
|
||||
public getDirtyEntities(flags: DirtyFlag = DirtyFlag.ALL): DirtyData[] {
|
||||
const result: DirtyData[] = [];
|
||||
|
||||
for (const dirtyData of this._dirtyEntities.values()) {
|
||||
if ((dirtyData.flags & flags) !== 0) {
|
||||
result.push(dirtyData);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量处理脏实体
|
||||
*
|
||||
* 使用时间分片的方式处理脏实体,避免单帧卡顿
|
||||
*/
|
||||
public processDirtyEntities(): void {
|
||||
if (this._isProcessing) return;
|
||||
|
||||
this._isProcessing = true;
|
||||
const startTime = performance.now();
|
||||
|
||||
if (this._processingQueue.length === 0) {
|
||||
this._processingQueue.push(...this._dirtyEntities.values());
|
||||
}
|
||||
|
||||
let processed = 0;
|
||||
while (this._processingQueue.length > 0 && processed < this._batchSize) {
|
||||
const elapsed = performance.now() - startTime;
|
||||
if (elapsed > this._maxProcessingTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
const dirtyData = this._processingQueue.shift()!;
|
||||
this.processEntity(dirtyData);
|
||||
processed++;
|
||||
}
|
||||
|
||||
if (this._processingQueue.length === 0) {
|
||||
this._isProcessing = false;
|
||||
this.onFrameEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加脏标记监听器
|
||||
*
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public addListener(listener: DirtyListener): void {
|
||||
this._listeners.push(listener);
|
||||
|
||||
this._listeners.sort((a, b) => (a.priority || 100) - (b.priority || 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除脏标记监听器
|
||||
*
|
||||
* @param callback 要移除的回调函数
|
||||
*/
|
||||
public removeListener(callback: (dirtyData: DirtyData) => void): void {
|
||||
const index = this._listeners.findIndex(l => l.callback === callback);
|
||||
if (index !== -1) {
|
||||
this._listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始新的帧
|
||||
*/
|
||||
public beginFrame(): void {
|
||||
this._currentFrame++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束当前帧
|
||||
*/
|
||||
public endFrame(): void {
|
||||
if (!this._isProcessing) {
|
||||
this.processDirtyEntities();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): DirtyStats {
|
||||
return {
|
||||
dirtyEntityCount: this._dirtyEntities.size,
|
||||
totalMarkings: this._stats.totalMarkings,
|
||||
totalCleanups: this._stats.totalCleanups,
|
||||
listenerCount: this._listeners.length,
|
||||
avgDirtyPerFrame: this._stats.frameCount > 0 ?
|
||||
this._stats.totalDirtyPerFrame / this._stats.frameCount : 0,
|
||||
estimatedMemoryUsage: this.estimateMemoryUsage()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有脏标记和统计信息
|
||||
*/
|
||||
public clear(): void {
|
||||
this._dirtyEntities.clear();
|
||||
this._processingQueue.length = 0;
|
||||
this._isProcessing = false;
|
||||
this._stats = {
|
||||
totalMarkings: 0,
|
||||
totalCleanups: 0,
|
||||
frameCount: 0,
|
||||
totalDirtyPerFrame: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置批量处理参数
|
||||
*
|
||||
* @param batchSize 每次处理的最大实体数量
|
||||
* @param maxProcessingTime 每帧最大处理时间(毫秒)
|
||||
*/
|
||||
public configureBatchProcessing(batchSize: number, maxProcessingTime: number): void {
|
||||
this._batchSize = batchSize;
|
||||
this._maxProcessingTime = maxProcessingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个脏实体
|
||||
*/
|
||||
private processEntity(dirtyData: DirtyData): void {
|
||||
for (const listener of this._listeners) {
|
||||
if ((dirtyData.flags & listener.flags) !== 0) {
|
||||
try {
|
||||
listener.callback(dirtyData);
|
||||
} catch (error) {
|
||||
DirtyTrackingSystem._logger.error('脏数据监听器错误:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.clearDirty(dirtyData.entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知监听器
|
||||
*/
|
||||
private notifyListeners(dirtyData: DirtyData, newFlags: DirtyFlag): void {
|
||||
for (const listener of this._listeners) {
|
||||
if ((newFlags & listener.flags) !== 0) {
|
||||
try {
|
||||
listener.callback(dirtyData);
|
||||
} catch (error) {
|
||||
DirtyTrackingSystem._logger.error('脏数据监听器通知错误:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 帧结束时的统计更新
|
||||
*/
|
||||
private onFrameEnd(): void {
|
||||
this._stats.frameCount++;
|
||||
this._stats.totalDirtyPerFrame += this._dirtyEntities.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算内存使用量
|
||||
*/
|
||||
private estimateMemoryUsage(): number {
|
||||
let usage = 0;
|
||||
|
||||
usage += this._dirtyEntities.size * 100;
|
||||
usage += this._listeners.length * 50;
|
||||
usage += this._processingQueue.length * 8;
|
||||
|
||||
return usage;
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { Component } from '../../ECS/Component';
|
||||
import { getComponentInstanceTypeName } from '../../ECS/Decorators';
|
||||
|
||||
/**
|
||||
* 调试数据格式化工具
|
||||
*/
|
||||
export class DebugDataFormatter {
|
||||
/**
|
||||
* 格式化属性值
|
||||
*/
|
||||
public static formatPropertyValue(value: any, depth: number = 0): any {
|
||||
// 防止无限递归,限制最大深度
|
||||
if (depth > 5) {
|
||||
return value?.toString() || 'null';
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
// 对于数组,总是返回完整数组,让前端决定如何显示
|
||||
return value.map(item => this.formatPropertyValue(item, depth + 1));
|
||||
} else {
|
||||
// 通用对象处理:提取所有可枚举属性,不限制数量
|
||||
try {
|
||||
const keys = Object.keys(value);
|
||||
if (keys.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const result: any = {};
|
||||
keys.forEach(key => {
|
||||
const propValue = value[key];
|
||||
// 避免循环引用和函数属性
|
||||
if (propValue !== value && typeof propValue !== 'function') {
|
||||
try {
|
||||
result[key] = this.formatPropertyValue(propValue, depth + 1);
|
||||
} catch (error) {
|
||||
// 如果属性访问失败,记录错误信息
|
||||
result[key] = `[访问失败: ${error instanceof Error ? error.message : String(error)}]`;
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
return `[对象解析失败: ${error instanceof Error ? error.message : String(error)}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取组件详细信息
|
||||
*/
|
||||
public static extractComponentDetails(components: Component[]): Array<{
|
||||
typeName: string;
|
||||
properties: Record<string, any>;
|
||||
}> {
|
||||
return components.map((component: Component) => {
|
||||
const componentDetail = {
|
||||
typeName: getComponentInstanceTypeName(component),
|
||||
properties: {} as Record<string, any>
|
||||
};
|
||||
|
||||
// 安全地提取组件属性
|
||||
try {
|
||||
const propertyKeys = Object.keys(component);
|
||||
propertyKeys.forEach(propertyKey => {
|
||||
// 跳过私有属性和实体引用,避免循环引用
|
||||
if (!propertyKey.startsWith('_') && propertyKey !== 'entity') {
|
||||
const propertyValue = (component as any)[propertyKey];
|
||||
if (propertyValue !== undefined && propertyValue !== null) {
|
||||
componentDetail.properties[propertyKey] = this.formatPropertyValue(propertyValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
componentDetail.properties['_extractionError'] = '属性提取失败';
|
||||
}
|
||||
|
||||
return componentDetail;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算对象大小
|
||||
*/
|
||||
public static calculateObjectSize(obj: any, excludeKeys: string[] = []): number {
|
||||
if (!obj || typeof obj !== 'object') return 0;
|
||||
|
||||
let size = 0;
|
||||
const visited = new WeakSet();
|
||||
|
||||
const calculate = (item: any): number => {
|
||||
if (!item || typeof item !== 'object' || visited.has(item)) return 0;
|
||||
visited.add(item);
|
||||
|
||||
let itemSize = 0;
|
||||
|
||||
try {
|
||||
for (const key in item) {
|
||||
if (excludeKeys.includes(key)) continue;
|
||||
|
||||
const value = item[key];
|
||||
itemSize += key.length * 2; // key size
|
||||
|
||||
if (typeof value === 'string') {
|
||||
itemSize += value.length * 2;
|
||||
} else if (typeof value === 'number') {
|
||||
itemSize += 8;
|
||||
} else if (typeof value === 'boolean') {
|
||||
itemSize += 4;
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
itemSize += calculate(value);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略无法访问的属性
|
||||
}
|
||||
|
||||
return itemSize;
|
||||
};
|
||||
|
||||
return calculate(obj);
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
import { ComponentIndex } from '../../../src/ECS/Core/ComponentIndex';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
|
||||
// 测试组件类
|
||||
class TransformComponent extends Component {
|
||||
constructor(public x: number = 0, public y: number = 0, public rotation: number = 0) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
class PhysicsComponent extends Component {
|
||||
constructor(public mass: number = 1, public friction: number = 0.1) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
class AudioComponent extends Component {
|
||||
constructor(public volume: number = 1.0, public muted: boolean = false) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
class GraphicsComponent extends Component {
|
||||
constructor(public color: string = '#ffffff', public alpha: number = 1.0) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
describe('ComponentIndex with SparseSet', () => {
|
||||
let componentIndex: ComponentIndex;
|
||||
let entities: Entity[];
|
||||
|
||||
beforeEach(() => {
|
||||
componentIndex = new ComponentIndex();
|
||||
entities = [];
|
||||
|
||||
// 创建测试实体
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const entity = new Entity(`testEntity${i}`, i);
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
// entity0: Transform
|
||||
entities[0].addComponent(new TransformComponent(10, 20, 30));
|
||||
|
||||
// entity1: Transform + Physics
|
||||
entities[1].addComponent(new TransformComponent(40, 50, 60));
|
||||
entities[1].addComponent(new PhysicsComponent(2.0, 0.2));
|
||||
|
||||
// entity2: Physics + Audio
|
||||
entities[2].addComponent(new PhysicsComponent(3.0, 0.3));
|
||||
entities[2].addComponent(new AudioComponent(0.8, false));
|
||||
|
||||
// entity3: Transform + Physics + Audio
|
||||
entities[3].addComponent(new TransformComponent(70, 80, 90));
|
||||
entities[3].addComponent(new PhysicsComponent(4.0, 0.4));
|
||||
entities[3].addComponent(new AudioComponent(0.6, true));
|
||||
|
||||
// entity4: Graphics
|
||||
entities[4].addComponent(new GraphicsComponent('#ff0000', 0.5));
|
||||
|
||||
// 添加所有实体到索引
|
||||
entities.forEach(entity => componentIndex.addEntity(entity));
|
||||
});
|
||||
|
||||
describe('基本索引操作', () => {
|
||||
it('应该正确添加实体到索引', () => {
|
||||
const stats = componentIndex.getStats();
|
||||
expect(stats.size).toBe(5);
|
||||
});
|
||||
|
||||
it('应该能移除实体', () => {
|
||||
componentIndex.removeEntity(entities[0]);
|
||||
|
||||
const stats = componentIndex.getStats();
|
||||
expect(stats.size).toBe(4);
|
||||
|
||||
const transformEntities = componentIndex.query(TransformComponent);
|
||||
expect(transformEntities.has(entities[0])).toBe(false);
|
||||
});
|
||||
|
||||
it('应该能清空索引', () => {
|
||||
componentIndex.clear();
|
||||
|
||||
const stats = componentIndex.getStats();
|
||||
expect(stats.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('单组件查询', () => {
|
||||
it('应该能查询Transform组件', () => {
|
||||
const result = componentIndex.query(TransformComponent);
|
||||
|
||||
expect(result.size).toBe(3);
|
||||
expect(result.has(entities[0])).toBe(true);
|
||||
expect(result.has(entities[1])).toBe(true);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能查询Physics组件', () => {
|
||||
const result = componentIndex.query(PhysicsComponent);
|
||||
|
||||
expect(result.size).toBe(3);
|
||||
expect(result.has(entities[1])).toBe(true);
|
||||
expect(result.has(entities[2])).toBe(true);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能查询Audio组件', () => {
|
||||
const result = componentIndex.query(AudioComponent);
|
||||
|
||||
expect(result.size).toBe(2);
|
||||
expect(result.has(entities[2])).toBe(true);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能查询Graphics组件', () => {
|
||||
const result = componentIndex.query(GraphicsComponent);
|
||||
|
||||
expect(result.size).toBe(1);
|
||||
expect(result.has(entities[4])).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('多组件AND查询', () => {
|
||||
it('应该能查询Transform+Physics组件', () => {
|
||||
const result = componentIndex.queryMultiple([TransformComponent, PhysicsComponent], 'AND');
|
||||
|
||||
expect(result.size).toBe(2);
|
||||
expect(result.has(entities[1])).toBe(true);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能查询Physics+Audio组件', () => {
|
||||
const result = componentIndex.queryMultiple([PhysicsComponent, AudioComponent], 'AND');
|
||||
|
||||
expect(result.size).toBe(2);
|
||||
expect(result.has(entities[2])).toBe(true);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能查询Transform+Physics+Audio组件', () => {
|
||||
const result = componentIndex.queryMultiple([TransformComponent, PhysicsComponent, AudioComponent], 'AND');
|
||||
|
||||
expect(result.size).toBe(1);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该处理不存在的组合', () => {
|
||||
const result = componentIndex.queryMultiple([TransformComponent, GraphicsComponent], 'AND');
|
||||
expect(result.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('多组件OR查询', () => {
|
||||
it('应该能查询Transform或Graphics组件', () => {
|
||||
const result = componentIndex.queryMultiple([TransformComponent, GraphicsComponent], 'OR');
|
||||
|
||||
expect(result.size).toBe(4);
|
||||
expect(result.has(entities[0])).toBe(true);
|
||||
expect(result.has(entities[1])).toBe(true);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
expect(result.has(entities[4])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能查询Audio或Graphics组件', () => {
|
||||
const result = componentIndex.queryMultiple([AudioComponent, GraphicsComponent], 'OR');
|
||||
|
||||
expect(result.size).toBe(3);
|
||||
expect(result.has(entities[2])).toBe(true);
|
||||
expect(result.has(entities[3])).toBe(true);
|
||||
expect(result.has(entities[4])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能查询所有组件类型', () => {
|
||||
const result = componentIndex.queryMultiple([
|
||||
TransformComponent,
|
||||
PhysicsComponent,
|
||||
AudioComponent,
|
||||
GraphicsComponent
|
||||
], 'OR');
|
||||
|
||||
expect(result.size).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况', () => {
|
||||
it('应该处理空组件列表', () => {
|
||||
const andResult = componentIndex.queryMultiple([], 'AND');
|
||||
const orResult = componentIndex.queryMultiple([], 'OR');
|
||||
|
||||
expect(andResult.size).toBe(0);
|
||||
expect(orResult.size).toBe(0);
|
||||
});
|
||||
|
||||
it('应该处理单组件查询', () => {
|
||||
const result = componentIndex.queryMultiple([TransformComponent], 'AND');
|
||||
const directResult = componentIndex.query(TransformComponent);
|
||||
|
||||
expect(result.size).toBe(directResult.size);
|
||||
expect([...result]).toEqual([...directResult]);
|
||||
});
|
||||
|
||||
it('应该处理重复添加实体', () => {
|
||||
const initialStats = componentIndex.getStats();
|
||||
|
||||
componentIndex.addEntity(entities[0]);
|
||||
|
||||
const finalStats = componentIndex.getStats();
|
||||
expect(finalStats.size).toBe(initialStats.size);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能统计', () => {
|
||||
it('应该跟踪查询统计信息', () => {
|
||||
// 执行一些查询
|
||||
componentIndex.query(TransformComponent);
|
||||
componentIndex.queryMultiple([PhysicsComponent, AudioComponent], 'AND');
|
||||
componentIndex.queryMultiple([TransformComponent, GraphicsComponent], 'OR');
|
||||
|
||||
const stats = componentIndex.getStats();
|
||||
|
||||
expect(stats.queryCount).toBe(3);
|
||||
expect(stats.avgQueryTime).toBeGreaterThanOrEqual(0);
|
||||
expect(stats.memoryUsage).toBeGreaterThan(0);
|
||||
expect(stats.lastUpdated).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('应该提供准确的内存使用信息', () => {
|
||||
const stats = componentIndex.getStats();
|
||||
|
||||
expect(stats.memoryUsage).toBeGreaterThan(0);
|
||||
expect(stats.size).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('动态实体管理', () => {
|
||||
it('应该处理实体组件变化', () => {
|
||||
// 为实体添加新组件
|
||||
entities[4].addComponent(new TransformComponent(100, 200, 300));
|
||||
componentIndex.addEntity(entities[4]); // 重新添加以更新索引
|
||||
|
||||
const result = componentIndex.query(TransformComponent);
|
||||
expect(result.has(entities[4])).toBe(true);
|
||||
expect(result.size).toBe(4);
|
||||
});
|
||||
|
||||
it('应该处理实体组件移除', () => {
|
||||
// 验证初始状态
|
||||
const initialResult = componentIndex.query(TransformComponent);
|
||||
expect(initialResult.has(entities[1])).toBe(true);
|
||||
expect(initialResult.size).toBe(3);
|
||||
|
||||
// 创建一个没有Transform组件的新实体,模拟组件移除后的状态
|
||||
const modifiedEntity = new Entity('modifiedEntity', entities[1].id);
|
||||
modifiedEntity.addComponent(new PhysicsComponent(2.0, 0.2)); // 只保留Physics组件
|
||||
|
||||
// 从索引中移除原实体,添加修改后的实体
|
||||
componentIndex.removeEntity(entities[1]);
|
||||
componentIndex.addEntity(modifiedEntity);
|
||||
|
||||
const result = componentIndex.query(TransformComponent);
|
||||
expect(result.has(entities[1])).toBe(false);
|
||||
expect(result.has(modifiedEntity)).toBe(false);
|
||||
expect(result.size).toBe(2);
|
||||
|
||||
// 验证Physics查询仍然能找到修改后的实体
|
||||
const physicsResult = componentIndex.query(PhysicsComponent);
|
||||
expect(physicsResult.has(modifiedEntity)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('复杂查询场景', () => {
|
||||
it('应该支持复杂的组合查询', () => {
|
||||
// 查询有Transform和Physics但没有Audio的实体
|
||||
const withTransformPhysics = componentIndex.queryMultiple([TransformComponent, PhysicsComponent], 'AND');
|
||||
const withAudio = componentIndex.queryMultiple([AudioComponent], 'OR');
|
||||
|
||||
const withoutAudio = new Set([...withTransformPhysics].filter(e => !withAudio.has(e)));
|
||||
|
||||
expect(withoutAudio.size).toBe(1);
|
||||
expect(withoutAudio.has(entities[1])).toBe(true);
|
||||
});
|
||||
|
||||
it('应该支持性能敏感的批量查询', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
// 执行大量查询
|
||||
for (let i = 0; i < 100; i++) {
|
||||
componentIndex.query(TransformComponent);
|
||||
componentIndex.queryMultiple([PhysicsComponent, AudioComponent], 'AND');
|
||||
componentIndex.queryMultiple([TransformComponent, GraphicsComponent], 'OR');
|
||||
}
|
||||
|
||||
const duration = performance.now() - startTime;
|
||||
|
||||
// 应该在合理时间内完成
|
||||
expect(duration).toBeLessThan(100);
|
||||
|
||||
const stats = componentIndex.getStats();
|
||||
expect(stats.queryCount).toBe(300);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user