Skip to content

Commit 6cd336d

Browse files
authored
Merge pull request #4 from Flowduino/scheduled_dispatch
Scheduled dispatch
2 parents ee5ab68 + f970665 commit 6cd336d

File tree

7 files changed

+158
-7
lines changed

7 files changed

+158
-7
lines changed

README.md

Lines changed: 15 additions & 5 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.0.0")
133+
.upToNextMajor(from: "4.2.0")
134134
),
135135
],
136136
//...
@@ -191,6 +191,19 @@ temperatureEvent.stack(priority: .highest)
191191
```
192192
Above would be with `.highest` *Priority*.
193193

194+
### Scheduled *Dispatching* of an *Event*
195+
Version 4.2.0 introduced *Scheduled Dispatch* into the library:
196+
```swift
197+
temperatureEvent.scheduleQueue(at: DispatchTime.now() + TimeInterval().advanced(by: 4), priority: .highest)
198+
```
199+
The above would *Dispatch* the `temperatureEvent` after 4 seconds, via the *Queue*, with the *highest Priority*
200+
```swift
201+
temperatureEvent.scheduleStack(at: DispatchTime.now() + TimeInterval().advanced(by: 4), priority: .highest)
202+
```
203+
The above would *Dispatch* the `temperatureEvent` after 4 seconds, via the *Stack*, with the *highest Priority*
204+
205+
*Scheduled Event Dispatch* is a massive advantage when your use-case requires a fixed or calculated time delay between the composition of an *Event*, and its *Dispatch* for processing.
206+
194207
### Defining an `EventThread`
195208
So, we have an *Event* type, and we are able to *Dispatch* it through a *Queue* or a *Stack*, with whatever *Priority* we desire. Now we need to define an `EventThread` to listen for and process our `TemperatureEvent`s.
196209

@@ -443,13 +456,10 @@ The above example would use the `EventPoolLowestLoadBalancer` implementation, wh
443456
## Features Coming Soon
444457
`EventDrivenSwift` is an evolving and ever-improving Library, so here are lists of the features you can expect in future releases.
445458

446-
Version 4.2.0 (or 5.0.0 if interface-breaking changes are required):
459+
Version 4.3.0 (or 5.0.0 if interface-breaking changes are required):
447460
- **Event Pool Scalers** - Dynamic Scaling for `EventPool` instances will be fully-implemented
448461
- **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)
449462

450-
Version 5.1.0 (or 6.0.0 if interface-breaking changes are required):
451-
- **Event Scheduling** - A Dispatch Scheduler to ensure that *Events* aren't Dispatched until a specific time (or after a specific interval)
452-
453463
## License
454464

455465
`EventDrivenSwift` is available under the MIT license. See the [LICENSE file](./LICENSE) for more info.

Sources/EventDrivenSwift/Central/EventCentral.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Singleton for the Central Event Dispatcher.
1616
- Note: This is used when invoking the `queue` and `stack` methods of `Eventable`.
1717
*/
1818
final public class EventCentral: EventDispatcher, EventCentralable {
19+
1920
/**
2021
Singleton Instance of our Central Event Dispatcher
2122
- Author: Simon J. Stuart
@@ -86,6 +87,14 @@ final public class EventCentral: EventDispatcher, EventCentralable {
8687
_shared.eventListener.removeListener(token, typeOf: typeOf)
8788
}
8889

90+
@inline(__always) static public func scheduleQueue(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
91+
_shared.scheduleQueue(event, at: at, priority: priority)
92+
}
93+
94+
@inline(__always) static public func scheduleStack(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
95+
_shared.scheduleStack(event, at: at, priority: priority)
96+
}
97+
8998
/// This just makes it so that your code cannot initialise instances of `EventCentral`. It's a Singleton!
9099
override private init() {}
91100
}

Sources/EventDrivenSwift/Central/EventCentralable.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,24 @@ public protocol EventCentralable {
8989
- typeOf: The Event Type for which the Listener identified by the given `token` is interested
9090
*/
9191
static func removeListener(_ token: UUID, typeOf: Eventable.Type)
92+
93+
/**
94+
Schedule the Event to be dispatched through the Central Queue with the given `priority`
95+
- Author: Simon J. Stuart
96+
- Version: 4.2.0
97+
- Parameters:
98+
- at: The `DispatchTime` after which to dispatch the Event
99+
- priority: The `EventPriority` with which to process the Event
100+
*/
101+
static func scheduleQueue(_ event: Eventable, at: DispatchTime, priority: EventPriority)
102+
103+
/**
104+
Schedule the Event to be dispatched through the Central Stack with the given `priority`
105+
- Author: Simon J. Stuart
106+
- Version: 4.2.0
107+
- Parameters:
108+
- at: The `DispatchTime` after which to dispatch the Event
109+
- priority: The `EventPriority` with which to process the Event
110+
*/
111+
static func scheduleStack(_ event: Eventable, at: DispatchTime, priority: EventPriority)
92112
}

Sources/EventDrivenSwift/Event/Eventable.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ public protocol Eventable {
3434
*/
3535
func stack(priority: EventPriority)
3636

37+
/**
38+
Schedule the Event to be dispatched through the Central Queue with the given `priority`
39+
- Author: Simon J. Stuart
40+
- Version: 4.2.0
41+
- Parameters:
42+
- at: The `DispatchTime` after which to dispatch the Event
43+
- priority: The `EventPriority` with which to process the Event
44+
*/
45+
func scheduleQueue(at: DispatchTime, priority: EventPriority)
46+
47+
/**
48+
Schedule the Event to be dispatched through the Central Stack with the given `priority`
49+
- Author: Simon J. Stuart
50+
- Version: 4.2.0
51+
- Parameters:
52+
- at: The `DispatchTime` after which to dispatch the Event
53+
- priority: The `EventPriority` with which to process the Event
54+
*/
55+
func scheduleStack(at: DispatchTime, priority: EventPriority)
56+
3757
/**
3858
Registers an Event Listner Callback for the given `Eventable` Type with the Central Event Listener
3959
- Author: Simon J. Stuart
@@ -74,14 +94,22 @@ extension Eventable {
7494
- Version: 1.0.0
7595
*/
7696
extension Eventable {
77-
public func queue(priority: EventPriority = .normal) {
97+
@inline(__always) public func queue(priority: EventPriority = .normal) {
7898
EventCentral.queueEvent(self, priority: priority)
7999
}
80100

81-
public func stack(priority: EventPriority = .normal) {
101+
@inline(__always) public func stack(priority: EventPriority = .normal) {
82102
EventCentral.stackEvent(self, priority: priority)
83103
}
84104

105+
@inline(__always) public func scheduleQueue(at: DispatchTime, priority: EventPriority = .normal) {
106+
EventCentral.scheduleQueue(self, at: at, priority: priority)
107+
}
108+
109+
@inline(__always) public func scheduleStack(at: DispatchTime, priority: EventPriority = .normal) {
110+
EventCentral.scheduleStack(self, at: at, priority: priority)
111+
}
112+
85113
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling {
86114
return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn)
87115
}

Sources/EventDrivenSwift/EventHandler/EventHandler.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,23 @@ open class EventHandler: ObservableThread, EventHandling {
9393
eventsPending.signal()
9494
}
9595

96+
public func scheduleQueue(_ event: any Eventable, at: DispatchTime, priority: EventPriority) {
97+
Task {
98+
DispatchQueue.main.asyncAfter(deadline: at) {
99+
self.queueEvent(event, priority: priority)
100+
}
101+
}
102+
}
103+
104+
public func scheduleStack(_ event: any Eventable, at: DispatchTime, priority: EventPriority) {
105+
Task {
106+
DispatchQueue.main.asyncAfter(deadline: at) {
107+
self.stackEvent(event, priority: priority)
108+
}
109+
}
110+
}
111+
112+
96113
/**
97114
Processes an Event
98115
- Author: Simon J. Stuart

Sources/EventDrivenSwift/EventHandler/EventHandling.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ public protocol EventHandling {
3434
*/
3535
func stackEvent(_ event: any Eventable, priority: EventPriority)
3636

37+
/**
38+
Schedule the Event to be dispatched with the given `priority`
39+
- Author: Simon J. Stuart
40+
- Version: 4.2.0
41+
- Parameters:
42+
- at: The `DispatchTime` after which to dispatch the Event
43+
- priority: The `EventPriority` with which to process the Event
44+
*/
45+
func scheduleQueue(_ event: any Eventable, at: DispatchTime, priority: EventPriority)
46+
47+
/**
48+
Schedule the Event to be dispatched with the given `priority`
49+
- Author: Simon J. Stuart
50+
- Version: 4.2.0
51+
- Parameters:
52+
- at: The `DispatchTime` after which to dispatch the Event
53+
- priority: The `EventPriority` with which to process the Event
54+
*/
55+
func scheduleStack(_ event: any Eventable, at: DispatchTime, priority: EventPriority)
56+
3757
/**
3858
The number of Events currently pending in the Queue and Stack combined
3959
- Author: Simon J. Stuart
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// BasicEventSchedulingTests.swift
3+
//
4+
//
5+
// Created by Simon Stuart on 28/08/2022.
6+
//
7+
8+
import XCTest
9+
import ThreadSafeSwift
10+
@testable import EventDrivenSwift
11+
12+
final class BasicEventSchedulingTests: XCTestCase {
13+
private struct TestEvent: Eventable {
14+
public var foo: String
15+
}
16+
17+
@ThreadSafeSemaphore private var testValue: String = "Hello"
18+
private var exp: XCTestExpectation? = nil
19+
private var executed: DispatchTime? = nil
20+
21+
func testPerformanceExample() throws {
22+
TestEvent.addListener(self, { (event: TestEvent, priority) in
23+
print("TestEvent where foo = \(event.foo)")
24+
self.testValue = event.foo
25+
self.executed = DispatchTime.now()
26+
self.exp?.fulfill()
27+
}, executeOn: .taskThread)
28+
29+
exp = expectation(description: "Event Executed")
30+
31+
XCTAssertEqual(testValue, "Hello")
32+
let scheduledFor = DispatchTime.now() + TimeInterval().advanced(by: 4) // Schedule for T+5 seconds
33+
TestEvent(foo: "World").scheduleQueue(at: scheduledFor)
34+
35+
let result = XCTWaiter.wait(for: [exp!], timeout: 5.0)
36+
37+
XCTAssertNotEqual(result, .timedOut)
38+
39+
XCTAssertEqual(testValue, "World")
40+
XCTAssertNotNil(executed)
41+
if executed != nil {
42+
XCTAssertLessThan(scheduledFor, executed!)
43+
XCTAssertLessThan(executed!, scheduledFor + TimeInterval().advanced(by: 4.001))
44+
}
45+
}
46+
47+
}

0 commit comments

Comments
 (0)