支持wx/browser的worker(由于wx限制默认不开启worker)

This commit is contained in:
YHH
2025-09-29 13:21:08 +08:00
parent be11060674
commit 62bc6b547e
10 changed files with 1545 additions and 31 deletions

7
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@types/multer": "^1.4.13", "@types/multer": "^1.4.13",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"coi-serviceworker": "^0.1.7", "coi-serviceworker": "^0.1.7",
"minigame-api-typings": "^3.8.12",
"protobufjs": "^7.5.3", "protobufjs": "^7.5.3",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"ws": "^8.18.2" "ws": "^8.18.2"
@@ -11220,6 +11221,12 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/minigame-api-typings": {
"version": "3.8.12",
"resolved": "https://registry.npmjs.org/minigame-api-typings/-/minigame-api-typings-3.8.12.tgz",
"integrity": "sha512-72KNUlj0oo7pK0yYIBaaagJ7gdN2oX8z/UIWyunPRHeIT4UTH70Z3HAovOZ53E5ENOQVPD5+EcnlaOdUFbk2PQ==",
"license": "MIT"
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",

View File

@@ -91,6 +91,7 @@
"@types/multer": "^1.4.13", "@types/multer": "^1.4.13",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"coi-serviceworker": "^0.1.7", "coi-serviceworker": "^0.1.7",
"minigame-api-typings": "^3.8.12",
"protobufjs": "^7.5.3", "protobufjs": "^7.5.3",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"ws": "^8.18.2" "ws": "^8.18.2"

View File

