Feature/editor optimization (#251)

* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
YHH
2025-12-01 22:28:51 +08:00
committed by GitHub
parent 189714c727
commit b42a7b4e43
468 changed files with 18301 additions and 9075 deletions

View File

@@ -0,0 +1,143 @@
/**
* Browser Platform Adapter
* 浏览器平台适配器
*
* 用于独立浏览器运行时的平台适配器
* Platform adapter for standalone browser runtime
*/
import type {
IPlatformAdapter,
IPathResolver,
PlatformCapabilities,
PlatformAdapterConfig
} from '../IPlatformAdapter';
/**
* 浏览器路径解析器
* Browser path resolver
*/
export class BrowserPathResolver implements IPathResolver {
private _baseUrl: string;
constructor(baseUrl: string = '') {
this._baseUrl = baseUrl;
}
resolve(path: string): string {
// 如果已经是完整 URL直接返回
if (path.startsWith('http://') ||
path.startsWith('https://') ||
path.startsWith('data:') ||
path.startsWith('blob:') ||
path.startsWith('/asset?')) {
return path;
}
// 相对路径,添加资产请求前缀
return `/asset?path=${encodeURIComponent(path)}`;
}
/**
* 更新基础 URL
*/
setBaseUrl(baseUrl: string): void {
this._baseUrl = baseUrl;
}
}
/**
* 浏览器平台适配器配置
*/
export interface BrowserPlatformConfig {
/** WASM 模块(预加载的)*/
wasmModule?: any;
/** WASM 模块加载器(异步加载)*/
wasmModuleLoader?: () => Promise<any>;
/** 资产基础 URL */
assetBaseUrl?: string;
}
/**
* 浏览器平台适配器
* 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 };
constructor(config: BrowserPlatformConfig = {}) {
this._config = config;
this._pathResolver = new BrowserPathResolver(config.assetBaseUrl || '');
}
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 };
}
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;
}
dispose(): void {
this._canvas = null;
}
}

View File

@@ -0,0 +1,185 @@
/**
* Editor Platform Adapter
* 编辑器平台适配器
*
* 用于 Tauri 编辑器内嵌预览的平台适配器
* Platform adapter for Tauri editor embedded preview
*/
import type {
IPlatformAdapter,
IPathResolver,
PlatformCapabilities,
PlatformAdapterConfig
} from '../IPlatformAdapter';
/**
* 编辑器路径解析器
* 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;
}
/**
* 编辑器平台适配器
* 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;
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 };
}
}
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;
}
dispose(): void {
this._canvas = null;
}
}

View File

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