feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 (#228)

* feat: 集成Rust WASM渲染引擎与TypeScript ECS框架

* feat: 增强编辑器UI功能与跨平台支持

* fix: 修复CI测试和类型检查问题

* fix: 修复CI问题并提高测试覆盖率

* fix: 修复CI问题并提高测试覆盖率
This commit is contained in:
YHH
2025-11-21 10:03:18 +08:00
committed by GitHub
parent 8b9616837d
commit a768b890fd
107 changed files with 10221 additions and 477 deletions

View File

@@ -0,0 +1,174 @@
/**
* Web 平台 Canvas 子系统
*/
import type {
IPlatformCanvasSubsystem,
IPlatformCanvas,
IPlatformImage,
TempFilePathOptions,
CanvasContextAttributes
} from '@esengine/platform-common';
/**
* Web Canvas 包装
*/
class WebCanvas implements IPlatformCanvas {
private _canvas: HTMLCanvasElement;
constructor(canvas: HTMLCanvasElement) {
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 attrs: WebGLContextAttributes | undefined = contextAttributes ? {
alpha: typeof contextAttributes.alpha === 'number'
? contextAttributes.alpha > 0
: contextAttributes.alpha,
antialias: contextAttributes.antialias,
depth: contextAttributes.depth,
stencil: contextAttributes.stencil,
premultipliedAlpha: contextAttributes.premultipliedAlpha,
preserveDrawingBuffer: contextAttributes.preserveDrawingBuffer,
failIfMajorPerformanceCaveat: contextAttributes.failIfMajorPerformanceCaveat,
powerPreference: contextAttributes.powerPreference
} : undefined;
return this._canvas.getContext(contextType, attrs);
}
toDataURL(): string {
return this._canvas.toDataURL();
}
toTempFilePath(_options: TempFilePathOptions): void {
throw new Error('toTempFilePath is not supported on Web platform');
}
getNativeCanvas(): HTMLCanvasElement {
return this._canvas;
}
}
/**
* Web Image 包装
*/
class WebImage implements IPlatformImage {
private _image: HTMLImageElement;
constructor() {
this._image = new 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;
}
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;
}
getNativeImage(): HTMLImageElement {
return this._image;
}
}
/**
* Web 平台 Canvas 子系统实现
*/
export class WebCanvasSubsystem implements IPlatformCanvasSubsystem {
private _mainCanvas: WebCanvas | null = null;
createCanvas(width?: number, height?: number): IPlatformCanvas {
const canvas = document.createElement('canvas');
if (width !== undefined) {
canvas.width = width;
}
if (height !== undefined) {
canvas.height = height;
}
const wrappedCanvas = new WebCanvas(canvas);
if (!this._mainCanvas) {
this._mainCanvas = wrappedCanvas;
}
return wrappedCanvas;
}
createImage(): IPlatformImage {
return new WebImage();
}
createImageData(width: number, height: number): ImageData {
return new ImageData(width, height);
}
getScreenWidth(): number {
return window.screen.width;
}
getScreenHeight(): number {
return window.screen.height;
}
getDevicePixelRatio(): number {
return window.devicePixelRatio || 1;
}
getMainCanvas(): IPlatformCanvas | null {
return this._mainCanvas;
}
getWindowWidth(): number {
return window.innerWidth;
}
getWindowHeight(): number {
return window.innerHeight;
}
}

View File