@@ -4,6 +4,8 @@ import { Matcher } from '../Utils/Matcher';
import { Time } from '../../Utils/Time'; import { Time } from '../../Utils/Time';
import { createLogger } from '../../Utils/Logger'; import { createLogger } from '../../Utils/Logger';
import type { IComponent } from '../../Types'; import type { IComponent } from '../../Types';
import { PlatformManager } from '../../Platform/PlatformManager';
import type { IPlatformAdapter, PlatformWorker } from '../../Platform/IPlatformAdapter';
/** /**
* Worker处理函数类型 * Worker处理函数类型
@@ -191,14 +193,18 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
systemConfig?: any; systemConfig?: any;
entitiesPerWorker?: number; entitiesPerWorker?: number;
}; };
private workerPool: WebWorkerPool | null = null; private workerPool: PlatformWorkerPool | null = null;
private isProcessing = false; private isProcessing = false;
protected sharedBuffer: SharedArrayBuffer | null = null; protected sharedBuffer: SharedArrayBuffer | null = null;
protected sharedFloatArray: Float32Array | null = null; protected sharedFloatArray: Float32Array | null = null;
private platformAdapter: IPlatformAdapter;
constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) { constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) {
super(matcher); super(matcher);
// 获取平台适配器
this.platformAdapter = PlatformManager.getInstance().getAdapter();
// 验证和调整 worker 数量,确保不超过系统最大值 // 验证和调整 worker 数量,确保不超过系统最大值
const requestedWorkerCount = config.workerCount ?? this.getMaxSystemWorkerCount(); const requestedWorkerCount = config.workerCount ?? this.getMaxSystemWorkerCount();
const maxSystemWorkerCount = this.getMaxSystemWorkerCount(); const maxSystemWorkerCount = this.getMaxSystemWorkerCount();
@@ -233,25 +239,22 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
* 检查是否支持Worker * 检查是否支持Worker
*/ */
private isWorkerSupported(): boolean { private isWorkerSupported(): boolean {
return typeof Worker !== 'undefined' && typeof Blob !== 'undefined'; return this.platformAdapter.isWorkerSupported();
} }
/** /**
* 检查是否支持SharedArrayBuffer * 检查是否支持SharedArrayBuffer
*/ */
private isSharedArrayBufferSupported(): boolean { private isSharedArrayBufferSupported(): boolean {
return typeof SharedArrayBuffer !== 'undefined' && self.crossOriginIsolated; return this.platformAdapter.isSharedArrayBufferSupported();
} }
/** /**
* 获取系统支持的最大Worker数量 * 获取系统支持的最大Worker数量
*/ */
private getMaxSystemWorkerCount(): number { private getMaxSystemWorkerCount(): number {
if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { const platformConfig = this.platformAdapter.getPlatformConfig();
// 使用全部CPU核心数作为最大限制 return platformConfig.maxWorkerCount;
return navigator.hardwareConcurrency;
}
return 4; // 降级默认值
} }
/** /**
@@ -267,7 +270,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
try { try {
// 检查是否支持SharedArrayBuffer // 检查是否支持SharedArrayBuffer
if (!this.isSharedArrayBufferSupported()) { if (!this.isSharedArrayBufferSupported()) {
this.logger.warn(`${this.systemName}: 不支持SharedArrayBuffer降级到单Worker模式以保证数据处理完整性`); this.logger.warn(`${this.systemName}: 平台不支持SharedArrayBuffer降级到单Worker模式以保证数据处理完整性`);
this.config.useSharedArrayBuffer = false; this.config.useSharedArrayBuffer = false;
// 降级到单Worker模式确保所有实体在同一个Worker中处理维持实体间交互的完整性 // 降级到单Worker模式确保所有实体在同一个Worker中处理维持实体间交互的完整性
this.config.workerCount = 1; this.config.workerCount = 1;
@@ -277,8 +280,10 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
// 使用配置的实体数据大小和最大实体数量 // 使用配置的实体数据大小和最大实体数量
// 预分配缓冲区maxEntities * entityDataSize * 4字节 // 预分配缓冲区maxEntities * entityDataSize * 4字节
const bufferSize = this.config.maxEntities * this.config.entityDataSize * 4; const bufferSize = this.config.maxEntities * this.config.entityDataSize * 4;
this.sharedBuffer = new SharedArrayBuffer(bufferSize); this.sharedBuffer = this.platformAdapter.createSharedArrayBuffer(bufferSize);
this.sharedFloatArray = new Float32Array(this.sharedBuffer); if (this.sharedBuffer) {
this.sharedFloatArray = new Float32Array(this.sharedBuffer);
}
this.logger.info(`${this.systemName}: SharedArrayBuffer初始化成功 (${bufferSize} 字节)`); this.logger.info(`${this.systemName}: SharedArrayBuffer初始化成功 (${bufferSize} 字节)`);
} catch (error) { } catch (error) {
@@ -296,9 +301,26 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
private initializeWorkerPool(): void { private initializeWorkerPool(): void {
try { try {
const script = this.createWorkerScript(); const script = this.createWorkerScript();
this.workerPool = new WebWorkerPool(
this.config.workerCount, // 在WorkerEntitySystem中处理平台相关逻辑
script, const workers: PlatformWorker[] = [];
const platformConfig = this.platformAdapter.getPlatformConfig();
const fullScript = (platformConfig.workerScriptPrefix || '') + script;
for (let i = 0; i < this.config.workerCount; i++) {
try {
const worker = this.platformAdapter.createWorker(fullScript, {
name: `WorkerEntitySystem-${i}`
});
workers.push(worker);
} catch (error) {
this.logger.error(`创建Worker ${i} 失败:`, error);
throw error;
}
}
this.workerPool = new PlatformWorkerPool(
workers,
this.sharedBuffer // 传递SharedArrayBuffer给Worker池 this.sharedBuffer // 传递SharedArrayBuffer给Worker池
); );
} catch (error) { } catch (error) {
@@ -832,10 +854,10 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
} }
/** /**
* Web Worker池管理器 * 平台适配的Worker池管理器
*/ */
class WebWorkerPool { class PlatformWorkerPool {
private workers: Worker[] = []; private workers: PlatformWorker[] = [];
private taskQueue: Array<{ private taskQueue: Array<{
id: string; id: string;
data: any; data: any;
@@ -845,18 +867,22 @@ class WebWorkerPool {
private busyWorkers = new Set<number>(); private busyWorkers = new Set<number>();
private taskCounter = 0; private taskCounter = 0;
private sharedBuffer: SharedArrayBuffer | null = null; private sharedBuffer: SharedArrayBuffer | null = null;
private readonly logger = createLogger('PlatformWorkerPool');
constructor(workerCount: number, script: string, sharedBuffer?: SharedArrayBuffer | null) { constructor(
this.sharedBuffer = sharedBuffer || null; workers: PlatformWorker[],
sharedBuffer: SharedArrayBuffer | null = null
) {
this.sharedBuffer = sharedBuffer;
this.workers = workers;
const blob = new Blob([script], { type: 'application/javascript' }); // 为每个Worker设置消息处理器
const scriptURL = URL.createObjectURL(blob); for (let i = 0; i < workers.length; i++) {
const worker = workers[i];
for (let i = 0; i < workerCount; i++) { // 设置消息处理器
const worker = new Worker(scriptURL); worker.onMessage((event) => this.handleWorkerMessage(i, event.data));
worker.onError((error) => this.handleWorkerError(i, error));
worker.onmessage = (e) => this.handleWorkerMessage(i, e.data);
worker.onerror = (error) => this.handleWorkerError(i, error);
// 如果有SharedArrayBuffer发送给Worker // 如果有SharedArrayBuffer发送给Worker
if (sharedBuffer) { if (sharedBuffer) {
@@ -865,11 +891,7 @@ class WebWorkerPool {
sharedBuffer: sharedBuffer sharedBuffer: sharedBuffer
}); });
} }
this.workers.push(worker);
} }
URL.revokeObjectURL(scriptURL);
} }
/** /**

View File

@@ -0,0 +1,278 @@
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig,
BrowserDeviceInfo
} from './IPlatformAdapter';
import { createLogger, type ILogger } from '../Utils/Logger';
/**
* 浏览器平台适配器
* 支持标准Web API的浏览器环境
*/
export class BrowserAdapter implements IPlatformAdapter {
public readonly name = 'browser';
public readonly version = this.getBrowserVersion();
private readonly logger: ILogger;
constructor() {
this.logger = createLogger('BrowserAdapter');
}
/**
* 检查是否支持Worker
*/
public isWorkerSupported(): boolean {
return typeof Worker !== 'undefined' && typeof Blob !== 'undefined' && typeof URL !== 'undefined';
}
/**
* 检查是否支持SharedArrayBuffer
*/
public isSharedArrayBufferSupported(): boolean {
return typeof SharedArrayBuffer !== 'undefined' &&
typeof self !== 'undefined' &&
self.crossOriginIsolated === true;
}
/**
* 获取硬件并发数
*/
public getHardwareConcurrency(): number {
if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) {
return navigator.hardwareConcurrency;
}
return 4; // 默认值
}
/**
* 创建Worker
*/
public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
const blob = new Blob([script], { type: 'application/javascript' });
const scriptURL = URL.createObjectURL(blob);
const worker = new Worker(scriptURL, {
type: options.type || 'classic',
credentials: options.credentials || 'same-origin',
name: options.name
});
return new BrowserWorker(worker, scriptURL);
}
/**
* 创建SharedArrayBuffer
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
if (!this.isSharedArrayBufferSupported()) {
return null;
}
try {
return new SharedArrayBuffer(length);
} catch (error) {
this.logger.warn('Failed to create SharedArrayBuffer:', error);
return null;
}
}
/**
* 获取高精度时间戳
*/
public getHighResTimestamp(): number {
if (typeof performance !== 'undefined' && performance.now) {
return performance.now();
}
return Date.now();
}
/**
* 获取平台配置
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: this.getHardwareConcurrency(),
supportsModuleWorker: this.supportsModuleWorker(),
supportsTransferableObjects: this.supportsTransferableObjects(),
maxSharedArrayBufferSize: this.getMaxSharedArrayBufferSize(),
workerScriptPrefix: '',
limitations: {
noEval: false,
requiresWorkerInit: false
},
extensions: {
supportsServiceWorker: typeof navigator !== 'undefined' && 'serviceWorker' in navigator,
supportsWebAssembly: typeof WebAssembly !== 'undefined'
}
};
}
/**
* 获取浏览器设备信息
*/
public getDeviceInfo(): BrowserDeviceInfo {
try {
const deviceInfo: BrowserDeviceInfo = {};
// 浏览器基础信息
if (typeof navigator !== 'undefined') {
deviceInfo.userAgent = navigator.userAgent;
deviceInfo.vendor = navigator.vendor;
deviceInfo.language = navigator.language;
deviceInfo.languages = navigator.languages as string[];
deviceInfo.cookieEnabled = navigator.cookieEnabled;
deviceInfo.onLine = navigator.onLine;
deviceInfo.doNotTrack = navigator.doNotTrack;
deviceInfo.platform = navigator.platform;
deviceInfo.appVersion = navigator.appVersion;
deviceInfo.appName = navigator.appName;
// 硬件信息
deviceInfo.hardwareConcurrency = navigator.hardwareConcurrency;
deviceInfo.maxTouchPoints = navigator.maxTouchPoints;
// 设备内存实验性API
if ('deviceMemory' in navigator) {
deviceInfo.deviceMemory = (navigator as any).deviceMemory;
}
// 网络连接信息实验性API
if ('connection' in navigator) {
const connection = (navigator as any).connection;
deviceInfo.connection = {
effectiveType: connection.effectiveType,
downlink: connection.downlink,
rtt: connection.rtt,
saveData: connection.saveData
};
}
}
// 屏幕信息
if (typeof screen !== 'undefined') {
deviceInfo.screenWidth = screen.width;
deviceInfo.screenHeight = screen.height;
deviceInfo.availWidth = screen.availWidth;
deviceInfo.availHeight = screen.availHeight;
deviceInfo.colorDepth = screen.colorDepth;
deviceInfo.pixelDepth = screen.pixelDepth;
}
// 窗口信息
if (typeof window !== 'undefined') {
deviceInfo.innerWidth = window.innerWidth;
deviceInfo.innerHeight = window.innerHeight;
deviceInfo.outerWidth = window.outerWidth;
deviceInfo.outerHeight = window.outerHeight;
deviceInfo.devicePixelRatio = window.devicePixelRatio;
}
return deviceInfo;
} catch (error) {
this.logger.warn('获取浏览器设备信息失败', error);
return {} as BrowserDeviceInfo;
}
}
/**
* 获取浏览器版本信息
*/
private getBrowserVersion(): string {
if (typeof navigator !== 'undefined') {
return navigator.userAgent;
}
return 'unknown';
}
/**
* 检查是否支持模块Worker
*/
private supportsModuleWorker(): boolean {
try {
// 尝试创建一个简单的模块Worker来测试支持
const blob = new Blob(['export {};'], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
const worker = new Worker(url, { type: 'module' });
worker.terminate();
URL.revokeObjectURL(url);
return true;
} catch {
return false;
}
}
/**
* 检查是否支持Transferable Objects
*/
private supportsTransferableObjects(): boolean {
try {
const buffer = new ArrayBuffer(8);
const blob = new Blob(['self.postMessage(null);'], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
worker.postMessage(buffer, [buffer]);
worker.terminate();
URL.revokeObjectURL(url);
return buffer.byteLength === 0; // Buffer应该被transfer走
} catch {
return false;
}
}
/**
* 获取SharedArrayBuffer最大大小限制
*/
private getMaxSharedArrayBufferSize(): number {
// 浏览器环境返回保守的默认值
// 大多数现代浏览器都支持较大的SharedArrayBuffer
return 256 * 1024 * 1024; // 256MB适合大多数ECS应用场景
}
}
/**
* 浏览器Worker封装
*/
class BrowserWorker implements PlatformWorker {
private _state: 'running' | 'terminated' = 'running';
constructor(
private worker: Worker,
private scriptURL: string
) {}
public get state(): 'running' | 'terminated' {
return this._state;
}
public postMessage(message: any, transfer?: Transferable[]): void {
if (this._state === 'terminated') {
throw new Error('Worker has been terminated');
}
if (transfer && transfer.length > 0) {
this.worker.postMessage(message, transfer);
} else {
this.worker.postMessage(message);
}
}
public onMessage(handler: (event: { data: any }) => void): void {
this.worker.onmessage = (event: MessageEvent) => {
handler({ data: event.data });
};
}
public onError(handler: (error: ErrorEvent) => void): void {
this.worker.onerror = handler;
}
public terminate(): void {
if (this._state === 'running') {
this.worker.terminate();
URL.revokeObjectURL(this.scriptURL);
this._state = 'terminated';
}
}
}

View File

@@ -0,0 +1,289 @@
/**
* 平台适配器接口
* 用于适配不同的运行环境(浏览器、微信小游戏、字节跳动小游戏等)
*/
export interface IPlatformAdapter {
/**
* 平台名称
*/
readonly name: string;
/**
* 平台版本信息
*/
readonly version?: string;
/**
* 检查是否支持Worker
*/
isWorkerSupported(): boolean;
/**
* 检查是否支持SharedArrayBuffer
*/
isSharedArrayBufferSupported(): boolean;
/**
* 获取硬件并发数CPU核心数
*/
getHardwareConcurrency(): number;
/**
* 创建Worker
* @param script Worker脚本内容
* @param options Worker选项
*/
createWorker(script: string, options?: WorkerCreationOptions): PlatformWorker;
/**
* 创建SharedArrayBuffer
* @param length 缓冲区大小(字节)
*/
createSharedArrayBuffer(length: number): SharedArrayBuffer | null;
/**
* 获取高精度时间戳
*/
getHighResTimestamp(): number;
/**
* 获取平台特定的配置
*/
getPlatformConfig(): PlatformConfig;
/**
* 异步获取平台配置(包含需要异步获取的信息)
*/
getPlatformConfigAsync?(): Promise<PlatformConfig>;
}
/**
* Worker创建选项
*/
export interface WorkerCreationOptions {
/**
* Worker类型
*/
type?: 'classic' | 'module';
/**
* 凭据模式
*/
credentials?: 'omit' | 'same-origin' | 'include';
/**
* Worker名称用于调试
*/
name?: string;
}
/**
* 平台Worker接口
*/
export interface PlatformWorker {
/**
* 发送消息到Worker
*/
postMessage(message: any, transfer?: Transferable[]): void;
/**
* 监听Worker消息
*/
onMessage(handler: (event: { data: any }) => void): void;
/**
* 监听Worker错误
*/
onError(handler: (error: ErrorEvent) => void): void;
/**
* 终止Worker
*/
terminate(): void;
/**
* Worker状态
*/
readonly state: 'running' | 'terminated';
}
/**
* 平台配置
*/
export interface PlatformConfig {
/**
* 最大Worker数量限制
*/
maxWorkerCount: number;
/**
* 是否支持模块Worker
*/
supportsModuleWorker: boolean;
/**
* 是否支持Transferable Objects
*/
supportsTransferableObjects: boolean;
/**
* SharedArrayBuffer的最大大小限制字节
*/
maxSharedArrayBufferSize?: number;
/**
* 平台特定的Worker脚本前缀如果需要
*/
workerScriptPrefix?: string;
/**
* 平台特定的限制和特性
*/
limitations?: {
/**
* 是否禁用eval影响动态脚本创建
*/
noEval?: boolean;
/**
* 是否需要特殊的Worker初始化
*/
requiresWorkerInit?: boolean;
/**
* 内存限制(字节)
*/
memoryLimit?: number;
/**
* Worker是否不受支持用于明确标记不支持Worker的平台
*/
workerNotSupported?: boolean;
/**
* Worker限制说明列表
*/
workerLimitations?: string[];
};
/**
* 平台特定的扩展属性
*/
extensions?: Record<string, any>;
}
/**
* 平台检测结果
*/
export interface PlatformDetectionResult {
/**
* 平台类型
*/
platform: 'browser' | 'wechat-minigame' | 'bytedance-minigame' | 'alipay-minigame' | 'baidu-minigame' | 'unknown';
/**
* 是否确定检测结果
*/
confident: boolean;
/**
* 检测到的特征
*/
features: string[];
/**
* 建议使用的适配器类名
*/
adapterClass?: string;
}
/**
* 微信小游戏设备信息接口
*/
export interface WeChatDeviceInfo {
// 设备基础信息
brand?: string;
model?: string;
platform?: string;
system?: string;
benchmarkLevel?: number;
cpuType?: string;
memorySize?: string;
deviceAbi?: string;
abi?: string;
// 窗口信息
screenWidth?: number;
screenHeight?: number;
screenTop?: number;
windowWidth?: number;
windowHeight?: number;
pixelRatio?: number;
statusBarHeight?: number;
safeArea?: {
top: number;
bottom: number;
left: number;
right: number;
width: number;
height: number;
};
// 应用信息
version?: string;
language?: string;
theme?: string;
SDKVersion?: string;
enableDebug?: boolean;
fontSizeSetting?: number;
host?: {
appId: string;
};
}
/**
* 浏览器设备信息接口
*/
export interface BrowserDeviceInfo {
// 浏览器信息
userAgent?: string;
vendor?: string;
language?: string;
languages?: string[];
cookieEnabled?: boolean;
onLine?: boolean;
doNotTrack?: string | null;
// 硬件信息
hardwareConcurrency?: number;
deviceMemory?: number;
maxTouchPoints?: number;
// 屏幕信息
screenWidth?: number;
screenHeight?: number;
availWidth?: number;
availHeight?: number;
colorDepth?: number;
pixelDepth?: number;
// 窗口信息
innerWidth?: number;
innerHeight?: number;
outerWidth?: number;
outerHeight?: number;
devicePixelRatio?: number;
// 连接信息(如果支持)
connection?: {
effectiveType?: string;
downlink?: number;
rtt?: number;
saveData?: boolean;
};
// 平台信息
platform?: string;
appVersion?: string;
appName?: string;
}

View File

@@ -0,0 +1,209 @@
import type { PlatformDetectionResult } from './IPlatformAdapter';
/**
* 平台检测器
* 自动检测当前运行环境并返回对应的平台信息
*/
export class PlatformDetector {
/**
* 检测当前平台
*/
public static detect(): PlatformDetectionResult {
const features: string[] = [];
let platform: PlatformDetectionResult['platform'] = 'unknown';
let confident = false;
let adapterClass: string | undefined;
// 检查全局对象和API
if (typeof globalThis !== 'undefined') {
features.push('globalThis');
}
if (typeof window !== 'undefined') {
features.push('window');
}
if (typeof self !== 'undefined') {
features.push('self');
}
// 检测微信小游戏环境
if (this.isWeChatMiniGame()) {
platform = 'wechat-minigame';
confident = true;
adapterClass = 'WeChatMiniGameAdapter';
features.push('wx', 'wechat-minigame');
}
// 检测字节跳动小游戏环境
else if (this.isByteDanceMiniGame()) {
platform = 'bytedance-minigame';
confident = true;
adapterClass = 'ByteDanceMiniGameAdapter';
features.push('tt', 'bytedance-minigame');
}
// 检测支付宝小游戏环境
else if (this.isAlipayMiniGame()) {
platform = 'alipay-minigame';
confident = true;
adapterClass = 'AlipayMiniGameAdapter';
features.push('my', 'alipay-minigame');
}
// 检测百度小游戏环境
else if (this.isBaiduMiniGame()) {
platform = 'baidu-minigame';
confident = true;
adapterClass = 'BaiduMiniGameAdapter';
features.push('swan', 'baidu-minigame');
}
// 检测浏览器环境
else if (this.isBrowser()) {
platform = 'browser';
confident = true;
adapterClass = 'BrowserAdapter';
features.push('browser');
}
// 添加功能检测特征
if (typeof Worker !== 'undefined') {
features.push('Worker');
}
if (typeof SharedArrayBuffer !== 'undefined') {
features.push('SharedArrayBuffer');
}
if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) {
features.push('hardwareConcurrency');
}
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
features.push('performance.now');
}
if (typeof Blob !== 'undefined') {
features.push('Blob');
}
if (typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function') {
features.push('URL.createObjectURL');
}
return {
platform,
confident,
features,
adapterClass
};
}
/**
* 检测是否为微信小游戏环境
*/
private static isWeChatMiniGame(): boolean {
// 检查wx全局对象
if (typeof (globalThis as any).wx !== 'undefined') {
const wx = (globalThis as any).wx;
// 检查微信小游戏特有的API
return !!(wx.getSystemInfo && wx.createCanvas && wx.createImage);
}
return false;
}
/**
* 检测是否为字节跳动小游戏环境
*/
private static isByteDanceMiniGame(): boolean {
// 检查tt全局对象
if (typeof (globalThis as any).tt !== 'undefined') {
const tt = (globalThis as any).tt;
// 检查字节跳动小游戏特有的API
return !!(tt.getSystemInfo && tt.createCanvas && tt.createImage);
}
return false;
}
/**
* 检测是否为支付宝小游戏环境
*/
private static isAlipayMiniGame(): boolean {
// 检查my全局对象
if (typeof (globalThis as any).my !== 'undefined') {
const my = (globalThis as any).my;
// 检查支付宝小游戏特有的API
return !!(my.getSystemInfo && my.createCanvas);
}
return false;
}
/**
* 检测是否为百度小游戏环境
*/
private static isBaiduMiniGame(): boolean {
// 检查swan全局对象
if (typeof (globalThis as any).swan !== 'undefined') {
const swan = (globalThis as any).swan;
// 检查百度小游戏特有的API
return !!(swan.getSystemInfo && swan.createCanvas);
}
return false;
}
/**
* 检测是否为浏览器环境
*/
private static isBrowser(): boolean {
// 检查浏览器特有的对象和API
return typeof window !== 'undefined' &&
typeof document !== 'undefined' &&
typeof navigator !== 'undefined' &&
window.location !== undefined;
}
/**
* 获取详细的环境信息(用于调试)
*/
public static getDetailedInfo(): Record<string, any> {
const info: Record<string, any> = {};
// 基础检测
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'
};
// Worker相关检测
info.workerSupport = {
Worker: typeof Worker !== 'undefined',
SharedWorker: typeof SharedWorker !== 'undefined',
ServiceWorker: typeof navigator !== 'undefined' && 'serviceWorker' in navigator,
SharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
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',
createObjectURL: typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function',
ArrayBuffer: typeof ArrayBuffer !== 'undefined',
TypedArrays: typeof Float32Array !== 'undefined'
};
return info;
}
}

