Skip to content

Commit e9f82d1

Browse files
authored
[refactor]: introduce a 'HostContext' for exposing host info through tree (#325)
- introduce a `HostContext` type to hold 'host-level' information to be distributed to various entities throughout the workflow tree - currently it holds the two optional top-level injected properties: 'observers' and the 'debugger' - in the future i expect this to be useful for propagating additional top-level configuration information downward (e.g. new runtime behavior options), as well as offering a convenient means for communicating back to the 'root' in some circumstances (e.g. recording nodes that changed when applying actions) note: no new tests added as no new functionality introduced yet
1 parent c98a83a commit e9f82d1

6 files changed

+108
-37
lines changed

Workflow/Sources/SubtreeManager.swift

+25-17
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,19 @@ extension WorkflowNode {
3636

3737
private let session: WorkflowSession
3838

39-
private let observer: WorkflowObserver?
39+
/// Reference to the context object for the entity hosting the corresponding node.
40+
private let hostContext: HostContext
41+
42+
private var observer: WorkflowObserver? {
43+
hostContext.observer
44+
}
4045

4146
init(
4247
session: WorkflowSession,
43-
observer: WorkflowObserver? = nil
48+
hostContext: HostContext
4449
) {
4550
self.session = session
46-
self.observer = observer
51+
self.hostContext = hostContext
4752
}
4853

4954
/// Performs an update pass using the given closure.
@@ -60,8 +65,8 @@ extension WorkflowNode {
6065
previousSinks: previousSinks,
6166
originalChildWorkflows: childWorkflows,
6267
originalSideEffectLifetimes: sideEffectLifetimes,
63-
session: session,
64-
observer: observer
68+
hostContext: hostContext,
69+
session: session
6570
)
6671

6772
let wrapped = RenderContext.make(implementation: context)
@@ -148,18 +153,21 @@ extension WorkflowNode.SubtreeManager {
148153
private let originalSideEffectLifetimes: [AnyHashable: SideEffectLifetime]
149154
private(set) var usedSideEffectLifetimes: [AnyHashable: SideEffectLifetime]
150155

156+
private let hostContext: HostContext
151157
private let session: WorkflowSession
152-
private let observer: WorkflowObserver?
158+
159+
private var observer: WorkflowObserver? {
160+
hostContext.observer
161+
}
153162

154163
init(
155164
previousSinks: [ObjectIdentifier: AnyReusableSink],
156165
originalChildWorkflows: [ChildKey: AnyChildWorkflow],
157166
originalSideEffectLifetimes: [AnyHashable: SideEffectLifetime],
158-
session: WorkflowSession,
159-
observer: WorkflowObserver?
167+
hostContext: HostContext,
168+
session: WorkflowSession
160169
) {
161170
self.eventPipes = []
162-
163171
self.sinkStore = SinkStore(previousSinks: previousSinks)
164172

165173
self.originalChildWorkflows = originalChildWorkflows
@@ -168,8 +176,8 @@ extension WorkflowNode.SubtreeManager {
168176
self.originalSideEffectLifetimes = originalSideEffectLifetimes
169177
self.usedSideEffectLifetimes = [:]
170178

179+
self.hostContext = hostContext
171180
self.session = session
172-
self.observer = observer
173181
}
174182

175183
func render<Child, Action>(
@@ -194,7 +202,7 @@ extension WorkflowNode.SubtreeManager {
194202
let eventPipe = EventPipe()
195203
eventPipes.append(eventPipe)
196204

197-
/// See if we can
205+
/// See if we can reuse an existing child node for the given key.
198206
if let existing = originalChildWorkflows[childKey] {
199207
/// Cast the untyped child into a specific typed child. Because our children are keyed by their workflow
200208
/// type, this should never fail.
@@ -217,8 +225,8 @@ extension WorkflowNode.SubtreeManager {
217225
outputMap: { outputMap($0) },
218226
eventPipe: eventPipe,
219227
key: key,
220-
parentSession: session,
221-
observer: observer
228+
hostContext: hostContext,
229+
parentSession: session
222230
)
223231
}
224232

@@ -446,15 +454,15 @@ extension WorkflowNode.SubtreeManager {
446454
outputMap: @escaping (W.Output) -> any WorkflowAction<WorkflowType>,
447455
eventPipe: EventPipe,
448456
key: String,
449-
parentSession: WorkflowSession?,
450-
observer: WorkflowObserver?
457+
hostContext: HostContext,
458+
parentSession: WorkflowSession?
451459
) {
452460
self.outputMap = outputMap
453461
self.node = WorkflowNode<W>(
454462
workflow: workflow,
455463
key: key,
456-
parentSession: parentSession,
457-
observer: observer
464+
hostContext: hostContext,
465+
parentSession: parentSession
458466
)
459467

460468
super.init(eventPipe: eventPipe)

Workflow/Sources/WorkflowHost.swift

+31-6
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ public protocol WorkflowDebugger {
3232

3333
/// Manages an active workflow hierarchy.
3434
public final class WorkflowHost<WorkflowType: Workflow> {
35-
private let debugger: WorkflowDebugger?
36-
3735
private let (outputEvent, outputEventObserver) = Signal<WorkflowType.Output, Never>.pipe()
3836

3937
// @testable
@@ -45,6 +43,13 @@ public final class WorkflowHost<WorkflowType: Workflow> {
4543
/// as state transitions occur within the hierarchy.
4644
public let rendering: Property<WorkflowType.Rendering>
4745

46+
/// Context object to pass down to descendant nodes in the tree.
47+
let context: HostContext
48+
49+
private var debugger: WorkflowDebugger? {
50+
context.debugger
51+
}
52+
4853
/// Initializes a new host with the given workflow at the root.
4954
///
5055
/// - Parameter workflow: The root workflow in the hierarchy
@@ -56,17 +61,20 @@ public final class WorkflowHost<WorkflowType: Workflow> {
5661
observers: [WorkflowObserver] = [],
5762
debugger: WorkflowDebugger? = nil
5863
) {
59-
self.debugger = debugger
60-
6164
let observer = WorkflowObservation
6265
.sharedObserversInterceptor
6366
.workflowObservers(for: observers)
6467
.chained()
6568

69+
self.context = HostContext(
70+
observer: observer,
71+
debugger: debugger
72+
)
73+
6674
self.rootNode = WorkflowNode(
6775
workflow: workflow,
68-
parentSession: nil,
69-
observer: observer
76+
hostContext: context,
77+
parentSession: nil
7078
)
7179

7280
self.mutableRendering = MutableProperty(rootNode.render())
@@ -115,3 +123,20 @@ public final class WorkflowHost<WorkflowType: Workflow> {
115123
outputEvent
116124
}
117125
}
126+
127+
// MARK: - HostContext
128+
129+
/// A context object to expose certain root-level information to each node
130+
/// in the Workflow tree.
131+
final class HostContext {
132+
let observer: WorkflowObserver?
133+
let debugger: WorkflowDebugger?
134+
135+
init(
136+
observer: WorkflowObserver?,
137+
debugger: WorkflowDebugger?
138+
) {
139+
self.observer = observer
140+
self.debugger = debugger
141+
}
142+
}

Workflow/Sources/WorkflowNode.swift

+18-12
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,53 @@
1616

1717
/// Manages a running workflow.
1818
final class WorkflowNode<WorkflowType: Workflow> {
19-
/// Holds the current state of the workflow
19+
/// The current `State` of the node's `Workflow`.
2020
private var state: WorkflowType.State
2121

22-
/// Holds the current workflow.
22+
/// Holds the current `Workflow` managed by this node.
2323
private var workflow: WorkflowType
2424

25-
/// An optional `WorkflowObserver` instance
26-
let observer: WorkflowObserver?
27-
28-
var onOutput: ((Output) -> Void)?
25+
/// Reference to the context object for the entity hosting this node.
26+
let hostContext: HostContext
2927

3028
/// Manages the children of this workflow, including diffs during/after render passes.
3129
private let subtreeManager: SubtreeManager
3230

3331
/// 'Session' metadata associated with this node
3432
let session: WorkflowSession
3533

34+
/// Callback to invoke when a child `Output` is produced.
35+
var onOutput: ((Output) -> Void)?
36+
37+
/// An optional `WorkflowObserver` instance
38+
var observer: WorkflowObserver? {
39+
hostContext.observer
40+
}
41+
3642
init(
3743
workflow: WorkflowType,
3844
key: String = "",
39-
parentSession: WorkflowSession? = nil,
40-
observer: WorkflowObserver? = nil
45+
hostContext: HostContext,
46+
parentSession: WorkflowSession? = nil
4147
) {
4248
/// Get the initial state
4349
self.workflow = workflow
44-
self.observer = observer
50+
self.hostContext = hostContext
4551
self.session = WorkflowSession(
4652
workflow: workflow,
4753
renderKey: key,
4854
parent: parentSession
4955
)
5056
self.subtreeManager = SubtreeManager(
5157
session: session,
52-
observer: observer
58+
hostContext: hostContext
5359
)
5460

55-
self.observer?.sessionDidBegin(session)
61+
hostContext.observer?.sessionDidBegin(session)
5662

5763
self.state = workflow.makeInitialState()
5864

59-
self.observer?.workflowDidMakeInitialState(
65+
observer?.workflowDidMakeInitialState(
6066
workflow,
6167
initialState: state,
6268
session: session

Workflow/Tests/SubtreeManagerTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ extension WorkflowNode.SubtreeManager {
353353
fileprivate convenience init() {
354354
self.init(
355355
session: .testing(),
356-
observer: nil
356+
hostContext: .testing()
357357
)
358358
}
359359
}

Workflow/Tests/TestUtilities.swift

+15-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*/
1616

1717
import Foundation
18-
import Workflow
18+
19+
@testable import Workflow
1920

2021
/// Renders to a model that contains a callback, which in turn sends an output event.
2122
struct StateTransitioningWorkflow: Workflow {
@@ -55,3 +56,16 @@ struct StateTransitioningWorkflow: Workflow {
5556
}
5657
}
5758
}
59+
60+
// MARK: -
61+
62+
extension HostContext {
63+
static func testing(
64+
observer: WorkflowObserver? = nil
65+
) -> HostContext {
66+
HostContext(
67+
observer: observer,
68+
debugger: nil
69+
)
70+
}
71+
}

Workflow/Tests/WorkflowNodeTests.swift

+18
Original file line numberDiff line numberDiff line change
@@ -400,3 +400,21 @@ private class SessionCollectingObserver: WorkflowObserver {
400400
#else
401401
extension Never: Equatable {}
402402
#endif
403+
404+
// MARK: -
405+
406+
extension WorkflowNode {
407+
convenience init(
408+
workflow: WorkflowType,
409+
key: String = "",
410+
parentSession: WorkflowSession? = nil,
411+
observer: WorkflowObserver? = nil
412+
) {
413+
self.init(
414+
workflow: workflow,
415+
key: key,
416+
hostContext: HostContext.testing(observer: observer),
417+
parentSession: parentSession
418+
)
419+
}
420+
}

0 commit comments

Comments
 (0)