Skip to content

Commit f8aac7c

Browse files
authored
Merge pull request #5 from Flowduino/v5.0.0_LatestOnlyListeners
V5.0.0 latest only listeners
2 parents 6cd336d + 83592b6 commit f8aac7c

21 files changed

+263
-90
lines changed

README.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ let package = Package(
130130
dependencies: [
131131
.package(
132132
url: "https://github.com/Flowduino/EventDrivenSwift.git",
133-
.upToNextMajor(from: "4.2.0")
133+
.upToNextMajor(from: "5.0.0")
134134
),
135135
],
136136
//...
@@ -217,7 +217,7 @@ class TemperatureProcessor: EventThread {
217217
}
218218

219219
/// Define our Callback Function to process received TemperatureEvent Events
220-
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority) {
220+
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {
221221

222222
}
223223
}
@@ -230,6 +230,8 @@ The function `registerEventListeners` will be called automatically when an insta
230230

231231
Our *Callback* (or *Handler* or *Listener Event*) is called `onTemperatureEvent`, which is where we will implement whatever *Operation* is to be performed against a `TemperatureEvent`.
232232

233+
Version 5.0.0 introduces the new parameter, `dispatchTime`, which will always provide the `DispatchTime` reference at which the *Event* was *Dispatched*. You can use this to determine *Delta* (how much time has passed since the *Event* was *Dispatched*), which is particularly useful if you are performing interpolation and/or extrapolation.
234+
233235
Now, let's actually do something with our `TemperatureEvent` in the `onTemperatureEvent` method.
234236
```swift
235237
/// An Enum to map a Temperature value onto a Rating
@@ -262,7 +264,7 @@ Now, let's actually do something with our `TemperatureEvent` in the `onTemperatu
262264
@ThreadSafeSemaphore public var temperatureInCelsius: Float = Float.zero
263265
@ThreadSafeSemaphore public var temperatureRating: TemperatureRating = .freezing
264266

