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:
YHH
2025-12-24 20:57:08 +08:00
committed by GitHub
parent 58f70a5783
commit dbc6793dc4
133 changed files with 6880 additions and 9141 deletions

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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++;
}
/**

View File

@@ -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
});
}

View File

@@ -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();

View File

@@ -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 装饰器,可能导致序列化和代码压缩问题`
);
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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',

View File

@@ -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();
}
}

View 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;
}

View File

@@ -7,6 +7,7 @@ import type { IWorldManagerConfig } from '../ECS';
// 导出TypeScript类型增强工具
export * from './TypeHelpers';
export * from './IUpdatable';
export * from './GlobalTypes';
/**
* 组件接口

View File

@@ -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;
}

View File

@@ -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,