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