From 383dac0f9d283b8c0bc1fb577a388a0cf0fde0b5 Mon Sep 17 00:00:00 2001 From: LK-Simon Date: Sun, 28 Aug 2022 19:54:41 +0200 Subject: [PATCH 1/2] Event Scheduling added - Event Scheduling has been added throughout the system. - Unit Test coverage for Event Scheduling has also been added. --- .../Central/EventCentral.swift | 9 ++++ .../Central/EventCentralable.swift | 20 ++++++++ .../EventDrivenSwift/Event/Eventable.swift | 32 ++++++++++++- .../EventHandler/EventHandler.swift | 17 +++++++ .../EventHandler/EventHandling.swift | 20 ++++++++ .../BasicEventSchedulingTests.swift | 47 +++++++++++++++++++ 6 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 Tests/EventDrivenSwiftTests/BasicEventSchedulingTests.swift diff --git a/Sources/EventDrivenSwift/Central/EventCentral.swift b/Sources/EventDrivenSwift/Central/EventCentral.swift index 76cda07..71eeb8d 100644 --- a/Sources/EventDrivenSwift/Central/EventCentral.swift +++ b/Sources/EventDrivenSwift/Central/EventCentral.swift @@ -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 @@ -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() {} } diff --git a/Sources/EventDrivenSwift/Central/EventCentralable.swift b/Sources/EventDrivenSwift/Central/EventCentralable.swift index cecf13d..02538b4 100644 --- a/Sources/EventDrivenSwift/Central/EventCentralable.swift +++ b/Sources/EventDrivenSwift/Central/EventCentralable.swift @@ -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) } diff --git a/Sources/EventDrivenSwift/Event/Eventable.swift b/Sources/EventDrivenSwift/Event/Eventable.swift index 90e1a8a..a51ed03 100644 --- a/Sources/EventDrivenSwift/Event/Eventable.swift +++ b/Sources/EventDrivenSwift/Event/Eventable.swift @@ -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 @@ -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(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling { return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn) } diff --git a/Sources/EventDrivenSwift/EventHandler/EventHandler.swift b/Sources/EventDrivenSwift/EventHandler/EventHandler.swift index f0e8394..c1f8d3a 100644 --- a/Sources/EventDrivenSwift/EventHandler/EventHandler.swift +++ b/Sources/EventDrivenSwift/EventHandler/EventHandler.swift @@ -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 diff --git a/Sources/EventDrivenSwift/EventHandler/EventHandling.swift b/Sources/EventDrivenSwift/EventHandler/EventHandling.swift index 5961b7a..363a5e4 100644 --- a/Sources/EventDrivenSwift/EventHandler/EventHandling.swift +++ b/Sources/EventDrivenSwift/EventHandler/EventHandling.swift @@ -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 diff --git a/Tests/EventDrivenSwiftTests/BasicEventSchedulingTests.swift b/Tests/EventDrivenSwiftTests/BasicEventSchedulingTests.swift new file mode 100644 index 0000000..af97c6d --- /dev/null +++ b/Tests/EventDrivenSwiftTests/BasicEventSchedulingTests.swift @@ -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)) + } + } + +} From f970665e971ae53637d4cdf800514f0a21ddc841 Mon Sep 17 00:00:00 2001 From: LK-Simon Date: Sun, 28 Aug 2022 20:00:32 +0200 Subject: [PATCH 2/2] README.md updated for v4.2.0 Added examples for Event Scheduling, and updated the future features accordingly. --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0d07c79..ddfc370 100644 --- a/README.md +++ b/README.md @@ -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") ), ], //... @@ -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. @@ -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.