编辑器核心框架

This commit is contained in:
YHH
2025-10-14 22:33:55 +08:00
parent cac6aedf78
commit b20b2ae4ce
12 changed files with 1697 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
import type { IService } from '@esengine/ecs-framework';
import { Injectable } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
const logger = createLogger('MessageHub');
/**
* 消息处理器类型
*/
export type MessageHandler<T = any> = (data: T) => void | Promise<void>;
/**
* 消息订阅
*/
interface MessageSubscription {
topic: string;
handler: MessageHandler;
once: boolean;
}
/**
* 消息总线
*
* 提供插件间的事件通信机制,支持订阅/发布模式。
*/
@Injectable()
export class MessageHub implements IService {
private subscriptions: Map<string, MessageSubscription[]> = new Map();
private subscriptionId: number = 0;
/**
* 订阅消息
*
* @param topic - 消息主题
* @param handler - 消息处理器
* @returns 取消订阅的函数
*/
public subscribe<T = any>(topic: string, handler: MessageHandler<T>): () => void {
return this.addSubscription(topic, handler, false);
}
/**
* 订阅一次性消息
*
* @param topic - 消息主题
* @param handler - 消息处理器
* @returns 取消订阅的函数
*/
public subscribeOnce<T = any>(topic: string, handler: MessageHandler<T>): () => void {
return this.addSubscription(topic, handler, true);
}
/**
* 添加订阅
*/
private addSubscription(topic: string, handler: MessageHandler, once: boolean): () => void {
const subscription: MessageSubscription = {
topic,
handler,
once
};
let subs = this.subscriptions.get(topic);
if (!subs) {
subs = [];
this.subscriptions.set(topic, subs);
}
subs.push(subscription);
const subId = ++this.subscriptionId;
logger.debug(`Subscribed to topic: ${topic} (id: ${subId}, once: ${once})`);
return () => {
this.unsubscribe(topic, subscription);
logger.debug(`Unsubscribed from topic: ${topic} (id: ${subId})`);
};
}
/**
* 取消订阅
*/
private unsubscribe(topic: string, subscription: MessageSubscription): void {
const subs = this.subscriptions.get(topic);
if (!subs) {
return;
}
const index = subs.indexOf(subscription);
if (index !== -1) {
subs.splice(index, 1);
}
if (subs.length === 0) {
this.subscriptions.delete(topic);
}
}
/**
* 发布消息
*
* @param topic - 消息主题
* @param data - 消息数据
*/
public async publish<T = any>(topic: string, data?: T): Promise<void> {
const subs = this.subscriptions.get(topic);
if (!subs || subs.length === 0) {
logger.debug(`No subscribers for topic: ${topic}`);
return;
}
logger.debug(`Publishing message to topic: ${topic} (${subs.length} subscribers)`);
const onceSubscriptions: MessageSubscription[] = [];
for (const sub of subs) {
try {
await sub.handler(data);
if (sub.once) {
onceSubscriptions.push(sub);
}
} catch (error) {
logger.error(`Error in message handler for topic ${topic}:`, error);
}
}
for (const sub of onceSubscriptions) {
this.unsubscribe(topic, sub);
}
}
/**
* 同步发布消息
*
* @param topic - 消息主题
* @param data - 消息数据
*/
public publishSync<T = any>(topic: string, data?: T): void {
const subs = this.subscriptions.get(topic);
if (!subs || subs.length === 0) {
logger.debug(`No subscribers for topic: ${topic}`);
return;
}
logger.debug(`Publishing sync message to topic: ${topic} (${subs.length} subscribers)`);
const onceSubscriptions: MessageSubscription[] = [];
for (const sub of subs) {
try {
const result = sub.handler(data);
if (result instanceof Promise) {
logger.warn(`Async handler used with publishSync for topic: ${topic}`);
}
if (sub.once) {
onceSubscriptions.push(sub);
}
} catch (error) {
logger.error(`Error in message handler for topic ${topic}:`, error);
}
}
for (const sub of onceSubscriptions) {
this.unsubscribe(topic, sub);
}
}
/**
* 取消所有指定主题的订阅
*
* @param topic - 消息主题
*/
public unsubscribeAll(topic: string): void {
const deleted = this.subscriptions.delete(topic);
if (deleted) {
logger.debug(`Unsubscribed all from topic: ${topic}`);
}
}
/**
* 检查主题是否有订阅者
*
* @param topic - 消息主题
* @returns 是否有订阅者
*/
public hasSubscribers(topic: string): boolean {
const subs = this.subscriptions.get(topic);
return subs !== undefined && subs.length > 0;
}
/**
* 获取所有主题
*
* @returns 主题列表
*/
public getTopics(): string[] {
return Array.from(this.subscriptions.keys());
}
/**
* 获取主题的订阅者数量
*
* @param topic - 消息主题
* @returns 订阅者数量
*/
public getSubscriberCount(topic: string): number {
const subs = this.subscriptions.get(topic);
return subs ? subs.length : 0;
}
/**
* 释放资源
*/
public dispose(): void {
this.subscriptions.clear();
logger.info('MessageHub disposed');
}
}