View File

@@ -0,0 +1,206 @@
import type { IPlatformAdapter } from './IPlatformAdapter';
import { PlatformDetector } from './PlatformDetector';
import { BrowserAdapter } from './BrowserAdapter';
import { WeChatMiniGameAdapter } from './WeChatMiniGameAdapter';
import { createLogger, type ILogger } from '../Utils/Logger';
/**
* 平台管理器
* 负责自动检测平台并提供对应的适配器
*/
export class PlatformManager {
private static instance: PlatformManager;
private adapter: IPlatformAdapter | null = null;
private detectionResult: any = null;
private readonly logger: ILogger;
private constructor() {
this.logger = createLogger('PlatformManager');
this.detectAndInitialize();
}
/**
* 获取单例实例
*/
public static getInstance(): PlatformManager {
if (!PlatformManager.instance) {
PlatformManager.instance = new PlatformManager();
}
return PlatformManager.instance;
}
/**
* 获取当前平台适配器
*/
public getAdapter(): IPlatformAdapter {
if (!this.adapter) {
throw new Error('平台适配器未初始化,请检查平台环境');
}
return this.adapter;
}
/**
* 获取平台检测结果
*/
public getDetectionResult() {
return this.detectionResult;
}
/**
* 手动设置适配器(用于测试或特殊情况)
*/
public setAdapter(adapter: IPlatformAdapter): void {
this.adapter = adapter;
}
/**
* 检测平台并初始化对应的适配器
*/
private detectAndInitialize(): void {
this.detectionResult = PlatformDetector.detect();
try {
switch (this.detectionResult.platform) {
case 'wechat-minigame':
this.adapter = new WeChatMiniGameAdapter();
break;
case 'bytedance-minigame':
// TODO: 实现字节跳动小游戏适配器
this.logger.warn('字节跳动小游戏适配器尚未实现,降级到浏览器适配器');
this.adapter = new BrowserAdapter();
break;
case 'alipay-minigame':
// TODO: 实现支付宝小游戏适配器
this.logger.warn('支付宝小游戏适配器尚未实现,降级到浏览器适配器');
this.adapter = new BrowserAdapter();
break;
case 'baidu-minigame':
// TODO: 实现百度小游戏适配器
this.logger.warn('百度小游戏适配器尚未实现,降级到浏览器适配器');
this.adapter = new BrowserAdapter();
break;
case 'browser':
default:
this.adapter = new BrowserAdapter();
break;
}
// 输出初始化信息
this.logInitializationInfo();
} catch (error) {
this.logger.error('平台适配器初始化失败:', error);
// 降级到浏览器适配器
this.adapter = new BrowserAdapter();
}
}
/**
* 输出初始化信息
*/
private logInitializationInfo(): void {
if (!this.adapter) return;
const config = this.adapter.getPlatformConfig();
this.logger.info(`平台适配器初始化完成:`, {
platform: this.detectionResult.platform,
adapterName: this.adapter.name,
version: this.adapter.version,
features: this.detectionResult.features,
config: {
maxWorkerCount: config.maxWorkerCount,
supportsSharedArrayBuffer: this.adapter.isSharedArrayBufferSupported(),
supportsWorker: this.adapter.isWorkerSupported(),
hardwareConcurrency: this.adapter.getHardwareConcurrency()
}
});
}
/**
* 获取详细的平台信息(用于调试)
*/
public getDetailedInfo(): any {
return {
detectionResult: this.detectionResult,
detailedInfo: PlatformDetector.getDetailedInfo(),
adapterInfo: this.adapter ? {
name: this.adapter.name,
version: this.adapter.version,
config: this.adapter.getPlatformConfig()
} : null
};
}
/**
* 检查当前平台是否支持特定功能
*/
public supportsFeature(feature: 'worker' | 'shared-array-buffer' | 'transferable-objects' | 'module-worker'): boolean {
if (!this.adapter) return false;
const config = this.adapter.getPlatformConfig();
switch (feature) {
case 'worker':
return this.adapter.isWorkerSupported();
case 'shared-array-buffer':
return this.adapter.isSharedArrayBufferSupported();
case 'transferable-objects':
return config.supportsTransferableObjects;
case 'module-worker':
return config.supportsModuleWorker;
default:
return false;
}
}
/**
* 获取基础的Worker配置信息不做自动决策
* 用户应该根据自己的业务需求来配置Worker参数
*/
public getBasicWorkerConfig(): {
platformSupportsWorker: boolean;
platformSupportsSharedArrayBuffer: boolean;
platformMaxWorkerCount: number;
platformLimitations: any;
} {
if (!this.adapter) {
return {
platformSupportsWorker: false,
platformSupportsSharedArrayBuffer: false,
platformMaxWorkerCount: 1,
platformLimitations: {}
};
}
const config = this.adapter.getPlatformConfig();
return {
platformSupportsWorker: this.adapter.isWorkerSupported(),
platformSupportsSharedArrayBuffer: this.adapter.isSharedArrayBufferSupported(),
platformMaxWorkerCount: config.maxWorkerCount,
platformLimitations: config.limitations || {}
};
}
/**
* 异步获取完整的平台配置信息(包含性能信息)
*/
public async getFullPlatformConfig(): Promise<any> {
if (!this.adapter) {
return null;
}
// 如果适配器支持异步获取配置,使用异步方法
if (typeof this.adapter.getPlatformConfigAsync === 'function') {
return await this.adapter.getPlatformConfigAsync();
}
// 否则返回同步配置
return this.adapter.getPlatformConfig();
}
}

