-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathGraphCalculate.swift
More file actions
137 lines (110 loc) · 5.32 KB
/
GraphCalculate.swift
File metadata and controls
137 lines (110 loc) · 5.32 KB
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
//
// GraphCalculate.swift
//
//
// Created by Elliot Boschwitz on 6/8/24.
//
import Foundation
extension GraphCalculatable {
@MainActor
/// The main graph calculator. Shouldn't be called directly unless you know what you're doing.
public func calculate(
from nodeIds: Set<Node.ID>
) -> (
Set<NodePortType<Node>>, // portsToUpdate
Bool // shouldResortPreviewLayers
) {
let redEdgeCycles = self.topologicalData.cycleNodesForNextGraphStep
return self.traverseGraph(from: nodeIds,
redEdgeCycles: redEdgeCycles) { queuedNodeResult, queue in
// Retrieve the node afresh everytime,
// since an upstream node's changes may have changed its inputs
guard let node = self.getNode(id: queuedNodeResult.nodeId) else {
// Not necessarily bad -- can happen if we deleted nodes but those nodes were still scheduled to run.
// fatalErrorIfDebug()
return
}
let existingOutputValues = node.outputsValuesList
let outputCoordinates = node.outputCoordinates
guard let evalResult = self.calculateNode(node) else {
return
}
// Update queue with NodeIds for nodes which need re-evaluation
let changedInputIds = self.getChangedDownstreamInputIds(
evalResult: evalResult,
sourceNode: node,
existingOutputsValues: existingOutputValues,
outputCoordinates: outputCoordinates)
let changedNodeIds = Set(changedInputIds.map { (id: Self.Node.InputRow.ID) in
id.nodeId
})
// Update queue set with changed downstream nodes
queue = changedNodeIds.union(Set(queue))
}
}
@MainActor
/// The main graph calculator. Shouldn't be called directly unless you know what you're doing.
public func traverseGraph(
from nodeIds: Set<Node.ID>,
redEdgeCycles: Set<CycleNodeUpdateData<Node>>,
callback: @escaping @MainActor (QueuedNodeResultType<Node>, inout Set<Node.ID>) -> ()
) -> (
Set<NodePortType<Node>>, // portsToUpdate
Bool // shouldResortPreviewLayers
) {
let graphState = self
var visitedNodes = Set<Node.ID>()
var queue = nodeIds
// Reset state on scheduled cycle nodes
self.topologicalData.nodesForNextGraphStep = .init()
self.topologicalData.cycleNodesForNextGraphStep = .init()
var portsToUpdate = Set<NodePortType<Node>>()
// First handle red edge cycle nodes from last graph step by update inputs of nodes
for cycleNodeData in redEdgeCycles {
let didChangeInputs = self.updateInputs(inputCoordinate: cycleNodeData.portId,
upstreamOutputValues: cycleNodeData.values,
mediaList: cycleNodeData.mediaList,
upstreamOutputChanged: true,
// empty cycle starting points ensures we update inputs
cycleStartingPoints: .init())
// Run red edge cycle node's eval if inputs changed from this update
if didChangeInputs {
queue.insert(cycleNodeData.nodeId)
}
}
while let queuedNodeResult = topologicalData.getNextNodeToCalculate(for: queue,
visitedNodes: visitedNodes) {
let nodeId = queuedNodeResult.nodeId
queue.remove(nodeId)
guard !visitedNodes.contains(nodeId) else {
assertInDebug(self.topologicalData.cycleContains(nodeId))
#if DEV_DEBUG
// print("GraphState.calculate: scheduling cycle node \(nodeId)")
#endif
self.topologicalData.nodesForNextGraphStep.insert(nodeId)
continue
}
visitedNodes.insert(nodeId)
callback(queuedNodeResult, &queue)
// Track changed outputs here, inputs in didInputsUpdate
portsToUpdate.insert(NodePortType<Node>.allOutputs(nodeId))
} // while let ...
// Why are we unioning this?
// Can we just return the value instead?
// Is `self.portsToUpdate` updated somewhere else, e.g. in the update of an input-observer?
self.portsToUpdate = self.portsToUpdate.union(portsToUpdate)
return (self.portsToUpdate, self.shouldResortPreviewLayers)
}
@MainActor
public func scheduleForNextGraphStep(_ id: Node.ID) {
self.topologicalData.nodesForNextGraphStep.insert(id)
}
@MainActor
public func scheduleForNextGraphStep(_ idSet: Set<Node.ID>) {
self.topologicalData.nodesForNextGraphStep = self.topologicalData.nodesForNextGraphStep.union(idSet)
}
@MainActor
public func scheduleForNextGraphStep(_ idList: [Node.ID]) {
self.scheduleForNextGraphStep(Set(idList))
}
}