View File

@@ -0,0 +1,181 @@
import type { IService } from '@esengine/ecs-framework';
import { Injectable } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
import type { ISerializer } from '../Plugins/IEditorPlugin';
const logger = createLogger('SerializerRegistry');
/**
* 序列化器注册表
*
* 管理所有数据序列化器的注册和查询。
*/
@Injectable()
export class SerializerRegistry implements IService {
private serializers: Map<string, ISerializer> = new Map();
/**
* 注册序列化器
*
* @param pluginName - 插件名称
* @param serializer - 序列化器实例
*/
public register(pluginName: string, serializer: ISerializer): void {
const type = serializer.getSupportedType();
const key = `${pluginName}:${type}`;
if (this.serializers.has(key)) {
logger.warn(`Serializer for ${key} is already registered`);
return;
}
this.serializers.set(key, serializer);
logger.info(`Registered serializer: ${key}`);
}
/**
* 批量注册序列化器
*
* @param pluginName - 插件名称
* @param serializers - 序列化器实例数组
*/
public registerMultiple(pluginName: string, serializers: ISerializer[]): void {
for (const serializer of serializers) {
this.register(pluginName, serializer);
}
}
/**
* 注销序列化器
*
* @param pluginName - 插件名称
* @param type - 数据类型
* @returns 是否成功注销
*/
public unregister(pluginName: string, type: string): boolean {
const key = `${pluginName}:${type}`;
const result = this.serializers.delete(key);
if (result) {
logger.info(`Unregistered serializer: ${key}`);
}
return result;
}
/**
* 注销插件的所有序列化器
*
* @param pluginName - 插件名称
*/
public unregisterAll(pluginName: string): void {
const prefix = `${pluginName}:`;
const keysToDelete: string[] = [];
for (const key of this.serializers.keys()) {
if (key.startsWith(prefix)) {
keysToDelete.push(key);
}
}
for (const key of keysToDelete) {
this.serializers.delete(key);
logger.info(`Unregistered serializer: ${key}`);
}
}
/**
* 获取序列化器
*
* @param pluginName - 插件名称
* @param type - 数据类型
* @returns 序列化器实例,如果未找到则返回 undefined
*/
public get(pluginName: string, type: string): ISerializer | undefined {
const key = `${pluginName}:${type}`;
return this.serializers.get(key);
}
/**
* 查找支持指定类型的序列化器
*
* @param type - 数据类型
* @returns 序列化器实例数组
*/
public findByType(type: string): ISerializer[] {
const result: ISerializer[] = [];
for (const [key, serializer] of this.serializers) {
if (key.endsWith(`:${type}`)) {
result.push(serializer);
}
}
return result;
}
/**
* 获取所有序列化器
*
* @returns 序列化器映射表
*/
public getAll(): Map<string, ISerializer> {
return new Map(this.serializers);
}
/**
* 检查序列化器是否已注册
*
* @param pluginName - 插件名称
* @param type - 数据类型
* @returns 是否已注册
*/
public has(pluginName: string, type: string): boolean {
const key = `${pluginName}:${type}`;
return this.serializers.has(key);
}
/**
* 序列化数据
*
* @param pluginName - 插件名称
* @param type - 数据类型
* @param data - 要序列化的数据
* @returns 二进制数据
* @throws 如果序列化器未注册
*/
public serialize<T = any>(pluginName: string, type: string, data: T): Uint8Array {
const serializer = this.get(pluginName, type);
if (!serializer) {
throw new Error(`Serializer not found: ${pluginName}:${type}`);
}
return serializer.serialize(data);
}
/**
* 反序列化数据
*
* @param pluginName - 插件名称
* @param type - 数据类型
* @param data - 二进制数据
* @returns 反序列化后的数据
* @throws 如果序列化器未注册
*/
public deserialize<T = any>(pluginName: string, type: string, data: Uint8Array): T {
const serializer = this.get(pluginName, type);
if (!serializer) {
throw new Error(`Serializer not found: ${pluginName}:${type}`);
}
return serializer.deserialize(data);
}
/**
* 释放资源
*/
public dispose(): void {
this.serializers.clear();
logger.info('SerializerRegistry disposed');
}
}

