Files
esengine/packages/editor-core/src/Services/MessageHub.ts

222 lines
5.9 KiB
TypeScript
Raw Normal View History

2025-10-14 22:33:55 +08:00
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');
}
}
// Service identifier for DI registration (用于跨包插件访问)
// 使用 Symbol.for 确保跨包共享同一个 Symbol
export const IMessageHub = Symbol.for('IMessageHub');