Skip to content

Commit 3806700

Browse files
committed
Add options for stablize graph on appear
1 parent 737e68c commit 3806700

File tree

4 files changed

+71
-4
lines changed

4 files changed

+71
-4
lines changed

Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MyRing.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import ForceSimulation
1212

1313
struct MyRing: View {
1414

15-
@State var graphStates = ForceDirectedGraphState()
15+
@State var graphStates = ForceDirectedGraphState(
16+
ticksOnAppear: .untilStable
17+
)
1618

1719
@State var draggingNodeID: Int? = nil
1820

Sources/ForceSimulation/Simulation.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1+
@usableFromInline
2+
package enum Ticks<Scalar: Sendable & FloatingPoint>: Sendable {
3+
case untilReachingAlpha(Scalar?)
4+
case iteration(Int)
5+
6+
@inlinable
7+
public static var zero: Self {
8+
.iteration(0)
9+
}
10+
11+
@inlinable
12+
public static var untilStable: Self {
13+
.untilReachingAlpha(nil)
14+
}
15+
}
16+
117
/// An any-dimensional force simulation.
218
/// The points are placed in a space where you use a SIMD data structure
319
/// to describe their coordinates.
4-
public final class Simulation<Vector, ForceField>
20+
public final class Simulation<Vector, ForceField>: @unchecked Sendable
521
where Vector: SimulatableVector & L2NormCalculatable, ForceField: ForceProtocol<Vector> {
622

723
@usableFromInline
@@ -89,6 +105,7 @@ where Vector: SimulatableVector & L2NormCalculatable, ForceField: ForceProtocol<
89105

90106
/// Run a number of iterations of ticks.
91107
@inlinable
108+
@inline(__always)
92109
public func tick(iterations: UInt = 1) {
93110
// print(self.kinetics.alpha, self.kinetics.alphaMin)
94111
guard self.kinetics.alpha >= self.kinetics.alphaMin else { return }
@@ -99,13 +116,29 @@ where Vector: SimulatableVector & L2NormCalculatable, ForceField: ForceProtocol<
99116
}
100117
}
101118

119+
/// Run a number of iterations of ticks.
120+
@inlinable
121+
package func tick(ticks: Ticks<Vector.Scalar>) {
122+
switch ticks {
123+
case .iteration(let count):
124+
self.tick(iterations: UInt(count))
125+
case .untilReachingAlpha(let alpha):
126+
let alpha = alpha ?? self.kinetics.alphaMin
127+
while self.kinetics.alpha >= alpha {
128+
self.kinetics.updateAlpha()
129+
self.forceField.apply(to: &self.kinetics)
130+
self.kinetics.updatePositions()
131+
}
132+
}
133+
}
134+
135+
@inlinable
102136
deinit {
103137
self.forceField.dispose()
104138
}
105139

106140
}
107141

108-
109142
public typealias Simulation2D<ForceField> = Simulation<SIMD2<Double>, ForceField>
110143
where ForceField: ForceProtocol<SIMD2<Double>>
111144

Sources/Grape/Views/ForceDirectedGraphModel.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,18 @@ public final class ForceDirectedGraphModel<NodeID: Hashable> {
287287

288288
@inlinable
289289
func trackStateMixin() {
290+
Task { @MainActor [self] in
291+
switch stateMixinRef.ticksOnAppear {
292+
case .iteration(let count):
293+
simulationContext.storage.tick(ticks: .iteration(count))
294+
case .untilReachingAlpha(let alpha):
295+
simulationContext.storage.tick(ticks: .untilReachingAlpha(alpha))
296+
}
297+
withMutation(keyPath: \.currentFrame) {
298+
currentFrame += 1
299+
}
300+
}
301+
290302
if stateMixinRef.isRunning {
291303
start()
292304
} else {

Sources/Grape/Views/ForceDirectedGraphState.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,26 @@ import Observation
1616
// }
1717
// }
1818

19+
public enum Ticks: Sendable {
20+
case untilReachingAlpha(Double?)
21+
case iteration(Int)
22+
23+
@inlinable
24+
public static var zero: Self {
25+
.iteration(0)
26+
}
27+
28+
@inlinable
29+
public static var untilStable: Self {
30+
.untilReachingAlpha(nil)
31+
}
32+
}
33+
1934
public class ForceDirectedGraphState: Observation.Observable {
2035

36+
@usableFromInline
37+
internal var ticksOnAppear: Ticks
38+
2139
@usableFromInline
2240
internal var _$modelTransform: ViewportTransform
2341

@@ -53,11 +71,13 @@ public class ForceDirectedGraphState: Observation.Observable {
5371
@inlinable
5472
public init(
5573
initialIsRunning: Bool = true,
56-
initialModelTransform: ViewportTransform = .identity
74+
initialModelTransform: ViewportTransform = .identity,
75+
ticksOnAppear: Ticks = .iteration(0)
5776
) {
5877
self._reg = Observation.ObservationRegistrar()
5978
self._$modelTransform = initialModelTransform
6079
self._$isRunning = initialIsRunning
80+
self.ticksOnAppear = ticksOnAppear
6181
}
6282

6383
// MARK: - Observation

0 commit comments

Comments
 (0)