View File

@@ -0,0 +1,202 @@
import type { IService } from '@esengine/ecs-framework';
import { Injectable } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
import type { MenuItem, ToolbarItem, PanelDescriptor } from '../Types/UITypes';
const logger = createLogger('UIRegistry');
/**
* UI 注册表
*
* 管理所有编辑器 UI 扩展点的注册和查询。
*/
@Injectable()
export class UIRegistry implements IService {
private menus: Map<string, MenuItem> = new Map();
private toolbarItems: Map<string, ToolbarItem> = new Map();
private panels: Map<string, PanelDescriptor> = new Map();
/**
* 注册菜单项
*/
public registerMenu(item: MenuItem): void {
if (this.menus.has(item.id)) {
logger.warn(`Menu item ${item.id} is already registered`);
return;
}
this.menus.set(item.id, item);
logger.debug(`Registered menu item: ${item.id}`);
}
/**
* 批量注册菜单项
*/
public registerMenus(items: MenuItem[]): void {
for (const item of items) {
this.registerMenu(item);
}
}
/**
* 注销菜单项
*/
public unregisterMenu(id: string): boolean {
const result = this.menus.delete(id);
if (result) {
logger.debug(`Unregistered menu item: ${id}`);
}
return result;
}
/**
* 获取菜单项
*/
public getMenu(id: string): MenuItem | undefined {
return this.menus.get(id);
}
/**
* 获取所有菜单项
*/
public getAllMenus(): MenuItem[] {
return Array.from(this.menus.values()).sort((a, b) => {
return (a.order ?? 0) - (b.order ?? 0);
});
}
/**
* 获取指定父菜单的子菜单
*/
public getChildMenus(parentId: string): MenuItem[] {
return this.getAllMenus()
.filter(item => item.parentId === parentId)
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
}
/**
* 注册工具栏项
*/
public registerToolbarItem(item: ToolbarItem): void {
if (this.toolbarItems.has(item.id)) {
logger.warn(`Toolbar item ${item.id} is already registered`);
return;
}
this.toolbarItems.set(item.id, item);
logger.debug(`Registered toolbar item: ${item.id}`);
}
/**
* 批量注册工具栏项
*/
public registerToolbarItems(items: ToolbarItem[]): void {
for (const item of items) {
this.registerToolbarItem(item);
}
}
/**
* 注销工具栏项
*/
public unregisterToolbarItem(id: string): boolean {
const result = this.toolbarItems.delete(id);
if (result) {
logger.debug(`Unregistered toolbar item: ${id}`);
}
return result;
}
/**
* 获取工具栏项
*/
public getToolbarItem(id: string): ToolbarItem | undefined {
return this.toolbarItems.get(id);
}
/**
* 获取所有工具栏项
*/
public getAllToolbarItems(): ToolbarItem[] {
return Array.from(this.toolbarItems.values()).sort((a, b) => {
return (a.order ?? 0) - (b.order ?? 0);
});
}
/**
* 获取指定组的工具栏项
*/
public getToolbarItemsByGroup(groupId: string): ToolbarItem[] {
return this.getAllToolbarItems()
.filter(item => item.groupId === groupId)
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
}
/**
* 注册面板
*/
public registerPanel(panel: PanelDescriptor): void {
if (this.panels.has(panel.id)) {
logger.warn(`Panel ${panel.id} is already registered`);
return;
}
this.panels.set(panel.id, panel);
logger.debug(`Registered panel: ${panel.id}`);
}
/**
* 批量注册面板
*/
public registerPanels(panels: PanelDescriptor[]): void {
for (const panel of panels) {
this.registerPanel(panel);
}
}
/**
* 注销面板
*/
public unregisterPanel(id: string): boolean {
const result = this.panels.delete(id);
if (result) {
logger.debug(`Unregistered panel: ${id}`);
}
return result;
}
/**
* 获取面板
*/
public getPanel(id: string): PanelDescriptor | undefined {
return this.panels.get(id);
}
/**
* 获取所有面板
*/
public getAllPanels(): PanelDescriptor[] {
return Array.from(this.panels.values()).sort((a, b) => {
return (a.order ?? 0) - (b.order ?? 0);
});
}
/**
* 获取指定位置的面板
*/
public getPanelsByPosition(position: string): PanelDescriptor[] {
return this.getAllPanels()
.filter(panel => panel.position === position)
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
}
/**
* 释放资源
*/
public dispose(): void {
this.menus.clear();
this.toolbarItems.clear();
this.panels.clear();
logger.info('UIRegistry disposed');
}
}