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,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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
12
packages/engine/runtime-core/src/adapters/index.ts
Normal file
12
packages/engine/runtime-core/src/adapters/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Platform Adapters
|
||||
* 平台适配器
|
||||
*/
|
||||
|
||||
export {
|
||||
BrowserPlatformAdapter,
|
||||
BrowserPathResolver,
|
||||
type BrowserPlatformConfig,
|
||||
type BrowserPathResolveMode
|
||||
} from './BrowserPlatformAdapter';
|
||||
export { EditorPlatformAdapter, EditorPathResolver, type EditorPlatformConfig } from './EditorPlatformAdapter';
|
||||
Reference in New Issue
Block a user