From c043560f00e9d766ad6211ea362889ac3ff47ef9 Mon Sep 17 00:00:00 2001 From: Ethan Ferrari Date: Wed, 7 Sep 2022 14:55:14 -0500 Subject: [PATCH 1/4] track listener counts internally --- src/strongbus.ts | 73 +++++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/src/strongbus.ts b/src/strongbus.ts index 65fac2f..066be3d 100644 --- a/src/strongbus.ts +++ b/src/strongbus.ts @@ -68,7 +68,10 @@ export class Bus implements } private _active = false; + private _ownListenerTotalCount: number = 0; private _delegates = new Map, Events.Subscription[]>(); + private _delegateListenerTotalCount: number = 0; + private _delegateListenerCountsByEvent = new Map|Events.WILDCARD, number>(); private readonly subscriptionCache = new Map(); private readonly options: Required & {thresholds: Required}; @@ -447,9 +450,9 @@ export class Bus implements if(!this._delegates.has(delegate)) { this._delegates.set(delegate, [ delegate.hook(Lifecycle.willAddListener, this.willAddListener), - delegate.hook(Lifecycle.didAddListener, this.didAddDelegateListener), + delegate.hook(Lifecycle.didAddListener, event => this.didAddListener(event, delegate)), delegate.hook(Lifecycle.willRemoveListener, this.willRemoveListener), - delegate.hook(Lifecycle.didRemoveListener, this.didRemoveDelegateListener) + delegate.hook(Lifecycle.didRemoveListener, event => this.didRemoveListener(event, delegate)) ]); } } @@ -514,12 +517,7 @@ export class Bus implements * @getter `boolean` */ public get hasDelegateListeners(): boolean { - for(const delegate of this._delegates.keys()) { - if(delegate.hasListeners) { - return true; - } - } - return false; + return this._delegateListenerTotalCount > 0; } private _cachedGetListersValue: Map|Events.WILDCARD, ReadonlySet>; @@ -559,21 +557,31 @@ export class Bus implements } public hasListenersFor(event: EventKeys|Events.WILDCARD): boolean { - return this.hasOwnListenersFor(event) || this.hasDelegateListenersFor(event); + return this.getListenerCountFor(event) > 0; } public hasOwnListenersFor(event: EventKeys|Events.WILDCARD): boolean { - const handlers = this.bus.get(event); - return handlers?.size > 0; + return this.getOwnListenerCountFor(event) > 0; } public hasDelegateListenersFor(event: EventKeys|Events.WILDCARD): boolean { - for(const delegate of this._delegates.keys()) { - if(delegate.hasListenersFor(event)) { - return true; - } - } - return false; + return this.getDelegateListenerCountFor(event) > 0; + } + + public get listenerCount(): number { + return this._ownListenerTotalCount + this._delegateListenerTotalCount; + } + + public getListenerCountFor(event: EventKeys|Events.WILDCARD): number { + return this.getOwnListenerCountFor(event) + this.getDelegateListenerCountFor(event); + } + + public getOwnListenerCountFor(event: EventKeys|Events.WILDCARD): number { + return this.bus.get(event)?.size ?? 0; + } + + public getDelegateListenerCountFor(event: EventKeys|Events.WILDCARD): number { + return (this._delegateListenerCountsByEvent.get(event) ?? 0); } /** @@ -715,11 +723,18 @@ export class Bus implements } } - private didAddListener(event: EventKeys|Events.WILDCARD, invokedByDelegate: boolean = false) { + private didAddListener(event: EventKeys|Events.WILDCARD, bus: Bus = this) { + this._cachedGetListersValue = null; - if(!invokedByDelegate) { + if(bus === this) { this._cachedGetOwnListenersValue = null; + this._ownListenerTotalCount = Math.max(this._ownListenerTotalCount + 1, 0); + } else { + const currCount = this._delegateListenerCountsByEvent.get(event) ?? 0; + this._delegateListenerCountsByEvent.set(event, Math.max(currCount + 1, 0)); + this._delegateListenerTotalCount = Math.max(this._delegateListenerTotalCount + 1, 0); } + this.emitLifecycleEvent(Lifecycle.didAddListener, event); if(!this.active && this.hasListeners) { this._active = true; @@ -727,12 +742,9 @@ export class Bus implements } } - private didAddDelegateListener(event: EventKeys|Events.WILDCARD): void { - this.didAddListener(event, true); - } private willRemoveListener(event: EventKeys|Events.WILDCARD): void { - const eventHandlerCount = this.listeners.get(event)?.size || 0; + const eventHandlerCount = this.getListenerCountFor(event); if(eventHandlerCount) { this.emitLifecycleEvent(Lifecycle.willRemoveListener, event); if(this.active && this.listeners.size === 1 && eventHandlerCount === 1) { @@ -741,21 +753,24 @@ export class Bus implements } } - private didRemoveListener(event: EventKeys|Events.WILDCARD, invokedByDelegate: boolean = false) { + private didRemoveListener(event: EventKeys|Events.WILDCARD, bus: Bus = this) { + this._cachedGetListersValue = null; - if(!invokedByDelegate) { + if(bus === this) { this._cachedGetOwnListenersValue = null; + this._ownListenerTotalCount = Math.max(this._ownListenerTotalCount - 1, 0); + } else { + const currCount = this._delegateListenerCountsByEvent.get(event) ?? 0; + this._delegateListenerCountsByEvent.set(event, Math.max(currCount - 1, 0)); + this._delegateListenerTotalCount = Math.max(this._delegateListenerTotalCount - 1, 0); } + this.emitLifecycleEvent(Lifecycle.didRemoveListener, event); if(this.active && !this.hasListeners) { this._active = false; this.emitLifecycleEvent(Lifecycle.idle, null); } } - - private didRemoveDelegateListener(event: EventKeys|Events.WILDCARD): void { - this.didRemoveListener(event, true); - } } From 7f1f470c974dd26657594d281a3f42bff9bd082f Mon Sep 17 00:00:00 2001 From: Ethan Ferrari Date: Wed, 7 Sep 2022 14:58:58 -0500 Subject: [PATCH 2/4] replace call to this.listeners with count read --- src/strongbus.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strongbus.ts b/src/strongbus.ts index 066be3d..aa71478 100644 --- a/src/strongbus.ts +++ b/src/strongbus.ts @@ -718,7 +718,7 @@ export class Bus implements private willAddListener(event: EventKeys|Events.WILDCARD) { this.emitLifecycleEvent(Lifecycle.willAddListener, event); - if(!this.active) { + if(!this._active) { this.emitLifecycleEvent(Lifecycle.willActivate, null); } } @@ -736,7 +736,7 @@ export class Bus implements } this.emitLifecycleEvent(Lifecycle.didAddListener, event); - if(!this.active && this.hasListeners) { + if(!this._active && this.hasListeners) { this._active = true; this.emitLifecycleEvent(Lifecycle.active, null); } @@ -747,7 +747,7 @@ export class Bus implements const eventHandlerCount = this.getListenerCountFor(event); if(eventHandlerCount) { this.emitLifecycleEvent(Lifecycle.willRemoveListener, event); - if(this.active && this.listeners.size === 1 && eventHandlerCount === 1) { + if(this._active && this.listenerCount === 1 && eventHandlerCount === 1) { this.emitLifecycleEvent(Lifecycle.willIdle, null); } } @@ -766,7 +766,7 @@ export class Bus implements } this.emitLifecycleEvent(Lifecycle.didRemoveListener, event); - if(this.active && !this.hasListeners) { + if(this._active && !this.hasListeners) { this._active = false; this.emitLifecycleEvent(Lifecycle.idle, null); } From ec8206b85d98a30a07954691f89d3eaded01413b Mon Sep 17 00:00:00 2001 From: Ethan Ferrari Date: Wed, 7 Sep 2022 15:14:10 -0500 Subject: [PATCH 3/4] add spec for listenerCount --- src/strongbus_spec.ts | 88 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/strongbus_spec.ts b/src/strongbus_spec.ts index 0bc4aae..f1fd7b8 100644 --- a/src/strongbus_spec.ts +++ b/src/strongbus_spec.ts @@ -1250,6 +1250,94 @@ describe('Strongbus.Bus', () => { }); }); + describe('#listenerCount', () => { + describe('given an instance has no delegates', () => { + it('counts listeners for the instance', () => { + bus.on('foo', onTestEvent); + bus.on('bar', () => ({})); + + expect(bus.listenerCount).toEqual(2); + }); + }); + + describe('given an instance has delegates', () => { + let bus2: DelegateTestBus; + beforeEach(() => { + bus2 = new DelegateTestBus({emulateListenerCount: true}); + bus.pipe(bus2); + }); + + describe('and a delegate has listeners', () => { + it('counts listeners for the instance and its delegates', () => { + bus.on('foo', onTestEvent); + bus.on('bar', () => ({})); + bus2.on('foo', onTestEvent); + + expect(bus.listenerCount).toEqual(3); + }); + }); + + describe('and delegates have no listeners', () => { + it('counts listeners for the instance and its delegates', () => { + bus.on('foo', onTestEvent); + bus.on('bar', () => ({})); + + expect(bus.listenerCount).toEqual(2); + }); + }); + }); + }); + + describe('#listenerCount', () => { + describe('given an instance has no delegates', () => { + it('counts listeners for the instance', () => { + const sub1 = bus.on('foo', onTestEvent); + const sub2 = bus.on('bar', () => ({})); + + expect(bus.listenerCount).toEqual(2); + sub1.unsubscribe(); + expect(bus.listenerCount).toEqual(1); + sub2.unsubscribe(); + expect(bus.listenerCount).toEqual(0); + }); + }); + + describe('given an instance has delegates', () => { + let bus2: DelegateTestBus; + beforeEach(() => { + bus2 = new DelegateTestBus({emulateListenerCount: true}); + bus.pipe(bus2); + }); + + describe('and a delegate has listeners', () => { + it('counts listeners for the instance and its delegates', () => { + const sub1 = bus.on('foo', onTestEvent); + const sub2 = bus.on('bar', () => ({})); + const sub3 = bus2.on('foo', onTestEvent); + + expect(bus.listenerCount).toEqual(3); + sub1.unsubscribe(); + expect(bus.listenerCount).toEqual(2); + sub2.unsubscribe(); + expect(bus.listenerCount).toEqual(1); + sub3.unsubscribe(); + expect(bus.listenerCount).toEqual(0); + sub1.unsubscribe(); + expect(bus.listenerCount).toEqual(0); + }); + }); + + describe('and delegates have no listeners', () => { + it('counts listeners for the instance and its delegates', () => { + bus.on('foo', onTestEvent); + bus.on('bar', () => ({})); + + expect(bus.listenerCount).toEqual(2); + }); + }); + }); + }); + describe('#destroy', () => { it('removes all event listeners, triggering proper lifecycle events', () => { bus = new Strongbus.Bus({allowUnhandledEvents: false}); From 39a33b456c5370dc76226d8a31ea94137cd08ceb Mon Sep 17 00:00:00 2001 From: Ethan Ferrari Date: Wed, 7 Sep 2022 15:17:34 -0500 Subject: [PATCH 4/4] remove redundant memo --- src/strongbus.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/strongbus.ts b/src/strongbus.ts index aa71478..5635166 100644 --- a/src/strongbus.ts +++ b/src/strongbus.ts @@ -68,7 +68,6 @@ export class Bus implements } private _active = false; - private _ownListenerTotalCount: number = 0; private _delegates = new Map, Events.Subscription[]>(); private _delegateListenerTotalCount: number = 0; private _delegateListenerCountsByEvent = new Map|Events.WILDCARD, number>(); @@ -569,7 +568,7 @@ export class Bus implements } public get listenerCount(): number { - return this._ownListenerTotalCount + this._delegateListenerTotalCount; + return this.bus.size + this._delegateListenerTotalCount; } public getListenerCountFor(event: EventKeys|Events.WILDCARD): number { @@ -728,7 +727,6 @@ export class Bus implements this._cachedGetListersValue = null; if(bus === this) { this._cachedGetOwnListenersValue = null; - this._ownListenerTotalCount = Math.max(this._ownListenerTotalCount + 1, 0); } else { const currCount = this._delegateListenerCountsByEvent.get(event) ?? 0; this._delegateListenerCountsByEvent.set(event, Math.max(currCount + 1, 0)); @@ -758,7 +756,6 @@ export class Bus implements this._cachedGetListersValue = null; if(bus === this) { this._cachedGetOwnListenersValue = null; - this._ownListenerTotalCount = Math.max(this._ownListenerTotalCount - 1, 0); } else { const currCount = this._delegateListenerCountsByEvent.get(event) ?? 0; this._delegateListenerCountsByEvent.set(event, Math.max(currCount - 1, 0));