Files
esengine/packages/core/src/Platform/BrowserAdapter.ts

278 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
}
}
}