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,349 @@
import type { FGUIEvents } from './Events';
/**
* Event type key from FGUIEvents
* FGUIEvents 事件类型键
*/
export type FGUIEventType = (typeof FGUIEvents)[keyof typeof FGUIEvents];
/**
* Event data mapping - maps event types to their data types
* 事件数据映射 - 将事件类型映射到其数据类型
*/
export interface IEventDataMap {
[key: string]: unknown;
}
/**
* Event listener callback with type safety
* 类型安全的事件监听回调
*/
export type TypedEventListener<T = unknown> = (data: T) => void;
/**
* Legacy event listener (for backwards compatibility)
* 传统事件监听器(向后兼容)
*/
export type EventListener = (data?: unknown) => void;
/**
* Event listener info
* 事件监听信息
*/
interface ListenerInfo<T = unknown> {
listener: TypedEventListener<T>;
thisArg: unknown;
once: boolean;
priority: number;
}
/**
* Event propagation control
* 事件传播控制
*/
export interface IEventContext {
/** Stop propagation | 停止传播 */
stopped: boolean;
/** Prevent default behavior | 阻止默认行为 */
defaultPrevented: boolean;
/** Event type | 事件类型 */
type: string;
/** Current target | 当前目标 */
currentTarget: EventDispatcher | null;
/** Original target | 原始目标 */
target: EventDispatcher | null;
}
/**
* Create event context
* 创建事件上下文
*/
function createEventContext(type: string, target: EventDispatcher): IEventContext {
return {
stopped: false,
defaultPrevented: false,
type,
currentTarget: target,
target
};
}
/**
* EventDispatcher
*
* Modern event dispatching system with type safety and priority support.
*
* 现代化的事件分发系统,支持类型安全和优先级
*
* Features:
* - Type-safe event listeners
* - Priority-based listener ordering
* - Event propagation control
* - Capture phase support
* - Memory-efficient listener management
*/
export class EventDispatcher {
private _listeners: Map<string, ListenerInfo[]> = new Map();
private _captureListeners: Map<string, ListenerInfo[]> = new Map();
private _dispatching: Set<string> = new Set();
private _pendingRemovals: Map<string, ListenerInfo[]> = new Map();
/**
* Register an event listener with optional priority
* 注册事件监听器(支持优先级)
*
* @param type Event type | 事件类型
* @param listener Callback function | 回调函数
* @param thisArg Context for callback | 回调上下文
* @param priority Higher priority listeners are called first (default: 0) | 优先级越高越先调用
*/
public on<T = unknown>(
type: string,
listener: TypedEventListener<T>,
thisArg?: unknown,
priority: number = 0
): this {
this.addListener(this._listeners, type, listener as TypedEventListener, thisArg, false, priority);
return this;
}
/**
* Register a one-time event listener
* 注册一次性事件监听器
*/
public once<T = unknown>(
type: string,
listener: TypedEventListener<T>,
thisArg?: unknown,
priority: number = 0
): this {
this.addListener(this._listeners, type, listener as TypedEventListener, thisArg, true, priority);
return this;
}
/**
* Remove an event listener
* 移除事件监听器
*/
public off<T = unknown>(type: string, listener: TypedEventListener<T>, thisArg?: unknown): this {
this.removeListener(this._listeners, type, listener as TypedEventListener, thisArg);
return this;
}
/**
* Remove all listeners for a type, or all listeners
* 移除指定类型的所有监听器,或移除所有监听器
*/
public offAll(type?: string): this {
if (type) {
this._listeners.delete(type);
this._captureListeners.delete(type);
} else {
this._listeners.clear();
this._captureListeners.clear();
}
return this;
}
/**
* Emit an event with typed data
* 发送带类型数据的事件
*
* @returns true if event was handled, false otherwise
*/
public emit<T = unknown>(type: string, data?: T): boolean {
const listeners = this._listeners.get(type);
if (!listeners || listeners.length === 0) {
return false;
}
this._dispatching.add(type);
const toRemove: ListenerInfo[] = [];
try {
for (const info of listeners) {
try {
info.listener.call(info.thisArg, data);
} catch (error) {
console.error(`Error in event listener for "${type}":`, error);
}
if (info.once) {
toRemove.push(info);
}
}
} finally {
this._dispatching.delete(type);
}
// Remove one-time listeners
for (const info of toRemove) {
this.removeListener(this._listeners, type, info.listener, info.thisArg);
}
// Process pending removals
const pending = this._pendingRemovals.get(type);
if (pending) {
for (const info of pending) {
this.removeListener(this._listeners, type, info.listener, info.thisArg);
}
this._pendingRemovals.delete(type);
}
return true;
}
/**
* Emit with event context for propagation control
* 发送带事件上下文的事件(用于传播控制)
*/
public emitWithContext<T = unknown>(type: string, data?: T): IEventContext {
const context = createEventContext(type, this);
const listeners = this._listeners.get(type);
if (listeners && listeners.length > 0) {
this._dispatching.add(type);
const toRemove: ListenerInfo[] = [];
try {
for (const info of listeners) {
if (context.stopped) break;
try {
info.listener.call(info.thisArg, data);
} catch (error) {
console.error(`Error in event listener for "${type}":`, error);
}
if (info.once) {
toRemove.push(info);
}
}
} finally {
this._dispatching.delete(type);
}
for (const info of toRemove) {
this.removeListener(this._listeners, type, info.listener, info.thisArg);
}
}
return context;
}
/**
* Check if there are any listeners for a type
* 检查是否有指定类型的监听器
*/
public hasListener(type: string): boolean {
const listeners = this._listeners.get(type);
return listeners !== undefined && listeners.length > 0;
}
/**
* Get listener count for a type
* 获取指定类型的监听器数量
*/
public listenerCount(type: string): number {
const listeners = this._listeners.get(type);
return listeners?.length ?? 0;
}
/**
* Register a capture phase listener
* 注册捕获阶段监听器
*/
public onCapture<T = unknown>(
type: string,
listener: TypedEventListener<T>,
thisArg?: unknown,
priority: number = 0
): this {
this.addListener(this._captureListeners, type, listener as TypedEventListener, thisArg, false, priority);
return this;
}
/**
* Remove a capture phase listener
* 移除捕获阶段监听器
*/
public offCapture<T = unknown>(type: string, listener: TypedEventListener<T>, thisArg?: unknown): this {
this.removeListener(this._captureListeners, type, listener as TypedEventListener, thisArg);
return this;
}
/**
* Dispose all listeners
* 销毁所有监听器
*/
public dispose(): void {
this._listeners.clear();
this._captureListeners.clear();
this._dispatching.clear();
this._pendingRemovals.clear();
}
private addListener(
map: Map<string, ListenerInfo[]>,
type: string,
listener: TypedEventListener,
thisArg: unknown,
once: boolean,
priority: number
): void {
let listeners = map.get(type);
if (!listeners) {
listeners = [];
map.set(type, listeners);
}
// Check for duplicate
const exists = listeners.some((info) => info.listener === listener && info.thisArg === thisArg);
if (exists) return;
const info: ListenerInfo = { listener, thisArg, once, priority };
// Insert by priority (higher priority first)
let inserted = false;
for (let i = 0; i < listeners.length; i++) {
if (priority > listeners[i].priority) {
listeners.splice(i, 0, info);
inserted = true;
break;
}
}
if (!inserted) {
listeners.push(info);
}
}
private removeListener(
map: Map<string, ListenerInfo[]>,
type: string,
listener: TypedEventListener,
thisArg: unknown
): void {
const listeners = map.get(type);
if (!listeners) return;
// If dispatching, defer removal
if (this._dispatching.has(type)) {
let pending = this._pendingRemovals.get(type);
if (!pending) {
pending = [];
this._pendingRemovals.set(type, pending);
}
pending.push({ listener, thisArg, once: false, priority: 0 });
return;
}
const index = listeners.findIndex((info) => info.listener === listener && info.thisArg === thisArg);
if (index !== -1) {
listeners.splice(index, 1);
if (listeners.length === 0) {
map.delete(type);
}
}
}
}