@@ -0,0 +1,102 @@
/**
* Web 平台输入子系统
*/
import type {
IPlatformInputSubsystem,
TouchHandler,
TouchEvent
} from '@esengine/platform-common';
/**
* Web 平台输入子系统实现
*/
export class WebInputSubsystem implements IPlatformInputSubsystem {
private _touchStartHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
private _touchMoveHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
private _touchEndHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
private _touchCancelHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
onTouchStart(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchStartHandlers.set(handler, nativeHandler);
window.addEventListener('touchstart', nativeHandler);
}
onTouchMove(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchMoveHandlers.set(handler, nativeHandler);
window.addEventListener('touchmove', nativeHandler);
}
onTouchEnd(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchEndHandlers.set(handler, nativeHandler);
window.addEventListener('touchend', nativeHandler);
}
onTouchCancel(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchCancelHandlers.set(handler, nativeHandler);
window.addEventListener('touchcancel', nativeHandler);
}
offTouchStart(handler: TouchHandler): void {
const nativeHandler = this._touchStartHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchstart', nativeHandler);
this._touchStartHandlers.delete(handler);
}
}
offTouchMove(handler: TouchHandler): void {
const nativeHandler = this._touchMoveHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchmove', nativeHandler);
this._touchMoveHandlers.delete(handler);
}
}
offTouchEnd(handler: TouchHandler): void {
const nativeHandler = this._touchEndHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchend', nativeHandler);
this._touchEndHandlers.delete(handler);
}
}
offTouchCancel(handler: TouchHandler): void {
const nativeHandler = this._touchCancelHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchcancel', nativeHandler);
this._touchCancelHandlers.delete(handler);
}
}
supportsPressure(): boolean {
return 'force' in Touch.prototype;
}
private convertTouchEvent(e: globalThis.TouchEvent): TouchEvent {
const convertTouch = (touch: globalThis.Touch) => ({
identifier: touch.identifier,
x: touch.clientX,
y: touch.clientY,
force: (touch as any).force
});
return {
touches: Array.from(e.touches).map(convertTouch),
changedTouches: Array.from(e.changedTouches).map(convertTouch),
timeStamp: e.timeStamp
};
}
}

View File

@@ -0,0 +1,77 @@
/**
* Web 平台存储子系统
*/
import type {
IPlatformStorageSubsystem,
StorageInfo
} from '@esengine/platform-common';
/**
* Web 平台存储子系统实现
*/
export class WebStorageSubsystem implements IPlatformStorageSubsystem {
getStorageSync<T = any>(key: string): T | undefined {
try {
const value = localStorage.getItem(key);
if (value === null) {
return undefined;
}
return JSON.parse(value) as T;
} catch {
return undefined;
}
}
setStorageSync<T = any>(key: string, value: T): void {
localStorage.setItem(key, JSON.stringify(value));
}
removeStorageSync(key: string): void {
localStorage.removeItem(key);
}
clearStorageSync(): void {
localStorage.clear();
}
getStorageInfoSync(): StorageInfo {
const keys: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) {
keys.push(key);
}
}
let currentSize = 0;
for (const key of keys) {
const value = localStorage.getItem(key);
if (value) {
currentSize += key.length + value.length;
}
}
return {
keys,
currentSize: Math.ceil(currentSize / 1024),
limitSize: 5 * 1024
};
}
async getStorage<T = any>(key: string): Promise<T | undefined> {
return this.getStorageSync<T>(key);
}
async setStorage<T = any>(key: string, value: T): Promise<void> {
this.setStorageSync(key, value);
}
async removeStorage(key: string): Promise<void> {
this.removeStorageSync(key);
}
async clearStorage(): Promise<void> {
this.clearStorageSync();
}
}

View File

@@ -0,0 +1,44 @@
/**
* Web 平台 WASM 子系统
*/
import type {
IPlatformWASMSubsystem,
IWASMInstance,
WASMImports,
WASMExports
} from '@esengine/platform-common';
/**
* Web 平台 WASM 子系统实现
*/
export class WebWASMSubsystem implements IPlatformWASMSubsystem {
async instantiate(path: string, imports?: WASMImports): Promise<IWASMInstance> {
const response = await fetch(path);
const buffer = await response.arrayBuffer();
const result = await WebAssembly.instantiate(buffer, imports);
return {
exports: result.instance.exports as WASMExports
};
}
isSupported(): boolean {
return typeof WebAssembly !== 'undefined';
}
createMemory(initial: number, maximum?: number): WebAssembly.Memory {
return new WebAssembly.Memory({
initial,
maximum
});
}
createTable(initial: number, maximum?: number): WebAssembly.Table {
return new WebAssembly.Table({
element: 'anyfunc',
initial,
maximum
});
}
}