import type { EventsConstraint, SubscriberEntry } from "./common.ts" import type { InternalProxySubscriberEntryMap } from "./internal.ts" /** * @description 表示 InstanceEventProxy 用于接入底层事件目标的适配器。 */ export interface InstanceEventProxyTargetAdapter> { emit(event: K, ...args: Parameters): boolean subscribe(event: K, subscriber: Events[K]): void unsubscribe(event: K, subscriber: Events[K]): void } /** * @description 表示 InstanceEventProxy 的构造选项。 */ export interface InstanceEventProxyOptions> { targetAdapter: InstanceEventProxyTargetAdapter /** * @description 在代理管理的订阅者执行出错时接收错误与对应订阅项。 */ onSubscriberError?: ( subscriberEntry: SubscriberEntry, error: unknown, ) => void } /** * @description 将单个事件目标适配为可管理下游订阅关系的代理。 */ export class InstanceEventProxy> { protected options: InstanceEventProxyOptions protected subscribers: InternalProxySubscriberEntryMap constructor(options: InstanceEventProxyOptions) { this.options = options this.subscribers = {} } /** * @description 检查某个事件是否已经管理给定订阅者。 */ has(event: K, subscriber: Events[K]): boolean { const proxySubscriberEntry = this.subscribers[event] if (proxySubscriberEntry === undefined) { return false } const has = proxySubscriberEntry.managedSubscribers.has(subscriber) return has } protected internalSubscribe( event: K, subscriber: Events[K], once: boolean, ): SubscriberEntry { let proxySubscriberEntry = this.subscribers[event] if (proxySubscriberEntry === undefined) { const managedSubscribers = new Map>() const proxySubscriber = ((...args: Parameters) => { const managedSubscriberEntries = [...managedSubscribers.values()] for (const managedSubscriberEntry of managedSubscriberEntries) { try { managedSubscriberEntry.subscriber(...args) } catch (exception) { if (this.options.onSubscriberError !== undefined) { this.options.onSubscriberError(managedSubscriberEntry, exception) } else { console.error(`Error occurred while emitting event "${String(event)}":`, exception) } } finally { if (managedSubscriberEntry.once === true) { managedSubscriberEntry.unsubscribe() } } } }) as Events[K] proxySubscriberEntry = { proxySubscriber, managedSubscribers, } this.subscribers[event] = proxySubscriberEntry this.options.targetAdapter.subscribe(event, proxySubscriber) } const existingSubscriberEntry = proxySubscriberEntry.managedSubscribers.get(subscriber) if (existingSubscriberEntry !== undefined) { return existingSubscriberEntry } const unsubscribe = (): void => { this.unsubscribe(event, subscriber) } const newSubscriberEntry: SubscriberEntry = { event, once, subscriber, unsubscribe, } proxySubscriberEntry.managedSubscribers.set(subscriber, newSubscriberEntry) return newSubscriberEntry } /** * @description 为指定事件添加常规订阅者,并在首次接入时创建底层代理订阅。 */ subscribe(event: K, subscriber: Events[K]): SubscriberEntry { return this.internalSubscribe(event, subscriber, false) } /** * @description 为指定事件添加只触发一次的订阅者。 */ subscribeOnce(event: K, listener: Events[K]): SubscriberEntry { return this.internalSubscribe(event, listener, true) } /** * @description 移除指定事件上的给定订阅者。 */ unsubscribe(event: K, listener: Events[K]): this { const proxySubscriberEntry = this.subscribers[event] if (proxySubscriberEntry === undefined) { return this } proxySubscriberEntry.managedSubscribers.delete(listener) if (proxySubscriberEntry.managedSubscribers.size === 0) { this.options.targetAdapter.unsubscribe(event, proxySubscriberEntry.proxySubscriber) delete this.subscribers[event] } return this } /** * @description 移除指定事件上的全部代理订阅者。 */ removeSubscribersOfEvent(event: K): this { const proxySubscriberEntry = this.subscribers[event] if (proxySubscriberEntry === undefined) { return this } proxySubscriberEntry.managedSubscribers.clear() this.options.targetAdapter.unsubscribe(event, proxySubscriberEntry.proxySubscriber) delete this.subscribers[event] return this } /** * @description 移除代理当前管理的全部订阅者。 */ removeAllSubscribers(): this { const events = Object.keys(this.subscribers) for (const event of events) { this.removeSubscribersOfEvent(event as keyof Events) } this.subscribers = {} return this } /** * @description 通过底层适配器派发指定事件。 */ emit(event: K, ...args: Parameters): boolean { return this.options.targetAdapter.emit(event, ...args) } }