diff --git a/WildWestOnline/Core/Bang/Sources/Game/GameAction.swift b/WildWestOnline/Core/Bang/Sources/Game/GameAction.swift index e62611868..15fe7f862 100644 --- a/WildWestOnline/Core/Bang/Sources/Game/GameAction.swift +++ b/WildWestOnline/Core/Bang/Sources/Game/GameAction.swift @@ -5,7 +5,7 @@ // Created by Hugues Telolahy on 27/10/2024. // -public struct GameAction: Action, Equatable, Codable, Sendable { +public struct GameAction: Action, Equatable, Codable { public var kind: Kind public var payload: Payload diff --git a/WildWestOnline/Core/Bang/Sources/Redux/Store.swift b/WildWestOnline/Core/Bang/Sources/Redux/Store.swift index ee451ff20..97dacbf91 100644 --- a/WildWestOnline/Core/Bang/Sources/Redux/Store.swift +++ b/WildWestOnline/Core/Bang/Sources/Redux/Store.swift @@ -7,7 +7,6 @@ import Combine import Foundation -import SwiftUI // swiftlint:disable private_subject unowned_variable_capture /// ``Action`` is a plain object that describes what happened. @@ -22,7 +21,7 @@ public typealias Reducer = (State, Action) throws -> State /// ``Middleware`` is a plugin, or a composition of several plugins, /// that are assigned to the app global state pipeline in order to /// handle each action received action, to execute side-effects in response, and eventually dispatch more actions -public typealias Middleware = @Sendable (State, Action) -> Action? +public typealias Middleware = (State, Action) async -> Action? /// Namespace for Middlewares public enum Middlewares {} @@ -31,15 +30,13 @@ public enum Middlewares {} /// It defines two roles of a "Store": /// - receive/distribute `Action`; /// - and publish changes of the the current app `State` to possible subscribers. -public class Store: ObservableObject { +public class Store: ObservableObject, @unchecked Sendable { @Published public internal(set) var state: State public internal(set) var eventPublisher: PassthroughSubject public internal(set) var errorPublisher: PassthroughSubject private let reducer: Reducer private let middlewares: [Middleware] - private var cancellables: Set = [] - private let queue = DispatchQueue(label: "store-\(UUID())") private var completion: (() -> Void)? private var subscribedEffects: Int = 0 private var completedEffects: Int = 0 @@ -63,36 +60,25 @@ public class Store: ObservableObject { let newState = try reducer(state, action) eventPublisher.send(action) state = newState - runSideEfects(action, newState: newState) - } catch { - errorPublisher.send(error) - completion?() - } - } + subscribedEffects += middlewares.count + Task.detached { [unowned self] in + for middleware in middlewares { + let output = await middleware(newState, action) + DispatchQueue.main.async { [unowned self] in + if let output { + dispatch(output) + } - private func runSideEfects(_ action: Action, newState: State) { - middlewares.forEach { middleware in - subscribedEffects += 1 - Deferred { - Future { promise in - let output = middleware(newState, action) - promise(.success(output)) + completedEffects += 1 + if completedEffects == subscribedEffects { + completion?() + } + } } } - .eraseToAnyPublisher() - .subscribe(on: queue) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { [unowned self] _ in - completedEffects += 1 - if completedEffects == subscribedEffects { - completion?() - } - }, receiveValue: { [unowned self] value in - if let value { - dispatch(value) - } - }) - .store(in: &cancellables) + } catch { + errorPublisher.send(error) + completion?() } } } diff --git a/WildWestOnline/Core/Bang/Tests/Misc/GameStore+Dispatch.swift b/WildWestOnline/Core/Bang/Tests/Misc/GameStore+Dispatch.swift index 7a9dd6ae6..9e9b7340d 100644 --- a/WildWestOnline/Core/Bang/Tests/Misc/GameStore+Dispatch.swift +++ b/WildWestOnline/Core/Bang/Tests/Misc/GameStore+Dispatch.swift @@ -64,7 +64,7 @@ struct Choice { let selectionIndex: Int } -private final class ChoicesWrapper: @unchecked Sendable { +private class ChoicesWrapper { var choices: [Choice] init(choices: [Choice]) { diff --git a/WildWestOnline/Core/Bang/Tests/SimulationTest.swift b/WildWestOnline/Core/Bang/Tests/SimulationTest.swift index d0e257439..d0cfb0db7 100644 --- a/WildWestOnline/Core/Bang/Tests/SimulationTest.swift +++ b/WildWestOnline/Core/Bang/Tests/SimulationTest.swift @@ -69,13 +69,16 @@ private extension Middlewares { /// Middleare reproducting state according to received event static func verifyState(_ prevState: StateWrapper) -> Middleware { { state, action in - guard let nextState = try? GameReducer().reduce(prevState.value, action) else { - fatalError("Failed reducing \(action)") - } + DispatchQueue.main.async { + guard let nextState = try? GameReducer().reduce(prevState.value, action) else { + fatalError("Failed reducing \(action)") + } + + assert(nextState == state, "Inconsistent state after applying \(action)") - assert(nextState == state, "Inconsistent state after applying \(action)") + prevState.value = nextState + } - prevState.value = nextState return nil } }