Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddc7a7750e | ||
|
|
50a01d9dd3 | ||
|
|
793aad0a5e | ||
|
|
9c1bf8dbed | ||
|
|
620f3eecc7 | ||
|
|
4355538d8d | ||
|
|
3ad5dc9ca3 | ||
|
|
57c7e7be3f |
@@ -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
@@ -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); // 只加载一次
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建新原型
|
* 创建新原型
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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,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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|||||||
@@ -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,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';
|
||||||
|
|||||||
@@ -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 系统类型名称
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 如果系统应该处理,则为true,如果不处理则为false
|
* @returns 如果系统应该处理,则为true,如果不处理则为false
|
||||||
*/
|
*/
|
||||||
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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
/**
|
/**
|
||||||
* 世代式ID池管理器
|
* 世代式ID池管理器
|
||||||
*
|
*
|
||||||
* 用于管理实体ID的分配和回收,支持世代版本控制以防止悬空引用问题。
|
* 用于管理实体ID的分配和回收,支持世代版本控制以防止悬空引用问题。
|
||||||
* 世代式ID由索引和版本组成,当ID被回收时版本会递增,确保旧引用失效。
|
* 世代式ID由索引和版本组成,当ID被回收时版本会递增,确保旧引用失效。
|
||||||
*
|
*
|
||||||
* 支持动态扩展,理论上可以支持到65535个索引(16位),每个索引65535个版本(16位)。
|
* 支持动态扩展,理论上可以支持到65535个索引(16位),每个索引65535个版本(16位)。
|
||||||
* 总计可以处理超过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位版本 = 32位ID,确保高效位操作
|
* 这是框架设计选择:16位索引 + 16位版本 = 32位ID,确保高效位操作
|
||||||
* 不是硬件限制,而是性能和内存效率的权衡
|
* 不是硬件限制,而是性能和内存效率的权衡
|
||||||
*/
|
*/
|
||||||
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
|
||||||
*
|
*
|
||||||
* 返回一个32位ID,高16位为世代版本,低16位为索引。
|
* 返回一个32位ID,高16位为世代版本,低16位为索引。
|
||||||
*
|
*
|
||||||
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打包索引和世代为32位ID
|
* 打包索引和世代为32位ID
|
||||||
*
|
*
|
||||||
* @param index 索引(16位)
|
* @param index 索引(16位)
|
||||||
* @param generation 世代版本(16位)
|
* @param generation 世代版本(16位)
|
||||||
* @returns 打包后的32位ID
|
* @returns 打包后的32位ID
|
||||||
@@ -366,7 +366,7 @@ export class IdentifierPool {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 从ID中解包索引
|
* 从ID中解包索引
|
||||||
*
|
*
|
||||||
* @param id 32位ID
|
* @param id 32位ID
|
||||||
* @returns 索引部分(16位)
|
* @returns 索引部分(16位)
|
||||||
* @private
|
* @private
|
||||||
@@ -377,7 +377,7 @@ export class IdentifierPool {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 从ID中解包世代版本
|
* 从ID中解包世代版本
|
||||||
*
|
*
|
||||||
* @param id 32位ID
|
* @param id 32位ID
|
||||||
* @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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(' & ')}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,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) {
|
||||||
|
|||||||
@@ -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,4 +1,4 @@
|
|||||||
import type { LogLevel } from "./Constants";
|
import type { LogLevel } from './Constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志接口
|
* 日志接口
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ export interface PoolStats {
|
|||||||
hitRate: number;
|
hitRate: number;
|
||||||
/** 内存使用估算(字节) */
|
/** 内存使用估算(字节) */
|
||||||
estimatedMemoryUsage: number;
|
estimatedMemoryUsage: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,3 +1,3 @@
|
|||||||
export * from './IPoolable';
|
export * from './IPoolable';
|
||||||
export * from './Pool';
|
export * from './Pool';
|
||||||
export * from './PoolManager';
|
export * from './PoolManager';
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ export interface ITimer<TContext = unknown> {
|
|||||||
* 返回投向T的上下文,作为方便
|
* 返回投向T的上下文,作为方便
|
||||||
*/
|
*/
|
||||||
getContext<T>(): T;
|
getContext<T>(): T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+697
-698
File diff suppressed because it is too large
Load Diff
@@ -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
Reference in New Issue
Block a user