refactor: 代码规范化与依赖清理 (#317)
* refactor(deps): 统一编辑器包依赖配置 & 优化分层架构 - 将 ecs-engine-bindgen 提升为 Layer 1 核心包 - 统一 9 个编辑器包的依赖声明模式 - 清理废弃的包目录 (ui, ui-editor, network-*) * refactor(tokens): 修复 PrefabService 令牌冲突 & 补充 module.json - 将 editor-core 的 PrefabServiceToken 改名为 EditorPrefabServiceToken 避免与 asset-system 的 PrefabServiceToken 冲突 (Symbol.for 冲突) - 为 mesh-3d 添加 module.json - 为 world-streaming 添加 module.json * refactor(editor-core): 整理导出结构 & 添加 blueprint tokens.ts - 按功能分组整理 editor-core 的 65 行导出 - 添加清晰的分组注释 (中英双语) - 为 blueprint 添加占位符 tokens.ts * chore(editor): 为 14 个编辑器插件包添加 module.json 统一编辑器包的模块配置,包含: - isEditorPlugin 标识 - runtimeModule 关联 - exports 导出清单 (inspectors, panels, gizmos) * refactor(core): 改进类型安全 - 减少 as any 使用 - 添加 GlobalTypes.ts 定义小游戏平台和 Chrome API 类型 - SoAStorage 使用 IComponentTypeMetadata 替代 as any - PlatformDetector 使用类型安全的平台检测 - 添加 ISoAStorageStats/ISoAFieldStats 接口 * feat(editor): 添加 EditorServicesContext 解决 prop drilling - 新增 contexts/EditorServicesContext.tsx 提供统一服务访问 - App.tsx 包裹 EditorServicesProvider - 提供 useEditorServices/useMessageHub 等便捷 hooks - SceneHierarchy 添加迁移注释,后续可移除 props * docs(editor): 澄清 inspector 目录架构关系 - inspector/ 标记为内部实现,添加 @deprecated 警告 - inspectors/ 标记为公共 API 入口点 - 添加架构说明文档 * refactor(editor): 添加全局类型声明消除 window as any - 创建 editor-app/src/global.d.ts 声明 Window 接口扩展 - 创建 editor-core/src/global.d.ts 声明 Window 接口扩展 - 更新 App.tsx 使用类型安全的 window 属性访问 - 更新 PluginLoader.ts 使用 window.__ESENGINE_PLUGINS__ - 更新 PluginSDKRegistry.ts 使用 window.__ESENGINE_SDK__ - 更新 UserCodeService.ts 使用类型安全的全局变量访问 * refactor(editor): 提取项目和场景操作到独立 hooks - 创建 useProjectActions hook 封装项目操作 - 创建 useSceneActions hook 封装场景操作 - 为渐进式重构 App.tsx 做准备 * refactor(editor): 清理冗余代码和未使用文件 删除的目录和文件: - application/state/ - 重复的状态管理(与 stores/ 重复) - 8 个孤立 CSS 文件(对应组件不存在) - AssetBrowser.tsx - 仅为 ContentBrowser 的向后兼容包装 - AssetPicker.tsx - 未被使用 - AssetPickerDialog.tsx (顶级) - 已被 dialogs/ 版本取代 - EntityInspector.tsx (顶级) - 已被 inspectors/views/ 版本取代 修复: - 移除 App.tsx 中未使用的导入 - 更新 application/index.ts 移除已删除模块 - 修复 useProjectActions.ts 的 MutableRefObject 类型 * refactor(editor): 统一 inspectors 模块导出结构 - 在 inspectors/index.ts 重新导出 PropertyInspector - 创建 inspectors/fields/index.ts barrel export - 导出 views、fields、common 子模块 - 更新 EntityInspector 使用统一入口导入 * refactor(editor): 删除废弃的 Profiler 组件 删除未使用的组件(共 1059 行): - ProfilerPanel.tsx (229 行) - ProfilerWindow.tsx (589 行) - ProfilerDockPanel.tsx (241 行) - ProfilerPanel.css - ProfilerDockPanel.css 保留:AdvancedProfiler + AdvancedProfilerWindow(正在使用) * refactor(runtime-core): 统一依赖处理与插件状态管理 - 新增 DependencyUtils 统一拓扑排序和依赖验证 - 新增 PluginState 定义插件生命周期状态机 - 合并 UnifiedPluginLoader 到 PluginLoader - 清理 index.ts 移除不必要的 Token re-exports - 新增 RuntimeMode/UserCodeRealm/ImportMapGenerator * refactor(editor-core): 使用统一的 ImportMapGenerator - WebBuildPipeline 使用 runtime-core 的 generateImportMap - UserCodeService 添加 ImportMap 相关接口 * feat(compiler): 增强 esbuild 查找策略 - 支持本地 node_modules、pnpm exec、npx、全局多种来源 - EngineService 使用 RuntimeMode * refactor(runtime-core): 简化 GameRuntime 代码 - 合并 _disableGameLogicSystems/_enableGameLogicSystems 为 _setGameLogicSystemsEnabled - 精简本地 Token 定义的注释 * refactor(editor-core): 引入 BaseRegistry 基类消除代码重复 - 新增 BaseRegistry 和 PrioritizedRegistry 基类 - 重构 CompilerRegistry, InspectorRegistry, FieldEditorRegistry - 统一注册表的日志记录和错误处理 * refactor(editor-core): 扩展 BaseRegistry 重构 - ComponentInspectorRegistry 继承 PrioritizedRegistry - EditorComponentRegistry 继承 BaseRegistry - EntityCreationRegistry 继承 BaseRegistry - PropertyRendererRegistry 继承 PrioritizedRegistry - 导出 BaseRegistry 基类供外部使用 - 统一双语注释格式 * refactor(editor-core): 代码优雅性优化 CommandManager: - 提取 tryMergeWithLast() 和 pushToUndoStack() 消除重复代码 - 统一双语注释格式 FileActionRegistry: - 提取 normalizeExtension() 消除扩展名规范化重复 - 统一私有属性命名风格(_前缀) - 使用 createRegistryToken 统一 Token 创建 BaseRegistry: - 添加 IOrdered 接口 - 添加 sortByOrder() 排序辅助方法 EntityCreationRegistry: - 使用 sortByOrder() 简化排序逻辑 * refactor(editor-core): 统一日志系统 & 代码规范优化 - GizmoRegistry: 使用 createLogger 替代 console.warn - VirtualNodeRegistry: 使用 createLogger 替代 console.warn - WindowRegistry: 使用 logger、添加 _ 前缀、导出 IWindowRegistry token - EditorViewportService: 使用 createLogger 替代 console.warn - ComponentActionRegistry: 使用 logger、添加 _ 前缀、返回值改进 - SettingsRegistry: 使用 logger、提取 ensureCategory/ensureSection 方法 - 添加 WindowRegistry 到主导出 * refactor(editor-core): ModuleRegistry 使用 logger 替代 console * refactor(editor-core): SerializerRegistry/UIRegistry 添加 token 和 _ 前缀 * refactor(editor-core): UIRegistry 代码优雅性 & Token 命名统一 - UIRegistry: 提取 _sortByOrder 消除 6 处重复排序逻辑 - UIRegistry: 添加分节注释和双语文档 - FieldEditorRegistry: Token 重命名为 FieldEditorRegistryToken - PropertyRendererRegistry: Token 重命名为 PropertyRendererRegistryToken * refactor(core): 统一日志系统 - console 替换为 logger - ComponentSerializer: 使用 logger 替代 console.warn - ComponentRegistry: console.warn → logger.warn (已有 logger) - SceneSerializer: 添加 logger,替换 console.warn/error - SystemScheduler: 添加 logger,替换 console.warn - VersionMigration: 添加 logger,替换所有 console.warn - RuntimeModeService: console.error → logger.error - Core.ts: _logger 改为 readonly,双语错误消息 - SceneSerializer 修复:使用 getComponentTypeName 替代 constructor.name * fix(core): 修复 constructor.name 压缩后失效问题 - Scene.ts: 使用 system.systemName 替代 system.constructor.name - CommandBuffer.ts: 使用 getComponentTypeName() 替代 constructor.name * refactor(editor-core): 代码规范优化 - 私有方法命名 & 日志统一 - BuildService: console → logger - FileActionRegistry: 添加 logger, 私有方法 _ 前缀 - SettingsRegistry: 私有方法 _ 前缀 (ensureCategory → _ensureCategory) * refactor(core): Scene.ts 私有变量命名规范化 - logger → _logger (遵循私有变量 _ 前缀规范) * refactor(editor-core): 服务类私有成员命名规范化 - CommandManager: 私有变量/方法添加 _ 前缀 - undoStack/redoStack/config/isExecuting - tryMergeWithLast/pushToUndoStack - LocaleService: 私有变量/方法添加 _ 前缀 - currentLocale/translations/changeListeners - deepMerge/getNestedValue/loadSavedLocale/saveLocale * refactor(core): 私有成员命名规范化 & 单例模式优化 - Component.ts: _idGenerator 私有静态变量规范化 - PlatformManager.ts: _instance, _adapter, _logger 规范化 - AutoProfiler.ts: _instance, _config 及所有私有方法规范化 - ProfilerSDK.ts: _instance, _config 及所有私有方法规范化 - ComponentPoolManager: _instance, _pools, _usageTracker 规范化 - GlobalEventBus: _instance 规范化 - 添加中英双语 JSDoc 注释 * refactor(editor-app,behavior-tree-editor): 私有成员 & 单例模式命名规范化 editor-app: - EngineService: private static instance → _instance - EditorEngineSync: 所有私有成员添加 _ 前缀 - RuntimeResolver: 所有私有成员和方法添加 _ 前缀 - SettingsService: 所有私有成员和方法添加 _ 前缀 behavior-tree-editor: - GlobalBlackboardService: 所有私有成员和方法添加 _ 前缀 - NotificationService: private static instance → _instance - NodeRegistryService: 所有私有成员和方法添加 _ 前缀 - TreeStateAdapter: private static instance → _instance * fix(editor-runtime): 添加 editor-core 到 external 避免传递依赖问题 将 @esengine/editor-core 添加到 vite external 配置, 避免 editor-core → runtime-core → ecs-engine-bindgen 的传递依赖 被错误地打包进 editor-runtime.js,导致 CI 构建失败。 * fix(core): 修复空接口 lint 错误 将 IByteDanceMiniGameAPI、IAlipayMiniGameAPI、IBaiduMiniGameAPI 从空接口改为类型别名,修复 no-empty-object-type 规则报错
This commit is contained in:
@@ -73,7 +73,7 @@ export class Core {
|
||||
* @zh Core专用日志器
|
||||
* @en Core logger
|
||||
*/
|
||||
private static _logger = createLogger('Core');
|
||||
private static readonly _logger = createLogger('Core');
|
||||
|
||||
/**
|
||||
* @zh 调试模式标志,在调试模式下会启用额外的性能监控和错误检查
|
||||
@@ -204,7 +204,7 @@ export class Core {
|
||||
*/
|
||||
public static get services(): ServiceContainer {
|
||||
if (!this._instance) {
|
||||
throw new Error('Core实例未创建,请先调用Core.create()');
|
||||
throw new Error('Core instance not created, call Core.create() first | Core实例未创建,请先调用Core.create()');
|
||||
}
|
||||
return this._instance._serviceContainer;
|
||||
}
|
||||
@@ -238,7 +238,7 @@ export class Core {
|
||||
*/
|
||||
public static get pluginServices(): PluginServiceRegistry {
|
||||
if (!this._instance) {
|
||||
throw new Error('Core实例未创建,请先调用Core.create()');
|
||||
throw new Error('Core instance not created, call Core.create() first | Core实例未创建,请先调用Core.create()');
|
||||
}
|
||||
return this._instance._pluginServiceRegistry;
|
||||
}
|
||||
@@ -264,7 +264,7 @@ export class Core {
|
||||
*/
|
||||
public static get worldManager(): WorldManager {
|
||||
if (!this._instance) {
|
||||
throw new Error('Core实例未创建,请先调用Core.create()');
|
||||
throw new Error('Core instance not created, call Core.create() first | Core实例未创建,请先调用Core.create()');
|
||||
}
|
||||
return this._instance._worldManager;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
*/
|
||||
|
||||
import { createServiceToken } from './PluginServiceRegistry';
|
||||
import { createLogger } from '../Utils/Logger';
|
||||
|
||||
// ============================================================================
|
||||
// 接口定义 | Interface Definitions
|
||||
@@ -236,16 +237,18 @@ export class RuntimeModeService implements IRuntimeMode {
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly _logger = createLogger('RuntimeModeService');
|
||||
|
||||
/**
|
||||
* 通知模式变化
|
||||
* Notify mode change
|
||||
* @zh 通知模式变化
|
||||
* @en Notify mode change
|
||||
*/
|
||||
private _notifyChange(): void {
|
||||
for (const callback of this._callbacks) {
|
||||
try {
|
||||
callback(this);
|
||||
} catch (error) {
|
||||
console.error('[RuntimeModeService] Callback error:', error);
|
||||
RuntimeModeService._logger.error('Callback error:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export abstract class Component implements IComponent {
|
||||
* @zh 组件ID生成器,用于为每个组件分配唯一的ID
|
||||
* @en Component ID generator, used to assign unique IDs to each component
|
||||
*/
|
||||
private static idGenerator: number = 0;
|
||||
private static _idGenerator: number = 0;
|
||||
|
||||
/**
|
||||
* @zh 组件唯一标识符,在整个游戏生命周期中唯一
|
||||
@@ -81,7 +81,7 @@ export abstract class Component implements IComponent {
|
||||
* @en Create component instance, automatically assigns unique ID
|
||||
*/
|
||||
constructor() {
|
||||
this.id = Component.idGenerator++;
|
||||
this.id = Component._idGenerator++;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
import { ComponentType, GlobalComponentRegistry } from './ComponentStorage';
|
||||
import { getComponentTypeName } from '../Decorators';
|
||||
import { IScene } from '../IScene';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
|
||||
@@ -239,7 +240,8 @@ export class CommandBuffer {
|
||||
pending.adds.set(typeId, component);
|
||||
|
||||
if (this._debug) {
|
||||
logger.debug(`CommandBuffer: 延迟添加组件 ${component.constructor.name} 到实体 ${entity.name}`);
|
||||
const typeName = getComponentTypeName(component.constructor as ComponentType);
|
||||
logger.debug(`CommandBuffer: 延迟添加组件 ${typeName} 到实体 ${entity.name}`);
|
||||
}
|
||||
} else {
|
||||
// 旧模式
|
||||
@@ -435,9 +437,10 @@ export class CommandBuffer {
|
||||
entity.addComponent(component);
|
||||
commandCount++;
|
||||
} catch (error) {
|
||||
const typeName = getComponentTypeName(component.constructor as ComponentType);
|
||||
logger.error(`CommandBuffer: 添加组件失败`, {
|
||||
entity: entity.name,
|
||||
component: component.constructor.name,
|
||||
component: typeName,
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,24 +142,25 @@ interface ComponentUsageTracker {
|
||||
* 全局组件池管理器
|
||||
*/
|
||||
export class ComponentPoolManager {
|
||||
private static instance: ComponentPoolManager;
|
||||
private pools = new Map<string, ComponentPool<Component>>();
|
||||
private usageTracker = new Map<string, ComponentUsageTracker>();
|
||||
private static _instance: ComponentPoolManager;
|
||||
private _pools = new Map<string, ComponentPool<Component>>();
|
||||
private _usageTracker = new Map<string, ComponentUsageTracker>();
|
||||
|
||||
private autoCleanupInterval = 60000;
|
||||
private lastCleanupTime = 0;
|
||||
private _autoCleanupInterval = 60000;
|
||||
private _lastCleanupTime = 0;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): ComponentPoolManager {
|
||||
if (!ComponentPoolManager.instance) {
|
||||
ComponentPoolManager.instance = new ComponentPoolManager();
|
||||
if (!ComponentPoolManager._instance) {
|
||||
ComponentPoolManager._instance = new ComponentPoolManager();
|
||||
}
|
||||
return ComponentPoolManager.instance;
|
||||
return ComponentPoolManager._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册组件池
|
||||
* @zh 注册组件池
|
||||
* @en Register component pool
|
||||
*/
|
||||
registerPool<T extends Component>(
|
||||
componentName: string,
|
||||
@@ -168,9 +169,9 @@ export class ComponentPoolManager {
|
||||
maxSize?: number,
|
||||
minSize?: number
|
||||
): void {
|
||||
this.pools.set(componentName, new ComponentPool(createFn, resetFn, maxSize, minSize) as unknown as ComponentPool<Component>);
|
||||
this._pools.set(componentName, new ComponentPool(createFn, resetFn, maxSize, minSize) as unknown as ComponentPool<Component>);
|
||||
|
||||
this.usageTracker.set(componentName, {
|
||||
this._usageTracker.set(componentName, {
|
||||
createCount: 0,
|
||||
releaseCount: 0,
|
||||
lastAccessTime: Date.now()
|
||||
@@ -178,23 +179,25 @@ export class ComponentPoolManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件实例
|
||||
* @zh 获取组件实例
|
||||
* @en Acquire component instance
|
||||
*/
|
||||
acquireComponent<T extends Component>(componentName: string): T | null {
|
||||
const pool = this.pools.get(componentName);
|
||||
const pool = this._pools.get(componentName);
|
||||
|
||||
this.trackUsage(componentName, 'create');
|
||||
this._trackUsage(componentName, 'create');
|
||||
|
||||
return pool ? (pool.acquire() as T) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放组件实例
|
||||
* @zh 释放组件实例
|
||||
* @en Release component instance
|
||||
*/
|
||||
releaseComponent<T extends Component>(componentName: string, component: T): void {
|
||||
const pool = this.pools.get(componentName);
|
||||
const pool = this._pools.get(componentName);
|
||||
|
||||
this.trackUsage(componentName, 'release');
|
||||
this._trackUsage(componentName, 'release');
|
||||
|
||||
if (pool) {
|
||||
pool.release(component);
|
||||
@@ -202,10 +205,11 @@ export class ComponentPoolManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 追踪使用情况
|
||||
* @zh 追踪使用情况
|
||||
* @en Track usage
|
||||
*/
|
||||
private trackUsage(componentName: string, action: 'create' | 'release'): void {
|
||||
let tracker = this.usageTracker.get(componentName);
|
||||
private _trackUsage(componentName: string, action: 'create' | 'release'): void {
|
||||
let tracker = this._usageTracker.get(componentName);
|
||||
|
||||
if (!tracker) {
|
||||
tracker = {
|
||||
@@ -213,7 +217,7 @@ export class ComponentPoolManager {
|
||||
releaseCount: 0,
|
||||
lastAccessTime: Date.now()
|
||||
};
|
||||
this.usageTracker.set(componentName, tracker);
|
||||
this._usageTracker.set(componentName, tracker);
|
||||
}
|
||||
|
||||
if (action === 'create') {
|
||||
@@ -226,66 +230,72 @@ export class ComponentPoolManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动清理(定期调用)
|
||||
* @zh 自动清理(定期调用)
|
||||
* @en Auto cleanup (called periodically)
|
||||
*/
|
||||
public update(): void {
|
||||
const now = Date.now();
|
||||
|
||||
if (now - this.lastCleanupTime < this.autoCleanupInterval) {
|
||||
if (now - this._lastCleanupTime < this._autoCleanupInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [name, tracker] of this.usageTracker.entries()) {
|
||||
for (const [name, tracker] of this._usageTracker.entries()) {
|
||||
const inactive = now - tracker.lastAccessTime > 120000;
|
||||
|
||||
if (inactive) {
|
||||
const pool = this.pools.get(name);
|
||||
const pool = this._pools.get(name);
|
||||
if (pool) {
|
||||
pool.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.lastCleanupTime = now;
|
||||
this._lastCleanupTime = now;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热点组件列表
|
||||
* @zh 获取热点组件列表
|
||||
* @en Get hot components list
|
||||
*/
|
||||
public getHotComponents(threshold: number = 100): string[] {
|
||||
return Array.from(this.usageTracker.entries())
|
||||
return Array.from(this._usageTracker.entries())
|
||||
.filter(([_, tracker]) => tracker.createCount > threshold)
|
||||
.map(([name]) => name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热所有池
|
||||
* @zh 预热所有池
|
||||
* @en Prewarm all pools
|
||||
*/
|
||||
prewarmAll(count: number = 100): void {
|
||||
for (const pool of this.pools.values()) {
|
||||
for (const pool of this._pools.values()) {
|
||||
pool.prewarm(count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有池
|
||||
* @zh 清空所有池
|
||||
* @en Clear all pools
|
||||
*/
|
||||
clearAll(): void {
|
||||
for (const pool of this.pools.values()) {
|
||||
for (const pool of this._pools.values()) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置管理器
|
||||
* @zh 重置管理器
|
||||
* @en Reset manager
|
||||
*/
|
||||
reset(): void {
|
||||
this.pools.clear();
|
||||
this.usageTracker.clear();
|
||||
this._pools.clear();
|
||||
this._usageTracker.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局统计信息
|
||||
* @zh 获取全局统计信息
|
||||
* @en Get global stats
|
||||
*/
|
||||
getGlobalStats(): Array<{
|
||||
componentName: string;
|
||||
@@ -298,11 +308,11 @@ export class ComponentPoolManager {
|
||||
usage: ComponentUsageTracker | undefined;
|
||||
}> = [];
|
||||
|
||||
for (const [name, pool] of this.pools.entries()) {
|
||||
for (const [name, pool] of this._pools.entries()) {
|
||||
stats.push({
|
||||
componentName: name,
|
||||
poolStats: pool.getStats(),
|
||||
usage: this.usageTracker.get(name)
|
||||
usage: this._usageTracker.get(name)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -310,11 +320,12 @@ export class ComponentPoolManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池统计信息
|
||||
* @zh 获取池统计信息
|
||||
* @en Get pool stats
|
||||
*/
|
||||
getPoolStats(): Map<string, { available: number; maxSize: number }> {
|
||||
const stats = new Map();
|
||||
for (const [name, pool] of this.pools) {
|
||||
for (const [name, pool] of this._pools) {
|
||||
stats.set(name, {
|
||||
available: pool.getAvailableCount(),
|
||||
maxSize: pool.getMaxSize()
|
||||
@@ -324,11 +335,12 @@ export class ComponentPoolManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池利用率信息
|
||||
* @zh 获取池利用率信息
|
||||
* @en Get pool utilization info
|
||||
*/
|
||||
getPoolUtilization(): Map<string, { used: number; total: number; utilization: number }> {
|
||||
const utilization = new Map();
|
||||
for (const [name, pool] of this.pools) {
|
||||
for (const [name, pool] of this._pools) {
|
||||
const available = pool.getAvailableCount();
|
||||
const maxSize = pool.getMaxSize();
|
||||
const used = maxSize - available;
|
||||
@@ -344,10 +356,11 @@ export class ComponentPoolManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定组件的池利用率
|
||||
* @zh 获取指定组件的池利用率
|
||||
* @en Get component pool utilization
|
||||
*/
|
||||
getComponentUtilization(componentName: string): number {
|
||||
const pool = this.pools.get(componentName);
|
||||
const pool = this._pools.get(componentName);
|
||||
if (!pool) return 0;
|
||||
|
||||
const available = pool.getAvailableCount();
|
||||
|
||||
@@ -53,10 +53,11 @@ export class ComponentRegistry implements IComponentRegistry {
|
||||
// 检查是否使用了 @ECSComponent 装饰器
|
||||
if (!hasECSComponentDecorator(componentType) && !this._warnedComponents.has(componentType)) {
|
||||
this._warnedComponents.add(componentType);
|
||||
console.warn(
|
||||
`[ComponentRegistry] Component "${typeName}" is missing @ECSComponent decorator. ` +
|
||||
logger.warn(
|
||||
`Component "${typeName}" is missing @ECSComponent decorator. ` +
|
||||
`This may cause issues with serialization and code minification. ` +
|
||||
`Please add: @ECSComponent('${typeName}')`
|
||||
`Please add: @ECSComponent('${typeName}') | ` +
|
||||
`组件 "${typeName}" 缺少 @ECSComponent 装饰器,可能导致序列化和代码压缩问题`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -443,29 +443,33 @@ export class EventBus implements IEventBus {
|
||||
* 提供全局访问的事件总线
|
||||
*/
|
||||
export class GlobalEventBus {
|
||||
private static instance: EventBus;
|
||||
private static _instance: EventBus;
|
||||
|
||||
/**
|
||||
* 获取全局事件总线实例
|
||||
* @param debugMode 是否启用调试模式
|
||||
* @zh 获取全局事件总线实例
|
||||
* @en Get global event bus instance
|
||||
*
|
||||
* @param debugMode - @zh 是否启用调试模式 @en Whether to enable debug mode
|
||||
*/
|
||||
public static getInstance(debugMode: boolean = false): EventBus {
|
||||
if (!this.instance) {
|
||||
this.instance = new EventBus(debugMode);
|
||||
if (!this._instance) {
|
||||
this._instance = new EventBus(debugMode);
|
||||
}
|
||||
return this.instance;
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置全局事件总线实例
|
||||
* @param debugMode 是否启用调试模式
|
||||
* @zh 重置全局事件总线实例
|
||||
* @en Reset global event bus instance
|
||||
*
|
||||
* @param debugMode - @zh 是否启用调试模式 @en Whether to enable debug mode
|
||||
*/
|
||||
public static reset(debugMode: boolean = false): EventBus {
|
||||
if (this.instance) {
|
||||
this.instance.clear();
|
||||
if (this._instance) {
|
||||
this._instance.clear();
|
||||
}
|
||||
this.instance = new EventBus(debugMode);
|
||||
return this.instance;
|
||||
this._instance = new EventBus(debugMode);
|
||||
return this._instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TypedArrayTypeName
|
||||
} from './SoATypeRegistry';
|
||||
import { SoASerializer } from './SoASerializer';
|
||||
import type { IComponentTypeMetadata, ComponentTypeWithMetadata } from '../../Types';
|
||||
|
||||
// 重新导出类型,保持向后兼容
|
||||
export type { SupportedTypedArray, TypedArrayTypeName } from './SoATypeRegistry';
|
||||
@@ -13,170 +14,174 @@ export { SoATypeRegistry } from './SoATypeRegistry';
|
||||
export { SoASerializer } from './SoASerializer';
|
||||
|
||||
/**
|
||||
* 启用SoA优化装饰器
|
||||
* 默认关闭SoA,只有在大规模批量操作场景下才建议开启
|
||||
* @zh SoA 字段统计信息
|
||||
* @en SoA field statistics
|
||||
*/
|
||||
export interface ISoAFieldStats {
|
||||
size: number;
|
||||
capacity: number;
|
||||
type: string;
|
||||
memory: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh SoA 存储统计信息
|
||||
* @en SoA storage statistics
|
||||
*/
|
||||
export interface ISoAStorageStats {
|
||||
size: number;
|
||||
capacity: number;
|
||||
totalSlots: number;
|
||||
usedSlots: number;
|
||||
freeSlots: number;
|
||||
fragmentation: number;
|
||||
memoryUsage: number;
|
||||
fieldStats: Map<string, ISoAFieldStats>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 启用 SoA 优化装饰器 - 默认关闭,只在大规模批量操作场景下建议开启
|
||||
* @en Enable SoA optimization decorator - disabled by default, recommended only for large-scale batch operations
|
||||
*/
|
||||
export function EnableSoA<T extends ComponentType>(target: T): T {
|
||||
(target as any).__enableSoA = true;
|
||||
(target as ComponentType & IComponentTypeMetadata).__enableSoA = true;
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 64位浮点数装饰器
|
||||
* 标记字段使用Float64Array存储(更高精度但更多内存)
|
||||
* @zh 组件字段元数据键(仅 Set<string> 类型的字段)
|
||||
* @en Component field metadata keys (only Set<string> type fields)
|
||||
*/
|
||||
export function Float64(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__float64Fields) {
|
||||
target.constructor.__float64Fields = new Set();
|
||||
}
|
||||
target.constructor.__float64Fields.add(key);
|
||||
type ComponentFieldMetadataKey = Exclude<keyof IComponentTypeMetadata, '__enableSoA'>;
|
||||
|
||||
/**
|
||||
* @zh 装饰器目标原型接口
|
||||
* @en Decorator target prototype interface
|
||||
*/
|
||||
interface IDecoratorTarget {
|
||||
constructor: IComponentTypeMetadata & Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* 32位浮点数装饰器
|
||||
* 标记字段使用Float32Array存储(默认类型,平衡性能和精度)
|
||||
* @zh 辅助函数:获取或创建字段集合
|
||||
* @en Helper function: get or create field set
|
||||
*/
|
||||
export function Float32(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__float32Fields) {
|
||||
target.constructor.__float32Fields = new Set();
|
||||
function getOrCreateFieldSet(
|
||||
target: IDecoratorTarget,
|
||||
fieldName: ComponentFieldMetadataKey
|
||||
): Set<string> {
|
||||
const ctor = target.constructor as IComponentTypeMetadata;
|
||||
let fieldSet = ctor[fieldName];
|
||||
if (!fieldSet) {
|
||||
fieldSet = new Set<string>();
|
||||
ctor[fieldName] = fieldSet;
|
||||
}
|
||||
target.constructor.__float32Fields.add(key);
|
||||
return fieldSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 32位整数装饰器
|
||||
* 标记字段使用Int32Array存储(适用于整数值)
|
||||
* @zh 64位浮点数装饰器 - 标记字段使用 Float64Array 存储(更高精度但更多内存)
|
||||
* @en Float64 decorator - marks field to use Float64Array storage (higher precision but more memory)
|
||||
*/
|
||||
export function Int32(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__int32Fields) {
|
||||
target.constructor.__int32Fields = new Set();
|
||||
}
|
||||
target.constructor.__int32Fields.add(key);
|
||||
export function Float64(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__float64Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 32位无符号整数装饰器
|
||||
* 标记字段使用Uint32Array存储(适用于无符号整数,如ID、标志位等)
|
||||
* @zh 32位浮点数装饰器 - 标记字段使用 Float32Array 存储(默认类型,平衡性能和精度)
|
||||
* @en Float32 decorator - marks field to use Float32Array storage (default, balanced performance and precision)
|
||||
*/
|
||||
export function Uint32(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__uint32Fields) {
|
||||
target.constructor.__uint32Fields = new Set();
|
||||
}
|
||||
target.constructor.__uint32Fields.add(key);
|
||||
export function Float32(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__float32Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 16位整数装饰器
|
||||
* 标记字段使用Int16Array存储(适用于小范围整数)
|
||||
* @zh 32位整数装饰器 - 标记字段使用 Int32Array 存储(适用于整数值)
|
||||
* @en Int32 decorator - marks field to use Int32Array storage (for integer values)
|
||||
*/
|
||||
export function Int16(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__int16Fields) {
|
||||
target.constructor.__int16Fields = new Set();
|
||||
}
|
||||
target.constructor.__int16Fields.add(key);
|
||||
export function Int32(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__int32Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 16位无符号整数装饰器
|
||||
* 标记字段使用Uint16Array存储(适用于小范围无符号整数)
|
||||
* @zh 32位无符号整数装饰器 - 标记字段使用 Uint32Array 存储
|
||||
* @en Uint32 decorator - marks field to use Uint32Array storage
|
||||
*/
|
||||
export function Uint16(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__uint16Fields) {
|
||||
target.constructor.__uint16Fields = new Set();
|
||||
}
|
||||
target.constructor.__uint16Fields.add(key);
|
||||
export function Uint32(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__uint32Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 8位整数装饰器
|
||||
* 标记字段使用Int8Array存储(适用于很小的整数值)
|
||||
* @zh 16位整数装饰器 - 标记字段使用 Int16Array 存储
|
||||
* @en Int16 decorator - marks field to use Int16Array storage
|
||||
*/
|
||||
export function Int8(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__int8Fields) {
|
||||
target.constructor.__int8Fields = new Set();
|
||||
}
|
||||
target.constructor.__int8Fields.add(key);
|
||||
export function Int16(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__int16Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 8位无符号整数装饰器
|
||||
* 标记字段使用Uint8Array存储(适用于字节值、布尔标志等)
|
||||
* @zh 16位无符号整数装饰器 - 标记字段使用 Uint16Array 存储
|
||||
* @en Uint16 decorator - marks field to use Uint16Array storage
|
||||
*/
|
||||
export function Uint8(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__uint8Fields) {
|
||||
target.constructor.__uint8Fields = new Set();
|
||||
}
|
||||
target.constructor.__uint8Fields.add(key);
|
||||
export function Uint16(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__uint16Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 8位夹紧整数装饰器
|
||||
* 标记字段使用Uint8ClampedArray存储(适用于颜色值等需要夹紧的数据)
|
||||
* @zh 8位整数装饰器 - 标记字段使用 Int8Array 存储
|
||||
* @en Int8 decorator - marks field to use Int8Array storage
|
||||
*/
|
||||
export function Uint8Clamped(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__uint8ClampedFields) {
|
||||
target.constructor.__uint8ClampedFields = new Set();
|
||||
}
|
||||
target.constructor.__uint8ClampedFields.add(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 序列化Map装饰器
|
||||
* 标记Map字段需要序列化/反序列化存储
|
||||
*/
|
||||
export function SerializeMap(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__serializeMapFields) {
|
||||
target.constructor.__serializeMapFields = new Set();
|
||||
}
|
||||
target.constructor.__serializeMapFields.add(key);
|
||||
export function Int8(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__int8Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化Set装饰器
|
||||
* 标记Set字段需要序列化/反序列化存储
|
||||
* @zh 8位无符号整数装饰器 - 标记字段使用 Uint8Array 存储
|
||||
* @en Uint8 decorator - marks field to use Uint8Array storage
|
||||
*/
|
||||
export function SerializeSet(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__serializeSetFields) {
|
||||
target.constructor.__serializeSetFields = new Set();
|
||||
}
|
||||
target.constructor.__serializeSetFields.add(key);
|
||||
export function Uint8(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__uint8Fields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化Array装饰器
|
||||
* 标记Array字段需要序列化/反序列化存储
|
||||
* @zh 8位夹紧整数装饰器 - 标记字段使用 Uint8ClampedArray 存储(适用于颜色值)
|
||||
* @en Uint8Clamped decorator - marks field to use Uint8ClampedArray storage (for color values)
|
||||
*/
|
||||
export function SerializeArray(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__serializeArrayFields) {
|
||||
target.constructor.__serializeArrayFields = new Set();
|
||||
}
|
||||
target.constructor.__serializeArrayFields.add(key);
|
||||
export function Uint8Clamped(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__uint8ClampedFields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝装饰器
|
||||
* 标记字段需要深拷贝处理(适用于嵌套对象)
|
||||
* @zh 序列化 Map 装饰器 - 标记 Map 字段需要序列化/反序列化存储
|
||||
* @en SerializeMap decorator - marks Map field for serialization/deserialization
|
||||
*/
|
||||
export function DeepCopy(target: any, propertyKey: string | symbol): void {
|
||||
const key = String(propertyKey);
|
||||
if (!target.constructor.__deepCopyFields) {
|
||||
target.constructor.__deepCopyFields = new Set();
|
||||
}
|
||||
target.constructor.__deepCopyFields.add(key);
|
||||
export function SerializeMap(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__serializeMapFields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 序列化 Set 装饰器 - 标记 Set 字段需要序列化/反序列化存储
|
||||
* @en SerializeSet decorator - marks Set field for serialization/deserialization
|
||||
*/
|
||||
export function SerializeSet(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__serializeSetFields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 序列化 Array 装饰器 - 标记 Array 字段需要序列化/反序列化存储
|
||||
* @en SerializeArray decorator - marks Array field for serialization/deserialization
|
||||
*/
|
||||
export function SerializeArray(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__serializeArrayFields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 深拷贝装饰器 - 标记字段需要深拷贝处理(适用于嵌套对象)
|
||||
* @en DeepCopy decorator - marks field for deep copy handling (for nested objects)
|
||||
*/
|
||||
export function DeepCopy(target: object, propertyKey: string | symbol): void {
|
||||
getOrCreateFieldSet(target as IDecoratorTarget, '__deepCopyFields').add(String(propertyKey));
|
||||
}
|
||||
|
||||
|
||||
@@ -186,8 +191,8 @@ export function DeepCopy(target: any, propertyKey: string | symbol): void {
|
||||
*/
|
||||
export class SoAStorage<T extends Component> {
|
||||
private fields = new Map<string, SupportedTypedArray>();
|
||||
private stringFields = new Map<string, string[]>();
|
||||
private serializedFields = new Map<string, string[]>();
|
||||
private stringFields = new Map<string, Array<string | undefined>>();
|
||||
private serializedFields = new Map<string, Array<string | undefined>>();
|
||||
private complexFields = new Map<number, Map<string, unknown>>();
|
||||
private entityToIndex = new Map<number, number>();
|
||||
private indexToEntity: number[] = [];
|
||||
@@ -318,30 +323,29 @@ export class SoAStorage<T extends Component> {
|
||||
|
||||
private updateComponentAtIndex(index: number, component: T): void {
|
||||
const entityId = this.indexToEntity[index]!;
|
||||
const complexFieldMap = new Map<string, any>();
|
||||
const highPrecisionFields = (this.type as any).__highPrecisionFields || new Set();
|
||||
const serializeMapFields = (this.type as any).__serializeMapFields || new Set();
|
||||
const serializeSetFields = (this.type as any).__serializeSetFields || new Set();
|
||||
const serializeArrayFields = (this.type as any).__serializeArrayFields || new Set();
|
||||
const deepCopyFields = (this.type as any).__deepCopyFields || new Set();
|
||||
const complexFieldMap = new Map<string, unknown>();
|
||||
const typeWithMeta = this.type as ComponentTypeWithMetadata<T>;
|
||||
const highPrecisionFields = typeWithMeta.__highPrecisionFields || new Set<string>();
|
||||
const serializeMapFields = typeWithMeta.__serializeMapFields || new Set<string>();
|
||||
const serializeSetFields = typeWithMeta.__serializeSetFields || new Set<string>();
|
||||
const serializeArrayFields = typeWithMeta.__serializeArrayFields || new Set<string>();
|
||||
const deepCopyFields = typeWithMeta.__deepCopyFields || new Set<string>();
|
||||
|
||||
// 处理所有字段
|
||||
const componentRecord = component as Record<string, unknown>;
|
||||
for (const key in component) {
|
||||
if (component.hasOwnProperty(key) && key !== 'id') {
|
||||
const value = (component as any)[key];
|
||||
if (Object.prototype.hasOwnProperty.call(component, key) && key !== 'id') {
|
||||
const value = componentRecord[key];
|
||||
const type = typeof value;
|
||||
|
||||
if (type === 'number') {
|
||||
const numValue = value as number;
|
||||
if (highPrecisionFields.has(key) || !this.fields.has(key)) {
|
||||
// 标记为高精度或未在TypedArray中的数值作为复杂对象存储
|
||||
complexFieldMap.set(key, value);
|
||||
complexFieldMap.set(key, numValue);
|
||||
} else {
|
||||
// 存储到TypedArray
|
||||
const array = this.fields.get(key)!;
|
||||
array[index] = value;
|
||||
array[index] = numValue;
|
||||
}
|
||||
} else if (type === 'boolean' && this.fields.has(key)) {
|
||||
// 布尔值存储到TypedArray
|
||||
const array = this.fields.get(key)!;
|
||||
array[index] = value ? 1 : 0;
|
||||
} else if (this.stringFields.has(key)) {
|
||||
@@ -526,7 +530,8 @@ export class SoAStorage<T extends Component> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件的快照副本(用于序列化等需要独立副本的场景)
|
||||
* @zh 获取组件的快照副本(用于序列化等需要独立副本的场景)
|
||||
* @en Get a snapshot copy of the component (for serialization scenarios)
|
||||
*/
|
||||
public getComponentSnapshot(entityId: number): T | null {
|
||||
const index = this.entityToIndex.get(entityId);
|
||||
@@ -534,32 +539,26 @@ export class SoAStorage<T extends Component> {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 需要 any 因为要动态写入泛型 T 的属性
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const component = new this.type() as any;
|
||||
const component = new this.type();
|
||||
const componentRecord = component as unknown as Record<string, unknown>;
|
||||
|
||||
// 恢复数值字段
|
||||
for (const [fieldName, array] of this.fields.entries()) {
|
||||
const value = array[index];
|
||||
const fieldType = this.getFieldType(fieldName);
|
||||
|
||||
if (fieldType === 'boolean') {
|
||||
component[fieldName] = value === 1;
|
||||
} else {
|
||||
component[fieldName] = value;
|
||||
}
|
||||
componentRecord[fieldName] = fieldType === 'boolean' ? value === 1 : value;
|
||||
}
|
||||
|
||||
// 恢复字符串字段
|
||||
for (const [fieldName, stringArray] of this.stringFields.entries()) {
|
||||
component[fieldName] = stringArray[index];
|
||||
componentRecord[fieldName] = stringArray[index];
|
||||
}
|
||||
|
||||
// 恢复序列化字段
|
||||
for (const [fieldName, serializedArray] of this.serializedFields.entries()) {
|
||||
const serialized = serializedArray[index];
|
||||
if (serialized) {
|
||||
component[fieldName] = SoASerializer.deserialize(serialized, fieldName, {
|
||||
componentRecord[fieldName] = SoASerializer.deserialize(serialized, fieldName, {
|
||||
isMap: this.serializeMapFields.has(fieldName),
|
||||
isSet: this.serializeSetFields.has(fieldName),
|
||||
isArray: this.serializeArrayFields.has(fieldName)
|
||||
@@ -571,11 +570,11 @@ export class SoAStorage<T extends Component> {
|
||||
const complexFieldMap = this.complexFields.get(entityId);
|
||||
if (complexFieldMap) {
|
||||
for (const [fieldName, value] of complexFieldMap.entries()) {
|
||||
component[fieldName] = value;
|
||||
componentRecord[fieldName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return component as T;
|
||||
return component;
|
||||
}
|
||||
|
||||
private getFieldType(fieldName: string): string {
|
||||
@@ -673,14 +672,14 @@ export class SoAStorage<T extends Component> {
|
||||
// 重置字符串字段数组
|
||||
for (const stringArray of this.stringFields.values()) {
|
||||
for (let i = 0; i < stringArray.length; i++) {
|
||||
stringArray[i] = undefined as any;
|
||||
stringArray[i] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置序列化字段数组
|
||||
for (const serializedArray of this.serializedFields.values()) {
|
||||
for (let i = 0; i < serializedArray.length; i++) {
|
||||
serializedArray[i] = undefined as any;
|
||||
serializedArray[i] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -740,9 +739,13 @@ export class SoAStorage<T extends Component> {
|
||||
this._size = activeEntries.length;
|
||||
}
|
||||
|
||||
public getStats(): any {
|
||||
/**
|
||||
* @zh 获取 SoA 存储统计信息
|
||||
* @en Get SoA storage statistics
|
||||
*/
|
||||
public getStats(): ISoAStorageStats {
|
||||
let totalMemory = 0;
|
||||
const fieldStats = new Map<string, any>();
|
||||
const fieldStats = new Map<string, ISoAFieldStats>();
|
||||
|
||||
for (const [fieldName, array] of this.fields.entries()) {
|
||||
const typeName = SoATypeRegistry.getTypeName(array);
|
||||
@@ -761,7 +764,9 @@ export class SoAStorage<T extends Component> {
|
||||
return {
|
||||
size: this._size,
|
||||
capacity: this._capacity,
|
||||
usedSlots: this._size, // 兼容原测试
|
||||
totalSlots: this._capacity,
|
||||
usedSlots: this._size,
|
||||
freeSlots: this._capacity - this._size,
|
||||
fragmentation: this.freeIndices.length / this._capacity,
|
||||
memoryUsage: totalMemory,
|
||||
fieldStats: fieldStats
|
||||
|
||||
@@ -27,8 +27,11 @@
|
||||
*/
|
||||
|
||||
import { SystemDependencyGraph, CycleDependencyError, type SystemDependencyInfo } from './SystemDependencyGraph';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
import type { EntitySystem } from '../Systems/EntitySystem';
|
||||
|
||||
const logger = createLogger('SystemScheduler');
|
||||
|
||||
export { CycleDependencyError };
|
||||
|
||||
/**
|
||||
@@ -293,7 +296,7 @@ export class SystemScheduler {
|
||||
throw error;
|
||||
}
|
||||
// 其他错误回退到 updateOrder 排序
|
||||
console.warn('[SystemScheduler] 拓扑排序失败,回退到 updateOrder 排序', error);
|
||||
logger.warn('Topological sort failed, falling back to updateOrder | 拓扑排序失败,回退到 updateOrder 排序', error);
|
||||
return this.fallbackSort(systems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ export class Scene implements IScene {
|
||||
/**
|
||||
* 日志记录器
|
||||
*/
|
||||
private readonly logger: ReturnType<typeof createLogger>;
|
||||
private readonly _logger: ReturnType<typeof createLogger>;
|
||||
|
||||
/**
|
||||
* 性能监控器缓存
|
||||
@@ -298,12 +298,12 @@ export class Scene implements IScene {
|
||||
return this._systemScheduler.getAllSortedSystems(systems);
|
||||
} catch (error) {
|
||||
if (error instanceof CycleDependencyError) {
|
||||
this.logger.error(
|
||||
this._logger.error(
|
||||
`[Scene] 系统存在循环依赖,回退到 updateOrder 排序 | Cycle dependency detected, falling back to updateOrder sort`,
|
||||
error.involvedNodes
|
||||
);
|
||||
} else {
|
||||
this.logger.error(`[Scene] 系统排序失败 | System sorting failed`, error);
|
||||
this._logger.error(`[Scene] 系统排序失败 | System sorting failed`, error);
|
||||
}
|
||||
return this._sortSystemsByUpdateOrder(systems);
|
||||
}
|
||||
@@ -395,7 +395,7 @@ export class Scene implements IScene {
|
||||
this.referenceTracker = new ReferenceTracker();
|
||||
this.handleManager = new EntityHandleManager();
|
||||
this._services = new ServiceContainer();
|
||||
this.logger = createLogger('Scene');
|
||||
this._logger = createLogger('Scene');
|
||||
this._maxErrorCount = config?.maxSystemErrorCount ?? 10;
|
||||
|
||||
if (config?.name) {
|
||||
@@ -467,7 +467,7 @@ export class Scene implements IScene {
|
||||
try {
|
||||
callback();
|
||||
} catch (error) {
|
||||
this.logger.error('Error executing deferred component callback:', error);
|
||||
this._logger.error('Error executing deferred component callback:', error);
|
||||
}
|
||||
}
|
||||
this._deferredComponentCallbacks = [];
|
||||
@@ -580,7 +580,7 @@ export class Scene implements IScene {
|
||||
try {
|
||||
system.flushCommands();
|
||||
} catch (error) {
|
||||
this.logger.error(`Error flushing commands for system ${system.systemName}:`, error);
|
||||
this._logger.error(`Error flushing commands for system ${system.systemName}:`, error);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -601,15 +601,16 @@ export class Scene implements IScene {
|
||||
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}]:`,
|
||||
const name = system.systemName;
|
||||
this._logger.error(
|
||||
`Error in system ${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)`
|
||||
this._logger.error(
|
||||
`System ${name} has been disabled due to excessive errors (${errorCount} errors)`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1086,7 +1087,7 @@ export class Scene implements IScene {
|
||||
|
||||
if (this._services.isRegistered(constructor)) {
|
||||
const existingSystem = this._services.resolve(constructor) as T;
|
||||
this.logger.debug(`System ${constructor.name} already registered, returning existing instance`);
|
||||
this._logger.debug(`System ${constructor.name} already registered, returning existing instance`);
|
||||
return existingSystem;
|
||||
}
|
||||
|
||||
@@ -1102,10 +1103,10 @@ export class Scene implements IScene {
|
||||
if (this._services.isRegistered(constructor)) {
|
||||
const existingSystem = this._services.resolve(constructor);
|
||||
if (existingSystem === system) {
|
||||
this.logger.debug(`System ${constructor.name} instance already registered, returning it`);
|
||||
this._logger.debug(`System ${constructor.name} instance already registered, returning it`);
|
||||
return system;
|
||||
} else {
|
||||
this.logger.warn(
|
||||
this._logger.warn(
|
||||
`Attempting to register a different instance of ${constructor.name}, ` +
|
||||
'but type is already registered. Returning existing instance.'
|
||||
);
|
||||
@@ -1138,7 +1139,7 @@ export class Scene implements IScene {
|
||||
|
||||
system.initialize();
|
||||
|
||||
this.logger.debug(`System ${constructor.name} registered and initialized`);
|
||||
this._logger.debug(`System ${constructor.name} registered and initialized`);
|
||||
|
||||
return system;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ import { ComponentType } from '../Core/ComponentStorage';
|
||||
import { getComponentTypeName, isEntityRefProperty } from '../Decorators';
|
||||
import { getSerializationMetadata } from './SerializationDecorators';
|
||||
import { ValueSerializer, SerializableValue } from './ValueSerializer';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
import type { Entity } from '../Entity';
|
||||
import type { SerializationContext, SerializedEntityRef } from './SerializationContext';
|
||||
|
||||
const logger = createLogger('ComponentSerializer');
|
||||
|
||||
export type { SerializableValue } from './ValueSerializer';
|
||||
|
||||
export type SerializedComponent = {
|
||||
@@ -57,13 +60,13 @@ export class ComponentSerializer {
|
||||
): Component | null {
|
||||
const componentClass = componentRegistry.get(serializedData.type);
|
||||
if (!componentClass) {
|
||||
console.warn(`Component type not found: ${serializedData.type}`);
|
||||
logger.warn(`Component type not found: ${serializedData.type} | 未找到组件类型: ${serializedData.type}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const metadata = getSerializationMetadata(componentClass);
|
||||
if (!metadata) {
|
||||
console.warn(`Component ${serializedData.type} is not serializable`);
|
||||
logger.warn(`Component ${serializedData.type} is not serializable | 组件 ${serializedData.type} 不可序列化`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ import { HierarchySystem } from '../Systems/HierarchySystem';
|
||||
import { HierarchyComponent } from '../Components/HierarchyComponent';
|
||||
import { SerializationContext } from './SerializationContext';
|
||||
import { ValueSerializer, SerializableValue } from './ValueSerializer';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
|
||||
const logger = createLogger('SceneSerializer');
|
||||
|
||||
/**
|
||||
* 场景序列化格式
|
||||
@@ -309,9 +312,10 @@ export class SceneSerializer {
|
||||
const unresolvedCount = context.getUnresolvedCount();
|
||||
|
||||
if (unresolvedCount > 0) {
|
||||
console.warn(
|
||||
`[SceneSerializer] ${unresolvedCount} EntityRef(s) could not be resolved. ` +
|
||||
`Resolved: ${resolvedCount}, Total pending: ${context.getPendingCount()}`
|
||||
logger.warn(
|
||||
`${unresolvedCount} EntityRef(s) could not be resolved. ` +
|
||||
`Resolved: ${resolvedCount}, Total pending: ${context.getPendingCount()} | ` +
|
||||
`${unresolvedCount} 个实体引用无法解析`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -330,7 +334,7 @@ export class SceneSerializer {
|
||||
// 如果有异步的 onDeserialized,在后台执行
|
||||
if (deserializedPromises.length > 0) {
|
||||
Promise.all(deserializedPromises).catch(error => {
|
||||
console.error('Error in onDeserialized:', error);
|
||||
logger.error('Error in onDeserialized | onDeserialized 执行错误:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -349,7 +353,8 @@ export class SceneSerializer {
|
||||
promises.push(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error calling onDeserialized on component ${component.constructor.name}:`, error);
|
||||
const typeName = getComponentTypeName(component.constructor as ComponentType);
|
||||
logger.error(`Error calling onDeserialized on component ${typeName} | 调用组件 ${typeName} 的 onDeserialized 时出错:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
import { SerializedComponent } from './ComponentSerializer';
|
||||
import { SerializedScene } from './SceneSerializer';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
|
||||
const logger = createLogger('VersionMigration');
|
||||
|
||||
/**
|
||||
* 组件迁移函数
|
||||
@@ -123,7 +126,7 @@ export class VersionMigrationManager {
|
||||
|
||||
const migrations = this.componentMigrations.get(component.type);
|
||||
if (!migrations) {
|
||||
console.warn(`No migration path found for component ${component.type}`);
|
||||
logger.warn(`No migration path found for component ${component.type} | 未找到组件 ${component.type} 的迁移路径`);
|
||||
return component;
|
||||
}
|
||||
|
||||
@@ -135,8 +138,9 @@ export class VersionMigrationManager {
|
||||
const migration = migrations.get(version);
|
||||
|
||||
if (!migration) {
|
||||
console.warn(
|
||||
`Missing migration from version ${version} to ${version + 1} for ${component.type}`
|
||||
logger.warn(
|
||||
`Missing migration from version ${version} to ${version + 1} for ${component.type} | ` +
|
||||
`缺少组件 ${component.type} 从版本 ${version} 到 ${version + 1} 的迁移`
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -171,7 +175,7 @@ export class VersionMigrationManager {
|
||||
const migration = this.sceneMigrations.get(version);
|
||||
|
||||
if (!migration) {
|
||||
console.warn(`Missing scene migration from version ${version} to ${version + 1}`);
|
||||
logger.warn(`Missing scene migration from version ${version} to ${version + 1} | 缺少场景从版本 ${version} 到 ${version + 1} 的迁移`);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { PlatformDetectionResult } from './IPlatformAdapter';
|
||||
import { getGlobalWithMiniGame, type IGlobalThisWithMiniGame } from '../Types';
|
||||
|
||||
/**
|
||||
* 平台检测器
|
||||
* 自动检测当前运行环境并返回对应的平台信息
|
||||
* @zh 平台检测器 - 自动检测当前运行环境并返回对应的平台信息
|
||||
* @en Platform Detector - Automatically detect the current runtime environment
|
||||
*/
|
||||
export class PlatformDetector {
|
||||
private static readonly miniGameGlobals: IGlobalThisWithMiniGame = getGlobalWithMiniGame();
|
||||
/**
|
||||
* 检测当前平台
|
||||
*/
|
||||
@@ -104,26 +106,24 @@ export class PlatformDetector {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为微信小游戏环境
|
||||
* @zh 检测是否为微信小游戏环境
|
||||
* @en Check if running in WeChat Mini Game environment
|
||||
*/
|
||||
private static isWeChatMiniGame(): boolean {
|
||||
// 检查wx全局对象
|
||||
if (typeof (globalThis as any).wx !== 'undefined') {
|
||||
const wx = (globalThis as any).wx;
|
||||
// 检查微信小游戏特有的API
|
||||
const wx = this.miniGameGlobals.wx;
|
||||
if (wx) {
|
||||
return !!(wx.getSystemInfo && wx.createCanvas && wx.createImage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为字节跳动小游戏环境
|
||||
* @zh 检测是否为字节跳动小游戏环境
|
||||
* @en Check if running in ByteDance Mini Game environment
|
||||
*/
|
||||
private static isByteDanceMiniGame(): boolean {
|
||||
// 检查tt全局对象
|
||||
if (typeof (globalThis as any).tt !== 'undefined') {
|
||||
const tt = (globalThis as any).tt;
|
||||
// 检查字节跳动小游戏特有的API
|
||||
const tt = this.miniGameGlobals.tt;
|
||||
if (tt) {
|
||||
return !!(tt.getSystemInfo && tt.createCanvas && tt.createImage);
|
||||
}
|
||||
return false;
|
||||
@@ -152,26 +152,24 @@ export class PlatformDetector {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为支付宝小游戏环境
|
||||
* @zh 检测是否为支付宝小游戏环境
|
||||
* @en Check if running in Alipay Mini Game environment
|
||||
*/
|
||||
private static isAlipayMiniGame(): boolean {
|
||||
// 检查my全局对象
|
||||
if (typeof (globalThis as any).my !== 'undefined') {
|
||||
const my = (globalThis as any).my;
|
||||
// 检查支付宝小游戏特有的API
|
||||
const my = this.miniGameGlobals.my;
|
||||
if (my) {
|
||||
return !!(my.getSystemInfo && my.createCanvas);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为百度小游戏环境
|
||||
* @zh 检测是否为百度小游戏环境
|
||||
* @en Check if running in Baidu Mini Game environment
|
||||
*/
|
||||
private static isBaiduMiniGame(): boolean {
|
||||
// 检查swan全局对象
|
||||
if (typeof (globalThis as any).swan !== 'undefined') {
|
||||
const swan = (globalThis as any).swan;
|
||||
// 检查百度小游戏特有的API
|
||||
const swan = this.miniGameGlobals.swan;
|
||||
if (swan) {
|
||||
return !!(swan.getSystemInfo && swan.createCanvas);
|
||||
}
|
||||
return false;
|
||||
@@ -229,27 +227,26 @@ export class PlatformDetector {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详细的环境信息(用于调试)
|
||||
* @zh 获取详细的环境信息(用于调试)
|
||||
* @en Get detailed environment information for debugging
|
||||
*/
|
||||
public static getDetailedInfo(): Record<string, any> {
|
||||
const info: Record<string, any> = {};
|
||||
public static getDetailedInfo(): Record<string, unknown> {
|
||||
const info: Record<string, unknown> = {};
|
||||
const globals = this.miniGameGlobals;
|
||||
|
||||
// 基础检测
|
||||
info['userAgent'] = typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown';
|
||||
info['platform'] = typeof navigator !== 'undefined' ? navigator.platform : 'unknown';
|
||||
|
||||
// 全局对象检测
|
||||
info['globalObjects'] = {
|
||||
window: typeof window !== 'undefined',
|
||||
document: typeof document !== 'undefined',
|
||||
navigator: typeof navigator !== 'undefined',
|
||||
wx: typeof (globalThis as any).wx !== 'undefined',
|
||||
tt: typeof (globalThis as any).tt !== 'undefined',
|
||||
my: typeof (globalThis as any).my !== 'undefined',
|
||||
swan: typeof (globalThis as any).swan !== 'undefined'
|
||||
wx: globals.wx !== undefined,
|
||||
tt: globals.tt !== undefined,
|
||||
my: globals.my !== undefined,
|
||||
swan: globals.swan !== undefined
|
||||
};
|
||||
|
||||
// Worker相关检测
|
||||
info['workerSupport'] = {
|
||||
Worker: typeof Worker !== 'undefined',
|
||||
SharedWorker: typeof SharedWorker !== 'undefined',
|
||||
@@ -258,13 +255,11 @@ export class PlatformDetector {
|
||||
crossOriginIsolated: typeof self !== 'undefined' ? self.crossOriginIsolated : false
|
||||
};
|
||||
|
||||
// 性能相关检测
|
||||
info['performance'] = {
|
||||
performanceNow: typeof performance !== 'undefined' && typeof performance.now === 'function',
|
||||
hardwareConcurrency: typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : undefined
|
||||
};
|
||||
|
||||
// 其他API检测
|
||||
info['apiSupport'] = {
|
||||
Blob: typeof Blob !== 'undefined',
|
||||
URL: typeof URL !== 'undefined',
|
||||
|
||||
@@ -6,40 +6,43 @@ import { createLogger, type ILogger } from '../Utils/Logger';
|
||||
* 用户需要手动注册平台适配器
|
||||
*/
|
||||
export class PlatformManager {
|
||||
private static instance: PlatformManager;
|
||||
private adapter: IPlatformAdapter | null = null;
|
||||
private readonly logger: ILogger;
|
||||
private static _instance: PlatformManager;
|
||||
private _adapter: IPlatformAdapter | null = null;
|
||||
private readonly _logger: ILogger;
|
||||
|
||||
private constructor() {
|
||||
this.logger = createLogger('PlatformManager');
|
||||
this._logger = createLogger('PlatformManager');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
* @zh 获取单例实例
|
||||
* @en Get singleton instance
|
||||
*/
|
||||
public static getInstance(): PlatformManager {
|
||||
if (!PlatformManager.instance) {
|
||||
PlatformManager.instance = new PlatformManager();
|
||||
if (!PlatformManager._instance) {
|
||||
PlatformManager._instance = new PlatformManager();
|
||||
}
|
||||
return PlatformManager.instance;
|
||||
return PlatformManager._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前平台适配器
|
||||
* @zh 获取当前平台适配器
|
||||
* @en Get current platform adapter
|
||||
*/
|
||||
public getAdapter(): IPlatformAdapter {
|
||||
if (!this.adapter) {
|
||||
if (!this._adapter) {
|
||||
throw new Error('平台适配器未注册,请调用 registerAdapter() 注册适配器');
|
||||
}
|
||||
return this.adapter;
|
||||
return this._adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册平台适配器
|
||||
* @zh 注册平台适配器
|
||||
* @en Register platform adapter
|
||||
*/
|
||||
public registerAdapter(adapter: IPlatformAdapter): void {
|
||||
this.adapter = adapter;
|
||||
this.logger.info(`平台适配器已注册: ${adapter.name}`, {
|
||||
this._adapter = adapter;
|
||||
this._logger.info(`平台适配器已注册: ${adapter.name}`, {
|
||||
name: adapter.name,
|
||||
version: adapter.version,
|
||||
supportsWorker: adapter.isWorkerSupported(),
|
||||
@@ -49,37 +52,40 @@ export class PlatformManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已注册适配器
|
||||
* @zh 检查是否已注册适配器
|
||||
* @en Check if adapter is registered
|
||||
*/
|
||||
public hasAdapter(): boolean {
|
||||
return this.adapter !== null;
|
||||
return this._adapter !== null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取平台适配器信息(用于调试)
|
||||
* @zh 获取平台适配器信息(用于调试)
|
||||
* @en Get platform adapter info (for debugging)
|
||||
*/
|
||||
public getAdapterInfo(): any {
|
||||
return this.adapter ? {
|
||||
name: this.adapter.name,
|
||||
version: this.adapter.version,
|
||||
config: this.adapter.getPlatformConfig()
|
||||
return this._adapter ? {
|
||||
name: this._adapter.name,
|
||||
version: this._adapter.version,
|
||||
config: this._adapter.getPlatformConfig()
|
||||
} : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前平台是否支持特定功能
|
||||
* @zh 检查当前平台是否支持特定功能
|
||||
* @en Check if current platform supports specific feature
|
||||
*/
|
||||
public supportsFeature(feature: 'worker' | 'shared-array-buffer' | 'transferable-objects' | 'module-worker'): boolean {
|
||||
if (!this.adapter) return false;
|
||||
if (!this._adapter) return false;
|
||||
|
||||
const config = this.adapter.getPlatformConfig();
|
||||
const config = this._adapter.getPlatformConfig();
|
||||
|
||||
switch (feature) {
|
||||
case 'worker':
|
||||
return this.adapter.isWorkerSupported();
|
||||
return this._adapter.isWorkerSupported();
|
||||
case 'shared-array-buffer':
|
||||
return this.adapter.isSharedArrayBufferSupported();
|
||||
return this._adapter.isSharedArrayBufferSupported();
|
||||
case 'transferable-objects':
|
||||
return config.supportsTransferableObjects;
|
||||
case 'module-worker':
|
||||
@@ -90,8 +96,11 @@ export class PlatformManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础的Worker配置信息(不做自动决策)
|
||||
* 用户应该根据自己的业务需求来配置Worker参数
|
||||
* @zh 获取基础的Worker配置信息(不做自动决策)
|
||||
* @en Get basic Worker configuration (no auto-decision)
|
||||
*
|
||||
* @zh 用户应该根据自己的业务需求来配置Worker参数
|
||||
* @en Users should configure Worker parameters based on their business requirements
|
||||
*/
|
||||
public getBasicWorkerConfig(): {
|
||||
platformSupportsWorker: boolean;
|
||||
@@ -99,7 +108,7 @@ export class PlatformManager {
|
||||
platformMaxWorkerCount: number;
|
||||
platformLimitations: any;
|
||||
} {
|
||||
if (!this.adapter) {
|
||||
if (!this._adapter) {
|
||||
return {
|
||||
platformSupportsWorker: false,
|
||||
platformSupportsSharedArrayBuffer: false,
|
||||
@@ -108,30 +117,29 @@ export class PlatformManager {
|
||||
};
|
||||
}
|
||||
|
||||
const config = this.adapter.getPlatformConfig();
|
||||
const config = this._adapter.getPlatformConfig();
|
||||
|
||||
return {
|
||||
platformSupportsWorker: this.adapter.isWorkerSupported(),
|
||||
platformSupportsSharedArrayBuffer: this.adapter.isSharedArrayBufferSupported(),
|
||||
platformSupportsWorker: this._adapter.isWorkerSupported(),
|
||||
platformSupportsSharedArrayBuffer: this._adapter.isSharedArrayBufferSupported(),
|
||||
platformMaxWorkerCount: config.maxWorkerCount,
|
||||
platformLimitations: config.limitations || {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步获取完整的平台配置信息(包含性能信息)
|
||||
* @zh 异步获取完整的平台配置信息(包含性能信息)
|
||||
* @en Async get full platform configuration (includes performance info)
|
||||
*/
|
||||
public async getFullPlatformConfig(): Promise<any> {
|
||||
if (!this.adapter) {
|
||||
if (!this._adapter) {
|
||||
throw new Error('平台适配器未注册');
|
||||
}
|
||||
|
||||
// 如果适配器支持异步获取配置,使用异步方法
|
||||
if (typeof this.adapter.getPlatformConfigAsync === 'function') {
|
||||
return await this.adapter.getPlatformConfigAsync();
|
||||
if (typeof this._adapter.getPlatformConfigAsync === 'function') {
|
||||
return await this._adapter.getPlatformConfigAsync();
|
||||
}
|
||||
|
||||
// 否则返回同步配置
|
||||
return this.adapter.getPlatformConfig();
|
||||
return this._adapter.getPlatformConfig();
|
||||
}
|
||||
}
|
||||
|
||||
145
packages/core/src/Types/GlobalTypes.ts
Normal file
145
packages/core/src/Types/GlobalTypes.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @zh 全局类型声明 - 用于减少 as any 的使用
|
||||
* @en Global type declarations - to reduce as any usage
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// 小游戏平台 API 接口 | Mini-Game Platform API Interfaces
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 小游戏平台基础 API 接口
|
||||
* @en Base interface for mini-game platform APIs
|
||||
*/
|
||||
export interface IMiniGamePlatformAPI {
|
||||
getSystemInfo?: (options?: { success?: (res: unknown) => void }) => void;
|
||||
getSystemInfoSync?: () => Record<string, unknown>;
|
||||
createCanvas?: () => HTMLCanvasElement;
|
||||
createImage?: () => HTMLImageElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 微信小游戏 API
|
||||
* @en WeChat Mini Game API
|
||||
*/
|
||||
export interface IWeChatMiniGameAPI extends IMiniGamePlatformAPI {
|
||||
env?: {
|
||||
USER_DATA_PATH?: string;
|
||||
};
|
||||
getFileSystemManager?: () => unknown;
|
||||
createInnerAudioContext?: () => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 字节跳动小游戏 API
|
||||
* @en ByteDance Mini Game API
|
||||
*/
|
||||
export type IByteDanceMiniGameAPI = IMiniGamePlatformAPI;
|
||||
|
||||
/**
|
||||
* @zh 支付宝小游戏 API
|
||||
* @en Alipay Mini Game API
|
||||
*/
|
||||
export type IAlipayMiniGameAPI = IMiniGamePlatformAPI;
|
||||
|
||||
/**
|
||||
* @zh 百度小游戏 API
|
||||
* @en Baidu Mini Game API
|
||||
*/
|
||||
export type IBaiduMiniGameAPI = IMiniGamePlatformAPI;
|
||||
|
||||
/**
|
||||
* @zh 扩展的 globalThis 类型,包含小游戏平台
|
||||
* @en Extended globalThis type with mini-game platforms
|
||||
*/
|
||||
export interface IGlobalThisWithMiniGame {
|
||||
wx?: IWeChatMiniGameAPI;
|
||||
tt?: IByteDanceMiniGameAPI;
|
||||
my?: IAlipayMiniGameAPI;
|
||||
swan?: IBaiduMiniGameAPI;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Chrome 性能 API | Chrome Performance API
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh Chrome 内存信息接口
|
||||
* @en Chrome memory info interface
|
||||
*/
|
||||
export interface IChromeMemoryInfo {
|
||||
jsHeapSizeLimit: number;
|
||||
totalJSHeapSize: number;
|
||||
usedJSHeapSize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 扩展的 Performance 接口,包含 Chrome 特有 API
|
||||
* @en Extended Performance interface with Chrome-specific APIs
|
||||
*/
|
||||
export interface IPerformanceWithMemory extends Performance {
|
||||
memory?: IChromeMemoryInfo;
|
||||
measureUserAgentSpecificMemory?: () => Promise<{
|
||||
bytes: number;
|
||||
breakdown: Array<{
|
||||
bytes: number;
|
||||
types: string[];
|
||||
attribution: Array<{ scope: string; container?: unknown }>;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 组件元数据接口 | Component Metadata Interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh SoA 组件类型元数据接口
|
||||
* @en SoA component type metadata interface
|
||||
*
|
||||
* @zh 用于 SoA 存储装饰器附加的元数据
|
||||
* @en Used for metadata attached by SoA storage decorators
|
||||
*/
|
||||
export interface IComponentTypeMetadata {
|
||||
__enableSoA?: boolean;
|
||||
__float64Fields?: Set<string>;
|
||||
__float32Fields?: Set<string>;
|
||||
__int32Fields?: Set<string>;
|
||||
__uint32Fields?: Set<string>;
|
||||
__int16Fields?: Set<string>;
|
||||
__uint16Fields?: Set<string>;
|
||||
__int8Fields?: Set<string>;
|
||||
__uint8Fields?: Set<string>;
|
||||
__uint8ClampedFields?: Set<string>;
|
||||
__highPrecisionFields?: Set<string>;
|
||||
__serializeMapFields?: Set<string>;
|
||||
__serializeSetFields?: Set<string>;
|
||||
__serializeArrayFields?: Set<string>;
|
||||
__deepCopyFields?: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 带元数据的组件构造函数类型
|
||||
* @en Component constructor type with metadata
|
||||
*/
|
||||
export type ComponentTypeWithMetadata<T> = (new () => T) & IComponentTypeMetadata;
|
||||
|
||||
// ============================================================================
|
||||
// 类型守卫辅助函数 | Type Guard Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 获取全局小游戏平台对象
|
||||
* @en Get global mini-game platform objects
|
||||
*/
|
||||
export function getGlobalWithMiniGame(): IGlobalThisWithMiniGame {
|
||||
return globalThis as unknown as IGlobalThisWithMiniGame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取带内存 API 的 performance 对象
|
||||
* @en Get performance object with memory API
|
||||
*/
|
||||
export function getPerformanceWithMemory(): IPerformanceWithMemory {
|
||||
return performance as IPerformanceWithMemory;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import type { IWorldManagerConfig } from '../ECS';
|
||||
// 导出TypeScript类型增强工具
|
||||
export * from './TypeHelpers';
|
||||
export * from './IUpdatable';
|
||||
export * from './GlobalTypes';
|
||||
|
||||
/**
|
||||
* 组件接口
|
||||
|
||||
@@ -68,33 +68,35 @@ interface WrapInfo {
|
||||
* 自动性能分析器
|
||||
*/
|
||||
export class AutoProfiler {
|
||||
private static instance: AutoProfiler | null = null;
|
||||
private config: AutoProfilerConfig;
|
||||
private static _instance: AutoProfiler | null = null;
|
||||
private _config: AutoProfilerConfig;
|
||||
private wrappedObjects: WeakMap<object, Map<string, WrapInfo>> = new WeakMap();
|
||||
private samplingProfiler: SamplingProfiler | null = null;
|
||||
private registeredClasses: Map<string, { constructor: Function; category: ProfileCategory }> = new Map();
|
||||
|
||||
private constructor(config?: Partial<AutoProfilerConfig>) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
this._config = { ...DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
* @zh 获取单例实例
|
||||
* @en Get singleton instance
|
||||
*/
|
||||
public static getInstance(config?: Partial<AutoProfilerConfig>): AutoProfiler {
|
||||
if (!AutoProfiler.instance) {
|
||||
AutoProfiler.instance = new AutoProfiler(config);
|
||||
if (!AutoProfiler._instance) {
|
||||
AutoProfiler._instance = new AutoProfiler(config);
|
||||
}
|
||||
return AutoProfiler.instance;
|
||||
return AutoProfiler._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置实例
|
||||
* @zh 重置实例
|
||||
* @en Reset instance
|
||||
*/
|
||||
public static resetInstance(): void {
|
||||
if (AutoProfiler.instance) {
|
||||
AutoProfiler.instance.dispose();
|
||||
AutoProfiler.instance = null;
|
||||
if (AutoProfiler._instance) {
|
||||
AutoProfiler._instance.dispose();
|
||||
AutoProfiler._instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,17 +156,19 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置启用状态
|
||||
* @zh 设置启用状态
|
||||
* @en Set enabled state
|
||||
*/
|
||||
public setEnabled(enabled: boolean): void {
|
||||
this.config.enabled = enabled;
|
||||
this._config.enabled = enabled;
|
||||
if (!enabled && this.samplingProfiler) {
|
||||
this.samplingProfiler.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册类以进行自动分析
|
||||
* @zh 注册类以进行自动分析
|
||||
* @en Register class for automatic profiling
|
||||
*/
|
||||
public registerClass<T extends new (...args: any[]) => any>(
|
||||
constructor: T,
|
||||
@@ -177,11 +181,10 @@ export class AutoProfiler {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Required for Proxy construct handler
|
||||
const self = this;
|
||||
|
||||
// 创建代理类
|
||||
const ProxiedClass = new Proxy(constructor, {
|
||||
construct(target, args, newTarget) {
|
||||
const instance = Reflect.construct(target, args, newTarget);
|
||||
if (self.config.enabled) {
|
||||
if (self._config.enabled) {
|
||||
self.wrapInstance(instance, name, category);
|
||||
}
|
||||
return instance;
|
||||
@@ -192,14 +195,15 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装对象实例的所有方法
|
||||
* @zh 包装对象实例的所有方法
|
||||
* @en Wrap all methods of an object instance
|
||||
*/
|
||||
public wrapInstance<T extends object>(
|
||||
instance: T,
|
||||
className: string,
|
||||
category: ProfileCategory = ProfileCategory.Custom
|
||||
): T {
|
||||
if (!this.config.enabled) {
|
||||
if (!this._config.enabled) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -211,21 +215,20 @@ export class AutoProfiler {
|
||||
const wrapInfoMap = new Map<string, WrapInfo>();
|
||||
this.wrappedObjects.set(instance, wrapInfoMap);
|
||||
|
||||
// 获取所有方法(包括原型链上的)
|
||||
const methodNames = this.getAllMethodNames(instance);
|
||||
const methodNames = this._getAllMethodNames(instance);
|
||||
|
||||
for (const methodName of methodNames) {
|
||||
if (this.shouldExcludeMethod(methodName)) {
|
||||
if (this._shouldExcludeMethod(methodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const descriptor = this.getPropertyDescriptor(instance, methodName);
|
||||
const descriptor = this._getPropertyDescriptor(instance, methodName);
|
||||
if (!descriptor || typeof descriptor.value !== 'function') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const original = descriptor.value as Function;
|
||||
const wrapped = this.createWrappedMethod(original, className, methodName, category);
|
||||
const wrapped = this._createWrappedMethod(original, className, methodName, category);
|
||||
|
||||
wrapInfoMap.set(methodName, {
|
||||
className,
|
||||
@@ -245,14 +248,15 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装单个函数
|
||||
* @zh 包装单个函数
|
||||
* @en Wrap a single function
|
||||
*/
|
||||
public wrapFunction<T extends (...args: any[]) => any>(
|
||||
fn: T,
|
||||
name: string,
|
||||
category: ProfileCategory = ProfileCategory.Custom
|
||||
): T {
|
||||
if (!this.config.enabled) return fn;
|
||||
if (!this._config.enabled) return fn;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Required for wrapped function closure
|
||||
const self = this;
|
||||
@@ -262,24 +266,20 @@ export class AutoProfiler {
|
||||
try {
|
||||
const result = fn.apply(this, args);
|
||||
|
||||
// 处理 Promise
|
||||
if (self.config.trackAsync && result instanceof Promise) {
|
||||
if (self._config.trackAsync && result instanceof Promise) {
|
||||
return result.finally(() => {
|
||||
ProfilerSDK.endSample(handle);
|
||||
});
|
||||
}
|
||||
|
||||
// 同步函数,立即结束采样
|
||||
ProfilerSDK.endSample(handle);
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 发生错误时也要结束采样
|
||||
ProfilerSDK.endSample(handle);
|
||||
throw error;
|
||||
}
|
||||
} as T;
|
||||
|
||||
// 保留原函数的属性
|
||||
Object.defineProperty(wrapped, 'name', { value: fn.name || name });
|
||||
Object.defineProperty(wrapped, 'length', { value: fn.length });
|
||||
|
||||
@@ -287,11 +287,12 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动采样分析器
|
||||
* @zh 启动采样分析器
|
||||
* @en Start sampling profiler
|
||||
*/
|
||||
public startSampling(): void {
|
||||
if (!this.samplingProfiler) {
|
||||
this.samplingProfiler = new SamplingProfiler(this.config);
|
||||
this.samplingProfiler = new SamplingProfiler(this._config);
|
||||
}
|
||||
this.samplingProfiler.start();
|
||||
}
|
||||
@@ -318,9 +319,10 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建包装后的方法
|
||||
* @zh 创建包装后的方法
|
||||
* @en Create wrapped method
|
||||
*/
|
||||
private createWrappedMethod(
|
||||
private _createWrappedMethod(
|
||||
original: Function,
|
||||
className: string,
|
||||
methodName: string,
|
||||
@@ -329,10 +331,10 @@ export class AutoProfiler {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Required for wrapped method closure
|
||||
const self = this;
|
||||
const fullName = `${className}.${methodName}`;
|
||||
const minDuration = this.config.minDuration;
|
||||
const minDuration = this._config.minDuration;
|
||||
|
||||
return function(this: any, ...args: any[]): any {
|
||||
if (!self.config.enabled || !ProfilerSDK.isEnabled()) {
|
||||
if (!self._config.enabled || !ProfilerSDK.isEnabled()) {
|
||||
return original.apply(this, args);
|
||||
}
|
||||
|
||||
@@ -342,8 +344,7 @@ export class AutoProfiler {
|
||||
try {
|
||||
const result = original.apply(this, args);
|
||||
|
||||
// 处理异步方法
|
||||
if (self.config.trackAsync && result instanceof Promise) {
|
||||
if (self._config.trackAsync && result instanceof Promise) {
|
||||
return result.then(
|
||||
(value) => {
|
||||
const duration = performance.now() - startTime;
|
||||
@@ -359,14 +360,12 @@ export class AutoProfiler {
|
||||
);
|
||||
}
|
||||
|
||||
// 同步方法,检查最小耗时后结束采样
|
||||
const duration = performance.now() - startTime;
|
||||
if (duration >= minDuration) {
|
||||
ProfilerSDK.endSample(handle);
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 发生错误时也要结束采样
|
||||
ProfilerSDK.endSample(handle);
|
||||
throw error;
|
||||
}
|
||||
@@ -374,9 +373,10 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象的所有方法名
|
||||
* @zh 获取对象的所有方法名
|
||||
* @en Get all method names of an object
|
||||
*/
|
||||
private getAllMethodNames(obj: object): string[] {
|
||||
private _getAllMethodNames(obj: object): string[] {
|
||||
const methods = new Set<string>();
|
||||
let current = obj;
|
||||
|
||||
@@ -393,9 +393,10 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性描述符
|
||||
* @zh 获取属性描述符
|
||||
* @en Get property descriptor
|
||||
*/
|
||||
private getPropertyDescriptor(obj: object, name: string): PropertyDescriptor | undefined {
|
||||
private _getPropertyDescriptor(obj: object, name: string): PropertyDescriptor | undefined {
|
||||
let current = obj;
|
||||
while (current && current !== Object.prototype) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(current, name);
|
||||
@@ -406,16 +407,15 @@ export class AutoProfiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该排除该方法
|
||||
* @zh 判断是否应该排除该方法
|
||||
* @en Check if method should be excluded
|
||||
*/
|
||||
private shouldExcludeMethod(methodName: string): boolean {
|
||||
// 排除构造函数和内置方法
|
||||
private _shouldExcludeMethod(methodName: string): boolean {
|
||||
if (methodName === 'constructor' || methodName.startsWith('__')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查排除模式
|
||||
for (const pattern of this.config.excludePatterns) {
|
||||
for (const pattern of this._config.excludePatterns) {
|
||||
if (pattern.test(methodName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ function generateId(): string {
|
||||
* 性能分析器 SDK
|
||||
*/
|
||||
export class ProfilerSDK {
|
||||
private static instance: ProfilerSDK | null = null;
|
||||
private static _instance: ProfilerSDK | null = null;
|
||||
|
||||
private config: ProfilerConfig;
|
||||
private _config: ProfilerConfig;
|
||||
private currentFrame: ProfileFrame | null = null;
|
||||
private frameHistory: ProfileFrame[] = [];
|
||||
private frameNumber = 0;
|
||||
@@ -48,29 +48,31 @@ export class ProfilerSDK {
|
||||
private performanceObserver: PerformanceObserver | null = null;
|
||||
|
||||
private constructor(config?: Partial<ProfilerConfig>) {
|
||||
this.config = { ...DEFAULT_PROFILER_CONFIG, ...config };
|
||||
if (this.config.detectLongTasks) {
|
||||
this.setupLongTaskObserver();
|
||||
this._config = { ...DEFAULT_PROFILER_CONFIG, ...config };
|
||||
if (this._config.detectLongTasks) {
|
||||
this._setupLongTaskObserver();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
* @zh 获取单例实例
|
||||
* @en Get singleton instance
|
||||
*/
|
||||
public static getInstance(config?: Partial<ProfilerConfig>): ProfilerSDK {
|
||||
if (!ProfilerSDK.instance) {
|
||||
ProfilerSDK.instance = new ProfilerSDK(config);
|
||||
if (!ProfilerSDK._instance) {
|
||||
ProfilerSDK._instance = new ProfilerSDK(config);
|
||||
}
|
||||
return ProfilerSDK.instance;
|
||||
return ProfilerSDK._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置实例(测试用)
|
||||
* @zh 重置实例(测试用)
|
||||
* @en Reset instance (for testing)
|
||||
*/
|
||||
public static resetInstance(): void {
|
||||
if (ProfilerSDK.instance) {
|
||||
ProfilerSDK.instance.dispose();
|
||||
ProfilerSDK.instance = null;
|
||||
if (ProfilerSDK._instance) {
|
||||
ProfilerSDK._instance.dispose();
|
||||
ProfilerSDK._instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,10 +154,11 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否启用
|
||||
* @zh 检查是否启用
|
||||
* @en Check if enabled
|
||||
*/
|
||||
public static isEnabled(): boolean {
|
||||
return ProfilerSDK.getInstance().config.enabled;
|
||||
return ProfilerSDK.getInstance()._config.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,10 +190,11 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始采样
|
||||
* @zh 开始采样
|
||||
* @en Begin sample
|
||||
*/
|
||||
public beginSample(name: string, category: ProfileCategory = ProfileCategory.Custom): SampleHandle | null {
|
||||
if (!this.config.enabled || !this.config.enabledCategories.has(category)) {
|
||||
if (!this._config.enabled || !this._config.enabledCategories.has(category)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -198,7 +202,7 @@ export class ProfilerSDK {
|
||||
? this.sampleStack[this.sampleStack.length - 1]
|
||||
: undefined;
|
||||
|
||||
if (parentHandle && this.sampleStack.length >= this.config.maxSampleDepth) {
|
||||
if (parentHandle && this.sampleStack.length >= this._config.maxSampleDepth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -218,10 +222,11 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束采样
|
||||
* @zh 结束采样
|
||||
* @en End sample
|
||||
*/
|
||||
public endSample(handle: SampleHandle): void {
|
||||
if (!this.config.enabled || !this.activeSamples.has(handle.id)) {
|
||||
if (!this._config.enabled || !this.activeSamples.has(handle.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -249,7 +254,7 @@ export class ProfilerSDK {
|
||||
this.currentFrame.samples.push(sample);
|
||||
}
|
||||
|
||||
this.updateCallGraph(handle.name, handle.category, duration, parentHandle);
|
||||
this._updateCallGraph(handle.name, handle.category, duration, parentHandle);
|
||||
|
||||
this.activeSamples.delete(handle.id);
|
||||
const stackIndex = this.sampleStack.indexOf(handle);
|
||||
@@ -291,10 +296,11 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始帧
|
||||
* @zh 开始帧
|
||||
* @en Begin frame
|
||||
*/
|
||||
public beginFrame(): void {
|
||||
if (!this.config.enabled) return;
|
||||
if (!this._config.enabled) return;
|
||||
|
||||
this.frameNumber++;
|
||||
this.currentFrame = {
|
||||
@@ -305,28 +311,29 @@ export class ProfilerSDK {
|
||||
samples: [],
|
||||
sampleStats: [],
|
||||
counters: new Map(this.counters),
|
||||
memory: this.captureMemory(),
|
||||
memory: this._captureMemory(),
|
||||
categoryStats: new Map()
|
||||
};
|
||||
|
||||
this.resetFrameCounters();
|
||||
this._resetFrameCounters();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束帧
|
||||
* @zh 结束帧
|
||||
* @en End frame
|
||||
*/
|
||||
public endFrame(): void {
|
||||
if (!this.config.enabled || !this.currentFrame) return;
|
||||
if (!this._config.enabled || !this.currentFrame) return;
|
||||
|
||||
this.currentFrame.endTime = performance.now();
|
||||
this.currentFrame.duration = this.currentFrame.endTime - this.currentFrame.startTime;
|
||||
|
||||
this.calculateSampleStats();
|
||||
this.calculateCategoryStats();
|
||||
this._calculateSampleStats();
|
||||
this._calculateCategoryStats();
|
||||
|
||||
this.frameHistory.push(this.currentFrame);
|
||||
|
||||
while (this.frameHistory.length > this.config.maxFrameHistory) {
|
||||
while (this.frameHistory.length > this._config.maxFrameHistory) {
|
||||
this.frameHistory.shift();
|
||||
}
|
||||
|
||||
@@ -335,14 +342,15 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增计数器
|
||||
* @zh 递增计数器
|
||||
* @en Increment counter
|
||||
*/
|
||||
public incrementCounter(
|
||||
name: string,
|
||||
value: number = 1,
|
||||
category: ProfileCategory = ProfileCategory.Custom
|
||||
): void {
|
||||
if (!this.config.enabled) return;
|
||||
if (!this._config.enabled) return;
|
||||
|
||||
let counter = this.counters.get(name);
|
||||
if (!counter) {
|
||||
@@ -365,14 +373,15 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置仪表值
|
||||
* @zh 设置仪表值
|
||||
* @en Set gauge value
|
||||
*/
|
||||
public setGauge(
|
||||
name: string,
|
||||
value: number,
|
||||
category: ProfileCategory = ProfileCategory.Custom
|
||||
): void {
|
||||
if (!this.config.enabled) return;
|
||||
if (!this._config.enabled) return;
|
||||
|
||||
let counter = this.counters.get(name);
|
||||
if (!counter) {
|
||||
@@ -395,12 +404,13 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置启用状态
|
||||
* @zh 设置启用状态
|
||||
* @en Set enabled state
|
||||
*/
|
||||
public setEnabled(enabled: boolean): void {
|
||||
this.config.enabled = enabled;
|
||||
if (enabled && this.config.detectLongTasks && !this.performanceObserver) {
|
||||
this.setupLongTaskObserver();
|
||||
this._config.enabled = enabled;
|
||||
if (enabled && this._config.detectLongTasks && !this.performanceObserver) {
|
||||
this._setupLongTaskObserver();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,21 +438,20 @@ export class ProfilerSDK {
|
||||
: this.frameHistory;
|
||||
|
||||
if (frames.length === 0) {
|
||||
return this.createEmptyReport();
|
||||
return this._createEmptyReport();
|
||||
}
|
||||
|
||||
const frameTimes = frames.map((f) => f.duration);
|
||||
const sortedTimes = [...frameTimes].sort((a, b) => a - b);
|
||||
|
||||
const aggregatedStats = this.aggregateSampleStats(frames);
|
||||
const aggregatedStats = this._aggregateSampleStats(frames);
|
||||
const hotspots = aggregatedStats
|
||||
.sort((a, b) => b.inclusiveTime - a.inclusiveTime)
|
||||
.slice(0, 20);
|
||||
|
||||
const categoryBreakdown = this.aggregateCategoryStats(frames);
|
||||
const categoryBreakdown = this._aggregateCategoryStats(frames);
|
||||
|
||||
// 根据帧历史重新计算 callGraph(不使用全局累积的数据)
|
||||
const callGraph = this.buildCallGraphFromFrames(frames);
|
||||
const callGraph = this._buildCallGraphFromFrames(frames);
|
||||
|
||||
const firstFrame = frames[0];
|
||||
const lastFrame = frames[frames.length - 1];
|
||||
@@ -465,10 +474,13 @@ export class ProfilerSDK {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从帧历史构建调用图
|
||||
* 注意:totalTime 存储的是平均耗时(总耗时/调用次数),而不是累计总耗时
|
||||
* @zh 从帧历史构建调用图
|
||||
* @en Build call graph from frame history
|
||||
*
|
||||
* @zh 注意:totalTime 存储的是平均耗时(总耗时/调用次数),而不是累计总耗时
|
||||
* @en Note: totalTime stores average time (total/count), not cumulative total
|
||||
*/
|
||||
private buildCallGraphFromFrames(frames: ProfileFrame[]): Map<string, CallGraphNode> {
|
||||
private _buildCallGraphFromFrames(frames: ProfileFrame[]): Map<string, CallGraphNode> {
|
||||
// 临时存储累计数据
|
||||
const tempData = new Map<string, {
|
||||
category: ProfileCategory;
|
||||
@@ -597,7 +609,7 @@ export class ProfilerSDK {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
private captureMemory(): MemorySnapshot {
|
||||
private _captureMemory(): MemorySnapshot {
|
||||
const now = performance.now();
|
||||
let usedHeapSize = 0;
|
||||
let totalHeapSize = 0;
|
||||
@@ -632,7 +644,7 @@ export class ProfilerSDK {
|
||||
};
|
||||
}
|
||||
|
||||
private resetFrameCounters(): void {
|
||||
private _resetFrameCounters(): void {
|
||||
for (const counter of this.counters.values()) {
|
||||
if (counter.type === 'counter') {
|
||||
counter.value = 0;
|
||||
@@ -640,7 +652,7 @@ export class ProfilerSDK {
|
||||
}
|
||||
}
|
||||
|
||||
private calculateSampleStats(): void {
|
||||
private _calculateSampleStats(): void {
|
||||
if (!this.currentFrame) return;
|
||||
|
||||
const sampleMap = new Map<string, ProfileSampleStats>();
|
||||
@@ -701,7 +713,7 @@ export class ProfilerSDK {
|
||||
.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
|
||||
}
|
||||
|
||||
private calculateCategoryStats(): void {
|
||||
private _calculateCategoryStats(): void {
|
||||
if (!this.currentFrame) return;
|
||||
|
||||
const categoryMap = new Map<ProfileCategory, { totalTime: number; sampleCount: number }>();
|
||||
@@ -727,7 +739,7 @@ export class ProfilerSDK {
|
||||
}
|
||||
}
|
||||
|
||||
private updateCallGraph(
|
||||
private _updateCallGraph(
|
||||
name: string,
|
||||
category: ProfileCategory,
|
||||
duration: number,
|
||||
@@ -779,7 +791,7 @@ export class ProfilerSDK {
|
||||
}
|
||||
}
|
||||
|
||||
private aggregateSampleStats(frames: ProfileFrame[]): ProfileSampleStats[] {
|
||||
private _aggregateSampleStats(frames: ProfileFrame[]): ProfileSampleStats[] {
|
||||
const aggregated = new Map<string, ProfileSampleStats>();
|
||||
|
||||
for (const frame of frames) {
|
||||
@@ -810,7 +822,7 @@ export class ProfilerSDK {
|
||||
return Array.from(aggregated.values());
|
||||
}
|
||||
|
||||
private aggregateCategoryStats(frames: ProfileFrame[]): Map<ProfileCategory, {
|
||||
private _aggregateCategoryStats(frames: ProfileFrame[]): Map<ProfileCategory, {
|
||||
totalTime: number;
|
||||
averageTime: number;
|
||||
percentOfTotal: number;
|
||||
@@ -843,13 +855,13 @@ export class ProfilerSDK {
|
||||
return result;
|
||||
}
|
||||
|
||||
private setupLongTaskObserver(): void {
|
||||
private _setupLongTaskObserver(): void {
|
||||
if (typeof PerformanceObserver === 'undefined') return;
|
||||
|
||||
try {
|
||||
this.performanceObserver = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry.duration > this.config.longTaskThreshold) {
|
||||
if (entry.duration > this._config.longTaskThreshold) {
|
||||
this.longTasks.push({
|
||||
startTime: entry.startTime,
|
||||
duration: entry.duration,
|
||||
@@ -869,7 +881,7 @@ export class ProfilerSDK {
|
||||
}
|
||||
}
|
||||
|
||||
private createEmptyReport(): ProfileReport {
|
||||
private _createEmptyReport(): ProfileReport {
|
||||
return {
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
|
||||
Reference in New Issue
Block a user