feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)

* feat(platform-common): 添加WASM加载器和环境检测API

* feat(rapier2d): 新增Rapier2D WASM绑定包

* feat(physics-rapier2d): 添加跨平台WASM加载器

* feat(asset-system): 添加运行时资产目录和bundle格式

* feat(asset-system-editor): 新增编辑器资产管理包

* feat(editor-core): 添加构建系统和模块管理

* feat(editor-app): 重构浏览器预览使用import maps

* feat(platform-web): 添加BrowserRuntime和资产读取

* feat(engine): 添加材质系统和着色器管理

* feat(material): 新增材质系统和着色器编辑器

* feat(tilemap): 增强tilemap编辑器和动画系统

* feat(modules): 添加module.json配置

* feat(core): 添加module.json和类型定义更新

* chore: 更新依赖和构建配置

* refactor(plugins): 更新插件模板使用ModuleManifest

* chore: 添加第三方依赖库

* chore: 移除BehaviourTree-ai和ecs-astar子模块

* docs: 更新README和文档主题样式

* fix: 修复Rust文档测试和添加rapier2d WASM绑定

* fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题

* feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea)

* fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖

* fix: 添加缺失的包依赖修复CI构建

* fix: 修复CodeQL检测到的代码问题

* fix: 修复构建错误和缺失依赖

* fix: 修复类型检查错误

* fix(material-system): 修复tsconfig配置支持TypeScript项目引用

* fix(editor-core): 修复Rollup构建配置添加tauri external

* fix: 修复CodeQL检测到的代码问题

* fix: 修复CodeQL检测到的代码问题
This commit is contained in:
YHH
2025-12-03 22:15:22 +08:00
committed by GitHub
parent caf7622aa0
commit 63f006ab62
496 changed files with 77601 additions and 4067 deletions

View File

