Skip to content

Commit

Permalink
Merge pull request #4 from Flowduino/scheduled_dispatch
Browse files Browse the repository at this point in the history
Scheduled dispatch
  • Loading branch information
LK-Simon authored Aug 28, 2022
2 parents ee5ab68 + f970665 commit 6cd336d
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 7 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ let package = Package(
dependencies: [
.package(
url: "https://github.com/Flowduino/EventDrivenSwift.git",
.upToNextMajor(from: "4.0.0")
.upToNextMajor(from: "4.2.0")
),
],
//...
Expand Down Expand Up @@ -191,6 +191,19 @@ temperatureEvent.stack(priority: .highest)
```
Above would be with `.highest` *Priority*.

### Scheduled *Dispatching* of an *Event*
Version 4.2.0 introduced *Scheduled Dispatch* into the library:
```swift
temperatureEvent.scheduleQueue(at: DispatchTime.now() + TimeInterval().advanced(by: 4), priority: .highest)
```
The above would *Dispatch* the `temperatureEvent` after 4 seconds, via the *Queue*, with the *highest Priority*
```swift
temperatureEvent.scheduleStack(at: DispatchTime.now() + TimeInterval().advanced(by: 4), priority: .highest)
```
The above would *Dispatch* the `temperatureEvent` after 4 seconds, via the *Stack*, with the *highest Priority*

*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.

### Defining an `EventThread`
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.

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

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

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

## License

`EventDrivenSwift` is available under the MIT license. See the [LICENSE file](./LICENSE) for more info.
Expand Down
9 changes: 9 additions & 0 deletions Sources/EventDrivenSwift/Central/EventCentral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Singleton for the Central Event Dispatcher.
- Note: This is used when invoking the `queue` and `stack` methods of `Eventable`.
*/
final public class EventCentral: EventDispatcher, EventCentralable {

/**
Singleton Instance of our Central Event Dispatcher
- Author: Simon J. Stuart
Expand Down Expand Up @@ -86,6 +87,14 @@ final public class EventCentral: EventDispatcher, EventCentralable {
_shared.eventListener.removeListener(token, typeOf: typeOf)
}

@inline(__always) static public func scheduleQueue(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
_shared.scheduleQueue(event, at: at, priority: priority)
}

@inline(__always) static public func scheduleStack(_ event: Eventable, at: DispatchTime, priority: EventPriority) {
_shared.scheduleStack(event, at: at, priority: priority)
}

/// This just makes it so that your code cannot initialise instances of `EventCentral`. It's a Singleton!
override private init() {}
}
20 changes: 20 additions & 0 deletions Sources/EventDrivenSwift/Central/EventCentralable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,24 @@ public protocol EventCentralable {
- typeOf: The Event Type for which the Listener identified by the given `token` is interested
*/
static func removeListener(_ token: UUID, typeOf: Eventable.Type)

/**
Schedule the Event to be dispatched through the Central Queue with the given `priority`
- Author: Simon J. Stuart
- Version: 4.2.0
- Parameters:
- at: The `DispatchTime` after which to dispatch the Event
- priority: The `EventPriority` with which to process the Event
*/
static func scheduleQueue(_ event: Eventable, at: DispatchTime, priority: EventPriority)

/**
Schedule the Event to be dispatched through the Central Stack with the given `priority`
- Author: Simon J. Stuart
- Version: 4.2.0
- Parameters:
- at: The `DispatchTime` after which to dispatch the Event
- priority: The `EventPriority` with which to process the Event
*/
static func scheduleStack(_ event: Eventable, at: DispatchTime, priority: EventPriority)
}
32 changes: 30 additions & 2 deletions Sources/EventDrivenSwift/Event/Eventable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ public protocol Eventable {
*/
func stack(priority: EventPriority)

/**
Schedule the Event to be dispatched through the Central Queue with the given `priority`
- Author: Simon J. Stuart
- Version: 4.2.0
- Parameters:
- at: The `DispatchTime` after which to dispatch the Event
- priority: The `EventPriority` with which to process the Event
*/
func scheduleQueue(at: DispatchTime, priority: EventPriority)

/**
Schedule the Event to be dispatched through the Central Stack with the given `priority`
- Author: Simon J. Stuart
- Version: 4.2.0
- Parameters:
- at: The `DispatchTime` after which to dispatch the Event
- priority: The `EventPriority` with which to process the Event
*/
func scheduleStack(at: DispatchTime, priority: EventPriority)

/**
Registers an Event Listner Callback for the given `Eventable` Type with the Central Event Listener
- Author: Simon J. Stuart
Expand Down Expand Up @@ -74,14 +94,22 @@ extension Eventable {
- Version: 1.0.0
*/
extension Eventable {
public func queue(priority: EventPriority = .normal) {
@inline(__always) public func queue(priority: EventPriority = .normal) {
EventCentral.queueEvent(self, priority: priority)
}

public func stack(priority: EventPriority = .normal) {
@inline(__always) public func stack(priority: EventPriority = .normal) {
EventCentral.stackEvent(self, priority: priority)
}

@inline(__always) public func scheduleQueue(at: DispatchTime, priority: EventPriority = .normal) {
EventCentral.scheduleQueue(self, at: at, priority: priority)
}

@inline(__always) public func scheduleStack(at: DispatchTime, priority: EventPriority = .normal) {
EventCentral.scheduleStack(self, at: at, priority: priority)
}

@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling {
return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn)
}
Expand Down
17 changes: 17 additions & 0 deletions Sources/EventDrivenSwift/EventHandler/EventHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ open class EventHandler: ObservableThread, EventHandling {
eventsPending.signal()
}

public func scheduleQueue(_ event: any Eventable, at: DispatchTime, priority: EventPriority) {
Task {
DispatchQueue.main.asyncAfter(deadline: at) {
self.queueEvent(event, priority: priority)
}
}
}

public func scheduleStack(_ event: any Eventable, at: DispatchTime, priority: EventPriority) {
Task {
DispatchQueue.main.asyncAfter(deadline: at) {
self.stackEvent(event, priority: priority)
}
}
}


/**
Processes an Event
- Author: Simon J. Stuart
Expand Down
20 changes: 20 additions & 0 deletions Sources/EventDrivenSwift/EventHandler/EventHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ public protocol EventHandling {
*/
func stackEvent(_ event: any Eventable, priority: EventPriority)

/**
Schedule the Event to be dispatched with the given `priority`
- Author: Simon J. Stuart
- Version: 4.2.0
- Parameters:
- at: The `DispatchTime` after which to dispatch the Event
- priority: The `EventPriority` with which to process the Event
*/
func scheduleQueue(_ event: any Eventable, at: DispatchTime, priority: EventPriority)

/**
Schedule the Event to be dispatched with the given `priority`
- Author: Simon J. Stuart
- Version: 4.2.0
- Parameters:
- at: The `DispatchTime` after which to dispatch the Event
- priority: The `EventPriority` with which to process the Event
*/
func scheduleStack(_ event: any Eventable, at: DispatchTime, priority: EventPriority)

/**
The number of Events currently pending in the Queue and Stack combined
- Author: Simon J. Stuart
Expand Down
47 changes: 47 additions & 0 deletions Tests/EventDrivenSwiftTests/BasicEventSchedulingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// BasicEventSchedulingTests.swift
//
//
// Created by Simon Stuart on 28/08/2022.
//

import XCTest
import ThreadSafeSwift
@testable import EventDrivenSwift

final class BasicEventSchedulingTests: XCTestCase {
private struct TestEvent: Eventable {
public var foo: String
}

@ThreadSafeSemaphore private var testValue: String = "Hello"
private var exp: XCTestExpectation? = nil
private var executed: DispatchTime? = nil

func testPerformanceExample() throws {
TestEvent.addListener(self, { (event: TestEvent, priority) in
print("TestEvent where foo = \(event.foo)")
self.testValue = event.foo
self.executed = DispatchTime.now()
self.exp?.fulfill()
}, executeOn: .taskThread)

exp = expectation(description: "Event Executed")

XCTAssertEqual(testValue, "Hello")
let scheduledFor = DispatchTime.now() + TimeInterval().advanced(by: 4) // Schedule for T+5 seconds
TestEvent(foo: "World").scheduleQueue(at: scheduledFor)

let result = XCTWaiter.wait(for: [exp!], timeout: 5.0)

XCTAssertNotEqual(result, .timedOut)

XCTAssertEqual(testValue, "World")
XCTAssertNotNil(executed)
if executed != nil {
XCTAssertLessThan(scheduledFor, executed!)
XCTAssertLessThan(executed!, scheduledFor + TimeInterval().advanced(by: 4.001))
}
}

}

0 comments on commit 6cd336d

Please sign in to comment.