Skip to content

Commit

Permalink
Merge branch 'main' into muukii/accumulation
Browse files Browse the repository at this point in the history
  • Loading branch information
muukii authored Apr 28, 2024
2 parents fb7e89b + 7212708 commit 5ab756b
Show file tree
Hide file tree
Showing 30 changed files with 265 additions and 212 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,6 @@ fastlane/test_output
Derived
*.xcodeproj

Workspace.xcworkspace/

# End of https://www.gitignore.io/api/swift
2 changes: 2 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
tuist = "4.10.2"
18 changes: 0 additions & 18 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,6 @@
"version" : "1.1.0"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin.git",
"state" : {
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "swift-macro-testing",
"kind" : "remoteSourceControl",
Expand Down
1 change: 0 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.0.0"),
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.0"),
.package(url: "https://github.com/VergeGroup/swift-concurrency-task-manager", from: "1.1.0"),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Verge/Library/EventEmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

@_implementationOnly import Atomics
import Atomics
import Combine
import Foundation
import os
Expand Down
2 changes: 1 addition & 1 deletion Sources/Verge/Library/StoreActivitySubscription.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Combine
@_implementationOnly import Atomics
import Atomics

/**
A subscription that is compatible with Combine’s Cancellable.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Verge/Library/StoreStateSubscription.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Combine
@_implementationOnly import Atomics
import Atomics

@available(*, deprecated, message: "StoreSubscription has separated into StoreStateSubscription and StoreActivitySubscription.")
public typealias StoreSubscription = Cancellable
Expand Down
2 changes: 1 addition & 1 deletion Sources/Verge/Library/VergeAnyCancellable.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Combine
@_implementationOnly import Atomics
import Atomics

/// A typealias to `Set<AnyCancellable>`.
public typealias VergeAnyCancellables = Set<AnyCancellable>
Expand Down
2 changes: 1 addition & 1 deletion Sources/Verge/Store/AnyTargetQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// THE SOFTWARE.

import Foundation
@_implementationOnly import Atomics
import Atomics

public protocol TargetQueueType {
func execute(_ workItem: @escaping () -> Void)
Expand Down
50 changes: 50 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 Expand Up @@ -340,6 +351,33 @@ extension Changes {

}

@inline(__always)
fileprivate func _takeIfChanged_packed_nonEquatable<each Element>(
_ compose: (Value) throws -> (repeat each Element),
comparator: some Comparison<(repeat each Element)>
) rethrows -> (repeat each Element)? {

let current = self.primitive

guard let previousValue = previous else {
return try compose(consume current)
}

let old = previousValue.primitive

let composedFromCurrent = try compose(consume current)
let composedFromOld = try compose(old)

let isEqual = comparator(composedFromOld, composedFromCurrent)

guard isEqual == false else {
return nil
}

return composedFromCurrent

}

/// Performs a closure if the selected value changed from the previous one.
///
/// - Parameters:
Expand Down Expand Up @@ -376,6 +414,7 @@ extension Changes {
/**
Performs a closure if the selected value changed from the previous one.
*/
@available(*, deprecated, message: "Use another function that returns IfChangedBox")
public func ifChanged<T, Result>(
_ selector: ChangesKeyPath<T>,
_ comparer: some Comparison<T>,
Expand Down Expand Up @@ -416,6 +455,17 @@ extension Changes {
return .init(value: (repeat each result))
}

public borrowing func ifChanged<each Element>(
_ compose: (borrowing Value) -> (repeat each Element),
comparator: some Comparison<(repeat each Element)>
) -> IfChangedBox<(repeat each Element)> {
guard let result = _takeIfChanged_packed_nonEquatable(compose, comparator: comparator) else {
return .init()
}

return .init(value: (repeat each result))
}

/**
singular variant
*/
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.commonModes!
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.pollMainLoop { state in
print(state.count)
}
}
}

}
#endif

14 changes: 12 additions & 2 deletions Sources/Verge/Store/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Foundation
import os.log
import ConcurrencyTaskManager

@_implementationOnly import Atomics
import Atomics

#if canImport(Combine)
import Combine
Expand Down Expand Up @@ -323,6 +323,12 @@ extension Store {
// MARK: - Wait
extension Store {

/**
Commit operation does not mean that emitting latest state for all of subscribers synchronously.
Updating state of the store will be updated immediately.

To wait until all of the subscribers get the latest state, you can use this method.
*/
public func waitUntilAllEventConsumed() async {
await withCheckedContinuation { c in
accept(.waiter({
Expand Down Expand Up @@ -807,9 +813,13 @@ extension Store {
mutation: (inout InoutRef<State>) throws -> Result
) async rethrows -> Result {

return try await writer.perform { [self] _ in
let result = try await writer.perform { [self] _ in
try self.commit(mutation: mutation)
}

await self.waitUntilAllEventConsumed()

return result
}

}
Expand Down
13 changes: 13 additions & 0 deletions Sources/Verge/Store/StoreDriverType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,19 @@ extension StoreDriverType where Scope == TargetStore.State {

}

extension StoreDriverType {

/**
Commit operation does not mean that emitting latest state for all of subscribers synchronously.
Updating state of the store will be updated immediately.

To wait until all of the subscribers get the latest state, you can use this method.
*/
public func waitUntilAllEventConsumed() async {
await store.asStore().waitUntilAllEventConsumed()
}
}

extension StoreDriverType {

/**
Expand Down
28 changes: 24 additions & 4 deletions Sources/Verge/SwiftUI/OnReceive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,18 @@ extension View {
/// - Parameters:
/// - instance: The `DispatcherType` instance to subscribe to.
/// - perform: A closure to execute when the `State` changes.
public func onReceiveState<D: StoreDriverType>(_ instance: D, perform: @escaping @MainActor (Changes<D.TargetStore.State>) -> Void) -> some View {
onReceive(instance.store.asStore()._statePublisher().receive(on: DispatchQueue.main), perform: perform)
public func onReceiveState<D: StoreDriverType>(
_ instance: D,
perform: @escaping @MainActor (Changes<D.TargetStore.State>) -> Void
) -> some View {
onReceive(
instance.store.asStore()._statePublisher().receive(on: DispatchQueue.main),
perform: { value in
MainActor.assumeIsolated {
perform(value)
}
}
)
}

/// Adds an action to perform when the specified `DispatcherType` publishes an activity.
Expand All @@ -46,8 +56,18 @@ extension View {
/// - Parameters:
/// - instance: The `DispatcherType` instance to subscribe to.
/// - perform: A closure to execute when the `Activity` is published.
public func onReceiveActivity<D: StoreDriverType>(_ instance: D, perform: @escaping @MainActor (D.TargetStore.Activity) -> Void) -> some View {
onReceive(instance.store.asStore()._activityPublisher().receive(on: DispatchQueue.main), perform: perform)
public func onReceiveActivity<D: StoreDriverType>(
_ instance: D,
perform: @escaping @MainActor (D.TargetStore.Activity) -> Void
) -> some View {
onReceive(
instance.store.asStore()._activityPublisher().receive(on: DispatchQueue.main),
perform: { value in
MainActor.assumeIsolated {
perform(value)
}
}
)
}

}
2 changes: 1 addition & 1 deletion Sources/Verge/SwiftUI/StoreObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ enum Preview_StoreObject: PreviewProvider {
}
}

final class ViewModel: StoreComponentType {
final class ViewModel: StoreDriverType {

struct State: Equatable {
var count: Int = 0
Expand Down
Loading

0 comments on commit 5ab756b

Please sign in to comment.