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