321 lines
10 KiB
TypeScript
321 lines
10 KiB
TypeScript
/**
|
||
* WASM加载器
|
||
* 基于Cocos Creator官方文档的WASM/ASM加载最佳实践
|
||
* https://docs.cocos.com/creator/3.8/manual/zh/advanced-topics/wasm-asm-load.html
|
||
*/
|
||
|
||
import { Asset, assetManager, sys } from 'cc';
|
||
import { EDITOR } from 'cc/env';
|
||
|
||
// ASM模块内存配置常量
|
||
const PAGESIZE = 65536; // 64KiB
|
||
const PAGECOUNT = 32 * 16; // 可根据需要调整
|
||
const MEMORYSIZE = PAGESIZE * PAGECOUNT; // 32 MiB
|
||
|
||
|
||
export interface WasmLoadOptions {
|
||
bundleName?: string;
|
||
fileName: string;
|
||
editorUuid?: string; // 编辑器内的资源UUID
|
||
supportAsm?: boolean; // 是否支持ASM回退
|
||
asmFileName?: string; // ASM文件名
|
||
asmEditorUuid?: string; // ASM文件的编辑器UUID
|
||
wasmFactory?: any; // WASM工厂函数
|
||
asmFactory?: any; // ASM工厂函数
|
||
}
|
||
|
||
export interface WasmLoadResult {
|
||
instance: any;
|
||
isWasm: boolean; // true为WASM,false为ASM
|
||
}
|
||
|
||
export class WasmLoader {
|
||
private static instance: WasmLoader;
|
||
private initialized = false;
|
||
|
||
private constructor() { }
|
||
|
||
public static getInstance(): WasmLoader {
|
||
if (!WasmLoader.instance) {
|
||
WasmLoader.instance = new WasmLoader();
|
||
}
|
||
return WasmLoader.instance;
|
||
}
|
||
|
||
/**
|
||
* 初始化WASM加载器
|
||
* 注册WASM/ASM文件的下载器和解析器
|
||
*/
|
||
public initialize(): void {
|
||
if (this.initialized) {
|
||
return;
|
||
}
|
||
|
||
// 判断当前平台是否支持加载WASM文件
|
||
// Cocos引擎目前暂不支持在iOS平台加载WASM文件
|
||
if (sys.hasFeature(sys.Feature.WASM) || (sys.isNative && sys.os !== sys.OS.IOS)) {
|
||
if (sys.isNative) {
|
||
// 原生平台
|
||
//@ts-ignore
|
||
assetManager.downloader.register('.wasm', assetManager.downloader._downloaders[".bin"]);
|
||
//@ts-ignore
|
||
assetManager.parser.register('.wasm', assetManager.parser._parsers[".bin"]);
|
||
} else if (sys.isBrowser || sys.platform === sys.Platform.WECHAT_GAME) {
|
||
// 浏览器或微信小游戏平台
|
||
//@ts-ignore
|
||
assetManager.downloader.register('.wasm', assetManager.downloader._downloadArrayBuffer);
|
||
//@ts-ignore
|
||
assetManager.downloader.register('.mem', assetManager.downloader._downloadArrayBuffer);
|
||
}
|
||
} else {
|
||
// 不支持WASM的平台,注册ASM相关文件
|
||
if (sys.isNative) {
|
||
//@ts-ignore
|
||
assetManager.downloader.register('.mem', assetManager.downloader._downloaders[".bin"]);
|
||
//@ts-ignore
|
||
assetManager.parser.register('.mem', assetManager.parser._parsers[".bin"]);
|
||
}
|
||
}
|
||
|
||
this.initialized = true;
|
||
console.log('[WasmLoader] 初始化完成');
|
||
}
|
||
|
||
/**
|
||
* 加载WASM模块
|
||
* @param options 加载配置选项(必须包含工厂函数)
|
||
* @returns Promise<WasmLoadResult>
|
||
*/
|
||
public async loadWasmModule(options: WasmLoadOptions): Promise<WasmLoadResult> {
|
||
if (!this.initialized) {
|
||
this.initialize();
|
||
}
|
||
|
||
// 判断是否支持WASM
|
||
if (sys.hasFeature(sys.Feature.WASM) || (sys.isNative && sys.os !== sys.OS.IOS)) {
|
||
// 加载WASM
|
||
return this.loadWasm(options);
|
||
} else if (options.supportAsm && options.asmFileName && options.asmFactory) {
|
||
// 回退到ASM
|
||
return this.loadAsm(options);
|
||
} else {
|
||
throw new Error('当前平台不支持WASM,且未配置ASM回退选项');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载WASM
|
||
*/
|
||
private async loadWasm(options: WasmLoadOptions): Promise<WasmLoadResult> {
|
||
if (!options.wasmFactory) {
|
||
throw new Error('WASM工厂函数未提供');
|
||
}
|
||
|
||
try {
|
||
const wasmFactory = options.wasmFactory;
|
||
|
||
// 加载WASM二进制文件
|
||
const wasmFile = await this.loadWasmFile(
|
||
options.bundleName || '',
|
||
options.fileName,
|
||
options.editorUuid
|
||
);
|
||
|
||
// 初始化WASM
|
||
const instance = await this.initWasm(wasmFactory, wasmFile);
|
||
|
||
return {
|
||
instance,
|
||
isWasm: true
|
||
};
|
||
} catch (error) {
|
||
console.error('[WasmLoader] WASM加载失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载ASM
|
||
*/
|
||
private async loadAsm(options: WasmLoadOptions): Promise<WasmLoadResult> {
|
||
if (!options.asmFactory) {
|
||
throw new Error('ASM工厂函数未提供');
|
||
}
|
||
|
||
try {
|
||
const asmFactory = options.asmFactory;
|
||
|
||
// 加载ASM内存文件
|
||
const asmFile = await this.loadWasmFile(
|
||
options.bundleName || '',
|
||
options.asmFileName || options.fileName,
|
||
options.asmEditorUuid
|
||
);
|
||
|
||
// 初始化ASM
|
||
const instance = await this.initAsm(asmFactory, asmFile);
|
||
|
||
return {
|
||
instance,
|
||
isWasm: false
|
||
};
|
||
} catch (error) {
|
||
console.error('[WasmLoader] ASM加载失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载WASM/ASM二进制文件
|
||
*/
|
||
private loadWasmFile(bundleName: string, fileName: string, editorUuid?: string): Promise<Asset> {
|
||
return new Promise<Asset>((resolve, reject) => {
|
||
if (EDITOR) {
|
||
// 编辑器内通过UUID加载资源
|
||
if (editorUuid) {
|
||
assetManager.loadAny(editorUuid, (err, file: Asset) => {
|
||
if (!err) {
|
||
resolve(file);
|
||
} else {
|
||
reject(err);
|
||
}
|
||
});
|
||
} else {
|
||
reject(new Error('编辑器环境下需要提供editorUuid'));
|
||
}
|
||
} else {
|
||
// 运行时通过Bundle加载
|
||
if (bundleName && fileName) {
|
||
assetManager.loadBundle(bundleName, (err, bundle) => {
|
||
if (!err) {
|
||
bundle.load(fileName, Asset, (err2: any, file: Asset) => {
|
||
if (!err2) {
|
||
resolve(file);
|
||
} else {
|
||
reject(err2);
|
||
}
|
||
});
|
||
} else {
|
||
reject(err);
|
||
}
|
||
});
|
||
} else {
|
||
reject(new Error('运行时环境下需要提供bundleName和fileName'));
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 初始化WASM模块
|
||
*/
|
||
private initWasm(wasmFactory: any, wasmFile: Asset): Promise<any> {
|
||
const self = this;
|
||
return new Promise<any>((resolve, reject) => {
|
||
wasmFactory({
|
||
instantiateWasm(importObject: WebAssembly.Imports, receiveInstance: any) {
|
||
self.instantiateWasm(wasmFile, importObject).then((result) => {
|
||
receiveInstance(result.instance, result.module);
|
||
}).catch((err) => reject(err));
|
||
}
|
||
}).then((instance: any) => {
|
||
resolve(instance);
|
||
}).catch((err: any) => reject(err));
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 实例化WASM
|
||
*/
|
||
private instantiateWasm(wasmFile: Asset, importObject: WebAssembly.Imports): Promise<any> {
|
||
if (sys.isBrowser || sys.isNative) {
|
||
//@ts-ignore
|
||
return WebAssembly.instantiate(wasmFile._file, importObject);
|
||
} else if (sys.platform === sys.Platform.WECHAT_GAME) {
|
||
//@ts-ignore
|
||
return CCWebAssembly.instantiate(wasmFile.nativeUrl, importObject);
|
||
} else {
|
||
return Promise.reject(new Error('不支持的平台'));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化ASM模块
|
||
*/
|
||
private initAsm(asmFactory: any, asmFile: Asset): Promise<any> {
|
||
const asmMemory: any = {};
|
||
asmMemory.buffer = new ArrayBuffer(MEMORYSIZE);
|
||
|
||
const module = {
|
||
asmMemory,
|
||
memoryInitializerRequest: {
|
||
//@ts-ignore
|
||
response: asmFile._file,
|
||
status: 200,
|
||
} as Partial<XMLHttpRequest>,
|
||
};
|
||
|
||
return asmFactory(module);
|
||
}
|
||
|
||
/**
|
||
* 便捷方法:加载简单的WASM模块(仅WASM,不支持ASM回退)
|
||
*/
|
||
public async loadSimpleWasm(wasmFactory: any, fileName: string, editorUuid?: string, bundleName?: string): Promise<any> {
|
||
const result = await this.loadWasmModule({
|
||
fileName,
|
||
editorUuid,
|
||
bundleName,
|
||
supportAsm: false,
|
||
wasmFactory
|
||
});
|
||
return result.instance;
|
||
}
|
||
|
||
/**
|
||
* 便捷方法:加载支持ASM回退的WASM模块
|
||
*/
|
||
public async loadWasmWithAsmFallback(
|
||
wasmFactory: any,
|
||
asmFactory: any,
|
||
fileName: string,
|
||
asmFileName: string,
|
||
editorUuid?: string,
|
||
asmEditorUuid?: string,
|
||
bundleName?: string
|
||
): Promise<WasmLoadResult> {
|
||
return this.loadWasmModule({
|
||
fileName,
|
||
editorUuid,
|
||
bundleName,
|
||
supportAsm: true,
|
||
asmFileName,
|
||
asmEditorUuid,
|
||
wasmFactory,
|
||
asmFactory
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 检查当前平台是否支持WASM
|
||
*/
|
||
public isWasmSupported(): boolean {
|
||
return sys.hasFeature(sys.Feature.WASM) || (sys.isNative && sys.os !== sys.OS.IOS);
|
||
}
|
||
|
||
/**
|
||
* 获取推荐的加载策略
|
||
*/
|
||
public getRecommendedStrategy(): 'wasm' | 'asm' | 'unsupported' {
|
||
if (this.isWasmSupported()) {
|
||
return 'wasm';
|
||
} else if (sys.isNative || sys.isBrowser) {
|
||
return 'asm';
|
||
} else {
|
||
return 'unsupported';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 导出单例实例
|
||
export const wasmLoader = WasmLoader.getInstance(); |