forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMemoryAtlasRenderPlanCache.swift
More file actions
193 lines (179 loc) · 6.71 KB
/
Copy pathMemoryAtlasRenderPlanCache.swift
File metadata and controls
193 lines (179 loc) · 6.71 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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import SwiftUI
/// Reuses the camera-invariant portion of an Atlas render plan during a live
/// pan or magnification gesture.
///
/// `MemoryAtlasRenderPlanner` deliberately keeps a stable entity cohort for a
/// detail level. Its node and edge selection therefore does not depend on pan
/// or on the exact zoom within that level, but calculating it still walks the
/// full graph and allocates priority/fairness buckets. SwiftUI can evaluate the
/// body many times per second while either gesture is active, so doing that
/// work per frame becomes visible on production-scale graphs.
///
/// The cache is intentionally narrow: it only reuses a plan while
/// `isCameraMoving` is true and the graph has no active search or timeline
/// filter. The surface hides SwiftUI labels and hit targets during that state;
/// Canvas continues to project the cached entity cohort using the current
/// zoom/pan. When the gesture settles, the planner runs again so collision
/// admitted labels and interactive targets exactly match the final viewport.
/// This preserves continuous node fidelity while removing repeated sorting and
/// cohort-layout work from the gesture hot path.
final class MemoryAtlasRenderPlanCache {
private struct CohortKey: Hashable {
let detailLevel: Int
let compact: Bool
let isFullyLabelled: Bool
let usesCanvasLabels: Bool
let selectedNodeID: String?
}
private let snapshot: MemoryAtlasSnapshot
private var transientPlans: [CohortKey: MemoryAtlasRenderPlan] = [:]
/// Exposed for deterministic performance-harness assertions. These count
/// planner calls rather than wall-clock time, so they are stable across Macs.
private(set) var plannerInvocationCount = 0
private(set) var transientReuseCount = 0
init(snapshot: MemoryAtlasSnapshot) {
self.snapshot = snapshot
}
/// Returns the current plan, reusing only a camera-invariant plan while a
/// pan/zoom gesture is active. Search and time-travel plans intentionally
/// bypass reuse because those states change membership as the user types or
/// scrubs the timeline.
func makePlan(
viewportSize: CGSize,
zoom: CGFloat,
pan: CGSize,
compact: Bool,
selectedNodeID: String?,
matchingNodeIDs: Set<String>?,
matchingEdges: [MemoryAtlasEdgePlacement]?,
asOf: Date?,
timeline: MemoryAtlasTimeline? = nil,
timeCursor: Double? = nil,
isCameraMoving: Bool
) -> MemoryAtlasRenderPlan {
let isTimelineFiltered = timeline != nil && (timeCursor ?? 1) < 0.9995
guard
isCameraMoving,
selectedNodeID == nil,
matchingNodeIDs == nil,
matchingEdges == nil,
asOf == nil,
!isTimelineFiltered
else {
return makeFreshPlan(
viewportSize: viewportSize,
zoom: zoom,
pan: pan,
compact: compact,
selectedNodeID: selectedNodeID,
matchingNodeIDs: matchingNodeIDs,
matchingEdges: matchingEdges,
asOf: asOf,
timeline: timeline,
timeCursor: timeCursor
)
}
let key = CohortKey(
detailLevel: detailLevel(for: zoom),
compact: compact,
isFullyLabelled: isFullyLabelled(zoom: zoom, compact: compact),
usesCanvasLabels: usesCanvasLabels(zoom: zoom, compact: compact),
selectedNodeID: selectedNodeID
)
if let plan = transientPlans[key] {
transientReuseCount += 1
return plan
}
let plan = makeFreshPlan(
viewportSize: viewportSize,
zoom: zoom,
pan: pan,
compact: compact,
selectedNodeID: selectedNodeID,
matchingNodeIDs: nil,
matchingEdges: nil,
asOf: nil,
timeline: nil,
timeCursor: nil
)
transientPlans[key] = plan
return plan
}
private func makeFreshPlan(
viewportSize: CGSize,
zoom: CGFloat,
pan: CGSize,
compact: Bool,
selectedNodeID: String?,
matchingNodeIDs: Set<String>?,
matchingEdges: [MemoryAtlasEdgePlacement]?,
asOf: Date?,
timeline: MemoryAtlasTimeline?,
timeCursor: Double?
) -> MemoryAtlasRenderPlan {
plannerInvocationCount += 1
return MemoryAtlasRenderPlanner.makePlan(
snapshot: snapshot,
viewportSize: viewportSize,
zoom: zoom,
pan: pan,
compact: compact,
selectedNodeID: selectedNodeID,
matchingNodeIDs: matchingNodeIDs,
matchingEdges: matchingEdges,
asOf: asOf,
timeline: timeline,
timeCursor: timeCursor
)
}
private func detailLevel(for zoom: CGFloat) -> Int {
if zoom < MemoryAtlasZoomPolicy.neighborhoodZoom { return 0 }
if zoom < 1.9 { return 1 }
if zoom < MemoryAtlasZoomPolicy.focusModeZoom { return 2 }
if zoom < MemoryAtlasZoomPolicy.inspectModeZoom { return 3 }
return 4
}
private func isFullyLabelled(zoom: CGFloat, compact: Bool) -> Bool {
!compact && zoom >= MemoryAtlasZoomPolicy.fullyLabelledZoom(nodeCount: snapshot.nodes.count)
}
private func usesCanvasLabels(zoom: CGFloat, compact: Bool) -> Bool {
!compact
&& zoom >= MemoryAtlasZoomPolicy.automaticCanvasLabelZoom(nodeCount: snapshot.nodes.count)
}
}
/// The immutable, expensive part of one Brain Map revision.
///
/// A graph response can be large enough that calculating a content digest,
/// relaxing its layout, sorting its replay connections, and rebuilding the
/// camera-plan cache is real work. A SwiftUI view initializer is not a safe
/// owner for any of it: initializers run again whenever unrelated observed
/// state publishes (for example, a memory sync while the Brain Map is open).
///
/// This object is prepared once for a fetched graph revision and then passed
/// unchanged through every render of that revision. It deliberately owns the
/// mutable render-plan cache as well, so a gesture retains its cached cohort
/// instead of recreating it for each frame.
final class MemoryAtlasProjection: @unchecked Sendable {
let graph: KnowledgeGraphResponse
let snapshot: MemoryAtlasSnapshot
let renderPlanCache: MemoryAtlasRenderPlanCache
let connectionBirthFractions: [Double]
init(graph: KnowledgeGraphResponse, userName: String?) {
self.graph = graph
let snapshot = MemoryAtlasSnapshotCache.shared.snapshot(for: graph, userName: userName)
self.snapshot = snapshot
renderPlanCache = MemoryAtlasRenderPlanCache(snapshot: snapshot)
if let timeline = snapshot.timeline {
connectionBirthFractions = snapshot.edges.map { placement in
let endpointBirth =
[placement.edge.sourceId, placement.edge.targetId].map { nodeID in
nodeID == snapshot.anchorNodeID ? 0 : (timeline.playbackFractionByNodeID[nodeID] ?? 1)
}.max() ?? 1
return max(timeline.fraction(for: placement.edge.createdAt), endpointBirth)
}
.sorted()
} else {
connectionBirthFractions = []
}
}
}