Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Experiment] polling in runloop #468

Merged
merged 3 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Sources/Verge/Store/Changes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ public final class Changes<Value: Equatable>: @unchecked Sendable, ChangesType,
)
}

func replacePrevious(_ previous: Changes<Value>) -> Changes<Value> {
return .init(
previous: previous,
innerBox: innerBox,
version: version,
traces: traces,
modification: nil,
transaction: _transaction
)
}

@inlinable
public func asChanges() -> Changes<Value> {
self
Expand Down
130 changes: 130 additions & 0 deletions Sources/Verge/Store/Store+RunLoop.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import Foundation

extension Store {

/// Push an event to run event loop.
public func updateMainLoop() {
RunLoop.main.perform(inModes: [.common]) {}
}

/**
Subscribes state updates in given run-loop.
*/
public func pollMainLoop(receive: @escaping @MainActor (Changes<State>) -> Void) -> VergeAnyCancellable {

var latestState: Changes<State>? = nil

let subscription = RunLoopActivityObserver.addObserver(acitivity: .beforeWaiting, in: .main) {

let newState = self.state

guard (latestState?.version ?? 0) < newState.version else {
return
}

latestState = newState

let state: Changes<State>

if let latestState {
state = newState.replacePrevious(latestState)
} else {
state = newState.droppedPrevious()
}

MainActor.assumeIsolated {
receive(state)
}

}

return .init {
RunLoopActivityObserver.remove(subscription)
}
}

}

private enum RunLoopActivityObserver {

struct Subscription {
let mode: CFRunLoopMode
let observer: CFRunLoopObserver?
weak var targetRunLoop: RunLoop?
}

static func addObserver(acitivity: CFRunLoopActivity, in runLoop: RunLoop, callback: @escaping () -> Void) -> Subscription {

let o = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, acitivity.rawValue, true, Int.max, { observer, activity in
callback()
});

assert(o != nil)

let mode = CFRunLoopMode.defaultMode!
let cfRunLoop = runLoop.getCFRunLoop()

CFRunLoopAddObserver(cfRunLoop, o, mode);

return .init(mode: mode, observer: o, targetRunLoop: runLoop)
}

static func remove(_ subscription: Subscription) {

guard let observer = subscription.observer, let targetRunLoop = subscription.targetRunLoop else {
return
}

CFRunLoopRemoveObserver(targetRunLoop.getCFRunLoop(), observer, subscription.mode);
}

}

#if DEBUG && canImport(SwiftUI)
import SwiftUI

#Preview {
Content()
}

private struct StoreState: StateType {
var count: Int = 0
}

private struct Content: View {

let store = Store<_, Never>(initialState: StoreState())

@State var subscription: VergeAnyCancellable?
@State var timer: Timer?

var body: some View {
VStack {
Button("Up") {
store.commit {
$0.count += 1
}
}
Button("Background Up") {

for _ in 0..<10 {
store.commit {
$0.count += 1
}
}

}
Button("Run") {
RunLoop.main.run(until: Date(timeIntervalSinceNow: 19))
}
}
.onAppear {
subscription = store.poll { state in

Check failure on line 122 in Sources/Verge/Store/Store+RunLoop.swift

View workflow job for this annotation

GitHub Actions / test

value of type 'Store<StoreState, Never>' has no member 'poll'
print(state.count)
}
}
}

}
#endif

Loading