View File

@@ -0,0 +1,451 @@
/// <reference types="minigame-api-typings" />
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig,
WeChatDeviceInfo
} from './IPlatformAdapter';
import { createLogger, type ILogger } from '../Utils/Logger';
/**
* 微信小游戏平台适配器
* 适配微信小游戏环境的特殊限制和API
*/
export class WeChatMiniGameAdapter implements IPlatformAdapter {
public readonly name = 'wechat-minigame';
public readonly version = this.getWeChatVersion();
private readonly wx: WechatMinigame.Wx;
private readonly logger: ILogger;
constructor() {
this.logger = createLogger('WeChatMiniGameAdapter');
this.wx = (globalThis as any).wx;
if (!this.wx) {
throw new Error('微信小游戏环境未检测到wx对象');
}
}
/**
* 检查是否支持Worker
* 微信小游戏虽然有Worker API但限制太严格对于动态ECS系统不实用
*/
public isWorkerSupported(): boolean {
// 虽然微信小游戏有Worker API但由于以下限制实际上不适用于动态ECS系统
// 1. 不能动态创建Worker脚本必须预配置文件
// 2. 禁止eval不能动态执行用户的处理函数
// 3. 需要用户手动配置game.json
// 因此对于WorkerEntitySystem来说认为不支持Worker
return false;
}
/**
* 检查是否支持SharedArrayBuffer
* 微信小游戏不支持SharedArrayBuffer因为
* 1. 不满足crossOriginIsolated要求
* 2. 小游戏运行在受限沙箱环境中
* 3. 出于安全考虑微信限制了此API
*/
public isSharedArrayBufferSupported(): boolean {
// 微信小游戏明确不支持SharedArrayBuffer
// 即使有API存在也无法在沙箱环境中正常使用
return false;
}
/**
* 获取硬件并发数
* 微信小游戏没有直接的CPU核心数API返回保守的默认值
* 用户应该根据自己的业务需求和测试结果来设置Worker数量
*/
public getHardwareConcurrency(): number {
// 微信小游戏平台返回保守的默认值
// 用户可以通过getDevicePerformanceInfo()获取详细信息来自行判断
return 4;
}
/**
* 获取设备性能信息(供用户参考)
* 用户可以根据这些信息自行决定Worker配置
*/
public getDevicePerformanceInfo(): Promise<{
benchmarkLevel?: number;
modelLevel?: number;
memorySize?: string;
cpuType?: string;
platform?: string;
system?: string;
}> {
return new Promise((resolve) => {
const result: any = {};
// 获取设备基础信息
if (typeof this.wx.getDeviceInfo === 'function') {
try {
const deviceInfo = this.wx.getDeviceInfo();
Object.assign(result, {
memorySize: deviceInfo.memorySize,
cpuType: deviceInfo.cpuType,
platform: deviceInfo.platform,
system: deviceInfo.system,
benchmarkLevel: deviceInfo.benchmarkLevel
});
} catch (error) {
this.logger.warn('获取设备信息失败', error);
}
}
// 获取性能基准信息
if (typeof this.wx.getDeviceBenchmarkInfo === 'function') {
this.wx.getDeviceBenchmarkInfo({
success: (res) => {
result.benchmarkLevel = res.benchmarkLevel;
result.modelLevel = res.modelLevel;
resolve(result);
},
fail: () => {
resolve(result);
}
});
} else {
resolve(result);
}
});
}
/**
* 创建Worker
* 微信小游戏不支持WorkerEntitySystem所需的动态Worker创建
*/
public createWorker(_script: string, _options: WorkerCreationOptions = {}): PlatformWorker {
throw new Error(
'微信小游戏不支持WorkerEntitySystem。' +
'原因1) 不能动态创建Worker脚本 2) 禁止eval动态执行代码。' +
'WorkerEntitySystem将自动降级到同步模式运行。'
);
}
/**
* 从预配置的文件路径创建Worker仅供特殊用途
* 注意这不适用于WorkerEntitySystem仅供其他特殊Worker需求使用
*/
public createWorkerFromPath(scriptPath: string, options: {
useExperimentalWorker?: boolean;
name?: string;
} = {}): PlatformWorker {
// 即使支持原生Worker API也明确说明不适用于ECS
this.logger.warn('微信小游戏Worker不适用于WorkerEntitySystem建议使用同步模式');
return new WeChatWorker(scriptPath, options, this.wx);
}
/**
* 创建SharedArrayBuffer
* 微信小游戏通常不支持返回null
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
if (!this.isSharedArrayBufferSupported()) {
this.logger.info('微信小游戏环境不支持SharedArrayBuffer将使用Worker模式');
return null;
}
try {
return new SharedArrayBuffer(length);
} catch (error) {
this.logger.warn('SharedArrayBuffer创建失败', error);
return null;
}
}
/**
* 获取高精度时间戳
*/
public getHighResTimestamp(): number {
// 微信小游戏支持performance.now()
if (typeof performance !== 'undefined' && performance.now) {
return performance.now();
}
// 备选方案使用Date.now()
return Date.now();
}
/**
* 获取平台配置(同步版本,不包含异步获取的性能信息)
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: 0, // 不支持WorkerEntitySystem
supportsModuleWorker: false,
supportsTransferableObjects: false, // 对WorkerEntitySystem无意义
maxSharedArrayBufferSize: 0, // 不支持SharedArrayBuffer
workerScriptPrefix: '',
limitations: {
noEval: true, // 微信小游戏禁止eval
requiresWorkerInit: true,
// 明确说明Worker限制
workerNotSupported: true,
workerLimitations: [
'不能动态创建Worker脚本',
'禁止eval动态执行代码',
'必须预配置Worker文件路径',
'不适用于动态ECS系统'
]
},
extensions: {
wechatVersion: this.version,
supportedAPIs: this.getSupportedAPIs(),
deviceInfo: this.getDeviceInfo()
}
};
}
/**
* 异步获取平台配置(包含性能信息)
*/
public async getPlatformConfigAsync(): Promise<PlatformConfig> {
const baseConfig = this.getPlatformConfig();
try {
// 异步获取性能信息
const performanceInfo = await this.getDevicePerformanceInfo();
return {
...baseConfig,
extensions: {
...baseConfig.extensions,
performanceInfo
}
};
} catch (error) {
this.logger.warn('获取性能信息失败,返回基础配置', error);
return baseConfig;
}
}
/**
* 获取微信版本信息
*/
private getWeChatVersion(): string {
try {
const systemInfo = this.wx.getSystemInfoSync();
return `WeChat ${systemInfo.version} (${systemInfo.platform})`;
} catch {
return 'WeChat Unknown';
}
}
/**
* 获取支持的API列表
*/
private getSupportedAPIs(): string[] {
const apis: string[] = [];
// 检查常用的微信小游戏API
const commonAPIs = [
'getSystemInfo', 'createCanvas', 'createImage', 'createAudio',
'createWorker', 'downloadFile', 'request', 'connectSocket',
'setStorage', 'getStorage', 'showToast', 'showModal'
];
for (const api of commonAPIs) {
if (typeof (this.wx as any)[api] === 'function') {
apis.push(api);
}
}
return apis;
}
/**
* 获取设备信息
*/
public getDeviceInfo(): WeChatDeviceInfo {
try {
const deviceInfo: any = {};
// 优先使用最新的设备信息API
if (typeof this.wx.getDeviceInfo === 'function') {
const info = this.wx.getDeviceInfo();
Object.assign(deviceInfo, {
brand: info.brand,
model: info.model,
platform: info.platform,
system: info.system,
benchmarkLevel: info.benchmarkLevel,
cpuType: info.cpuType,
memorySize: info.memorySize,
deviceAbi: info.deviceAbi,
abi: info.abi
});
}
// 获取设备性能基准信息
if (typeof this.wx.getDeviceBenchmarkInfo === 'function') {
this.wx.getDeviceBenchmarkInfo({
success: (res) => {
deviceInfo.benchmarkLevel = res.benchmarkLevel;
deviceInfo.modelLevel = res.modelLevel;
}
});
}
// 获取窗口信息
if (typeof this.wx.getWindowInfo === 'function') {
const windowInfo = this.wx.getWindowInfo();
Object.assign(deviceInfo, {
screenWidth: windowInfo.screenWidth,
screenHeight: windowInfo.screenHeight,
screenTop: windowInfo.screenTop,
windowWidth: windowInfo.windowWidth,
windowHeight: windowInfo.windowHeight,
pixelRatio: windowInfo.pixelRatio,
statusBarHeight: windowInfo.statusBarHeight,
safeArea: windowInfo.safeArea
});
}
// 获取应用基础信息
if (typeof this.wx.getAppBaseInfo === 'function') {
try {
const appInfo = this.wx.getAppBaseInfo();
Object.assign(deviceInfo, {
version: appInfo.version,
language: appInfo.language,
theme: appInfo.theme,
SDKVersion: appInfo.SDKVersion,
enableDebug: appInfo.enableDebug,
fontSizeSetting: appInfo.fontSizeSetting,
// host是一个对象不是hostName
host: appInfo.host
});
} catch (error) {
this.logger.warn('获取应用基础信息失败', error);
}
}
// 如果新API不完整才使用废弃API作为兜底
if (Object.keys(deviceInfo).length === 0 && typeof this.wx.getSystemInfoSync === 'function') {
this.logger.warn('新API不可用使用废弃的getSystemInfoSync作为兜底');
try {
const systemInfo = this.wx.getSystemInfoSync();
return {
brand: systemInfo.brand,
model: systemInfo.model,
platform: systemInfo.platform,
system: systemInfo.system,
version: systemInfo.version,
benchmarkLevel: systemInfo.benchmarkLevel,
screenWidth: systemInfo.screenWidth,
screenHeight: systemInfo.screenHeight,
pixelRatio: systemInfo.pixelRatio
} as WeChatDeviceInfo;
} catch (error) {
this.logger.error('所有获取设备信息的方法都失败了', error);
return {} as WeChatDeviceInfo;
}
}
return deviceInfo as WeChatDeviceInfo;
} catch (error) {
this.logger.warn('获取设备信息失败', error);
return {} as WeChatDeviceInfo;
}
}
}
/**
* 微信小游戏Worker封装
*/
class WeChatWorker implements PlatformWorker {
private _state: 'running' | 'terminated' = 'running';
private worker!: WechatMinigame.Worker;
private readonly logger: ILogger;
constructor(
private scriptPath: string,
private options: { useExperimentalWorker?: boolean; name?: string },
private wx: WechatMinigame.Wx
) {
this.logger = createLogger('WeChatWorker');
this.createWeChatWorker();
}
public get state(): 'running' | 'terminated' {
return this._state;
}
public postMessage(message: any, _transfer?: Transferable[]): void {
if (this._state === 'terminated') {
throw new Error('Worker已被终止');
}
if (this.worker) {
this.worker.postMessage(message);
}
}
public onMessage(handler: (event: { data: any }) => void): void {
if (this.worker) {
this.worker.onMessage((data: any) => {
handler({ data });
});
}
}
public onError(handler: (error: ErrorEvent) => void): void {
if (this.worker) {
this.worker.onError((error: any) => {
// 转换微信错误格式为标准ErrorEvent格式
const errorEvent = new ErrorEvent('error', {
message: error.message || '微信Worker错误',
filename: error.filename || '',
lineno: error.lineno || 0,
colno: error.colno || 0,
error: error
});
handler(errorEvent);
});
}
}
public terminate(): void {
if (this._state === 'running' && this.worker) {
this.worker.terminate();
this._state = 'terminated';
}
}
/**
* 创建微信Worker
* 使用预配置的Worker脚本文件路径
*/
private createWeChatWorker(): void {
try {
// 使用微信小游戏的createWorker API传入预配置的脚本路径
this.worker = this.wx.createWorker(this.scriptPath, {
useExperimentalWorker: this.options.useExperimentalWorker || false
});
// 监听Worker被系统回收事件实验性Worker特有
if (this.options.useExperimentalWorker && this.worker.onProcessKilled) {
this.worker.onProcessKilled(() => {
this.logger.warn(`微信Worker ${this.scriptPath} 被系统回收`);
this._state = 'terminated';
});
}
} catch (error) {
this.logger.error('创建微信Worker失败:', error);
throw new Error(
`无法创建微信Worker: ${error}` +
`请确保已在game.json中配置workers字段并且Worker文件 ${this.scriptPath} 存在。`
);
}
}
}