@@ -58,6 +58,343 @@ function copyPluginModulesPlugin(): Plugin {
};
}
/**
* Plugin to copy engine modules after each build.
* 每次构建后复制引擎模块的插件。
*/
function copyEngineModulesPlugin(): Plugin {
const packagesDir = path.resolve(__dirname, '..');
function getEngineModules() {
const modules: Array<{
id: string;
name: string;
displayName: string;
packageDir: string;
moduleJsonPath: string;
distPath: string;
editorPackage?: string;
isCore: boolean;
category: string;
}> = [];
let packages: string[];
try {
packages = fs.readdirSync(packagesDir);
} catch {
return modules;
}
for (const pkg of packages) {
const pkgDir = path.join(packagesDir, pkg);
const moduleJsonPath = path.join(pkgDir, 'module.json');
try {
if (!fs.statSync(pkgDir).isDirectory()) continue;
} catch {
continue;
}
if (!fs.existsSync(moduleJsonPath)) continue;
try {
const moduleJson = JSON.parse(fs.readFileSync(moduleJsonPath, 'utf-8'));
if (moduleJson.isEngineModule !== false) {
// Use outputPath from module.json, default to "dist/index.js"
const outputPath = moduleJson.outputPath || 'dist/index.js';
const distPath = path.join(pkgDir, outputPath);
modules.push({
id: moduleJson.id || pkg,
name: moduleJson.name || `@esengine/${pkg}`,
displayName: moduleJson.displayName || pkg,
packageDir: pkgDir,
moduleJsonPath,
distPath,
editorPackage: moduleJson.editorPackage,
isCore: moduleJson.isCore || false,
category: moduleJson.category || 'Other'
});
}
} catch {
// Ignore parse errors
}
}
return modules;
}
return {
name: 'copy-engine-modules',
writeBundle(options) {
const outDir = options.dir || 'dist';
const engineDir = path.join(outDir, 'engine');
// Clean and recreate engine directory
if (fs.existsSync(engineDir)) {
fs.rmSync(engineDir, { recursive: true });
}
fs.mkdirSync(engineDir, { recursive: true });
const modules = getEngineModules();
const moduleInfos: Array<{
id: string;
name: string;
displayName: string;
hasRuntime: boolean;
editorPackage?: string;
isCore: boolean;
category: string;
jsSize?: number;
requiresWasm?: boolean;
wasmSize?: number;
wasmFiles?: string[];
}> = [];
const editorPackages = new Set<string>();
/**
* Calculate total WASM file size in a directory.
* 计算目录中 WASM 文件的总大小。
*/
function getWasmSize(pkgDir: string): number {
let totalSize = 0;
const checkDirs = [
pkgDir,
path.join(pkgDir, 'pkg'),
path.join(pkgDir, 'dist')
];
for (const dir of checkDirs) {
if (!fs.existsSync(dir)) continue;
try {
const files = fs.readdirSync(dir);
for (const file of files) {
if (file.endsWith('.wasm')) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
totalSize += stat.size;
}
}
} catch {
// Ignore errors
}
}
return totalSize;
}
console.log(`[copy-engine-modules] Copying ${modules.length} modules to dist/engine/`);
for (const module of modules) {
const moduleOutputDir = path.join(engineDir, module.id);
fs.mkdirSync(moduleOutputDir, { recursive: true });
// Read full module.json for additional fields
// 读取完整 module.json 获取额外字段
let moduleJson: Record<string, unknown> = {};
try {
moduleJson = JSON.parse(fs.readFileSync(module.moduleJsonPath, 'utf-8'));
} catch {
// Ignore parse errors
}
// Copy module.json
fs.copyFileSync(module.moduleJsonPath, path.join(moduleOutputDir, 'module.json'));
// Copy dist/index.js if exists
let hasRuntime = false;
let jsSize = 0;
if (fs.existsSync(module.distPath)) {
fs.copyFileSync(module.distPath, path.join(moduleOutputDir, 'index.js'));
// Get JS file size
jsSize = fs.statSync(module.distPath).size;
// Copy source map if exists
const sourceMapPath = module.distPath + '.map';
if (fs.existsSync(sourceMapPath)) {
fs.copyFileSync(sourceMapPath, path.join(moduleOutputDir, 'index.js.map'));
}
hasRuntime = true;
// Copy additional included files (e.g., chunks)
// 复制额外包含的文件(如 chunk
const includes = moduleJson.includes as string[] | undefined;
if (includes && includes.length > 0) {
const distDir = path.dirname(module.distPath);
for (const pattern of includes) {
// Convert glob pattern to regex
const regexPattern = pattern
.replace(/\\/g, '\\\\')
.replace(/\./g, '\\.')
.replace(/\*/g, '.*')
.replace(/\?/g, '.');
const regex = new RegExp(`^${regexPattern}$`);
// Find matching files in dist directory
if (fs.existsSync(distDir)) {
const files = fs.readdirSync(distDir);
for (const file of files) {
if (regex.test(file)) {
const srcFile = path.join(distDir, file);
const destFile = path.join(moduleOutputDir, file);
fs.copyFileSync(srcFile, destFile);
jsSize += fs.statSync(srcFile).size;
// Copy source map for included file if exists
const mapFile = srcFile + '.map';
if (fs.existsSync(mapFile)) {
fs.copyFileSync(mapFile, destFile + '.map');
}
console.log(`[copy-engine-modules] Copied include to ${module.id}/: ${file}`);
}
}
}
}
}
}
// Calculate WASM size and copy WASM files if module requires WASM
// 如果模块需要 WASM计算 WASM 大小并复制 WASM 文件
const requiresWasm = moduleJson.requiresWasm === true;
let wasmSize = 0;
const copiedWasmFiles: string[] = [];
if (requiresWasm) {
wasmSize = getWasmSize(module.packageDir);
if (wasmSize > 0) {
console.log(`[copy-engine-modules] ${module.id}: WASM size = ${(wasmSize / 1024).toFixed(1)} KB`);
}
// Copy WASM files from wasmPaths defined in module.json
// wasmPaths 现在是相对于源包目录的路径,如 "rapier_wasm2d_bg.wasm"
// 需要找到实际的 WASM 文件并复制到输出的模块目录
const wasmPaths = moduleJson.wasmPaths as string[] | undefined;
if (wasmPaths && wasmPaths.length > 0) {
for (const wasmRelPath of wasmPaths) {
const wasmFileName = path.basename(wasmRelPath);
// 查找源 WASM 文件的可能位置
// wasmPaths 里配置的是相对路径,实际文件在源包里
// 对于 @esengine/rapier2dWASM 在 packages/rapier2d/pkg/ 下
const possibleSrcPaths = [
// 直接在包目录下(如果 wasmRelPath 就是文件名)
path.join(module.packageDir, wasmRelPath),
// 在包的 pkg 目录下wasm-pack 输出)
path.join(module.packageDir, 'pkg', wasmFileName),
// 在包的 dist 目录下
path.join(module.packageDir, 'dist', wasmFileName),
];
// 对于依赖其他包 WASM 的情况,检查依赖包
// 例如 physics-rapier2d 依赖 rapier2d 的 WASM
const depMatch = moduleJson.name?.toString().match(/@esengine\/(.+)/);
if (depMatch) {
// 检查同名的依赖包(去掉 physics- 前缀)
const baseName = depMatch[1].replace('physics-', '');
possibleSrcPaths.push(
path.join(packagesDir, baseName, 'pkg', wasmFileName),
path.join(packagesDir, baseName, wasmFileName)
);
}
let copied = false;
for (const srcPath of possibleSrcPaths) {
if (fs.existsSync(srcPath)) {
const destPath = path.join(moduleOutputDir, wasmFileName);
fs.copyFileSync(srcPath, destPath);
copiedWasmFiles.push(wasmFileName);
console.log(`[copy-engine-modules] Copied WASM to ${module.id}/: ${wasmFileName}`);
copied = true;
break;
}
}
if (!copied) {
console.warn(`[copy-engine-modules] WASM file not found: ${wasmRelPath} (tried ${possibleSrcPaths.length} paths)`);
}
}
}
// Copy pkg directory if exists (for WASM JS bindings like rapier2d)
// 如果存在 pkg 目录则复制(用于 WASM JS 绑定如 rapier2d
// The JS and WASM files must be in the same directory for import.meta.url to work
// JS 和 WASM 文件必须在同一目录才能让 import.meta.url 正常工作
const pkgDir = path.join(module.packageDir, 'pkg');
if (fs.existsSync(pkgDir)) {
const pkgOutputDir = path.join(moduleOutputDir, 'pkg');
fs.mkdirSync(pkgOutputDir, { recursive: true });
const pkgFiles = fs.readdirSync(pkgDir);
for (const file of pkgFiles) {
// Copy both JS and WASM files to pkg directory
// 将 JS 和 WASM 文件都复制到 pkg 目录
if (file.endsWith('.js') || file.endsWith('.wasm')) {
const srcFile = path.join(pkgDir, file);
const destFile = path.join(pkgOutputDir, file);
fs.copyFileSync(srcFile, destFile);
console.log(`[copy-engine-modules] Copied pkg to ${module.id}/pkg/: ${file}`);
}
}
}
}
moduleInfos.push({
id: module.id,
name: module.name,
displayName: module.displayName,
hasRuntime,
editorPackage: module.editorPackage,
isCore: module.isCore,
category: module.category,
// Only include jsSize if there's actual runtime code
// 只有实际有运行时代码时才包含 jsSize
jsSize: jsSize > 0 ? jsSize : undefined,
requiresWasm: requiresWasm || undefined,
wasmSize: wasmSize > 0 ? wasmSize : undefined,
// WASM files that were copied to dist/wasm/
// 复制到 dist/wasm/ 的 WASM 文件
wasmFiles: copiedWasmFiles.length > 0 ? copiedWasmFiles : undefined
});
if (module.editorPackage) {
editorPackages.add(module.editorPackage);
}
}
// Copy editor packages
for (const editorPkg of editorPackages) {
const match = editorPkg.match(/@esengine\/(.+)/);
if (!match) continue;
const pkgName = match[1];
const pkgDir = path.join(packagesDir, pkgName);
const distPath = path.join(pkgDir, 'dist', 'index.js');
if (!fs.existsSync(distPath)) continue;
const editorOutputDir = path.join(engineDir, pkgName);
fs.mkdirSync(editorOutputDir, { recursive: true });
fs.copyFileSync(distPath, path.join(editorOutputDir, 'index.js'));
const sourceMapPath = distPath + '.map';
if (fs.existsSync(sourceMapPath)) {
fs.copyFileSync(sourceMapPath, path.join(editorOutputDir, 'index.js.map'));
}
}
// Create index.json
const indexData = {
version: '1.0.0',
generatedAt: new Date().toISOString(),
modules: moduleInfos
};
fs.writeFileSync(
path.join(engineDir, 'index.json'),
JSON.stringify(indexData, null, 2)
);
console.log(`[copy-engine-modules] Done! Created dist/engine/index.json`);
}
};
}
const host = process.env.TAURI_DEV_HOST;
const wasmPackages: string[] = [];
@@ -161,6 +498,7 @@ export default defineConfig({
tsDecorators: true,
}),
copyPluginModulesPlugin(),
copyEngineModulesPlugin(),
],
clearScreen: false,
server: {