/* * This file is part of TREB. * * TREB is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any * later version. * * TREB is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with TREB. If not, see . * * Copyright 2022-2026 trebco, llc. * info@treb.app * */ // import { Yield } from './dispatch'; // import { IEventSource } from './ievent_source'; let subscription_token_generator = 1000; interface EventSubscription { subscriber: (event: T) => void; token: number; } /** * relatively simple event publish/subscribe mechanism. * not as simple as it used to be. * * UPDATE removing unecessary interface (not sure what that * was for, but no one else is using it). */ export class EventSource { // implements IEventSource { /** pending events */ private queue: T[] = []; /** flag indicating whether we have already triggered a callback */ private dispatched = false; /** regular subscriptions */ private subscribers: Array> = []; /* * pass-through modules: these are peers * / private pass_through: Array> = []; */ constructor(private verbose = false, private log_id?: string) { } /** * FIXME: does anybody call this with an array? it's no longer * necessary for multiple messages to prevent extra callbacks... */ public Publish(event: T | T[]) { if (this.verbose) { console.info(`es publish (${this.log_id})`, event); } // here's our updated synchronous mechanism, passing through // FIXME: no one uses this (I think). drop it. // this.pass_through.forEach((source) => source.Publish(event)); // don't bother if there are no subscribers (implies you must // subscribe before first event... not sure if that's reasonable) /* if (!this.subscribers.length) { return; // ... } */ // queue event or events if (Array.isArray(event)) { this.queue.push(...event); } else { this.queue.push(event); } // then call the dispatch function. gate this in case we get // this call multiple times before a callback. if (!this.dispatched) { this.dispatched = true; Promise.resolve().then(() => { const events = this.queue.slice(0); this.dispatched = false; this.queue = []; // FIXME: should we cache subscribers as well? (...) for (const queued_event of events) { for (const subscription of this.subscribers) { subscription.subscriber(queued_event); } } }); } } /** * subscription returns a token which can be used to cancel subscription. * this token is a number, guaranteed to be !0 so you can test for falsy. */ public Subscribe(subscriber: (event: T) => void): number { const token = subscription_token_generator++; this.subscribers.push({ subscriber, token }); return token; } /** cancel a single subscription */ public Cancel(token: number) { this.subscribers = this.subscribers.filter((subscription) => subscription.token !== token); } /** * cancel all subscriptions AND ALL PASS-THROUGH SOURCES. */ public CancelAll() { this.subscribers = []; // this.pass_through = []; } /* * * pass-through (redirected) subscription, synchronous on this end. does * not support unsubscribe atm (FIXME) * / public PassThrough(source: EventSource) { this.pass_through.push(source); } */ }