-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathWorkflowHost.swift
139 lines (113 loc) · 5.32 KB
/
WorkflowHost.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
* Copyright 2020 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ReactiveSwift
/// Defines a type that receives debug information about a running workflow hierarchy.
public protocol WorkflowDebugger {
/// Called once when the workflow hierarchy initializes.
///
/// - Parameter snapshot: Debug information about the workflow hierarchy.
func didEnterInitialState(snapshot: WorkflowHierarchyDebugSnapshot)
/// Called when an update occurs anywhere within the workflow hierarchy.
///
/// - Parameter snapshot: Debug information about the workflow hierarchy *after* the update.
/// - Parameter updateInfo: Information about the update.
func didUpdate(snapshot: WorkflowHierarchyDebugSnapshot, updateInfo: WorkflowUpdateDebugInfo)
}
/// Manages an active workflow hierarchy.
public final class WorkflowHost<WorkflowType: Workflow> {
private let debugger: WorkflowDebugger?
private let (outputEvent, outputEventObserver) = Signal<WorkflowType.Output, Never>.pipe()
private let rootNode: WorkflowNode<WorkflowType>
private let mutableRendering: MutableProperty<WorkflowType.Rendering>
/// Represents the `Rendering` produced by the root workflow in the hierarchy. New `Rendering` values are produced
/// as state transitions occur within the hierarchy.
public let rendering: Property<WorkflowType.Rendering>
/// Initializes a new host with the given workflow at the root.
///
/// - Parameter workflow: The root workflow in the hierarchy
/// - Parameter debugger: An optional debugger. If provided, the host will notify the debugger of updates
/// to the workflow hierarchy as state transitions occur.
public init(workflow: WorkflowType, debugger: WorkflowDebugger? = nil) {
self.debugger = debugger
self.rootNode = WorkflowNode(workflow: workflow)
self.mutableRendering = MutableProperty(rootNode.render(isRootNode: true))
self.rendering = Property(mutableRendering)
rootNode.enableEvents()
debugger?.didEnterInitialState(snapshot: rootNode.makeDebugSnapshot())
rootNode.onOutput = { [weak self] output in
self?.handle(output: output)
}
}
/// Update the input for the workflow. Will cause a render pass.
public func update(workflow: WorkflowType) {
rootNode.update(workflow: workflow)
// Treat the update as an "output" from the workflow originating from an external event to force a render pass.
let output = WorkflowNode<WorkflowType>.Output(
outputEvent: nil,
debugInfo: WorkflowUpdateDebugInfo(
workflowType: "\(WorkflowType.self)",
kind: .didUpdate(source: .external)
)
)
handle(output: output)
}
private func handle(output: WorkflowNode<WorkflowType>.Output) {
mutableRendering.value = rootNode.render(isRootNode: true)
if let outputEvent = output.outputEvent {
outputEventObserver.send(value: outputEvent)
}
debugger?.didUpdate(
snapshot: rootNode.makeDebugSnapshot(),
updateInfo: output.debugInfo
)
rootNode.enableEvents()
}
/// A signal containing output events emitted by the root workflow in the hierarchy.
public var output: Signal<WorkflowType.Output, Never> {
return outputEvent
}
}
extension WorkflowHost {
/// Initializes a new host with the given workflow at the root.
///
/// - Parameter workflow: The root workflow in the hierarchy
/// - Parameter debugger: An optional debugger. If provided, the host will notify the debugger of updates
///
@_disfavoredOverload
public convenience init<AnyWorkflowType: AnyWorkflowConvertible>(
workflow: AnyWorkflowType,
debugger: WorkflowDebugger? = nil
) where WorkflowType == AnyWorkflowWrapper<AnyWorkflowType.Rendering, AnyWorkflowType.Output> {
self.init(workflow: AnyWorkflowWrapper(workflow), debugger: debugger)
}
}
public typealias AnyWorkflowHost<Rendering, Output> = WorkflowHost<AnyWorkflowWrapper<Rendering, Output>>
/// A wrapper around an AnyWorkflow that allows consumers to create a WorkflowHost from an
/// `AnyWorkflowConvertible`.
public struct AnyWorkflowWrapper<Rendering, Output>: Workflow {
public typealias State = Void
public typealias Output = Output
public typealias Rendering = Rendering
var wrapped: AnyWorkflow<Rendering, Output>
public init<W: AnyWorkflowConvertible>(_ wrapped: W) where W.Rendering == Rendering, W.Output == Output {
self.wrapped = wrapped.asAnyWorkflow()
}
public func render(state: State, context: RenderContext<Self>) -> Rendering {
return wrapped
.mapOutput { AnyWorkflowAction(sendingOutput: $0) }
.rendered(in: context)
}
}