Skip to content

Commit 9c0924c

Browse files
authored
Merge pull request #6 from Flowduino/event_younger_than
5.1.0 - Event "Younger Than" Age added
2 parents 90fe210 + 29b97b6 commit 9c0924c

File tree

13 files changed

+73
-12
lines changed

13 files changed

+73
-12
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.7
1+
// swift-tools-version: 5.6
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,32 @@ class TemperatureRatingViewModel: ObservableObject {
451451
```
452452
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.
453453

454+
## `EventListener` with *Maximum Age* Interest
455+
Version 5.1.0 of this library introduces the concent of *Maximum Age Listeners*. A *Maximum Age Listener* is a *Listener* that will only be invoked for *Events* of its registered *Event Type* that are younger than a defined *Maximum Age*. Any *Event* older than the defined *Maximum Age* will be skipped over, while any *Event* younger will invoke your *Listener*.
456+
457+
We have made it simple for you to configure your *Listener* to define a *Maximum Age* interest. Taking the previous code example, we can simply modify it as follows:
458+
```swift
459+
class TemperatureRatingViewModel: ObservableObject {
460+
@Published var temperatureInCelsius: Float
461+
@Published var temperatureRating: TemperatureRating
462+
463+
var listenerHandle: EventListenerHandling?
464+
465+
internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {
466+
temperatureInCelsius = event.temperatureInCelsius
467+
temperatureRating = event.temperatureRating
468+
}
469+
470+
init() {
471+
// Let's register our Event Listener Callback!
472+
listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, interestedIn: .youngerThan, maximumAge: 1 * 1_000_000_000)
473+
}
474+
}
475+
```
476+
In the above code example, `maximumAge` is a value defined in *nanoseconds*. With that in mind, `1 * 1_000_000_000` would be 1 second. This means that, any `TemperatureRatingEvent` older than 1 second would be ignored by the *Listener*, while any `TemperatureRatingEvent` *younger* than 1 second would invoke the `onTemperatureRatingEvent` method.
477+
478+
This functionality is very useful when the context of an *Event*'s usage would have a known, fixed expiry.
479+
454480
## `EventPool`
455481
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.
456482

Sources/EventDrivenSwift/Central/EventCentral.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -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, interestedIn: EventListenerInterest = .all) -> EventListenerHandling where TEvent : Eventable {
79-
return _shared.eventListener.addListener(requester, callback, forEventType: forEventType, executeOn: executeOn, interestedIn: interestedIn)
78+
@discardableResult @inline(__always) public static func addListener<TEvent>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread, interestedIn: EventListenerInterest = .all, maximumAge: UInt64 = 0) -> EventListenerHandling where TEvent : Eventable {
79+
return _shared.eventListener.addListener(requester, callback, forEventType: forEventType, executeOn: executeOn, interestedIn: interestedIn, maximumAge: maximumAge)
8080
}
8181

8282
@inline(__always) public static func removeListener(_ token: UUID) {

Sources/EventDrivenSwift/Central/EventCentralable.swift

+1-1
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, interestedIn: EventListenerInterest) -> EventListenerHandling
71+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn, interestedIn: EventListenerInterest, maximumAge: UInt64) -> EventListenerHandling
7272

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

Sources/EventDrivenSwift/Event/Eventable.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +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, interestedIn: EventListenerInterest) -> EventListenerHandling
66+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn, interestedIn: EventListenerInterest, maximumAge: UInt64) -> EventListenerHandling
6767

6868
/**
6969
Locates and removes the given Listener `token` (if it exists) from the Central Event Listener
@@ -122,8 +122,8 @@ extension Eventable {
122122
EventCentral.scheduleStack(self, at: at, priority: priority)
123123
}
124124

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)
125+
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread, interestedIn: EventListenerInterest = .all, maximumAge: UInt64 = 0) -> EventListenerHandling {
126+
return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn, interestedIn: interestedIn, maximumAge: maximumAge)
127127
}
128128

129129
public static func removeListener(_ token: UUID) {

Sources/EventDrivenSwift/EventDispatcher/EventDispatcher.swift

+3
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,11 @@ open class EventDispatcher: EventHandler, EventDispatching {
102102
if receiver.receiver == nil { /// If the Recevier is `nil`...
103103
continue
104104
}
105+
105106
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!
106107

108+
if receiver.receiver!.interestedIn == .youngerThan && receiver.receiver!.maximumEventAge != 0 && (DispatchTime.now().uptimeNanoseconds - event.dispatchTime.uptimeNanoseconds) > receiver.receiver!.maximumEventAge { continue } // If this Receiver has a maximum age of interest, and this Event is older than that... skip it!
109+
107110
// so, we have a receiver... let's deal with it!
108111
switch dispatchMethod {
109112
case .stack:

Sources/EventDrivenSwift/EventListener/EventListenable.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,17 @@ public protocol EventListenable: AnyObject, EventReceiving {
5353
/**
5454
Registers an Event Callback for the given `Eventable` Type
5555
- Author: Simon J. Stuart
56-
- Version: 3.0.0
56+
- Version: 5.1.0
5757
- Parameters:
5858
- requester: The Object owning the Callback Method
5959
- callback: The code to invoke for the given `Eventable` Type
6060
- forEventType: The `Eventable` Type for which to Register the Callback
6161
- executeOn: Tells the `EventListenable` whether to execute the Callback on the `requester`'s Thread, or the Listener's.
62+
- interestedIn: Defines the conditions under which the Listener is interested in an Event (anything outside of the given condition will be ignored by this Listener)
63+
- maximumAge: If `interestedIn` == `.youngerThan`, this is the number of nanoseconds between the time of dispatch and the moment of processing where the Listener will be interested in the Event. Any Event older will be ignored
6264
- Returns: A `UUID` value representing the `token` associated with this Event Callback
6365
*/
64-
@discardableResult func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn, interestedIn: EventListenerInterest) -> EventListenerHandling
66+
@discardableResult func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn, interestedIn: EventListenerInterest, maximumAge: UInt64) -> EventListenerHandling
6567

6668
/**
6769
Locates and removes the given Listener `token` (if it exists)

Sources/EventDrivenSwift/EventListener/EventListener.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import Observable
1717
- Note: Inherit from this to implement a discrete unit of code designed specifically to operate upon specific `Eventable` types containing information useful to its operation(s)
1818
*/
1919
open class EventListener: EventHandler, EventListenable {
20+
public var maximumEventAge: UInt64 = 0
21+
2022
public var interestedIn: EventListenerInterest = .all
2123

2224
/**
@@ -31,6 +33,7 @@ open class EventListener: EventHandler, EventListenable {
3133
var dispatchQueue: DispatchQueue?
3234
var executeOn: ExecuteEventOn = .requesterThread
3335
var interestedIn: EventListenerInterest = .all
36+
var maximumEventAge: UInt64 = 0
3437
}
3538

3639
/**
@@ -68,6 +71,8 @@ open class EventListener: EventHandler, EventListenable {
6871

6972
if listener.interestedIn == .latestOnly && event.dispatchTime < latestEventDispatchTime[event.event.getEventTypeName()]! { continue } // If this Listener is only interested in the Latest Event dispatched for this Event Type, and this Event is NOT the Latest... skip it!
7073

74+
if listener.interestedIn == .youngerThan && listener.maximumEventAge != 0 && (DispatchTime.now().uptimeNanoseconds - event.dispatchTime.uptimeNanoseconds) > listener.maximumEventAge { continue } // If this Receiver has a maximum age of interest, and this Event is older than that... skip it!
75+
7176
switch listener.executeOn {
7277
case .requesterThread:
7378
Task { // We raise a Task because we don't want the entire Listener blocked in the event the dispatchQueue is busy or blocked!
@@ -86,12 +91,12 @@ open class EventListener: EventHandler, EventListenable {
8691
}
8792
}
8893

89-
@discardableResult public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread, interestedIn: EventListenerInterest = .all) -> EventListenerHandling {
94+
@discardableResult public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread, interestedIn: EventListenerInterest = .all, maximumAge: UInt64 = 0) -> EventListenerHandling {
9095
let eventTypeName = forEventType.getEventTypeName()
9196
let method: EventCallback = { event, priority, dispatchTime in
9297
self.callTypedEventCallback(callback, forEvent: event, priority: priority, dispatchTime: dispatchTime)
9398
}
94-
let eventListenerContainer = EventListenerContainer(requester: requester, callback: method, dispatchQueue: OperationQueue.current?.underlyingQueue, executeOn: executeOn, interestedIn: interestedIn)
99+
let eventListenerContainer = EventListenerContainer(requester: requester, callback: method, dispatchQueue: OperationQueue.current?.underlyingQueue, executeOn: executeOn, interestedIn: interestedIn, maximumEventAge: maximumAge)
95100
_eventListeners.withLock { eventCallbacks in
96101
var bucket = eventCallbacks[eventTypeName]
97102
if bucket == nil { bucket = [EventListenerContainer]() } // Create a new bucket if there isn't already one!

Sources/EventDrivenSwift/EventListener/EventListenerInterest.swift

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@
99
import Foundation
1010

1111
public enum EventListenerInterest: CaseIterable {
12+
/**
13+
Receivers will receive all Events, regardless of age or whether they are the newest.
14+
*/
1215
case all
16+
17+
/**
18+
Receivers will ignore any Events older than the last one dispatched of the given `Eventable` type.
19+
*/
1320
case latestOnly
21+
22+
/**
23+
Receivers will ignore any Event that is older than a defined Delta (Maximum Age).
24+
- Author: Simon J. Stuart
25+
- Version: 5.0.0
26+
*/
27+
case youngerThan
1428
}

Sources/EventDrivenSwift/EventPool/EventPool.swift

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import ThreadSafeSwift
1818
- Note: Event Pools own and manage all instances of the given `TEventThread` type
1919
*/
2020
open class EventPool<TEventThread: EventThreadable>: EventHandler, EventPooling {
21+
public var maximumEventAge: UInt64 = 0
22+
2123
public var interestedIn: EventListenerInterest = .all
2224

2325
@ThreadSafeSemaphore public var balancer: EventPoolBalancing

Sources/EventDrivenSwift/EventReceiver/EventReceiver.swift

+2
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ import Observable
2020
- Note: `EventThread` inherits from this
2121
*/
2222
open class EventReceiver: EventHandler, EventReceiving {
23+
public var maximumEventAge: UInt64 = 0
24+
2325
public var interestedIn: EventListenerInterest = .all
2426
}

Sources/EventDrivenSwift/EventReceiver/EventReceiving.swift

+7
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,11 @@ public protocol EventReceiving: AnyObject, EventHandling {
2020
- Version: 4.3.0
2121
*/
2222
var interestedIn: EventListenerInterest { get set }
23+
24+
/**
25+
Declares the maximum age of an `Eventable` before it will be ignored if `interestedIn` == `.youngerThan`
26+
- Author: Simon J. Stuart
27+
- Version: 5.1.0
28+
*/
29+
var maximumEventAge: UInt64 { get set }
2330
}

Tests/EventDrivenSwiftTests/BasicEventListenerTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import ThreadSafeSwift
1010
@testable import EventDrivenSwift
1111

1212
final class BasicEventListenerTests: XCTestCase, EventListening {
13-
struct TestEventTypeOne: Eventable {
13+
struct TestEventTypeOne: Eventable {
1414
var foo: Int
1515
}
1616

0 commit comments

Comments
 (0)