View File

@@ -0,0 +1,48 @@
/**
* 平台适配模块导出
*/
// 接口和类型
export type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig,
PlatformDetectionResult,
WeChatDeviceInfo,
BrowserDeviceInfo
} from './IPlatformAdapter';
// 平台检测器
export { PlatformDetector } from './PlatformDetector';
// 平台管理器
export { PlatformManager } from './PlatformManager';
// 平台适配器实现
export { BrowserAdapter } from './BrowserAdapter';
export { WeChatMiniGameAdapter } from './WeChatMiniGameAdapter';
// 内部导入用于便利函数
import { PlatformManager } from './PlatformManager';
// 便利函数
export function getCurrentPlatform() {
return PlatformManager.getInstance().getDetectionResult();
}
export function getCurrentAdapter() {
return PlatformManager.getInstance().getAdapter();
}
export function getBasicWorkerConfig() {
return PlatformManager.getInstance().getBasicWorkerConfig();
}
export function getFullPlatformConfig() {
return PlatformManager.getInstance().getFullPlatformConfig();
}
export function supportsFeature(feature: 'worker' | 'shared-array-buffer' | 'transferable-objects' | 'module-worker') {
return PlatformManager.getInstance().supportsFeature(feature);
}

View File

@@ -32,4 +32,7 @@ export { ECSEventType, EventPriority, EVENT_TYPES, EventTypeValidator } from './
// 工具类和类型定义 // 工具类和类型定义
export * from './Utils'; export * from './Utils';
export * from './Types'; export * from './Types';
// 平台适配
export * from './Platform';