|
| 1 | +// |
| 2 | +// EventDispatcher.swift |
| 3 | +// Copyright (c) 2022, Flowduino |
| 4 | +// Authored by Simon J. Stuart on 4th August 2022 |
| 5 | +// |
| 6 | +// Subject to terms, restrictions, and liability waiver of the MIT License |
| 7 | +// |
| 8 | + |
| 9 | +import Foundation |
| 10 | +import ThreadSafeSwift |
| 11 | +import Observable |
| 12 | + |
| 13 | +public class EventDispatcher: EventHandler, EventDispatchable { |
| 14 | + struct ListenerContainer { |
| 15 | + weak var listener: (any EventReceivable)? |
| 16 | + } |
| 17 | + |
| 18 | + /** |
| 19 | + Stores all of the Listeners against the fully-qualified name of the corresponding `Eventable` Type |
| 20 | + - Author: Simon J. Stuart |
| 21 | + - Version: 1.0.0 |
| 22 | + */ |
| 23 | + @ThreadSafeSemaphore private var listeners = [String:[ListenerContainer]]() //TODO: Make this a Revolving Door collection! |
| 24 | + |
| 25 | + public func addListener(_ listener: any EventReceivable, forEventType: Eventable.Type) { |
| 26 | + let eventTypeName = String(reflecting: forEventType) |
| 27 | + |
| 28 | + _listeners.withLock { listeners in |
| 29 | + var bucket = listeners[eventTypeName] |
| 30 | + let newBucket = bucket == nil |
| 31 | + if newBucket { bucket = [ListenerContainer]() } /// If there's no Bucket for this Event Type, create one |
| 32 | + |
| 33 | + /// If it's NOT a New Bucket, and the Bucket already contains this Listener... |
| 34 | + if !newBucket && bucket!.contains(where: { listenerContainer in |
| 35 | + listenerContainer.listener != nil && ObjectIdentifier(listenerContainer.listener!) == ObjectIdentifier(listener) |
| 36 | + }) { |
| 37 | + return // ... just Return! |
| 38 | + } |
| 39 | + |
| 40 | + /// If we reach here, the Listener is not already in the Bucket, so let's add it! |
| 41 | + bucket!.append(ListenerContainer(listener: listener)) |
| 42 | + listeners[eventTypeName] = bucket! |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + public func removeListener(_ listener: any EventReceivable, forEventType: Eventable.Type) { |
| 47 | + let eventTypeName = String(reflecting: forEventType) |
| 48 | + |
| 49 | + _listeners.withLock { listeners in |
| 50 | + var bucket = listeners[eventTypeName] |
| 51 | + if bucket == nil { return } /// Can't remove a Listener if there isn't even a Bucket for hte Event Type |
| 52 | + |
| 53 | + /// Remove any Listeners from this Event-Type Bucket for the given `listener` instance. |
| 54 | + bucket!.removeAll { listenerContainer in |
| 55 | + listenerContainer.listener != nil && ObjectIdentifier(listenerContainer.listener!) == ObjectIdentifier(listener) |
| 56 | + } |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + public func removeListener(_ listener: any EventReceivable) { |
| 61 | + _listeners.withLock { listeners in |
| 62 | + for (eventTypeName, bucket) in listeners { /// Iterate every Event Type |
| 63 | + var newBucket = bucket // Copy the Bucket |
| 64 | + newBucket.removeAll { listenerContainer in /// Remove any occurences of the given Listener from the Bucket |
| 65 | + listenerContainer.listener != nil && ObjectIdentifier(listenerContainer.listener!) == ObjectIdentifier(listener) |
| 66 | + } |
| 67 | + listeners[eventTypeName] = newBucket /// Update the Bucket for this Event Type |
| 68 | + } |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + /** |
| 73 | + Dispatch the Event to all Subscribed Listeners |
| 74 | + - Author: Simon J. Stuart |
| 75 | + - Version: 1.0.0 |
| 76 | + */ |
| 77 | + override internal func processEvent(_ event: any Eventable, dispatchMethod: EventDispatchMethod, priority: EventPriority) { |
| 78 | + let eventTypeName = String(reflecting: type(of: event)) |
| 79 | + |
| 80 | + _listeners.withLock { listeners in |
| 81 | + let bucket = listeners[eventTypeName] |
| 82 | + if bucket == nil { return } /// No Listeners, so nothing more to do! |
| 83 | + |
| 84 | + var newBucket: [ListenerContainer]? = nil |
| 85 | + |
| 86 | + for listener in bucket! { |
| 87 | + if listener.listener == nil { /// If the Listener is `nil`... |
| 88 | + if newBucket != nil { continue } /// ...If we've already removed all `nil` values, move on |
| 89 | + ///... otherwise, remove the Listener from this bucket |
| 90 | + newBucket = bucket |
| 91 | + newBucket?.removeAll(where: { listenerContainer in |
| 92 | + listenerContainer.listener == nil |
| 93 | + }) |
| 94 | + continue |
| 95 | + } |
| 96 | + |
| 97 | + // so, we have a listener... let's deal with it! |
| 98 | + switch dispatchMethod { |
| 99 | + case .stack: |
| 100 | + listener.listener!.stackEvent(event, priority: priority) |
| 101 | + case .queue: |
| 102 | + listener.listener!.queueEvent(event, priority: priority) |
| 103 | + } |
| 104 | + } |
| 105 | + if newBucket != nil { listeners[eventTypeName] = newBucket } |
| 106 | + } |
| 107 | + } |
| 108 | +} |
0 commit comments