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,230 @@
/**
* Browser Platform Adapter
* 浏览器平台适配器
*
* 用于独立浏览器运行时的平台适配器
* Platform adapter for standalone browser runtime
*/
import type {
IPlatformAdapter,
IPathResolver,
PlatformCapabilities,
PlatformAdapterConfig
} from '../IPlatformAdapter';
import type { IPlatformInputSubsystem } from '@esengine/platform-common';
/**
* 浏览器路径解析模式
* Browser path resolve mode
*/
export type BrowserPathResolveMode = 'proxy' | 'direct';
/**
* 浏览器路径解析器
* Browser path resolver
*
* 支持两种模式:
* - 'proxy': 使用 /asset?path=... 格式(编辑器 "Run in Browser" 使用)
* - 'direct': 使用直接路径如 /assets/path.png独立 Web 构建使用)
*
* Supports two modes:
* - 'proxy': Uses /asset?path=... format (for editor "Run in Browser")
* - 'direct': Uses direct paths like /assets/path.png (for standalone web builds)
*/
export class BrowserPathResolver implements IPathResolver {
private _baseUrl: string;
private _mode: BrowserPathResolveMode;
constructor(baseUrl: string = '/assets', mode: BrowserPathResolveMode = 'proxy') {
this._baseUrl = baseUrl;
this._mode = mode;
}
resolve(path: string): string {
// 如果已经是完整 URL直接返回
// If already a full URL, return as-is
if (path.startsWith('http://') ||
path.startsWith('https://') ||
path.startsWith('data:') ||
path.startsWith('blob:') ||
path.startsWith('/asset?')) {
return path;
}
if (this._mode === 'proxy') {
// Proxy mode: use /asset?path=... format
// 代理模式:使用 /asset?path=... 格式
return `/asset?path=${encodeURIComponent(path)}`;
}
// Direct mode: use direct URL paths
// 直接模式:使用直接 URL 路径
// 规范化路径:移除 ./ 前缀,统一斜杠
// Normalize path: remove ./ prefix, unify slashes
let normalizedPath = path.replace(/\\/g, '/');
// 移除开头的 ./
if (normalizedPath.startsWith('./')) {
normalizedPath = normalizedPath.substring(2);
}
// 移除开头的 /
if (normalizedPath.startsWith('/')) {
normalizedPath = normalizedPath.substring(1);
}
// 如果路径以 assets/ 开头,移除它(避免与 baseUrl 重复)
// If path starts with assets/, remove it (avoid duplication with baseUrl)
if (normalizedPath.startsWith('assets/')) {
normalizedPath = normalizedPath.substring(7);
}
// 确保 baseUrl 没有尾部斜杠
// Ensure baseUrl has no trailing slash
const base = this._baseUrl.replace(/\/+$/, '');
return `${base}/${normalizedPath}`;
}
/**
* 更新基础 URL
* Update base URL
*/
setBaseUrl(baseUrl: string): void {
this._baseUrl = baseUrl;
}
/**
* 设置解析模式
* Set resolve mode
*/
setMode(mode: BrowserPathResolveMode): void {
this._mode = mode;
}
}
/**
* 浏览器平台适配器配置
* Browser platform adapter configuration
*/
export interface BrowserPlatformConfig {
/** WASM 模块(预加载的)| Pre-loaded WASM module */
wasmModule?: any;
/** WASM 模块加载器(异步加载)| Async WASM module loader */
wasmModuleLoader?: () => Promise<any>;
/** 资产基础 URL | Asset base URL */
assetBaseUrl?: string;
/**
* 路径解析模式 | Path resolve mode
* - 'proxy': 使用 /asset?path=... 格式(默认,编辑器使用)
* - 'direct': 使用直接路径(独立 Web 构建使用)
*/
pathResolveMode?: BrowserPathResolveMode;
/**
* 输入子系统工厂函数
* Input subsystem factory function
*/
inputSubsystemFactory?: () => IPlatformInputSubsystem;
}
/**
* 浏览器平台适配器
* Browser platform adapter
*/
export class BrowserPlatformAdapter implements IPlatformAdapter {
readonly name = 'browser';
readonly capabilities: PlatformCapabilities = {
fileSystem: false,
hotReload: false,
gizmos: false,
grid: false,
sceneEditing: false
};
private _pathResolver: BrowserPathResolver;
private _canvas: HTMLCanvasElement | null = null;
private _config: BrowserPlatformConfig;
private _viewportSize = { width: 0, height: 0 };
private _inputSubsystem: IPlatformInputSubsystem | null = null;
constructor(config: BrowserPlatformConfig = {}) {
this._config = config;
this._pathResolver = new BrowserPathResolver(
config.assetBaseUrl || '/assets',
config.pathResolveMode || 'proxy'
);
}
get pathResolver(): IPathResolver {
return this._pathResolver;
}
async initialize(config: PlatformAdapterConfig): Promise<void> {
// 获取 Canvas
this._canvas = document.getElementById(config.canvasId) as HTMLCanvasElement;
if (!this._canvas) {
throw new Error(`Canvas not found: ${config.canvasId}`);
}
// 设置尺寸
const width = config.width || window.innerWidth;
const height = config.height || window.innerHeight;
this._canvas.width = width;
this._canvas.height = height;
this._viewportSize = { width, height };
if (this._config.inputSubsystemFactory) {
this._inputSubsystem = this._config.inputSubsystemFactory();
}
}
async getWasmModule(): Promise<any> {
// 如果已提供模块,直接返回
if (this._config.wasmModule) {
return this._config.wasmModule;
}
// 如果提供了加载器,使用加载器
if (this._config.wasmModuleLoader) {
return this._config.wasmModuleLoader();
}
// 默认:尝试动态导入
throw new Error('No WASM module or loader provided');
}
getCanvas(): HTMLCanvasElement | null {
return this._canvas;
}
resize(width: number, height: number): void {
if (this._canvas) {
this._canvas.width = width;
this._canvas.height = height;
}
this._viewportSize = { width, height };
}
getViewportSize(): { width: number; height: number } {
return { ...this._viewportSize };
}
isEditorMode(): boolean {
return false;
}
getInputSubsystem(): IPlatformInputSubsystem | null {
return this._inputSubsystem;
}
dispose(): void {
if (this._inputSubsystem) {
this._inputSubsystem.dispose?.();
this._inputSubsystem = null;
}
this._canvas = null;
}
}

