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:
349
packages/rendering/fairygui/src/events/EventDispatcher.ts
Normal file
349
packages/rendering/fairygui/src/events/EventDispatcher.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
142
packages/rendering/fairygui/src/events/Events.ts
Normal file
142
packages/rendering/fairygui/src/events/Events.ts
Normal 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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user