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,97 @@
import {
singleton,
createLogger,
MessageHub,
IMessageHub,
} from '@esengine/editor-runtime';
import { useBehaviorTreeDataStore } from '../application/state/BehaviorTreeDataStore';
import type { BehaviorTree } from '../domain/models/BehaviorTree';
import { FileSystemService } from './FileSystemService';
import { PluginContext } from '../PluginContext';
import type { IBehaviorTreeService } from '../tokens';
const logger = createLogger('BehaviorTreeService');
@singleton()
export class BehaviorTreeService implements IBehaviorTreeService {
async createNew(): Promise<void> {
useBehaviorTreeDataStore.getState().reset();
}
async loadFromFile(filePath: string): Promise<void> {
try {
const services = PluginContext.getServices();
// 运行时解析 FileSystemService
const fileSystem = services.resolve(FileSystemService);
if (!fileSystem) {
throw new Error('FileSystemService not found. Please ensure the BehaviorTreePlugin is properly installed.');
}
const content = await fileSystem.readBehaviorTreeFile(filePath);
const fileName = filePath.split(/[\\/]/).pop()?.replace('.btree', '') || 'Untitled';
const store = useBehaviorTreeDataStore.getState();
store.importFromJSON(content);
// 在 store 中保存文件信息Panel 挂载时读取
store.setCurrentFile(filePath, fileName);
const messageHub = services.resolve<MessageHub>(IMessageHub);
if (messageHub) {
messageHub.publish('dynamic-panel:open', {
panelId: 'behavior-tree-editor',
title: `Behavior Tree - ${filePath.split(/[\\/]/).pop()}`
});
// 保留事件发布,以防 Panel 已挂载
messageHub.publish('behavior-tree:file-opened', {
filePath,
fileName
});
}
} catch (error) {
logger.error('Failed to load tree:', error);
throw error;
}
}
async saveToFile(filePath: string, metadata?: { name: string; description: string }): Promise<void> {
try {
const services = PluginContext.getServices();
// 运行时解析 FileSystemService
const fileSystem = services.resolve(FileSystemService);
if (!fileSystem) {
throw new Error('FileSystemService not found. Please ensure the BehaviorTreePlugin is properly installed.');
}
const store = useBehaviorTreeDataStore.getState();
// 如果没有提供元数据,使用文件名作为默认名称
const defaultMetadata = {
name: metadata?.name || filePath.split(/[\\/]/).pop()?.replace('.btree', '') || 'Untitled',
description: metadata?.description || ''
};
const content = store.exportToJSON(defaultMetadata);
await fileSystem.writeBehaviorTreeFile(filePath, content);
logger.info('Tree saved successfully:', filePath);
} catch (error) {
logger.error('Failed to save tree:', error);
throw error;
}
}
getCurrentTree(): BehaviorTree {
return useBehaviorTreeDataStore.getState().tree;
}
setTree(tree: BehaviorTree): void {
useBehaviorTreeDataStore.getState().setTree(tree);
}
dispose(): void {
useBehaviorTreeDataStore.getState().reset();
}
}

View File

@@ -0,0 +1,90 @@
import { singleton, invoke, type IService } from '@esengine/editor-runtime';
/**
* 文件系统服务
* 封装所有文件读写操作,使用通用后端命令
*/
@singleton()
export class FileSystemService implements IService {
/**
* 读取行为树文件
*/
async readBehaviorTreeFile(filePath: string): Promise<string> {
try {
return await invoke<string>('read_file_content', { path: filePath });
} catch (error) {
throw new Error(`Failed to read file ${filePath}: ${error}`);
}
}
/**
* 写入行为树文件
*/
async writeBehaviorTreeFile(filePath: string, content: string): Promise<void> {
try {
await invoke('write_file_content', { path: filePath, content });
} catch (error) {
throw new Error(`Failed to write file ${filePath}: ${error}`);
}
}
/**
* 读取全局黑板配置
* 业务逻辑在前端,后端只提供通用文件操作
*/
async readGlobalBlackboard(projectPath: string): Promise<string> {
try {
const configPath = `${projectPath}/.ecs/global-blackboard.json`;
const exists = await invoke<boolean>('path_exists', { path: configPath });
if (!exists) {
return JSON.stringify({ version: '1.0', variables: [] });
}
return await invoke<string>('read_file_content', { path: configPath });
} catch (error) {
throw new Error(`Failed to read global blackboard: ${error}`);
}
}
/**
* 写入全局黑板配置
* 业务逻辑在前端,后端只提供通用文件操作
*/
async writeGlobalBlackboard(projectPath: string, content: string): Promise<void> {
try {
const ecsDir = `${projectPath}/.ecs`;
const configPath = `${ecsDir}/global-blackboard.json`;
// 创建 .ecs 目录(如果不存在)
const dirExists = await invoke<boolean>('path_exists', { path: ecsDir });
if (!dirExists) {
await invoke('create_directory', { path: ecsDir });
}
await invoke('write_file_content', { path: configPath, content });
} catch (error) {
throw new Error(`Failed to write global blackboard: ${error}`);
}
}
async writeTextFile(filePath: string, content: string): Promise<void> {
try {
await invoke('write_file_content', { path: filePath, content });
} catch (error) {
throw new Error(`Failed to write text file ${filePath}: ${error}`);
}
}
async writeBinaryFile(filePath: string, data: Uint8Array): Promise<void> {
try {
await invoke('write_binary_file', { filePath, content: Array.from(data) });
} catch (error) {
throw new Error(`Failed to write binary file ${filePath}: ${error}`);
}
}
dispose(): void {
// 文件系统服务无需清理资源
}
}

View File

@@ -0,0 +1,68 @@
import { createLogger, PluginAPI } from '@esengine/editor-runtime';
import type { MessageHub } from '@esengine/editor-runtime';
const logger = createLogger('NotificationService');
export class NotificationService {
private static _instance: NotificationService;
private _messageHub: MessageHub | null = null;
private constructor() {
// 延迟获取 MessageHub因为初始化时可能还不可用
}
private _getMessageHub(): MessageHub | null {
if (!this._messageHub && PluginAPI.isAvailable) {
try {
this._messageHub = PluginAPI.messageHub;
} catch (error) {
logger.warn('MessageHub not available');
}
}
return this._messageHub;
}
public static getInstance(): NotificationService {
if (!NotificationService._instance) {
NotificationService._instance = new NotificationService();
}
return NotificationService._instance;
}
public showToast(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {
const hub = this._getMessageHub();
if (!hub) {
logger.info(`[Toast ${type}] ${message}`);
return;
}
const notification = {
type,
message,
timestamp: Date.now()
};
hub.publish('notification:show', notification);
}
public success(message: string): void {
this.showToast(message, 'success');
}
public error(message: string): void {
this.showToast(message, 'error');
}
public warning(message: string): void {
this.showToast(message, 'warning');
}
public info(message: string): void {
this.showToast(message, 'info');
}
}
// 导出单例实例的便捷方法
export const showToast = (message: string, type?: 'success' | 'error' | 'warning' | 'info') => {
NotificationService.getInstance().showToast(message, type);
};

View File

@@ -0,0 +1,7 @@
/**
* 服务导出
*/
export { BehaviorTreeService } from './BehaviorTreeService';
export { FileSystemService } from './FileSystemService';
export { NotificationService, showToast } from './NotificationService';