View File

@@ -0,0 +1,142 @@
/**
* FairyGUI Event Types
* FairyGUI 事件类型常量
*/
export const FGUIEvents = {
/** Size changed | 尺寸改变 */
SIZE_CHANGED: 'fguiSizeChanged',
/** Position changed | 位置改变 */
XY_CHANGED: 'fguiXYChanged',
/** Click event | 点击事件 */
CLICK: 'click',
/** Touch/Mouse begin | 触摸/鼠标按下 */
TOUCH_BEGIN: 'touchBegin',
/** Touch/Mouse end | 触摸/鼠标抬起 */
TOUCH_END: 'touchEnd',
/** Touch/Mouse move | 触摸/鼠标移动 */
TOUCH_MOVE: 'touchMove',
/** Roll over (mouse enter) | 鼠标进入 */
ROLL_OVER: 'rollOver',
/** Roll out (mouse leave) | 鼠标离开 */
ROLL_OUT: 'rollOut',
/** Focus in | 获得焦点 */
FOCUS_IN: 'focusIn',
/** Focus out | 失去焦点 */
FOCUS_OUT: 'focusOut',
/** Added to stage | 添加到舞台 */
ADDED_TO_STAGE: 'addedToStage',
/** Removed from stage | 从舞台移除 */
REMOVED_FROM_STAGE: 'removedFromStage',
/** Display (added and visible) | 显示(添加并可见) */
DISPLAY: 'display',
/** Status changed (for Controller) | 状态改变(控制器) */
STATUS_CHANGED: 'statusChanged',
/** State changed (for Button/Slider) | 状态改变(按钮/滑块) */
STATE_CHANGED: 'stateChanged',
/** Pull down release (for list refresh) | 下拉刷新释放 */
PULL_DOWN_RELEASE: 'pullDownRelease',
/** Pull up release (for list load more) | 上拉加载释放 */
PULL_UP_RELEASE: 'pullUpRelease',
/** Scroll event | 滚动事件 */
SCROLL: 'scroll',
/** Scroll end | 滚动结束 */
SCROLL_END: 'scrollEnd',
/** Drag start | 拖拽开始 */
DRAG_START: 'dragStart',
/** Drag move | 拖拽移动 */
DRAG_MOVE: 'dragMove',
/** Drag end | 拖拽结束 */
DRAG_END: 'dragEnd',
/** Drop event | 放下事件 */
DROP: 'drop',
/** Text changed | 文本改变 */
TEXT_CHANGED: 'textChanged',
/** Text submitted (Enter key) | 文本提交(回车键) */
TEXT_SUBMIT: 'textSubmit',
/** Gear stop (animation complete) | 齿轮动画停止 */
GEAR_STOP: 'gearStop',
/** Link click (rich text) | 链接点击(富文本) */
LINK: 'link',
/** Play complete (MovieClip/Transition) | 播放完成 */
PLAY_COMPLETE: 'playComplete',
/** Click on list item | 列表项点击 */
CLICK_ITEM: 'clickItem'
} as const;
/**
* Input event data
* 输入事件数据
*/
export interface IInputEventData {
/** Touch/Pointer ID | 触摸/指针 ID */
touchId: number;
/** Stage X position | 舞台 X 坐标 */
stageX: number;
/** Stage Y position | 舞台 Y 坐标 */
stageY: number;
/** Button pressed (0=left, 1=middle, 2=right) | 按下的按钮 */
button: number;
/** Wheel delta | 滚轮增量 */
wheelDelta: number;
/** Is Ctrl key pressed | 是否按下 Ctrl */
ctrlKey: boolean;
/** Is Shift key pressed | 是否按下 Shift */
shiftKey: boolean;
/** Is Alt key pressed | 是否按下 Alt */
altKey: boolean;
/** Original DOM event | 原始 DOM 事件 */
nativeEvent?: MouseEvent | TouchEvent | WheelEvent;
}
/**
* Create default input event data
* 创建默认输入事件数据
*/
export function createInputEventData(): IInputEventData {
return {
touchId: 0,
stageX: 0,
stageY: 0,
button: 0,
wheelDelta: 0,
ctrlKey: false,
shiftKey: false,
altKey: false
};
}