* 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
201 lines
6.7 KiB
TypeScript
201 lines
6.7 KiB
TypeScript
/**
|
|
* FGUIUpdateSystem
|
|
*
|
|
* ECS system that handles automatic loading and updating of FGUIComponents.
|
|
*
|
|
* 处理 FGUIComponent 自动加载和更新的 ECS 系统
|
|
*/
|
|
|
|
import { EntitySystem, Matcher, type Entity, Time } from '@esengine/ecs-framework';
|
|
import type { IAssetManager } from '@esengine/asset-system';
|
|
import { FGUIComponent } from './FGUIComponent';
|
|
import { getFGUIRenderSystem } from './FGUIRenderSystem';
|
|
import type { IFUIAsset } from '../asset/FUIAssetLoader';
|
|
|
|
/**
|
|
* Tracked state for detecting property changes
|
|
* 用于检测属性变化的跟踪状态
|
|
*/
|
|
interface TrackedState {
|
|
packageGuid: string;
|
|
componentName: string;
|
|
}
|
|
|
|
/**
|
|
* FGUIUpdateSystem
|
|
*
|
|
* Automatically loads FUI packages and creates UI components for FGUIComponent.
|
|
* 自动为 FGUIComponent 加载 FUI 包并创建 UI 组件
|
|
*/
|
|
export class FGUIUpdateSystem extends EntitySystem {
|
|
private _assetManager: IAssetManager | null = null;
|
|
private _trackedStates: WeakMap<FGUIComponent, TrackedState> = new WeakMap();
|
|
private _pendingLoads: Map<FGUIComponent, Promise<void>> = new Map();
|
|
|
|
constructor() {
|
|
super(Matcher.empty().all(FGUIComponent));
|
|
}
|
|
|
|
public setAssetManager(assetManager: IAssetManager): void {
|
|
this._assetManager = assetManager;
|
|
}
|
|
|
|
protected override process(entities: readonly Entity[]): void {
|
|
for (const entity of entities) {
|
|
const fguiComp = entity.getComponent(FGUIComponent) as FGUIComponent | null;
|
|
if (!fguiComp) continue;
|
|
|
|
// Skip if currently loading
|
|
if (fguiComp.isLoading || this._pendingLoads.has(fguiComp)) {
|
|
continue;
|
|
}
|
|
|
|
// Check if we need to reload
|
|
const tracked = this._trackedStates.get(fguiComp);
|
|
const needsReload = this._needsReload(fguiComp, tracked);
|
|
|
|
if (needsReload && fguiComp.packageGuid) {
|
|
this._loadComponent(fguiComp);
|
|
}
|
|
}
|
|
|
|
const renderSystem = getFGUIRenderSystem();
|
|
if (renderSystem) {
|
|
renderSystem.update(Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if component needs to reload
|
|
* 检查组件是否需要重新加载
|
|
*/
|
|
private _needsReload(comp: FGUIComponent, tracked: TrackedState | undefined): boolean {
|
|
// Not tracked yet - needs initial load
|
|
if (!tracked) {
|
|
return true;
|
|
}
|
|
|
|
// Package changed - needs full reload
|
|
if (tracked.packageGuid !== comp.packageGuid) {
|
|
return true;
|
|
}
|
|
|
|
// Component name changed - needs to recreate component
|
|
if (tracked.componentName !== comp.componentName) {
|
|
// If package is already loaded, just recreate the component
|
|
if (comp.package && comp.componentName) {
|
|
comp.createComponent(comp.componentName);
|
|
// Update tracked state
|
|
this._trackedStates.set(comp, {
|
|
packageGuid: comp.packageGuid,
|
|
componentName: comp.componentName
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private async _loadComponent(comp: FGUIComponent): Promise<void> {
|
|
if (!this._assetManager) {
|
|
return;
|
|
}
|
|
|
|
const loadPromise = this._doLoad(comp);
|
|
this._pendingLoads.set(comp, loadPromise);
|
|
|
|
try {
|
|
await loadPromise;
|
|
} finally {
|
|
this._pendingLoads.delete(comp);
|
|
}
|
|
}
|
|
|
|
private async _doLoad(comp: FGUIComponent): Promise<void> {
|
|
const packageRef = comp.packageGuid;
|
|
|
|
// Dispose previous content before loading new package
|
|
comp.dispose();
|
|
|
|
try {
|
|
// Check if packageRef is a path (contains / or . before extension) or a GUID
|
|
// GUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
// Path format: assets/ui/Bag.fui or similar
|
|
const isPath = packageRef.includes('/') || packageRef.includes('\\') || packageRef.endsWith('.fui');
|
|
const result = isPath
|
|
? await this._assetManager!.loadAssetByPath<IFUIAsset>(packageRef)
|
|
: await this._assetManager!.loadAsset<IFUIAsset>(packageRef);
|
|
if (!result || !result.asset) {
|
|
return;
|
|
}
|
|
|
|
const fuiAsset = result.asset;
|
|
|
|
if (fuiAsset.package) {
|
|
const width = comp.width > 0 ? comp.width : 1920;
|
|
const height = comp.height > 0 ? comp.height : 1080;
|
|
comp.initRoot(width, height);
|
|
comp.setLoadedPackage(fuiAsset.package);
|
|
|
|
if (comp.componentName) {
|
|
comp.createComponent(comp.componentName);
|
|
}
|
|
} else {
|
|
const asset = fuiAsset as unknown;
|
|
let data: ArrayBuffer | null = null;
|
|
|
|
if (asset instanceof ArrayBuffer) {
|
|
data = asset;
|
|
} else if (typeof asset === 'object' && asset !== null && 'data' in asset && (asset as { data: ArrayBuffer }).data instanceof ArrayBuffer) {
|
|
data = (asset as { data: ArrayBuffer }).data;
|
|
} else if (typeof asset === 'object' && asset !== null && 'buffer' in asset) {
|
|
data = (asset as { buffer: ArrayBuffer }).buffer;
|
|
}
|
|
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
const width = comp.width > 0 ? comp.width : 1920;
|
|
const height = comp.height > 0 ? comp.height : 1080;
|
|
comp.initRoot(width, height);
|
|
comp.loadPackage(packageRef, data);
|
|
|
|
if (comp.componentName) {
|
|
comp.createComponent(comp.componentName);
|
|
}
|
|
}
|
|
|
|
const renderSystem = getFGUIRenderSystem();
|
|
if (renderSystem && comp.isReady) {
|
|
renderSystem.registerComponent(comp);
|
|
}
|
|
|
|
// Update tracked state after successful load
|
|
this._trackedStates.set(comp, {
|
|
packageGuid: comp.packageGuid,
|
|
componentName: comp.componentName
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error(`[FGUI] Error loading package ${packageRef}:`, err);
|
|
}
|
|
}
|
|
|
|
protected override onDestroy(): void {
|
|
const renderSystem = getFGUIRenderSystem();
|
|
if (renderSystem && this.scene) {
|
|
for (const entity of this.scene.entities.buffer) {
|
|
const fguiComp = entity.getComponent(FGUIComponent) as FGUIComponent | null;
|
|
if (fguiComp) {
|
|
renderSystem.unregisterComponent(fguiComp);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._pendingLoads.clear();
|
|
this._trackedStates = new WeakMap();
|
|
}
|
|
}
|