Skip to content

Commit

Permalink
introduce WorkflowActionCore superprotocol
Browse files Browse the repository at this point in the history
  • Loading branch information
square-tomb committed Dec 15, 2023
1 parent fb95bae commit c98e54a
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 50 deletions.
6 changes: 3 additions & 3 deletions Workflow/Sources/AnyWorkflow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ extension AnyWorkflow {
/// That type information *is* present in our storage object, however, so we
/// pass the context down to that storage object which will ultimately call
/// through to `context.render(workflow:key:reducer:)`.
internal func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
internal func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowActionCore, Action.WorkflowType == Parent {
return storage.render(context: context, key: key, outputMap: outputMap)
}
}
Expand All @@ -103,7 +103,7 @@ extension AnyWorkflow {
fileprivate class AnyStorage {
var base: Any { fatalError() }

func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowActionCore, Action.WorkflowType == Parent {
fatalError()
}

Expand Down Expand Up @@ -140,7 +140,7 @@ extension AnyWorkflow {
return T.self
}

override func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
override func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowActionCore, Action.WorkflowType == Parent {
let outputMap: (T.Output) -> Action = { [outputTransform] output in
outputMap(outputTransform(output))
}
Expand Down
8 changes: 4 additions & 4 deletions Workflow/Sources/AnyWorkflowConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ extension AnyWorkflowConvertible {
/// - Parameter key: A string that uniquely identifies this workflow.
///
/// - Returns: The `Rendering` generated by the workflow.
public func rendered<Parent>(in context: RenderContext<Parent>, key: String = "") -> Rendering where Output: WorkflowAction, Output.WorkflowType == Parent {
public func rendered<Parent>(in context: RenderContext<Parent>, key: String = "") -> Rendering where Output: WorkflowActionCore, Output.WorkflowType == Parent {
return asAnyWorkflow().render(context: context, key: key, outputMap: { $0 })
}

public func rendered<Parent, Action>(in context: RenderContext<Parent>, key: String = "", outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
public func rendered<Parent, Action>(in context: RenderContext<Parent>, key: String = "", outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowActionCore, Action.WorkflowType == Parent {
return asAnyWorkflow().render(context: context, key: key, outputMap: { outputMap($0) })
}

Expand Down Expand Up @@ -73,12 +73,12 @@ extension AnyWorkflowConvertible where Output == Never {
}

extension AnyWorkflowConvertible where Rendering == Void {
public func running<Parent, Action>(in context: RenderContext<Parent>, key: String = "", outputMap: @escaping (Output) -> Action) where Action: WorkflowAction, Action.WorkflowType == Parent {
public func running<Parent, Action>(in context: RenderContext<Parent>, key: String = "", outputMap: @escaping (Output) -> Action) where Action: WorkflowActionCore, Action.WorkflowType == Parent {
rendered(in: context, key: key, outputMap: outputMap)
}
}

extension AnyWorkflowConvertible where Rendering == Void, Output: WorkflowAction {
extension AnyWorkflowConvertible where Rendering == Void, Output: WorkflowActionCore {
public func running<Parent>(in context: RenderContext<Parent>, key: String = "") where Output.WorkflowType == Parent {
rendered(in: context, key: key)
}
Expand Down
12 changes: 6 additions & 6 deletions Workflow/Sources/RenderContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
/// - Parameter key: A string that uniquely identifies this child.
///
/// - Returns: The `Rendering` result of the child's `render` method.
func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Child: Workflow, Action: WorkflowAction, WorkflowType == Action.WorkflowType {
func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Child: Workflow, Action: WorkflowActionCore, WorkflowType == Action.WorkflowType {
fatalError()
}

Expand All @@ -77,7 +77,7 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
///
/// - Parameter actionType: The type of Action this Sink may process
/// - Returns: A Sink capable of relaying `Action` instances to the Workflow runtime
public func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowAction, Action.WorkflowType == WorkflowType {
public func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowActionCore, Action.WorkflowType == WorkflowType {
fatalError()
}

Expand Down Expand Up @@ -118,12 +118,12 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
super.init()
}

override func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where WorkflowType == Action.WorkflowType, Child: Workflow, Action: WorkflowAction {
override func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where WorkflowType == Action.WorkflowType, Child: Workflow, Action: WorkflowActionCore {
assertStillValid()
return implementation.render(workflow: workflow, key: key, outputMap: outputMap)
}

override func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where WorkflowType == Action.WorkflowType, Action: WorkflowAction {
override func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where WorkflowType == Action.WorkflowType, Action: WorkflowActionCore {
assertStillValid()
return implementation.makeSink(of: actionType)
}
Expand All @@ -142,9 +142,9 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
internal protocol RenderContextType: AnyObject {
associatedtype WorkflowType: Workflow

func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Child: Workflow, Action: WorkflowAction, Action.WorkflowType == WorkflowType
func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Child: Workflow, Action: WorkflowActionCore, Action.WorkflowType == WorkflowType

func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowAction, Action.WorkflowType == WorkflowType
func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowActionCore, Action.WorkflowType == WorkflowType

func runSideEffect(key: AnyHashable, action: (_ lifetime: Lifetime) -> Void)
}
Expand Down
2 changes: 1 addition & 1 deletion Workflow/Sources/Sink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

/// Sink is a type that receives incoming values (commonly events or `WorkflowAction`)
/// Sink is a type that receives incoming values (commonly events or `WorkflowActionCore`)
///
/// Use `RenderContext.makeSink` to create instances.
public struct Sink<Value> {
Expand Down
16 changes: 8 additions & 8 deletions Workflow/Sources/SubtreeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ extension WorkflowNode {

extension WorkflowNode.SubtreeManager {
enum Output {
case update(any WorkflowAction<WorkflowType>, source: WorkflowUpdateDebugInfo.Source)
case update(any WorkflowActionCore<WorkflowType>, source: WorkflowUpdateDebugInfo.Source)
case childDidUpdate(WorkflowUpdateDebugInfo)
}
}
Expand Down Expand Up @@ -178,7 +178,7 @@ extension WorkflowNode.SubtreeManager {
outputMap: @escaping (Child.Output) -> Action
) -> Child.Rendering
where Child: Workflow,
Action: WorkflowAction,
Action: WorkflowActionCore,
WorkflowType == Action.WorkflowType {
/// A unique key used to identify this child workflow
let childKey = ChildKey(childType: Child.self, key: key)
Expand Down Expand Up @@ -227,7 +227,7 @@ extension WorkflowNode.SubtreeManager {
return child.render()
}

func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowAction, WorkflowType == Action.WorkflowType {
func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowActionCore, WorkflowType == Action.WorkflowType {
let reusableSink = sinkStore.findOrCreate(actionType: Action.self)

let sink = Sink<Action> { [weak reusableSink] action in
Expand Down Expand Up @@ -270,7 +270,7 @@ extension WorkflowNode.SubtreeManager {
self.usedSinks = [:]
}

mutating func findOrCreate<Action: WorkflowAction>(actionType: Action.Type) -> ReusableSink<Action> {
mutating func findOrCreate<Action: WorkflowActionCore>(actionType: Action.Type) -> ReusableSink<Action> {
let key = ObjectIdentifier(actionType)

let reusableSink: ReusableSink<Action>
Expand Down Expand Up @@ -302,7 +302,7 @@ extension WorkflowNode.SubtreeManager {
}
}

fileprivate final class ReusableSink<Action: WorkflowAction>: AnyReusableSink where Action.WorkflowType == WorkflowType {
fileprivate final class ReusableSink<Action: WorkflowActionCore>: AnyReusableSink where Action.WorkflowType == WorkflowType {
func handle(action: Action) {
let output = Output.update(action, source: .external)

Expand Down Expand Up @@ -445,11 +445,11 @@ extension WorkflowNode.SubtreeManager {

fileprivate final class ChildWorkflow<W: Workflow>: AnyChildWorkflow {
private let node: WorkflowNode<W>
private var outputMap: (W.Output) -> any WorkflowAction<WorkflowType>
private var outputMap: (W.Output) -> any WorkflowActionCore<WorkflowType>

init(
workflow: W,
outputMap: @escaping (W.Output) -> any WorkflowAction<WorkflowType>,
outputMap: @escaping (W.Output) -> any WorkflowActionCore<WorkflowType>,
eventPipe: EventPipe,
key: String,
parentSession: WorkflowSession?,
Expand Down Expand Up @@ -480,7 +480,7 @@ extension WorkflowNode.SubtreeManager {

func update(
workflow: W,
outputMap: @escaping (W.Output) -> any WorkflowAction<WorkflowType>,
outputMap: @escaping (W.Output) -> any WorkflowActionCore<WorkflowType>,
eventPipe: EventPipe
) {
self.outputMap = outputMap
Expand Down
55 changes: 41 additions & 14 deletions Workflow/Sources/WorkflowAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
* limitations under the License.
*/

/// Conforming types represent an action that advances a workflow. When applied, an action emits the next
/// state and / or output for the workflow.
public protocol WorkflowAction<WorkflowType> {
// TODO: document this
public protocol WorkflowActionCore<WorkflowType> {
/// The type of workflow that this action can be applied to.
associatedtype WorkflowType: Workflow

// TODO: document this
func apply(toState state: inout WorkflowType.State, workflow: WorkflowType) -> WorkflowType.Output?
}

/// Conforming types represent an action that advances a workflow. When applied, an action emits the next
/// state and / or output for the workflow.
public protocol WorkflowAction: WorkflowActionCore {
/// Applies this action to a given state of the workflow, optionally returning an output event.
///
/// - Parameter state: The current state of the workflow. The state is passed as an `inout` param, allowing actions
Expand All @@ -30,11 +36,17 @@ public protocol WorkflowAction<WorkflowType> {
func apply(toState state: inout WorkflowType.State) -> WorkflowType.Output?
}

public extension WorkflowActionCore where Self: WorkflowAction {
func apply(toState state: inout WorkflowType.State, workflow: WorkflowType) -> WorkflowType.Output? {
apply(toState: &state)
}
}

/// A type-erased workflow action.
///
/// The `AnyWorkflowAction` type forwards `apply` to an underlying workflow action, hiding its specific underlying type.
public struct AnyWorkflowAction<WorkflowType: Workflow>: WorkflowAction {
private let _apply: (inout WorkflowType.State) -> WorkflowType.Output?
public struct AnyWorkflowAction<WorkflowType: Workflow>: WorkflowActionCore {
private let _apply: (inout WorkflowType.State, WorkflowType) -> WorkflowType.Output?

/// The underlying type-erased `WorkflowAction`
public let base: Any
Expand All @@ -50,7 +62,7 @@ public struct AnyWorkflowAction<WorkflowType: Workflow>: WorkflowAction {
self = anyEvent
return
}
self._apply = { return base.apply(toState: &$0) }
self._apply = base.apply
self.base = base
self.isClosureBased = false
}
Expand All @@ -62,6 +74,21 @@ public struct AnyWorkflowAction<WorkflowType: Workflow>: WorkflowAction {
_ apply: @escaping (inout WorkflowType.State) -> WorkflowType.Output?,
fileID: StaticString = #fileID,
line: UInt = #line
) {
self.init(
{ state, _ in apply(&state) },
fileID: fileID,
line: line
)
}

/// Creates a type-erased workflow action with the given `apply` implementation.
///
/// - Parameter apply: the apply function for the resulting action.
public init(
_ apply: @escaping (inout WorkflowType.State, WorkflowType) -> WorkflowType.Output?,
fileID: StaticString = #fileID,
line: UInt = #line
) {
let closureAction = ClosureAction<WorkflowType>(
_apply: apply,
Expand All @@ -74,13 +101,13 @@ public struct AnyWorkflowAction<WorkflowType: Workflow>: WorkflowAction {
/// Private initializer forwarded to via `init(_ apply:...)`
/// - Parameter closureAction: The `ClosureAction` wrapping the underlying `apply` closure.
fileprivate init(closureAction: ClosureAction<WorkflowType>) {
self._apply = closureAction.apply(toState:)
self._apply = closureAction.apply(toState:workflow:)
self.base = closureAction
self.isClosureBased = true
}

public func apply(toState state: inout WorkflowType.State) -> WorkflowType.Output? {
return _apply(&state)
public func apply(toState state: inout WorkflowType.State, workflow: WorkflowType) -> WorkflowType.Output? {
return _apply(&state, workflow)
}
}

Expand Down Expand Up @@ -108,13 +135,13 @@ extension AnyWorkflowAction {
/// A `WorkflowAction` that wraps an `apply(...)` implementation defined by a closure.
/// Mainly used to provide more useful debugging/telemetry information for `AnyWorkflow` instances
/// defined via a closure.
struct ClosureAction<WorkflowType: Workflow>: WorkflowAction {
private let _apply: (inout WorkflowType.State) -> WorkflowType.Output?
struct ClosureAction<WorkflowType: Workflow>: WorkflowActionCore {
private let _apply: (inout WorkflowType.State, WorkflowType) -> WorkflowType.Output?
let fileID: StaticString
let line: UInt

init(
_apply: @escaping (inout WorkflowType.State) -> WorkflowType.Output?,
_apply: @escaping (inout WorkflowType.State, WorkflowType) -> WorkflowType.Output?,
fileID: StaticString,
line: UInt
) {
Expand All @@ -123,8 +150,8 @@ struct ClosureAction<WorkflowType: Workflow>: WorkflowAction {
self.line = line
}

func apply(toState state: inout WorkflowType.State) -> WorkflowType.Output? {
_apply(&state)
func apply(toState state: inout WorkflowType.State, workflow: WorkflowType) -> WorkflowType.Output? {
_apply(&state, workflow)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Workflow/Sources/WorkflowLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ final class WorkflowLogger {
os_signpost(.end, log: .active, name: "Alive", signpostID: signpostID)
}

static func logSinkEvent<Action: WorkflowAction>(ref: AnyObject, action: Action) {
static func logSinkEvent<Action: WorkflowActionCore>(ref: AnyObject, action: Action) {
guard WorkflowLogging.config.logActions else { return }

let signpostID = OSSignpostID(log: .active, object: ref)
Expand Down
10 changes: 5 additions & 5 deletions Workflow/Sources/WorkflowNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ final class WorkflowNode<WorkflowType: Workflow> {

switch subtreeOutput {
case .update(let action, let source):
/// 'Opens' the existential `any WorkflowAction<WorkflowType>` value
/// 'Opens' the existential `any WorkflowActionCore<WorkflowType>` value
/// allowing the underlying conformance to be applied to the Workflow's State
let outputEvent = openAndApply(
action,
Expand Down Expand Up @@ -178,12 +178,12 @@ extension WorkflowNode {
}

private extension WorkflowNode {
/// Applies an appropriate `WorkflowAction` to advance the underlying Workflow `State`
/// Applies an appropriate `WorkflowActionCore` to advance the underlying Workflow `State`
/// - Parameters:
/// - action: The `WorkflowAction` to apply
/// - action: The `WorkflowActionCore` to apply
/// - isExternal: Whether the handled action came from the 'outside world' vs being bubbled up from a child node
/// - Returns: An optional `Output` produced by the action application
func openAndApply<A: WorkflowAction>(
func openAndApply<A: WorkflowActionCore>(
_ action: A,
isExternal: Bool
) -> WorkflowType.Output? where A.WorkflowType == WorkflowType {
Expand All @@ -208,7 +208,7 @@ private extension WorkflowNode {
defer { observerCompletion?(state, output) }

/// Apply the action to the current state
output = action.apply(toState: &state)
output = action.apply(toState: &state, workflow: workflow)

return output
}
Expand Down
Loading

0 comments on commit c98e54a

Please sign in to comment.