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:
YHH
2025-12-26 14:50:35 +08:00
committed by GitHub
parent a84ff902e4
commit 155411e743
1936 changed files with 4147 additions and 11578 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,336 @@
/**
* Web 平台输入子系统
* Web platform input subsystem
*/
import type {
IPlatformInputSubsystem,
TouchHandler,
TouchEvent,
KeyboardHandler,
KeyboardEventInfo,
MouseHandler,
MouseEventInfo,
WheelHandler,
WheelEventInfo
} from '@esengine/platform-common';
import { MouseButton } from '@esengine/platform-common';
/**
* Web 平台输入子系统实现
* Web platform input subsystem implementation
*/
export class WebInputSubsystem implements IPlatformInputSubsystem {
// ========== Touch handlers ==========
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();
// ========== Keyboard handlers ==========
private _keyDownHandlers: Map<KeyboardHandler, (e: globalThis.KeyboardEvent) => void> = new Map();
private _keyUpHandlers: Map<KeyboardHandler, (e: globalThis.KeyboardEvent) => void> = new Map();
// ========== Mouse handlers ==========
private _mouseMoveHandlers: Map<MouseHandler, (e: globalThis.MouseEvent) => void> = new Map();
private _mouseDownHandlers: Map<MouseHandler, (e: globalThis.MouseEvent) => void> = new Map();
private _mouseUpHandlers: Map<MouseHandler, (e: globalThis.MouseEvent) => void> = new Map();
private _wheelHandlers: Map<WheelHandler, (e: globalThis.WheelEvent) => void> = new Map();
// ========== Touch events ==========
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 typeof Touch !== 'undefined' && 'force' in Touch.prototype;
}
// ========== Keyboard events ==========
onKeyDown(handler: KeyboardHandler): void {
const nativeHandler = (e: globalThis.KeyboardEvent) => {
handler(this.convertKeyboardEvent(e));
};
this._keyDownHandlers.set(handler, nativeHandler);
window.addEventListener('keydown', nativeHandler);
}
onKeyUp(handler: KeyboardHandler): void {
const nativeHandler = (e: globalThis.KeyboardEvent) => {
handler(this.convertKeyboardEvent(e));
};
this._keyUpHandlers.set(handler, nativeHandler);
window.addEventListener('keyup', nativeHandler);
}
offKeyDown(handler: KeyboardHandler): void {
const nativeHandler = this._keyDownHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('keydown', nativeHandler);
this._keyDownHandlers.delete(handler);
}
}
offKeyUp(handler: KeyboardHandler): void {
const nativeHandler = this._keyUpHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('keyup', nativeHandler);
this._keyUpHandlers.delete(handler);
}
}
// ========== Mouse events ==========
onMouseMove(handler: MouseHandler): void {
const nativeHandler = (e: globalThis.MouseEvent) => {
handler(this.convertMouseEvent(e));
};
this._mouseMoveHandlers.set(handler, nativeHandler);
window.addEventListener('mousemove', nativeHandler);
}
onMouseDown(handler: MouseHandler): void {
const nativeHandler = (e: globalThis.MouseEvent) => {
handler(this.convertMouseEvent(e));
};
this._mouseDownHandlers.set(handler, nativeHandler);
window.addEventListener('mousedown', nativeHandler);
}
onMouseUp(handler: MouseHandler): void {
const nativeHandler = (e: globalThis.MouseEvent) => {
handler(this.convertMouseEvent(e));
};
this._mouseUpHandlers.set(handler, nativeHandler);
window.addEventListener('mouseup', nativeHandler);
}
onWheel(handler: WheelHandler): void {
const nativeHandler = (e: globalThis.WheelEvent) => {
handler(this.convertWheelEvent(e));
};
this._wheelHandlers.set(handler, nativeHandler);
window.addEventListener('wheel', nativeHandler);
}
offMouseMove(handler: MouseHandler): void {
const nativeHandler = this._mouseMoveHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('mousemove', nativeHandler);
this._mouseMoveHandlers.delete(handler);
}
}
offMouseDown(handler: MouseHandler): void {
const nativeHandler = this._mouseDownHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('mousedown', nativeHandler);
this._mouseDownHandlers.delete(handler);
}
}
offMouseUp(handler: MouseHandler): void {
const nativeHandler = this._mouseUpHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('mouseup', nativeHandler);
this._mouseUpHandlers.delete(handler);
}
}
offWheel(handler: WheelHandler): void {
const nativeHandler = this._wheelHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('wheel', nativeHandler);
this._wheelHandlers.delete(handler);
}
}
// ========== Capability queries ==========
supportsKeyboard(): boolean {
return true;
}
supportsMouse(): boolean {
// 检测是否有鼠标设备 | Check if mouse device exists
return window.matchMedia('(pointer: fine)').matches;
}
// ========== Event converters ==========
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
};
}
private convertKeyboardEvent(e: globalThis.KeyboardEvent): KeyboardEventInfo {
return {
code: e.code,
key: e.key,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
metaKey: e.metaKey,
repeat: e.repeat,
timeStamp: e.timeStamp
};
}
private convertMouseEvent(e: globalThis.MouseEvent): MouseEventInfo {
return {
x: e.clientX,
y: e.clientY,
movementX: e.movementX,
movementY: e.movementY,
button: e.button as MouseButton,
buttons: e.buttons,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
metaKey: e.metaKey,
timeStamp: e.timeStamp
};
}
private convertWheelEvent(e: globalThis.WheelEvent): WheelEventInfo {
return {
x: e.clientX,
y: e.clientY,
deltaX: e.deltaX,
deltaY: e.deltaY,
deltaZ: e.deltaZ,
timeStamp: e.timeStamp
};
}
/**
* 销毁并移除所有事件监听器
* Dispose and remove all event listeners
*/
dispose(): void {
// 清理触摸事件 | Clean up touch events
this._touchStartHandlers.forEach((handler) => {
window.removeEventListener('touchstart', handler);
});
this._touchStartHandlers.clear();
this._touchMoveHandlers.forEach((handler) => {
window.removeEventListener('touchmove', handler);
});
this._touchMoveHandlers.clear();
this._touchEndHandlers.forEach((handler) => {
window.removeEventListener('touchend', handler);
});
this._touchEndHandlers.clear();
this._touchCancelHandlers.forEach((handler) => {
window.removeEventListener('touchcancel', handler);
});
this._touchCancelHandlers.clear();
// 清理键盘事件 | Clean up keyboard events
this._keyDownHandlers.forEach((handler) => {
window.removeEventListener('keydown', handler);
});
this._keyDownHandlers.clear();
this._keyUpHandlers.forEach((handler) => {
window.removeEventListener('keyup', handler);
});
this._keyUpHandlers.clear();
// 清理鼠标事件 | Clean up mouse events
this._mouseMoveHandlers.forEach((handler) => {
window.removeEventListener('mousemove', handler);
});
this._mouseMoveHandlers.clear();
this._mouseDownHandlers.forEach((handler) => {
window.removeEventListener('mousedown', handler);
});
this._mouseDownHandlers.clear();
this._mouseUpHandlers.forEach((handler) => {
window.removeEventListener('mouseup', handler);
});
this._mouseUpHandlers.clear();
this._wheelHandlers.forEach((handler) => {
window.removeEventListener('wheel', handler);
});
this._wheelHandlers.clear();
}
}

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