Compare commits

..
Author SHA1 Message Date
YHHandGitHub ddc7a7750e Chore/lint fixes (#212)
* fix(eslint): 修复装饰器缩进配置

* fix(eslint): 修复装饰器缩进配置

* chore: 删除未使用的导入

* chore(lint): 移除未使用的导入和变量

* chore(lint): 修复editor-app中未使用的函数参数

* chore(lint): 修复未使用的赋值变量

* chore(eslint): 将所有错误级别改为警告以通过CI

* fix(codeql): 修复GitHub Advanced Security检测到的问题
2025-11-02 23:50:41 +08:00
YHH 50a01d9dd3 chore: 统一并强化ESLint配置规则 2025-11-02 12:45:47 +08:00
YHH 793aad0a5e chore: 移除旧版ESLint配置并更新子模块 2025-11-02 12:22:27 +08:00
YHHandGitHub 9c1bf8dbed refactor(core): 移除全局EventBus,实现场景级事件隔离 (#211) 2025-11-01 18:19:23 +08:00
YHHandGitHub 620f3eecc7 style(core): ESLint自动修复代码格式问题 (#210) 2025-11-01 17:41:50 +08:00
YHHandGitHub 4355538d8d refactor(core): 重构Scene和Entity的代码质量,消除所有Lint警告 (#209) 2025-11-01 17:18:12 +08:00
YHHandGitHub 3ad5dc9ca3 refactor(core): 改进事件系统类型安全并消除 ESLint 警告 (#208) 2025-11-01 16:12:18 +08:00
YHHandGitHub 57c7e7be3f feat(core):统一 Core 库的命名规范和代码风格 (#207) 2025-11-01 10:23:46 +08:00
213 changed files with 12923 additions and 13161 deletions
-50
View File
@@ -1,50 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "single", { "avoidEscape": true }],
"indent": ["error", 4, { "SwitchCase": 1 }],
"no-trailing-spaces": "error",
"eol-last": ["error", "always"],
"comma-dangle": ["error", "none"],
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": ["error", "never"],
"arrow-parens": ["error", "always"],
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1 }],
"no-console": "off",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-return": "warn",
"@typescript-eslint/no-unsafe-argument": "warn",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-non-null-assertion": "off"
},
"ignorePatterns": [
"node_modules/",
"dist/",
"bin/",
"build/",
"coverage/",
"thirdparty/",
"examples/lawn-mower-demo/",
"extensions/",
"*.min.js",
"*.d.ts"
]
}
+28 -23
View File
@@ -10,38 +10,43 @@ export default [
parser: tseslint.parser, parser: tseslint.parser,
parserOptions: { parserOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
sourceType: 'module' sourceType: 'module',
project: true,
tsconfigRootDir: import.meta.dirname
} }
}, },
rules: { rules: {
'semi': 'warn', 'semi': ['warn', 'always'],
'quotes': 'warn', 'quotes': ['warn', 'single', { avoidEscape: true }],
'indent': 'off', 'indent': ['warn', 4, {
SwitchCase: 1,
ignoredNodes: [
'PropertyDefinition[decorators.length > 0]',
'TSTypeParameterInstantiation'
]
}],
'no-trailing-spaces': 'warn', 'no-trailing-spaces': 'warn',
'eol-last': 'warn', 'eol-last': ['warn', 'always'],
'comma-dangle': 'warn', 'comma-dangle': ['warn', 'never'],
'object-curly-spacing': 'warn', 'object-curly-spacing': ['warn', 'always'],
'array-bracket-spacing': 'warn', 'array-bracket-spacing': ['warn', 'never'],
'arrow-parens': 'warn', 'arrow-parens': ['warn', 'always'],
'prefer-const': 'warn', 'no-multiple-empty-lines': ['warn', { max: 2, maxEOF: 1 }],
'no-multiple-empty-lines': 'warn',
'no-console': 'off', 'no-console': 'off',
'no-empty': 'warn',
'no-case-declarations': 'warn',
'no-useless-catch': 'warn',
'no-prototype-builtins': 'warn',
'@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/no-unsafe-function-type': 'warn', '@typescript-eslint/no-unsafe-function-type': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off'
'@typescript-eslint/no-require-imports': 'warn',
'@typescript-eslint/no-this-alias': 'warn',
'no-case-declarations': 'warn',
'no-prototype-builtins': 'warn',
'no-empty': 'warn',
'no-useless-catch': 'warn'
} }
}, },
{ {
@@ -216,7 +216,7 @@ export class BehaviorTreeRuntimeComponent extends Component {
*/ */
unobserveBlackboard(nodeId: string): void { unobserveBlackboard(nodeId: string): void {
for (const observers of this.blackboardObservers.values()) { for (const observers of this.blackboardObservers.values()) {
const index = observers.findIndex(o => o.nodeId === nodeId); const index = observers.findIndex((o) => o.nodeId === nodeId);
if (index !== -1) { if (index !== -1) {
observers.splice(index, 1); observers.splice(index, 1);
} }
@@ -49,11 +49,11 @@ export class NodeMetadataRegistry {
} }
static getByCategory(category: string): NodeMetadata[] { static getByCategory(category: string): NodeMetadata[] {
return this.getAllMetadata().filter(m => m.category === category); return this.getAllMetadata().filter((m) => m.category === category);
} }
static getByNodeType(nodeType: NodeType): NodeMetadata[] { static getByNodeType(nodeType: NodeType): NodeMetadata[] {
return this.getAllMetadata().filter(m => m.nodeType === nodeType); return this.getAllMetadata().filter((m) => m.nodeType === nodeType);
} }
static getImplementationType(executorClass: Function): string | undefined { static getImplementationType(executorClass: Function): string | undefined {
@@ -131,7 +131,7 @@ export class BehaviorTreeAssetValidator {
errors.push('Missing or invalid nodes array'); errors.push('Missing or invalid nodes array');
} else { } else {
const nodeIds = new Set<string>(); const nodeIds = new Set<string>();
const rootNode = asset.nodes.find(n => n.id === asset.rootNodeId); const rootNode = asset.nodes.find((n) => n.id === asset.rootNodeId);
if (!rootNode) { if (!rootNode) {
errors.push(`Root node '${asset.rootNodeId}' not found in nodes array`); errors.push(`Root node '${asset.rootNodeId}' not found in nodes array`);
@@ -157,7 +157,7 @@ export class BehaviorTreeAssetValidator {
// 检查子节点引用 // 检查子节点引用
if (node.children) { if (node.children) {
for (const childId of node.children) { for (const childId of node.children) {
if (!asset.nodes.find(n => n.id === childId)) { if (!asset.nodes.find((n) => n.id === childId)) {
errors.push(`Node ${node.id} references non-existent child: ${childId}`); errors.push(`Node ${node.id} references non-existent child: ${childId}`);
} }
} }
@@ -167,7 +167,7 @@ export class BehaviorTreeAssetValidator {
// 检查是否有孤立节点 // 检查是否有孤立节点
const referencedNodes = new Set<string>([asset.rootNodeId]); const referencedNodes = new Set<string>([asset.rootNodeId]);
const collectReferencedNodes = (nodeId: string) => { const collectReferencedNodes = (nodeId: string) => {
const node = asset.nodes.find(n => n.id === nodeId); const node = asset.nodes.find((n) => n.id === nodeId);
if (node && node.children) { if (node && node.children) {
for (const childId of node.children) { for (const childId of node.children) {
referencedNodes.add(childId); referencedNodes.add(childId);
@@ -206,8 +206,8 @@ export class BehaviorTreeAssetValidator {
// 检查属性绑定 // 检查属性绑定
if (asset.propertyBindings && Array.isArray(asset.propertyBindings)) { if (asset.propertyBindings && Array.isArray(asset.propertyBindings)) {
const nodeIds = new Set(asset.nodes.map(n => n.id)); const nodeIds = new Set(asset.nodes.map((n) => n.id));
const varNames = new Set(asset.blackboard?.map(v => v.name) || []); const varNames = new Set(asset.blackboard?.map((v) => v.name) || []);
for (const binding of asset.propertyBindings) { for (const binding of asset.propertyBindings) {
if (!nodeIds.has(binding.nodeId)) { if (!nodeIds.has(binding.nodeId)) {
@@ -276,7 +276,7 @@ export class BehaviorTreeAssetValidator {
// 计算最大深度 // 计算最大深度
const getDepth = (nodeId: string, currentDepth: number = 0): number => { const getDepth = (nodeId: string, currentDepth: number = 0): number => {
const node = asset.nodes.find(n => n.id === nodeId); const node = asset.nodes.find((n) => n.id === nodeId);
if (!node || !node.children || node.children.length === 0) { if (!node || !node.children || node.children.length === 0) {
return currentDepth; return currentDepth;
} }
@@ -134,7 +134,7 @@ export class EditorFormatConverter {
* 查找根节点 * 查找根节点
*/ */
private static findRootNode(nodes: EditorNode[]): EditorNode | null { private static findRootNode(nodes: EditorNode[]): EditorNode | null {
return nodes.find(node => return nodes.find((node) =>
node.template.category === '根节点' || node.template.category === '根节点' ||
node.data.nodeType === 'root' node.data.nodeType === 'root'
) || null; ) || null;
@@ -144,7 +144,7 @@ export class EditorFormatConverter {
* 转换节点列表 * 转换节点列表
*/ */
private static convertNodes(editorNodes: EditorNode[]): BehaviorTreeNodeData[] { private static convertNodes(editorNodes: EditorNode[]): BehaviorTreeNodeData[] {
return editorNodes.map(node => this.convertNode(node)); return editorNodes.map((node) => this.convertNode(node));
} }
/** /**
@@ -211,13 +211,13 @@ export class EditorFormatConverter {
blackboard: BlackboardVariableDefinition[] blackboard: BlackboardVariableDefinition[]
): PropertyBinding[] { ): PropertyBinding[] {
const bindings: PropertyBinding[] = []; const bindings: PropertyBinding[] = [];
const blackboardVarNames = new Set(blackboard.map(v => v.name)); const blackboardVarNames = new Set(blackboard.map((v) => v.name));
const propertyConnections = connections.filter(conn => conn.connectionType === 'property'); const propertyConnections = connections.filter((conn) => conn.connectionType === 'property');
for (const conn of propertyConnections) { for (const conn of propertyConnections) {
const fromNode = nodes.find(n => n.id === conn.from); const fromNode = nodes.find((n) => n.id === conn.from);
const toNode = nodes.find(n => n.id === conn.to); const toNode = nodes.find((n) => n.id === conn.to);
if (!fromNode || !toNode || !conn.toProperty) { if (!fromNode || !toNode || !conn.toProperty) {
logger.warn(`跳过无效的属性连接: from=${conn.from}, to=${conn.to}`); logger.warn(`跳过无效的属性连接: from=${conn.from}, to=${conn.to}`);
@@ -154,14 +154,14 @@ export class NodeTemplates {
*/ */
static getAllTemplates(): NodeTemplate[] { static getAllTemplates(): NodeTemplate[] {
const allMetadata = NodeMetadataRegistry.getAllMetadata(); const allMetadata = NodeMetadataRegistry.getAllMetadata();
return allMetadata.map(metadata => this.convertMetadataToTemplate(metadata)); return allMetadata.map((metadata) => this.convertMetadataToTemplate(metadata));
} }
/** /**
* 根据类型和子类型获取模板 * 根据类型和子类型获取模板
*/ */
static getTemplate(type: NodeType, subType: string): NodeTemplate | undefined { static getTemplate(type: NodeType, subType: string): NodeTemplate | undefined {
return this.getAllTemplates().find(t => { return this.getAllTemplates().find((t) => {
if (t.type !== type) return false; if (t.type !== type) return false;
const config: any = t.defaultConfig; const config: any = t.defaultConfig;
@@ -266,7 +266,7 @@ export class NodeTemplates {
} }
if (field.options) { if (field.options) {
property.options = field.options.map(opt => ({ property.options = field.options.map((opt) => ({
label: opt, label: opt,
value: opt value: opt
})); }));
@@ -1,311 +0,0 @@
import { World, Scene, Entity } from '@esengine/ecs-framework';
import { AssetLoadingManager } from '../src/Services/AssetLoadingManager';
import {
LoadingState,
TimeoutError,
CircularDependencyError,
EntityDestroyedError
} from '../src/Services/AssetLoadingTypes';
describe('AssetLoadingManager', () => {
let manager: AssetLoadingManager;
let world: World;
let scene: Scene;
let parentEntity: Entity;
beforeEach(() => {
manager = new AssetLoadingManager();
world = new World();
scene = world.createScene('test');
parentEntity = scene.createEntity('parent');
});
afterEach(() => {
manager.dispose();
parentEntity.destroy();
});
describe('基本加载功能', () => {
test('成功加载资产', async () => {
const mockEntity = scene.createEntity('loaded');
const loader = jest.fn().mockResolvedValue(mockEntity);
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader
);
expect(handle.getState()).toBe(LoadingState.Loading);
const result = await handle.promise;
expect(result).toBe(mockEntity);
expect(handle.getState()).toBe(LoadingState.Loaded);
expect(loader).toHaveBeenCalledTimes(1);
});
test('加载失败', async () => {
const mockError = new Error('Load failed');
const loader = jest.fn().mockRejectedValue(mockError);
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader,
{ maxRetries: 0 }
);
await expect(handle.promise).rejects.toThrow('Load failed');
expect(handle.getState()).toBe(LoadingState.Failed);
expect(handle.getError()).toBe(mockError);
});
});
describe('超时机制', () => {
test('加载超时', async () => {
const loader = jest.fn().mockImplementation(() =>
new Promise(resolve => setTimeout(resolve, 10000))
);
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader,
{ timeoutMs: 100, maxRetries: 0 }
);
await expect(handle.promise).rejects.toThrow(TimeoutError);
expect(handle.getState()).toBe(LoadingState.Timeout);
});
test('超时前完成', async () => {
const mockEntity = scene.createEntity('loaded');
const loader = jest.fn().mockImplementation(() =>
new Promise(resolve => setTimeout(() => resolve(mockEntity), 50))
);
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader,
{ timeoutMs: 200 }
);
const result = await handle.promise;
expect(result).toBe(mockEntity);
expect(handle.getState()).toBe(LoadingState.Loaded);
});
});
describe('重试机制', () => {
test('失败后自动重试', async () => {
const mockEntity = scene.createEntity('loaded');
let attemptCount = 0;
const loader = jest.fn().mockImplementation(() => {
attemptCount++;
if (attemptCount < 3) {
return Promise.reject(new Error('Temporary error'));
}
return Promise.resolve(mockEntity);
});
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader,
{ maxRetries: 3 }
);
const result = await handle.promise;
expect(result).toBe(mockEntity);
expect(loader).toHaveBeenCalledTimes(3);
expect(handle.getState()).toBe(LoadingState.Loaded);
});
test('重试次数用尽后失败', async () => {
const loader = jest.fn().mockRejectedValue(new Error('Persistent error'));
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader,
{ maxRetries: 2 }
);
await expect(handle.promise).rejects.toThrow('Persistent error');
expect(loader).toHaveBeenCalledTimes(3); // 初始 + 2次重试
expect(handle.getState()).toBe(LoadingState.Failed);
});
});
describe('循环引用检测', () => {
test('检测直接循环引用', () => {
const loader = jest.fn().mockResolvedValue(scene.createEntity('loaded'));
// 先加载 assetA
const handleA = manager.startLoading(
'assetA',
parentEntity,
loader,
{ parentAssetId: undefined }
);
expect(handleA.getState()).toBe(LoadingState.Loading);
// 尝试在 assetA 的上下文中加载 assetB
// assetB 又尝试加载 assetA(循环)
expect(() => {
manager.startLoading(
'assetB',
parentEntity,
loader,
{ parentAssetId: 'assetB' } // assetB 的父是 assetB(自我循环)
);
}).toThrow(CircularDependencyError);
});
test('不误报非循环引用', () => {
const loader = jest.fn().mockResolvedValue(scene.createEntity('loaded'));
// assetA 加载 assetB(正常)
const handleA = manager.startLoading(
'assetA',
parentEntity,
loader
);
// assetB 加载 assetC(正常,不是循环)
expect(() => {
manager.startLoading(
'assetC',
parentEntity,
loader,
{ parentAssetId: 'assetB' }
);
}).not.toThrow();
});
});
describe('实体生命周期安全', () => {
test('实体销毁后取消加载', async () => {
const loader = jest.fn().mockImplementation(() =>
new Promise(resolve => setTimeout(resolve, 100))
);
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader
);
// 销毁实体
parentEntity.destroy();
// 等待一小段时间让检测生效
await new Promise(resolve => setTimeout(resolve, 50));
await expect(handle.promise).rejects.toThrow(EntityDestroyedError);
expect(handle.getState()).toBe(LoadingState.Cancelled);
});
});
describe('状态查询', () => {
test('获取加载进度', async () => {
const mockEntity = scene.createEntity('loaded');
const loader = jest.fn().mockImplementation(() =>
new Promise(resolve => setTimeout(() => resolve(mockEntity), 100))
);
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader
);
const progress = handle.getProgress();
expect(progress.state).toBe(LoadingState.Loading);
expect(progress.elapsedMs).toBeGreaterThanOrEqual(0);
expect(progress.retryCount).toBe(0);
expect(progress.maxRetries).toBe(3);
await handle.promise;
});
test('获取统计信息', () => {
const loader = jest.fn().mockResolvedValue(scene.createEntity('loaded'));
manager.startLoading('asset1', parentEntity, loader);
manager.startLoading('asset2', parentEntity, loader);
const stats = manager.getStats();
expect(stats.totalTasks).toBe(2);
expect(stats.loadingTasks).toBe(2);
});
test('获取正在加载的资产列表', () => {
const loader = jest.fn().mockResolvedValue(scene.createEntity('loaded'));
manager.startLoading('asset1', parentEntity, loader);
manager.startLoading('asset2', parentEntity, loader);
const loadingAssets = manager.getLoadingAssets();
expect(loadingAssets).toContain('asset1');
expect(loadingAssets).toContain('asset2');
expect(loadingAssets.length).toBe(2);
});
});
describe('任务管理', () => {
test('取消加载任务', () => {
const loader = jest.fn().mockImplementation(() =>
new Promise(resolve => setTimeout(resolve, 1000))
);
const handle = manager.startLoading(
'test-asset',
parentEntity,
loader
);
expect(handle.getState()).toBe(LoadingState.Loading);
handle.cancel();
expect(handle.getState()).toBe(LoadingState.Cancelled);
});
test('清空所有任务', async () => {
const loader = jest.fn().mockResolvedValue(scene.createEntity('loaded'));
manager.startLoading('asset1', parentEntity, loader);
manager.startLoading('asset2', parentEntity, loader);
expect(manager.getLoadingAssets().length).toBe(2);
manager.clear();
expect(manager.getLoadingAssets().length).toBe(0);
});
test('复用已存在的加载任务', () => {
const loader = jest.fn().mockResolvedValue(scene.createEntity('loaded'));
const handle1 = manager.startLoading('test-asset', parentEntity, loader);
const handle2 = manager.startLoading('test-asset', parentEntity, loader);
// 应该返回同一个任务
expect(handle1.assetId).toBe(handle2.assetId);
expect(loader).toHaveBeenCalledTimes(1); // 只加载一次
});
});
});
-50
View File
@@ -1,50 +0,0 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
eslint.configs.recommended,
...tseslint.configs.recommended,
{
files: ['src/**/*.{ts,tsx}'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: './tsconfig.json'
}
},
rules: {
'semi': ['error', 'always'],
'quotes': ['error', 'single', { avoidEscape: true }],
'indent': ['error', 4, { SwitchCase: 1 }],
'no-trailing-spaces': 'error',
'eol-last': ['error', 'always'],
'comma-dangle': ['error', 'never'],
'object-curly-spacing': ['error', 'always'],
'array-bracket-spacing': ['error', 'never'],
'arrow-parens': ['error', 'always'],
'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1 }],
'no-console': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-non-null-assertion': 'off'
}
},
{
ignores: [
'node_modules/**',
'dist/**',
'bin/**',
'coverage/**',
'**/*.min.js',
'**/*.d.ts'
]
}
];
+5 -5
View File
@@ -329,8 +329,8 @@ export class Core {
*/ */
public static setScene<T extends IScene>(scene: T): T { public static setScene<T extends IScene>(scene: T): T {
if (!this._instance) { if (!this._instance) {
Core._logger.warn("Core实例未创建,请先调用Core.create()"); Core._logger.warn('Core实例未创建,请先调用Core.create()');
throw new Error("Core实例未创建"); throw new Error('Core实例未创建');
} }
return this._instance._sceneManager.setScene(scene); return this._instance._sceneManager.setScene(scene);
@@ -387,7 +387,7 @@ export class Core {
*/ */
public static loadScene<T extends IScene>(scene: T): void { public static loadScene<T extends IScene>(scene: T): void {
if (!this._instance) { if (!this._instance) {
Core._logger.warn("Core实例未创建,请先调用Core.create()"); Core._logger.warn('Core实例未创建,请先调用Core.create()');
return; return;
} }
@@ -422,7 +422,7 @@ export class Core {
*/ */
public static update(deltaTime: number): void { public static update(deltaTime: number): void {
if (!this._instance) { if (!this._instance) {
Core._logger.warn("Core实例未创建,请先调用Core.create()"); Core._logger.warn('Core实例未创建,请先调用Core.create()');
return; return;
} }
@@ -472,7 +472,7 @@ export class Core {
*/ */
public static enableDebug(config: IECSDebugConfig): void { public static enableDebug(config: IECSDebugConfig): void {
if (!this._instance) { if (!this._instance) {
Core._logger.warn("Core实例未创建,请先调用Core.create()"); Core._logger.warn('Core实例未创建,请先调用Core.create()');
return; return;
} }
+3 -3
View File
@@ -123,7 +123,7 @@ export function Updatable(priority: number = 0): ClassDecorator {
if (!prototype || typeof prototype.update !== 'function') { if (!prototype || typeof prototype.update !== 'function') {
throw new Error( throw new Error(
`@Updatable() decorator requires class ${target.name} to implement IUpdatable interface with update() method. ` + `@Updatable() decorator requires class ${target.name} to implement IUpdatable interface with update() method. ` +
`Please add 'implements IUpdatable' and define update(deltaTime?: number): void method.` 'Please add \'implements IUpdatable\' and define update(deltaTime?: number): void method.'
); );
} }
@@ -249,7 +249,7 @@ export function createInstance<T>(
if (typeof serviceType === 'string' || typeof serviceType === 'symbol') { if (typeof serviceType === 'string' || typeof serviceType === 'symbol') {
// 字符串或Symbol类型的服务标识 // 字符串或Symbol类型的服务标识
throw new Error( throw new Error(
`String and Symbol service identifiers are not yet supported in constructor injection. ` + 'String and Symbol service identifiers are not yet supported in constructor injection. ' +
`Please use class types for ${constructor.name} parameter ${i}` `Please use class types for ${constructor.name} parameter ${i}`
); );
} else { } else {
@@ -338,7 +338,7 @@ export function registerInjectable<T extends IService>(
if (!isInjectable(serviceType)) { if (!isInjectable(serviceType)) {
throw new Error( throw new Error(
`${serviceType.name} is not marked as @Injectable(). ` + `${serviceType.name} is not marked as @Injectable(). ` +
`Please add @Injectable() decorator to the class.` 'Please add @Injectable() decorator to the class.'
); );
} }
+2 -2
View File
@@ -240,7 +240,7 @@ export class ServiceContainer {
// 检测循环依赖 // 检测循环依赖
if (this._resolving.has(type as ServiceType<IService>)) { if (this._resolving.has(type as ServiceType<IService>)) {
const chain = Array.from(this._resolving).map(t => t.name).join(' -> '); const chain = Array.from(this._resolving).map((t) => t.name).join(' -> ');
throw new Error(`Circular dependency detected: ${chain} -> ${type.name}`); throw new Error(`Circular dependency detected: ${chain} -> ${type.name}`);
} }
@@ -337,7 +337,7 @@ export class ServiceContainer {
// 如果有单例实例,调用 dispose // 如果有单例实例,调用 dispose
if (registration.instance) { if (registration.instance) {
// 从可更新列表中移除 // 从可更新列表中移除
const index = this._updatableServices.findIndex(item => item.instance === registration.instance); const index = this._updatableServices.findIndex((item) => item.instance === registration.instance);
if (index !== -1) { if (index !== -1) {
this._updatableServices.splice(index, 1); this._updatableServices.splice(index, 1);
} }
+2 -2
View File
@@ -36,7 +36,7 @@ export abstract class Component implements IComponent {
* *
* 用于为每个组件分配唯一的ID。 * 用于为每个组件分配唯一的ID。
*/ */
public static _idGenerator: number = 0; private static idGenerator: number = 0;
/** /**
* 组件唯一标识符 * 组件唯一标识符
@@ -58,7 +58,7 @@ export abstract class Component implements IComponent {
* 自动分配唯一ID给组件。 * 自动分配唯一ID给组件。
*/ */
constructor() { constructor() {
this.id = Component._idGenerator++; this.id = Component.idGenerator++;
} }
/** /**
+13 -13
View File
@@ -1,7 +1,7 @@
import { Entity } from '../Entity'; import { Entity } from '../Entity';
import { ComponentType, ComponentRegistry } from './ComponentStorage'; import { ComponentType, ComponentRegistry } from './ComponentStorage';
import { BitMask64Data, BitMask64Utils } from "../Utils"; import { BitMask64Data, BitMask64Utils } from '../Utils';
import { BitMaskHashMap } from "../Utils/BitMaskHashMap"; import { BitMaskHashMap } from '../Utils/BitMaskHashMap';
/** /**
* 原型标识符 * 原型标识符
@@ -32,7 +32,7 @@ export interface ArchetypeQueryResult {
/** /**
* Archetype系统 * Archetype系统
* *
* 根据实体的组件组合将实体分组到不同的原型中,提供高效的查询性能。 * 根据实体的组件组合将实体分组到不同的原型中,提供高效的查询性能。
*/ */
export class ArchetypeSystem { export class ArchetypeSystem {
@@ -50,7 +50,7 @@ export class ArchetypeSystem {
/** 所有原型 */ /** 所有原型 */
private _allArchetypes: Archetype[] = []; private _allArchetypes: Archetype[] = [];
/** /**
* 添加实体到原型系统 * 添加实体到原型系统
*/ */
@@ -66,7 +66,7 @@ export class ArchetypeSystem {
archetype.entities.add(entity); archetype.entities.add(entity);
this._entityToArchetype.set(entity, archetype); this._entityToArchetype.set(entity, archetype);
} }
/** /**
* 从原型系统中移除实体 * 从原型系统中移除实体
*/ */
@@ -118,7 +118,7 @@ export class ArchetypeSystem {
this._entityToArchetype.set(entity, newArchetype); this._entityToArchetype.set(entity, newArchetype);
} }
/** /**
* 查询包含指定组件组合的原型 * 查询包含指定组件组合的原型
* *
@@ -198,14 +198,14 @@ export class ArchetypeSystem {
totalEntities totalEntities
}; };
} }
/** /**
* 获取实体所属的原型 * 获取实体所属的原型
*/ */
public getEntityArchetype(entity: Entity): Archetype | undefined { public getEntityArchetype(entity: Entity): Archetype | undefined {
return this._entityToArchetype.get(entity); return this._entityToArchetype.get(entity);
} }
/** /**
* 获取所有原型 * 获取所有原型
*/ */
@@ -247,7 +247,7 @@ export class ArchetypeSystem {
*/ */
private updateAllArchetypeArrays(): void { private updateAllArchetypeArrays(): void {
this._allArchetypes = []; this._allArchetypes = [];
for (let archetype of this._archetypes.values()) { for (const archetype of this._archetypes.values()) {
this._allArchetypes.push(archetype); this._allArchetypes.push(archetype);
} }
} }
@@ -258,18 +258,18 @@ export class ArchetypeSystem {
private getEntityComponentTypes(entity: Entity): ComponentType[] { private getEntityComponentTypes(entity: Entity): ComponentType[] {
let componentTypes = this._entityComponentTypesCache.get(entity); let componentTypes = this._entityComponentTypesCache.get(entity);
if (!componentTypes) { if (!componentTypes) {
componentTypes = entity.components.map(component => component.constructor as ComponentType); componentTypes = entity.components.map((component) => component.constructor as ComponentType);
this._entityComponentTypesCache.set(entity, componentTypes); this._entityComponentTypesCache.set(entity, componentTypes);
} }
return componentTypes; return componentTypes;
} }
/** /**
* 生成原型ID * 生成原型ID
* 使用ComponentRegistry确保与Entity.componentMask使用相同的bitIndex * 使用ComponentRegistry确保与Entity.componentMask使用相同的bitIndex
*/ */
private generateArchetypeId(componentTypes: ComponentType[]): ArchetypeId { private generateArchetypeId(componentTypes: ComponentType[]): ArchetypeId {
let mask = BitMask64Utils.clone(BitMask64Utils.ZERO); const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const type of componentTypes) { for (const type of componentTypes) {
if (!ComponentRegistry.isRegistered(type)) { if (!ComponentRegistry.isRegistered(type)) {
ComponentRegistry.register(type); ComponentRegistry.register(type);
@@ -279,7 +279,7 @@ export class ArchetypeSystem {
} }
return mask; return mask;
} }
/** /**
* 创建新原型 * 创建新原型
*/ */
+1 -1
View File
@@ -356,4 +356,4 @@ export class ComponentPoolManager {
return maxSize > 0 ? (used / maxSize * 100) : 0; return maxSize > 0 ? (used / maxSize * 100) : 0;
} }
} }
@@ -9,7 +9,6 @@ import { ComponentRegistry, ComponentType } from './ComponentStorage/ComponentRe
export { ComponentRegistry, ComponentType }; export { ComponentRegistry, ComponentType };
/** /**
* 高性能组件存储器 * 高性能组件存储器
*/ */
@@ -21,7 +20,7 @@ export class ComponentStorage<T extends Component> {
constructor(componentType: ComponentType<T>) { constructor(componentType: ComponentType<T>) {
this.componentType = componentType; this.componentType = componentType;
// 确保组件类型已注册 // 确保组件类型已注册
if (!ComponentRegistry.isRegistered(componentType)) { if (!ComponentRegistry.isRegistered(componentType)) {
ComponentRegistry.register(componentType); ComponentRegistry.register(componentType);
@@ -152,7 +151,7 @@ export class ComponentStorage<T extends Component> {
usedSlots: number; usedSlots: number;
freeSlots: number; freeSlots: number;
fragmentation: number; fragmentation: number;
} { } {
const totalSlots = this.dense.length; const totalSlots = this.dense.length;
const usedSlots = this.dense.length; const usedSlots = this.dense.length;
const freeSlots = 0; // 永远无空洞 const freeSlots = 0; // 永远无空洞
@@ -342,15 +341,15 @@ export class ComponentStorageManager {
* @returns 组件位掩码 * @returns 组件位掩码
*/ */
public getComponentMask(entityId: number): BitMask64Data { public getComponentMask(entityId: number): BitMask64Data {
let mask = BitMask64Utils.clone(BitMask64Utils.ZERO); const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const [componentType, storage] of this.storages.entries()) { for (const [componentType, storage] of this.storages.entries()) {
if (storage.hasComponent(entityId)) { if (storage.hasComponent(entityId)) {
const componentMask = ComponentRegistry.getBitMask(componentType as ComponentType); const componentMask = ComponentRegistry.getBitMask(componentType as ComponentType);
BitMask64Utils.orInPlace(mask, componentMask); BitMask64Utils.orInPlace(mask, componentMask);
} }
} }
return mask; return mask;
} }
@@ -360,12 +359,12 @@ export class ComponentStorageManager {
*/ */
public getAllStats(): Map<string, { totalSlots: number; usedSlots: number; freeSlots: number; fragmentation: number }> { public getAllStats(): Map<string, { totalSlots: number; usedSlots: number; freeSlots: number; fragmentation: number }> {
const stats = new Map<string, { totalSlots: number; usedSlots: number; freeSlots: number; fragmentation: number }>(); const stats = new Map<string, { totalSlots: number; usedSlots: number; freeSlots: number; fragmentation: number }>();
for (const [componentType, storage] of this.storages.entries()) { for (const [componentType, storage] of this.storages.entries()) {
const typeName = getComponentTypeName(componentType as ComponentType); const typeName = getComponentTypeName(componentType as ComponentType);
stats.set(typeName, storage.getStats()); stats.set(typeName, storage.getStats());
} }
return stats; return stats;
} }
@@ -378,4 +377,4 @@ export class ComponentStorageManager {
} }
this.storages.clear(); this.storages.clear();
} }
} }
@@ -153,7 +153,7 @@ export class ComponentRegistry {
*/ */
public static createSingleComponentMask(componentName: string): BitMask64Data { public static createSingleComponentMask(componentName: string): BitMask64Data {
const cacheKey = `single:${componentName}`; const cacheKey = `single:${componentName}`;
if (this.maskCache.has(cacheKey)) { if (this.maskCache.has(cacheKey)) {
return this.maskCache.get(cacheKey)!; return this.maskCache.get(cacheKey)!;
} }
@@ -176,12 +176,12 @@ export class ComponentRegistry {
public static createComponentMask(componentNames: string[]): BitMask64Data { public static createComponentMask(componentNames: string[]): BitMask64Data {
const sortedNames = [...componentNames].sort(); const sortedNames = [...componentNames].sort();
const cacheKey = `multi:${sortedNames.join(',')}`; const cacheKey = `multi:${sortedNames.join(',')}`;
if (this.maskCache.has(cacheKey)) { if (this.maskCache.has(cacheKey)) {
return this.maskCache.get(cacheKey)!; return this.maskCache.get(cacheKey)!;
} }
let mask = BitMask64Utils.clone(BitMask64Utils.ZERO); const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const name of componentNames) { for (const name of componentNames) {
const componentId = this.getComponentId(name); const componentId = this.getComponentId(name);
if (componentId !== undefined) { if (componentId !== undefined) {
@@ -212,4 +212,4 @@ export class ComponentRegistry {
this.maskCache.clear(); this.maskCache.clear();
this.nextBitIndex = 0; this.nextBitIndex = 0;
} }
} }
+64 -62
View File
@@ -1,6 +1,6 @@
import { import {
IEventBus, IEventBus,
IEventListenerConfig, IEventListenerConfig,
IEventStats, IEventStats,
IEventData, IEventData,
IEntityEventData, IEntityEventData,
@@ -10,10 +10,10 @@ import {
IPerformanceEventData IPerformanceEventData
} from '../../Types'; } from '../../Types';
import { createLogger } from '../../Utils/Logger'; import { createLogger } from '../../Utils/Logger';
import { import {
TypeSafeEventSystem, TypeSafeEventSystem,
EventListenerConfig, EventListenerConfig,
EventStats EventStats
} from './EventSystem'; } from './EventSystem';
import { import {
ECSEventType, ECSEventType,
@@ -30,12 +30,12 @@ export class EventBus implements IEventBus {
private eventSystem: TypeSafeEventSystem; private eventSystem: TypeSafeEventSystem;
private eventIdCounter = 0; private eventIdCounter = 0;
private isDebugMode = false; private isDebugMode = false;
constructor(debugMode: boolean = false) { constructor(debugMode: boolean = false) {
this.eventSystem = new TypeSafeEventSystem(); this.eventSystem = new TypeSafeEventSystem();
this.isDebugMode = debugMode; this.isDebugMode = debugMode;
} }
/** /**
* 发射事件 * 发射事件
* @param eventType 事件类型 * @param eventType 事件类型
@@ -53,7 +53,7 @@ export class EventBus implements IEventBus {
this.eventSystem.emitSync(eventType, finalData); this.eventSystem.emitSync(eventType, finalData);
} }
/** /**
* 异步发射事件 * 异步发射事件
* @param eventType 事件类型 * @param eventType 事件类型
@@ -71,7 +71,7 @@ export class EventBus implements IEventBus {
await this.eventSystem.emit(eventType, finalData); await this.eventSystem.emit(eventType, finalData);
} }
/** /**
* 监听事件 * 监听事件
* @param eventType 事件类型 * @param eventType 事件类型
@@ -80,26 +80,29 @@ export class EventBus implements IEventBus {
* @returns 监听器ID * @returns 监听器ID
*/ */
public on<T>( public on<T>(
eventType: string, eventType: string,
handler: (data: T) => void, handler: (data: T) => void,
config: IEventListenerConfig = {} config: IEventListenerConfig = {}
): string { ): string {
this.validateEventType(eventType); this.validateEventType(eventType);
const eventConfig: EventListenerConfig = { const eventConfig: EventListenerConfig = {
once: config.once || false, once: config.once || false,
priority: config.priority || EventPriority.NORMAL, priority: config.priority || EventPriority.NORMAL,
async: config.async || false, async: config.async || false
context: config.context
}; };
if (config.thisArg) {
eventConfig.thisArg = config.thisArg as object;
}
if (this.isDebugMode) { if (this.isDebugMode) {
EventBus._logger.info(`添加监听器: ${eventType}`, eventConfig); EventBus._logger.info(`添加监听器: ${eventType}`, eventConfig);
} }
return this.eventSystem.on(eventType, handler, eventConfig); return this.eventSystem.on(eventType, handler, eventConfig);
} }
/** /**
* 监听事件(一次性) * 监听事件(一次性)
* @param eventType 事件类型 * @param eventType 事件类型
@@ -108,13 +111,13 @@ export class EventBus implements IEventBus {
* @returns 监听器ID * @returns 监听器ID
*/ */
public once<T>( public once<T>(
eventType: string, eventType: string,
handler: (data: T) => void, handler: (data: T) => void,
config: IEventListenerConfig = {} config: IEventListenerConfig = {}
): string { ): string {
return this.on(eventType, handler, { ...config, once: true }); return this.on(eventType, handler, { ...config, once: true });
} }
/** /**
* 异步监听事件 * 异步监听事件
* @param eventType 事件类型 * @param eventType 事件类型
@@ -123,13 +126,13 @@ export class EventBus implements IEventBus {
* @returns 监听器ID * @returns 监听器ID
*/ */
public onAsync<T>( public onAsync<T>(
eventType: string, eventType: string,
handler: (data: T) => Promise<void>, handler: (data: T) => Promise<void>,
config: IEventListenerConfig = {} config: IEventListenerConfig = {}
): string { ): string {
return this.on(eventType, handler as any, { ...config, async: true }); return this.on(eventType, handler, { ...config, async: true });
} }
/** /**
* 移除事件监听器 * 移除事件监听器
* @param eventType 事件类型 * @param eventType 事件类型
@@ -139,10 +142,10 @@ export class EventBus implements IEventBus {
if (this.isDebugMode) { if (this.isDebugMode) {
EventBus._logger.info(`移除监听器: ${listenerId} 事件: ${eventType}`); EventBus._logger.info(`移除监听器: ${listenerId} 事件: ${eventType}`);
} }
return this.eventSystem.off(eventType, listenerId); return this.eventSystem.off(eventType, listenerId);
} }
/** /**
* 移除指定事件类型的所有监听器 * 移除指定事件类型的所有监听器
* @param eventType 事件类型 * @param eventType 事件类型
@@ -151,10 +154,10 @@ export class EventBus implements IEventBus {
if (this.isDebugMode) { if (this.isDebugMode) {
EventBus._logger.info(`移除所有监听器: ${eventType}`); EventBus._logger.info(`移除所有监听器: ${eventType}`);
} }
this.eventSystem.offAll(eventType); this.eventSystem.offAll(eventType);
} }
/** /**
* 检查是否有指定事件的监听器 * 检查是否有指定事件的监听器
* @param eventType 事件类型 * @param eventType 事件类型
@@ -162,14 +165,14 @@ export class EventBus implements IEventBus {
public hasListeners(eventType: string): boolean { public hasListeners(eventType: string): boolean {
return this.eventSystem.hasListeners(eventType); return this.eventSystem.hasListeners(eventType);
} }
/** /**
* 获取事件统计信息 * 获取事件统计信息
* @param eventType 事件类型(可选) * @param eventType 事件类型(可选)
*/ */
public getStats(eventType?: string): IEventStats | Map<string, IEventStats> { public getStats(eventType?: string): IEventStats | Map<string, IEventStats> {
const stats = this.eventSystem.getStats(eventType); const stats = this.eventSystem.getStats(eventType);
if (stats instanceof Map) { if (stats instanceof Map) {
// 转换Map中的每个EventStats为IEventStats // 转换Map中的每个EventStats为IEventStats
const result = new Map<string, IEventStats>(); const result = new Map<string, IEventStats>();
@@ -181,7 +184,7 @@ export class EventBus implements IEventBus {
return this.convertEventStats(stats); return this.convertEventStats(stats);
} }
} }
/** /**
* 清空所有监听器 * 清空所有监听器
*/ */
@@ -189,10 +192,10 @@ export class EventBus implements IEventBus {
if (this.isDebugMode) { if (this.isDebugMode) {
EventBus._logger.info('清空所有监听器'); EventBus._logger.info('清空所有监听器');
} }
this.eventSystem.clear(); this.eventSystem.clear();
} }
/** /**
* 启用或禁用事件系统 * 启用或禁用事件系统
* @param enabled 是否启用 * @param enabled 是否启用
@@ -200,7 +203,7 @@ export class EventBus implements IEventBus {
public setEnabled(enabled: boolean): void { public setEnabled(enabled: boolean): void {
this.eventSystem.setEnabled(enabled); this.eventSystem.setEnabled(enabled);
} }
/** /**
* 设置调试模式 * 设置调试模式
* @param debug 是否启用调试 * @param debug 是否启用调试
@@ -208,7 +211,7 @@ export class EventBus implements IEventBus {
public setDebugMode(debug: boolean): void { public setDebugMode(debug: boolean): void {
this.isDebugMode = debug; this.isDebugMode = debug;
} }
/** /**
* 设置最大监听器数量 * 设置最大监听器数量
* @param max 最大数量 * @param max 最大数量
@@ -216,7 +219,7 @@ export class EventBus implements IEventBus {
public setMaxListeners(max: number): void { public setMaxListeners(max: number): void {
this.eventSystem.setMaxListeners(max); this.eventSystem.setMaxListeners(max);
} }
/** /**
* 获取监听器数量 * 获取监听器数量
* @param eventType 事件类型 * @param eventType 事件类型
@@ -224,7 +227,7 @@ export class EventBus implements IEventBus {
public getListenerCount(eventType: string): number { public getListenerCount(eventType: string): number {
return this.eventSystem.getListenerCount(eventType); return this.eventSystem.getListenerCount(eventType);
} }
/** /**
* 设置事件批处理配置 * 设置事件批处理配置
* @param eventType 事件类型 * @param eventType 事件类型
@@ -238,7 +241,7 @@ export class EventBus implements IEventBus {
enabled: true enabled: true
}); });
} }
/** /**
* 刷新指定事件的批处理队列 * 刷新指定事件的批处理队列
* @param eventType 事件类型 * @param eventType 事件类型
@@ -246,7 +249,7 @@ export class EventBus implements IEventBus {
public flushBatch(eventType: string): void { public flushBatch(eventType: string): void {
this.eventSystem.flushBatch(eventType); this.eventSystem.flushBatch(eventType);
} }
/** /**
* 重置事件统计 * 重置事件统计
* @param eventType 事件类型(可选) * @param eventType 事件类型(可选)
@@ -254,9 +257,9 @@ export class EventBus implements IEventBus {
public resetStats(eventType?: string): void { public resetStats(eventType?: string): void {
this.eventSystem.resetStats(eventType); this.eventSystem.resetStats(eventType);
} }
// 便捷方法:发射预定义的ECS事件 // 便捷方法:发射预定义的ECS事件
/** /**
* 发射实体创建事件 * 发射实体创建事件
* @param entityData 实体事件数据 * @param entityData 实体事件数据
@@ -320,59 +323,59 @@ export class EventBus implements IEventBus {
public emitPerformanceWarning(performanceData: IPerformanceEventData): void { public emitPerformanceWarning(performanceData: IPerformanceEventData): void {
this.emit(ECSEventType.PERFORMANCE_WARNING, performanceData); this.emit(ECSEventType.PERFORMANCE_WARNING, performanceData);
} }
// 便捷方法:监听预定义的ECS事件 // 便捷方法:监听预定义的ECS事件
/** /**
* 监听实体创建事件 * 监听实体创建事件
* @param handler 事件处理器 * @param handler 事件处理器
* @param config 监听器配置 * @param config 监听器配置
*/ */
public onEntityCreated( public onEntityCreated(
handler: (data: IEntityEventData) => void, handler: (data: IEntityEventData) => void,
config?: IEventListenerConfig config?: IEventListenerConfig
): string { ): string {
return this.on(ECSEventType.ENTITY_CREATED, handler, config); return this.on(ECSEventType.ENTITY_CREATED, handler, config);
} }
/** /**
* 监听组件添加事件 * 监听组件添加事件
* @param handler 事件处理器 * @param handler 事件处理器
* @param config 监听器配置 * @param config 监听器配置
*/ */
public onComponentAdded( public onComponentAdded(
handler: (data: IComponentEventData) => void, handler: (data: IComponentEventData) => void,
config?: IEventListenerConfig config?: IEventListenerConfig
): string { ): string {
return this.on(ECSEventType.COMPONENT_ADDED, handler, config); return this.on(ECSEventType.COMPONENT_ADDED, handler, config);
} }
/** /**
* 监听系统错误事件 * 监听系统错误事件
* @param handler 事件处理器 * @param handler 事件处理器
* @param config 监听器配置 * @param config 监听器配置
*/ */
public onSystemError( public onSystemError(
handler: (data: ISystemEventData) => void, handler: (data: ISystemEventData) => void,
config?: IEventListenerConfig config?: IEventListenerConfig
): string { ): string {
return this.on(ECSEventType.SYSTEM_ERROR, handler, config); return this.on(ECSEventType.SYSTEM_ERROR, handler, config);
} }
/** /**
* 监听性能警告事件 * 监听性能警告事件
* @param handler 事件处理器 * @param handler 事件处理器
* @param config 监听器配置 * @param config 监听器配置
*/ */
public onPerformanceWarning( public onPerformanceWarning(
handler: (data: IPerformanceEventData) => void, handler: (data: IPerformanceEventData) => void,
config?: IEventListenerConfig config?: IEventListenerConfig
): string { ): string {
return this.on(ECSEventType.PERFORMANCE_WARNING, handler, config); return this.on(ECSEventType.PERFORMANCE_WARNING, handler, config);
} }
// 私有方法 // 私有方法
/** /**
* 验证事件类型(仅在debug模式下执行,提升性能) * 验证事件类型(仅在debug模式下执行,提升性能)
* @param eventType 事件类型 * @param eventType 事件类型
@@ -387,7 +390,7 @@ export class EventBus implements IEventBus {
} }
} }
} }
/** /**
* 增强事件数据 * 增强事件数据
* @param eventType 事件类型 * @param eventType 事件类型
@@ -402,9 +405,9 @@ export class EventBus implements IEventBus {
source: 'EventBus' source: 'EventBus'
} as T & IEventData; } as T & IEventData;
} }
const enhanced = data as T & IEventData; const enhanced = data as T & IEventData;
// 如果数据还没有基础事件属性,添加它们 // 如果数据还没有基础事件属性,添加它们
if (!enhanced.timestamp) { if (!enhanced.timestamp) {
enhanced.timestamp = Date.now(); enhanced.timestamp = Date.now();
@@ -415,10 +418,10 @@ export class EventBus implements IEventBus {
if (!enhanced.source) { if (!enhanced.source) {
enhanced.source = 'EventBus'; enhanced.source = 'EventBus';
} }
return enhanced; return enhanced;
} }
/** /**
* 转换EventStats为IEventStats * 转换EventStats为IEventStats
* @param stats EventStats实例 * @param stats EventStats实例
@@ -441,7 +444,7 @@ export class EventBus implements IEventBus {
*/ */
export class GlobalEventBus { export class GlobalEventBus {
private static instance: EventBus; private static instance: EventBus;
/** /**
* 获取全局事件总线实例 * 获取全局事件总线实例
* @param debugMode 是否启用调试模式 * @param debugMode 是否启用调试模式
@@ -452,7 +455,7 @@ export class GlobalEventBus {
} }
return this.instance; return this.instance;
} }
/** /**
* 重置全局事件总线实例 * 重置全局事件总线实例
* @param debugMode 是否启用调试模式 * @param debugMode 是否启用调试模式
@@ -466,4 +469,3 @@ export class GlobalEventBus {
} }
} }
+30 -34
View File
@@ -3,12 +3,12 @@ import { createLogger } from '../../Utils/Logger';
/** /**
* 事件处理器函数类型 * 事件处理器函数类型
*/ */
export type EventHandler<T = any> = (event: T) => void; export type EventHandler<T> = (event: T) => void;
/** /**
* 异步事件处理器函数类型 * 异步事件处理器函数类型
*/ */
export type AsyncEventHandler<T = any> = (event: T) => Promise<void>; export type AsyncEventHandler<T> = (event: T) => Promise<void>;
/** /**
* 事件监听器配置 * 事件监听器配置
@@ -20,15 +20,20 @@ export interface EventListenerConfig {
priority?: number; priority?: number;
/** 是否异步执行 */ /** 是否异步执行 */
async?: boolean; async?: boolean;
/** 执行上下文 */ /** 事件处理函数的 this 绑定对象 */
context?: any; thisArg?: object;
} }
/** /**
* 内部事件监听器 * 内部事件监听器
*
* 注意:handler 使用 any 是必要的类型擦除
* 原因:需要在同一数组中存储处理不同事件类型(T)的监听器
* 类型安全保证:公共 API (on<T>/emit<T>) 在编译时保证类型匹配
*/ */
interface InternalEventListener<T = any> { interface InternalEventListener {
handler: EventHandler<T> | AsyncEventHandler<T>; // eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: EventHandler<any> | AsyncEventHandler<any>;
config: EventListenerConfig; config: EventListenerConfig;
id: string; id: string;
} }
@@ -71,8 +76,9 @@ export class TypeSafeEventSystem {
private static readonly _logger = createLogger('EventSystem'); private static readonly _logger = createLogger('EventSystem');
private listeners = new Map<string, InternalEventListener[]>(); private listeners = new Map<string, InternalEventListener[]>();
private stats = new Map<string, EventStats>(); private stats = new Map<string, EventStats>();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private batchQueue = new Map<string, any[]>(); private batchQueue = new Map<string, any[]>();
private batchTimers = new Map<string, number>(); private batchTimers = new Map<string, ReturnType<typeof setTimeout>>();
private batchConfigs = new Map<string, EventBatchConfig>(); private batchConfigs = new Map<string, EventBatchConfig>();
private nextListenerId = 0; private nextListenerId = 0;
private isEnabled = true; private isEnabled = true;
@@ -81,13 +87,13 @@ export class TypeSafeEventSystem {
/** /**
* 添加事件监听器 * 添加事件监听器
* @param eventType 事件类型 * @param eventType 事件类型
* @param handler 事件处理器 * @param handler 事件处理器(同步或异步,根据 config.async 决定)
* @param config 监听器配置 * @param config 监听器配置
* @returns 监听器ID(用于移除) * @returns 监听器ID(用于移除)
*/ */
public on<T>( public on<T>(
eventType: string, eventType: string,
handler: EventHandler<T>, handler: EventHandler<T> | AsyncEventHandler<T>,
config: EventListenerConfig = {} config: EventListenerConfig = {}
): string { ): string {
return this.addListener(eventType, handler, config); return this.addListener(eventType, handler, config);
@@ -100,11 +106,7 @@ export class TypeSafeEventSystem {
* @param config 监听器配置 * @param config 监听器配置
* @returns 监听器ID * @returns 监听器ID
*/ */
public once<T>( public once<T>(eventType: string, handler: EventHandler<T>, config: EventListenerConfig = {}): string {
eventType: string,
handler: EventHandler<T>,
config: EventListenerConfig = {}
): string {
return this.addListener(eventType, handler, { ...config, once: true }); return this.addListener(eventType, handler, { ...config, once: true });
} }
@@ -115,11 +117,7 @@ export class TypeSafeEventSystem {
* @param config 监听器配置 * @param config 监听器配置
* @returns 监听器ID * @returns 监听器ID
*/ */
public onAsync<T>( public onAsync<T>(eventType: string, handler: AsyncEventHandler<T>, config: EventListenerConfig = {}): string {
eventType: string,
handler: AsyncEventHandler<T>,
config: EventListenerConfig = {}
): string {
return this.addListener(eventType, handler, { ...config, async: true }); return this.addListener(eventType, handler, { ...config, async: true });
} }
@@ -133,7 +131,7 @@ export class TypeSafeEventSystem {
const listeners = this.listeners.get(eventType); const listeners = this.listeners.get(eventType);
if (!listeners) return false; if (!listeners) return false;
const index = listeners.findIndex(l => l.id === listenerId); const index = listeners.findIndex((l) => l.id === listenerId);
if (index === -1) return false; if (index === -1) return false;
listeners.splice(index, 1); listeners.splice(index, 1);
@@ -197,8 +195,8 @@ export class TypeSafeEventSystem {
if (listener.config.async) continue; // 跳过异步监听器 if (listener.config.async) continue; // 跳过异步监听器
try { try {
if (listener.config.context) { if (listener.config.thisArg) {
(listener.handler as EventHandler<T>).call(listener.config.context, event); (listener.handler as EventHandler<T>).call(listener.config.thisArg, event);
} else { } else {
(listener.handler as EventHandler<T>)(event); (listener.handler as EventHandler<T>)(event);
} }
@@ -344,7 +342,7 @@ export class TypeSafeEventSystem {
} }
const listenerId = `listener_${this.nextListenerId++}`; const listenerId = `listener_${this.nextListenerId++}`;
const listener: InternalEventListener<T> = { const listener: InternalEventListener = {
handler, handler,
config: { config: {
priority: 0, priority: 0,
@@ -379,14 +377,14 @@ export class TypeSafeEventSystem {
const sortedListeners = this.sortListenersByPriority(listeners); const sortedListeners = this.sortListenersByPriority(listeners);
// 分离同步和异步监听器 // 分离同步和异步监听器
const syncListeners = sortedListeners.filter(l => !l.config.async); const syncListeners = sortedListeners.filter((l) => !l.config.async);
const asyncListeners = sortedListeners.filter(l => l.config.async); const asyncListeners = sortedListeners.filter((l) => l.config.async);
// 执行同步监听器 // 执行同步监听器
for (const listener of syncListeners) { for (const listener of syncListeners) {
try { try {
if (listener.config.context) { if (listener.config.thisArg) {
(listener.handler as EventHandler<T>).call(listener.config.context, event); (listener.handler as EventHandler<T>).call(listener.config.thisArg, event);
} else { } else {
(listener.handler as EventHandler<T>)(event); (listener.handler as EventHandler<T>)(event);
} }
@@ -402,8 +400,8 @@ export class TypeSafeEventSystem {
// 执行异步监听器 // 执行异步监听器
const asyncPromises = asyncListeners.map(async (listener) => { const asyncPromises = asyncListeners.map(async (listener) => {
try { try {
if (listener.config.context) { if (listener.config.thisArg) {
await (listener.handler as AsyncEventHandler<T>).call(listener.config.context, event); await (listener.handler as AsyncEventHandler<T>).call(listener.config.thisArg, event);
} else { } else {
await (listener.handler as AsyncEventHandler<T>)(event); await (listener.handler as AsyncEventHandler<T>)(event);
} }
@@ -431,7 +429,7 @@ export class TypeSafeEventSystem {
* @param listeners 监听器数组 * @param listeners 监听器数组
* @returns 排序后的监听器数组 * @returns 排序后的监听器数组
*/ */
private sortListenersByPriority<T>(listeners: InternalEventListener<T>[]): InternalEventListener<T>[] { private sortListenersByPriority(listeners: InternalEventListener[]): InternalEventListener[] {
return listeners.slice().sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0)); return listeners.slice().sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0));
} }
@@ -447,7 +445,7 @@ export class TypeSafeEventSystem {
if (!listeners) return; if (!listeners) return;
for (const id of listenerIds) { for (const id of listenerIds) {
const index = listeners.findIndex(l => l.id === id); const index = listeners.findIndex((l) => l.id === id);
if (index !== -1) { if (index !== -1) {
listeners.splice(index, 1); listeners.splice(index, 1);
} }
@@ -488,7 +486,7 @@ export class TypeSafeEventSystem {
this.flushBatch(eventType); this.flushBatch(eventType);
}, config.delay); }, config.delay);
this.batchTimers.set(eventType, timer as any); this.batchTimers.set(eventType, timer);
} }
} }
@@ -577,5 +575,3 @@ export class TypeSafeEventSystem {
* 全局事件系统实例 * 全局事件系统实例
*/ */
export const GlobalEventSystem = new TypeSafeEventSystem(); export const GlobalEventSystem = new TypeSafeEventSystem();
+1 -1
View File
@@ -1,2 +1,2 @@
export { EventBus, GlobalEventBus } from '../EventBus'; export { EventBus, GlobalEventBus } from '../EventBus';
export { TypeSafeEventSystem, EventListenerConfig, EventStats } from '../EventSystem'; export { TypeSafeEventSystem, EventListenerConfig, EventStats } from '../EventSystem';
+1 -1
View File
@@ -8,4 +8,4 @@ export {
createECSAPI, createECSAPI,
initializeECS, initializeECS,
ECS ECS
} from './FluentAPI/index'; } from './FluentAPI/index';
@@ -52,4 +52,4 @@ export class ComponentBuilder<T extends Component> {
public build(): T { public build(): T {
return this.component; return this.component;
} }
} }
@@ -47,7 +47,7 @@ export class ECSFluentAPI {
* @returns 组件构建器 * @returns 组件构建器
*/ */
public createComponent<T extends Component>( public createComponent<T extends Component>(
componentClass: new (...args: unknown[]) => T, componentClass: new (...args: unknown[]) => T,
...args: unknown[] ...args: unknown[]
): ComponentBuilder<T> { ): ComponentBuilder<T> {
return new ComponentBuilder(componentClass, ...args); return new ComponentBuilder(componentClass, ...args);
@@ -164,7 +164,7 @@ export class ECSFluentAPI {
componentStats: Map<string, unknown>; componentStats: Map<string, unknown>;
queryStats: unknown; queryStats: unknown;
eventStats: Map<string, unknown>; eventStats: Map<string, unknown>;
} { } {
return { return {
entityCount: this.scene.entities.count, entityCount: this.scene.entities.count,
systemCount: this.scene.systems.length, systemCount: this.scene.systems.length,
@@ -183,8 +183,8 @@ export class ECSFluentAPI {
* @returns ECS流式API实例 * @returns ECS流式API实例
*/ */
export function createECSAPI( export function createECSAPI(
scene: IScene, scene: IScene,
querySystem: QuerySystem, querySystem: QuerySystem,
eventSystem: TypeSafeEventSystem eventSystem: TypeSafeEventSystem
): ECSFluentAPI { ): ECSFluentAPI {
return new ECSFluentAPI(scene, querySystem, eventSystem); return new ECSFluentAPI(scene, querySystem, eventSystem);
@@ -202,9 +202,9 @@ export let ECS: ECSFluentAPI;
* @param eventSystem 事件系统 * @param eventSystem 事件系统
*/ */
export function initializeECS( export function initializeECS(
scene: IScene, scene: IScene,
querySystem: QuerySystem, querySystem: QuerySystem,
eventSystem: TypeSafeEventSystem eventSystem: TypeSafeEventSystem
): void { ): void {
ECS = createECSAPI(scene, querySystem, eventSystem); ECS = createECSAPI(scene, querySystem, eventSystem);
} }
@@ -95,4 +95,4 @@ export class EntityBatchOperator {
public count(): number { public count(): number {
return this.entities.length; return this.entities.length;
} }
} }
@@ -15,7 +15,7 @@ export class EntityBuilder {
this.scene = scene; this.scene = scene;
this.storageManager = storageManager; this.storageManager = storageManager;
const id = scene.identifierPool.checkOut(); const id = scene.identifierPool.checkOut();
this.entity = new Entity("", id); this.entity = new Entity('', id);
this.entity.scene = this.scene as any; this.entity.scene = this.scene as any;
} }
@@ -92,7 +92,7 @@ export class EntityBuilder {
* @returns 实体构建器 * @returns 实体构建器
*/ */
public configure<T extends Component>( public configure<T extends Component>(
componentType: ComponentType<T>, componentType: ComponentType<T>,
configurator: (component: T) => void configurator: (component: T) => void
): EntityBuilder { ): EntityBuilder {
const component = this.entity.getComponent(componentType); const component = this.entity.getComponent(componentType);
@@ -199,4 +199,4 @@ export class EntityBuilder {
newBuilder.entity = this.entity; // 实际应该是深度克隆 newBuilder.entity = this.entity; // 实际应该是深度克隆
return newBuilder; return newBuilder;
} }
} }
@@ -87,4 +87,4 @@ export class SceneBuilder {
public build(): Scene { public build(): Scene {
return this.scene; return this.scene;
} }
} }
@@ -2,4 +2,4 @@ export { EntityBuilder } from './EntityBuilder';
export { SceneBuilder } from './SceneBuilder'; export { SceneBuilder } from './SceneBuilder';
export { ComponentBuilder } from './ComponentBuilder'; export { ComponentBuilder } from './ComponentBuilder';
export { EntityBatchOperator } from './EntityBatchOperator'; export { EntityBatchOperator } from './EntityBatchOperator';
export { ECSFluentAPI, createECSAPI, initializeECS, ECS } from './ECSFluentAPI'; export { ECSFluentAPI, createECSAPI, initializeECS, ECS } from './ECSFluentAPI';
@@ -1,3 +1,2 @@
export { QuerySystem } from '../QuerySystem'; export { QuerySystem } from '../QuerySystem';
export { ECSFluentAPI, createECSAPI } from '../FluentAPI'; export { ECSFluentAPI, createECSAPI } from '../FluentAPI';
+147 -150
View File
@@ -30,34 +30,34 @@ interface QueryCacheEntry {
/** /**
* 高性能实体查询系统 * 高性能实体查询系统
* *
* 提供快速的实体查询功能,支持按组件类型、标签、名称等多种方式查询实体。 * 提供快速的实体查询功能,支持按组件类型、标签、名称等多种方式查询实体。
* *
* @example * @example
* ```typescript * ```typescript
* // 查询所有包含Position和Velocity组件的实体 * // 查询所有包含Position和Velocity组件的实体
* const movingEntities = querySystem.queryAll(PositionComponent, VelocityComponent); * const movingEntities = querySystem.queryAll(PositionComponent, VelocityComponent);
* *
* // 查询特定标签的实体 * // 查询特定标签的实体
* const playerEntities = querySystem.queryByTag(PLAYER_TAG); * const playerEntities = querySystem.queryByTag(PLAYER_TAG);
* ``` * ```
*/ */
export class QuerySystem { export class QuerySystem {
private _logger = createLogger('QuerySystem'); private readonly _logger = createLogger('QuerySystem');
private entities: Entity[] = []; private _entities: Entity[] = [];
private entityIndex: EntityIndex; private _entityIndex: EntityIndex;
private _version = 0; private _version = 0;
private queryCache = new Map<string, QueryCacheEntry>(); private _queryCache = new Map<string, QueryCacheEntry>();
private cacheMaxSize = 1000; private _cacheMaxSize = 1000;
private cacheTimeout = 5000; private _cacheTimeout = 5000;
private componentMaskCache = new Map<string, BitMask64Data>(); private _componentMaskCache = new Map<string, BitMask64Data>();
private archetypeSystem: ArchetypeSystem; private _archetypeSystem: ArchetypeSystem;
private queryStats = { private _queryStats = {
totalQueries: 0, totalQueries: 0,
cacheHits: 0, cacheHits: 0,
indexHits: 0, indexHits: 0,
@@ -67,12 +67,12 @@ export class QuerySystem {
}; };
constructor() { constructor() {
this.entityIndex = { this._entityIndex = {
byTag: new Map(), byTag: new Map(),
byName: new Map() byName: new Map()
}; };
this.archetypeSystem = new ArchetypeSystem(); this._archetypeSystem = new ArchetypeSystem();
} }
/** /**
@@ -84,7 +84,7 @@ export class QuerySystem {
* @param entities 新的实体列表 * @param entities 新的实体列表
*/ */
public setEntities(entities: Entity[]): void { public setEntities(entities: Entity[]): void {
this.entities = entities; this._entities = entities;
this.clearQueryCache(); this.clearQueryCache();
this.clearReactiveQueries(); this.clearReactiveQueries();
this.rebuildIndexes(); this.rebuildIndexes();
@@ -92,19 +92,19 @@ export class QuerySystem {
/** /**
* 添加单个实体到查询系统 * 添加单个实体到查询系统
* *
* 将新实体添加到查询系统中,并自动更新相关索引。 * 将新实体添加到查询系统中,并自动更新相关索引。
* 为了提高批量添加性能,可以延迟缓存清理。 * 为了提高批量添加性能,可以延迟缓存清理。
* *
* @param entity 要添加的实体 * @param entity 要添加的实体
* @param deferCacheClear 是否延迟缓存清理(用于批量操作) * @param deferCacheClear 是否延迟缓存清理(用于批量操作)
*/ */
public addEntity(entity: Entity, deferCacheClear: boolean = false): void { public addEntity(entity: Entity, deferCacheClear: boolean = false): void {
if (!this.entities.includes(entity)) { if (!this._entities.includes(entity)) {
this.entities.push(entity); this._entities.push(entity);
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
this.archetypeSystem.addEntity(entity); this._archetypeSystem.addEntity(entity);
// 通知响应式查询 // 通知响应式查询
this.notifyReactiveQueriesEntityAdded(entity); this.notifyReactiveQueriesEntityAdded(entity);
@@ -121,26 +121,26 @@ export class QuerySystem {
/** /**
* 批量添加实体 * 批量添加实体
* *
* 高效地批量添加多个实体,减少缓存清理次数。 * 高效地批量添加多个实体,减少缓存清理次数。
* 使用Set来避免O(n)的重复检查。 * 使用Set来避免O(n)的重复检查。
* *
* @param entities 要添加的实体列表 * @param entities 要添加的实体列表
*/ */
public addEntities(entities: Entity[]): void { public addEntities(entities: Entity[]): void {
if (entities.length === 0) return; if (entities.length === 0) return;
// 使用Set来快速检查重复 // 使用Set来快速检查重复
const existingIds = new Set(this.entities.map(e => e.id)); const existingIds = new Set(this._entities.map((e) => e.id));
let addedCount = 0; let addedCount = 0;
for (const entity of entities) { for (const entity of entities) {
if (!existingIds.has(entity.id)) { if (!existingIds.has(entity.id)) {
this.entities.push(entity); this._entities.push(entity);
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
// 更新索引管理器 // 更新索引管理器
this.archetypeSystem.addEntity(entity); this._archetypeSystem.addEntity(entity);
existingIds.add(entity.id); existingIds.add(entity.id);
addedCount++; addedCount++;
@@ -155,10 +155,10 @@ export class QuerySystem {
/** /**
* 批量添加实体(无重复检查版本) * 批量添加实体(无重复检查版本)
* *
* 假设所有实体都是新的,跳过重复检查以获得最大性能。 * 假设所有实体都是新的,跳过重复检查以获得最大性能。
* 仅在确保没有重复实体时使用。 * 仅在确保没有重复实体时使用。
* *
* @param entities 要添加的实体列表 * @param entities 要添加的实体列表
*/ */
public addEntitiesUnchecked(entities: Entity[]): void { public addEntitiesUnchecked(entities: Entity[]): void {
@@ -166,7 +166,7 @@ export class QuerySystem {
// 避免调用栈溢出,分批添加 // 避免调用栈溢出,分批添加
for (const entity of entities) { for (const entity of entities) {
this.entities.push(entity); this._entities.push(entity);
} }
// 批量更新索引 // 批量更新索引
@@ -174,7 +174,7 @@ export class QuerySystem {
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
// 更新索引管理器 // 更新索引管理器
this.archetypeSystem.addEntity(entity); this._archetypeSystem.addEntity(entity);
} }
// 清理缓存 // 清理缓存
@@ -189,17 +189,17 @@ export class QuerySystem {
* @param entity 要移除的实体 * @param entity 要移除的实体
*/ */
public removeEntity(entity: Entity): void { public removeEntity(entity: Entity): void {
const index = this.entities.indexOf(entity); const index = this._entities.indexOf(entity);
if (index !== -1) { if (index !== -1) {
const componentTypes: ComponentType[] = []; const componentTypes: ComponentType[] = [];
for (const component of entity.components) { for (const component of entity.components) {
componentTypes.push(component.constructor as ComponentType); componentTypes.push(component.constructor as ComponentType);
} }
this.entities.splice(index, 1); this._entities.splice(index, 1);
this.removeEntityFromIndexes(entity); this.removeEntityFromIndexes(entity);
this.archetypeSystem.removeEntity(entity); this._archetypeSystem.removeEntity(entity);
if (componentTypes.length > 0) { if (componentTypes.length > 0) {
this.notifyReactiveQueriesEntityRemoved(entity, componentTypes); this.notifyReactiveQueriesEntityRemoved(entity, componentTypes);
@@ -222,7 +222,7 @@ export class QuerySystem {
*/ */
public updateEntity(entity: Entity): void { public updateEntity(entity: Entity): void {
// 检查实体是否在查询系统中 // 检查实体是否在查询系统中
if (!this.entities.includes(entity)) { if (!this._entities.includes(entity)) {
// 如果实体不在系统中,直接添加 // 如果实体不在系统中,直接添加
this.addEntity(entity); this.addEntity(entity);
return; return;
@@ -232,7 +232,7 @@ export class QuerySystem {
this.removeEntityFromIndexes(entity); this.removeEntityFromIndexes(entity);
// 更新ArchetypeSystem中的实体状态 // 更新ArchetypeSystem中的实体状态
this.archetypeSystem.updateEntity(entity); this._archetypeSystem.updateEntity(entity);
// 重新添加实体到索引(基于新的组件状态) // 重新添加实体到索引(基于新的组件状态)
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
@@ -253,28 +253,27 @@ export class QuerySystem {
// 标签索引 // 标签索引
const tag = entity.tag; const tag = entity.tag;
if (tag !== undefined) { if (tag !== undefined) {
const tagSet = this.entityIndex.byTag.get(tag) || this.createAndSetTagIndex(tag); const tagSet = this._entityIndex.byTag.get(tag) || this.createAndSetTagIndex(tag);
tagSet.add(entity); tagSet.add(entity);
} }
// 名称索引 // 名称索引
const name = entity.name; const name = entity.name;
if (name) { if (name) {
const nameSet = this.entityIndex.byName.get(name) || this.createAndSetNameIndex(name); const nameSet = this._entityIndex.byName.get(name) || this.createAndSetNameIndex(name);
nameSet.add(entity); nameSet.add(entity);
} }
} }
private createAndSetTagIndex(tag: number): Set<Entity> { private createAndSetTagIndex(tag: number): Set<Entity> {
const set = new Set<Entity>(); const set = new Set<Entity>();
this.entityIndex.byTag.set(tag, set); this._entityIndex.byTag.set(tag, set);
return set; return set;
} }
private createAndSetNameIndex(name: string): Set<Entity> { private createAndSetNameIndex(name: string): Set<Entity> {
const set = new Set<Entity>(); const set = new Set<Entity>();
this.entityIndex.byName.set(name, set); this._entityIndex.byName.set(name, set);
return set; return set;
} }
@@ -284,22 +283,22 @@ export class QuerySystem {
private removeEntityFromIndexes(entity: Entity): void { private removeEntityFromIndexes(entity: Entity): void {
// 从标签索引移除 // 从标签索引移除
if (entity.tag !== undefined) { if (entity.tag !== undefined) {
const tagSet = this.entityIndex.byTag.get(entity.tag); const tagSet = this._entityIndex.byTag.get(entity.tag);
if (tagSet) { if (tagSet) {
tagSet.delete(entity); tagSet.delete(entity);
if (tagSet.size === 0) { if (tagSet.size === 0) {
this.entityIndex.byTag.delete(entity.tag); this._entityIndex.byTag.delete(entity.tag);
} }
} }
} }
// 从名称索引移除 // 从名称索引移除
if (entity.name) { if (entity.name) {
const nameSet = this.entityIndex.byName.get(entity.name); const nameSet = this._entityIndex.byName.get(entity.name);
if (nameSet) { if (nameSet) {
nameSet.delete(entity); nameSet.delete(entity);
if (nameSet.size === 0) { if (nameSet.size === 0) {
this.entityIndex.byName.delete(entity.name); this._entityIndex.byName.delete(entity.name);
} }
} }
} }
@@ -312,15 +311,15 @@ export class QuerySystem {
* 通常在大量实体变更后调用以确保索引一致性。 * 通常在大量实体变更后调用以确保索引一致性。
*/ */
private rebuildIndexes(): void { private rebuildIndexes(): void {
this.entityIndex.byTag.clear(); this._entityIndex.byTag.clear();
this.entityIndex.byName.clear(); this._entityIndex.byName.clear();
// 清理ArchetypeSystem和ComponentIndexManager // 清理ArchetypeSystem和ComponentIndexManager
this.archetypeSystem.clear(); this._archetypeSystem.clear();
for (const entity of this.entities) { for (const entity of this._entities) {
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
this.archetypeSystem.addEntity(entity); this._archetypeSystem.addEntity(entity);
} }
} }
@@ -342,7 +341,7 @@ export class QuerySystem {
*/ */
public queryAll(...componentTypes: ComponentType[]): QueryResult { public queryAll(...componentTypes: ComponentType[]): QueryResult {
const startTime = performance.now(); const startTime = performance.now();
this.queryStats.totalQueries++; this._queryStats.totalQueries++;
// 使用内部响应式查询作为智能缓存 // 使用内部响应式查询作为智能缓存
const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.ALL, componentTypes); const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.ALL, componentTypes);
@@ -351,7 +350,7 @@ export class QuerySystem {
const entities = reactiveQuery.getEntities(); const entities = reactiveQuery.getEntities();
// 统计为缓存命中(响应式查询本质上是永不过期的智能缓存) // 统计为缓存命中(响应式查询本质上是永不过期的智能缓存)
this.queryStats.cacheHits++; this._queryStats.cacheHits++;
return { return {
entities, entities,
@@ -379,7 +378,7 @@ export class QuerySystem {
*/ */
public queryAny(...componentTypes: ComponentType[]): QueryResult { public queryAny(...componentTypes: ComponentType[]): QueryResult {
const startTime = performance.now(); const startTime = performance.now();
this.queryStats.totalQueries++; this._queryStats.totalQueries++;
// 使用内部响应式查询作为智能缓存 // 使用内部响应式查询作为智能缓存
const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.ANY, componentTypes); const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.ANY, componentTypes);
@@ -388,7 +387,7 @@ export class QuerySystem {
const entities = reactiveQuery.getEntities(); const entities = reactiveQuery.getEntities();
// 统计为缓存命中(响应式查询本质上是永不过期的智能缓存) // 统计为缓存命中(响应式查询本质上是永不过期的智能缓存)
this.queryStats.cacheHits++; this._queryStats.cacheHits++;
return { return {
entities, entities,
@@ -416,7 +415,7 @@ export class QuerySystem {
*/ */
public queryNone(...componentTypes: ComponentType[]): QueryResult { public queryNone(...componentTypes: ComponentType[]): QueryResult {
const startTime = performance.now(); const startTime = performance.now();
this.queryStats.totalQueries++; this._queryStats.totalQueries++;
// 使用内部响应式查询作为智能缓存 // 使用内部响应式查询作为智能缓存
const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.NONE, componentTypes); const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.NONE, componentTypes);
@@ -425,7 +424,7 @@ export class QuerySystem {
const entities = reactiveQuery.getEntities(); const entities = reactiveQuery.getEntities();
// 统计为缓存命中(响应式查询本质上是永不过期的智能缓存) // 统计为缓存命中(响应式查询本质上是永不过期的智能缓存)
this.queryStats.cacheHits++; this._queryStats.cacheHits++;
return { return {
entities, entities,
@@ -437,13 +436,13 @@ export class QuerySystem {
/** /**
* 按标签查询实体 * 按标签查询实体
* *
* 返回具有指定标签的所有实体。 * 返回具有指定标签的所有实体。
* 标签查询使用专用索引,具有很高的查询性能。 * 标签查询使用专用索引,具有很高的查询性能。
* *
* @param tag 要查询的标签值 * @param tag 要查询的标签值
* @returns 查询结果,包含匹配的实体和性能信息 * @returns 查询结果,包含匹配的实体和性能信息
* *
* @example * @example
* ```typescript * ```typescript
* // 查询所有玩家实体 * // 查询所有玩家实体
@@ -452,14 +451,14 @@ export class QuerySystem {
*/ */
public queryByTag(tag: number): QueryResult { public queryByTag(tag: number): QueryResult {
const startTime = performance.now(); const startTime = performance.now();
this.queryStats.totalQueries++; this._queryStats.totalQueries++;
const cacheKey = `tag:${tag}`; const cacheKey = `tag:${tag}`;
// 检查缓存 // 检查缓存
const cached = this.getFromCache(cacheKey); const cached = this.getFromCache(cacheKey);
if (cached) { if (cached) {
this.queryStats.cacheHits++; this._queryStats.cacheHits++;
return { return {
entities: cached, entities: cached,
count: cached.length, count: cached.length,
@@ -469,8 +468,8 @@ export class QuerySystem {
} }
// 使用索引查询 // 使用索引查询
this.queryStats.indexHits++; this._queryStats.indexHits++;
const entities = Array.from(this.entityIndex.byTag.get(tag) || []); const entities = Array.from(this._entityIndex.byTag.get(tag) || []);
// 缓存结果 // 缓存结果
this.addToCache(cacheKey, entities); this.addToCache(cacheKey, entities);
@@ -485,13 +484,13 @@ export class QuerySystem {
/** /**
* 按名称查询实体 * 按名称查询实体
* *
* 返回具有指定名称的所有实体。 * 返回具有指定名称的所有实体。
* 名称查询使用专用索引,适用于查找特定的命名实体。 * 名称查询使用专用索引,适用于查找特定的命名实体。
* *
* @param name 要查询的实体名称 * @param name 要查询的实体名称
* @returns 查询结果,包含匹配的实体和性能信息 * @returns 查询结果,包含匹配的实体和性能信息
* *
* @example * @example
* ```typescript * ```typescript
* // 查找名为"Player"的实体 * // 查找名为"Player"的实体
@@ -500,14 +499,14 @@ export class QuerySystem {
*/ */
public queryByName(name: string): QueryResult { public queryByName(name: string): QueryResult {
const startTime = performance.now(); const startTime = performance.now();
this.queryStats.totalQueries++; this._queryStats.totalQueries++;
const cacheKey = `name:${name}`; const cacheKey = `name:${name}`;
// 检查缓存 // 检查缓存
const cached = this.getFromCache(cacheKey); const cached = this.getFromCache(cacheKey);
if (cached) { if (cached) {
this.queryStats.cacheHits++; this._queryStats.cacheHits++;
return { return {
entities: cached, entities: cached,
count: cached.length, count: cached.length,
@@ -517,8 +516,8 @@ export class QuerySystem {
} }
// 使用索引查询 // 使用索引查询
this.queryStats.indexHits++; this._queryStats.indexHits++;
const entities = Array.from(this.entityIndex.byName.get(name) || []); const entities = Array.from(this._entityIndex.byName.get(name) || []);
// 缓存结果 // 缓存结果
this.addToCache(cacheKey, entities); this.addToCache(cacheKey, entities);
@@ -533,13 +532,13 @@ export class QuerySystem {
/** /**
* 按单个组件类型查询实体 * 按单个组件类型查询实体
* *
* 返回包含指定组件类型的所有实体。 * 返回包含指定组件类型的所有实体。
* 这是最基础的查询方法,具有最高的查询性能。 * 这是最基础的查询方法,具有最高的查询性能。
* *
* @param componentType 要查询的组件类型 * @param componentType 要查询的组件类型
* @returns 查询结果,包含匹配的实体和性能信息 * @returns 查询结果,包含匹配的实体和性能信息
* *
* @example * @example
* ```typescript * ```typescript
* // 查询所有具有位置组件的实体 * // 查询所有具有位置组件的实体
@@ -548,14 +547,14 @@ export class QuerySystem {
*/ */
public queryByComponent<T extends Component>(componentType: ComponentType<T>): QueryResult { public queryByComponent<T extends Component>(componentType: ComponentType<T>): QueryResult {
const startTime = performance.now(); const startTime = performance.now();
this.queryStats.totalQueries++; this._queryStats.totalQueries++;
const cacheKey = this.generateCacheKey('component', [componentType]); const cacheKey = this.generateCacheKey('component', [componentType]);
// 检查缓存 // 检查缓存
const cached = this.getFromCache(cacheKey); const cached = this.getFromCache(cacheKey);
if (cached) { if (cached) {
this.queryStats.cacheHits++; this._queryStats.cacheHits++;
return { return {
entities: cached, entities: cached,
count: cached.length, count: cached.length,
@@ -564,8 +563,8 @@ export class QuerySystem {
}; };
} }
this.queryStats.indexHits++; this._queryStats.indexHits++;
const entities = this.archetypeSystem.getEntitiesByComponent(componentType); const entities = this._archetypeSystem.getEntitiesByComponent(componentType);
// 缓存结果 // 缓存结果
this.addToCache(cacheKey, entities); this.addToCache(cacheKey, entities);
@@ -582,12 +581,12 @@ export class QuerySystem {
* 从缓存获取查询结果 * 从缓存获取查询结果
*/ */
private getFromCache(cacheKey: string): readonly Entity[] | null { private getFromCache(cacheKey: string): readonly Entity[] | null {
const entry = this.queryCache.get(cacheKey); const entry = this._queryCache.get(cacheKey);
if (!entry) return null; if (!entry) return null;
// 检查缓存是否过期或版本过期 // 检查缓存是否过期或版本过期
if (Date.now() - entry.timestamp > this.cacheTimeout || entry.version !== this._version) { if (Date.now() - entry.timestamp > this._cacheTimeout || entry.version !== this._version) {
this.queryCache.delete(cacheKey); this._queryCache.delete(cacheKey);
return null; return null;
} }
@@ -600,11 +599,11 @@ export class QuerySystem {
*/ */
private addToCache(cacheKey: string, entities: Entity[]): void { private addToCache(cacheKey: string, entities: Entity[]): void {
// 如果缓存已满,清理最少使用的条目 // 如果缓存已满,清理最少使用的条目
if (this.queryCache.size >= this.cacheMaxSize) { if (this._queryCache.size >= this._cacheMaxSize) {
this.cleanupCache(); this.cleanupCache();
} }
this.queryCache.set(cacheKey, { this._queryCache.set(cacheKey, {
entities: entities, // 直接使用引用,通过版本号控制失效 entities: entities, // 直接使用引用,通过版本号控制失效
timestamp: Date.now(), timestamp: Date.now(),
hitCount: 0, hitCount: 0,
@@ -618,22 +617,24 @@ export class QuerySystem {
private cleanupCache(): void { private cleanupCache(): void {
// 移除过期的缓存条目 // 移除过期的缓存条目
const now = Date.now(); const now = Date.now();
for (const [key, entry] of this.queryCache.entries()) { for (const [key, entry] of this._queryCache.entries()) {
if (now - entry.timestamp > this.cacheTimeout) { if (now - entry.timestamp > this._cacheTimeout) {
this.queryCache.delete(key); this._queryCache.delete(key);
} }
} }
// 如果还是太满,移除最少使用的条目 // 如果还是太满,移除最少使用的条目
if (this.queryCache.size >= this.cacheMaxSize) { if (this._queryCache.size >= this._cacheMaxSize) {
let minHitCount = Infinity; let minHitCount = Infinity;
let oldestKey = ''; let oldestKey = '';
let oldestTimestamp = Infinity; let oldestTimestamp = Infinity;
// 单次遍历找到最少使用或最旧的条目 // 单次遍历找到最少使用或最旧的条目
for (const [key, entry] of this.queryCache.entries()) { for (const [key, entry] of this._queryCache.entries()) {
if (entry.hitCount < minHitCount || if (
(entry.hitCount === minHitCount && entry.timestamp < oldestTimestamp)) { entry.hitCount < minHitCount ||
(entry.hitCount === minHitCount && entry.timestamp < oldestTimestamp)
) {
minHitCount = entry.hitCount; minHitCount = entry.hitCount;
oldestKey = key; oldestKey = key;
oldestTimestamp = entry.timestamp; oldestTimestamp = entry.timestamp;
@@ -641,7 +642,7 @@ export class QuerySystem {
} }
if (oldestKey) { if (oldestKey) {
this.queryCache.delete(oldestKey); this._queryCache.delete(oldestKey);
} }
} }
} }
@@ -650,8 +651,8 @@ export class QuerySystem {
* 清除所有查询缓存 * 清除所有查询缓存
*/ */
private clearQueryCache(): void { private clearQueryCache(): void {
this.queryCache.clear(); this._queryCache.clear();
this.componentMaskCache.clear(); this._componentMaskCache.clear();
} }
/** /**
@@ -679,10 +680,13 @@ export class QuerySystem {
} }
// 多组件查询:使用排序后的类型名称创建键 // 多组件查询:使用排序后的类型名称创建键
const sortKey = componentTypes.map(t => { const sortKey = componentTypes
const name = getComponentTypeName(t); .map((t) => {
return name; const name = getComponentTypeName(t);
}).sort().join(','); return name;
})
.sort()
.join(',');
const fullKey = `${prefix}:${sortKey}`; const fullKey = `${prefix}:${sortKey}`;
@@ -724,10 +728,7 @@ export class QuerySystem {
* }); * });
* ``` * ```
*/ */
public createReactiveQuery( public createReactiveQuery(componentTypes: ComponentType[], config?: ReactiveQueryConfig): ReactiveQuery {
componentTypes: ComponentType[],
config?: ReactiveQueryConfig
): ReactiveQuery {
if (!componentTypes || componentTypes.length === 0) { if (!componentTypes || componentTypes.length === 0) {
throw new Error('组件类型列表不能为空'); throw new Error('组件类型列表不能为空');
} }
@@ -741,10 +742,7 @@ export class QuerySystem {
const query = new ReactiveQuery(condition, config); const query = new ReactiveQuery(condition, config);
const initialEntities = this.executeTraditionalQuery( const initialEntities = this.executeTraditionalQuery(QueryConditionType.ALL, componentTypes);
QueryConditionType.ALL,
componentTypes
);
query.initializeWith(initialEntities); query.initializeWith(initialEntities);
const cacheKey = this.generateCacheKey('all', componentTypes); const cacheKey = this.generateCacheKey('all', componentTypes);
@@ -810,18 +808,21 @@ export class QuerySystem {
*/ */
private createComponentMask(componentTypes: ComponentType[]): BitMask64Data { private createComponentMask(componentTypes: ComponentType[]): BitMask64Data {
// 生成缓存键 // 生成缓存键
const cacheKey = componentTypes.map(t => { const cacheKey = componentTypes
return getComponentTypeName(t); .map((t) => {
}).sort().join(','); return getComponentTypeName(t);
})
.sort()
.join(',');
// 检查缓存 // 检查缓存
const cached = this.componentMaskCache.get(cacheKey); const cached = this._componentMaskCache.get(cacheKey);
if (cached) { if (cached) {
return cached; return cached;
} }
// 使用ComponentRegistry而不是ComponentTypeManager,确保bitIndex一致 // 使用ComponentRegistry而不是ComponentTypeManager,确保bitIndex一致
let mask = BitMask64Utils.clone(BitMask64Utils.ZERO); const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const type of componentTypes) { for (const type of componentTypes) {
// 确保组件已注册 // 确保组件已注册
if (!ComponentRegistry.isRegistered(type)) { if (!ComponentRegistry.isRegistered(type)) {
@@ -832,7 +833,7 @@ export class QuerySystem {
} }
// 缓存结果 // 缓存结果
this.componentMaskCache.set(cacheKey, mask); this._componentMaskCache.set(cacheKey, mask);
return mask; return mask;
} }
@@ -842,20 +843,20 @@ export class QuerySystem {
public get version(): number { public get version(): number {
return this._version; return this._version;
} }
/** /**
* 获取所有实体 * 获取所有实体
*/ */
public getAllEntities(): readonly Entity[] { public getAllEntities(): readonly Entity[] {
return this.entities; return this._entities;
} }
/** /**
* 获取系统统计信息 * 获取系统统计信息
* *
* 返回查询系统的详细统计信息,包括实体数量、索引状态、 * 返回查询系统的详细统计信息,包括实体数量、索引状态、
* 查询性能统计等,用于性能监控和调试。 * 查询性能统计等,用于性能监控和调试。
* *
* @returns 系统统计信息对象 * @returns 系统统计信息对象
*/ */
public getStats(): { public getStats(): {
@@ -881,30 +882,34 @@ export class QuerySystem {
size: number; size: number;
hitRate: string; hitRate: string;
}; };
} { } {
return { return {
entityCount: this.entities.length, entityCount: this._entities.length,
indexStats: { indexStats: {
componentIndexSize: this.archetypeSystem.getAllArchetypes().length, componentIndexSize: this._archetypeSystem.getAllArchetypes().length,
tagIndexSize: this.entityIndex.byTag.size, tagIndexSize: this._entityIndex.byTag.size,
nameIndexSize: this.entityIndex.byName.size nameIndexSize: this._entityIndex.byName.size
}, },
queryStats: { queryStats: {
...this.queryStats, ...this._queryStats,
cacheHitRate: this.queryStats.totalQueries > 0 ? cacheHitRate:
(this.queryStats.cacheHits / this.queryStats.totalQueries * 100).toFixed(2) + '%' : '0%' this._queryStats.totalQueries > 0
? ((this._queryStats.cacheHits / this._queryStats.totalQueries) * 100).toFixed(2) + '%'
: '0%'
}, },
optimizationStats: { optimizationStats: {
archetypeSystem: this.archetypeSystem.getAllArchetypes().map(a => ({ archetypeSystem: this._archetypeSystem.getAllArchetypes().map((a) => ({
id: a.id, id: a.id,
componentTypes: a.componentTypes.map(t => getComponentTypeName(t)), componentTypes: a.componentTypes.map((t) => getComponentTypeName(t)),
entityCount: a.entities.size entityCount: a.entities.size
})) }))
}, },
cacheStats: { cacheStats: {
size: this._reactiveQueries.size, size: this._reactiveQueries.size,
hitRate: this.queryStats.totalQueries > 0 ? hitRate:
(this.queryStats.cacheHits / this.queryStats.totalQueries * 100).toFixed(2) + '%' : '0%' this._queryStats.totalQueries > 0
? ((this._queryStats.cacheHits / this._queryStats.totalQueries) * 100).toFixed(2) + '%'
: '0%'
} }
}; };
} }
@@ -915,7 +920,7 @@ export class QuerySystem {
* @param entity 要查询的实体 * @param entity 要查询的实体
*/ */
public getEntityArchetype(entity: Entity): Archetype | undefined { public getEntityArchetype(entity: Entity): Archetype | undefined {
return this.archetypeSystem.getEntityArchetype(entity); return this._archetypeSystem.getEntityArchetype(entity);
} }
// ============================================================ // ============================================================
@@ -941,10 +946,7 @@ export class QuerySystem {
* @param componentTypes 组件类型列表 * @param componentTypes 组件类型列表
* @returns 响应式查询实例 * @returns 响应式查询实例
*/ */
private getOrCreateReactiveQuery( private getOrCreateReactiveQuery(queryType: QueryConditionType, componentTypes: ComponentType[]): ReactiveQuery {
queryType: QueryConditionType,
componentTypes: ComponentType[]
): ReactiveQuery {
// 生成缓存键(与传统缓存键格式一致) // 生成缓存键(与传统缓存键格式一致)
const cacheKey = this.generateCacheKey(queryType, componentTypes); const cacheKey = this.generateCacheKey(queryType, componentTypes);
@@ -996,13 +998,10 @@ export class QuerySystem {
* @param componentTypes 组件类型列表 * @param componentTypes 组件类型列表
* @returns 匹配的实体列表 * @returns 匹配的实体列表
*/ */
private executeTraditionalQuery( private executeTraditionalQuery(queryType: QueryConditionType, componentTypes: ComponentType[]): Entity[] {
queryType: QueryConditionType,
componentTypes: ComponentType[]
): Entity[] {
switch (queryType) { switch (queryType) {
case QueryConditionType.ALL: { case QueryConditionType.ALL: {
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND'); const archetypeResult = this._archetypeSystem.queryArchetypes(componentTypes, 'AND');
const entities: Entity[] = []; const entities: Entity[] = [];
for (const archetype of archetypeResult.archetypes) { for (const archetype of archetypeResult.archetypes) {
for (const entity of archetype.entities) { for (const entity of archetype.entities) {
@@ -1012,7 +1011,7 @@ export class QuerySystem {
return entities; return entities;
} }
case QueryConditionType.ANY: { case QueryConditionType.ANY: {
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'OR'); const archetypeResult = this._archetypeSystem.queryArchetypes(componentTypes, 'OR');
const entities: Entity[] = []; const entities: Entity[] = [];
for (const archetype of archetypeResult.archetypes) { for (const archetype of archetypeResult.archetypes) {
for (const entity of archetype.entities) { for (const entity of archetype.entities) {
@@ -1023,9 +1022,7 @@ export class QuerySystem {
} }
case QueryConditionType.NONE: { case QueryConditionType.NONE: {
const mask = this.createComponentMask(componentTypes); const mask = this.createComponentMask(componentTypes);
return this.entities.filter(entity => return this._entities.filter((entity) => BitMask64Utils.hasNone(entity.componentMask, mask));
BitMask64Utils.hasNone(entity.componentMask, mask)
);
} }
default: default:
return []; return [];
@@ -1139,10 +1136,10 @@ export class QuerySystem {
/** /**
* 查询构建器 * 查询构建器
* *
* 提供链式API来构建复杂的实体查询条件。 * 提供链式API来构建复杂的实体查询条件。
* 支持组合多种查询条件,创建灵活的查询表达式。 * 支持组合多种查询条件,创建灵活的查询表达式。
* *
* @example * @example
* ```typescript * ```typescript
* const result = new QueryBuilder(querySystem) * const result = new QueryBuilder(querySystem)
@@ -1162,7 +1159,7 @@ export class QueryBuilder {
/** /**
* 添加"必须包含所有组件"条件 * 添加"必须包含所有组件"条件
* *
* @param componentTypes 必须包含的组件类型 * @param componentTypes 必须包含的组件类型
* @returns 查询构建器实例,支持链式调用 * @returns 查询构建器实例,支持链式调用
*/ */
@@ -1177,7 +1174,7 @@ export class QueryBuilder {
/** /**
* 添加"必须包含任意组件"条件 * 添加"必须包含任意组件"条件
* *
* @param componentTypes 必须包含其中任意一个的组件类型 * @param componentTypes 必须包含其中任意一个的组件类型
* @returns 查询构建器实例,支持链式调用 * @returns 查询构建器实例,支持链式调用
*/ */
@@ -1192,7 +1189,7 @@ export class QueryBuilder {
/** /**
* 添加"不能包含任何组件"条件 * 添加"不能包含任何组件"条件
* *
* @param componentTypes 不能包含的组件类型 * @param componentTypes 不能包含的组件类型
* @returns 查询构建器实例,支持链式调用 * @returns 查询构建器实例,支持链式调用
*/ */
@@ -1207,9 +1204,9 @@ export class QueryBuilder {
/** /**
* 执行查询并返回结果 * 执行查询并返回结果
* *
* 根据已添加的查询条件执行实体查询。 * 根据已添加的查询条件执行实体查询。
* *
* @returns 查询结果,包含匹配的实体和性能信息 * @returns 查询结果,包含匹配的实体和性能信息
*/ */
public execute(): QueryResult { public execute(): QueryResult {
@@ -1241,7 +1238,7 @@ export class QueryBuilder {
* 创建组件掩码 * 创建组件掩码
*/ */
private createComponentMask(componentTypes: ComponentType[]): BitMask64Data { private createComponentMask(componentTypes: ComponentType[]): BitMask64Data {
let mask = BitMask64Utils.clone(BitMask64Utils.ZERO); const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const type of componentTypes) { for (const type of componentTypes) {
try { try {
const bitMask = ComponentRegistry.getBitMask(type); const bitMask = ComponentRegistry.getBitMask(type);
@@ -1255,13 +1252,13 @@ export class QueryBuilder {
/** /**
* 重置查询构建器 * 重置查询构建器
* *
* 清除所有已添加的查询条件,重新开始构建查询。 * 清除所有已添加的查询条件,重新开始构建查询。
* *
* @returns 查询构建器实例,支持链式调用 * @returns 查询构建器实例,支持链式调用
*/ */
public reset(): QueryBuilder { public reset(): QueryBuilder {
this.conditions = []; this.conditions = [];
return this; return this;
} }
} }
+1 -1
View File
@@ -136,7 +136,7 @@ export class ReactiveQuery {
private generateQueryId(): string { private generateQueryId(): string {
const typeStr = this._condition.type; const typeStr = this._condition.type;
const componentsStr = this._condition.componentTypes const componentsStr = this._condition.componentTypes
.map(t => t.name) .map((t) => t.name)
.sort() .sort()
.join(','); .join(',');
return `${typeStr}:${componentsStr}`; return `${typeStr}:${componentsStr}`;
+51 -51
View File
@@ -345,12 +345,12 @@ export class SoAStorage<T extends Component> {
private _size = 0; private _size = 0;
private _capacity = 1000; private _capacity = 1000;
public readonly type: ComponentType<T>; public readonly type: ComponentType<T>;
constructor(componentType: ComponentType<T>) { constructor(componentType: ComponentType<T>) {
this.type = componentType; this.type = componentType;
this.initializeFields(componentType); this.initializeFields(componentType);
} }
private initializeFields(componentType: ComponentType<T>): void { private initializeFields(componentType: ComponentType<T>): void {
const instance = new componentType(); const instance = new componentType();
const highPrecisionFields = (componentType as any).__highPrecisionFields || new Set(); const highPrecisionFields = (componentType as any).__highPrecisionFields || new Set();
@@ -368,12 +368,12 @@ export class SoAStorage<T extends Component> {
const serializeSetFields = (componentType as any).__serializeSetFields || new Set(); const serializeSetFields = (componentType as any).__serializeSetFields || new Set();
const serializeArrayFields = (componentType as any).__serializeArrayFields || new Set(); const serializeArrayFields = (componentType as any).__serializeArrayFields || new Set();
// const deepCopyFields = (componentType as any).__deepCopyFields || new Set(); // 未使用,但保留供future使用 // const deepCopyFields = (componentType as any).__deepCopyFields || new Set(); // 未使用,但保留供future使用
for (const key in instance) { for (const key in instance) {
if (instance.hasOwnProperty(key) && key !== 'id') { if (instance.hasOwnProperty(key) && key !== 'id') {
const value = (instance as any)[key]; const value = (instance as any)[key];
const type = typeof value; const type = typeof value;
if (type === 'number') { if (type === 'number') {
if (highPrecisionFields.has(key)) { if (highPrecisionFields.has(key)) {
// 标记为高精度,作为复杂对象处理 // 标记为高精度,作为复杂对象处理
@@ -438,14 +438,14 @@ export class SoAStorage<T extends Component> {
} }
} }
} }
public addComponent(entityId: number, component: T): void { public addComponent(entityId: number, component: T): void {
if (this.entityToIndex.has(entityId)) { if (this.entityToIndex.has(entityId)) {
const index = this.entityToIndex.get(entityId)!; const index = this.entityToIndex.get(entityId)!;
this.updateComponentAtIndex(index, component); this.updateComponentAtIndex(index, component);
return; return;
} }
let index: number; let index: number;
if (this.freeIndices.length > 0) { if (this.freeIndices.length > 0) {
index = this.freeIndices.pop()!; index = this.freeIndices.pop()!;
@@ -455,13 +455,13 @@ export class SoAStorage<T extends Component> {
this.resize(this._capacity * 2); this.resize(this._capacity * 2);
} }
} }
this.entityToIndex.set(entityId, index); this.entityToIndex.set(entityId, index);
this.indexToEntity[index] = entityId; this.indexToEntity[index] = entityId;
this.updateComponentAtIndex(index, component); this.updateComponentAtIndex(index, component);
this._size++; this._size++;
} }
private updateComponentAtIndex(index: number, component: T): void { private updateComponentAtIndex(index: number, component: T): void {
const entityId = this.indexToEntity[index]!; const entityId = this.indexToEntity[index]!;
const complexFieldMap = new Map<string, any>(); const complexFieldMap = new Map<string, any>();
@@ -470,13 +470,13 @@ export class SoAStorage<T extends Component> {
const serializeSetFields = (this.type as any).__serializeSetFields || new Set(); const serializeSetFields = (this.type as any).__serializeSetFields || new Set();
const serializeArrayFields = (this.type as any).__serializeArrayFields || new Set(); const serializeArrayFields = (this.type as any).__serializeArrayFields || new Set();
const deepCopyFields = (this.type as any).__deepCopyFields || new Set(); const deepCopyFields = (this.type as any).__deepCopyFields || new Set();
// 处理所有字段 // 处理所有字段
for (const key in component) { for (const key in component) {
if (component.hasOwnProperty(key) && key !== 'id') { if (component.hasOwnProperty(key) && key !== 'id') {
const value = (component as any)[key]; const value = (component as any)[key];
const type = typeof value; const type = typeof value;
if (type === 'number') { if (type === 'number') {
if (highPrecisionFields.has(key) || !this.fields.has(key)) { if (highPrecisionFields.has(key) || !this.fields.has(key)) {
// 标记为高精度或未在TypedArray中的数值作为复杂对象存储 // 标记为高精度或未在TypedArray中的数值作为复杂对象存储
@@ -487,7 +487,7 @@ export class SoAStorage<T extends Component> {
array[index] = value; array[index] = value;
} }
} else if (type === 'boolean' && this.fields.has(key)) { } else if (type === 'boolean' && this.fields.has(key)) {
// 布尔值存储到TypedArray // 布尔值存储到TypedArray
const array = this.fields.get(key)!; const array = this.fields.get(key)!;
array[index] = value ? 1 : 0; array[index] = value ? 1 : 0;
} else if (this.stringFields.has(key)) { } else if (this.stringFields.has(key)) {
@@ -509,13 +509,13 @@ export class SoAStorage<T extends Component> {
} }
} }
} }
// 存储复杂字段 // 存储复杂字段
if (complexFieldMap.size > 0) { if (complexFieldMap.size > 0) {
this.complexFields.set(entityId, complexFieldMap); this.complexFields.set(entityId, complexFieldMap);
} }
} }
/** /**
* 序列化值为JSON字符串 * 序列化值为JSON字符串
*/ */
@@ -539,14 +539,14 @@ export class SoAStorage<T extends Component> {
return '{}'; return '{}';
} }
} }
/** /**
* 反序列化JSON字符串为值 * 反序列化JSON字符串为值
*/ */
private deserializeValue(serialized: string, key: string, mapFields: Set<string>, setFields: Set<string>, arrayFields: Set<string>): any { private deserializeValue(serialized: string, key: string, mapFields: Set<string>, setFields: Set<string>, arrayFields: Set<string>): any {
try { try {
const parsed = JSON.parse(serialized); const parsed = JSON.parse(serialized);
if (mapFields.has(key)) { if (mapFields.has(key)) {
// 恢复Map // 恢复Map
return new Map(parsed); return new Map(parsed);
@@ -564,7 +564,7 @@ export class SoAStorage<T extends Component> {
return null; return null;
} }
} }
/** /**
* 深拷贝对象 * 深拷贝对象
*/ */
@@ -572,15 +572,15 @@ export class SoAStorage<T extends Component> {
if (obj === null || typeof obj !== 'object') { if (obj === null || typeof obj !== 'object') {
return obj; return obj;
} }
if (obj instanceof Date) { if (obj instanceof Date) {
return new Date(obj.getTime()); return new Date(obj.getTime());
} }
if (obj instanceof Array) { if (obj instanceof Array) {
return obj.map(item => this.deepClone(item)); return obj.map((item) => this.deepClone(item));
} }
if (obj instanceof Map) { if (obj instanceof Map) {
const cloned = new Map(); const cloned = new Map();
for (const [key, value] of obj.entries()) { for (const [key, value] of obj.entries()) {
@@ -588,7 +588,7 @@ export class SoAStorage<T extends Component> {
} }
return cloned; return cloned;
} }
if (obj instanceof Set) { if (obj instanceof Set) {
const cloned = new Set(); const cloned = new Set();
for (const value of obj.values()) { for (const value of obj.values()) {
@@ -596,7 +596,7 @@ export class SoAStorage<T extends Component> {
} }
return cloned; return cloned;
} }
// 普通对象 // 普通对象
const cloned: any = {}; const cloned: any = {};
for (const key in obj) { for (const key in obj) {
@@ -606,36 +606,36 @@ export class SoAStorage<T extends Component> {
} }
return cloned; return cloned;
} }
public getComponent(entityId: number): T | null { public getComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId); const index = this.entityToIndex.get(entityId);
if (index === undefined) { if (index === undefined) {
return null; return null;
} }
// 创建真正的组件实例以保持兼容性 // 创建真正的组件实例以保持兼容性
const component = new this.type() as any; const component = new this.type() as any;
const serializeMapFields = (this.type as any).__serializeMapFields || new Set(); const serializeMapFields = (this.type as any).__serializeMapFields || new Set();
const serializeSetFields = (this.type as any).__serializeSetFields || new Set(); const serializeSetFields = (this.type as any).__serializeSetFields || new Set();
const serializeArrayFields = (this.type as any).__serializeArrayFields || new Set(); const serializeArrayFields = (this.type as any).__serializeArrayFields || new Set();
// 恢复数值字段 // 恢复数值字段
for (const [fieldName, array] of this.fields.entries()) { for (const [fieldName, array] of this.fields.entries()) {
const value = array[index]; const value = array[index];
const fieldType = this.getFieldType(fieldName); const fieldType = this.getFieldType(fieldName);
if (fieldType === 'boolean') { if (fieldType === 'boolean') {
component[fieldName] = value === 1; component[fieldName] = value === 1;
} else { } else {
component[fieldName] = value; component[fieldName] = value;
} }
} }
// 恢复字符串字段 // 恢复字符串字段
for (const [fieldName, stringArray] of this.stringFields.entries()) { for (const [fieldName, stringArray] of this.stringFields.entries()) {
component[fieldName] = stringArray[index]; component[fieldName] = stringArray[index];
} }
// 恢复序列化字段 // 恢复序列化字段
for (const [fieldName, serializedArray] of this.serializedFields.entries()) { for (const [fieldName, serializedArray] of this.serializedFields.entries()) {
const serialized = serializedArray[index]; const serialized = serializedArray[index];
@@ -643,7 +643,7 @@ export class SoAStorage<T extends Component> {
component[fieldName] = this.deserializeValue(serialized, fieldName, serializeMapFields, serializeSetFields, serializeArrayFields); component[fieldName] = this.deserializeValue(serialized, fieldName, serializeMapFields, serializeSetFields, serializeArrayFields);
} }
} }
// 恢复复杂字段 // 恢复复杂字段
const complexFieldMap = this.complexFields.get(entityId); const complexFieldMap = this.complexFields.get(entityId);
if (complexFieldMap) { if (complexFieldMap) {
@@ -651,39 +651,39 @@ export class SoAStorage<T extends Component> {
component[fieldName] = value; component[fieldName] = value;
} }
} }
return component as T; return component as T;
} }
private getFieldType(fieldName: string): string { private getFieldType(fieldName: string): string {
// 通过创建临时实例检查字段类型 // 通过创建临时实例检查字段类型
const tempInstance = new this.type(); const tempInstance = new this.type();
const value = (tempInstance as any)[fieldName]; const value = (tempInstance as any)[fieldName];
return typeof value; return typeof value;
} }
public hasComponent(entityId: number): boolean { public hasComponent(entityId: number): boolean {
return this.entityToIndex.has(entityId); return this.entityToIndex.has(entityId);
} }
public removeComponent(entityId: number): T | null { public removeComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId); const index = this.entityToIndex.get(entityId);
if (index === undefined) { if (index === undefined) {
return null; return null;
} }
// 获取组件副本以便返回 // 获取组件副本以便返回
const component = this.getComponent(entityId); const component = this.getComponent(entityId);
// 清理复杂字段 // 清理复杂字段
this.complexFields.delete(entityId); this.complexFields.delete(entityId);
this.entityToIndex.delete(entityId); this.entityToIndex.delete(entityId);
this.freeIndices.push(index); this.freeIndices.push(index);
this._size--; this._size--;
return component; return component;
} }
private resize(newCapacity: number): void { private resize(newCapacity: number): void {
// 调整数值字段的TypedArray // 调整数值字段的TypedArray
for (const [fieldName, oldArray] of this.fields.entries()) { for (const [fieldName, oldArray] of this.fields.entries()) {
@@ -716,7 +716,7 @@ export class SoAStorage<T extends Component> {
newArray.set(oldArray); newArray.set(oldArray);
this.fields.set(fieldName, newArray); this.fields.set(fieldName, newArray);
} }
// 调整字符串字段的数组 // 调整字符串字段的数组
for (const [fieldName, oldArray] of this.stringFields.entries()) { for (const [fieldName, oldArray] of this.stringFields.entries()) {
const newArray = new Array(newCapacity); const newArray = new Array(newCapacity);
@@ -725,7 +725,7 @@ export class SoAStorage<T extends Component> {
} }
this.stringFields.set(fieldName, newArray); this.stringFields.set(fieldName, newArray);
} }
// 调整序列化字段的数组 // 调整序列化字段的数组
for (const [fieldName, oldArray] of this.serializedFields.entries()) { for (const [fieldName, oldArray] of this.serializedFields.entries()) {
const newArray = new Array(newCapacity); const newArray = new Array(newCapacity);
@@ -734,14 +734,14 @@ export class SoAStorage<T extends Component> {
} }
this.serializedFields.set(fieldName, newArray); this.serializedFields.set(fieldName, newArray);
} }
this._capacity = newCapacity; this._capacity = newCapacity;
} }
public getActiveIndices(): number[] { public getActiveIndices(): number[] {
return Array.from(this.entityToIndex.values()); return Array.from(this.entityToIndex.values());
} }
public getFieldArray(fieldName: string): SupportedTypedArray | null { public getFieldArray(fieldName: string): SupportedTypedArray | null {
return this.fields.get(fieldName) || null; return this.fields.get(fieldName) || null;
} }
@@ -749,38 +749,38 @@ export class SoAStorage<T extends Component> {
public getTypedFieldArray<K extends keyof T>(fieldName: K): SupportedTypedArray | null { public getTypedFieldArray<K extends keyof T>(fieldName: K): SupportedTypedArray | null {
return this.fields.get(String(fieldName)) || null; return this.fields.get(String(fieldName)) || null;
} }
public getEntityIndex(entityId: number): number | undefined { public getEntityIndex(entityId: number): number | undefined {
return this.entityToIndex.get(entityId); return this.entityToIndex.get(entityId);
} }
public getEntityIdByIndex(index: number): number | undefined { public getEntityIdByIndex(index: number): number | undefined {
return this.indexToEntity[index]; return this.indexToEntity[index];
} }
public size(): number { public size(): number {
return this._size; return this._size;
} }
public clear(): void { public clear(): void {
this.entityToIndex.clear(); this.entityToIndex.clear();
this.indexToEntity = []; this.indexToEntity = [];
this.freeIndices = []; this.freeIndices = [];
this.complexFields.clear(); this.complexFields.clear();
this._size = 0; this._size = 0;
// 重置数值字段数组 // 重置数值字段数组
for (const array of this.fields.values()) { for (const array of this.fields.values()) {
array.fill(0); array.fill(0);
} }
// 重置字符串字段数组 // 重置字符串字段数组
for (const stringArray of this.stringFields.values()) { for (const stringArray of this.stringFields.values()) {
for (let i = 0; i < stringArray.length; i++) { for (let i = 0; i < stringArray.length; i++) {
stringArray[i] = undefined as any; stringArray[i] = undefined as any;
} }
} }
// 重置序列化字段数组 // 重置序列化字段数组
for (const serializedArray of this.serializedFields.values()) { for (const serializedArray of this.serializedFields.values()) {
for (let i = 0; i < serializedArray.length; i++) { for (let i = 0; i < serializedArray.length; i++) {
@@ -884,7 +884,7 @@ export class SoAStorage<T extends Component> {
bytesPerElement = 4; bytesPerElement = 4;
typeName = 'unknown'; typeName = 'unknown';
} }
const memory = array.length * bytesPerElement; const memory = array.length * bytesPerElement;
totalMemory += memory; totalMemory += memory;
@@ -914,4 +914,4 @@ export class SoAStorage<T extends Component> {
const activeIndices = this.getActiveIndices(); const activeIndices = this.getActiveIndices();
operation(this.fields, activeIndices); operation(this.fields, activeIndices);
} }
} }
+1 -1
View File
@@ -1,3 +1,3 @@
export { ComponentPool, ComponentPoolManager } from '../ComponentPool'; export { ComponentPool, ComponentPoolManager } from '../ComponentPool';
export { ComponentStorage, ComponentRegistry } from '../ComponentStorage'; export { ComponentStorage, ComponentRegistry } from '../ComponentStorage';
export { EnableSoA, HighPrecision, Float64, Float32, Int32, SerializeMap, SoAStorage } from '../SoAStorage'; export { EnableSoA, HighPrecision, Float64, Float32, Int32, SerializeMap, SoAStorage } from '../SoAStorage';
@@ -46,4 +46,4 @@ export {
// 类型定义 // 类型定义
SupportedTypedArray SupportedTypedArray
} from './SoAStorage'; } from './SoAStorage';
+18 -18
View File
@@ -13,14 +13,14 @@ export enum ECSEventType {
ENTITY_TAG_ADDED = 'entity:tag:added', ENTITY_TAG_ADDED = 'entity:tag:added',
ENTITY_TAG_REMOVED = 'entity:tag:removed', ENTITY_TAG_REMOVED = 'entity:tag:removed',
ENTITY_NAME_CHANGED = 'entity:name:changed', ENTITY_NAME_CHANGED = 'entity:name:changed',
// 组件相关事件 // 组件相关事件
COMPONENT_ADDED = 'component:added', COMPONENT_ADDED = 'component:added',
COMPONENT_REMOVED = 'component:removed', COMPONENT_REMOVED = 'component:removed',
COMPONENT_MODIFIED = 'component:modified', COMPONENT_MODIFIED = 'component:modified',
COMPONENT_ENABLED = 'component:enabled', COMPONENT_ENABLED = 'component:enabled',
COMPONENT_DISABLED = 'component:disabled', COMPONENT_DISABLED = 'component:disabled',
// 系统相关事件 // 系统相关事件
SYSTEM_ADDED = 'system:added', SYSTEM_ADDED = 'system:added',
SYSTEM_REMOVED = 'system:removed', SYSTEM_REMOVED = 'system:removed',
@@ -29,7 +29,7 @@ export enum ECSEventType {
SYSTEM_PROCESSING_START = 'system:processing:start', SYSTEM_PROCESSING_START = 'system:processing:start',
SYSTEM_PROCESSING_END = 'system:processing:end', SYSTEM_PROCESSING_END = 'system:processing:end',
SYSTEM_ERROR = 'system:error', SYSTEM_ERROR = 'system:error',
// 场景相关事件 // 场景相关事件
SCENE_CREATED = 'scene:created', SCENE_CREATED = 'scene:created',
SCENE_DESTROYED = 'scene:destroyed', SCENE_DESTROYED = 'scene:destroyed',
@@ -37,41 +37,41 @@ export enum ECSEventType {
SCENE_DEACTIVATED = 'scene:deactivated', SCENE_DEACTIVATED = 'scene:deactivated',
SCENE_PAUSED = 'scene:paused', SCENE_PAUSED = 'scene:paused',
SCENE_RESUMED = 'scene:resumed', SCENE_RESUMED = 'scene:resumed',
// 查询相关事件 // 查询相关事件
QUERY_EXECUTED = 'query:executed', QUERY_EXECUTED = 'query:executed',
QUERY_CACHE_HIT = 'query:cache:hit', QUERY_CACHE_HIT = 'query:cache:hit',
QUERY_CACHE_MISS = 'query:cache:miss', QUERY_CACHE_MISS = 'query:cache:miss',
QUERY_OPTIMIZED = 'query:optimized', QUERY_OPTIMIZED = 'query:optimized',
// 性能相关事件 // 性能相关事件
PERFORMANCE_WARNING = 'performance:warning', PERFORMANCE_WARNING = 'performance:warning',
PERFORMANCE_CRITICAL = 'performance:critical', PERFORMANCE_CRITICAL = 'performance:critical',
MEMORY_USAGE_HIGH = 'memory:usage:high', MEMORY_USAGE_HIGH = 'memory:usage:high',
FRAME_RATE_DROP = 'frame:rate:drop', FRAME_RATE_DROP = 'frame:rate:drop',
// 索引相关事件 // 索引相关事件
INDEX_CREATED = 'index:created', INDEX_CREATED = 'index:created',
INDEX_UPDATED = 'index:updated', INDEX_UPDATED = 'index:updated',
INDEX_OPTIMIZED = 'index:optimized', INDEX_OPTIMIZED = 'index:optimized',
// Archetype相关事件 // Archetype相关事件
ARCHETYPE_CREATED = 'archetype:created', ARCHETYPE_CREATED = 'archetype:created',
ARCHETYPE_ENTITY_ADDED = 'archetype:entity:added', ARCHETYPE_ENTITY_ADDED = 'archetype:entity:added',
ARCHETYPE_ENTITY_REMOVED = 'archetype:entity:removed', ARCHETYPE_ENTITY_REMOVED = 'archetype:entity:removed',
// 脏标记相关事件 // 脏标记相关事件
DIRTY_MARK_ADDED = 'dirty:mark:added', DIRTY_MARK_ADDED = 'dirty:mark:added',
DIRTY_BATCH_PROCESSED = 'dirty:batch:processed', DIRTY_BATCH_PROCESSED = 'dirty:batch:processed',
// 错误和警告事件 // 错误和警告事件
ERROR_OCCURRED = 'error:occurred', ERROR_OCCURRED = 'error:occurred',
WARNING_ISSUED = 'warning:issued', WARNING_ISSUED = 'warning:issued',
// 生命周期事件 // 生命周期事件
FRAMEWORK_INITIALIZED = 'framework:initialized', FRAMEWORK_INITIALIZED = 'framework:initialized',
FRAMEWORK_SHUTDOWN = 'framework:shutdown', FRAMEWORK_SHUTDOWN = 'framework:shutdown',
// 调试相关事件 // 调试相关事件
DEBUG_INFO = 'debug:info', DEBUG_INFO = 'debug:info',
DEBUG_STATS_UPDATED = 'debug:stats:updated' DEBUG_STATS_UPDATED = 'debug:stats:updated'
@@ -105,7 +105,7 @@ export const EVENT_TYPES = {
TAG_REMOVED: ECSEventType.ENTITY_TAG_REMOVED, TAG_REMOVED: ECSEventType.ENTITY_TAG_REMOVED,
NAME_CHANGED: ECSEventType.ENTITY_NAME_CHANGED NAME_CHANGED: ECSEventType.ENTITY_NAME_CHANGED
}, },
// 组件事件 // 组件事件
COMPONENT: { COMPONENT: {
ADDED: ECSEventType.COMPONENT_ADDED, ADDED: ECSEventType.COMPONENT_ADDED,
@@ -114,7 +114,7 @@ export const EVENT_TYPES = {
ENABLED: ECSEventType.COMPONENT_ENABLED, ENABLED: ECSEventType.COMPONENT_ENABLED,
DISABLED: ECSEventType.COMPONENT_DISABLED DISABLED: ECSEventType.COMPONENT_DISABLED
}, },
// 系统事件 // 系统事件
SYSTEM: { SYSTEM: {
ADDED: ECSEventType.SYSTEM_ADDED, ADDED: ECSEventType.SYSTEM_ADDED,
@@ -125,7 +125,7 @@ export const EVENT_TYPES = {
PROCESSING_END: ECSEventType.SYSTEM_PROCESSING_END, PROCESSING_END: ECSEventType.SYSTEM_PROCESSING_END,
ERROR: ECSEventType.SYSTEM_ERROR ERROR: ECSEventType.SYSTEM_ERROR
}, },
// 性能事件 // 性能事件
PERFORMANCE: { PERFORMANCE: {
WARNING: ECSEventType.PERFORMANCE_WARNING, WARNING: ECSEventType.PERFORMANCE_WARNING,
@@ -147,7 +147,7 @@ export class EventTypeValidator {
...Object.values(EVENT_TYPES.SYSTEM), ...Object.values(EVENT_TYPES.SYSTEM),
...Object.values(EVENT_TYPES.PERFORMANCE) ...Object.values(EVENT_TYPES.PERFORMANCE)
]); ]);
/** /**
* 验证事件类型是否有效 * 验证事件类型是否有效
* @param eventType 事件类型 * @param eventType 事件类型
@@ -156,7 +156,7 @@ export class EventTypeValidator {
public static isValid(eventType: string): boolean { public static isValid(eventType: string): boolean {
return this.validTypes.has(eventType); return this.validTypes.has(eventType);
} }
/** /**
* 获取所有有效的事件类型 * 获取所有有效的事件类型
* @returns 事件类型数组 * @returns 事件类型数组
@@ -164,7 +164,7 @@ export class EventTypeValidator {
public static getAllValidTypes(): string[] { public static getAllValidTypes(): string[] {
return Array.from(this.validTypes); return Array.from(this.validTypes);
} }
/** /**
* 添加自定义事件类型 * 添加自定义事件类型
* @param eventType 事件类型 * @param eventType 事件类型
@@ -172,7 +172,7 @@ export class EventTypeValidator {
public static addCustomType(eventType: string): void { public static addCustomType(eventType: string): void {
this.validTypes.add(eventType); this.validTypes.add(eventType);
} }
/** /**
* 移除自定义事件类型 * 移除自定义事件类型
* @param eventType 事件类型 * @param eventType 事件类型
@@ -1,6 +1,6 @@
import type {Component} from '../Component'; import type { Component } from '../Component';
import type {EntitySystem} from '../Systems'; import type { EntitySystem } from '../Systems';
import {ComponentType} from "../../Types"; import { ComponentType } from '../../Types';
/** /**
* 存储组件类型名称的Symbol键 * 存储组件类型名称的Symbol键
@@ -15,7 +15,7 @@ export const SYSTEM_TYPE_NAME = Symbol('SystemTypeName');
/** /**
* 组件类型装饰器 * 组件类型装饰器
* 用于为组件类指定固定的类型名称,避免在代码混淆后失效 * 用于为组件类指定固定的类型名称,避免在代码混淆后失效
* *
* @param typeName 组件类型名称 * @param typeName 组件类型名称
* @example * @example
* ```typescript * ```typescript
@@ -31,10 +31,10 @@ export function ECSComponent(typeName: string) {
if (!typeName || typeof typeName !== 'string') { if (!typeName || typeof typeName !== 'string') {
throw new Error('ECSComponent装饰器必须提供有效的类型名称'); throw new Error('ECSComponent装饰器必须提供有效的类型名称');
} }
// 在构造函数上存储类型名称 // 在构造函数上存储类型名称
(target as any)[COMPONENT_TYPE_NAME] = typeName; (target as any)[COMPONENT_TYPE_NAME] = typeName;
return target; return target;
}; };
} }
@@ -107,7 +107,7 @@ export function getSystemMetadata(systemType: new (...args: any[]) => EntitySyst
/** /**
* 获取组件类型的名称,优先使用装饰器指定的名称 * 获取组件类型的名称,优先使用装饰器指定的名称
* *
* @param componentType 组件构造函数 * @param componentType 组件构造函数
* @returns 组件类型名称 * @returns 组件类型名称
*/ */
@@ -119,14 +119,14 @@ export function getComponentTypeName(
if (decoratorName) { if (decoratorName) {
return decoratorName; return decoratorName;
} }
// 回退到constructor.name // 回退到constructor.name
return componentType.name || 'UnknownComponent'; return componentType.name || 'UnknownComponent';
} }
/** /**
* 获取系统类型的名称,优先使用装饰器指定的名称 * 获取系统类型的名称,优先使用装饰器指定的名称
* *
* @param systemType 系统构造函数 * @param systemType 系统构造函数
* @returns 系统类型名称 * @returns 系统类型名称
*/ */
@@ -138,14 +138,14 @@ export function getSystemTypeName<T extends EntitySystem>(
if (decoratorName) { if (decoratorName) {
return decoratorName; return decoratorName;
} }
// 回退到constructor.name // 回退到constructor.name
return systemType.name || 'UnknownSystem'; return systemType.name || 'UnknownSystem';
} }
/** /**
* 从组件实例获取类型名称 * 从组件实例获取类型名称
* *
* @param component 组件实例 * @param component 组件实例
* @returns 组件类型名称 * @returns 组件类型名称
*/ */
@@ -155,7 +155,7 @@ export function getComponentInstanceTypeName(component: Component): string {
/** /**
* 从系统实例获取类型名称 * 从系统实例获取类型名称
* *
* @param system 系统实例 * @param system 系统实例
* @returns 系统类型名称 * @returns 系统类型名称
*/ */
+1 -1
View File
@@ -19,4 +19,4 @@ export {
ENTITY_REF_METADATA ENTITY_REF_METADATA
} from './EntityRefDecorator'; } from './EntityRefDecorator';
export type { EntityRefMetadata } from './EntityRefDecorator'; export type { EntityRefMetadata } from './EntityRefDecorator';
+104 -108
View File
@@ -1,55 +1,58 @@
import { Component } from './Component'; import { Component } from './Component';
import { ComponentRegistry, ComponentType } from './Core/ComponentStorage'; import { ComponentRegistry, ComponentType } from './Core/ComponentStorage';
import { EventBus } from './Core/EventBus';
import { BitMask64Utils, BitMask64Data } from './Utils/BigIntCompatibility'; import { BitMask64Utils, BitMask64Data } from './Utils/BigIntCompatibility';
import { createLogger } from '../Utils/Logger'; import { createLogger } from '../Utils/Logger';
import { getComponentInstanceTypeName, getComponentTypeName } from './Decorators'; import { getComponentInstanceTypeName, getComponentTypeName } from './Decorators';
import type { IScene } from './IScene'; import type { IScene } from './IScene';
/**
* 组件活跃状态变化接口
*/
interface IActiveChangeable {
onActiveChanged(): void;
}
/** /**
* 实体比较器 * 实体比较器
* *
* 用于比较两个实体的优先级,首先按更新顺序比较,然后按ID比较。 * 用于比较两个实体的优先级,首先按更新顺序比较,然后按ID比较。
*/ */
export class EntityComparer { export class EntityComparer {
/** /**
* 比较两个实体 * 比较两个实体
* *
* @param self - 第一个实体 * @param self - 第一个实体
* @param other - 第二个实体 * @param other - 第二个实体
* @returns 比较结果,负数表示self优先级更高,正数表示other优先级更高,0表示相等 * @returns 比较结果,负数表示self优先级更高,正数表示other优先级更高,0表示相等
*/ */
public compare(self: Entity, other: Entity): number { public compare(self: Entity, other: Entity): number {
let compare = self.updateOrder - other.updateOrder; let compare = self.updateOrder - other.updateOrder;
if (compare == 0) if (compare == 0) compare = self.id - other.id;
compare = self.id - other.id;
return compare; return compare;
} }
} }
/** /**
* 游戏实体类 * 游戏实体类
* *
* ECS架构中的实体(Entity),作为组件的容器。 * ECS架构中的实体(Entity),作为组件的容器。
* 实体本身不包含游戏逻辑,所有功能都通过组件来实现。 * 实体本身不包含游戏逻辑,所有功能都通过组件来实现。
* 支持父子关系,可以构建实体层次结构。 * 支持父子关系,可以构建实体层次结构。
* *
* @example * @example
* ```typescript * ```typescript
* // 创建实体 * // 创建实体
* const entity = new Entity("Player", 1); * const entity = new Entity("Player", 1);
* *
* // 添加组件 * // 添加组件
* const healthComponent = entity.addComponent(new HealthComponent(100)); * const healthComponent = entity.addComponent(new HealthComponent(100));
* *
* // 获取组件 * // 获取组件
* const health = entity.getComponent(HealthComponent); * const health = entity.getComponent(HealthComponent);
* *
* // 添加位置组件 * // 添加位置组件
* entity.addComponent(new PositionComponent(100, 200)); * entity.addComponent(new PositionComponent(100, 200));
* *
* // 添加子实体 * // 添加子实体
* const weapon = new Entity("Weapon", 2); * const weapon = new Entity("Weapon", 2);
* entity.addChild(weapon); * entity.addChild(weapon);
@@ -60,41 +63,22 @@ export class Entity {
* Entity专用日志器 * Entity专用日志器
*/ */
private static _logger = createLogger('Entity'); private static _logger = createLogger('Entity');
/** /**
* 实体比较器实例 * 实体比较器实例
*/ */
public static entityComparer: EntityComparer = new EntityComparer(); public static entityComparer: EntityComparer = new EntityComparer();
/**
* 全局事件总线实例
* 用于发射组件相关事件
*/
public static eventBus: EventBus | null = null;
/**
* 通知Scene中的QuerySystem实体组件发生变动
*
* @param entity 发生组件变动的实体
*/
private static notifyQuerySystems(entity: Entity): void {
// 只通知Scene中的QuerySystem
if (entity.scene && entity.scene.querySystem) {
entity.scene.querySystem.updateEntity(entity);
entity.scene.clearSystemEntityCaches();
}
}
/** /**
* 实体名称 * 实体名称
*/ */
public name: string; public name: string;
/** /**
* 实体唯一标识符 * 实体唯一标识符
*/ */
public readonly id: number; public readonly id: number;
/** /**
* 所属场景引用 * 所属场景引用
*/ */
@@ -103,7 +87,7 @@ export class Entity {
/** /**
* 销毁状态标志 * 销毁状态标志
*/ */
public _isDestroyed: boolean = false; private _isDestroyed: boolean = false;
/** /**
* 父实体引用 * 父实体引用
@@ -164,6 +148,18 @@ export class Entity {
return this._isDestroyed; return this._isDestroyed;
} }
/**
* 设置销毁状态(内部使用)
*
* 此方法供Scene和批量操作使用,以提高性能。
* 不应在普通业务逻辑中调用,应使用destroy()方法。
*
* @internal
*/
public setDestroyedState(destroyed: boolean): void {
this._isDestroyed = destroyed;
}
/** /**
* 获取组件数组(懒加载) * 获取组件数组(懒加载)
* @returns 只读的组件数组 * @returns 只读的组件数组
@@ -193,10 +189,7 @@ export class Entity {
if (BitMask64Utils.getBit(mask, bitIndex)) { if (BitMask64Utils.getBit(mask, bitIndex)) {
const componentType = ComponentRegistry.getTypeByBitIndex(bitIndex); const componentType = ComponentRegistry.getTypeByBitIndex(bitIndex);
if (componentType) { if (componentType) {
const component = this.scene.componentStorageManager.getComponent( const component = this.scene.componentStorageManager.getComponent(this.id, componentType);
this.id,
componentType
);
if (component) { if (component) {
components.push(component); components.push(component);
@@ -218,7 +211,7 @@ export class Entity {
/** /**
* 获取子实体数组的只读副本 * 获取子实体数组的只读副本
* *
* @returns 子实体数组的副本 * @returns 子实体数组的副本
*/ */
public get children(): readonly Entity[] { public get children(): readonly Entity[] {
@@ -227,7 +220,7 @@ export class Entity {
/** /**
* 获取子实体数量 * 获取子实体数量
* *
* @returns 子实体的数量 * @returns 子实体的数量
*/ */
public get childCount(): number { public get childCount(): number {
@@ -236,7 +229,7 @@ export class Entity {
/** /**
* 获取活跃状态 * 获取活跃状态
* *
* @returns 如果实体处于活跃状态则返回true * @returns 如果实体处于活跃状态则返回true
*/ */
public get active(): boolean { public get active(): boolean {
@@ -245,9 +238,9 @@ export class Entity {
/** /**
* 设置活跃状态 * 设置活跃状态
* *
* 设置实体的活跃状态,会影响子实体的有效活跃状态。 * 设置实体的活跃状态,会影响子实体的有效活跃状态。
* *
* @param value - 新的活跃状态 * @param value - 新的活跃状态
*/ */
public set active(value: boolean) { public set active(value: boolean) {
@@ -259,9 +252,9 @@ export class Entity {
/** /**
* 获取实体的有效活跃状态 * 获取实体的有效活跃状态
* *
* 考虑父实体的活跃状态,只有当实体本身和所有父实体都处于活跃状态时才返回true。 * 考虑父实体的活跃状态,只有当实体本身和所有父实体都处于活跃状态时才返回true。
* *
* @returns 有效的活跃状态 * @returns 有效的活跃状态
*/ */
public get activeInHierarchy(): boolean { public get activeInHierarchy(): boolean {
@@ -272,7 +265,7 @@ export class Entity {
/** /**
* 获取实体标签 * 获取实体标签
* *
* @returns 实体的数字标签 * @returns 实体的数字标签
*/ */
public get tag(): number { public get tag(): number {
@@ -281,7 +274,7 @@ export class Entity {
/** /**
* 设置实体标签 * 设置实体标签
* *
* @param value - 新的标签值 * @param value - 新的标签值
*/ */
public set tag(value: number) { public set tag(value: number) {
@@ -290,7 +283,7 @@ export class Entity {
/** /**
* 获取启用状态 * 获取启用状态
* *
* @returns 如果实体已启用则返回true * @returns 如果实体已启用则返回true
*/ */
public get enabled(): boolean { public get enabled(): boolean {
@@ -299,7 +292,7 @@ export class Entity {
/** /**
* 设置启用状态 * 设置启用状态
* *
* @param value - 新的启用状态 * @param value - 新的启用状态
*/ */
public set enabled(value: boolean) { public set enabled(value: boolean) {
@@ -308,7 +301,7 @@ export class Entity {
/** /**
* 获取更新顺序 * 获取更新顺序
* *
* @returns 实体的更新顺序值 * @returns 实体的更新顺序值
*/ */
public get updateOrder(): number { public get updateOrder(): number {
@@ -317,7 +310,7 @@ export class Entity {
/** /**
* 设置更新顺序 * 设置更新顺序
* *
* @param value - 新的更新顺序值 * @param value - 新的更新顺序值
*/ */
public set updateOrder(value: number) { public set updateOrder(value: number) {
@@ -326,7 +319,7 @@ export class Entity {
/** /**
* 获取组件位掩码 * 获取组件位掩码
* *
* @returns 实体的组件位掩码 * @returns 实体的组件位掩码
*/ */
public get componentMask(): BitMask64Data { public get componentMask(): BitMask64Data {
@@ -348,8 +341,9 @@ export class Entity {
*/ */
public createComponent<T extends Component>( public createComponent<T extends Component>(
componentType: ComponentType<T>, componentType: ComponentType<T>,
...args: any[] ...args: ConstructorParameters<ComponentType<T>>
): T { ): T {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const component = new componentType(...args); const component = new componentType(...args);
return this.addComponent(component); return this.addComponent(component);
} }
@@ -377,6 +371,16 @@ export class Entity {
return component; return component;
} }
/**
* 通知Scene中的QuerySystem实体组件发生变动
*/
private notifyQuerySystems(): void {
if (this.scene && this.scene.querySystem) {
this.scene.querySystem.updateEntity(this);
this.scene.clearSystemEntityCaches();
}
}
/** /**
* 添加组件到实体 * 添加组件到实体
* *
@@ -394,11 +398,13 @@ export class Entity {
const componentType = component.constructor as ComponentType<T>; const componentType = component.constructor as ComponentType<T>;
if (!this.scene) { if (!this.scene) {
throw new Error(`Entity must be added to Scene before adding components. Use scene.createEntity() instead of new Entity()`); throw new Error(
'Entity must be added to Scene before adding components. Use scene.createEntity() instead of new Entity()'
);
} }
if (!this.scene.componentStorageManager) { if (!this.scene.componentStorageManager) {
throw new Error(`Scene does not have componentStorageManager`); throw new Error('Scene does not have componentStorageManager');
} }
if (this.hasComponent(componentType)) { if (this.hasComponent(componentType)) {
@@ -415,8 +421,8 @@ export class Entity {
} }
component.onAddedToEntity(); component.onAddedToEntity();
if (Entity.eventBus) { if (this.scene && this.scene.eventSystem) {
Entity.eventBus.emitComponentAdded({ this.scene.eventSystem.emitSync('component:added', {
timestamp: Date.now(), timestamp: Date.now(),
source: 'Entity', source: 'Entity',
entityId: this.id, entityId: this.id,
@@ -427,9 +433,7 @@ export class Entity {
}); });
} }
this.notifyQuerySystems();
// 通知所有相关的QuerySystem组件已变动
Entity.notifyQuerySystems(this);
return component; return component;
} }
@@ -464,9 +468,6 @@ export class Entity {
return component as T | null; return component as T | null;
} }
/** /**
* 检查实体是否拥有指定类型的组件 * 检查实体是否拥有指定类型的组件
* *
@@ -485,7 +486,7 @@ export class Entity {
if (!ComponentRegistry.isRegistered(type)) { if (!ComponentRegistry.isRegistered(type)) {
return false; return false;
} }
const mask = ComponentRegistry.getBitMask(type); const mask = ComponentRegistry.getBitMask(type);
return BitMask64Utils.hasAny(this._componentMask, mask); return BitMask64Utils.hasAny(this._componentMask, mask);
} }
@@ -508,10 +509,11 @@ export class Entity {
*/ */
public getOrCreateComponent<T extends Component>( public getOrCreateComponent<T extends Component>(
type: ComponentType<T>, type: ComponentType<T>,
...args: any[] ...args: ConstructorParameters<ComponentType<T>>
): T { ): T {
let component = this.getComponent(type); let component = this.getComponent(type);
if (!component) { if (!component) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
component = this.createComponent(type, ...args); component = this.createComponent(type, ...args);
} }
return component; return component;
@@ -552,8 +554,8 @@ export class Entity {
component.entityId = null; component.entityId = null;
if (Entity.eventBus) { if (this.scene && this.scene.eventSystem) {
Entity.eventBus.emitComponentRemoved({ this.scene.eventSystem.emitSync('component:removed', {
timestamp: Date.now(), timestamp: Date.now(),
source: 'Entity', source: 'Entity',
entityId: this.id, entityId: this.id,
@@ -564,13 +566,12 @@ export class Entity {
}); });
} }
// 通知所有相关的QuerySystem组件已变动 this.notifyQuerySystems();
Entity.notifyQuerySystems(this);
} }
/** /**
* 移除指定类型的组件 * 移除指定类型的组件
* *
* @param type - 组件类型 * @param type - 组件类型
* @returns 被移除的组件实例或null * @returns 被移除的组件实例或null
*/ */
@@ -605,19 +606,18 @@ export class Entity {
component.onRemovedFromEntity(); component.onRemovedFromEntity();
} }
// 通知所有相关的QuerySystem组件已全部移除 this.notifyQuerySystems();
Entity.notifyQuerySystems(this);
} }
/** /**
* 批量添加组件 * 批量添加组件
* *
* @param components - 要添加的组件数组 * @param components - 要添加的组件数组
* @returns 添加的组件数组 * @returns 添加的组件数组
*/ */
public addComponents<T extends Component>(components: T[]): T[] { public addComponents<T extends Component>(components: T[]): T[] {
const addedComponents: T[] = []; const addedComponents: T[] = [];
for (const component of components) { for (const component of components) {
try { try {
addedComponents.push(this.addComponent(component)); addedComponents.push(this.addComponent(component));
@@ -625,28 +625,26 @@ export class Entity {
Entity._logger.warn(`添加组件失败 ${getComponentInstanceTypeName(component)}:`, error); Entity._logger.warn(`添加组件失败 ${getComponentInstanceTypeName(component)}:`, error);
} }
} }
return addedComponents; return addedComponents;
} }
/** /**
* 批量移除组件类型 * 批量移除组件类型
* *
* @param componentTypes - 要移除的组件类型数组 * @param componentTypes - 要移除的组件类型数组
* @returns 被移除的组件数组 * @returns 被移除的组件数组
*/ */
public removeComponentsByTypes<T extends Component>(componentTypes: ComponentType<T>[]): (T | null)[] { public removeComponentsByTypes<T extends Component>(componentTypes: ComponentType<T>[]): (T | null)[] {
const removedComponents: (T | null)[] = []; const removedComponents: (T | null)[] = [];
for (const componentType of componentTypes) { for (const componentType of componentTypes) {
removedComponents.push(this.removeComponentByType(componentType)); removedComponents.push(this.removeComponentByType(componentType));
} }
return removedComponents; return removedComponents;
} }
/** /**
* 获取所有指定类型的组件 * 获取所有指定类型的组件
* *
@@ -694,13 +692,13 @@ export class Entity {
/** /**
* 添加子实体 * 添加子实体
* *
* @param child - 要添加的子实体 * @param child - 要添加的子实体
* @returns 添加的子实体 * @returns 添加的子实体
*/ */
public addChild(child: Entity): Entity { public addChild(child: Entity): Entity {
if (child === this) { if (child === this) {
throw new Error("Entity cannot be its own child"); throw new Error('Entity cannot be its own child');
} }
if (child._parent === this) { if (child._parent === this) {
@@ -724,7 +722,7 @@ export class Entity {
/** /**
* 移除子实体 * 移除子实体
* *
* @param child - 要移除的子实体 * @param child - 要移除的子实体
* @returns 是否成功移除 * @returns 是否成功移除
*/ */
@@ -745,7 +743,7 @@ export class Entity {
*/ */
public removeAllChildren(): void { public removeAllChildren(): void {
const childrenToRemove = [...this._children]; const childrenToRemove = [...this._children];
for (const child of childrenToRemove) { for (const child of childrenToRemove) {
this.removeChild(child); this.removeChild(child);
} }
@@ -753,7 +751,7 @@ export class Entity {
/** /**
* 根据名称查找子实体 * 根据名称查找子实体
* *
* @param name - 子实体名称 * @param name - 子实体名称
* @param recursive - 是否递归查找 * @param recursive - 是否递归查找
* @returns 找到的子实体或null * @returns 找到的子实体或null
@@ -779,7 +777,7 @@ export class Entity {
/** /**
* 根据标签查找子实体 * 根据标签查找子实体
* *
* @param tag - 标签 * @param tag - 标签
* @param recursive - 是否递归查找 * @param recursive - 是否递归查找
* @returns 找到的子实体数组 * @returns 找到的子实体数组
@@ -804,20 +802,19 @@ export class Entity {
/** /**
* 获取根实体 * 获取根实体
* *
* @returns 层次结构的根实体 * @returns 层次结构的根实体
*/ */
public getRoot(): Entity { public getRoot(): Entity {
let root: Entity = this; if (!this._parent) {
while (root._parent) { return this;
root = root._parent;
} }
return root; return this._parent.getRoot();
} }
/** /**
* 检查是否是指定实体的祖先 * 检查是否是指定实体的祖先
* *
* @param entity - 要检查的实体 * @param entity - 要检查的实体
* @returns 如果是祖先则返回true * @returns 如果是祖先则返回true
*/ */
@@ -834,7 +831,7 @@ export class Entity {
/** /**
* 检查是否是指定实体的后代 * 检查是否是指定实体的后代
* *
* @param entity - 要检查的实体 * @param entity - 要检查的实体
* @returns 如果是后代则返回true * @returns 如果是后代则返回true
*/ */
@@ -844,7 +841,7 @@ export class Entity {
/** /**
* 获取层次深度 * 获取层次深度
* *
* @returns 在层次结构中的深度(根实体为0) * @returns 在层次结构中的深度(根实体为0)
*/ */
public getDepth(): number { public getDepth(): number {
@@ -859,7 +856,7 @@ export class Entity {
/** /**
* 遍历所有子实体(深度优先) * 遍历所有子实体(深度优先)
* *
* @param callback - 对每个子实体执行的回调函数 * @param callback - 对每个子实体执行的回调函数
* @param recursive - 是否递归遍历 * @param recursive - 是否递归遍历
*/ */
@@ -878,20 +875,19 @@ export class Entity {
private onActiveChanged(): void { private onActiveChanged(): void {
for (const component of this.components) { for (const component of this.components) {
if ('onActiveChanged' in component && typeof component.onActiveChanged === 'function') { if ('onActiveChanged' in component && typeof component.onActiveChanged === 'function') {
(component as any).onActiveChanged(); (component as IActiveChangeable).onActiveChanged();
} }
} }
if (this.scene && this.scene.eventSystem) { if (this.scene && this.scene.eventSystem) {
this.scene.eventSystem.emitSync('entity:activeChanged', { this.scene.eventSystem.emitSync('entity:activeChanged', {
entity: this, entity: this,
active: this._active, active: this._active,
activeInHierarchy: this.activeInHierarchy activeInHierarchy: this.activeInHierarchy
}); });
} }
} }
/** /**
* 销毁实体 * 销毁实体
* *
@@ -949,7 +945,7 @@ export class Entity {
collectChildren(this); collectChildren(this);
for (const entity of toDestroy) { for (const entity of toDestroy) {
entity._isDestroyed = true; entity.setDestroyedState(true);
} }
for (const entity of toDestroy) { for (const entity of toDestroy) {
@@ -970,7 +966,7 @@ export class Entity {
/** /**
* 比较实体 * 比较实体
* *
* @param other - 另一个实体 * @param other - 另一个实体
* @returns 比较结果 * @returns 比较结果
*/ */
@@ -980,7 +976,7 @@ export class Entity {
/** /**
* 获取实体的字符串表示 * 获取实体的字符串表示
* *
* @returns 实体的字符串描述 * @returns 实体的字符串描述
*/ */
public toString(): string { public toString(): string {
@@ -989,7 +985,7 @@ export class Entity {
/** /**
* 获取实体的调试信息(包含组件缓存信息) * 获取实体的调试信息(包含组件缓存信息)
* *
* @returns 包含实体详细信息的对象 * @returns 包含实体详细信息的对象
*/ */
public getDebugInfo(): { public getDebugInfo(): {
@@ -1007,7 +1003,7 @@ export class Entity {
childIds: number[]; childIds: number[];
depth: number; depth: number;
cacheBuilt: boolean; cacheBuilt: boolean;
} { } {
return { return {
name: this.name, name: this.name,
id: this.id, id: this.id,
@@ -1016,11 +1012,11 @@ export class Entity {
activeInHierarchy: this.activeInHierarchy, activeInHierarchy: this.activeInHierarchy,
destroyed: this._isDestroyed, destroyed: this._isDestroyed,
componentCount: this.components.length, componentCount: this.components.length,
componentTypes: this.components.map(c => getComponentInstanceTypeName(c)), componentTypes: this.components.map((c) => getComponentInstanceTypeName(c)),
componentMask: BitMask64Utils.toString(this._componentMask, 2), // 二进制表示 componentMask: BitMask64Utils.toString(this._componentMask, 2), // 二进制表示
parentId: this._parent?.id || null, parentId: this._parent?.id || null,
childCount: this._children.length, childCount: this._children.length,
childIds: this._children.map(c => c.id), childIds: this._children.map((c) => c.id),
depth: this.getDepth(), depth: this.getDepth(),
cacheBuilt: this._componentCache !== null cacheBuilt: this._componentCache !== null
}; };
+3 -3
View File
@@ -50,12 +50,12 @@ export interface IScene {
* 标识符池 * 标识符池
*/ */
readonly identifierPool: IdentifierPool; readonly identifierPool: IdentifierPool;
/** /**
* 组件存储管理器 * 组件存储管理器
*/ */
readonly componentStorageManager: ComponentStorageManager; readonly componentStorageManager: ComponentStorageManager;
/** /**
* 查询系统 * 查询系统
*/ */
@@ -316,4 +316,4 @@ export interface ISceneConfig {
* 场景名称 * 场景名称
*/ */
name?: string; name?: string;
} }
+134 -96
View File
@@ -2,19 +2,26 @@ import { Entity } from './Entity';
import { EntityList } from './Utils/EntityList'; import { EntityList } from './Utils/EntityList';
import { IdentifierPool } from './Utils/IdentifierPool'; import { IdentifierPool } from './Utils/IdentifierPool';
import { EntitySystem } from './Systems/EntitySystem'; import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager, ComponentRegistry } from './Core/ComponentStorage'; import { ComponentStorageManager, ComponentRegistry, ComponentType } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem'; import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem'; import { TypeSafeEventSystem } from './Core/EventSystem';
import { EventBus } from './Core/EventBus';
import { ReferenceTracker } from './Core/ReferenceTracker'; import { ReferenceTracker } from './Core/ReferenceTracker';
import { IScene, ISceneConfig } from './IScene'; import { IScene, ISceneConfig } from './IScene';
import { getComponentInstanceTypeName, getSystemInstanceTypeName, getSystemMetadata } from "./Decorators"; import { getComponentInstanceTypeName, getSystemInstanceTypeName, getSystemMetadata } from './Decorators';
import { TypedQueryBuilder } from './Core/Query/TypedQuery'; import { TypedQueryBuilder } from './Core/Query/TypedQuery';
import { SceneSerializer, SceneSerializationOptions, SceneDeserializationOptions } from './Serialization/SceneSerializer'; import {
import { IncrementalSerializer, IncrementalSnapshot, IncrementalSerializationOptions } from './Serialization/IncrementalSerializer'; SceneSerializer,
SceneSerializationOptions,
SceneDeserializationOptions
} from './Serialization/SceneSerializer';
import {
IncrementalSerializer,
IncrementalSnapshot,
IncrementalSerializationOptions
} from './Serialization/IncrementalSerializer';
import { ComponentPoolManager } from './Core/ComponentPool'; import { ComponentPoolManager } from './Core/ComponentPool';
import { PerformanceMonitor } from '../Utils/PerformanceMonitor'; import { PerformanceMonitor } from '../Utils/PerformanceMonitor';
import { ServiceContainer, type ServiceType } from '../Core/ServiceContainer'; import { ServiceContainer, type ServiceType, type IService } from '../Core/ServiceContainer';
import { createInstance, isInjectable, injectProperties } from '../Core/DI'; import { createInstance, isInjectable, injectProperties } from '../Core/DI';
import { createLogger } from '../Utils/Logger'; import { createLogger } from '../Utils/Logger';
@@ -30,13 +37,14 @@ export class Scene implements IScene {
* *
* 用于标识和调试的友好名称。 * 用于标识和调试的友好名称。
*/ */
public name: string = ""; public name: string = '';
/** /**
* 场景自定义数据 * 场景自定义数据
* *
* 用于存储场景级别的配置和状态数据。 * 用于存储场景级别的配置和状态数据。
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public readonly sceneData: Map<string, any> = new Map(); public readonly sceneData: Map<string, any> = new Map();
/** /**
@@ -45,25 +53,24 @@ export class Scene implements IScene {
* 管理场景内所有实体的生命周期。 * 管理场景内所有实体的生命周期。
*/ */
public readonly entities: EntityList; public readonly entities: EntityList;
/** /**
* 实体ID池 * 实体ID池
* *
* 用于分配和回收实体的唯一标识符。 * 用于分配和回收实体的唯一标识符。
*/ */
public readonly identifierPool: IdentifierPool; public readonly identifierPool: IdentifierPool;
/** /**
* 组件存储管理器 * 组件存储管理器
* *
* 高性能的组件存储和查询系统。 * 高性能的组件存储和查询系统。
*/ */
public readonly componentStorageManager: ComponentStorageManager; public readonly componentStorageManager: ComponentStorageManager;
/** /**
* 查询系统 * 查询系统
* *
* 基于位掩码的高性能实体查询系统。 * 基于位掩码的高性能实体查询系统。
*/ */
public readonly querySystem: QuerySystem; public readonly querySystem: QuerySystem;
@@ -119,6 +126,20 @@ export class Scene implements IScene {
*/ */
private _systemsOrderDirty: boolean = true; private _systemsOrderDirty: boolean = true;
/**
* 系统错误计数器
*
* 跟踪每个系统的错误次数,用于自动禁用频繁出错的系统
*/
private _systemErrorCount: Map<EntitySystem, number> = new Map();
/**
* 最大允许错误次数
*
* 系统错误次数超过此阈值后将被自动禁用
*/
private _maxErrorCount: number = 10;
/** /**
* 获取场景中所有已注册的EntitySystem * 获取场景中所有已注册的EntitySystem
* *
@@ -131,24 +152,35 @@ export class Scene implements IScene {
return this._cachedSystems; return this._cachedSystems;
} }
// 重新构建系统列表 this._cachedSystems = this._rebuildSystemsCache();
const services = this._services.getAll();
const systems: EntitySystem[] = [];
for (const service of services) {
if (service instanceof EntitySystem) {
systems.push(service);
}
}
// 按updateOrder排序
systems.sort((a, b) => a.updateOrder - b.updateOrder);
// 缓存结果
this._cachedSystems = systems;
this._systemsOrderDirty = false; this._systemsOrderDirty = false;
return systems; return this._cachedSystems;
}
/**
* 重新构建系统缓存
*
* 从服务容器中提取所有EntitySystem并排序
*/
private _rebuildSystemsCache(): EntitySystem[] {
const allServices = this._services.getAll();
const systems = this._filterEntitySystems(allServices);
return this._sortSystemsByUpdateOrder(systems);
}
/**
* 从服务列表中过滤出EntitySystem实例
*/
private _filterEntitySystems(services: IService[]): EntitySystem[] {
return services.filter((service): service is EntitySystem => service instanceof EntitySystem);
}
/**
* 按updateOrder排序系统
*/
private _sortSystemsByUpdateOrder(systems: EntitySystem[]): EntitySystem[] {
return systems.sort((a, b) => a.updateOrder - b.updateOrder);
} }
/** /**
@@ -212,16 +244,6 @@ export class Scene implements IScene {
if (config?.name) { if (config?.name) {
this.name = config.name; this.name = config.name;
} }
if (!Entity.eventBus) {
Entity.eventBus = new EventBus(false);
}
if (Entity.eventBus) {
Entity.eventBus.onComponentAdded((data: unknown) => {
this.eventSystem.emitSync('component:added', data);
});
}
} }
/** /**
@@ -231,35 +253,31 @@ export class Scene implements IScene {
*/ */
private get performanceMonitor(): PerformanceMonitor { private get performanceMonitor(): PerformanceMonitor {
if (!this._performanceMonitor) { if (!this._performanceMonitor) {
this._performanceMonitor = this._services.tryResolve(PerformanceMonitor) this._performanceMonitor = this._services.tryResolve(PerformanceMonitor) ?? new PerformanceMonitor();
?? new PerformanceMonitor();
} }
return this._performanceMonitor; return this._performanceMonitor;
} }
/** /**
* 初始化场景 * 初始化场景
* *
* 在场景创建时调用,子类可以重写此方法来设置初始实体和组件。 * 在场景创建时调用,子类可以重写此方法来设置初始实体和组件。
*/ */
public initialize(): void { public initialize(): void {}
}
/** /**
* 场景开始运行时的回调 * 场景开始运行时的回调
* *
* 在场景开始运行时调用,可以在此方法中执行场景启动逻辑。 * 在场景开始运行时调用,可以在此方法中执行场景启动逻辑。
*/ */
public onStart(): void { public onStart(): void {}
}
/** /**
* 场景卸载时的回调 * 场景卸载时的回调
* *
* 在场景被销毁时调用,可以在此方法中执行清理工作。 * 在场景被销毁时调用,可以在此方法中执行清理工作。
*/ */
public unload(): void { public unload(): void {}
}
/** /**
* 开始场景,启动实体处理器等 * 开始场景,启动实体处理器等
@@ -309,39 +327,64 @@ export class Scene implements IScene {
this.entities.updateLists(); this.entities.updateLists();
// 更新所有EntitySystem
const systems = this.systems; const systems = this.systems;
for (const system of systems) { for (const system of systems) {
if (system.enabled) { if (system.enabled) {
try { try {
system.update(); system.update();
} catch (error) { } catch (error) {
this.logger.error(`Error in system ${system.constructor.name}.update():`, error); this._handleSystemError(system, 'update', error);
} }
} }
} }
// LateUpdate
for (const system of systems) { for (const system of systems) {
if (system.enabled) { if (system.enabled) {
try { try {
system.lateUpdate(); system.lateUpdate();
} catch (error) { } catch (error) {
this.logger.error(`Error in system ${system.constructor.name}.lateUpdate():`, error); this._handleSystemError(system, 'lateUpdate', error);
} }
} }
} }
} }
/**
* 处理系统执行错误
*
* 记录错误信息并跟踪错误次数。当系统错误次数超过阈值时自动禁用该系统。
*
* @param system 出错的系统
* @param phase 错误发生的阶段(update 或 lateUpdate
* @param error 错误对象
*/
private _handleSystemError(system: EntitySystem, phase: 'update' | 'lateUpdate', error: unknown): void {
const errorCount = (this._systemErrorCount.get(system) || 0) + 1;
this._systemErrorCount.set(system, errorCount);
this.logger.error(
`Error in system ${system.constructor.name}.${phase}() [${errorCount}/${this._maxErrorCount}]:`,
error
);
if (errorCount >= this._maxErrorCount) {
system.enabled = false;
this.logger.error(
`System ${system.constructor.name} has been disabled due to excessive errors (${errorCount} errors)`
);
}
}
/** /**
* 将实体添加到此场景,并返回它 * 将实体添加到此场景,并返回它
* @param name 实体名称 * @param name 实体名称
*/ */
public createEntity(name: string) { public createEntity(name: string) {
let entity = new Entity(name, this.identifierPool.checkOut()); const entity = new Entity(name, this.identifierPool.checkOut());
this.eventSystem.emitSync('entity:created', { entityName: name, entity, scene: this }); this.eventSystem.emitSync('entity:created', { entityName: name, entity, scene: this });
return this.addEntity(entity); return this.addEntity(entity);
} }
@@ -384,31 +427,30 @@ export class Scene implements IScene {
* @param namePrefix 实体名称前缀 * @param namePrefix 实体名称前缀
* @returns 创建的实体列表 * @returns 创建的实体列表
*/ */
public createEntities(count: number, namePrefix: string = "Entity"): Entity[] { public createEntities(count: number, namePrefix: string = 'Entity'): Entity[] {
const entities: Entity[] = []; const entities: Entity[] = [];
// 批量创建实体对象,不立即添加到系统 // 批量创建实体对象,不立即添加到系统
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut()); const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
entity.scene = this; entity.scene = this;
entities.push(entity); entities.push(entity);
} }
// 批量添加到实体列表 // 批量添加到实体列表
for (const entity of entities) { for (const entity of entities) {
this.entities.add(entity); this.entities.add(entity);
} }
// 批量添加到查询系统(无重复检查,性能最优) // 批量添加到查询系统(无重复检查,性能最优)
this.querySystem.addEntitiesUnchecked(entities); this.querySystem.addEntitiesUnchecked(entities);
// 批量触发事件(可选,减少事件开销) // 批量触发事件(可选,减少事件开销)
this.eventSystem.emitSync('entities:batch_added', { entities, scene: this, count }); this.eventSystem.emitSync('entities:batch_added', { entities, scene: this, count });
return entities; return entities;
} }
/** /**
* 批量销毁实体 * 批量销毁实体
*/ */
@@ -416,7 +458,7 @@ export class Scene implements IScene {
if (entities.length === 0) return; if (entities.length === 0) return;
for (const entity of entities) { for (const entity of entities) {
entity._isDestroyed = true; entity.setDestroyedState(true);
} }
for (const entity of entities) { for (const entity of entities) {
@@ -473,7 +515,9 @@ export class Scene implements IScene {
/** /**
* 根据名称查找实体(别名方法) * 根据名称查找实体(别名方法)
*
* @param name 实体名称 * @param name 实体名称
* @deprecated 请使用 findEntity() 代替此方法
*/ */
public getEntityByName(name: string): Entity | null { public getEntityByName(name: string): Entity | null {
return this.findEntity(name); return this.findEntity(name);
@@ -481,7 +525,9 @@ export class Scene implements IScene {
/** /**
* 根据标签查找实体(别名方法) * 根据标签查找实体(别名方法)
*
* @param tag 实体标签 * @param tag 实体标签
* @deprecated 请使用 findEntitiesByTag() 代替此方法
*/ */
public getEntitiesByTag(tag: number): Entity[] { public getEntitiesByTag(tag: number): Entity[] {
return this.findEntitiesByTag(tag); return this.findEntitiesByTag(tag);
@@ -502,7 +548,7 @@ export class Scene implements IScene {
* } * }
* ``` * ```
*/ */
public queryAll(...componentTypes: any[]): { entities: readonly Entity[] } { public queryAll(...componentTypes: ComponentType[]): { entities: readonly Entity[] } {
return this.querySystem.queryAll(...componentTypes); return this.querySystem.queryAll(...componentTypes);
} }
@@ -512,7 +558,7 @@ export class Scene implements IScene {
* @param componentTypes - 组件类型数组 * @param componentTypes - 组件类型数组
* @returns 查询结果 * @returns 查询结果
*/ */
public queryAny(...componentTypes: any[]): { entities: readonly Entity[] } { public queryAny(...componentTypes: ComponentType[]): { entities: readonly Entity[] } {
return this.querySystem.queryAny(...componentTypes); return this.querySystem.queryAny(...componentTypes);
} }
@@ -522,7 +568,7 @@ export class Scene implements IScene {
* @param componentTypes - 组件类型数组 * @param componentTypes - 组件类型数组
* @returns 查询结果 * @returns 查询结果
*/ */
public queryNone(...componentTypes: any[]): { entities: readonly Entity[] } { public queryNone(...componentTypes: ComponentType[]): { entities: readonly Entity[] } {
return this.querySystem.queryNone(...componentTypes); return this.querySystem.queryNone(...componentTypes);
} }
@@ -577,11 +623,9 @@ export class Scene implements IScene {
* scene.addEntityProcessor(system); * scene.addEntityProcessor(system);
* ``` * ```
*/ */
public addEntityProcessor<T extends EntitySystem>( public addEntityProcessor<T extends EntitySystem>(systemTypeOrInstance: ServiceType<T> | T): T {
systemTypeOrInstance: ServiceType<T> | T
): T {
let system: T; let system: T;
let constructor: any; let constructor: ServiceType<T>;
if (typeof systemTypeOrInstance === 'function') { if (typeof systemTypeOrInstance === 'function') {
constructor = systemTypeOrInstance; constructor = systemTypeOrInstance;
@@ -595,11 +639,11 @@ export class Scene implements IScene {
if (isInjectable(constructor)) { if (isInjectable(constructor)) {
system = createInstance(constructor, this._services) as T; system = createInstance(constructor, this._services) as T;
} else { } else {
system = new (constructor as any)() as T; system = new constructor() as T;
} }
} else { } else {
system = systemTypeOrInstance; system = systemTypeOrInstance;
constructor = system.constructor; constructor = system.constructor as ServiceType<T>;
if (this._services.isRegistered(constructor)) { if (this._services.isRegistered(constructor)) {
const existingSystem = this._services.resolve(constructor); const existingSystem = this._services.resolve(constructor);
@@ -609,7 +653,7 @@ export class Scene implements IScene {
} else { } else {
this.logger.warn( this.logger.warn(
`Attempting to register a different instance of ${constructor.name}, ` + `Attempting to register a different instance of ${constructor.name}, ` +
`but type is already registered. Returning existing instance.` 'but type is already registered. Returning existing instance.'
); );
return existingSystem as T; return existingSystem as T;
} }
@@ -701,7 +745,7 @@ export class Scene implements IScene {
* @param processor 要删除的处理器 * @param processor 要删除的处理器
*/ */
public removeEntityProcessor(processor: EntitySystem): void { public removeEntityProcessor(processor: EntitySystem): void {
const constructor = processor.constructor as any; const constructor = processor.constructor as ServiceType<EntitySystem>;
// 从ServiceContainer移除 // 从ServiceContainer移除
this._services.unregister(constructor); this._services.unregister(constructor);
@@ -740,7 +784,7 @@ export class Scene implements IScene {
* ``` * ```
*/ */
public getEntityProcessor<T extends EntitySystem>(type: new (...args: unknown[]) => T): T | null { public getEntityProcessor<T extends EntitySystem>(type: new (...args: unknown[]) => T): T | null {
return this._services.tryResolve(type as any) as T | null; return this._services.tryResolve(type as ServiceType<T>) as T | null;
} }
/** /**
@@ -749,8 +793,8 @@ export class Scene implements IScene {
public getStats(): { public getStats(): {
entityCount: number; entityCount: number;
processorCount: number; processorCount: number;
componentStorageStats: Map<string, any>; componentStorageStats: Map<string, { totalSlots: number; usedSlots: number; freeSlots: number; fragmentation: number }>;
} { } {
return { return {
entityCount: this.entities.count, entityCount: this.entities.count,
processorCount: this.systems.length, processorCount: this.systems.length,
@@ -758,7 +802,6 @@ export class Scene implements IScene {
}; };
} }
/** /**
* 获取场景的调试信息 * 获取场景的调试信息
*/ */
@@ -778,24 +821,24 @@ export class Scene implements IScene {
updateOrder: number; updateOrder: number;
entityCount: number; entityCount: number;
}>; }>;
componentStats: Map<string, any>; componentStats: Map<string, { totalSlots: number; usedSlots: number; freeSlots: number; fragmentation: number }>;
} { } {
const systems = this.systems; const systems = this.systems;
return { return {
name: this.name || this.constructor.name, name: this.name || this.constructor.name,
entityCount: this.entities.count, entityCount: this.entities.count,
processorCount: systems.length, processorCount: systems.length,
isRunning: this._didSceneBegin, isRunning: this._didSceneBegin,
entities: this.entities.buffer.map(entity => ({ entities: this.entities.buffer.map((entity) => ({
name: entity.name, name: entity.name,
id: entity.id, id: entity.id,
componentCount: entity.components.length, componentCount: entity.components.length,
componentTypes: entity.components.map(c => getComponentInstanceTypeName(c)) componentTypes: entity.components.map((c) => getComponentInstanceTypeName(c))
})), })),
processors: systems.map(processor => ({ processors: systems.map((processor) => ({
name: getSystemInstanceTypeName(processor), name: getSystemInstanceTypeName(processor),
updateOrder: processor.updateOrder, updateOrder: processor.updateOrder,
entityCount: (processor as any)._entities?.length || 0 entityCount: processor.entities.length
})), })),
componentStats: this.componentStorageManager.getAllStats() componentStats: this.componentStorageManager.getAllStats()
}; };
@@ -855,7 +898,7 @@ export class Scene implements IScene {
// ==================== 增量序列化 API ==================== // ==================== 增量序列化 API ====================
/** 增量序列化的基础快照 */ /** 增量序列化的基础快照 */
private _incrementalBaseSnapshot?: any; private _incrementalBaseSnapshot?: unknown;
/** /**
* 创建增量序列化的基础快照 * 创建增量序列化的基础快照
@@ -910,11 +953,7 @@ export class Scene implements IScene {
throw new Error('必须先调用 createIncrementalSnapshot() 创建基础快照'); throw new Error('必须先调用 createIncrementalSnapshot() 创建基础快照');
} }
return IncrementalSerializer.computeIncremental( return IncrementalSerializer.computeIncremental(this, this._incrementalBaseSnapshot as Parameters<typeof IncrementalSerializer.computeIncremental>[1], options);
this,
this._incrementalBaseSnapshot,
options
);
} }
/** /**
@@ -939,16 +978,15 @@ export class Scene implements IScene {
*/ */
public applyIncremental( public applyIncremental(
incremental: IncrementalSnapshot | string | Uint8Array, incremental: IncrementalSnapshot | string | Uint8Array,
componentRegistry?: Map<string, any> componentRegistry?: Map<string, ComponentType>
): void { ): void {
const isSerializedData = typeof incremental === 'string' || const isSerializedData = typeof incremental === 'string' || incremental instanceof Uint8Array;
incremental instanceof Uint8Array;
const snapshot = isSerializedData const snapshot = isSerializedData
? IncrementalSerializer.deserializeIncremental(incremental as string | Uint8Array) ? IncrementalSerializer.deserializeIncremental(incremental as string | Uint8Array)
: incremental as IncrementalSnapshot; : (incremental as IncrementalSnapshot);
const registry = componentRegistry || ComponentRegistry.getAllComponentNames() as Map<string, any>; const registry = componentRegistry || (ComponentRegistry.getAllComponentNames() as Map<string, ComponentType>);
IncrementalSerializer.applyIncremental(this, snapshot, registry); IncrementalSerializer.applyIncremental(this, snapshot, registry);
} }
@@ -996,4 +1034,4 @@ export class Scene implements IScene {
public hasIncrementalSnapshot(): boolean { public hasIncrementalSnapshot(): boolean {
return this._incrementalBaseSnapshot !== undefined; return this._incrementalBaseSnapshot !== undefined;
} }
} }
@@ -212,7 +212,7 @@ export class ComponentSerializer {
// 数组 // 数组
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.map(item => this.serializeValue(item)); return value.map((item) => this.serializeValue(item));
} }
// Map (如果没有使用@SerializeMap装饰器) // Map (如果没有使用@SerializeMap装饰器)
@@ -276,7 +276,7 @@ export class ComponentSerializer {
// 数组 // 数组
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.map(item => this.deserializeValue(item)); return value.map((item) => this.deserializeValue(item));
} }
// 普通对象 // 普通对象
@@ -340,10 +340,10 @@ export class ComponentSerializer {
return { return {
type: metadata.options.typeId || getComponentTypeName(componentType), type: metadata.options.typeId || getComponentTypeName(componentType),
version: metadata.options.version, version: metadata.options.version,
fields: Array.from(metadata.fields.keys()).map(k => fields: Array.from(metadata.fields.keys()).map((k) =>
typeof k === 'symbol' ? k.toString() : k typeof k === 'symbol' ? k.toString() : k
), ),
ignoredFields: Array.from(metadata.ignoredFields).map(k => ignoredFields: Array.from(metadata.ignoredFields).map((k) =>
typeof k === 'symbol' ? k.toString() : k typeof k === 'symbol' ? k.toString() : k
), ),
isSerializable: true isSerializable: true
@@ -696,22 +696,22 @@ export class IncrementalSerializer {
componentChanges: incremental.componentChanges.length, componentChanges: incremental.componentChanges.length,
sceneDataChanges: incremental.sceneDataChanges.length, sceneDataChanges: incremental.sceneDataChanges.length,
addedEntities: incremental.entityChanges.filter( addedEntities: incremental.entityChanges.filter(
c => c.operation === ChangeOperation.EntityAdded (c) => c.operation === ChangeOperation.EntityAdded
).length, ).length,
removedEntities: incremental.entityChanges.filter( removedEntities: incremental.entityChanges.filter(
c => c.operation === ChangeOperation.EntityRemoved (c) => c.operation === ChangeOperation.EntityRemoved
).length, ).length,
updatedEntities: incremental.entityChanges.filter( updatedEntities: incremental.entityChanges.filter(
c => c.operation === ChangeOperation.EntityUpdated (c) => c.operation === ChangeOperation.EntityUpdated
).length, ).length,
addedComponents: incremental.componentChanges.filter( addedComponents: incremental.componentChanges.filter(
c => c.operation === ChangeOperation.ComponentAdded (c) => c.operation === ChangeOperation.ComponentAdded
).length, ).length,
removedComponents: incremental.componentChanges.filter( removedComponents: incremental.componentChanges.filter(
c => c.operation === ChangeOperation.ComponentRemoved (c) => c.operation === ChangeOperation.ComponentRemoved
).length, ).length,
updatedComponents: incremental.componentChanges.filter( updatedComponents: incremental.componentChanges.filter(
c => c.operation === ChangeOperation.ComponentUpdated (c) => c.operation === ChangeOperation.ComponentUpdated
).length ).length
}; };
} }
@@ -363,7 +363,7 @@ export class SceneSerializer {
// 数组 // 数组
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.map(item => this.serializeValue(item)); return value.map((item) => this.serializeValue(item));
} }
// 普通对象 // 普通对象
@@ -409,7 +409,7 @@ export class SceneSerializer {
// 数组 // 数组
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.map(item => this.deserializeValue(item)); return value.map((item) => this.deserializeValue(item));
} }
// 普通对象 // 普通对象
@@ -437,8 +437,8 @@ export class SceneSerializer {
const componentTypeSet = new Set(options.components); const componentTypeSet = new Set(options.components);
// 只返回拥有指定组件的实体 // 只返回拥有指定组件的实体
return entities.filter(entity => { return entities.filter((entity) => {
return Array.from(entity.components).some(component => return Array.from(entity.components).some((component) =>
componentTypeSet.has(component.constructor as ComponentType) componentTypeSet.has(component.constructor as ComponentType)
); );
}); });
@@ -127,7 +127,7 @@ export class VersionMigrationManager {
return component; return component;
} }
let migratedData = { ...component }; const migratedData = { ...component };
let version = currentVersion; let version = currentVersion;
// 执行迁移链 // 执行迁移链
@@ -193,12 +193,12 @@ export class VersionMigrationManager {
private static migrateSceneComponents(scene: SerializedScene): SerializedScene { private static migrateSceneComponents(scene: SerializedScene): SerializedScene {
const migratedScene = { ...scene }; const migratedScene = { ...scene };
migratedScene.entities = scene.entities.map(entity => ({ migratedScene.entities = scene.entities.map((entity) => ({
...entity, ...entity,
components: entity.components.map(component => { components: entity.components.map((component) => {
// 查找组件的目标版本 // 查找组件的目标版本
const typeInfo = scene.componentTypeRegistry.find( const typeInfo = scene.componentTypeRegistry.find(
t => t.typeName === component.type (t) => t.typeName === component.type
); );
if (typeInfo && typeInfo.version !== component.version) { if (typeInfo && typeInfo.version !== component.version) {
@@ -220,10 +220,10 @@ export class VersionMigrationManager {
entities: any[], entities: any[],
typeRegistry: Array<{ typeName: string; version: number }> typeRegistry: Array<{ typeName: string; version: number }>
): any[] { ): any[] {
return entities.map(entity => ({ return entities.map((entity) => ({
...entity, ...entity,
components: entity.components.map((component: SerializedComponent) => { components: entity.components.map((component: SerializedComponent) => {
const typeInfo = typeRegistry.find(t => t.typeName === component.type); const typeInfo = typeRegistry.find((t) => t.typeName === component.type);
if (typeInfo && typeInfo.version !== component.version) { if (typeInfo && typeInfo.version !== component.version) {
return this.migrateComponent(component, typeInfo.version); return this.migrateComponent(component, typeInfo.version);
@@ -0,0 +1,166 @@
import { Entity } from '../Entity';
/**
*
*
* EntitySystem
* 使
*
* @example
* ```typescript
* const cache = new EntityCache();
* cache.setPersistent(entities);
* const cached = cache.getPersistent();
* cache.invalidate();
* ```
*/
export class EntityCache {
/**
*
*
* update周期内使用
*/
private _frameCache: readonly Entity[] | null = null;
/**
*
*
* 使
*/
private _persistentCache: readonly Entity[] | null = null;
/**
*
*
*
*/
private _trackedEntities: Set<Entity> = new Set();
/**
*
*/
public getFrame(): readonly Entity[] | null {
return this._frameCache;
}
/**
*
*
* @param entities
*/
public setFrame(entities: readonly Entity[]): void {
this._frameCache = entities;
}
/**
*
*/
public getPersistent(): readonly Entity[] | null {
return this._persistentCache;
}
/**
*
*
* @param entities
*/
public setPersistent(entities: readonly Entity[]): void {
this._persistentCache = entities;
}
/**
*
*/
public getTracked(): ReadonlySet<Entity> {
return this._trackedEntities;
}
/**
*
*
* @param entity
*/
public addTracked(entity: Entity): void {
this._trackedEntities.add(entity);
}
/**
*
*
* @param entity
*/
public removeTracked(entity: Entity): void {
this._trackedEntities.delete(entity);
}
/**
*
*
* @param entity
*/
public isTracked(entity: Entity): boolean {
return this._trackedEntities.has(entity);
}
/**
* 使
*
*
*/
public invalidate(): void {
this._persistentCache = null;
}
/**
*
*
*
*/
public clearFrame(): void {
this._frameCache = null;
}
/**
*
*
*
*/
public clearAll(): void {
this._frameCache = null;
this._persistentCache = null;
this._trackedEntities.clear();
}
/**
*
*/
public hasPersistent(): boolean {
return this._persistentCache !== null;
}
/**
*
*/
public hasFrame(): boolean {
return this._frameCache !== null;
}
/**
*
*/
public getStats(): {
hasFrame: boolean;
hasPersistent: boolean;
trackedCount: number;
frameEntityCount: number;
persistentEntityCount: number;
} {
return {
hasFrame: this._frameCache !== null,
hasPersistent: this._persistentCache !== null,
trackedCount: this._trackedEntities.size,
frameEntityCount: this._frameCache?.length ?? 0,
persistentEntityCount: this._persistentCache?.length ?? 0
};
}
}
+75 -104
View File
@@ -9,18 +9,18 @@ import { createLogger } from '../../Utils/Logger';
import type { EventListenerConfig, TypeSafeEventSystem, EventHandler } from '../Core/EventSystem'; import type { EventListenerConfig, TypeSafeEventSystem, EventHandler } from '../Core/EventSystem';
import type { ComponentConstructor, ComponentInstance } from '../../Types/TypeHelpers'; import type { ComponentConstructor, ComponentInstance } from '../../Types/TypeHelpers';
import type { IService } from '../../Core/ServiceContainer'; import type { IService } from '../../Core/ServiceContainer';
import { EntityCache } from './EntityCache';
/** /**
* *
*
*/ */
interface EventListenerRecord { interface EventListenerRecord {
eventSystem: TypeSafeEventSystem; eventSystem: TypeSafeEventSystem;
eventType: string; eventType: string;
handler: EventHandler;
listenerRef: string; listenerRef: string;
} }
/** /**
* *
* *
@@ -64,9 +64,7 @@ interface EventListenerRecord {
* } * }
* ``` * ```
*/ */
export abstract class EntitySystem< export abstract class EntitySystem implements ISystemBase, IService {
_TComponents extends readonly ComponentConstructor[] = []
> implements ISystemBase, IService {
private _updateOrder: number; private _updateOrder: number;
private _enabled: boolean; private _enabled: boolean;
private _performanceMonitor: PerformanceMonitor | null; private _performanceMonitor: PerformanceMonitor | null;
@@ -77,7 +75,6 @@ export abstract class EntitySystem<
private _scene: Scene | null; private _scene: Scene | null;
protected logger: ReturnType<typeof createLogger>; protected logger: ReturnType<typeof createLogger>;
/** /**
* ID映射缓存 * ID映射缓存
*/ */
@@ -87,30 +84,24 @@ export abstract class EntitySystem<
/** /**
* *
*/ */
private _entityCache: { private _entityCache: EntityCache;
frame: readonly Entity[] | null;
persistent: readonly Entity[] | null;
tracked: Set<Entity>;
invalidate(): void;
clearFrame(): void;
clearAll(): void;
};
/** /**
* *
*/ */
public get entities(): readonly Entity[] { public get entities(): readonly Entity[] {
// 如果在update周期内,优先使用帧缓存 // 如果在update周期内,优先使用帧缓存
if (this._entityCache.frame !== null) { const frameCache = this._entityCache.getFrame();
return this._entityCache.frame; if (frameCache !== null) {
return frameCache;
} }
// 否则使用持久缓存 // 否则使用持久缓存
if (this._entityCache.persistent === null) { if (!this._entityCache.hasPersistent()) {
this._entityCache.persistent = this.queryEntities(); this._entityCache.setPersistent(this.queryEntities());
} }
return this._entityCache.persistent; return this._entityCache.getPersistent()!;
} }
/** /**
@@ -161,23 +152,7 @@ export abstract class EntitySystem<
// 初始化logger // 初始化logger
this.logger = createLogger(this.getLoggerName()); this.logger = createLogger(this.getLoggerName());
this._entityCache = new EntityCache();
this._entityCache = {
frame: null,
persistent: null,
tracked: new Set<Entity>(),
invalidate() {
this.persistent = null;
},
clearFrame() {
this.frame = null;
},
clearAll() {
this.frame = null;
this.persistent = null;
this.tracked.clear();
}
};
} }
/** /**
@@ -203,7 +178,9 @@ export abstract class EntitySystem<
*/ */
private getPerformanceMonitor(): PerformanceMonitor { private getPerformanceMonitor(): PerformanceMonitor {
if (!this._performanceMonitor) { if (!this._performanceMonitor) {
throw new Error(`${this._systemName}: PerformanceMonitor未注入,请确保在Core.create()之后再添加System到Scene`); throw new Error(
`${this._systemName}: PerformanceMonitor未注入,请确保在Core.create()之后再添加System到Scene`
);
} }
return this._performanceMonitor; return this._performanceMonitor;
} }
@@ -251,7 +228,7 @@ export abstract class EntitySystem<
/** /**
* *
* *
* *
*/ */
protected onInitialize(): void { protected onInitialize(): void {
@@ -316,16 +293,28 @@ export abstract class EntitySystem<
/** /**
* *
*
* 使
* - all: 第0位 (1)
* - any: 1 (2)
* - none: 第2位 (4)
* - tag: 第3位 (8)
* - name: 第4位 (16)
* - component: 第5位 (32)
*/ */
private isSingleCondition(condition: QueryCondition): boolean { private isSingleCondition(condition: QueryCondition): boolean {
// 使用位OR运算合并所有条件标记
const flags = const flags =
((condition.all.length > 0) ? 1 : 0) | (condition.all.length > 0 ? 1 : 0) |
((condition.any.length > 0) ? 2 : 0) | (condition.any.length > 0 ? 2 : 0) |
((condition.none.length > 0) ? 4 : 0) | (condition.none.length > 0 ? 4 : 0) |
((condition.tag !== undefined) ? 8 : 0) | (condition.tag !== undefined ? 8 : 0) |
((condition.name !== undefined) ? 16 : 0) | (condition.name !== undefined ? 16 : 0) |
((condition.component !== undefined) ? 32 : 0); (condition.component !== undefined ? 32 : 0);
// 位运算技巧:如果只有一个位被设置,则 flags & (flags - 1) == 0
// 例如:flags=4 (100), flags-1=3 (011), 4&3=0
// 但如果 flags=6 (110), flags-1=5 (101), 6&5=4≠0
return flags !== 0 && (flags & (flags - 1)) === 0; return flags !== 0 && (flags & (flags - 1)) === 0;
} }
@@ -472,8 +461,7 @@ export abstract class EntitySystem<
*/ */
private getEntityIdMap(allEntities: readonly Entity[]): Map<number, Entity> { private getEntityIdMap(allEntities: readonly Entity[]): Map<number, Entity> {
const currentVersion = this.scene?.querySystem?.version ?? 0; const currentVersion = this.scene?.querySystem?.version ?? 0;
if (this._entityIdMap !== null && if (this._entityIdMap !== null && this._entityIdMapVersion === currentVersion) {
this._entityIdMapVersion === currentVersion) {
return this._entityIdMap; return this._entityIdMap;
} }
@@ -511,7 +499,7 @@ export abstract class EntitySystem<
const entityMap = this.getEntityIdMap(allEntities); const entityMap = this.getEntityIdMap(allEntities);
const size = idSet.size; const size = idSet.size;
const result = new Array(size); const result = new Array<Entity>(size);
let index = 0; let index = 0;
for (const id of idSet) { for (const id of idSet) {
@@ -531,7 +519,7 @@ export abstract class EntitySystem<
/** /**
* *
* *
* 使ID集合的单次扫描算法进行复杂查询 * 使ID集合的单次扫描算法进行复杂查询
*/ */
private executeComplexQuery(condition: QueryCondition, querySystem: QuerySystem): readonly Entity[] { private executeComplexQuery(condition: QueryCondition, querySystem: QuerySystem): readonly Entity[] {
@@ -554,10 +542,11 @@ export abstract class EntitySystem<
this.onBegin(); this.onBegin();
// 查询实体并存储到帧缓存中 // 查询实体并存储到帧缓存中
// 响应式查询会自动维护最新的实体列表,updateEntityTracking会在检测到变化时invalidate // 响应式查询会自动维护最新的实体列表,updateEntityTracking会在检测到变化时invalidate
this._entityCache.frame = this.queryEntities(); const queriedEntities = this.queryEntities();
entityCount = this._entityCache.frame.length; this._entityCache.setFrame(queriedEntities);
entityCount = queriedEntities.length;
this.process(this._entityCache.frame); this.process(queriedEntities);
} finally { } finally {
monitor.endMonitoring(this._systemName, startTime, entityCount); monitor.endMonitoring(this._systemName, startTime, entityCount);
} }
@@ -577,7 +566,7 @@ export abstract class EntitySystem<
try { try {
// 使用缓存的实体列表,避免重复查询 // 使用缓存的实体列表,避免重复查询
const entities = this._entityCache.frame || []; const entities = this._entityCache.getFrame() || [];
entityCount = entities.length; entityCount = entities.length;
this.lateProcess(entities); this.lateProcess(entities);
this.onEnd(); this.onEnd();
@@ -590,7 +579,7 @@ export abstract class EntitySystem<
/** /**
* *
* *
* *
*/ */
protected onBegin(): void { protected onBegin(): void {
@@ -599,9 +588,9 @@ export abstract class EntitySystem<
/** /**
* *
* *
* *
* *
* @param entities * @param entities
*/ */
protected process(_entities: readonly Entity[]): void { protected process(_entities: readonly Entity[]): void {
@@ -610,9 +599,9 @@ export abstract class EntitySystem<
/** /**
* *
* *
* *
* *
* @param entities * @param entities
*/ */
protected lateProcess(_entities: readonly Entity[]): void { protected lateProcess(_entities: readonly Entity[]): void {
@@ -621,7 +610,7 @@ export abstract class EntitySystem<
/** /**
* *
* *
* *
*/ */
protected onEnd(): void { protected onEnd(): void {
@@ -630,10 +619,10 @@ export abstract class EntitySystem<
/** /**
* *
* *
* *
* *
* *
* @returns truefalse * @returns truefalse
*/ */
protected onCheckProcessing(): boolean { protected onCheckProcessing(): boolean {
@@ -667,7 +656,7 @@ export abstract class EntitySystem<
/** /**
* *
* *
* @returns * @returns
*/ */
public toString(): string { public toString(): string {
@@ -687,17 +676,17 @@ export abstract class EntitySystem<
// 检查新增的实体 // 检查新增的实体
for (const entity of currentEntities) { for (const entity of currentEntities) {
if (!this._entityCache.tracked.has(entity)) { if (!this._entityCache.isTracked(entity)) {
this._entityCache.tracked.add(entity); this._entityCache.addTracked(entity);
this.onAdded(entity); this.onAdded(entity);
hasChanged = true; hasChanged = true;
} }
} }
// 检查移除的实体 // 检查移除的实体
for (const entity of this._entityCache.tracked) { for (const entity of this._entityCache.getTracked()) {
if (!currentSet.has(entity)) { if (!currentSet.has(entity)) {
this._entityCache.tracked.delete(entity); this._entityCache.removeTracked(entity);
this.onRemoved(entity); this.onRemoved(entity);
hasChanged = true; hasChanged = true;
} }
@@ -711,9 +700,9 @@ export abstract class EntitySystem<
/** /**
* *
* *
* *
* *
* @param entity * @param entity
*/ */
protected onAdded(_entity: Entity): void { protected onAdded(_entity: Entity): void {
@@ -768,15 +757,16 @@ export abstract class EntitySystem<
* @param eventType * @param eventType
* @param handler * @param handler
* @param config * @param config
* @returns ID
*/ */
protected addEventListener<T = any>( protected addEventListener<T>(
eventType: string, eventType: string,
handler: EventHandler<T>, handler: EventHandler<T>,
config?: EventListenerConfig config?: EventListenerConfig
): void { ): string | null {
if (!this.scene?.eventSystem) { if (!this.scene?.eventSystem) {
this.logger.warn(`${this.systemName}: 无法添加事件监听器,scene.eventSystem 不可用`); this.logger.warn(`${this.systemName}: 无法添加事件监听器,scene.eventSystem 不可用`);
return; return null;
} }
const listenerRef = this.scene.eventSystem.on(eventType, handler, config); const listenerRef = this.scene.eventSystem.on(eventType, handler, config);
@@ -786,24 +776,22 @@ export abstract class EntitySystem<
this._eventListeners.push({ this._eventListeners.push({
eventSystem: this.scene.eventSystem, eventSystem: this.scene.eventSystem,
eventType, eventType,
handler,
listenerRef listenerRef
}); });
} }
return listenerRef;
} }
/** /**
* *
* *
* @param eventType * @param eventType
* @param handler * @param listenerRef ID addEventListener
*/ */
protected removeEventListener<T = any>( protected removeEventListener(eventType: string, listenerRef: string): void {
eventType: string,
handler: EventHandler<T>
): void {
const listenerIndex = this._eventListeners.findIndex( const listenerIndex = this._eventListeners.findIndex(
listener => listener.eventType === eventType && listener.handler === handler (listener) => listener.eventType === eventType && listener.listenerRef === listenerRef
); );
if (listenerIndex >= 0) { if (listenerIndex >= 0) {
@@ -887,15 +875,10 @@ export abstract class EntitySystem<
* } * }
* ``` * ```
*/ */
protected requireComponent<T extends ComponentConstructor>( protected requireComponent<T extends ComponentConstructor>(entity: Entity, componentType: T): ComponentInstance<T> {
entity: Entity, const component = entity.getComponent(componentType);
componentType: T
): ComponentInstance<T> {
const component = entity.getComponent(componentType as any);
if (!component) { if (!component) {
throw new Error( throw new Error(`Component ${componentType.name} not found on entity ${entity.name} in ${this.systemName}`);
`Component ${componentType.name} not found on entity ${entity.name} in ${this.systemName}`
);
} }
return component as ComponentInstance<T>; return component as ComponentInstance<T>;
} }
@@ -927,9 +910,9 @@ export abstract class EntitySystem<
entity: Entity, entity: Entity,
...components: T ...components: T
): { [K in keyof T]: ComponentInstance<T[K]> } { ): { [K in keyof T]: ComponentInstance<T[K]> } {
return components.map((type) => return components.map((type) => this.requireComponent(entity, type)) as {
this.requireComponent(entity, type) [K in keyof T]: ComponentInstance<T[K]>;
) as any; };
} }
/** /**
@@ -950,10 +933,7 @@ export abstract class EntitySystem<
* } * }
* ``` * ```
*/ */
protected forEach( protected forEach(entities: readonly Entity[], processor: (entity: Entity, index: number) => void): void {
entities: readonly Entity[],
processor: (entity: Entity, index: number) => void
): void {
for (let i = 0; i < entities.length; i++) { for (let i = 0; i < entities.length; i++) {
processor(entities[i]!, i); processor(entities[i]!, i);
} }
@@ -1000,10 +980,7 @@ export abstract class EntitySystem<
* } * }
* ``` * ```
*/ */
protected mapEntities<R>( protected mapEntities<R>(entities: readonly Entity[], mapper: (entity: Entity, index: number) => R): R[] {
entities: readonly Entity[],
mapper: (entity: Entity, index: number) => R
): R[] {
return Array.from(entities).map(mapper); return Array.from(entities).map(mapper);
} }
@@ -1052,10 +1029,7 @@ export abstract class EntitySystem<
* } * }
* ``` * ```
*/ */
protected someEntity( protected someEntity(entities: readonly Entity[], predicate: (entity: Entity, index: number) => boolean): boolean {
entities: readonly Entity[],
predicate: (entity: Entity, index: number) => boolean
): boolean {
for (let i = 0; i < entities.length; i++) { for (let i = 0; i < entities.length; i++) {
if (predicate(entities[i]!, i)) { if (predicate(entities[i]!, i)) {
return true; return true;
@@ -1081,10 +1055,7 @@ export abstract class EntitySystem<
* } * }
* ``` * ```
*/ */
protected everyEntity( protected everyEntity(entities: readonly Entity[], predicate: (entity: Entity, index: number) => boolean): boolean {
entities: readonly Entity[],
predicate: (entity: Entity, index: number) => boolean
): boolean {
for (let i = 0; i < entities.length; i++) { for (let i = 0; i < entities.length; i++) {
if (!predicate(entities[i]!, i)) { if (!predicate(entities[i]!, i)) {
return false; return false;
@@ -55,4 +55,4 @@ export abstract class IntervalSystem extends EntitySystem {
protected getIntervalDelta(): number { protected getIntervalDelta(): number {
return this.interval + this.intervalRemainder; return this.interval + this.intervalRemainder;
} }
} }
@@ -8,7 +8,7 @@ import { Matcher } from '../Utils/Matcher';
* *
*/ */
export abstract class PassiveSystem extends EntitySystem { export abstract class PassiveSystem extends EntitySystem {
constructor(matcher?: Matcher) { constructor(matcher?: Matcher) {
super(matcher); super(matcher);
} }
@@ -8,7 +8,7 @@ import { Matcher } from '../Utils/Matcher';
* processSystem方法 * processSystem方法
*/ */
export abstract class ProcessingSystem extends EntitySystem { export abstract class ProcessingSystem extends EntitySystem {
constructor(matcher?: Matcher) { constructor(matcher?: Matcher) {
super(matcher); super(matcher);
} }
@@ -197,7 +197,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
protected sharedBuffer: SharedArrayBuffer | null = null; protected sharedBuffer: SharedArrayBuffer | null = null;
protected sharedFloatArray: Float32Array | null = null; protected sharedFloatArray: Float32Array | null = null;
private platformAdapter: IPlatformAdapter; private platformAdapter: IPlatformAdapter;
private hasLoggedSyncMode = false; private hasLoggedSyncMode = false;
constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) { constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) {
super(matcher); super(matcher);
@@ -414,7 +414,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
${sharedProcessFunctionBody} ${sharedProcessFunctionBody}
}; };
userProcessFunction(sharedFloatArray, startIndex, endIndex, deltaTime, systemConfig); userProcessFunction(sharedFloatArray, startIndex, endIndex, deltaTime, systemConfig);
` : ``} ` : ''}
} }
`; `;
} }
@@ -494,7 +494,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
const deltaTime = Time.deltaTime; const deltaTime = Time.deltaTime;
// 3. Worker执行阶段 // 3. Worker执行阶段
const promises = batches.map(batch => const promises = batches.map((batch) =>
this.workerPool!.execute({ this.workerPool!.execute({
entities: batch, entities: batch,
deltaTime, deltaTime,
@@ -525,7 +525,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
*/ */
private processSynchronously(entities: readonly Entity[]): void { private processSynchronously(entities: readonly Entity[]): void {
// 1. 数据提取阶段 // 1. 数据提取阶段
const entityData = entities.map(entity => this.extractEntityData(entity)); const entityData = entities.map((entity) => this.extractEntityData(entity));
// 2. 主线程处理阶段 // 2. 主线程处理阶段
const deltaTime = Time.deltaTime; const deltaTime = Time.deltaTime;
@@ -534,7 +534,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
// 3. 结果应用阶段 // 3. 结果应用阶段
// 处理Promise返回值 // 处理Promise返回值
if (results && typeof (results as any).then === 'function') { if (results && typeof (results as any).then === 'function') {
(results as Promise<TEntityData[]>).then(finalResults => { (results as Promise<TEntityData[]>).then((finalResults) => {
entities.forEach((entity, index) => { entities.forEach((entity, index) => {
this.applyResult(entity, finalResults[index]!); this.applyResult(entity, finalResults[index]!);
}); });
@@ -813,7 +813,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
sharedArrayBufferSupported: boolean; sharedArrayBufferSupported: boolean;
sharedArrayBufferEnabled: boolean; sharedArrayBufferEnabled: boolean;
currentMode: 'shared-buffer' | 'worker' | 'sync'; currentMode: 'shared-buffer' | 'worker' | 'sync';
} { } {
let currentMode: 'shared-buffer' | 'worker' | 'sync' = 'sync'; let currentMode: 'shared-buffer' | 'worker' | 'sync' = 'sync';
if (this.config.enableWorker && this.workerPool) { if (this.config.enableWorker && this.workerPool) {
+1 -1
View File
@@ -10,4 +10,4 @@ export type {
WorkerProcessFunction, WorkerProcessFunction,
WorkerSystemConfig, WorkerSystemConfig,
SharedArrayBufferProcessFunction SharedArrayBufferProcessFunction
} from './WorkerEntitySystem'; } from './WorkerEntitySystem';
@@ -69,7 +69,7 @@ export class BitMask64Utils {
return maskSegments.some((seg, index) => { return maskSegments.some((seg, index) => {
const bitsSeg = bitsSegments[index]; const bitsSeg = bitsSegments[index];
return bitsSeg && ((seg[SegmentPart.LOW] & bitsSeg[SegmentPart.LOW]) !== 0 || (seg[SegmentPart.HIGH] & bitsSeg[SegmentPart.HIGH]) !== 0); return bitsSeg && ((seg[SegmentPart.LOW] & bitsSeg[SegmentPart.LOW]) !== 0 || (seg[SegmentPart.HIGH] & bitsSeg[SegmentPart.HIGH]) !== 0);
}) });
} }
/** /**
@@ -145,7 +145,7 @@ export class BitMask64Utils {
return baseIsZero; return baseIsZero;
} }
// 额外检查扩展区域是否都为0 // 额外检查扩展区域是否都为0
return mask.segments.every(seg => seg[SegmentPart.LOW] === 0 && seg[SegmentPart.HIGH] === 0); return mask.segments.every((seg) => seg[SegmentPart.LOW] === 0 && seg[SegmentPart.HIGH] === 0);
} }
/** /**
@@ -155,7 +155,7 @@ export class BitMask64Utils {
* @returns true * @returns true
*/ */
public static equals(a: BitMask64Data, b: BitMask64Data): boolean { public static equals(a: BitMask64Data, b: BitMask64Data): boolean {
let baseEquals = a.base[SegmentPart.LOW] === b.base[SegmentPart.LOW] && a.base[SegmentPart.HIGH] === b.base[SegmentPart.HIGH]; const baseEquals = a.base[SegmentPart.LOW] === b.base[SegmentPart.LOW] && a.base[SegmentPart.HIGH] === b.base[SegmentPart.HIGH];
// base不相等,或ab都没有扩展区域位,直接返回base比较结果 // base不相等,或ab都没有扩展区域位,直接返回base比较结果
if(!baseEquals || (!a.segments && !b.segments)) return baseEquals; if(!baseEquals || (!a.segments && !b.segments)) return baseEquals;
// 不能假设a,b的segments都存在或长度相同. // 不能假设a,b的segments都存在或长度相同.
@@ -355,7 +355,7 @@ export class BitMask64Utils {
if(!source.segments || source.segments.length == 0) return; if(!source.segments || source.segments.length == 0) return;
// 没有拓展段,则直接复制数组 // 没有拓展段,则直接复制数组
if(!target.segments){ if(!target.segments){
target.segments = source.segments.map(seg => [...seg]); target.segments = source.segments.map((seg) => [...seg]);
return; return;
} }
// source有扩展段,target扩展段不足,则补充长度 // source有扩展段,target扩展段不足,则补充长度
@@ -382,7 +382,7 @@ export class BitMask64Utils {
public static clone(mask: BitMask64Data): BitMask64Data { public static clone(mask: BitMask64Data): BitMask64Data {
return { return {
base: mask.base.slice() as BitMask64Segment, base: mask.base.slice() as BitMask64Segment,
...(mask.segments && { segments: mask.segments.map(seg => [...seg] as BitMask64Segment) }) ...(mask.segments && { segments: mask.segments.map((seg) => [...seg] as BitMask64Segment) })
}; };
} }
@@ -414,8 +414,8 @@ export class BitMask64Utils {
for (let i = -1; i < totalLength; i++) { for (let i = -1; i < totalLength; i++) {
let segResult = ''; let segResult = '';
const bitMaskData = i == -1 ? mask.base : mask.segments![i]!; const bitMaskData = i == -1 ? mask.base : mask.segments![i]!;
let hi = bitMaskData[SegmentPart.HIGH]; const hi = bitMaskData[SegmentPart.HIGH];
let lo = bitMaskData[SegmentPart.LOW]; const lo = bitMaskData[SegmentPart.LOW];
if(radix == 2){ if(radix == 2){
const hiBits = hi.toString(2).padStart(32, '0'); const hiBits = hi.toString(2).padStart(32, '0');
const loBits = lo.toString(2).padStart(32, '0'); const loBits = lo.toString(2).padStart(32, '0');
@@ -493,4 +493,4 @@ export class BitMask64Utils {
return segments[targetSegIndex] ?? null; return segments[targetSegIndex] ?? null;
} }
} }
} }
@@ -1,4 +1,4 @@
import { BitMask64Data } from "./BigIntCompatibility"; import { BitMask64Data } from './BigIntCompatibility';
// FlatHashMapFast.ts // FlatHashMapFast.ts
+10 -10
View File
@@ -156,10 +156,10 @@ export class Bits {
if (maxBits > 64) { if (maxBits > 64) {
maxBits = 64; maxBits = 64;
} }
const result = new Bits(); const result = new Bits();
BitMask64Utils.copy(this._value, result._value); BitMask64Utils.copy(this._value, result._value);
if (maxBits <= 32) { if (maxBits <= 32) {
const mask = (1 << maxBits) - 1; const mask = (1 << maxBits) - 1;
result._value.base[SegmentPart.LOW] = (~result._value.base[SegmentPart.LOW]) & mask; result._value.base[SegmentPart.LOW] = (~result._value.base[SegmentPart.LOW]) & mask;
@@ -174,7 +174,7 @@ export class Bits {
result._value.base[SegmentPart.HIGH] = ~result._value.base[SegmentPart.HIGH]; result._value.base[SegmentPart.HIGH] = ~result._value.base[SegmentPart.HIGH];
} }
} }
return result; return result;
} }
@@ -317,7 +317,7 @@ export class Bits {
if (BitMask64Utils.isZero(this._value)) { if (BitMask64Utils.isZero(this._value)) {
return -1; return -1;
} }
if (this._value.base[SegmentPart.HIGH] !== 0) { if (this._value.base[SegmentPart.HIGH] !== 0) {
for (let i = 31; i >= 0; i--) { for (let i = 31; i >= 0; i--) {
if ((this._value.base[SegmentPart.HIGH] & (1 << i)) !== 0) { if ((this._value.base[SegmentPart.HIGH] & (1 << i)) !== 0) {
@@ -325,13 +325,13 @@ export class Bits {
} }
} }
} }
for (let i = 31; i >= 0; i--) { for (let i = 31; i >= 0; i--) {
if ((this._value.base[SegmentPart.LOW] & (1 << i)) !== 0) { if ((this._value.base[SegmentPart.LOW] & (1 << i)) !== 0) {
return i; return i;
} }
} }
return -1; return -1;
} }
@@ -343,19 +343,19 @@ export class Bits {
if (BitMask64Utils.isZero(this._value)) { if (BitMask64Utils.isZero(this._value)) {
return -1; return -1;
} }
for (let i = 0; i < 32; i++) { for (let i = 0; i < 32; i++) {
if ((this._value.base[SegmentPart.LOW] & (1 << i)) !== 0) { if ((this._value.base[SegmentPart.LOW] & (1 << i)) !== 0) {
return i; return i;
} }
} }
for (let i = 0; i < 32; i++) { for (let i = 0; i < 32; i++) {
if ((this._value.base[SegmentPart.HIGH] & (1 << i)) !== 0) { if ((this._value.base[SegmentPart.HIGH] & (1 << i)) !== 0) {
return i + 32; return i + 32;
} }
} }
return -1; return -1;
} }
} }
@@ -7,14 +7,14 @@ import { IPoolable } from '../../Utils/Pool/IPoolable';
/** /**
* *
* *
* IPoolable接口 * IPoolable接口
*/ */
class PoolableEntitySet extends Set<Entity> implements IPoolable { class PoolableEntitySet extends Set<Entity> implements IPoolable {
constructor(..._args: unknown[]) { constructor(..._args: unknown[]) {
super(); super();
} }
reset(): void { reset(): void {
this.clear(); this.clear();
} }
@@ -22,9 +22,9 @@ class PoolableEntitySet extends Set<Entity> implements IPoolable {
/** /**
* *
* *
* *
* *
* *
* - * -
* - * -
@@ -33,42 +33,42 @@ class PoolableEntitySet extends Set<Entity> implements IPoolable {
export class ComponentSparseSet { export class ComponentSparseSet {
/** /**
* *
* *
* O(1) * O(1)
*/ */
private _entities: SparseSet<Entity>; private _entities: SparseSet<Entity>;
/** /**
* *
* *
* *
* *
*/ */
private _componentMasks: BitMask64Data[] = []; private _componentMasks: BitMask64Data[] = [];
/** /**
* *
* *
* *
*/ */
private _componentToEntities = new Map<ComponentType, PoolableEntitySet>(); private _componentToEntities = new Map<ComponentType, PoolableEntitySet>();
/** /**
* *
* *
* 使core库的Pool系统来管理PoolableEntitySet对象的复用 * 使core库的Pool系统来管理PoolableEntitySet对象的复用
*/ */
private static _entitySetPool = Pool.getPool(PoolableEntitySet, 50, 512); private static _entitySetPool = Pool.getPool(PoolableEntitySet, 50, 512);
constructor() { constructor() {
this._entities = new SparseSet<Entity>(); this._entities = new SparseSet<Entity>();
} }
/** /**
* *
* *
* *
* *
* @param entity * @param entity
*/ */
public addEntity(entity: Entity): void { public addEntity(entity: Entity): void {
@@ -76,44 +76,44 @@ export class ComponentSparseSet {
if (this._entities.has(entity)) { if (this._entities.has(entity)) {
this.removeEntity(entity); this.removeEntity(entity);
} }
let componentMask = BitMask64Utils.clone(BitMask64Utils.ZERO); const componentMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
const entityComponents = new Set<ComponentType>(); const entityComponents = new Set<ComponentType>();
// 分析实体组件并构建位掩码 // 分析实体组件并构建位掩码
for (const component of entity.components) { for (const component of entity.components) {
const componentType = component.constructor as ComponentType; const componentType = component.constructor as ComponentType;
entityComponents.add(componentType); entityComponents.add(componentType);
// 确保组件类型已注册 // 确保组件类型已注册
if (!ComponentRegistry.isRegistered(componentType)) { if (!ComponentRegistry.isRegistered(componentType)) {
ComponentRegistry.register(componentType); ComponentRegistry.register(componentType);
} }
// 获取组件位掩码并合并 // 获取组件位掩码并合并
const bitMask = ComponentRegistry.getBitMask(componentType); const bitMask = ComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(componentMask, bitMask); BitMask64Utils.orInPlace(componentMask, bitMask);
} }
// 添加实体到稀疏集合 // 添加实体到稀疏集合
this._entities.add(entity); this._entities.add(entity);
const entityIndex = this._entities.getIndex(entity)!; const entityIndex = this._entities.getIndex(entity)!;
// 确保位掩码数组有足够空间 // 确保位掩码数组有足够空间
while (this._componentMasks.length <= entityIndex) { while (this._componentMasks.length <= entityIndex) {
this._componentMasks.push(BitMask64Utils.clone(BitMask64Utils.ZERO)); this._componentMasks.push(BitMask64Utils.clone(BitMask64Utils.ZERO));
} }
this._componentMasks[entityIndex] = componentMask; this._componentMasks[entityIndex] = componentMask;
// 更新组件类型到实体的映射 // 更新组件类型到实体的映射
this.updateComponentMappings(entity, entityComponents, true); this.updateComponentMappings(entity, entityComponents, true);
} }
/** /**
* *
* *
* *
* *
* @param entity * @param entity
*/ */
public removeEntity(entity: Entity): void { public removeEntity(entity: Entity): void {
@@ -121,16 +121,16 @@ export class ComponentSparseSet {
if (entityIndex === undefined) { if (entityIndex === undefined) {
return; // 实体不存在 return; // 实体不存在
} }
// 获取实体的组件类型集合 // 获取实体的组件类型集合
const entityComponents = this.getEntityComponentTypes(entity); const entityComponents = this.getEntityComponentTypes(entity);
// 更新组件类型到实体的映射 // 更新组件类型到实体的映射
this.updateComponentMappings(entity, entityComponents, false); this.updateComponentMappings(entity, entityComponents, false);
// 从稀疏集合中移除实体 // 从稀疏集合中移除实体
this._entities.remove(entity); this._entities.remove(entity);
// 维护位掩码数组的紧凑性 // 维护位掩码数组的紧凑性
const lastIndex = this._componentMasks.length - 1; const lastIndex = this._componentMasks.length - 1;
if (entityIndex !== lastIndex) { if (entityIndex !== lastIndex) {
@@ -139,10 +139,10 @@ export class ComponentSparseSet {
} }
this._componentMasks.pop(); this._componentMasks.pop();
} }
/** /**
* *
* *
* @param componentType * @param componentType
* @returns * @returns
*/ */
@@ -150,12 +150,12 @@ export class ComponentSparseSet {
const entities = this._componentToEntities.get(componentType); const entities = this._componentToEntities.get(componentType);
return entities ? new Set(entities) : new Set<Entity>(); return entities ? new Set(entities) : new Set<Entity>();
} }
/** /**
* AND操作 * AND操作
* *
* *
* *
* @param componentTypes * @param componentTypes
* @returns * @returns
*/ */
@@ -163,13 +163,13 @@ export class ComponentSparseSet {
if (componentTypes.length === 0) { if (componentTypes.length === 0) {
return new Set<Entity>(); return new Set<Entity>();
} }
if (componentTypes.length === 1) { if (componentTypes.length === 1) {
return this.queryByComponent(componentTypes[0]!); return this.queryByComponent(componentTypes[0]!);
} }
// 构建目标位掩码 // 构建目标位掩码
let targetMask = BitMask64Utils.clone(BitMask64Utils.ZERO); const targetMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const componentType of componentTypes) { for (const componentType of componentTypes) {
if (!ComponentRegistry.isRegistered(componentType)) { if (!ComponentRegistry.isRegistered(componentType)) {
return new Set<Entity>(); // 未注册的组件类型,结果为空 return new Set<Entity>(); // 未注册的组件类型,结果为空
@@ -177,9 +177,9 @@ export class ComponentSparseSet {
const bitMask = ComponentRegistry.getBitMask(componentType); const bitMask = ComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(targetMask, bitMask); BitMask64Utils.orInPlace(targetMask, bitMask);
} }
const result = ComponentSparseSet._entitySetPool.obtain(); const result = ComponentSparseSet._entitySetPool.obtain();
// 遍历所有实体,检查位掩码匹配 // 遍历所有实体,检查位掩码匹配
this._entities.forEach((entity, index) => { this._entities.forEach((entity, index) => {
const entityMask = this._componentMasks[index]!; const entityMask = this._componentMasks[index]!;
@@ -187,15 +187,15 @@ export class ComponentSparseSet {
result.add(entity); result.add(entity);
} }
}); });
return result; return result;
} }
/** /**
* OR操作 * OR操作
* *
* *
* *
* @param componentTypes * @param componentTypes
* @returns * @returns
*/ */
@@ -203,26 +203,26 @@ export class ComponentSparseSet {
if (componentTypes.length === 0) { if (componentTypes.length === 0) {
return new Set<Entity>(); return new Set<Entity>();
} }
if (componentTypes.length === 1) { if (componentTypes.length === 1) {
return this.queryByComponent(componentTypes[0]!); return this.queryByComponent(componentTypes[0]!);
} }
// 构建目标位掩码 // 构建目标位掩码
let targetMask = BitMask64Utils.clone(BitMask64Utils.ZERO); const targetMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const componentType of componentTypes) { for (const componentType of componentTypes) {
if (ComponentRegistry.isRegistered(componentType)) { if (ComponentRegistry.isRegistered(componentType)) {
const bitMask = ComponentRegistry.getBitMask(componentType); const bitMask = ComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(targetMask, bitMask); BitMask64Utils.orInPlace(targetMask, bitMask);
} }
} }
if (BitMask64Utils.equals(targetMask, BitMask64Utils.ZERO)) { if (BitMask64Utils.equals(targetMask, BitMask64Utils.ZERO)) {
return new Set<Entity>(); // 没有有效的组件类型 return new Set<Entity>(); // 没有有效的组件类型
} }
const result = ComponentSparseSet._entitySetPool.obtain(); const result = ComponentSparseSet._entitySetPool.obtain();
// 遍历所有实体,检查位掩码匹配 // 遍历所有实体,检查位掩码匹配
this._entities.forEach((entity, index) => { this._entities.forEach((entity, index) => {
const entityMask = this._componentMasks[index]!; const entityMask = this._componentMasks[index]!;
@@ -230,13 +230,13 @@ export class ComponentSparseSet {
result.add(entity); result.add(entity);
} }
}); });
return result; return result;
} }
/** /**
* *
* *
* @param entity * @param entity
* @param componentType * @param componentType
* @returns * @returns
@@ -246,20 +246,20 @@ export class ComponentSparseSet {
if (entityIndex === undefined) { if (entityIndex === undefined) {
return false; return false;
} }
if (!ComponentRegistry.isRegistered(componentType)) { if (!ComponentRegistry.isRegistered(componentType)) {
return false; return false;
} }
const entityMask = this._componentMasks[entityIndex]!; const entityMask = this._componentMasks[entityIndex]!;
const componentMask = ComponentRegistry.getBitMask(componentType); const componentMask = ComponentRegistry.getBitMask(componentType);
return BitMask64Utils.hasAny(entityMask, componentMask); return BitMask64Utils.hasAny(entityMask, componentMask);
} }
/** /**
* *
* *
* @param entity * @param entity
* @returns undefined * @returns undefined
*/ */
@@ -270,33 +270,33 @@ export class ComponentSparseSet {
} }
return this._componentMasks[entityIndex]; return this._componentMasks[entityIndex];
} }
/** /**
* *
* *
* @returns * @returns
*/ */
public getAllEntities(): Entity[] { public getAllEntities(): Entity[] {
return this._entities.toArray(); return this._entities.toArray();
} }
/** /**
* *
*/ */
public get size(): number { public get size(): number {
return this._entities.size; return this._entities.size;
} }
/** /**
* *
*/ */
public get isEmpty(): boolean { public get isEmpty(): boolean {
return this._entities.isEmpty; return this._entities.isEmpty;
} }
/** /**
* *
* *
* @param callback * @param callback
*/ */
public forEach(callback: (entity: Entity, mask: BitMask64Data, index: number) => void): void { public forEach(callback: (entity: Entity, mask: BitMask64Data, index: number) => void): void {
@@ -304,21 +304,21 @@ export class ComponentSparseSet {
callback(entity, this._componentMasks[index]!, index); callback(entity, this._componentMasks[index]!, index);
}); });
} }
/** /**
* *
*/ */
public clear(): void { public clear(): void {
this._entities.clear(); this._entities.clear();
this._componentMasks.length = 0; this._componentMasks.length = 0;
// 清理时将所有持有的实体集合返回到池中 // 清理时将所有持有的实体集合返回到池中
for (const entitySet of this._componentToEntities.values()) { for (const entitySet of this._componentToEntities.values()) {
ComponentSparseSet._entitySetPool.release(entitySet); ComponentSparseSet._entitySetPool.release(entitySet);
} }
this._componentToEntities.clear(); this._componentToEntities.clear();
} }
/** /**
* 使 * 使
*/ */
@@ -327,15 +327,15 @@ export class ComponentSparseSet {
masksMemory: number; masksMemory: number;
mappingsMemory: number; mappingsMemory: number;
totalMemory: number; totalMemory: number;
} { } {
const entitiesStats = this._entities.getMemoryStats(); const entitiesStats = this._entities.getMemoryStats();
const masksMemory = this._componentMasks.length * 16; // 估计每个BigInt 16字节 const masksMemory = this._componentMasks.length * 16; // 估计每个BigInt 16字节
let mappingsMemory = this._componentToEntities.size * 16; // Map条目开销 let mappingsMemory = this._componentToEntities.size * 16; // Map条目开销
for (const entitySet of this._componentToEntities.values()) { for (const entitySet of this._componentToEntities.values()) {
mappingsMemory += entitySet.size * 8; // 每个实体引用8字节 mappingsMemory += entitySet.size * 8; // 每个实体引用8字节
} }
return { return {
entitiesMemory: entitiesStats.totalMemory, entitiesMemory: entitiesStats.totalMemory,
masksMemory, masksMemory,
@@ -343,7 +343,7 @@ export class ComponentSparseSet {
totalMemory: entitiesStats.totalMemory + masksMemory + mappingsMemory totalMemory: entitiesStats.totalMemory + masksMemory + mappingsMemory
}; };
} }
/** /**
* *
*/ */
@@ -352,12 +352,12 @@ export class ComponentSparseSet {
if (!this._entities.validate()) { if (!this._entities.validate()) {
return false; return false;
} }
// 检查位掩码数组长度一致性 // 检查位掩码数组长度一致性
if (this._componentMasks.length !== this._entities.size) { if (this._componentMasks.length !== this._entities.size) {
return false; return false;
} }
// 检查组件映射的一致性 // 检查组件映射的一致性
const allMappedEntities = new Set<Entity>(); const allMappedEntities = new Set<Entity>();
for (const entitySet of this._componentToEntities.values()) { for (const entitySet of this._componentToEntities.values()) {
@@ -365,17 +365,17 @@ export class ComponentSparseSet {
allMappedEntities.add(entity); allMappedEntities.add(entity);
} }
} }
// 验证映射中的实体都在稀疏集合中 // 验证映射中的实体都在稀疏集合中
for (const entity of allMappedEntities) { for (const entity of allMappedEntities) {
if (!this._entities.has(entity)) { if (!this._entities.has(entity)) {
return false; return false;
} }
} }
return true; return true;
} }
/** /**
* *
*/ */
@@ -386,18 +386,18 @@ export class ComponentSparseSet {
} }
return componentTypes; return componentTypes;
} }
/** /**
* *
*/ */
private updateComponentMappings( private updateComponentMappings(
entity: Entity, entity: Entity,
componentTypes: Set<ComponentType>, componentTypes: Set<ComponentType>,
add: boolean add: boolean
): void { ): void {
for (const componentType of componentTypes) { for (const componentType of componentTypes) {
let entities = this._componentToEntities.get(componentType); let entities = this._componentToEntities.get(componentType);
if (add) { if (add) {
if (!entities) { if (!entities) {
entities = ComponentSparseSet._entitySetPool.obtain(); entities = ComponentSparseSet._entitySetPool.obtain();
@@ -415,5 +415,5 @@ export class ComponentSparseSet {
} }
} }
} }
} }
+12 -12
View File
@@ -45,7 +45,7 @@ export class EntityList {
this.buffer.push(entity); this.buffer.push(entity);
this._idToEntity.set(entity.id, entity); this._idToEntity.set(entity.id, entity);
// 更新名称索引 // 更新名称索引
this.updateNameIndex(entity, true); this.updateNameIndex(entity, true);
} }
@@ -67,10 +67,10 @@ export class EntityList {
if (index !== -1) { if (index !== -1) {
this.buffer.splice(index, 1); this.buffer.splice(index, 1);
this._idToEntity.delete(entity.id); this._idToEntity.delete(entity.id);
// 更新名称索引 // 更新名称索引
this.updateNameIndex(entity, false); this.updateNameIndex(entity, false);
// 回收实体ID到ID池 // 回收实体ID到ID池
if (this._scene && this._scene.identifierPool) { if (this._scene && this._scene.identifierPool) {
this._scene.identifierPool.checkIn(entity.id); this._scene.identifierPool.checkIn(entity.id);
@@ -84,19 +84,19 @@ export class EntityList {
public removeAllEntities(): void { public removeAllEntities(): void {
// 收集所有实体ID用于回收 // 收集所有实体ID用于回收
const idsToRecycle: number[] = []; const idsToRecycle: number[] = [];
for (let i = this.buffer.length - 1; i >= 0; i--) { for (let i = this.buffer.length - 1; i >= 0; i--) {
idsToRecycle.push(this.buffer[i]!.id); idsToRecycle.push(this.buffer[i]!.id);
this.buffer[i]!.destroy(); this.buffer[i]!.destroy();
} }
// 批量回收ID // 批量回收ID
if (this._scene && this._scene.identifierPool) { if (this._scene && this._scene.identifierPool) {
for (const id of idsToRecycle) { for (const id of idsToRecycle) {
this._scene.identifierPool.checkIn(id); this._scene.identifierPool.checkIn(id);
} }
} }
this.buffer.length = 0; this.buffer.length = 0;
this._idToEntity.clear(); this._idToEntity.clear();
this._nameToEntities.clear(); this._nameToEntities.clear();
@@ -170,13 +170,13 @@ export class EntityList {
*/ */
public findEntitiesByTag(tag: number): Entity[] { public findEntitiesByTag(tag: number): Entity[] {
const result: Entity[] = []; const result: Entity[] = [];
for (const entity of this.buffer) { for (const entity of this.buffer) {
if (entity.tag === tag) { if (entity.tag === tag) {
result.push(entity); result.push(entity);
} }
} }
return result; return result;
} }
@@ -187,13 +187,13 @@ export class EntityList {
*/ */
public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] { public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] {
const result: Entity[] = []; const result: Entity[] = [];
for (const entity of this.buffer) { for (const entity of this.buffer) {
if (entity.hasComponent(componentType)) { if (entity.hasComponent(componentType)) {
result.push(entity); result.push(entity);
} }
} }
return result; return result;
} }
@@ -243,7 +243,7 @@ export class EntityList {
const index = entities.indexOf(entity); const index = entities.indexOf(entity);
if (index !== -1) { if (index !== -1) {
entities.splice(index, 1); entities.splice(index, 1);
// 如果数组为空,删除映射 // 如果数组为空,删除映射
if (entities.length === 0) { if (entities.length === 0) {
this._nameToEntities.delete(entity.name); this._nameToEntities.delete(entity.name);
@@ -263,7 +263,7 @@ export class EntityList {
pendingAdd: number; pendingAdd: number;
pendingRemove: number; pendingRemove: number;
nameIndexSize: number; nameIndexSize: number;
} { } {
let activeCount = 0; let activeCount = 0;
for (const entity of this.buffer) { for (const entity of this.buffer) {
if (entity.enabled && !entity.isDestroyed) { if (entity.enabled && !entity.isDestroyed) {
@@ -53,7 +53,7 @@ export class EntityProcessorList {
/** /**
* *
* *
* *
*/ */
public begin(): void { public begin(): void {
+64 -64
View File
@@ -1,22 +1,22 @@
/** /**
* ID池管理器 * ID池管理器
* *
* ID的分配和回收 * ID的分配和回收
* ID由索引和版本组成ID被回收时版本会递增 * ID由索引和版本组成ID被回收时版本会递增
* *
* 65535166553516 * 65535166553516
* 42亿ID组合ECS大规模实体需求 * 42亿ID组合ECS大规模实体需求
* *
* @example * @example
* ```typescript * ```typescript
* const pool = new IdentifierPool(); * const pool = new IdentifierPool();
* *
* // 分配ID * // 分配ID
* const id = pool.checkOut(); // 例如: 65536 (版本1,索引0) * const id = pool.checkOut(); // 例如: 65536 (版本1,索引0)
* *
* // 回收ID * // 回收ID
* pool.checkIn(id); * pool.checkIn(id);
* *
* // 验证ID是否有效 * // 验证ID是否有效
* const isValid = pool.isValid(id); // false,因为版本已递增 * const isValid = pool.isValid(id); // false,因为版本已递增
* ``` * ```
@@ -26,18 +26,18 @@ export class IdentifierPool {
* *
*/ */
private _nextAvailableIndex = 0; private _nextAvailableIndex = 0;
/** /**
* *
*/ */
private _freeIndices: number[] = []; private _freeIndices: number[] = [];
/** /**
* *
* Map * Map
*/ */
private _generations = new Map<number, number>(); private _generations = new Map<number, number>();
/** /**
* *
* ID * ID
@@ -47,30 +47,30 @@ export class IdentifierPool {
generation: number; generation: number;
timestamp: number; timestamp: number;
}> = []; }> = [];
/** /**
* *
*/ */
private _recycleDelay: number = 100; private _recycleDelay: number = 100;
/** /**
* 16 * 16
* 16 + 16 = 32ID * 16 + 16 = 32ID
* *
*/ */
private static readonly MAX_INDEX = 0xFFFF; // 65535 private static readonly MAX_INDEX = 0xFFFF; // 65535
/** /**
* 16 * 16
*/ */
private static readonly MAX_GENERATION = 0xFFFF; // 65535 private static readonly MAX_GENERATION = 0xFFFF; // 65535
/** /**
* *
* *
*/ */
private _expansionBlockSize: number; private _expansionBlockSize: number;
/** /**
* *
*/ */
@@ -83,32 +83,32 @@ export class IdentifierPool {
/** /**
* *
* *
* @param recycleDelay 100ms * @param recycleDelay 100ms
* @param expansionBlockSize 1024 * @param expansionBlockSize 1024
*/ */
constructor(recycleDelay: number = 100, expansionBlockSize: number = 1024) { constructor(recycleDelay: number = 100, expansionBlockSize: number = 1024) {
this._recycleDelay = recycleDelay; this._recycleDelay = recycleDelay;
this._expansionBlockSize = expansionBlockSize; this._expansionBlockSize = expansionBlockSize;
// 预分配第一个块的世代信息 // 预分配第一个块的世代信息
this._preAllocateGenerations(0, this._expansionBlockSize); this._preAllocateGenerations(0, this._expansionBlockSize);
} }
/** /**
* ID * ID
* *
* 32ID1616 * 32ID1616
* *
* @returns ID * @returns ID
* @throws {Error} * @throws {Error}
*/ */
public checkOut(): number { public checkOut(): number {
// 处理延迟回收队列 // 处理延迟回收队列
this._processDelayedRecycle(); this._processDelayedRecycle();
let index: number; let index: number;
if (this._freeIndices.length > 0) { if (this._freeIndices.length > 0) {
// 重用回收的索引 // 重用回收的索引
index = this._freeIndices.pop()!; index = this._freeIndices.pop()!;
@@ -117,69 +117,69 @@ export class IdentifierPool {
if (this._nextAvailableIndex > IdentifierPool.MAX_INDEX) { if (this._nextAvailableIndex > IdentifierPool.MAX_INDEX) {
throw new Error( throw new Error(
`实体索引已达到框架设计限制 (${IdentifierPool.MAX_INDEX})。` + `实体索引已达到框架设计限制 (${IdentifierPool.MAX_INDEX})。` +
`这意味着您已经分配了超过65535个不同的实体索引。` + '这意味着您已经分配了超过65535个不同的实体索引。' +
`这是16位索引设计的限制,考虑优化实体回收策略或升级到64位ID设计。` '这是16位索引设计的限制,考虑优化实体回收策略或升级到64位ID设计。'
); );
} }
index = this._nextAvailableIndex++; index = this._nextAvailableIndex++;
// 按需扩展世代存储 // 按需扩展世代存储
this._ensureGenerationCapacity(index); this._ensureGenerationCapacity(index);
} }
const generation = this._generations.get(index) || 1; const generation = this._generations.get(index) || 1;
this._stats.totalAllocated++; this._stats.totalAllocated++;
this._stats.currentActive++; this._stats.currentActive++;
return this._packId(index, generation); return this._packId(index, generation);
} }
/** /**
* ID * ID
* *
* ID的有效性后 * ID的有效性后
* ID不会立即可重用 * ID不会立即可重用
* *
* @param id ID * @param id ID
* @returns ID是否有效且未被重复回收 * @returns ID是否有效且未被重复回收
*/ */
public checkIn(id: number): boolean { public checkIn(id: number): boolean {
const index = this._unpackIndex(id); const index = this._unpackIndex(id);
const generation = this._unpackGeneration(id); const generation = this._unpackGeneration(id);
// 验证ID有效性 // 验证ID有效性
if (!this._isValidId(index, generation)) { if (!this._isValidId(index, generation)) {
return false; return false;
} }
// 检查是否已经在待回收队列中 // 检查是否已经在待回收队列中
const alreadyPending = this._pendingRecycle.some( const alreadyPending = this._pendingRecycle.some(
item => item.index === index && item.generation === generation (item) => item.index === index && item.generation === generation
); );
if (alreadyPending) { if (alreadyPending) {
return false; // 已经在回收队列中,拒绝重复回收 return false; // 已经在回收队列中,拒绝重复回收
} }
// 加入延迟回收队列 // 加入延迟回收队列
this._pendingRecycle.push({ this._pendingRecycle.push({
index, index,
generation, generation,
timestamp: Date.now() timestamp: Date.now()
}); });
this._stats.currentActive--; this._stats.currentActive--;
this._stats.totalRecycled++; this._stats.totalRecycled++;
return true; return true;
} }
/** /**
* ID是否有效 * ID是否有效
* *
* ID的索引和世代版本是否匹配当前状态 * ID的索引和世代版本是否匹配当前状态
* *
* @param id ID * @param id ID
* @returns ID是否有效 * @returns ID是否有效
*/ */
@@ -191,7 +191,7 @@ export class IdentifierPool {
/** /**
* *
* *
* @returns * @returns
*/ */
public getStats(): { public getStats(): {
@@ -217,20 +217,20 @@ export class IdentifierPool {
averageGeneration: number; averageGeneration: number;
/** 世代存储大小 */ /** 世代存储大小 */
generationStorageSize: number; generationStorageSize: number;
} { } {
// 计算平均世代版本 // 计算平均世代版本
let totalGeneration = 0; let totalGeneration = 0;
let generationCount = 0; let generationCount = 0;
for (const [index, generation] of this._generations) { for (const [index, generation] of this._generations) {
if (index < this._nextAvailableIndex) { if (index < this._nextAvailableIndex) {
totalGeneration += generation; totalGeneration += generation;
generationCount++; generationCount++;
} }
} }
const averageGeneration = generationCount > 0 const averageGeneration = generationCount > 0
? totalGeneration / generationCount ? totalGeneration / generationCount
: 1; : 1;
return { return {
@@ -250,7 +250,7 @@ export class IdentifierPool {
/** /**
* *
* *
* *
* *
*/ */
@@ -260,19 +260,19 @@ export class IdentifierPool {
/** /**
* *
* *
* *
* *
* @param forceAll * @param forceAll
* @private * @private
*/ */
private _processDelayedRecycle(forceAll: boolean = false): void { private _processDelayedRecycle(forceAll: boolean = false): void {
if (this._pendingRecycle.length === 0) return; if (this._pendingRecycle.length === 0) return;
const now = Date.now(); const now = Date.now();
const readyToRecycle: typeof this._pendingRecycle = []; const readyToRecycle: typeof this._pendingRecycle = [];
const stillPending: typeof this._pendingRecycle = []; const stillPending: typeof this._pendingRecycle = [];
// 分离已到期和未到期的项 // 分离已到期和未到期的项
for (const item of this._pendingRecycle) { for (const item of this._pendingRecycle) {
if (forceAll || now - item.timestamp >= this._recycleDelay) { if (forceAll || now - item.timestamp >= this._recycleDelay) {
@@ -281,33 +281,33 @@ export class IdentifierPool {
stillPending.push(item); stillPending.push(item);
} }
} }
// 处理到期的回收项 // 处理到期的回收项
for (const item of readyToRecycle) { for (const item of readyToRecycle) {
// 再次验证ID有效性(防止重复回收) // 再次验证ID有效性(防止重复回收)
if (this._isValidId(item.index, item.generation)) { if (this._isValidId(item.index, item.generation)) {
// 递增世代版本 // 递增世代版本
let newGeneration = item.generation + 1; let newGeneration = item.generation + 1;
// 防止世代版本溢出 // 防止世代版本溢出
if (newGeneration > IdentifierPool.MAX_GENERATION) { if (newGeneration > IdentifierPool.MAX_GENERATION) {
newGeneration = 1; // 重置为1而不是0 newGeneration = 1; // 重置为1而不是0
} }
this._generations.set(item.index, newGeneration); this._generations.set(item.index, newGeneration);
// 添加到空闲列表 // 添加到空闲列表
this._freeIndices.push(item.index); this._freeIndices.push(item.index);
} }
} }
// 更新待回收队列 // 更新待回收队列
this._pendingRecycle = stillPending; this._pendingRecycle = stillPending;
} }
/** /**
* *
* *
* @param startIndex * @param startIndex
* @param count * @param count
* @private * @private
@@ -324,7 +324,7 @@ export class IdentifierPool {
/** /**
* *
* *
* @param index * @param index
* @private * @private
*/ */
@@ -332,7 +332,7 @@ export class IdentifierPool {
if (!this._generations.has(index)) { if (!this._generations.has(index)) {
// 计算需要扩展的起始位置 // 计算需要扩展的起始位置
const expansionStart = Math.floor(index / this._expansionBlockSize) * this._expansionBlockSize; const expansionStart = Math.floor(index / this._expansionBlockSize) * this._expansionBlockSize;
// 预分配一个块 // 预分配一个块
this._preAllocateGenerations(expansionStart, this._expansionBlockSize); this._preAllocateGenerations(expansionStart, this._expansionBlockSize);
} }
@@ -340,7 +340,7 @@ export class IdentifierPool {
/** /**
* 使 * 使
* *
* @returns 使 * @returns 使
* @private * @private
*/ */
@@ -348,13 +348,13 @@ export class IdentifierPool {
const generationMapSize = this._generations.size * 16; // Map overhead + number pair const generationMapSize = this._generations.size * 16; // Map overhead + number pair
const freeIndicesSize = this._freeIndices.length * 8; const freeIndicesSize = this._freeIndices.length * 8;
const pendingRecycleSize = this._pendingRecycle.length * 32; const pendingRecycleSize = this._pendingRecycle.length * 32;
return generationMapSize + freeIndicesSize + pendingRecycleSize; return generationMapSize + freeIndicesSize + pendingRecycleSize;
} }
/** /**
* 32ID * 32ID
* *
* @param index 16 * @param index 16
* @param generation 16 * @param generation 16
* @returns 32ID * @returns 32ID
@@ -366,7 +366,7 @@ export class IdentifierPool {
/** /**
* ID中解包索引 * ID中解包索引
* *
* @param id 32ID * @param id 32ID
* @returns 16 * @returns 16
* @private * @private
@@ -377,7 +377,7 @@ export class IdentifierPool {
/** /**
* ID中解包世代版本 * ID中解包世代版本
* *
* @param id 32ID * @param id 32ID
* @returns 16 * @returns 16
* @private * @private
@@ -388,7 +388,7 @@ export class IdentifierPool {
/** /**
* ID有效性检查 * ID有效性检查
* *
* @param index * @param index
* @param generation * @param generation
* @returns * @returns
@@ -398,8 +398,8 @@ export class IdentifierPool {
if (index < 0 || index >= this._nextAvailableIndex) { if (index < 0 || index >= this._nextAvailableIndex) {
return false; return false;
} }
const currentGeneration = this._generations.get(index); const currentGeneration = this._generations.get(index);
return currentGeneration !== undefined && currentGeneration === generation; return currentGeneration !== undefined && currentGeneration === generation;
} }
} }
+16 -16
View File
@@ -15,15 +15,15 @@ export interface QueryCondition {
/** /**
* *
* *
* *
* *
* @example * @example
* ```typescript * ```typescript
* const matcher = Matcher.all(Position, Velocity) * const matcher = Matcher.all(Position, Velocity)
* .any(Health, Shield) * .any(Health, Shield)
* .none(Dead); * .none(Dead);
* *
* // 获取查询条件 * // 获取查询条件
* const condition = matcher.getCondition(); * const condition = matcher.getCondition();
* ``` * ```
@@ -219,8 +219,8 @@ export class Matcher {
* *
*/ */
public isEmpty(): boolean { public isEmpty(): boolean {
return this.condition.all.length === 0 && return this.condition.all.length === 0 &&
this.condition.any.length === 0 && this.condition.any.length === 0 &&
this.condition.none.length === 0 && this.condition.none.length === 0 &&
this.condition.tag === undefined && this.condition.tag === undefined &&
this.condition.name === undefined && this.condition.name === undefined &&
@@ -265,32 +265,32 @@ export class Matcher {
*/ */
public toString(): string { public toString(): string {
const parts: string[] = []; const parts: string[] = [];
if (this.condition.all.length > 0) { if (this.condition.all.length > 0) {
parts.push(`all(${this.condition.all.map(t => getComponentTypeName(t)).join(', ')})`); parts.push(`all(${this.condition.all.map((t) => getComponentTypeName(t)).join(', ')})`);
} }
if (this.condition.any.length > 0) { if (this.condition.any.length > 0) {
parts.push(`any(${this.condition.any.map(t => getComponentTypeName(t)).join(', ')})`); parts.push(`any(${this.condition.any.map((t) => getComponentTypeName(t)).join(', ')})`);
} }
if (this.condition.none.length > 0) { if (this.condition.none.length > 0) {
parts.push(`none(${this.condition.none.map(t => getComponentTypeName(t)).join(', ')})`); parts.push(`none(${this.condition.none.map((t) => getComponentTypeName(t)).join(', ')})`);
} }
if (this.condition.tag !== undefined) { if (this.condition.tag !== undefined) {
parts.push(`tag(${this.condition.tag})`); parts.push(`tag(${this.condition.tag})`);
} }
if (this.condition.name !== undefined) { if (this.condition.name !== undefined) {
parts.push(`name(${this.condition.name})`); parts.push(`name(${this.condition.name})`);
} }
if (this.condition.component !== undefined) { if (this.condition.component !== undefined) {
parts.push(`component(${getComponentTypeName(this.condition.component)})`); parts.push(`component(${getComponentTypeName(this.condition.component)})`);
} }
return `Matcher[${parts.join(' & ')}]`; return `Matcher[${parts.join(' & ')}]`;
} }
} }
+55 -55
View File
@@ -1,22 +1,22 @@
/** /**
* *
* *
* O(1) * O(1)
* 使访 * 使访
* *
* @template T * @template T
* *
* @example * @example
* ```typescript * ```typescript
* const sparseSet = new SparseSet<Entity>(); * const sparseSet = new SparseSet<Entity>();
* *
* sparseSet.add(entity1); * sparseSet.add(entity1);
* sparseSet.add(entity2); * sparseSet.add(entity2);
* *
* if (sparseSet.has(entity1)) { * if (sparseSet.has(entity1)) {
* sparseSet.remove(entity1); * sparseSet.remove(entity1);
* } * }
* *
* sparseSet.forEach((entity, index) => { * sparseSet.forEach((entity, index) => {
* console.log(`Entity at index ${index}: ${entity.name}`); * console.log(`Entity at index ${index}: ${entity.name}`);
* }); * });
@@ -25,21 +25,21 @@
export class SparseSet<T> { export class SparseSet<T> {
/** /**
* *
* *
* *
*/ */
private _dense: T[] = []; private _dense: T[] = [];
/** /**
* *
* *
* O(1) * O(1)
*/ */
private _sparse = new Map<T, number>(); private _sparse = new Map<T, number>();
/** /**
* *
* *
* @param item * @param item
* @returns false表示元素已存在 * @returns false表示元素已存在
*/ */
@@ -47,21 +47,21 @@ export class SparseSet<T> {
if (this._sparse.has(item)) { if (this._sparse.has(item)) {
return false; // 元素已存在 return false; // 元素已存在
} }
const index = this._dense.length; const index = this._dense.length;
this._dense.push(item); this._dense.push(item);
this._sparse.set(item, index); this._sparse.set(item, index);
return true; return true;
} }
/** /**
* *
* *
* 使swap-and-pop技术保持数组紧凑性 * 使swap-and-pop技术保持数组紧凑性
* 1. * 1.
* 2. * 2.
* 3. * 3.
* *
* @param item * @param item
* @returns false表示元素不存在 * @returns false表示元素不存在
*/ */
@@ -70,72 +70,72 @@ export class SparseSet<T> {
if (index === undefined) { if (index === undefined) {
return false; // 元素不存在 return false; // 元素不存在
} }
const lastIndex = this._dense.length - 1; const lastIndex = this._dense.length - 1;
// 如果不是最后一个元素,则与最后一个元素交换 // 如果不是最后一个元素,则与最后一个元素交换
if (index !== lastIndex) { if (index !== lastIndex) {
const lastItem = this._dense[lastIndex]!; const lastItem = this._dense[lastIndex]!;
this._dense[index] = lastItem; this._dense[index] = lastItem;
this._sparse.set(lastItem, index); this._sparse.set(lastItem, index);
} }
// 移除最后一个元素 // 移除最后一个元素
this._dense.pop(); this._dense.pop();
this._sparse.delete(item); this._sparse.delete(item);
return true; return true;
} }
/** /**
* *
* *
* @param item * @param item
* @returns * @returns
*/ */
public has(item: T): boolean { public has(item: T): boolean {
return this._sparse.has(item); return this._sparse.has(item);
} }
/** /**
* *
* *
* @param item * @param item
* @returns undefined * @returns undefined
*/ */
public getIndex(item: T): number | undefined { public getIndex(item: T): number | undefined {
return this._sparse.get(item); return this._sparse.get(item);
} }
/** /**
* *
* *
* @param index * @param index
* @returns undefined * @returns undefined
*/ */
public getByIndex(index: number): T | undefined { public getByIndex(index: number): T | undefined {
return this._dense[index]; return this._dense[index];
} }
/** /**
* *
*/ */
public get size(): number { public get size(): number {
return this._dense.length; return this._dense.length;
} }
/** /**
* *
*/ */
public get isEmpty(): boolean { public get isEmpty(): boolean {
return this._dense.length === 0; return this._dense.length === 0;
} }
/** /**
* *
* *
* *
* *
* *
* @param callback * @param callback
*/ */
public forEach(callback: (item: T, index: number) => void): void { public forEach(callback: (item: T, index: number) => void): void {
@@ -143,10 +143,10 @@ export class SparseSet<T> {
callback(this._dense[i]!, i); callback(this._dense[i]!, i);
} }
} }
/** /**
* *
* *
* @param callback * @param callback
* @returns * @returns
*/ */
@@ -157,10 +157,10 @@ export class SparseSet<T> {
} }
return result; return result;
} }
/** /**
* *
* *
* @param predicate * @param predicate
* @returns * @returns
*/ */
@@ -173,10 +173,10 @@ export class SparseSet<T> {
} }
return result; return result;
} }
/** /**
* *
* *
* @param predicate * @param predicate
* @returns undefined * @returns undefined
*/ */
@@ -188,10 +188,10 @@ export class SparseSet<T> {
} }
return undefined; return undefined;
} }
/** /**
* *
* *
* @param predicate * @param predicate
* @returns * @returns
*/ */
@@ -203,10 +203,10 @@ export class SparseSet<T> {
} }
return false; return false;
} }
/** /**
* *
* *
* @param predicate * @param predicate
* @returns * @returns
*/ */
@@ -218,26 +218,26 @@ export class SparseSet<T> {
} }
return true; return true;
} }
/** /**
* *
* *
* *
*/ */
public getDenseArray(): readonly T[] { public getDenseArray(): readonly T[] {
return [...this._dense]; return [...this._dense];
} }
/** /**
* 使 * 使
* *
* *
* 使 * 使
*/ */
public getDenseArrayUnsafe(): readonly T[] { public getDenseArrayUnsafe(): readonly T[] {
return this._dense; return this._dense;
} }
/** /**
* *
*/ */
@@ -245,21 +245,21 @@ export class SparseSet<T> {
this._dense.length = 0; this._dense.length = 0;
this._sparse.clear(); this._sparse.clear();
} }
/** /**
* *
*/ */
public toArray(): T[] { public toArray(): T[] {
return [...this._dense]; return [...this._dense];
} }
/** /**
* Set * Set
*/ */
public toSet(): Set<T> { public toSet(): Set<T> {
return new Set(this._dense); return new Set(this._dense);
} }
/** /**
* 使 * 使
*/ */
@@ -267,20 +267,20 @@ export class SparseSet<T> {
denseArraySize: number; denseArraySize: number;
sparseMapSize: number; sparseMapSize: number;
totalMemory: number; totalMemory: number;
} { } {
const denseArraySize = this._dense.length * 8; // 估计每个引用8字节 const denseArraySize = this._dense.length * 8; // 估计每个引用8字节
const sparseMapSize = this._sparse.size * 16; // 估计每个Map条目16字节 const sparseMapSize = this._sparse.size * 16; // 估计每个Map条目16字节
return { return {
denseArraySize, denseArraySize,
sparseMapSize, sparseMapSize,
totalMemory: denseArraySize + sparseMapSize totalMemory: denseArraySize + sparseMapSize
}; };
} }
/** /**
* *
* *
* *
*/ */
public validate(): boolean { public validate(): boolean {
@@ -288,7 +288,7 @@ export class SparseSet<T> {
if (this._dense.length !== this._sparse.size) { if (this._dense.length !== this._sparse.size) {
return false; return false;
} }
// 检查映射关系的正确性 // 检查映射关系的正确性
for (let i = 0; i < this._dense.length; i++) { for (let i = 0; i < this._dense.length; i++) {
const item = this._dense[i]!; const item = this._dense[i]!;
@@ -297,14 +297,14 @@ export class SparseSet<T> {
return false; return false;
} }
} }
// 检查稀疏映射中的所有项都在密集数组中 // 检查稀疏映射中的所有项都在密集数组中
for (const [item, index] of this._sparse) { for (const [item, index] of this._sparse) {
if (index >= this._dense.length || this._dense[index] !== item) { if (index >= this._dense.length || this._dense[index] !== item) {
return false; return false;
} }
} }
return true; return true;
} }
} }
+1 -1
View File
@@ -6,4 +6,4 @@ export { Matcher } from './Matcher';
export { Bits } from './Bits'; export { Bits } from './Bits';
export { BitMask64Utils, BitMask64Data } from './BigIntCompatibility'; export { BitMask64Utils, BitMask64Data } from './BigIntCompatibility';
export { SparseSet } from './SparseSet'; export { SparseSet } from './SparseSet';
export { ComponentSparseSet } from './ComponentSparseSet'; export { ComponentSparseSet } from './ComponentSparseSet';
+27 -27
View File
@@ -13,22 +13,22 @@ export interface IGlobalSystem {
* *
*/ */
readonly name: string; readonly name: string;
/** /**
* *
*/ */
initialize?(): void; initialize?(): void;
/** /**
* *
*/ */
update(deltaTime?: number): void; update(deltaTime?: number): void;
/** /**
* *
*/ */
reset?(): void; reset?(): void;
/** /**
* *
*/ */
@@ -43,17 +43,17 @@ export interface IWorldConfig {
* World名称 * World名称
*/ */
name?: string; name?: string;
/** /**
* *
*/ */
debug?: boolean; debug?: boolean;
/** /**
* Scene数量限制 * Scene数量限制
*/ */
maxScenes?: number; maxScenes?: number;
/** /**
* Scene * Scene
*/ */
@@ -62,22 +62,22 @@ export interface IWorldConfig {
/** /**
* World类 - ECS世界管理器 * World类 - ECS世界管理器
* *
* World是Scene的容器World可以管理多个Scene * World是Scene的容器World可以管理多个Scene
* *
* - World * - World
* - * -
* - * -
* *
* @example * @example
* ```typescript * ```typescript
* // 创建游戏房间的World * // 创建游戏房间的World
* const roomWorld = new World({ name: 'Room_001' }); * const roomWorld = new World({ name: 'Room_001' });
* *
* // 在World中创建Scene * // 在World中创建Scene
* const gameScene = roomWorld.createScene('game', new Scene()); * const gameScene = roomWorld.createScene('game', new Scene());
* const uiScene = roomWorld.createScene('ui', new Scene()); * const uiScene = roomWorld.createScene('ui', new Scene());
* *
* // 更新整个World * // 更新整个World
* roomWorld.update(deltaTime); * roomWorld.update(deltaTime);
* ``` * ```
@@ -99,7 +99,7 @@ export class World {
autoCleanup: true, autoCleanup: true,
...config ...config
}; };
this.name = this._config.name!; this.name = this._config.name!;
this._createdAt = Date.now(); this._createdAt = Date.now();
} }
@@ -120,7 +120,7 @@ export class World {
// 如果没有提供Scene实例,创建默认Scene // 如果没有提供Scene实例,创建默认Scene
const scene = sceneInstance || (new Scene() as unknown as T); const scene = sceneInstance || (new Scene() as unknown as T);
// 设置Scene的标识 // 设置Scene的标识
if ('id' in scene) { if ('id' in scene) {
(scene as any).id = sceneId; (scene as any).id = sceneId;
@@ -154,7 +154,7 @@ export class World {
// 清理Scene资源 // 清理Scene资源
scene.end(); scene.end();
this._scenes.delete(sceneId); this._scenes.delete(sceneId);
logger.info(`从World '${this.name}' 中移除Scene: ${sceneId}`); logger.info(`从World '${this.name}' 中移除Scene: ${sceneId}`);
return true; return true;
} }
@@ -244,7 +244,7 @@ export class World {
if (system.initialize) { if (system.initialize) {
system.initialize(); system.initialize();
} }
logger.debug(`在World '${this.name}' 中添加全局System: ${system.name}`); logger.debug(`在World '${this.name}' 中添加全局System: ${system.name}`);
return system; return system;
} }
@@ -262,7 +262,7 @@ export class World {
if (system.reset) { if (system.reset) {
system.reset(); system.reset();
} }
logger.debug(`从World '${this.name}' 中移除全局System: ${system.name}`); logger.debug(`从World '${this.name}' 中移除全局System: ${system.name}`);
return true; return true;
} }
@@ -290,14 +290,14 @@ export class World {
} }
this._isActive = true; this._isActive = true;
// 启动所有全局System // 启动所有全局System
for (const system of this._globalSystems) { for (const system of this._globalSystems) {
if (system.initialize) { if (system.initialize) {
system.initialize(); system.initialize();
} }
} }
logger.info(`启动World: ${this.name}`); logger.info(`启动World: ${this.name}`);
} }
@@ -408,7 +408,7 @@ export class World {
globalSystemCount: this._globalSystems.length, globalSystemCount: this._globalSystems.length,
createdAt: this._createdAt, createdAt: this._createdAt,
config: { ...this._config }, config: { ...this._config },
scenes: Array.from(this._scenes.keys()).map(sceneId => ({ scenes: Array.from(this._scenes.keys()).map((sceneId) => ({
id: sceneId, id: sceneId,
isActive: this._activeScenes.has(sceneId), isActive: this._activeScenes.has(sceneId),
name: this._scenes.get(sceneId)?.name || sceneId name: this._scenes.get(sceneId)?.name || sceneId
@@ -454,8 +454,8 @@ export class World {
const cleanupThreshold = 5 * 60 * 1000; // 5分钟 const cleanupThreshold = 5 * 60 * 1000; // 5分钟
for (const [sceneId, scene] of this._scenes) { for (const [sceneId, scene] of this._scenes) {
if (!this._activeScenes.has(sceneId) && if (!this._activeScenes.has(sceneId) &&
scene.entities && scene.entities &&
scene.entities.count === 0 && scene.entities.count === 0 &&
(currentTime - this._createdAt) > cleanupThreshold) { (currentTime - this._createdAt) > cleanupThreshold) {
return true; return true;
@@ -472,15 +472,15 @@ export class World {
const sceneIds = Array.from(this._scenes.keys()); const sceneIds = Array.from(this._scenes.keys());
const currentTime = Date.now(); const currentTime = Date.now();
const cleanupThreshold = 5 * 60 * 1000; // 5分钟 const cleanupThreshold = 5 * 60 * 1000; // 5分钟
for (const sceneId of sceneIds) { for (const sceneId of sceneIds) {
const scene = this._scenes.get(sceneId); const scene = this._scenes.get(sceneId);
if (scene && if (scene &&
!this._activeScenes.has(sceneId) && !this._activeScenes.has(sceneId) &&
scene.entities && scene.entities &&
scene.entities.count === 0 && scene.entities.count === 0 &&
(currentTime - this._createdAt) > cleanupThreshold) { (currentTime - this._createdAt) > cleanupThreshold) {
this.removeScene(sceneId); this.removeScene(sceneId);
logger.debug(`自动清理空Scene: ${sceneId} from World ${this.name}`); logger.debug(`自动清理空Scene: ${sceneId} from World ${this.name}`);
} }
@@ -509,4 +509,4 @@ export class World {
public get createdAt(): number { public get createdAt(): number {
return this._createdAt; return this._createdAt;
} }
} }
+10 -10
View File
@@ -12,17 +12,17 @@ export interface IWorldManagerConfig {
* World数量 * World数量
*/ */
maxWorlds?: number; maxWorlds?: number;
/** /**
* World * World
*/ */
autoCleanup?: boolean; autoCleanup?: boolean;
/** /**
* *
*/ */
cleanupInterval?: number; cleanupInterval?: number;
/** /**
* *
*/ */
@@ -242,11 +242,11 @@ export class WorldManager implements IService {
*/ */
public startAll(): void { public startAll(): void {
this._isRunning = true; this._isRunning = true;
for (const worldId of this._worlds.keys()) { for (const worldId of this._worlds.keys()) {
this.setWorldActive(worldId, true); this.setWorldActive(worldId, true);
} }
logger.info('启动所有World'); logger.info('启动所有World');
} }
@@ -255,11 +255,11 @@ export class WorldManager implements IService {
*/ */
public stopAll(): void { public stopAll(): void {
this._isRunning = false; this._isRunning = false;
for (const worldId of this._activeWorlds) { for (const worldId of this._activeWorlds) {
this.setWorldActive(worldId, false); this.setWorldActive(worldId, false);
} }
logger.info('停止所有World'); logger.info('停止所有World');
} }
@@ -432,7 +432,7 @@ export class WorldManager implements IService {
// 1. World未激活 // 1. World未激活
// 2. 没有Scene或所有Scene都是空的 // 2. 没有Scene或所有Scene都是空的
// 3. 创建时间超过10分钟 // 3. 创建时间超过10分钟
if (world.isActive) { if (world.isActive) {
return false; return false;
} }
@@ -444,7 +444,7 @@ export class WorldManager implements IService {
// 检查是否所有Scene都是空的 // 检查是否所有Scene都是空的
const allScenes = world.getAllScenes(); const allScenes = world.getAllScenes();
const hasEntities = allScenes.some(scene => const hasEntities = allScenes.some((scene) =>
scene.entities && scene.entities.count > 0 scene.entities && scene.entities.count > 0
); );
@@ -485,4 +485,4 @@ export class WorldManager implements IService {
public get config(): IWorldManagerConfig { public get config(): IWorldManagerConfig {
return { ...this._config }; return { ...this._config };
} }
} }
+1 -1
View File
@@ -17,4 +17,4 @@ export * from './Serialization';
export { ReferenceTracker, getSceneByEntityId } from './Core/ReferenceTracker'; export { ReferenceTracker, getSceneByEntityId } from './Core/ReferenceTracker';
export type { EntityRefRecord } from './Core/ReferenceTracker'; export type { EntityRefRecord } from './Core/ReferenceTracker';
export { ReactiveQuery, ReactiveQueryChangeType } from './Core/ReactiveQuery'; export { ReactiveQuery, ReactiveQueryChangeType } from './Core/ReactiveQuery';
export type { ReactiveQueryChange, ReactiveQueryListener, ReactiveQueryConfig } from './Core/ReactiveQuery'; export type { ReactiveQueryChange, ReactiveQueryListener, ReactiveQueryConfig } from './Core/ReactiveQuery';
@@ -235,4 +235,4 @@ export class PlatformDetector {
return info; return info;
} }
} }
@@ -98,7 +98,7 @@ export class PlatformManager {
platformSupportsSharedArrayBuffer: boolean; platformSupportsSharedArrayBuffer: boolean;
platformMaxWorkerCount: number; platformMaxWorkerCount: number;
platformLimitations: any; platformLimitations: any;
} { } {
if (!this.adapter) { if (!this.adapter) {
return { return {
platformSupportsWorker: false, platformSupportsWorker: false,
@@ -134,4 +134,4 @@ export class PlatformManager {
// 否则返回同步配置 // 否则返回同步配置
return this.adapter.getPlatformConfig(); return this.adapter.getPlatformConfig();
} }
} }
+1 -1
View File
@@ -44,4 +44,4 @@ export function supportsFeature(feature: 'worker' | 'shared-array-buffer' | 'tra
export function hasAdapter() { export function hasAdapter() {
return PlatformManager.getInstance().hasAdapter(); return PlatformManager.getInstance().hasAdapter();
} }
+4 -4
View File
@@ -208,8 +208,8 @@ export class DebugPlugin implements IPlugin, IService {
return { return {
name: scene.name, name: scene.name,
entityCount: entities.length, entityCount: entities.length,
systems: systems.map(sys => this.getSystemInfo(sys)), systems: systems.map((sys) => this.getSystemInfo(sys)),
entities: entities.map(entity => this.getEntityInfo(entity)) entities: entities.map((entity) => this.getEntityInfo(entity))
}; };
} }
@@ -246,7 +246,7 @@ export class DebugPlugin implements IPlugin, IService {
enabled: entity.enabled, enabled: entity.enabled,
tag: entity.tag, tag: entity.tag,
componentCount: components.length, componentCount: components.length,
components: components.map(comp => this.getComponentInfo(comp)) components: components.map((comp) => this.getComponentInfo(comp))
}; };
} }
@@ -304,7 +304,7 @@ export class DebugPlugin implements IPlugin, IService {
if (filter.hasComponent) { if (filter.hasComponent) {
const hasComp = entity.components.some( const hasComp = entity.components.some(
c => c.constructor.name === filter.hasComponent (c) => c.constructor.name === filter.hasComponent
); );
if (!hasComp) { if (!hasComp) {
continue; continue;
+15 -15
View File
@@ -28,7 +28,7 @@ export interface IComponent {
/** /**
* *
* *
* EntitySystem类提供类型定义 * EntitySystem类提供类型定义
*/ */
export interface ISystemBase { export interface ISystemBase {
@@ -38,7 +38,7 @@ export interface ISystemBase {
updateOrder: number; updateOrder: number;
/** 系统启用状态 */ /** 系统启用状态 */
enabled: boolean; enabled: boolean;
/** 系统初始化 */ /** 系统初始化 */
initialize(): void; initialize(): void;
/** 更新系统(主要处理阶段) */ /** 更新系统(主要处理阶段) */
@@ -49,7 +49,7 @@ export interface ISystemBase {
/** /**
* *
* *
* *
* *
*/ */
@@ -66,14 +66,14 @@ export interface IEventBus {
* @param data * @param data
*/ */
emit<T>(eventType: string, data: T): void; emit<T>(eventType: string, data: T): void;
/** /**
* *
* @param eventType * @param eventType
* @param data * @param data
*/ */
emitAsync<T>(eventType: string, data: T): Promise<void>; emitAsync<T>(eventType: string, data: T): Promise<void>;
/** /**
* *
* @param eventType * @param eventType
@@ -82,7 +82,7 @@ export interface IEventBus {
* @returns ID * @returns ID
*/ */
on<T>(eventType: string, handler: (data: T) => void, config?: IEventListenerConfig): string; on<T>(eventType: string, handler: (data: T) => void, config?: IEventListenerConfig): string;
/** /**
* *
* @param eventType * @param eventType
@@ -91,7 +91,7 @@ export interface IEventBus {
* @returns ID * @returns ID
*/ */
once<T>(eventType: string, handler: (data: T) => void, config?: IEventListenerConfig): string; once<T>(eventType: string, handler: (data: T) => void, config?: IEventListenerConfig): string;
/** /**
* *
* @param eventType * @param eventType
@@ -100,32 +100,32 @@ export interface IEventBus {
* @returns ID * @returns ID
*/ */
onAsync<T>(eventType: string, handler: (data: T) => Promise<void>, config?: IEventListenerConfig): string; onAsync<T>(eventType: string, handler: (data: T) => Promise<void>, config?: IEventListenerConfig): string;
/** /**
* *
* @param eventType * @param eventType
* @param listenerId ID * @param listenerId ID
*/ */
off(eventType: string, listenerId: string): boolean; off(eventType: string, listenerId: string): boolean;
/** /**
* *
* @param eventType * @param eventType
*/ */
offAll(eventType: string): void; offAll(eventType: string): void;
/** /**
* *
* @param eventType * @param eventType
*/ */
hasListeners(eventType: string): boolean; hasListeners(eventType: string): boolean;
/** /**
* *
* @param eventType * @param eventType
*/ */
getStats(eventType?: string): IEventStats | Map<string, IEventStats>; getStats(eventType?: string): IEventStats | Map<string, IEventStats>;
/** /**
* *
*/ */
@@ -142,8 +142,8 @@ export interface IEventListenerConfig {
priority?: number; priority?: number;
/** 是否异步执行 */ /** 是否异步执行 */
async?: boolean; async?: boolean;
/** 执行上下文 */ /** 事件处理函数的 this 绑定对象 */
context?: unknown; thisArg?: object;
} }
/** /**
@@ -499,4 +499,4 @@ export interface ISceneDebugData {
sceneMemory: number; sceneMemory: number;
/** 场景启动时间 */ /** 场景启动时间 */
sceneUptime: number; sceneUptime: number;
} }
@@ -47,18 +47,18 @@ export class ComponentDataCollector {
}); });
// 获取池利用率信息 // 获取池利用率信息
let poolUtilizations = new Map<string, number>(); const poolUtilizations = new Map<string, number>();
let poolSizes = new Map<string, number>(); const poolSizes = new Map<string, number>();
try { try {
const poolManager = ComponentPoolManager.getInstance(); const poolManager = ComponentPoolManager.getInstance();
const poolStats = poolManager.getPoolStats(); const poolStats = poolManager.getPoolStats();
const utilizations = poolManager.getPoolUtilization(); const utilizations = poolManager.getPoolUtilization();
for (const [typeName, stats] of poolStats.entries()) { for (const [typeName, stats] of poolStats.entries()) {
poolSizes.set(typeName, stats.maxSize); poolSizes.set(typeName, stats.maxSize);
} }
for (const [typeName, util] of utilizations.entries()) { for (const [typeName, util] of utilizations.entries()) {
poolUtilizations.set(typeName, util.utilization); poolUtilizations.set(typeName, util.utilization);
} }
@@ -74,7 +74,7 @@ export class ComponentDataCollector {
const poolUtilization = poolUtilizations.get(typeName) || 0; const poolUtilization = poolUtilizations.get(typeName) || 0;
// 使用预估的基础内存大小,避免每帧计算 // 使用预估的基础内存大小,避免每帧计算
const memoryPerInstance = this.getEstimatedComponentSize(typeName, scene); const memoryPerInstance = this.getEstimatedComponentSize(typeName, scene);
return { return {
typeName, typeName,
instanceCount: stats.count, instanceCount: stats.count,
@@ -97,12 +97,12 @@ export class ComponentDataCollector {
} }
if (!scene) return 64; if (!scene) return 64;
const entityList = (scene as any).entities; const entityList = (scene as any).entities;
if (!entityList?.buffer) return 64; if (!entityList?.buffer) return 64;
let calculatedSize = 64; let calculatedSize = 64;
try { try {
for (const entity of entityList.buffer) { for (const entity of entityList.buffer) {
if (entity.components) { if (entity.components) {
@@ -116,25 +116,25 @@ export class ComponentDataCollector {
} catch (error) { } catch (error) {
calculatedSize = 64; calculatedSize = 64;
} }
ComponentDataCollector.componentSizeCache.set(typeName, calculatedSize); ComponentDataCollector.componentSizeCache.set(typeName, calculatedSize);
return calculatedSize; return calculatedSize;
} }
private calculateQuickObjectSize(obj: any): number { private calculateQuickObjectSize(obj: any): number {
if (!obj || typeof obj !== 'object') return 8; if (!obj || typeof obj !== 'object') return 8;
let size = 32; let size = 32;
const visited = new WeakSet(); const visited = new WeakSet();
const calculate = (item: any, depth: number = 0): number => { const calculate = (item: any, depth: number = 0): number => {
if (!item || typeof item !== 'object' || visited.has(item) || depth > 3) { if (!item || typeof item !== 'object' || visited.has(item) || depth > 3) {
return 0; return 0;
} }
visited.add(item); visited.add(item);
let itemSize = 0; let itemSize = 0;
try { try {
const keys = Object.keys(item); const keys = Object.keys(item);
for (let i = 0; i < Math.min(keys.length, 20); i++) { for (let i = 0; i < Math.min(keys.length, 20); i++) {
@@ -143,7 +143,7 @@ export class ComponentDataCollector {
const value = item[key]; const value = item[key];
itemSize += key.length * 2; itemSize += key.length * 2;
if (typeof value === 'string') { if (typeof value === 'string') {
itemSize += Math.min(value.length * 2, 200); itemSize += Math.min(value.length * 2, 200);
} else if (typeof value === 'number') { } else if (typeof value === 'number') {
@@ -157,10 +157,10 @@ export class ComponentDataCollector {
} catch (error) { } catch (error) {
return 32; return 32;
} }
return itemSize; return itemSize;
}; };
size += calculate(obj); size += calculate(obj);
return Math.max(size, 32); return Math.max(size, 32);
} }
@@ -176,7 +176,7 @@ export class ComponentDataCollector {
const entityList = (scene as any).entities; const entityList = (scene as any).entities;
if (!entityList?.buffer) return this.getEstimatedComponentSize(typeName, scene); if (!entityList?.buffer) return this.getEstimatedComponentSize(typeName, scene);
try { try {
// 找到第一个包含此组件的实体,分析组件大小 // 找到第一个包含此组件的实体,分析组件大小
for (const entity of entityList.buffer) { for (const entity of entityList.buffer) {
@@ -190,7 +190,7 @@ export class ComponentDataCollector {
} catch (error) { } catch (error) {
// 忽略错误,使用估算值 // 忽略错误,使用估算值
} }
return this.getEstimatedComponentSize(typeName, scene); return this.getEstimatedComponentSize(typeName, scene);
} }
@@ -201,10 +201,10 @@ export class ComponentDataCollector {
private estimateObjectSize(obj: any, visited = new WeakSet(), depth = 0): number { private estimateObjectSize(obj: any, visited = new WeakSet(), depth = 0): number {
if (obj === null || obj === undefined || depth > 10) return 0; if (obj === null || obj === undefined || depth > 10) return 0;
if (visited.has(obj)) return 0; if (visited.has(obj)) return 0;
let size = 0; let size = 0;
const type = typeof obj; const type = typeof obj;
switch (type) { switch (type) {
case 'boolean': case 'boolean':
size = 4; size = 4;
@@ -217,7 +217,7 @@ export class ComponentDataCollector {
break; break;
case 'object': case 'object':
visited.add(obj); visited.add(obj);
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
size = 40 + (obj.length * 8); size = 40 + (obj.length * 8);
const maxElements = Math.min(obj.length, 50); const maxElements = Math.min(obj.length, 50);
@@ -226,11 +226,11 @@ export class ComponentDataCollector {
} }
} else { } else {
size = 32; size = 32;
try { try {
const ownKeys = Object.getOwnPropertyNames(obj); const ownKeys = Object.getOwnPropertyNames(obj);
const maxProps = Math.min(ownKeys.length, 30); const maxProps = Math.min(ownKeys.length, 30);
for (let i = 0; i < maxProps; i++) { for (let i = 0; i < maxProps; i++) {
const key = ownKeys[i]; const key = ownKeys[i];
if (!key) continue; if (!key) continue;
@@ -263,11 +263,11 @@ export class ComponentDataCollector {
default: default:
size = 8; size = 8;
} }
return Math.ceil(size / 8) * 8; return Math.ceil(size / 8) * 8;
} }
public static clearCache(): void { public static clearCache(): void {
ComponentDataCollector.componentSizeCache.clear(); ComponentDataCollector.componentSizeCache.clear();
} }
} }
+7 -11
View File
@@ -135,7 +135,7 @@ export class DebugManager implements IService, IUpdatable {
* *
*/ */
private formatLogMessage(args: unknown[]): string { private formatLogMessage(args: unknown[]): string {
return args.map(arg => { return args.map((arg) => {
if (typeof arg === 'string') return arg; if (typeof arg === 'string') return arg;
if (arg instanceof Error) return `${arg.name}: ${arg.message}`; if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
if (arg === null) return 'null'; if (arg === null) return 'null';
@@ -173,7 +173,7 @@ export class DebugManager implements IService, IUpdatable {
seen.add(value); seen.add(value);
if (Array.isArray(value)) { if (Array.isArray(value)) {
const result = value.map(item => stringify(item, depth + 1)); const result = value.map((item) => stringify(item, depth + 1));
seen.delete(value); seen.delete(value);
return result; return result;
} }
@@ -436,9 +436,6 @@ export class DebugManager implements IService, IUpdatable {
} }
/** /**
* *
*/ */
@@ -519,7 +516,7 @@ export class DebugManager implements IService, IUpdatable {
jsHeapSizeLimit: number; jsHeapSizeLimit: number;
} | null; } | null;
detailedMemory?: unknown; detailedMemory?: unknown;
} { } {
const memoryInfo = { const memoryInfo = {
totalMemory: 0, totalMemory: 0,
usedMemory: 0, usedMemory: 0,
@@ -575,7 +572,6 @@ export class DebugManager implements IService, IUpdatable {
} }
/** /**
* *
*/ */
@@ -672,7 +668,7 @@ export class DebugManager implements IService, IUpdatable {
enabled: boolean; enabled: boolean;
updateOrder: number; updateOrder: number;
}>; }>;
} { } {
const scene = this.sceneManager.currentScene; const scene = this.sceneManager.currentScene;
let totalSystemMemory = 0; let totalSystemMemory = 0;
const systemBreakdown: Array<{ const systemBreakdown: Array<{
@@ -766,7 +762,7 @@ export class DebugManager implements IService, IUpdatable {
utilization: number; utilization: number;
hitRate?: number; hitRate?: number;
}>; }>;
} { } {
let totalPoolMemory = 0; let totalPoolMemory = 0;
const poolBreakdown: Array<{ const poolBreakdown: Array<{
typeName: string; typeName: string;
@@ -845,7 +841,7 @@ export class DebugManager implements IService, IUpdatable {
samples: number; samples: number;
}>; }>;
error?: string; error?: string;
} { } {
try { try {
if (!this.performanceMonitor) { if (!this.performanceMonitor) {
return { enabled: false }; return { enabled: false };
@@ -959,4 +955,4 @@ export class DebugManager implements IService, IUpdatable {
console.warn = this.originalConsole.warn; console.warn = this.originalConsole.warn;
console.error = this.originalConsole.error; console.error = this.originalConsole.error;
} }
} }
@@ -39,7 +39,7 @@ export class EntityDataCollector {
} }
const archetypeData = this.collectArchetypeData(scene); const archetypeData = this.collectArchetypeData(scene);
return { return {
totalEntities: stats.totalEntities, totalEntities: stats.totalEntities,
activeEntities: stats.activeEntities, activeEntities: stats.activeEntities,
@@ -52,7 +52,6 @@ export class EntityDataCollector {
}; };
} }
/** /**
* *
* @param scene * @param scene
@@ -92,7 +91,6 @@ export class EntityDataCollector {
})); }));
} }
/** /**
* *
* @param entityId ID * @param entityId ID
@@ -108,9 +106,9 @@ export class EntityDataCollector {
const entity = entityList.buffer.find((e: any) => e.id === entityId); const entity = entityList.buffer.find((e: any) => e.id === entityId);
if (!entity) return null; if (!entity) return null;
const baseDebugInfo = entity.getDebugInfo ? const baseDebugInfo = entity.getDebugInfo
entity.getDebugInfo() : ? entity.getDebugInfo()
this.buildFallbackEntityInfo(entity, scene); : this.buildFallbackEntityInfo(entity, scene);
const componentDetails = this.extractComponentDetails(entity.components); const componentDetails = this.extractComponentDetails(entity.components);
@@ -140,7 +138,7 @@ export class EntityDataCollector {
private getSceneInfo(scene: any): { name: string; type: string } { private getSceneInfo(scene: any): { name: string; type: string } {
let sceneName = '当前场景'; let sceneName = '当前场景';
let sceneType = 'Scene'; let sceneType = 'Scene';
try { try {
if (scene.name && typeof scene.name === 'string' && scene.name.trim()) { if (scene.name && typeof scene.name === 'string' && scene.name.trim()) {
sceneName = scene.name.trim(); sceneName = scene.name.trim();
@@ -159,11 +157,10 @@ export class EntityDataCollector {
} catch (error) { } catch (error) {
sceneName = '场景名获取失败'; sceneName = '场景名获取失败';
} }
return { name: sceneName, type: sceneType }; return { name: sceneName, type: sceneType };
} }
/** /**
* *
* @param scene * @param scene
@@ -182,20 +179,20 @@ export class EntityDataCollector {
try { try {
stats = entityList.getStats ? entityList.getStats() : this.calculateFallbackEntityStats(entityList); stats = entityList.getStats ? entityList.getStats() : this.calculateFallbackEntityStats(entityList);
} catch (error) { } catch (error) {
return { return {
totalEntities: 0, totalEntities: 0,
activeEntities: 0, activeEntities: 0,
pendingAdd: 0, pendingAdd: 0,
pendingRemove: 0, pendingRemove: 0,
entitiesPerArchetype: [], entitiesPerArchetype: [],
topEntitiesByComponents: [], topEntitiesByComponents: [],
entityHierarchy: [], entityHierarchy: [],
entityDetailsMap: {} entityDetailsMap: {}
}; };
} }
const archetypeData = this.collectArchetypeDataWithMemory(scene); const archetypeData = this.collectArchetypeDataWithMemory(scene);
return { return {
totalEntities: stats.totalEntities, totalEntities: stats.totalEntities,
activeEntities: stats.activeEntities, activeEntities: stats.activeEntities,
@@ -208,7 +205,6 @@ export class EntityDataCollector {
}; };
} }
private collectArchetypeData(scene: any): { private collectArchetypeData(scene: any): {
distribution: Array<{ signature: string; count: number; memory: number }>; distribution: Array<{ signature: string; count: number; memory: number }>;
topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>; topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>;
@@ -224,7 +220,9 @@ export class EntityDataCollector {
}; };
} }
private getArchetypeDistributionFast(entityContainer: any): Array<{ signature: string; count: number; memory: number }> { private getArchetypeDistributionFast(
entityContainer: any
): Array<{ signature: string; count: number; memory: number }> {
const distribution = new Map<string, { count: number; componentTypes: string[] }>(); const distribution = new Map<string, { count: number; componentTypes: string[] }>();
if (entityContainer && entityContainer.entities) { if (entityContainer && entityContainer.entities) {
@@ -251,7 +249,9 @@ export class EntityDataCollector {
.slice(0, 20); .slice(0, 20);
} }
private getTopEntitiesByComponentsFast(entityContainer: any): Array<{ id: string; name: string; componentCount: number; memory: number }> { private getTopEntitiesByComponentsFast(
entityContainer: any
): Array<{ id: string; name: string; componentCount: number; memory: number }> {
if (!entityContainer || !entityContainer.entities) { if (!entityContainer || !entityContainer.entities) {
return []; return [];
} }
@@ -266,7 +266,6 @@ export class EntityDataCollector {
.sort((a: any, b: any) => b.componentCount - a.componentCount); .sort((a: any, b: any) => b.componentCount - a.componentCount);
} }
private collectArchetypeDataWithMemory(scene: any): { private collectArchetypeDataWithMemory(scene: any): {
distribution: Array<{ signature: string; count: number; memory: number }>; distribution: Array<{ signature: string; count: number; memory: number }>;
topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>; topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>;
@@ -282,7 +281,6 @@ export class EntityDataCollector {
}; };
} }
private extractArchetypeStatistics(archetypeSystem: any): { private extractArchetypeStatistics(archetypeSystem: any): {
distribution: Array<{ signature: string; count: number; memory: number }>; distribution: Array<{ signature: string; count: number; memory: number }>;
topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>; topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>;
@@ -294,7 +292,7 @@ export class EntityDataCollector {
archetypes.forEach((archetype: any) => { archetypes.forEach((archetype: any) => {
const signature = archetype.componentTypes?.map((type: any) => type.name).join(',') || 'Unknown'; const signature = archetype.componentTypes?.map((type: any) => type.name).join(',') || 'Unknown';
const entityCount = archetype.entities?.length || 0; const entityCount = archetype.entities?.length || 0;
distribution.push({ distribution.push({
signature, signature,
count: entityCount, count: entityCount,
@@ -319,7 +317,6 @@ export class EntityDataCollector {
return { distribution, topEntities }; return { distribution, topEntities };
} }
private extractArchetypeStatisticsWithMemory(archetypeSystem: any): { private extractArchetypeStatisticsWithMemory(archetypeSystem: any): {
distribution: Array<{ signature: string; count: number; memory: number }>; distribution: Array<{ signature: string; count: number; memory: number }>;
topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>; topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>;
@@ -336,11 +333,11 @@ export class EntityDataCollector {
if (archetype.entities && archetype.entities.length > 0) { if (archetype.entities && archetype.entities.length > 0) {
const sampleSize = Math.min(5, archetype.entities.length); const sampleSize = Math.min(5, archetype.entities.length);
let sampleMemory = 0; let sampleMemory = 0;
for (let i = 0; i < sampleSize; i++) { for (let i = 0; i < sampleSize; i++) {
sampleMemory += this.estimateEntityMemoryUsage(archetype.entities[i]); sampleMemory += this.estimateEntityMemoryUsage(archetype.entities[i]);
} }
actualMemory = (sampleMemory / sampleSize) * entityCount; actualMemory = (sampleMemory / sampleSize) * entityCount;
} }
@@ -368,9 +365,9 @@ export class EntityDataCollector {
return { distribution, topEntities }; return { distribution, topEntities };
} }
private getArchetypeDistributionWithMemory(
entityContainer: any
private getArchetypeDistributionWithMemory(entityContainer: any): Array<{ signature: string; count: number; memory: number }> { ): Array<{ signature: string; count: number; memory: number }> {
const distribution = new Map<string, { count: number; memory: number; componentTypes: string[] }>(); const distribution = new Map<string, { count: number; memory: number; componentTypes: string[] }>();
if (entityContainer && entityContainer.entities) { if (entityContainer && entityContainer.entities) {
@@ -403,8 +400,9 @@ export class EntityDataCollector {
.sort((a, b) => b.count - a.count); .sort((a, b) => b.count - a.count);
} }
private getTopEntitiesByComponentsWithMemory(
private getTopEntitiesByComponentsWithMemory(entityContainer: any): Array<{ id: string; name: string; componentCount: number; memory: number }> { entityContainer: any
): Array<{ id: string; name: string; componentCount: number; memory: number }> {
if (!entityContainer || !entityContainer.entities) { if (!entityContainer || !entityContainer.entities) {
return []; return [];
} }
@@ -419,7 +417,6 @@ export class EntityDataCollector {
.sort((a: any, b: any) => b.componentCount - a.componentCount); .sort((a: any, b: any) => b.componentCount - a.componentCount);
} }
private getEmptyEntityDebugData(): IEntityDebugData { private getEmptyEntityDebugData(): IEntityDebugData {
return { return {
totalEntities: 0, totalEntities: 0,
@@ -433,20 +430,20 @@ export class EntityDataCollector {
}; };
} }
private calculateFallbackEntityStats(entityList: any): any { private calculateFallbackEntityStats(entityList: any): any {
const allEntities = entityList.buffer || []; const allEntities = entityList.buffer || [];
const activeEntities = allEntities.filter((entity: any) => const activeEntities = allEntities.filter((entity: any) => entity.enabled && !entity.isDestroyed);
entity.enabled && !entity._isDestroyed
);
return { return {
totalEntities: allEntities.length, totalEntities: allEntities.length,
activeEntities: activeEntities.length, activeEntities: activeEntities.length,
pendingAdd: 0, pendingAdd: 0,
pendingRemove: 0, pendingRemove: 0,
averageComponentsPerEntity: activeEntities.length > 0 ? averageComponentsPerEntity:
allEntities.reduce((sum: number, e: any) => sum + (e.components?.length || 0), 0) / activeEntities.length : 0 activeEntities.length > 0
? allEntities.reduce((sum: number, e: any) => sum + (e.components?.length || 0), 0) /
activeEntities.length
: 0
}; };
} }
@@ -476,37 +473,40 @@ export class EntityDataCollector {
public calculateObjectSize(obj: any, excludeKeys: string[] = []): number { public calculateObjectSize(obj: any, excludeKeys: string[] = []): number {
if (!obj || typeof obj !== 'object') return 0; if (!obj || typeof obj !== 'object') return 0;
const visited = new WeakSet(); const visited = new WeakSet();
const maxDepth = 2; const maxDepth = 2;
const calculate = (item: any, depth: number = 0): number => { const calculate = (item: any, depth: number = 0): number => {
if (!item || typeof item !== 'object' || depth >= maxDepth) { if (!item || typeof item !== 'object' || depth >= maxDepth) {
return 0; return 0;
} }
if (visited.has(item)) return 0; if (visited.has(item)) return 0;
visited.add(item); visited.add(item);
let itemSize = 32; let itemSize = 32;
try { try {
const keys = Object.keys(item); const keys = Object.keys(item);
const maxKeys = Math.min(keys.length, 20); const maxKeys = Math.min(keys.length, 20);
for (let i = 0; i < maxKeys; i++) { for (let i = 0; i < maxKeys; i++) {
const key = keys[i]; const key = keys[i];
if (!key || excludeKeys.includes(key) || if (
!key ||
excludeKeys.includes(key) ||
key === 'constructor' || key === 'constructor' ||
key === '__proto__' || key === '__proto__' ||
key.startsWith('_cc_') || key.startsWith('_cc_') ||
key.startsWith('__')) { key.startsWith('__')
) {
continue; continue;
} }
const value = item[key]; const value = item[key];
itemSize += key.length * 2; itemSize += key.length * 2;
if (typeof value === 'string') { if (typeof value === 'string') {
itemSize += Math.min(value.length * 2, 200); itemSize += Math.min(value.length * 2, 200);
} else if (typeof value === 'number') { } else if (typeof value === 'number') {
@@ -522,10 +522,10 @@ export class EntityDataCollector {
} catch (error) { } catch (error) {
return 64; return 64;
} }
return itemSize; return itemSize;
}; };
try { try {
const size = calculate(obj); const size = calculate(obj);
return Math.max(size, 32); return Math.max(size, 32);
@@ -534,7 +534,6 @@ export class EntityDataCollector {
} }
} }
private buildEntityHierarchyTree(entityList: { buffer?: Entity[] }): Array<{ private buildEntityHierarchyTree(entityList: { buffer?: Entity[] }): Array<{
id: number; id: number;
name: string; name: string;
@@ -553,7 +552,6 @@ export class EntityDataCollector {
const rootEntities: any[] = []; const rootEntities: any[] = [];
entityList.buffer.forEach((entity: Entity) => { entityList.buffer.forEach((entity: Entity) => {
if (!entity.parent) { if (!entity.parent) {
const hierarchyNode = this.buildEntityHierarchyNode(entity); const hierarchyNode = this.buildEntityHierarchyNode(entity);
@@ -626,12 +624,13 @@ export class EntityDataCollector {
const batch = entities.slice(i, i + batchSize); const batch = entities.slice(i, i + batchSize);
batch.forEach((entity: Entity) => { batch.forEach((entity: Entity) => {
const baseDebugInfo = entity.getDebugInfo ? const baseDebugInfo = entity.getDebugInfo
entity.getDebugInfo() : ? entity.getDebugInfo()
this.buildFallbackEntityInfo(entity, scene); : this.buildFallbackEntityInfo(entity, scene);
const componentCacheStats = (entity as any).getComponentCacheStats ? const componentCacheStats = (entity as any).getComponentCacheStats
(entity as any).getComponentCacheStats() : null; ? (entity as any).getComponentCacheStats()
: null;
const componentDetails = this.extractComponentDetails(entity.components); const componentDetails = this.extractComponentDetails(entity.components);
@@ -639,13 +638,14 @@ export class EntityDataCollector {
...baseDebugInfo, ...baseDebugInfo,
parentName: entity.parent?.name || null, parentName: entity.parent?.name || null,
components: componentDetails, components: componentDetails,
componentTypes: baseDebugInfo.componentTypes || componentTypes: baseDebugInfo.componentTypes || componentDetails.map((comp) => comp.typeName),
componentDetails.map((comp) => comp.typeName), cachePerformance: componentCacheStats
cachePerformance: componentCacheStats ? { ? {
hitRate: componentCacheStats.cacheStats.hitRate, hitRate: componentCacheStats.cacheStats.hitRate,
size: componentCacheStats.cacheStats.size, size: componentCacheStats.cacheStats.size,
maxSize: componentCacheStats.cacheStats.maxSize maxSize: componentCacheStats.cacheStats.maxSize
} : null }
: null
}; };
}); });
} }
@@ -658,7 +658,7 @@ export class EntityDataCollector {
*/ */
private buildFallbackEntityInfo(entity: Entity, scene?: IScene | null): any { private buildFallbackEntityInfo(entity: Entity, scene?: IScene | null): any {
const sceneInfo = this.getSceneInfo(scene); const sceneInfo = this.getSceneInfo(scene);
return { return {
name: entity.name || `Entity_${entity.id}`, name: entity.name || `Entity_${entity.id}`,
id: entity.id, id: entity.id,
@@ -691,10 +691,10 @@ export class EntityDataCollector {
return components.map((component: Component) => { return components.map((component: Component) => {
const typeName = getComponentInstanceTypeName(component); const typeName = getComponentInstanceTypeName(component);
const properties: Record<string, any> = {}; const properties: Record<string, any> = {};
try { try {
const propertyKeys = Object.keys(component); const propertyKeys = Object.keys(component);
propertyKeys.forEach(propertyKey => { propertyKeys.forEach((propertyKey) => {
if (!propertyKey.startsWith('_') && propertyKey !== 'entity' && propertyKey !== 'constructor') { if (!propertyKey.startsWith('_') && propertyKey !== 'entity' && propertyKey !== 'constructor') {
const propertyValue = (component as any)[propertyKey]; const propertyValue = (component as any)[propertyKey];
if (propertyValue !== undefined && propertyValue !== null) { if (propertyValue !== undefined && propertyValue !== null) {
@@ -702,7 +702,7 @@ export class EntityDataCollector {
} }
} }
}); });
// 如果没有找到任何属性,添加一些调试信息 // 如果没有找到任何属性,添加一些调试信息
if (Object.keys(properties).length === 0) { if (Object.keys(properties).length === 0) {
properties['_info'] = '该组件没有公开属性'; properties['_info'] = '该组件没有公开属性';
@@ -712,7 +712,7 @@ export class EntityDataCollector {
properties['_error'] = '属性提取失败'; properties['_error'] = '属性提取失败';
properties['_componentId'] = getComponentInstanceTypeName(component); properties['_componentId'] = getComponentInstanceTypeName(component);
} }
return { return {
typeName: typeName, typeName: typeName,
properties: properties properties: properties
@@ -726,7 +726,11 @@ export class EntityDataCollector {
* @param componentIndex * @param componentIndex
* @param scene * @param scene
*/ */
public getComponentProperties(entityId: number, componentIndex: number, scene?: IScene | null): Record<string, any> { public getComponentProperties(
entityId: number,
componentIndex: number,
scene?: IScene | null
): Record<string, any> {
try { try {
if (!scene) return {}; if (!scene) return {};
@@ -739,20 +743,20 @@ export class EntityDataCollector {
const component = entity.components[componentIndex]; const component = entity.components[componentIndex];
const properties: Record<string, any> = {}; const properties: Record<string, any> = {};
const propertyKeys = Object.keys(component); const propertyKeys = Object.keys(component);
propertyKeys.forEach(propertyKey => { propertyKeys.forEach((propertyKey) => {
if (!propertyKey.startsWith('_') && propertyKey !== 'entity') { if (!propertyKey.startsWith('_') && propertyKey !== 'entity') {
const propertyValue = (component as any)[propertyKey]; const propertyValue = (component as any)[propertyKey];
if (propertyValue !== undefined && propertyValue !== null) { if (propertyValue !== undefined && propertyValue !== null) {
properties[propertyKey] = this.formatPropertyValue(propertyValue); properties[propertyKey] = this.formatPropertyValue(propertyValue);
} }
} }
}); });
return properties; return properties;
} catch (error) { } catch (error) {
return { _error: '属性提取失败' }; return { _error: '属性提取失败' };
} }
} }
/** /**
@@ -786,7 +790,7 @@ export class EntityDataCollector {
if (obj.length === 0) return []; if (obj.length === 0) return [];
if (obj.length > 10) { if (obj.length > 10) {
const sample = obj.slice(0, 3).map(item => this.formatPropertyValue(item, 1)); const sample = obj.slice(0, 3).map((item) => this.formatPropertyValue(item, 1));
return { return {
_isLazyArray: true, _isLazyArray: true,
_arrayLength: obj.length, _arrayLength: obj.length,
@@ -795,7 +799,7 @@ export class EntityDataCollector {
}; };
} }
return obj.map(item => this.formatPropertyValue(item, 1)); return obj.map((item) => this.formatPropertyValue(item, 1));
} }
const keys = Object.keys(obj); const keys = Object.keys(obj);
@@ -842,8 +846,8 @@ export class EntityDataCollector {
try { try {
const typeName = obj.constructor?.name || 'Object'; const typeName = obj.constructor?.name || 'Object';
const summary = this.getObjectSummary(obj, typeName); const summary = this.getObjectSummary(obj, typeName);
return { return {
_isLazyObject: true, _isLazyObject: true,
_typeName: typeName, _typeName: typeName,
_summary: summary, _summary: summary,
@@ -922,7 +926,12 @@ export class EntityDataCollector {
* @param propertyPath * @param propertyPath
* @param scene * @param scene
*/ */
public expandLazyObject(entityId: number, componentIndex: number, propertyPath: string, scene?: IScene | null): any { public expandLazyObject(
entityId: number,
componentIndex: number,
propertyPath: string,
scene?: IScene | null
): any {
try { try {
if (!scene) return null; if (!scene) return null;
@@ -983,4 +992,4 @@ export class EntityDataCollector {
return current; return current;
} }
} }
@@ -17,11 +17,11 @@ export class PerformanceDataCollector {
const frameTimeSeconds = Time.deltaTime; const frameTimeSeconds = Time.deltaTime;
const engineFrameTimeMs = frameTimeSeconds * 1000; const engineFrameTimeMs = frameTimeSeconds * 1000;
const currentFps = frameTimeSeconds > 0 ? Math.round(1 / frameTimeSeconds) : 0; const currentFps = frameTimeSeconds > 0 ? Math.round(1 / frameTimeSeconds) : 0;
const ecsPerformanceData = this.getECSPerformanceData(performanceMonitor); const ecsPerformanceData = this.getECSPerformanceData(performanceMonitor);
const ecsExecutionTimeMs = ecsPerformanceData.totalExecutionTime; const ecsExecutionTimeMs = ecsPerformanceData.totalExecutionTime;
const ecsPercentage = engineFrameTimeMs > 0 ? (ecsExecutionTimeMs / engineFrameTimeMs * 100) : 0; const ecsPercentage = engineFrameTimeMs > 0 ? (ecsExecutionTimeMs / engineFrameTimeMs * 100) : 0;
let memoryUsage = 0; let memoryUsage = 0;
if ((performance as any).memory) { if ((performance as any).memory) {
memoryUsage = (performance as any).memory.usedJSHeapSize / 1024 / 1024; memoryUsage = (performance as any).memory.usedJSHeapSize / 1024 / 1024;
@@ -32,9 +32,9 @@ export class PerformanceDataCollector {
if (this.frameTimeHistory.length > this.maxHistoryLength) { if (this.frameTimeHistory.length > this.maxHistoryLength) {
this.frameTimeHistory.shift(); this.frameTimeHistory.shift();
} }
// 计算ECS执行时间统计 // 计算ECS执行时间统计
const history = this.frameTimeHistory.filter(t => t >= 0); const history = this.frameTimeHistory.filter((t) => t >= 0);
const averageECSTime = history.length > 0 ? history.reduce((a, b) => a + b, 0) / history.length : ecsExecutionTimeMs; const averageECSTime = history.length > 0 ? history.reduce((a, b) => a + b, 0) / history.length : ecsExecutionTimeMs;
const minECSTime = history.length > 0 ? Math.min(...history) : ecsExecutionTimeMs; const minECSTime = history.length > 0 ? Math.min(...history) : ecsExecutionTimeMs;
const maxECSTime = history.length > 0 ? Math.max(...history) : ecsExecutionTimeMs; const maxECSTime = history.length > 0 ? Math.max(...history) : ecsExecutionTimeMs;
@@ -77,20 +77,20 @@ export class PerformanceDataCollector {
try { try {
let totalTime = 0; let totalTime = 0;
const systemBreakdown = []; const systemBreakdown = [];
const stats = performanceMonitor.getAllSystemStats(); const stats = performanceMonitor.getAllSystemStats();
if (stats.size === 0) { if (stats.size === 0) {
return { totalExecutionTime: 0, systemBreakdown: [] }; return { totalExecutionTime: 0, systemBreakdown: [] };
} }
// 计算各系统的执行时间 // 计算各系统的执行时间
for (const [systemName, stat] of stats.entries()) { for (const [systemName, stat] of stats.entries()) {
// 使用最近的执行时间而不是平均时间,这样更能反映当前状态 // 使用最近的执行时间而不是平均时间,这样更能反映当前状态
const systemTime = stat.recentTimes && stat.recentTimes.length > 0 ? const systemTime = stat.recentTimes && stat.recentTimes.length > 0 ?
stat.recentTimes[stat.recentTimes.length - 1] : stat.recentTimes[stat.recentTimes.length - 1] :
(stat.averageTime || 0); (stat.averageTime || 0);
totalTime += systemTime; totalTime += systemTime;
systemBreakdown.push({ systemBreakdown.push({
systemName: systemName, systemName: systemName,
@@ -98,15 +98,15 @@ export class PerformanceDataCollector {
percentage: 0 // 后面计算 percentage: 0 // 后面计算
}); });
} }
// 计算各系统占ECS总时间的百分比 // 计算各系统占ECS总时间的百分比
systemBreakdown.forEach(system => { systemBreakdown.forEach((system) => {
system.percentage = totalTime > 0 ? (system.executionTime / totalTime * 100) : 0; system.percentage = totalTime > 0 ? (system.executionTime / totalTime * 100) : 0;
}); });
// 按执行时间排序 // 按执行时间排序
systemBreakdown.sort((a, b) => b.executionTime - a.executionTime); systemBreakdown.sort((a, b) => b.executionTime - a.executionTime);
return { return {
totalExecutionTime: totalTime, totalExecutionTime: totalTime,
systemBreakdown: systemBreakdown systemBreakdown: systemBreakdown
@@ -127,7 +127,7 @@ export class PerformanceDataCollector {
try { try {
const stats = performanceMonitor.getAllSystemStats(); const stats = performanceMonitor.getAllSystemStats();
const systemData = performanceMonitor.getAllSystemData(); const systemData = performanceMonitor.getAllSystemData();
return Array.from(stats.entries() as Iterable<[string, any]>).map(([systemName, stat]) => { return Array.from(stats.entries() as Iterable<[string, any]>).map(([systemName, stat]) => {
const data = systemData.get(systemName); const data = systemData.get(systemName);
return { return {
@@ -167,7 +167,7 @@ export class PerformanceDataCollector {
memoryInfo.totalMemory = perfMemory.jsHeapSizeLimit || 512 * 1024 * 1024; memoryInfo.totalMemory = perfMemory.jsHeapSizeLimit || 512 * 1024 * 1024;
memoryInfo.usedMemory = perfMemory.usedJSHeapSize || 0; memoryInfo.usedMemory = perfMemory.usedJSHeapSize || 0;
memoryInfo.freeMemory = memoryInfo.totalMemory - memoryInfo.usedMemory; memoryInfo.freeMemory = memoryInfo.totalMemory - memoryInfo.usedMemory;
// 检测GC:如果使用的内存突然大幅减少,可能发生了GC // 检测GC:如果使用的内存突然大幅减少,可能发生了GC
if (this.lastMemoryCheck > 0) { if (this.lastMemoryCheck > 0) {
const memoryDrop = this.lastMemoryCheck - memoryInfo.usedMemory; const memoryDrop = this.lastMemoryCheck - memoryInfo.usedMemory;
@@ -207,16 +207,16 @@ export class PerformanceDataCollector {
// 实际的GC检测需要更复杂的逻辑 // 实际的GC检测需要更复杂的逻辑
return this.gcCollections; return this.gcCollections;
} }
// 如果有其他GC检测API,可以在这里添加 // 如果有其他GC检测API,可以在这里添加
if ((performance as any).measureUserAgentSpecificMemory) { if ((performance as any).measureUserAgentSpecificMemory) {
// 实验性API,可能不可用 // 实验性API,可能不可用
return this.gcCollections; return this.gcCollections;
} }
return this.gcCollections; return this.gcCollections;
} catch (error) { } catch (error) {
return this.gcCollections; return this.gcCollections;
} }
} }
} }
@@ -47,4 +47,4 @@ export class SceneDataCollector {
public setSceneStartTime(time: number): void { public setSceneStartTime(time: number): void {
this.sceneStartTime = time; this.sceneStartTime = time;
} }
} }
@@ -28,11 +28,11 @@ export class SystemDataCollector {
} }
const systems = entityProcessors.processors || []; const systems = entityProcessors.processors || [];
// 获取性能监控数据 // 获取性能监控数据
let systemStats: Map<string, any> = new Map(); let systemStats: Map<string, any> = new Map();
let systemData: Map<string, any> = new Map(); let systemData: Map<string, any> = new Map();
if (performanceMonitor) { if (performanceMonitor) {
try { try {
systemStats = performanceMonitor.getAllSystemStats(); systemStats = performanceMonitor.getAllSystemStats();
@@ -41,14 +41,14 @@ export class SystemDataCollector {
// 忽略错误,使用空的Map // 忽略错误,使用空的Map
} }
} }
return { return {
totalSystems: systems.length, totalSystems: systems.length,
systemsInfo: systems.map((system: any) => { systemsInfo: systems.map((system: any) => {
const systemName = system.systemName || getSystemInstanceTypeName(system); const systemName = system.systemName || getSystemInstanceTypeName(system);
const stats = systemStats.get(systemName); const stats = systemStats.get(systemName);
const data = systemData.get(systemName); const data = systemData.get(systemName);
return { return {
name: systemName, name: systemName,
type: getSystemInstanceTypeName(system), type: getSystemInstanceTypeName(system),
@@ -64,4 +64,4 @@ export class SystemDataCollector {
}) })
}; };
} }
} }
@@ -33,7 +33,7 @@ export class WebSocketManager {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
this.ws = new WebSocket(this.url); this.ws = new WebSocket(this.url);
this.ws.onopen = (event) => { this.ws.onopen = (event) => {
this.handleOpen(event); this.handleOpen(event);
resolve(); resolve();
@@ -112,7 +112,7 @@ export class WebSocketManager {
this.reconnectAttempts++; this.reconnectAttempts++;
this.reconnectTimer = setTimeout(() => { this.reconnectTimer = setTimeout(() => {
this.connect().catch(_error => { this.connect().catch((_error) => {
if (this.reconnectAttempts < this.maxReconnectAttempts) { if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.scheduleReconnect(); this.scheduleReconnect();
} }
@@ -137,7 +137,7 @@ export class WebSocketManager {
private handleOpen(event: Event): void { private handleOpen(event: Event): void {
this.isConnected = true; this.isConnected = true;
this.reconnectAttempts = 0; this.reconnectAttempts = 0;
if (this.onOpen) { if (this.onOpen) {
this.onOpen(event); this.onOpen(event);
} }
@@ -145,11 +145,11 @@ export class WebSocketManager {
private handleClose(event: CloseEvent): void { private handleClose(event: CloseEvent): void {
this.isConnected = false; this.isConnected = false;
if (this.onClose) { if (this.onClose) {
this.onClose(event); this.onClose(event);
} }
if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) { if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
this.scheduleReconnect(); this.scheduleReconnect();
} }
@@ -166,4 +166,4 @@ export class WebSocketManager {
this.onError(error); this.onError(error);
} }
} }
} }
+1 -1
View File
@@ -5,4 +5,4 @@ export { ComponentDataCollector } from './ComponentDataCollector';
export { SceneDataCollector } from './SceneDataCollector'; export { SceneDataCollector } from './SceneDataCollector';
export { WebSocketManager } from './WebSocketManager'; export { WebSocketManager } from './WebSocketManager';
export { DebugManager } from './DebugManager'; export { DebugManager } from './DebugManager';
export { DebugConfigService } from './DebugConfigService'; export { DebugConfigService } from './DebugConfigService';
+6 -6
View File
@@ -47,9 +47,9 @@ export class Emitter<T, TContext = unknown> {
* @param handler * @param handler
*/ */
public removeObserver(eventType: T, handler: Function) { public removeObserver(eventType: T, handler: Function) {
let messageData = this._messageTable.get(eventType); const messageData = this._messageTable.get(eventType);
if (messageData) { if (messageData) {
let index = messageData.findIndex(data => data.func == handler); const index = messageData.findIndex((data) => data.func == handler);
if (index != -1) if (index != -1)
messageData.splice(index, 1); messageData.splice(index, 1);
} }
@@ -61,9 +61,9 @@ export class Emitter<T, TContext = unknown> {
* @param data * @param data
*/ */
public emit<TData = unknown>(eventType: T, ...data: TData[]) { public emit<TData = unknown>(eventType: T, ...data: TData[]) {
let list = this._messageTable.get(eventType); const list = this._messageTable.get(eventType);
if (list) { if (list) {
for (let observer of list) { for (const observer of list) {
observer.func.call(observer.context, ...data); observer.func.call(observer.context, ...data);
} }
} }
@@ -75,8 +75,8 @@ export class Emitter<T, TContext = unknown> {
* @param handler * @param handler
*/ */
public hasObserver(eventType: T, handler: Function): boolean { public hasObserver(eventType: T, handler: Function): boolean {
let list = this._messageTable.get(eventType); const list = this._messageTable.get(eventType);
return list ? list.some(observer => observer.func === handler) : false; return list ? list.some((observer) => observer.func === handler) : false;
} }
/** /**
@@ -12,4 +12,4 @@ export class NumberExtension {
if (value == undefined) return 0; if (value == undefined) return 0;
return Number(value); return Number(value);
} }
} }
@@ -11,4 +11,4 @@ export class TypeUtils {
public static getType(obj: any) { public static getType(obj: any) {
return obj.constructor; return obj.constructor;
} }
} }
+1 -1
View File
@@ -1,3 +1,3 @@
// 扩展工具类导出 // 扩展工具类导出
export { TypeUtils } from './TypeUtils'; export { TypeUtils } from './TypeUtils';
export { NumberExtension } from './NumberExtension'; export { NumberExtension } from './NumberExtension';
@@ -1,5 +1,5 @@
import { Colors, LogLevel } from "./Constants"; import { Colors, LogLevel } from './Constants';
import { ILogger, LoggerColorConfig, LoggerConfig } from "./Types"; import { ILogger, LoggerColorConfig, LoggerConfig } from './Types';
/** /**
@@ -138,7 +138,7 @@ export class ConsoleLogger implements ILogger {
*/ */
private outputToConsole(level: LogLevel, message: string, ...args: unknown[]): void { private outputToConsole(level: LogLevel, message: string, ...args: unknown[]): void {
const colors = this._config.enableColors ? this.getColors() : null; const colors = this._config.enableColors ? this.getColors() : null;
switch (level) { switch (level) {
case LogLevel.Debug: case LogLevel.Debug:
if (colors) { if (colors) {
+2 -2
View File
@@ -23,7 +23,7 @@ export const Colors = {
MAGENTA: '\x1b[35m', MAGENTA: '\x1b[35m',
CYAN: '\x1b[36m', CYAN: '\x1b[36m',
WHITE: '\x1b[37m', WHITE: '\x1b[37m',
// 亮色版本 // 亮色版本
BRIGHT_BLACK: '\x1b[90m', BRIGHT_BLACK: '\x1b[90m',
BRIGHT_RED: '\x1b[91m', BRIGHT_RED: '\x1b[91m',
@@ -33,7 +33,7 @@ export const Colors = {
BRIGHT_MAGENTA: '\x1b[95m', BRIGHT_MAGENTA: '\x1b[95m',
BRIGHT_CYAN: '\x1b[96m', BRIGHT_CYAN: '\x1b[96m',
BRIGHT_WHITE: '\x1b[97m', BRIGHT_WHITE: '\x1b[97m',
// 特殊 // 特殊
RESET: '\x1b[0m', RESET: '\x1b[0m',
BOLD: '\x1b[1m', BOLD: '\x1b[1m',
@@ -1,6 +1,6 @@
import { ConsoleLogger } from "./ConsoleLogger"; import { ConsoleLogger } from './ConsoleLogger';
import { LogLevel } from "./Constants"; import { LogLevel } from './Constants';
import { ILogger, LoggerColorConfig } from "./Types"; import { ILogger, LoggerColorConfig } from './Types';
/** /**
* *
@@ -186,7 +186,7 @@ export function setGlobalLogLevel(level: LogLevel): void {
} }
/** /**
* *
* @param factory * @param factory
*/ */
export function setLoggerFactory(factory: (name?: string) => ILogger): void { export function setLoggerFactory(factory: (name?: string) => ILogger): void {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { LogLevel } from "./Constants"; import type { LogLevel } from './Constants';
/** /**
* *
+13 -13
View File
@@ -183,7 +183,7 @@ export class PerformanceMonitor implements IService {
*/ */
private updateStats(systemName: string, executionTime: number): void { private updateStats(systemName: string, executionTime: number): void {
let stats = this._systemStats.get(systemName); let stats = this._systemStats.get(systemName);
if (!stats) { if (!stats) {
stats = { stats = {
totalTime: 0, totalTime: 0,
@@ -231,7 +231,7 @@ export class PerformanceMonitor implements IService {
// 计算百分位数 // 计算百分位数
const sortedTimes = [...stats.recentTimes].sort((a, b) => a - b); const sortedTimes = [...stats.recentTimes].sort((a, b) => a - b);
const len = sortedTimes.length; const len = sortedTimes.length;
stats.percentile95 = sortedTimes[Math.floor(len * 0.95)] || 0; stats.percentile95 = sortedTimes[Math.floor(len * 0.95)] || 0;
stats.percentile99 = sortedTimes[Math.floor(len * 0.99)] || 0; stats.percentile99 = sortedTimes[Math.floor(len * 0.99)] || 0;
} }
@@ -276,12 +276,12 @@ export class PerformanceMonitor implements IService {
*/ */
public getPerformanceReport(): string { public getPerformanceReport(): string {
if (!this._isEnabled) { if (!this._isEnabled) {
return "Performance monitoring is disabled."; return 'Performance monitoring is disabled.';
} }
const lines: string[] = []; const lines: string[] = [];
lines.push("=== ECS Performance Report ==="); lines.push('=== ECS Performance Report ===');
lines.push(""); lines.push('');
// 按平均执行时间排序 // 按平均执行时间排序
const sortedSystems = Array.from(this._systemStats.entries()) const sortedSystems = Array.from(this._systemStats.entries())
@@ -289,24 +289,24 @@ export class PerformanceMonitor implements IService {
for (const [systemName, stats] of sortedSystems) { for (const [systemName, stats] of sortedSystems) {
const data = this._systemData.get(systemName); const data = this._systemData.get(systemName);
lines.push(`System: ${systemName}`); lines.push(`System: ${systemName}`);
lines.push(` Current: ${data?.executionTime.toFixed(2)}ms (${data?.entityCount} entities)`); lines.push(` Current: ${data?.executionTime.toFixed(2)}ms (${data?.entityCount} entities)`);
lines.push(` Average: ${stats.averageTime.toFixed(2)}ms`); lines.push(` Average: ${stats.averageTime.toFixed(2)}ms`);
lines.push(` Min/Max: ${stats.minTime.toFixed(2)}ms / ${stats.maxTime.toFixed(2)}ms`); lines.push(` Min/Max: ${stats.minTime.toFixed(2)}ms / ${stats.maxTime.toFixed(2)}ms`);
lines.push(` Total: ${stats.totalTime.toFixed(2)}ms (${stats.executionCount} calls)`); lines.push(` Total: ${stats.totalTime.toFixed(2)}ms (${stats.executionCount} calls)`);
if (data?.averageTimePerEntity && data.averageTimePerEntity > 0) { if (data?.averageTimePerEntity && data.averageTimePerEntity > 0) {
lines.push(` Per Entity: ${data.averageTimePerEntity.toFixed(4)}ms`); lines.push(` Per Entity: ${data.averageTimePerEntity.toFixed(4)}ms`);
} }
lines.push(""); lines.push('');
} }
// 总体统计 // 总体统计
const totalCurrentTime = Array.from(this._systemData.values()) const totalCurrentTime = Array.from(this._systemData.values())
.reduce((sum, data) => sum + data.executionTime, 0); .reduce((sum, data) => sum + data.executionTime, 0);
lines.push(`Total Frame Time: ${totalCurrentTime.toFixed(2)}ms`); lines.push(`Total Frame Time: ${totalCurrentTime.toFixed(2)}ms`);
lines.push(`Systems Count: ${this._systemData.size}`); lines.push(`Systems Count: ${this._systemData.size}`);
@@ -337,13 +337,13 @@ export class PerformanceMonitor implements IService {
*/ */
public getPerformanceWarnings(thresholdMs: number = 16.67): string[] { public getPerformanceWarnings(thresholdMs: number = 16.67): string[] {
const warnings: string[] = []; const warnings: string[] = [];
for (const [systemName, data] of this._systemData.entries()) { for (const [systemName, data] of this._systemData.entries()) {
if (data.executionTime > thresholdMs) { if (data.executionTime > thresholdMs) {
warnings.push(`${systemName}: ${data.executionTime.toFixed(2)}ms (>${thresholdMs}ms)`); warnings.push(`${systemName}: ${data.executionTime.toFixed(2)}ms (>${thresholdMs}ms)`);
} }
} }
return warnings; return warnings;
} }
@@ -370,4 +370,4 @@ export class PerformanceMonitor implements IService {
this._systemStats.clear(); this._systemStats.clear();
this._isEnabled = false; this._isEnabled = false;
} }
} }
+1 -1
View File
@@ -26,4 +26,4 @@ export interface PoolStats {
hitRate: number; hitRate: number;
/** 内存使用估算(字节) */ /** 内存使用估算(字节) */
estimatedMemoryUsage: number; estimatedMemoryUsage: number;
} }
+20 -20
View File
@@ -6,7 +6,7 @@ import { IPoolable, PoolStats } from './IPoolable';
*/ */
export class Pool<T extends IPoolable> { export class Pool<T extends IPoolable> {
private static _pools = new Map<Function, Pool<any>>(); private static _pools = new Map<Function, Pool<any>>();
private _objects: T[] = []; private _objects: T[] = [];
private _createFn: () => T; private _createFn: () => T;
private _maxSize: number; private _maxSize: number;
@@ -42,17 +42,17 @@ export class Pool<T extends IPoolable> {
* @returns * @returns
*/ */
public static getPool<T extends IPoolable>( public static getPool<T extends IPoolable>(
type: new (...args: unknown[]) => T, type: new (...args: unknown[]) => T,
maxSize: number = 100, maxSize: number = 100,
estimatedObjectSize: number = 1024 estimatedObjectSize: number = 1024
): Pool<T> { ): Pool<T> {
let pool = this._pools.get(type); let pool = this._pools.get(type);
if (!pool) { if (!pool) {
pool = new Pool<T>(() => new type(), maxSize, estimatedObjectSize); pool = new Pool<T>(() => new type(), maxSize, estimatedObjectSize);
this._pools.set(type, pool); this._pools.set(type, pool);
} }
return pool; return pool;
} }
@@ -62,7 +62,7 @@ export class Pool<T extends IPoolable> {
*/ */
public obtain(): T { public obtain(): T {
this._stats.totalObtained++; this._stats.totalObtained++;
if (this._objects.length > 0) { if (this._objects.length > 0) {
const obj = this._objects.pop()!; const obj = this._objects.pop()!;
this._stats.size--; this._stats.size--;
@@ -70,7 +70,7 @@ export class Pool<T extends IPoolable> {
this._updateMemoryUsage(); this._updateMemoryUsage();
return obj; return obj;
} }
// 池中没有可用对象,创建新对象 // 池中没有可用对象,创建新对象
this._stats.totalCreated++; this._stats.totalCreated++;
this._updateHitRate(); this._updateHitRate();
@@ -83,9 +83,9 @@ export class Pool<T extends IPoolable> {
*/ */
public release(obj: T): void { public release(obj: T): void {
if (!obj) return; if (!obj) return;
this._stats.totalReleased++; this._stats.totalReleased++;
// 如果池未满,将对象放回池中 // 如果池未满,将对象放回池中
if (this._stats.size < this._maxSize) { if (this._stats.size < this._maxSize) {
// 重置对象状态 // 重置对象状态
@@ -113,7 +113,7 @@ export class Pool<T extends IPoolable> {
for (const obj of this._objects) { for (const obj of this._objects) {
obj.reset(); obj.reset();
} }
this._objects.length = 0; this._objects.length = 0;
this._stats.size = 0; this._stats.size = 0;
this._updateMemoryUsage(); this._updateMemoryUsage();
@@ -125,7 +125,7 @@ export class Pool<T extends IPoolable> {
*/ */
public compact(targetSize?: number): void { public compact(targetSize?: number): void {
const target = targetSize ?? Math.floor(this._objects.length / 2); const target = targetSize ?? Math.floor(this._objects.length / 2);
while (this._objects.length > target) { while (this._objects.length > target) {
const obj = this._objects.pop(); const obj = this._objects.pop();
if (obj) { if (obj) {
@@ -133,7 +133,7 @@ export class Pool<T extends IPoolable> {
this._stats.size--; this._stats.size--;
} }
} }
this._updateMemoryUsage(); this._updateMemoryUsage();
} }
@@ -143,7 +143,7 @@ export class Pool<T extends IPoolable> {
*/ */
public prewarm(count: number): void { public prewarm(count: number): void {
const actualCount = Math.min(count, this._maxSize - this._objects.length); const actualCount = Math.min(count, this._maxSize - this._objects.length);
for (let i = 0; i < actualCount; i++) { for (let i = 0; i < actualCount; i++) {
const obj = this._createFn(); const obj = this._createFn();
obj.reset(); obj.reset();
@@ -151,7 +151,7 @@ export class Pool<T extends IPoolable> {
this._stats.totalCreated++; this._stats.totalCreated++;
this._stats.size++; this._stats.size++;
} }
this._updateMemoryUsage(); this._updateMemoryUsage();
} }
@@ -162,7 +162,7 @@ export class Pool<T extends IPoolable> {
public setMaxSize(maxSize: number): void { public setMaxSize(maxSize: number): void {
this._maxSize = maxSize; this._maxSize = maxSize;
this._stats.maxSize = maxSize; this._stats.maxSize = maxSize;
// 如果当前池大小超过新的最大值,进行压缩 // 如果当前池大小超过新的最大值,进行压缩
if (this._objects.length > maxSize) { if (this._objects.length > maxSize) {
this.compact(maxSize); this.compact(maxSize);
@@ -207,12 +207,12 @@ export class Pool<T extends IPoolable> {
*/ */
public static getAllPoolStats(): Record<string, PoolStats> { public static getAllPoolStats(): Record<string, PoolStats> {
const stats: Record<string, PoolStats> = {}; const stats: Record<string, PoolStats> = {};
for (const [type, pool] of this._pools) { for (const [type, pool] of this._pools) {
const typeName = type.name || type.toString(); const typeName = type.name || type.toString();
stats[typeName] = pool.getStats(); stats[typeName] = pool.getStats();
} }
return stats; return stats;
} }
@@ -242,12 +242,12 @@ export class Pool<T extends IPoolable> {
public static getGlobalStatsString(): string { public static getGlobalStatsString(): string {
const stats = this.getAllPoolStats(); const stats = this.getAllPoolStats();
const lines: string[] = ['=== Object Pool Global Statistics ===', '']; const lines: string[] = ['=== Object Pool Global Statistics ===', ''];
if (Object.keys(stats).length === 0) { if (Object.keys(stats).length === 0) {
lines.push('No pools registered'); lines.push('No pools registered');
return lines.join('\n'); return lines.join('\n');
} }
for (const [typeName, stat] of Object.entries(stats)) { for (const [typeName, stat] of Object.entries(stats)) {
lines.push(`${typeName}:`); lines.push(`${typeName}:`);
lines.push(` Size: ${stat.size}/${stat.maxSize}`); lines.push(` Size: ${stat.size}/${stat.maxSize}`);
@@ -257,7 +257,7 @@ export class Pool<T extends IPoolable> {
lines.push(` Memory: ${(stat.estimatedMemoryUsage / 1024).toFixed(1)} KB`); lines.push(` Memory: ${(stat.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
lines.push(''); lines.push('');
} }
return lines.join('\n'); return lines.join('\n');
} }
@@ -279,4 +279,4 @@ export class Pool<T extends IPoolable> {
private _updateMemoryUsage(): void { private _updateMemoryUsage(): void {
this._stats.estimatedMemoryUsage = this._stats.size * this._objectSize; this._stats.estimatedMemoryUsage = this._stats.size * this._objectSize;
} }
} }
+13 -13
View File
@@ -38,7 +38,7 @@ export class PoolManager implements IService {
*/ */
public update(): void { public update(): void {
const now = Date.now(); const now = Date.now();
if (now - this.lastCompactTime > this.autoCompactInterval) { if (now - this.lastCompactTime > this.autoCompactInterval) {
this.compactAllPools(); this.compactAllPools();
this.lastCompactTime = now; this.lastCompactTime = now;
@@ -60,12 +60,12 @@ export class PoolManager implements IService {
estimatedObjectSize: number = 1024 estimatedObjectSize: number = 1024
): Pool<T> { ): Pool<T> {
let pool = this.pools.get(name) as Pool<T>; let pool = this.pools.get(name) as Pool<T>;
if (!pool) { if (!pool) {
pool = new Pool(createFn, maxSize, estimatedObjectSize); pool = new Pool(createFn, maxSize, estimatedObjectSize);
this.pools.set(name, pool); this.pools.set(name, pool);
} }
return pool; return pool;
} }
@@ -125,11 +125,11 @@ export class PoolManager implements IService {
*/ */
public getAllStats(): Map<string, PoolStats> { public getAllStats(): Map<string, PoolStats> {
const stats = new Map<string, PoolStats>(); const stats = new Map<string, PoolStats>();
for (const [name, pool] of this.pools) { for (const [name, pool] of this.pools) {
stats.set(name, pool.getStats()); stats.set(name, pool.getStats());
} }
return stats; return stats;
} }
@@ -144,7 +144,7 @@ export class PoolManager implements IService {
let totalObtained = 0; let totalObtained = 0;
let totalReleased = 0; let totalReleased = 0;
let totalMemoryUsage = 0; let totalMemoryUsage = 0;
for (const pool of this.pools.values()) { for (const pool of this.pools.values()) {
const stats = pool.getStats(); const stats = pool.getStats();
totalSize += stats.size; totalSize += stats.size;
@@ -154,9 +154,9 @@ export class PoolManager implements IService {
totalReleased += stats.totalReleased; totalReleased += stats.totalReleased;
totalMemoryUsage += stats.estimatedMemoryUsage; totalMemoryUsage += stats.estimatedMemoryUsage;
} }
const hitRate = totalObtained === 0 ? 0 : (totalObtained - totalCreated) / totalObtained; const hitRate = totalObtained === 0 ? 0 : (totalObtained - totalCreated) / totalObtained;
return { return {
size: totalSize, size: totalSize,
maxSize: totalMaxSize, maxSize: totalMaxSize,
@@ -174,18 +174,18 @@ export class PoolManager implements IService {
*/ */
public getStatsString(): string { public getStatsString(): string {
const lines: string[] = ['=== Pool Manager Statistics ===', '']; const lines: string[] = ['=== Pool Manager Statistics ===', ''];
if (this.pools.size === 0) { if (this.pools.size === 0) {
lines.push('No pools registered'); lines.push('No pools registered');
return lines.join('\n'); return lines.join('\n');
} }
const globalStats = this.getGlobalStats(); const globalStats = this.getGlobalStats();
lines.push(`Total Pools: ${this.pools.size}`); lines.push(`Total Pools: ${this.pools.size}`);
lines.push(`Global Hit Rate: ${(globalStats.hitRate * 100).toFixed(1)}%`); lines.push(`Global Hit Rate: ${(globalStats.hitRate * 100).toFixed(1)}%`);
lines.push(`Global Memory Usage: ${(globalStats.estimatedMemoryUsage / 1024).toFixed(1)} KB`); lines.push(`Global Memory Usage: ${(globalStats.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
lines.push(''); lines.push('');
for (const [name, pool] of this.pools) { for (const [name, pool] of this.pools) {
const stats = pool.getStats(); const stats = pool.getStats();
lines.push(`${name}:`); lines.push(`${name}:`);
@@ -194,7 +194,7 @@ export class PoolManager implements IService {
lines.push(` Memory: ${(stats.estimatedMemoryUsage / 1024).toFixed(1)} KB`); lines.push(` Memory: ${(stats.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
lines.push(''); lines.push('');
} }
return lines.join('\n'); return lines.join('\n');
} }
@@ -233,4 +233,4 @@ export class PoolManager implements IService {
public dispose(): void { public dispose(): void {
this.reset(); this.reset();
} }
} }
+1 -1
View File
@@ -1,3 +1,3 @@
export * from './IPoolable'; export * from './IPoolable';
export * from './Pool'; export * from './Pool';
export * from './PoolManager'; export * from './PoolManager';
+6 -6
View File
@@ -7,27 +7,27 @@ export class Time {
* *
*/ */
public static deltaTime: number = 0; public static deltaTime: number = 0;
/** /**
* *
*/ */
public static unscaledDeltaTime: number = 0; public static unscaledDeltaTime: number = 0;
/** /**
* *
*/ */
public static totalTime: number = 0; public static totalTime: number = 0;
/** /**
* *
*/ */
public static unscaledTotalTime: number = 0; public static unscaledTotalTime: number = 0;
/** /**
* *
*/ */
public static timeScale: number = 1; public static timeScale: number = 1;
/** /**
* *
*/ */
@@ -70,4 +70,4 @@ export class Time {
public static checkEvery(interval: number, lastTime: number): boolean { public static checkEvery(interval: number, lastTime: number): boolean {
return this.totalTime - lastTime >= interval; return this.totalTime - lastTime >= interval;
} }
} }
+1 -1
View File
@@ -15,4 +15,4 @@ export interface ITimer<TContext = unknown> {
* T的上下文便 * T的上下文便
*/ */
getContext<T>(): T; getContext<T>(): T;
} }
+1 -1
View File
@@ -67,4 +67,4 @@ export class Timer<TContext = unknown> implements ITimer<TContext>{
this.context = null as unknown as TContext; this.context = null as unknown as TContext;
this._onTime = null!; this._onTime = null!;
} }
} }
@@ -30,7 +30,7 @@ export class TimerManager implements IService, IUpdatable {
* @param onTime * @param onTime
*/ */
public schedule<TContext = unknown>(timeInSeconds: number, repeats: boolean, context: TContext, onTime: (timer: ITimer<TContext>)=>void): Timer<TContext> { public schedule<TContext = unknown>(timeInSeconds: number, repeats: boolean, context: TContext, onTime: (timer: ITimer<TContext>)=>void): Timer<TContext> {
let timer = new Timer<TContext>(); const timer = new Timer<TContext>();
timer.initialize(timeInSeconds, repeats, context, onTime); timer.initialize(timeInSeconds, repeats, context, onTime);
this._timers.push(timer as Timer<unknown>); this._timers.push(timer as Timer<unknown>);
@@ -46,4 +46,4 @@ export class TimerManager implements IService, IUpdatable {
} }
this._timers = []; this._timers = [];
} }
} }
+1 -1
View File
@@ -6,4 +6,4 @@ export * from './PerformanceMonitor';
export { Time } from './Time'; export { Time } from './Time';
export * from './Debug'; export * from './Debug';
export * from './Logger'; export * from './Logger';
export * from './BinarySerializer'; export * from './BinarySerializer';
+7 -7
View File
@@ -36,13 +36,13 @@ export { ITimer } from './Utils/Timers/ITimer';
export { Timer } from './Utils/Timers/Timer'; export { Timer } from './Utils/Timers/Timer';
// 日志系统 // 日志系统
export { export {
LoggerManager, LoggerManager,
ConsoleLogger, ConsoleLogger,
Logger, Logger,
createLogger, createLogger,
setGlobalLogLevel, setGlobalLogLevel,
LogLevel LogLevel
} from './Utils/Logger'; } from './Utils/Logger';
export type { ILogger, LoggerConfig } from './Utils/Logger'; export type { ILogger, LoggerConfig } from './Utils/Logger';
@@ -64,4 +64,4 @@ export * from './Types';
export { ComponentPool, ComponentPoolManager } from './ECS/Core/Storage'; export { ComponentPool, ComponentPoolManager } from './ECS/Core/Storage';
// 平台适配 // 平台适配
export * from './Platform'; export * from './Platform';
+1 -3
View File
@@ -27,7 +27,6 @@ describe('Component - 组件基类测试', () => {
let scene: Scene; let scene: Scene;
beforeEach(() => { beforeEach(() => {
Component._idGenerator = 0;
component = new TestComponent(); component = new TestComponent();
scene = new Scene(); scene = new Scene();
entity = scene.createEntity('TestEntity'); entity = scene.createEntity('TestEntity');
@@ -51,12 +50,11 @@ describe('Component - 组件基类测试', () => {
}); });
test('组件ID应该递增分配', () => { test('组件ID应该递增分配', () => {
const startId = Component._idGenerator;
const component1 = new TestComponent(); const component1 = new TestComponent();
const component2 = new TestComponent(); const component2 = new TestComponent();
expect(component2.id).toBe(component1.id + 1); expect(component2.id).toBe(component1.id + 1);
expect(component1.id).toBeGreaterThanOrEqual(startId); expect(component1.id).toBeGreaterThanOrEqual(0);
}); });
}); });
@@ -50,17 +50,19 @@ class ConcreteEntitySystem extends EntitySystem {
const handler = (event: any) => { const handler = (event: any) => {
this.eventHandlerCallCount++; this.eventHandlerCallCount++;
}; };
this.addEventListener('manual_event', handler); const listenerRef = this.addEventListener('manual_event', handler);
this.removeEventListener('manual_event', handler); if (listenerRef) {
this.removeEventListener('manual_event', listenerRef);
}
} }
// 公开测试方法 // 公开测试方法
public testAddEventListener(eventType: string, handler: (event: any) => void): void { public testAddEventListener(eventType: string, handler: (event: any) => void): string | null {
this.addEventListener(eventType, handler); return this.addEventListener(eventType, handler);
} }
public testRemoveEventListener(eventType: string, handler: (event: any) => void): void { public testRemoveEventListener(eventType: string, listenerRef: string): void {
this.removeEventListener(eventType, handler); this.removeEventListener(eventType, listenerRef);
} }
} }
@@ -118,14 +120,16 @@ describe('EntitySystem', () => {
const handler = jest.fn(); const handler = jest.fn();
// 添加监听器 // 添加监听器
system.testAddEventListener('manual_remove_event', handler); const listenerRef = system.testAddEventListener('manual_remove_event', handler);
// 发射事件验证监听器工作 // 发射事件验证监听器工作
scene.eventSystem.emitSync('manual_remove_event', {}); scene.eventSystem.emitSync('manual_remove_event', {});
expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledTimes(1);
// 移除监听器 // 移除监听器
system.testRemoveEventListener('manual_remove_event', handler); if (listenerRef) {
system.testRemoveEventListener('manual_remove_event', listenerRef);
}
// 再次发射事件 // 再次发射事件
scene.eventSystem.emitSync('manual_remove_event', {}); scene.eventSystem.emitSync('manual_remove_event', {});
@@ -205,11 +209,11 @@ describe('EntitySystem', () => {
}); });
it('当移除不存在的监听器时,应该安全处理', () => { it('当移除不存在的监听器时,应该安全处理', () => {
const nonExistentHandler = () => {}; const nonExistentListenerRef = 'non_existent_listener_ref';
// 应该不会抛出错误 // 应该不会抛出错误
expect(() => { expect(() => {
system.testRemoveEventListener('non_existent_event', nonExistentHandler); system.testRemoveEventListener('non_existent_event', nonExistentListenerRef);
}).not.toThrow(); }).not.toThrow();
}); });
}); });
File diff suppressed because it is too large Load Diff
+77 -77
View File
@@ -4,148 +4,148 @@ import { invoke } from '@tauri-apps/api/core';
* Tauri IPC * Tauri IPC
*/ */
export class TauriAPI { export class TauriAPI {
/** /**
* *
*/ */
static async greet(name: string): Promise<string> { static async greet(name: string): Promise<string> {
return await invoke<string>('greet', { name }); return await invoke<string>('greet', { name });
} }
static async openProjectDialog(): Promise<string | null> { static async openProjectDialog(): Promise<string | null> {
return await invoke<string | null>('open_project_dialog'); return await invoke<string | null>('open_project_dialog');
} }
static async openProject(path: string): Promise<string> { static async openProject(path: string): Promise<string> {
return await invoke<string>('open_project', { path }); return await invoke<string>('open_project', { path });
} }
/** /**
* *
*/ */
static async saveProject(path: string, data: string): Promise<void> { static async saveProject(path: string, data: string): Promise<void> {
return await invoke<void>('save_project', { path, data }); return await invoke<void>('save_project', { path, data });
} }
/** /**
* *
*/ */
static async exportBinary(data: Uint8Array, outputPath: string): Promise<void> { static async exportBinary(data: Uint8Array, outputPath: string): Promise<void> {
return await invoke<void>('export_binary', { return await invoke<void>('export_binary', {
data: Array.from(data), data: Array.from(data),
outputPath outputPath
}); });
} }
/** /**
* *
*/ */
static async scanDirectory(path: string, pattern: string): Promise<string[]> { static async scanDirectory(path: string, pattern: string): Promise<string[]> {
return await invoke<string[]>('scan_directory', { path, pattern }); return await invoke<string[]>('scan_directory', { path, pattern });
} }
/** /**
* *
*/ */
static async readFileContent(path: string): Promise<string> { static async readFileContent(path: string): Promise<string> {
return await invoke<string>('read_file_content', { path }); return await invoke<string>('read_file_content', { path });
} }
/** /**
* *
*/ */
static async listDirectory(path: string): Promise<DirectoryEntry[]> { static async listDirectory(path: string): Promise<DirectoryEntry[]> {
return await invoke<DirectoryEntry[]>('list_directory', { path }); return await invoke<DirectoryEntry[]>('list_directory', { path });
} }
/** /**
* Custom Protocol * Custom Protocol
*/ */
static async setProjectBasePath(path: string): Promise<void> { static async setProjectBasePath(path: string): Promise<void> {
return await invoke<void>('set_project_base_path', { path }); return await invoke<void>('set_project_base_path', { path });
} }
/** /**
* debug模式下可用 * debug模式下可用
*/ */
static async toggleDevtools(): Promise<void> { static async toggleDevtools(): Promise<void> {
return await invoke<void>('toggle_devtools'); return await invoke<void>('toggle_devtools');
} }
/** /**
* *
* @param defaultName * @param defaultName
* @returns null * @returns null
*/ */
static async saveSceneDialog(defaultName?: string): Promise<string | null> { static async saveSceneDialog(defaultName?: string): Promise<string | null> {
return await invoke<string | null>('save_scene_dialog', { defaultName }); return await invoke<string | null>('save_scene_dialog', { defaultName });
} }
/** /**
* *
* @returns null * @returns null
*/ */
static async openSceneDialog(): Promise<string | null> { static async openSceneDialog(): Promise<string | null> {
return await invoke<string | null>('open_scene_dialog'); return await invoke<string | null>('open_scene_dialog');
} }
/** /**
* *
* @param path * @param path
*/ */
static async createDirectory(path: string): Promise<void> { static async createDirectory(path: string): Promise<void> {
return await invoke<void>('create_directory', { path }); return await invoke<void>('create_directory', { path });
} }
/** /**
* *
* @param path * @param path
* @param content * @param content
*/ */
static async writeFileContent(path: string, content: string): Promise<void> { static async writeFileContent(path: string, content: string): Promise<void> {
return await invoke<void>('write_file_content', { path, content }); return await invoke<void>('write_file_content', { path, content });
} }
/** /**
* *
* @param path * @param path
* @returns * @returns
*/ */
static async pathExists(path: string): Promise<boolean> { static async pathExists(path: string): Promise<boolean> {
return await invoke<boolean>('path_exists', { path }); return await invoke<boolean>('path_exists', { path });
} }
/** /**
* 使 * 使
* @param path * @param path
*/ */
static async openFileWithSystemApp(path: string): Promise<void> { static async openFileWithSystemApp(path: string): Promise<void> {
await invoke('open_file_with_default_app', { filePath: path }); await invoke('open_file_with_default_app', { filePath: path });
} }
/** /**
* *
* @param path * @param path
*/ */
static async showInFolder(path: string): Promise<void> { static async showInFolder(path: string): Promise<void> {
await invoke('show_in_folder', { filePath: path }); await invoke('show_in_folder', { filePath: path });
} }
/** /**
* *
* @returns null * @returns null
*/ */
static async openBehaviorTreeDialog(): Promise<string | null> { static async openBehaviorTreeDialog(): Promise<string | null> {
return await invoke<string | null>('open_behavior_tree_dialog'); return await invoke<string | null>('open_behavior_tree_dialog');
} }
/** /**
* *
* @param projectPath * @param projectPath
* @returns ID列表 .ecs/behaviors * @returns ID列表 .ecs/behaviors
*/ */
static async scanBehaviorTrees(projectPath: string): Promise<string[]> { static async scanBehaviorTrees(projectPath: string): Promise<string[]> {
return await invoke<string[]>('scan_behavior_trees', { projectPath }); return await invoke<string[]>('scan_behavior_trees', { projectPath });
} }
} }
export interface DirectoryEntry { export interface DirectoryEntry {
@@ -23,358 +23,358 @@ interface AssetBrowserProps {
} }
export function AssetBrowser({ projectPath, locale, onOpenScene, onOpenBehaviorTree }: AssetBrowserProps) { export function AssetBrowser({ projectPath, locale, onOpenScene, onOpenBehaviorTree }: AssetBrowserProps) {
const [currentPath, setCurrentPath] = useState<string | null>(null); const [currentPath, setCurrentPath] = useState<string | null>(null);
const [selectedPath, setSelectedPath] = useState<string | null>(null); const [selectedPath, setSelectedPath] = useState<string | null>(null);
const [assets, setAssets] = useState<AssetItem[]>([]); const [assets, setAssets] = useState<AssetItem[]>([]);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [contextMenu, setContextMenu] = useState<{ const [contextMenu, setContextMenu] = useState<{
position: { x: number; y: number }; position: { x: number; y: number };
asset: AssetItem; asset: AssetItem;
} | null>(null); } | null>(null);
const translations = { const translations = {
en: { en: {
title: 'Content Browser', title: 'Content Browser',
noProject: 'No project loaded', noProject: 'No project loaded',
loading: 'Loading...', loading: 'Loading...',
empty: 'No assets found', empty: 'No assets found',
search: 'Search...', search: 'Search...',
name: 'Name', name: 'Name',
type: 'Type', type: 'Type',
file: 'File', file: 'File',
folder: 'Folder' folder: 'Folder'
}, },
zh: { zh: {
title: '内容浏览器', title: '内容浏览器',
noProject: '没有加载项目', noProject: '没有加载项目',
loading: '加载中...', loading: '加载中...',
empty: '没有找到资产', empty: '没有找到资产',
search: '搜索...', search: '搜索...',
name: '名称', name: '名称',
type: '类型', type: '类型',
file: '文件', file: '文件',
folder: '文件夹' folder: '文件夹'
}
};
const t = translations[locale as keyof typeof translations] || translations.en;
useEffect(() => {
if (projectPath) {
setCurrentPath(projectPath);
loadAssets(projectPath);
} else {
setAssets([]);
setCurrentPath(null);
setSelectedPath(null);
}
}, [projectPath]);
// Listen for asset reveal requests
useEffect(() => {
const messageHub = Core.services.resolve(MessageHub);
if (!messageHub) return;
const unsubscribe = messageHub.subscribe('asset:reveal', (data: any) => {
const filePath = data.path;
if (filePath) {
setSelectedPath(filePath);
const lastSlashIndex = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
const dirPath = lastSlashIndex > 0 ? filePath.substring(0, lastSlashIndex) : null;
if (dirPath) {
setCurrentPath(dirPath);
loadAssets(dirPath);
} }
} };
});
return () => unsubscribe(); const t = translations[locale as keyof typeof translations] || translations.en;
}, []);
const loadAssets = async (path: string) => { useEffect(() => {
setLoading(true); if (projectPath) {
try { setCurrentPath(projectPath);
const entries = await TauriAPI.listDirectory(path); loadAssets(projectPath);
} else {
setAssets([]);
setCurrentPath(null);
setSelectedPath(null);
}
}, [projectPath]);
const assetItems: AssetItem[] = entries.map((entry: DirectoryEntry) => { // Listen for asset reveal requests
const extension = entry.is_dir ? undefined : useEffect(() => {
(entry.name.includes('.') ? entry.name.split('.').pop() : undefined); const messageHub = Core.services.resolve(MessageHub);
if (!messageHub) return;
return { const unsubscribe = messageHub.subscribe('asset:reveal', (data: any) => {
name: entry.name, const filePath = data.path;
path: entry.path, if (filePath) {
type: entry.is_dir ? 'folder' as const : 'file' as const, setSelectedPath(filePath);
extension const lastSlashIndex = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
}; const dirPath = lastSlashIndex > 0 ? filePath.substring(0, lastSlashIndex) : null;
}); if (dirPath) {
setCurrentPath(dirPath);
loadAssets(dirPath);
}
}
});
setAssets(assetItems.sort((a, b) => { return () => unsubscribe();
if (a.type === b.type) return a.name.localeCompare(b.name); }, []);
return a.type === 'folder' ? -1 : 1;
}));
} catch (error) {
console.error('Failed to load assets:', error);
setAssets([]);
} finally {
setLoading(false);
}
};
const handleFolderSelect = (path: string) => { const loadAssets = async (path: string) => {
setCurrentPath(path); setLoading(true);
loadAssets(path);
};
const handleAssetClick = (asset: AssetItem) => {
setSelectedPath(asset.path);
};
const handleAssetDoubleClick = async (asset: AssetItem) => {
if (asset.type === 'folder') {
setCurrentPath(asset.path);
loadAssets(asset.path);
} else if (asset.type === 'file') {
if (asset.extension === 'ecs' && onOpenScene) {
onOpenScene(asset.path);
} else if (asset.extension === 'btree' && onOpenBehaviorTree) {
onOpenBehaviorTree(asset.path);
} else {
// 其他文件使用系统默认程序打开
try { try {
await TauriAPI.openFileWithSystemApp(asset.path); const entries = await TauriAPI.listDirectory(path);
const assetItems: AssetItem[] = entries.map((entry: DirectoryEntry) => {
const extension = entry.is_dir ? undefined :
(entry.name.includes('.') ? entry.name.split('.').pop() : undefined);
return {
name: entry.name,
path: entry.path,
type: entry.is_dir ? 'folder' as const : 'file' as const,
extension
};
});
setAssets(assetItems.sort((a, b) => {
if (a.type === b.type) return a.name.localeCompare(b.name);
return a.type === 'folder' ? -1 : 1;
}));
} catch (error) { } catch (error) {
console.error('Failed to open file:', error); console.error('Failed to load assets:', error);
setAssets([]);
} finally {
setLoading(false);
} }
} };
}
};
const handleContextMenu = (e: React.MouseEvent, asset: AssetItem) => { const handleFolderSelect = (path: string) => {
e.preventDefault(); setCurrentPath(path);
setContextMenu({ loadAssets(path);
position: { x: e.clientX, y: e.clientY }, };
asset
});
};
const getContextMenuItems = (asset: AssetItem): ContextMenuItem[] => { const handleAssetClick = (asset: AssetItem) => {
const items: ContextMenuItem[] = []; setSelectedPath(asset.path);
};
// 打开 const handleAssetDoubleClick = async (asset: AssetItem) => {
if (asset.type === 'file') { if (asset.type === 'folder') {
items.push({ setCurrentPath(asset.path);
label: locale === 'zh' ? '打开' : 'Open', loadAssets(asset.path);
icon: <File size={16} />, } else if (asset.type === 'file') {
onClick: () => handleAssetDoubleClick(asset) if (asset.extension === 'ecs' && onOpenScene) {
}); onOpenScene(asset.path);
} } else if (asset.extension === 'btree' && onOpenBehaviorTree) {
onOpenBehaviorTree(asset.path);
// 在文件管理器中显示 } else {
items.push({ // 其他文件使用系统默认程序打开
label: locale === 'zh' ? '在文件管理器中显示' : 'Show in Explorer', try {
icon: <FolderOpen size={16} />, await TauriAPI.openFileWithSystemApp(asset.path);
onClick: async () => { } catch (error) {
try { console.error('Failed to open file:', error);
await TauriAPI.showInFolder(asset.path); }
} catch (error) { }
console.error('Failed to show in folder:', error);
} }
} };
});
items.push({ label: '', separator: true, onClick: () => {} }); const handleContextMenu = (e: React.MouseEvent, asset: AssetItem) => {
e.preventDefault();
setContextMenu({
position: { x: e.clientX, y: e.clientY },
asset
});
};
// 复制路径 const getContextMenuItems = (asset: AssetItem): ContextMenuItem[] => {
items.push({ const items: ContextMenuItem[] = [];
label: locale === 'zh' ? '复制路径' : 'Copy Path',
icon: <Copy size={16} />,
onClick: () => {
navigator.clipboard.writeText(asset.path);
}
});
items.push({ label: '', separator: true, onClick: () => {} }); // 打开
if (asset.type === 'file') {
items.push({
label: locale === 'zh' ? '打开' : 'Open',
icon: <File size={16} />,
onClick: () => handleAssetDoubleClick(asset)
});
}
// 重命名 // 在文件管理器中显示
items.push({ items.push({
label: locale === 'zh' ? '重命名' : 'Rename', label: locale === 'zh' ? '在文件管理器中显示' : 'Show in Explorer',
icon: <Edit3 size={16} />, icon: <FolderOpen size={16} />,
onClick: () => { onClick: async () => {
// TODO: 实现重命名功能 try {
console.log('Rename:', asset.path); await TauriAPI.showInFolder(asset.path);
}, } catch (error) {
disabled: true console.error('Failed to show in folder:', error);
}); }
}
});
// 删除 items.push({ label: '', separator: true, onClick: () => {} });
items.push({
label: locale === 'zh' ? '删除' : 'Delete',
icon: <Trash2 size={16} />,
onClick: () => {
// TODO: 实现删除功能
console.log('Delete:', asset.path);
},
disabled: true
});
return items; // 复制路径
}; items.push({
label: locale === 'zh' ? '复制路径' : 'Copy Path',
icon: <Copy size={16} />,
onClick: () => {
navigator.clipboard.writeText(asset.path);
}
});
const getBreadcrumbs = () => { items.push({ label: '', separator: true, onClick: () => {} });
if (!currentPath || !projectPath) return [];
const relative = currentPath.replace(projectPath, ''); // 重命名
const parts = relative.split(/[/\\]/).filter(p => p); items.push({
label: locale === 'zh' ? '重命名' : 'Rename',
icon: <Edit3 size={16} />,
onClick: () => {
// TODO: 实现重命名功能
console.log('Rename:', asset.path);
},
disabled: true
});
const crumbs = [{ name: 'Content', path: projectPath }]; // 删除
let accPath = projectPath; items.push({
label: locale === 'zh' ? '删除' : 'Delete',
icon: <Trash2 size={16} />,
onClick: () => {
// TODO: 实现删除功能
console.log('Delete:', asset.path);
},
disabled: true
});
for (const part of parts) { return items;
accPath = `${accPath}${accPath.endsWith('\\') || accPath.endsWith('/') ? '' : '/'}${part}`; };
crumbs.push({ name: part, path: accPath });
}
return crumbs; const getBreadcrumbs = () => {
}; if (!currentPath || !projectPath) return [];
const filteredAssets = searchQuery const relative = currentPath.replace(projectPath, '');
? assets.filter(asset => const parts = relative.split(/[/\\]/).filter((p) => p);
asset.name.toLowerCase().includes(searchQuery.toLowerCase())
)
: assets;
const getFileIcon = (asset: AssetItem) => { const crumbs = [{ name: 'Content', path: projectPath }];
if (asset.type === 'folder') { let accPath = projectPath;
return <Folder className="asset-icon" style={{ color: '#ffa726' }} size={20} />;
}
const ext = asset.extension?.toLowerCase(); for (const part of parts) {
switch (ext) { accPath = `${accPath}${accPath.endsWith('\\') || accPath.endsWith('/') ? '' : '/'}${part}`;
case 'ecs': crumbs.push({ name: part, path: accPath });
return <File className="asset-icon" style={{ color: '#66bb6a' }} size={20} />; }
case 'btree':
return <FileText className="asset-icon" style={{ color: '#ab47bc' }} size={20} />;
case 'ts':
case 'tsx':
case 'js':
case 'jsx':
return <FileCode className="asset-icon" style={{ color: '#42a5f5' }} size={20} />;
case 'json':
return <FileJson className="asset-icon" style={{ color: '#ffa726' }} size={20} />;
case 'png':
case 'jpg':
case 'jpeg':
case 'gif':
return <FileImage className="asset-icon" style={{ color: '#ec407a' }} size={20} />;
default:
return <File className="asset-icon" size={20} />;
}
};
if (!projectPath) { return crumbs;
return ( };
<div className="asset-browser">
<div className="asset-browser-header">
<h3>{t.title}</h3>
</div>
<div className="asset-browser-empty">
<p>{t.noProject}</p>
</div>
</div>
);
}
const breadcrumbs = getBreadcrumbs(); const filteredAssets = searchQuery
? assets.filter((asset) =>
asset.name.toLowerCase().includes(searchQuery.toLowerCase())
)
: assets;
return ( const getFileIcon = (asset: AssetItem) => {
<div className="asset-browser"> if (asset.type === 'folder') {
<div className="asset-browser-header"> return <Folder className="asset-icon" style={{ color: '#ffa726' }} size={20} />;
<h3>{t.title}</h3> }
</div>
<div className="asset-browser-content"> const ext = asset.extension?.toLowerCase();
<ResizablePanel switch (ext) {
direction="horizontal" case 'ecs':
defaultSize={200} return <File className="asset-icon" style={{ color: '#66bb6a' }} size={20} />;
minSize={150} case 'btree':
maxSize={400} return <FileText className="asset-icon" style={{ color: '#ab47bc' }} size={20} />;
leftOrTop={ case 'ts':
<div className="asset-browser-tree"> case 'tsx':
<FileTree case 'js':
rootPath={projectPath} case 'jsx':
onSelectFile={handleFolderSelect} return <FileCode className="asset-icon" style={{ color: '#42a5f5' }} size={20} />;
selectedPath={currentPath} case 'json':
/> return <FileJson className="asset-icon" style={{ color: '#ffa726' }} size={20} />;
</div> case 'png':
} case 'jpg':
rightOrBottom={ case 'jpeg':
<div className="asset-browser-list"> case 'gif':
<div className="asset-browser-breadcrumb"> return <FileImage className="asset-icon" style={{ color: '#ec407a' }} size={20} />;
{breadcrumbs.map((crumb, index) => ( default:
<span key={crumb.path}> return <File className="asset-icon" size={20} />;
<span }
className="breadcrumb-item" };
onClick={() => {
setCurrentPath(crumb.path); if (!projectPath) {
loadAssets(crumb.path); return (
}} <div className="asset-browser">
> <div className="asset-browser-header">
{crumb.name} <h3>{t.title}</h3>
</span>
{index < breadcrumbs.length - 1 && <span className="breadcrumb-separator"> / </span>}
</span>
))}
</div>
<div className="asset-browser-toolbar">
<input
type="text"
className="asset-search"
placeholder={t.search}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
{loading ? (
<div className="asset-browser-loading">
<p>{t.loading}</p>
</div> </div>
) : filteredAssets.length === 0 ? (
<div className="asset-browser-empty"> <div className="asset-browser-empty">
<p>{t.empty}</p> <p>{t.noProject}</p>
</div> </div>
) : (
<div className="asset-list">
{filteredAssets.map((asset, index) => (
<div
key={index}
className={`asset-item ${selectedPath === asset.path ? 'selected' : ''}`}
onClick={() => handleAssetClick(asset)}
onDoubleClick={() => handleAssetDoubleClick(asset)}
onContextMenu={(e) => handleContextMenu(e, asset)}
>
{getFileIcon(asset)}
<div className="asset-name" title={asset.name}>
{asset.name}
</div>
<div className="asset-type">
{asset.type === 'folder' ? t.folder : (asset.extension || t.file)}
</div>
</div>
))}
</div>
)}
</div> </div>
} );
/> }
</div>
{contextMenu && ( const breadcrumbs = getBreadcrumbs();
<ContextMenu
items={getContextMenuItems(contextMenu.asset)} return (
position={contextMenu.position} <div className="asset-browser">
onClose={() => setContextMenu(null)} <div className="asset-browser-header">
/> <h3>{t.title}</h3>
)} </div>
</div>
); <div className="asset-browser-content">
<ResizablePanel
direction="horizontal"
defaultSize={200}
minSize={150}
maxSize={400}
leftOrTop={
<div className="asset-browser-tree">
<FileTree
rootPath={projectPath}
onSelectFile={handleFolderSelect}
selectedPath={currentPath}
/>
</div>
}
rightOrBottom={
<div className="asset-browser-list">
<div className="asset-browser-breadcrumb">
{breadcrumbs.map((crumb, index) => (
<span key={crumb.path}>
<span
className="breadcrumb-item"
onClick={() => {
setCurrentPath(crumb.path);
loadAssets(crumb.path);
}}
>
{crumb.name}
</span>
{index < breadcrumbs.length - 1 && <span className="breadcrumb-separator"> / </span>}
</span>
))}
</div>
<div className="asset-browser-toolbar">
<input
type="text"
className="asset-search"
placeholder={t.search}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
{loading ? (
<div className="asset-browser-loading">
<p>{t.loading}</p>
</div>
) : filteredAssets.length === 0 ? (
<div className="asset-browser-empty">
<p>{t.empty}</p>
</div>
) : (
<div className="asset-list">
{filteredAssets.map((asset, index) => (
<div
key={index}
className={`asset-item ${selectedPath === asset.path ? 'selected' : ''}`}
onClick={() => handleAssetClick(asset)}
onDoubleClick={() => handleAssetDoubleClick(asset)}
onContextMenu={(e) => handleContextMenu(e, asset)}
>
{getFileIcon(asset)}
<div className="asset-name" title={asset.name}>
{asset.name}
</div>
<div className="asset-type">
{asset.type === 'folder' ? t.folder : (asset.extension || t.file)}
</div>
</div>
))}
</div>
)}
</div>
}
/>
</div>
{contextMenu && (
<ContextMenu
items={getContextMenuItems(contextMenu.asset)}
position={contextMenu.position}
onClose={() => setContextMenu(null)}
/>
)}
</div>
);
} }

Some files were not shown because too many files have changed in this diff Show More