refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 微信小游戏音频子系统
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformAudioSubsystem,
|
||||
IPlatformAudioContext
|
||||
} from '@esengine/platform-common';
|
||||
import { getWx, promisify } from '../utils';
|
||||
|
||||
/**
|
||||
* 微信音频上下文包装
|
||||
*/
|
||||
class WeChatAudioContext implements IPlatformAudioContext {
|
||||
private _ctx: WechatMinigame.InnerAudioContext;
|
||||
|
||||
constructor(ctx: WechatMinigame.InnerAudioContext) {
|
||||
this._ctx = ctx;
|
||||
}
|
||||
|
||||
get src(): string { return this._ctx.src; }
|
||||
set src(value: string) { this._ctx.src = value; }
|
||||
|
||||
get autoplay(): boolean { return this._ctx.autoplay; }
|
||||
set autoplay(value: boolean) { this._ctx.autoplay = value; }
|
||||
|
||||
get loop(): boolean { return this._ctx.loop; }
|
||||
set loop(value: boolean) { this._ctx.loop = value; }
|
||||
|
||||
get volume(): number { return this._ctx.volume; }
|
||||
set volume(value: number) { this._ctx.volume = value; }
|
||||
|
||||
get duration(): number { return this._ctx.duration; }
|
||||
get currentTime(): number { return this._ctx.currentTime; }
|
||||
get paused(): boolean { return this._ctx.paused; }
|
||||
get buffered(): number { return this._ctx.buffered; }
|
||||
|
||||
play(): void { this._ctx.play(); }
|
||||
pause(): void { this._ctx.pause(); }
|
||||
stop(): void { this._ctx.stop(); }
|
||||
seek(position: number): void { this._ctx.seek(position); }
|
||||
destroy(): void { this._ctx.destroy(); }
|
||||
|
||||
onPlay(callback: () => void): void { this._ctx.onPlay(callback); }
|
||||
onPause(callback: () => void): void { this._ctx.onPause(callback); }
|
||||
onStop(callback: () => void): void { this._ctx.onStop(callback); }
|
||||
onEnded(callback: () => void): void { this._ctx.onEnded(callback); }
|
||||
onError(callback: (error: { errCode: number; errMsg: string }) => void): void {
|
||||
this._ctx.onError(callback as any);
|
||||
}
|
||||
onTimeUpdate(callback: () => void): void { this._ctx.onTimeUpdate(callback); }
|
||||
onCanplay(callback: () => void): void { this._ctx.onCanplay(callback); }
|
||||
onSeeking(callback: () => void): void { this._ctx.onSeeking(callback); }
|
||||
onSeeked(callback: () => void): void { this._ctx.onSeeked(callback); }
|
||||
|
||||
offPlay(callback: () => void): void { this._ctx.offPlay(callback); }
|
||||
offPause(callback: () => void): void { this._ctx.offPause(callback); }
|
||||
offStop(callback: () => void): void { this._ctx.offStop(callback); }
|
||||
offEnded(callback: () => void): void { this._ctx.offEnded(callback); }
|
||||
offError(callback: (error: { errCode: number; errMsg: string }) => void): void {
|
||||
this._ctx.offError(callback as any);
|
||||
}
|
||||
offTimeUpdate(callback: () => void): void { this._ctx.offTimeUpdate(callback); }
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小游戏音频子系统实现
|
||||
*/
|
||||
export class WeChatAudioSubsystem implements IPlatformAudioSubsystem {
|
||||
createAudioContext(_options?: { useWebAudioImplement?: boolean }): IPlatformAudioContext {
|
||||
const ctx = getWx().createInnerAudioContext({
|
||||
useWebAudioImplement: _options?.useWebAudioImplement
|
||||
});
|
||||
return new WeChatAudioContext(ctx);
|
||||
}
|
||||
|
||||
getSupportedFormats(): string[] {
|
||||
return ['mp3', 'wav', 'aac', 'm4a'];
|
||||
}
|
||||
|
||||
async setInnerAudioOption(options: {
|
||||
mixWithOther?: boolean;
|
||||
obeyMuteSwitch?: boolean;
|
||||
speakerOn?: boolean;
|
||||
}): Promise<void> {
|
||||
return promisify(getWx().setInnerAudioOption.bind(getWx()), options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 微信小游戏 Canvas 子系统
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformCanvasSubsystem,
|
||||
IPlatformCanvas,
|
||||
IPlatformImage,
|
||||
TempFilePathOptions,
|
||||
CanvasContextAttributes
|
||||
} from '@esengine/platform-common';
|
||||
import { getWx } from '../utils';
|
||||
|
||||
/**
|
||||
* 微信小游戏 Canvas 包装
|
||||
*/
|
||||
class WeChatCanvas implements IPlatformCanvas {
|
||||
private _canvas: WechatMinigame.Canvas;
|
||||
|
||||
constructor(canvas: WechatMinigame.Canvas) {
|
||||
this._canvas = canvas;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this._canvas.width;
|
||||
}
|
||||
|
||||
set width(value: number) {
|
||||
this._canvas.width = value;
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this._canvas.height;
|
||||
}
|
||||
|
||||
set height(value: number) {
|
||||
this._canvas.height = value;
|
||||
}
|
||||
|
||||
getContext(
|
||||
contextType: '2d' | 'webgl' | 'webgl2',
|
||||
contextAttributes?: CanvasContextAttributes
|
||||
): RenderingContext | null {
|
||||
const wxAttributes: WechatMinigame.ContextAttributes | undefined = contextAttributes ? {
|
||||
alpha: typeof contextAttributes.alpha === 'boolean'
|
||||
? (contextAttributes.alpha ? 1 : 0)
|
||||
: contextAttributes.alpha,
|
||||
antialias: contextAttributes.antialias,
|
||||
preserveDrawingBuffer: contextAttributes.preserveDrawingBuffer,
|
||||
antialiasSamples: contextAttributes.antialiasSamples
|
||||
} : undefined;
|
||||
return this._canvas.getContext(contextType, wxAttributes);
|
||||
}
|
||||
|
||||
toDataURL(): string {
|
||||
return this._canvas.toDataURL();
|
||||
}
|
||||
|
||||
toTempFilePath(options: TempFilePathOptions): void {
|
||||
this._canvas.toTempFilePath({
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
destWidth: options.destWidth,
|
||||
destHeight: options.destHeight,
|
||||
fileType: options.fileType,
|
||||
quality: options.quality,
|
||||
success: options.success,
|
||||
fail: options.fail,
|
||||
complete: options.complete
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始微信 Canvas 对象
|
||||
*/
|
||||
getNativeCanvas(): WechatMinigame.Canvas {
|
||||
return this._canvas;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小游戏 Image 包装
|
||||
*/
|
||||
class WeChatImage implements IPlatformImage {
|
||||
private _image: WechatMinigame.Image;
|
||||
|
||||
constructor(image: WechatMinigame.Image) {
|
||||
this._image = image;
|
||||
}
|
||||
|
||||
get src(): string {
|
||||
return this._image.src;
|
||||
}
|
||||
|
||||
set src(value: string) {
|
||||
this._image.src = value;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this._image.width;
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this._image.height;
|
||||
}
|
||||
|
||||
get onload(): (() => void) | null {
|
||||
return this._image.onload as (() => void) | null;
|
||||
}
|
||||
|
||||
set onload(value: (() => void) | null) {
|
||||
this._image.onload = value as any;
|
||||
}
|
||||
|
||||
get onerror(): ((error: any) => void) | null {
|
||||
return this._image.onerror as ((error: any) => void) | null;
|
||||
}
|
||||
|
||||
set onerror(value: ((error: any) => void) | null) {
|
||||
this._image.onerror = value as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始微信 Image 对象
|
||||
*/
|
||||
getNativeImage(): WechatMinigame.Image {
|
||||
return this._image;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小游戏 Canvas 子系统实现
|
||||
*/
|
||||
export class WeChatCanvasSubsystem implements IPlatformCanvasSubsystem {
|
||||
private _mainCanvas: WeChatCanvas | null = null;
|
||||
private _windowInfo: WechatMinigame.WindowInfo;
|
||||
|
||||
constructor() {
|
||||
this._windowInfo = getWx().getWindowInfo();
|
||||
}
|
||||
|
||||
createCanvas(width?: number, height?: number): IPlatformCanvas {
|
||||
const canvas = getWx().createCanvas();
|
||||
|
||||
// 设置尺寸
|
||||
if (width !== undefined) {
|
||||
canvas.width = width;
|
||||
}
|
||||
if (height !== undefined) {
|
||||
canvas.height = height;
|
||||
}
|
||||
|
||||
const wrappedCanvas = new WeChatCanvas(canvas);
|
||||
|
||||
// 首次创建的是主 Canvas
|
||||
if (!this._mainCanvas) {
|
||||
this._mainCanvas = wrappedCanvas;
|
||||
}
|
||||
|
||||
return wrappedCanvas;
|
||||
}
|
||||
|
||||
createImage(): IPlatformImage {
|
||||
const image = getWx().createImage();
|
||||
return new WeChatImage(image);
|
||||
}
|
||||
|
||||
createImageData(width: number, height: number): ImageData {
|
||||
// 微信小游戏 3.4.10+ 支持 createImageData
|
||||
if (typeof getWx().createImageData === 'function') {
|
||||
return getWx().createImageData(width, height) as unknown as ImageData;
|
||||
}
|
||||
|
||||
// 降级方案:创建标准 ImageData
|
||||
const data = new Uint8ClampedArray(width * height * 4);
|
||||
return {
|
||||
data,
|
||||
width,
|
||||
height,
|
||||
colorSpace: 'srgb'
|
||||
} as ImageData;
|
||||
}
|
||||
|
||||
getScreenWidth(): number {
|
||||
return this._windowInfo.screenWidth;
|
||||
}
|
||||
|
||||
getScreenHeight(): number {
|
||||
return this._windowInfo.screenHeight;
|
||||
}
|
||||
|
||||
getDevicePixelRatio(): number {
|
||||
return this._windowInfo.pixelRatio;
|
||||
}
|
||||
|
||||
getMainCanvas(): IPlatformCanvas | null {
|
||||
return this._mainCanvas;
|
||||
}
|
||||
|
||||
getWindowWidth(): number {
|
||||
return this._windowInfo.windowWidth;
|
||||
}
|
||||
|
||||
getWindowHeight(): number {
|
||||
return this._windowInfo.windowHeight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* 微信小游戏文件系统子系统
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformFileSubsystem,
|
||||
FileInfo
|
||||
} from '@esengine/platform-common';
|
||||
import { getWx } from '../utils';
|
||||
|
||||
/**
|
||||
* 微信小游戏文件系统子系统实现
|
||||
*/
|
||||
export class WeChatFileSubsystem implements IPlatformFileSubsystem {
|
||||
private _fs: WechatMinigame.FileSystemManager;
|
||||
|
||||
constructor() {
|
||||
this._fs = getWx().getFileSystemManager();
|
||||
}
|
||||
|
||||
async readFile(options: {
|
||||
filePath: string;
|
||||
encoding?: 'ascii' | 'base64' | 'binary' | 'hex' | 'ucs2' | 'utf-8' | 'utf8';
|
||||
position?: number;
|
||||
length?: number;
|
||||
}): Promise<string | ArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.readFile({
|
||||
filePath: options.filePath,
|
||||
encoding: options.encoding as any,
|
||||
position: options.position,
|
||||
length: options.length,
|
||||
success: (res) => resolve(res.data),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
readFileSync(
|
||||
filePath: string,
|
||||
encoding?: 'ascii' | 'base64' | 'binary' | 'hex' | 'ucs2' | 'utf-8' | 'utf8',
|
||||
position?: number,
|
||||
length?: number
|
||||
): string | ArrayBuffer {
|
||||
return this._fs.readFileSync(filePath, encoding as any, position, length);
|
||||
}
|
||||
|
||||
async writeFile(options: {
|
||||
filePath: string;
|
||||
data: string | ArrayBuffer;
|
||||
encoding?: 'ascii' | 'base64' | 'binary' | 'hex' | 'ucs2' | 'utf-8' | 'utf8';
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.writeFile({
|
||||
filePath: options.filePath,
|
||||
data: options.data,
|
||||
encoding: options.encoding as any,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
filePath: string,
|
||||
data: string | ArrayBuffer,
|
||||
encoding?: 'ascii' | 'base64' | 'binary' | 'hex' | 'ucs2' | 'utf-8' | 'utf8'
|
||||
): void {
|
||||
this._fs.writeFileSync(filePath, data, encoding as any);
|
||||
}
|
||||
|
||||
async appendFile(options: {
|
||||
filePath: string;
|
||||
data: string | ArrayBuffer;
|
||||
encoding?: 'ascii' | 'base64' | 'binary' | 'hex' | 'ucs2' | 'utf-8' | 'utf8';
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.appendFile({
|
||||
filePath: options.filePath,
|
||||
data: options.data,
|
||||
encoding: options.encoding as any,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async unlink(filePath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.unlink({
|
||||
filePath,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async mkdir(options: {
|
||||
dirPath: string;
|
||||
recursive?: boolean;
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.mkdir({
|
||||
dirPath: options.dirPath,
|
||||
recursive: options.recursive,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async rmdir(options: {
|
||||
dirPath: string;
|
||||
recursive?: boolean;
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.rmdir({
|
||||
dirPath: options.dirPath,
|
||||
recursive: options.recursive,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async readdir(dirPath: string): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.readdir({
|
||||
dirPath,
|
||||
success: (res) => resolve(res.files),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async stat(path: string): Promise<FileInfo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.stat({
|
||||
path,
|
||||
success: (res) => {
|
||||
const stats = res.stats as WechatMinigame.Stats;
|
||||
resolve({
|
||||
size: stats.size,
|
||||
createTime: stats.lastAccessedTime,
|
||||
modifyTime: stats.lastModifiedTime,
|
||||
isDirectory: stats.isDirectory(),
|
||||
isFile: stats.isFile()
|
||||
});
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async access(path: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.access({
|
||||
path,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async rename(oldPath: string, newPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.rename({
|
||||
oldPath,
|
||||
newPath,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async copyFile(srcPath: string, destPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.copyFile({
|
||||
srcPath,
|
||||
destPath,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUserDataPath(): string {
|
||||
return `${getWx().env.USER_DATA_PATH}`;
|
||||
}
|
||||
|
||||
async unzip(options: {
|
||||
zipFilePath: string;
|
||||
targetPath: string;
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fs.unzip({
|
||||
zipFilePath: options.zipFilePath,
|
||||
targetPath: options.targetPath,
|
||||
success: () => resolve(),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 微信小游戏输入子系统
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformInputSubsystem,
|
||||
TouchHandler,
|
||||
TouchEvent
|
||||
} from '@esengine/platform-common';
|
||||
import { getWx } from '../utils';
|
||||
|
||||
/**
|
||||
* 微信小游戏输入子系统实现
|
||||
*/
|
||||
export class WeChatInputSubsystem implements IPlatformInputSubsystem {
|
||||
onTouchStart(handler: TouchHandler): void {
|
||||
getWx().onTouchStart((res) => {
|
||||
handler(this.convertTouchEvent(res));
|
||||
});
|
||||
}
|
||||
|
||||
onTouchMove(handler: TouchHandler): void {
|
||||
getWx().onTouchMove((res) => {
|
||||
handler(this.convertTouchEvent(res));
|
||||
});
|
||||
}
|
||||
|
||||
onTouchEnd(handler: TouchHandler): void {
|
||||
getWx().onTouchEnd((res) => {
|
||||
handler(this.convertTouchEvent(res));
|
||||
});
|
||||
}
|
||||
|
||||
onTouchCancel(handler: TouchHandler): void {
|
||||
getWx().onTouchCancel((res) => {
|
||||
handler(this.convertTouchEvent(res));
|
||||
});
|
||||
}
|
||||
|
||||
offTouchStart(handler: TouchHandler): void {
|
||||
getWx().offTouchStart(handler as any);
|
||||
}
|
||||
|
||||
offTouchMove(handler: TouchHandler): void {
|
||||
getWx().offTouchMove(handler as any);
|
||||
}
|
||||
|
||||
offTouchEnd(handler: TouchHandler): void {
|
||||
getWx().offTouchEnd(handler as any);
|
||||
}
|
||||
|
||||
offTouchCancel(handler: TouchHandler): void {
|
||||
getWx().offTouchCancel(handler as any);
|
||||
}
|
||||
|
||||
supportsPressure(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private convertTouchEvent(res: WechatMinigame.OnTouchStartListenerResult): TouchEvent {
|
||||
return {
|
||||
touches: res.touches.map((t: WechatMinigame.Touch) => ({
|
||||
identifier: t.identifier,
|
||||
x: t.clientX,
|
||||
y: t.clientY,
|
||||
force: t.force
|
||||
})),
|
||||
changedTouches: res.changedTouches.map((t: WechatMinigame.Touch) => ({
|
||||
identifier: t.identifier,
|
||||
x: t.clientX,
|
||||
y: t.clientY,
|
||||
force: t.force
|
||||
})),
|
||||
timeStamp: res.timeStamp
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* 微信小游戏网络子系统
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformNetworkSubsystem,
|
||||
RequestConfig,
|
||||
RequestResponse,
|
||||
IDownloadTask,
|
||||
IUploadTask,
|
||||
IPlatformWebSocket
|
||||
} from '@esengine/platform-common';
|
||||
import { getWx, promisify } from '../utils';
|
||||
|
||||
/**
|
||||
* 微信 WebSocket 包装
|
||||
*/
|
||||
class WeChatWebSocket implements IPlatformWebSocket {
|
||||
private _task: WechatMinigame.SocketTask;
|
||||
|
||||
constructor(task: WechatMinigame.SocketTask) {
|
||||
this._task = task;
|
||||
}
|
||||
|
||||
send(data: string | ArrayBuffer): void {
|
||||
this._task.send({ data });
|
||||
}
|
||||
|
||||
close(code?: number, reason?: string): void {
|
||||
this._task.close({ code, reason });
|
||||
}
|
||||
|
||||
onOpen(callback: (res: { header: Record<string, string> }) => void): void {
|
||||
this._task.onOpen(callback as any);
|
||||
}
|
||||
|
||||
onClose(callback: (res: { code: number; reason: string }) => void): void {
|
||||
this._task.onClose(callback as any);
|
||||
}
|
||||
|
||||
onError(callback: (error: any) => void): void {
|
||||
this._task.onError(callback);
|
||||
}
|
||||
|
||||
onMessage(callback: (res: { data: string | ArrayBuffer }) => void): void {
|
||||
this._task.onMessage(callback as any);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小游戏网络子系统实现
|
||||
*/
|
||||
export class WeChatNetworkSubsystem implements IPlatformNetworkSubsystem {
|
||||
async request<T = any>(config: RequestConfig): Promise<RequestResponse<T>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
getWx().request({
|
||||
url: config.url,
|
||||
method: config.method as any,
|
||||
data: config.data,
|
||||
header: config.header,
|
||||
timeout: config.timeout,
|
||||
dataType: config.dataType as any,
|
||||
responseType: config.responseType as any,
|
||||
success: (res) => {
|
||||
resolve({
|
||||
data: res.data as T,
|
||||
statusCode: res.statusCode,
|
||||
header: res.header as Record<string, string>
|
||||
});
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile(options: {
|
||||
url: string;
|
||||
filePath?: string;
|
||||
header?: Record<string, string>;
|
||||
timeout?: number;
|
||||
}): Promise<{ tempFilePath: string; filePath?: string; statusCode: number }> & IDownloadTask {
|
||||
const task = getWx().downloadFile({
|
||||
url: options.url,
|
||||
filePath: options.filePath,
|
||||
header: options.header,
|
||||
timeout: options.timeout,
|
||||
success: () => {},
|
||||
fail: () => {}
|
||||
});
|
||||
|
||||
const promise = new Promise<{ tempFilePath: string; filePath?: string; statusCode: number }>((resolve, reject) => {
|
||||
task.onProgressUpdate(() => {});
|
||||
getWx().downloadFile({
|
||||
...options,
|
||||
success: (res) => resolve({
|
||||
tempFilePath: res.tempFilePath,
|
||||
filePath: res.filePath,
|
||||
statusCode: res.statusCode
|
||||
}),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
return Object.assign(promise, {
|
||||
abort: () => task.abort(),
|
||||
onProgressUpdate: (callback: any) => task.onProgressUpdate(callback),
|
||||
offProgressUpdate: (callback: any) => task.offProgressUpdate(callback)
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile(options: {
|
||||
url: string;
|
||||
filePath: string;
|
||||
name: string;
|
||||
header?: Record<string, string>;
|
||||
formData?: Record<string, any>;
|
||||
timeout?: number;
|
||||
}): Promise<{ data: string; statusCode: number }> & IUploadTask {
|
||||
const task = getWx().uploadFile({
|
||||
url: options.url,
|
||||
filePath: options.filePath,
|
||||
name: options.name,
|
||||
header: options.header,
|
||||
formData: options.formData,
|
||||
timeout: options.timeout,
|
||||
success: () => {},
|
||||
fail: () => {}
|
||||
});
|
||||
|
||||
const promise = new Promise<{ data: string; statusCode: number }>((resolve, reject) => {
|
||||
getWx().uploadFile({
|
||||
...options,
|
||||
success: (res) => resolve({
|
||||
data: res.data,
|
||||
statusCode: res.statusCode
|
||||
}),
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
return Object.assign(promise, {
|
||||
abort: () => task.abort(),
|
||||
onProgressUpdate: (callback: any) => task.onProgressUpdate(callback),
|
||||
offProgressUpdate: (callback: any) => task.offProgressUpdate(callback)
|
||||
});
|
||||
}
|
||||
|
||||
connectSocket(options: {
|
||||
url: string;
|
||||
header?: Record<string, string>;
|
||||
protocols?: string[];
|
||||
timeout?: number;
|
||||
}): IPlatformWebSocket {
|
||||
const task = getWx().connectSocket({
|
||||
url: options.url,
|
||||
header: options.header,
|
||||
protocols: options.protocols,
|
||||
timeout: options.timeout
|
||||
});
|
||||
return new WeChatWebSocket(task);
|
||||
}
|
||||
|
||||
async getNetworkType(): Promise<'wifi' | '2g' | '3g' | '4g' | '5g' | 'unknown' | 'none'> {
|
||||
const res = await promisify<{ networkType: string }>(
|
||||
getWx().getNetworkType.bind(getWx()),
|
||||
{}
|
||||
);
|
||||
return res.networkType as any;
|
||||
}
|
||||
|
||||
onNetworkStatusChange(callback: (res: {
|
||||
isConnected: boolean;
|
||||
networkType: string;
|
||||
}) => void): void {
|
||||
getWx().onNetworkStatusChange(callback);
|
||||
}
|
||||
|
||||
offNetworkStatusChange(callback: Function): void {
|
||||
getWx().offNetworkStatusChange(callback as any);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 微信小游戏存储子系统
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformStorageSubsystem,
|
||||
StorageInfo
|
||||
} from '@esengine/platform-common';
|
||||
import { getWx, promisify } from '../utils';
|
||||
|
||||
/**
|
||||
* 微信小游戏存储子系统实现
|
||||
*/
|
||||
export class WeChatStorageSubsystem implements IPlatformStorageSubsystem {
|
||||
getStorageSync<T = any>(key: string): T | undefined {
|
||||
try {
|
||||
return getWx().getStorageSync<T>(key);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
setStorageSync<T = any>(key: string, value: T): void {
|
||||
getWx().setStorageSync(key, value);
|
||||
}
|
||||
|
||||
removeStorageSync(key: string): void {
|
||||
getWx().removeStorageSync(key);
|
||||
}
|
||||
|
||||
clearStorageSync(): void {
|
||||
getWx().clearStorageSync();
|
||||
}
|
||||
|
||||
getStorageInfoSync(): StorageInfo {
|
||||
const info = getWx().getStorageInfoSync();
|
||||
return {
|
||||
keys: info.keys,
|
||||
currentSize: info.currentSize,
|
||||
limitSize: info.limitSize
|
||||
};
|
||||
}
|
||||
|
||||
async getStorage<T = any>(key: string): Promise<T | undefined> {
|
||||
try {
|
||||
const res = await promisify<{ data: T }>(
|
||||
getWx().getStorage.bind(getWx()),
|
||||
{ key }
|
||||
);
|
||||
return res.data;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async setStorage<T = any>(key: string, value: T): Promise<void> {
|
||||
await promisify(getWx().setStorage.bind(getWx()), { key, data: value });
|
||||
}
|
||||
|
||||
async removeStorage(key: string): Promise<void> {
|
||||
await promisify(getWx().removeStorage.bind(getWx()), { key });
|
||||
}
|
||||
|
||||
async clearStorage(): Promise<void> {
|
||||
await promisify(getWx().clearStorage.bind(getWx()), {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 微信小游戏 WASM 子系统
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformWASMSubsystem,
|
||||
IWASMInstance,
|
||||
WASMImports,
|
||||
WASMExports
|
||||
} from '@esengine/platform-common';
|
||||
|
||||
/**
|
||||
* 微信小游戏 WASM 子系统实现
|
||||
*/
|
||||
export class WeChatWASMSubsystem implements IPlatformWASMSubsystem {
|
||||
async instantiate(path: string, imports?: WASMImports): Promise<IWASMInstance> {
|
||||
// 微信小游戏使用 WXWebAssembly.instantiate
|
||||
// path 应该是相对于小游戏根目录的 .wasm 文件路径
|
||||
if (typeof WXWebAssembly === 'undefined') {
|
||||
throw new Error('当前微信基础库版本不支持 WebAssembly');
|
||||
}
|
||||
|
||||
const wxImports: WXWebAssembly.Imports | undefined = imports as WXWebAssembly.Imports | undefined;
|
||||
const instance = await WXWebAssembly.instantiate(path, wxImports);
|
||||
|
||||
return {
|
||||
exports: instance.exports as WASMExports
|
||||
};
|
||||
}
|
||||
|
||||
isSupported(): boolean {
|
||||
return typeof WXWebAssembly !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WASM 内存
|
||||
* 用于 Rust/WASM 引擎的内存交互
|
||||
*/
|
||||
createMemory(initial: number, maximum?: number): WebAssembly.Memory {
|
||||
if (typeof WXWebAssembly === 'undefined') {
|
||||
throw new Error('当前微信基础库版本不支持 WebAssembly');
|
||||
}
|
||||
|
||||
return new WXWebAssembly.Memory({
|
||||
initial,
|
||||
maximum,
|
||||
shared: false // 微信小游戏不支持 shared memory
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 WASM Table
|
||||
*/
|
||||
createTable(initial: number, maximum?: number): WebAssembly.Table {
|
||||
if (typeof WXWebAssembly === 'undefined') {
|
||||
throw new Error('当前微信基础库版本不支持 WebAssembly');
|
||||
}
|
||||
|
||||
return new WXWebAssembly.Table({
|
||||
element: 'anyfunc',
|
||||
initial,
|
||||
maximum
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user