View File

@@ -0,0 +1,216 @@
/**
* Editor Platform Adapter
* 编辑器平台适配器
*
* 用于 Tauri 编辑器内嵌预览的平台适配器
* Platform adapter for Tauri editor embedded preview
*/
import type {
IPlatformAdapter,
IPathResolver,
PlatformCapabilities,
PlatformAdapterConfig
} from '../IPlatformAdapter';
import type { IPlatformInputSubsystem } from '@esengine/platform-common';
/**
* 编辑器路径解析器
* Editor path resolver
*
* 使用 Tauri 的 convertFileSrc 转换本地文件路径
* Uses Tauri's convertFileSrc to convert local file paths
*/
export class EditorPathResolver implements IPathResolver {
private _pathTransformer: (path: string) => string;
constructor(pathTransformer: (path: string) => string) {
this._pathTransformer = pathTransformer;
}
resolve(path: string): string {
// 如果已经是 URL直接返回
if (path.startsWith('http://') ||
path.startsWith('https://') ||
path.startsWith('data:') ||
path.startsWith('blob:') ||
path.startsWith('asset://')) {
return path;
}
// 使用 Tauri 路径转换器
return this._pathTransformer(path);
}
/**
* 更新路径转换器
*/
setPathTransformer(transformer: (path: string) => string): void {
this._pathTransformer = transformer;
}
}
/**
* 编辑器平台适配器配置
*/
export interface EditorPlatformConfig {
/** WASM 模块(预加载的)*/
wasmModule: any;
/** 路径转换函数(使用 Tauri 的 convertFileSrc*/
pathTransformer: (path: string) => string;
/** Gizmo 数据提供者 */
gizmoDataProvider?: (component: any, entity: any, isSelected: boolean) => any;
/** Gizmo 检查函数 */
hasGizmoProvider?: (component: any) => boolean;
/**
* 输入子系统工厂函数
* Input subsystem factory function
*
* 用于在 Play 模式下接收游戏输入
* Used to receive game input in Play mode
*/
inputSubsystemFactory?: () => IPlatformInputSubsystem;
}
/**
* 编辑器平台适配器
* Editor platform adapter
*/
export class EditorPlatformAdapter implements IPlatformAdapter {
readonly name = 'editor';
readonly capabilities: PlatformCapabilities = {
fileSystem: true,
hotReload: true,
gizmos: true,
grid: true,
sceneEditing: true
};
private _pathResolver: EditorPathResolver;
private _canvas: HTMLCanvasElement | null = null;
private _config: EditorPlatformConfig;
private _viewportSize = { width: 0, height: 0 };
private _showGrid = true;
private _showGizmos = true;
private _inputSubsystem: IPlatformInputSubsystem | null = null;
constructor(config: EditorPlatformConfig) {
this._config = config;
this._pathResolver = new EditorPathResolver(config.pathTransformer);
}
get pathResolver(): IPathResolver {
return this._pathResolver;
}
/**
* 获取 Gizmo 数据提供者
*/
get gizmoDataProvider() {
return this._config.gizmoDataProvider;
}
/**
* 获取 Gizmo 检查函数
*/
get hasGizmoProvider() {
return this._config.hasGizmoProvider;
}
async initialize(config: PlatformAdapterConfig): Promise<void> {
// 获取 Canvas
this._canvas = document.getElementById(config.canvasId) as HTMLCanvasElement;
if (!this._canvas) {
throw new Error(`Canvas not found: ${config.canvasId}`);
}
// 处理 DPR 缩放
const dpr = window.devicePixelRatio || 1;
const container = this._canvas.parentElement;
if (container) {
const rect = container.getBoundingClientRect();
const width = config.width || Math.floor(rect.width * dpr);
const height = config.height || Math.floor(rect.height * dpr);
this._canvas.width = width;
this._canvas.height = height;
this._canvas.style.width = `${rect.width}px`;
this._canvas.style.height = `${rect.height}px`;
this._viewportSize = { width, height };
} else {
const width = config.width || window.innerWidth;
const height = config.height || window.innerHeight;
this._canvas.width = width;
this._canvas.height = height;
this._viewportSize = { width, height };
}
// 创建输入子系统(如果提供了工厂函数)
// Create input subsystem (if factory provided)
if (this._config.inputSubsystemFactory) {
this._inputSubsystem = this._config.inputSubsystemFactory();
}
}
async getWasmModule(): Promise<any> {
return this._config.wasmModule;
}
getCanvas(): HTMLCanvasElement | null {
return this._canvas;
}
resize(width: number, height: number): void {
if (this._canvas) {
this._canvas.width = width;
this._canvas.height = height;
}
this._viewportSize = { width, height };
}
getViewportSize(): { width: number; height: number } {
return { ...this._viewportSize };
}
isEditorMode(): boolean {
return true;
}
setShowGrid(show: boolean): void {
this._showGrid = show;
}
getShowGrid(): boolean {
return this._showGrid;
}
setShowGizmos(show: boolean): void {
this._showGizmos = show;
}
getShowGizmos(): boolean {
return this._showGizmos;
}
/**
* 获取输入子系统
* Get input subsystem
*
* 用于 InputSystem 接收游戏输入事件
* Used by InputSystem to receive game input events
*/
getInputSubsystem(): IPlatformInputSubsystem | null {
return this._inputSubsystem;
}
dispose(): void {
if (this._inputSubsystem) {
this._inputSubsystem.dispose?.();
this._inputSubsystem = null;
}
this._canvas = null;
}
}

View File

@@ -0,0 +1,12 @@
/**
* Platform Adapters
* 平台适配器
*/
export {
BrowserPlatformAdapter,
BrowserPathResolver,
type BrowserPlatformConfig,
type BrowserPathResolveMode
} from './BrowserPlatformAdapter';
export { EditorPlatformAdapter, EditorPathResolver, type EditorPlatformConfig } from './EditorPlatformAdapter';