265-
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority) {
267+
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {
266268
temperatureInCelsius = event.temperatureInCelsius
267269
temperatureRating = TemperatureRating.fromTemperature(event.temperatureInCelsius)
268270
}
@@ -315,7 +317,7 @@ protocol TemperatureProcessorObserver: AnyObject {
315317
```
316318
Now let's modify the `onTemperatureEvent` method we implemented in the previous example:
317319
```swift
318-
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority) {
320+
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {
319321
temperatureInCelsius = event.temperatureInCelsius
320322
temperatureRating = TemperatureRating.fromTemperature(event.temperatureInCelsius)
321323

@@ -343,7 +345,7 @@ enum TemperatureRatingEvent: Eventable {
343345
```
344346
With the *Event* type defined, we can now once more expand our `onTemperatureEvent` to *Dispatch* our reciprocal `TemperatureRatingEvent`:
345347
```swift
346-
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority) {
348+
func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {
347349
temperatureInCelsius = event.temperatureInCelsius
348350
temperatureRating = TemperatureRating.fromTemperature(event.temperatureInCelsius)
349351

@@ -385,7 +387,7 @@ class TemperatureRatingViewModel: ObservableObject {
385387

386388
var listenerHandle: EventListenerHandling?
387389

388-
internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority) {
390+
internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {
389391
temperatureInCelsius = event.temperatureInCelsius
390392
temperatureRating = event.temperatureRating
391393
}
@@ -427,6 +429,30 @@ This will remove your *Listener Callback*, meaning it will no longer be invoked
427429

428430
`EventListener`s are an extremely versatile and very powerful addition to `EventDrivenSwift`.
429431

432+
## `EventListener` with *Latest-Only* Interest
433+
Version 4.3.0 of this library introduces the concept of *Latest-Only Listeners*. A *Latest-Only Listener* is a *Listener* that will only be invoked for the very latest *Event* of its requested *Event Type*. If there are a number of older *Events* of this type pending in a Queue/Stack, they will simply be skipped over... and only the very *Latest* will invoke your *Listener*.
434+
435+
We have made it incredibly simple for you to configure your *Listener* to be a *Latest-Only Listener*. Taking the previous code example, we can simply modify it as follows:
436+
```swift
437+
class TemperatureRatingViewModel: ObservableObject {
438+
@Published var temperatureInCelsius: Float
439+
@Published var temperatureRating: TemperatureRating
440+
441+
var listenerHandle: EventListenerHandling?
442+
443+
internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {
444+
temperatureInCelsius = event.temperatureInCelsius
445+
temperatureRating = event.temperatureRating
446+
}
447+
448+
init() {
449+
// Let's register our Event Listener Callback!
450+
listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, interestedIn: .latestOnly)
451+
}
452+
}
453+
```
454+
By including the `interestedIn` optional parameter when invoking `addListener` against any `Eventable` type, and passing for this parameter a value of `.latestOnly`, we define that this *Listener* is only interested in the *Latest* `TemperatureRatingEvent` to be *Dispatched*. Should a number of `TemperatureRatingEvent`s build up in the Queue/Stack, the above-defined *Listener* will simply discard any older Events, and only invoke for the newest.
455+
430456
## `EventPool`
431457
Version 4.0.0 introduces the extremely powerful `EventPool` solution, making it possible to create managed groups of `EventThread`s, where inbound *Events* will be directed to the best `EventThread` in the `EventPool` at any given moment.
432458

@@ -456,9 +482,8 @@ The above example would use the `EventPoolLowestLoadBalancer` implementation, wh
456482
## Features Coming Soon
457483
`EventDrivenSwift` is an evolving and ever-improving Library, so here are lists of the features you can expect in future releases.
458484

459-
Version 4.3.0 (or 5.0.0 if interface-breaking changes are required):
460-
- **Event Pool Scalers** - Dynamic Scaling for `EventPool` instances will be fully-implemented
461-
- **Latest-Only Events** - A Dispatch option to replace any unprocessed (older) *Events* with the newest *Event* of that specific *Eventable* type. This will be useful for things like sensor readings, where you only care about the most recent value possible (because older values are no longer relevant)
485+
Version 5.1.0 (or 6.0.0 if interface-breaking changes are required):
486+
- **Event Pool Scalers** - Dynamic Scaling for `EventPool` instances will be fully-implemented (for the moment, no automatic Scaling will occur, and you cannot change the scale of an *Event Pool* once it has been initialised)
462487

463488
## License
464489

Sources/EventDrivenSwift/Central/EventCentral.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ final public class EventCentral: EventDispatcher, EventCentralable {
6666
return _shared.eventCount
6767
}
6868
}
69-
69+
7070
private var _eventListener: EventListenable?
7171
internal var eventListener: EventListenable {
7272
get {
@@ -75,8 +75,8 @@ final public class EventCentral: EventDispatcher, EventCentralable {
7575
}
7676
}
7777

78-
@discardableResult @inline(__always) public static func addListener<TEvent>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling where TEvent : Eventable {
79-
return _shared.eventListener.addListener(requester, callback, forEventType: forEventType, executeOn: executeOn)
78+
@discardableResult @inline(__always) public static func addListener<TEvent>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread, interestedIn: EventListenerInterest = .all) -> EventListenerHandling where TEvent : Eventable {
79+
return _shared.eventListener.addListener(requester, callback, forEventType: forEventType, executeOn: executeOn, interestedIn: interestedIn)
8080
}
8181

8282
@inline(__always) public static func removeListener(_ token: UUID) {
@@ -87,11 +87,11 @@ final public class EventCentral: EventDispatcher, EventCentralable {
8787
_shared.eventListener.removeListener(token, typeOf: typeOf)
8888
}
8989

90-
@inline(__always) static public func scheduleQueue(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
90+
@inline(__always) public static func scheduleQueue(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
9191
_shared.scheduleQueue(event, at: at, priority: priority)
9292
}
9393

94-
@inline(__always) static public func scheduleStack(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
94+
@inline(__always) public static func scheduleStack(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
9595
_shared.scheduleStack(event, at: at, priority: priority)
9696
}
9797

Sources/EventDrivenSwift/Central/EventCentralable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public protocol EventCentralable {
6868
- forEventType: The `Eventable` Type for which to Register the Callback
6969
- Returns: A `UUID` value representing the `token` associated with this Event Callback
7070
*/
71-
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn) -> EventListenerHandling
71+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn, interestedIn: EventListenerInterest) -> EventListenerHandling
7272

7373
/**
7474
Locates and removes the given Listener `token` (if it exists) from the Central Event Listener

Sources/EventDrivenSwift/Event/Eventable.swift

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ public protocol Eventable {
6363
- callback: The code to invoke for the given `Eventable` Type
6464
- Returns: A `UUID` value representing the `token` associated with this Event Callback
6565
*/
66-
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn) -> EventListenerHandling
67-
68-
// @discardableResult static func addListener(_ requester: AnyObject?, _ eventType: any Eventable.Type, _ callback: @escaping TypedEventCallback<any Eventable.Type>, executeOn: ExecuteEventOn) -> UUID
66+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn, interestedIn: EventListenerInterest) -> EventListenerHandling
6967

7068
/**
7169
Locates and removes the given Listener `token` (if it exists) from the Central Event Listener
@@ -75,6 +73,20 @@ public protocol Eventable {
7573
- token: The Token of the Listener you wish to remove
7674
*/
7775
static func removeListener(_ token: UUID)
76+
77+
/**
78+
Returns the Fully-Qualified Type Name for this Eventable Type
79+
- Author: Simon J. Stuart
80+
- Version: 4.3.0
81+
*/
82+
func getEventTypeName() -> String
83+
84+
/**
85+
Returns the Fully-Qualified Type Name for this Eventable Type
86+
- Author: Simon J. Stuart
87+
- Version: 4.3.0
88+
*/
89+
static func getEventTypeName() -> String
7890
}
7991

8092
/**
@@ -110,12 +122,26 @@ extension Eventable {
110122
EventCentral.scheduleStack(self, at: at, priority: priority)
111123
}
112124

113-
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling {
114-
return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn)
125+
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread, interestedIn: EventListenerInterest = .all) -> EventListenerHandling {
126+
return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn, interestedIn: interestedIn)
115127
}
116128

117129
public static func removeListener(_ token: UUID) {
118130
EventCentral.removeListener(token, typeOf: Self.self)
119131
}
120132
}
121133

134+
/**
135+
Extension to provide central access to Event Type Names
136+
- Author: Simon J. Stuart
137+
- Version: 4.3.0
138+
*/
139+
extension Eventable {
140+
@inline(__always) public func getEventTypeName() -> String {
141+
return String(reflecting: type(of: self))
142+
}
143+
144+
@inline(__always) public static func getEventTypeName() -> String {
145+
return String(reflecting: self)
146+
}
147+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// EventListeners.swift
3+
// Copyright (c) 2022, Flowduino
4+
// Authored by Simon J. Stuart on 21st August 2022
5+
//
6+
// Subject to terms, restrictions, and liability waiver of the MIT License
7+
//
8+
9+
import Foundation
10+
/*
11+
public protocol EventListenerCallbackContainer {
12+
associatedtype TEventType: Eventable
13+
14+
static func buildBlock(_ callback: @escaping ((_ event: TEventType, _ priority: EventPriority) -> ()), _ owner: AnyObject, _ executeOn: ExecuteEventOn) -> EventListenerHandling
15+
}
16+
17+
@resultBuilder
18+
public struct EventListenerCallback<TEventType: Eventable>: EventListenerCallbackContainer {
19+
public static func buildBlock(_ callback: @escaping ((_ event: TEventType, _ priority: EventPriority) -> ()), _ owner: AnyObject, _ executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling {
20+
return TEventType.addListener(owner, callback, executeOn: executeOn)
21+
}
22+
23+
init() {
24+
print("Init called")
25+
}
26+
}
27+
28+
@resultBuilder
29+
public struct EventListeners<TOwner: AnyObject> {
30+
public static func buildBlock(_ owner: TOwner, _ executeOn: ExecuteEventOn = .requesterThread, _ eventType: Eventable.Type, _ events: EventCallback...) -> [EventListenerHandling] {
31+
var results = [EventListenerHandling]()
32+
for event in events {
33+
// results.append(EventCentral.addListener(owner, event, forEventType: eventType))
34+
}
35+
return results
36+
}
37+
}
38+
*/

Sources/EventDrivenSwift/Event/Wrappers/EventMethod.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import Foundation
1010

11-
public typealias EventMethodTypedEventCallback<TOwner: AnyObject, TEvent: Any> = (_ sender: TOwner, _ event: TEvent, _ priority: EventPriority) -> ()
11+
public typealias EventMethodTypedEventCallback<TOwner: AnyObject, TEvent: Any> = (_ sender: TOwner, _ event: TEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) -> ()
1212

1313
/**
1414
Any Property wrapped with `EventMethod` will automatically conform to `EventMethodContainer`
@@ -28,7 +28,7 @@ public protocol EventMethodContainer {
2828
/**
2929
Decorate Typed Event Callback Closures as `var` with `@EventMethod<TEventType>` to automatically register them.
3030
- Author: Simon J. Stuart
31-
- Version: 4.1.0
31+
- Version: 5.0.0
3232
*/
3333
@propertyWrapper
3434
public struct EventMethod<TOwner: AnyObject, TEventType: Eventable>: EventMethodContainer {
@@ -37,9 +37,9 @@ public struct EventMethod<TOwner: AnyObject, TEventType: Eventable>: EventMethod
3737

3838
private weak var owner: AnyObject? = nil
3939

40-
private func callback(event: TEventType, priority: EventPriority) {
40+
private func callback(event: TEventType, priority: EventPriority, dispatchTime: DispatchTime) {
4141
if let typedOwner = owner as? TOwner {
42-
wrappedValue?(typedOwner, event, priority)
42+
wrappedValue?(typedOwner, event, priority, dispatchTime)
4343
}
4444
}
4545

Sources/EventDrivenSwift/EventDispatcher/EventDispatcher.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ open class EventDispatcher: EventHandler, EventDispatching {
2929
@ThreadSafeSemaphore private var receivers = [String:[ReceiverContainer]]()
3030

3131
public func addReceiver(_ receiver: any EventReceiving, forEventType: Eventable.Type) {
32-
let eventTypeName = String(reflecting: forEventType)
32+
let eventTypeName = forEventType.getEventTypeName()
3333

3434
_receivers.withLock { receivers in
3535
var bucket = receivers[eventTypeName]
@@ -50,7 +50,7 @@ open class EventDispatcher: EventHandler, EventDispatching {
5050
}
5151

5252
public func removeReceiver(_ receiver: any EventReceiving, forEventType: Eventable.Type) {
53-
let eventTypeName = String(reflecting: forEventType)
53+
let eventTypeName = forEventType.getEventTypeName()
5454

5555
_receivers.withLock { receivers in
5656
var bucket = receivers[eventTypeName]
@@ -82,8 +82,8 @@ open class EventDispatcher: EventHandler, EventDispatching {
8282
- Author: Simon J. Stuart
8383
- Version: 1.0.0
8484
*/
85-
override open func processEvent(_ event: any Eventable, dispatchMethod: EventDispatchMethod, priority: EventPriority) {
86-
let eventTypeName = String(reflecting: type(of: event))
85+
override open func processEvent(_ event: EventDispatchContainer, dispatchMethod: EventDispatchMethod, priority: EventPriority) {
86+
let eventTypeName = event.event.getEventTypeName()
8787

8888
var snapReceivers = [String:[ReceiverContainer]]()
8989

@@ -102,6 +102,7 @@ open class EventDispatcher: EventHandler, EventDispatching {
102102
if receiver.receiver == nil { /// If the Recevier is `nil`...
103103
continue
104104
}
105+
if receiver.receiver!.interestedIn == .latestOnly && event.dispatchTime < latestEventDispatchTime[event.event.getEventTypeName()]! { continue } // If this Receiver is only interested in the Latest Event dispatched for this Event Type, and this Event is NOT the Latest... skip it!
105106

106107
// so, we have a receiver... let's deal with it!
107108
switch dispatchMethod {

0 commit comments

Comments
 (0)