Skip to content

Commit e131fa5

Browse files
committed
Refactoring
1 parent d98d473 commit e131fa5

12 files changed

+110
-125
lines changed

Sources/Atoms/Core/Loader/AtomLoader.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ public protocol AtomLoader {
1818

1919
/// Returns a boolean value indicating whether it should notify updates downstream
2020
/// by checking the equivalence of the given old value and new value.
21-
func shouldPropagateUpdate(newValue: Value, oldValue: Value) -> Bool
21+
func shouldUpdateTransitively(newValue: Value, oldValue: Value) -> Bool
2222

23-
/// Performs atom update.
24-
func performPropagativeUpdate(_ body: () -> Void)
23+
/// Performs transitive update for dependent atoms .
24+
func performTransitiveUpdate(_ body: () -> Void)
2525
}
2626

2727
public extension AtomLoader {
28-
func shouldPropagateUpdate(newValue: Value, oldValue: Value) -> Bool {
28+
func shouldUpdateTransitively(newValue: Value, oldValue: Value) -> Bool {
2929
true
3030
}
3131

32-
func performPropagativeUpdate(_ body: () -> Void) {
32+
func performTransitiveUpdate(_ body: () -> Void) {
3333
body()
3434
}
3535
}

Sources/Atoms/Core/Loader/ModifiedAtomLoader.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ public struct ModifiedAtomLoader<Node: Atom, Modifier: AtomModifier>: AtomLoader
2727

2828
/// Returns a boolean value indicating whether it should notify updates downstream
2929
/// by checking the equivalence of the given old value and new value.
30-
public func shouldPropagateUpdate(newValue: Value, oldValue: Value) -> Bool {
31-
modifier.shouldPropagateUpdate(newValue: newValue, oldValue: oldValue)
30+
public func shouldUpdateTransitively(newValue: Value, oldValue: Value) -> Bool {
31+
modifier.shouldUpdateTransitively(newValue: newValue, oldValue: oldValue)
3232
}
3333

34-
/// Performs atom update.
35-
public func performPropagativeUpdate(_ body: () -> Void) {
36-
modifier.performPropagativeUpdate(body)
34+
/// Performs transitive update for dependent atoms .
35+
public func performTransitiveUpdate(_ body: () -> Void) {
36+
modifier.performTransitiveUpdate(body)
3737
}
3838
}
3939

Sources/Atoms/Core/StoreContext.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ private extension StoreContext {
340340
) {
341341
store.state.caches[key] = AtomCache(atom: atom, value: newValue)
342342

343-
// Check if the atom should propagate the update to downstream.
344-
guard atom._loader.shouldPropagateUpdate(newValue: newValue, oldValue: oldValue) else {
343+
// Check whether if the dependent atoms should be updated transitively.
344+
guard atom._loader.shouldUpdateTransitively(newValue: newValue, oldValue: oldValue) else {
345345
return
346346
}
347347

@@ -351,7 +351,7 @@ private extension StoreContext {
351351
atom.updated(newValue: newValue, oldValue: oldValue, context: context)
352352

353353
// Calculate topological order for updating downstream efficiently.
354-
let (edges, omitted) = topologicalSort(key: key, store: store)
354+
let (edges, redundant) = topologicalSort(key: key, store: store)
355355
var skippingFrom = Set<AtomKey>()
356356

357357
// Updates the given atom.
@@ -364,8 +364,8 @@ private extension StoreContext {
364364
let override = lookupOverride(of: atom)
365365
let newCache = makeCache(of: atom, for: key, override: override)
366366

367-
// Skip if the atom should not propagate update to downstream.
368-
guard atom._loader.shouldPropagateUpdate(newValue: newCache.value, oldValue: cache.value) else {
367+
// Check whether if the dependent atoms should be updated transitively.
368+
guard atom._loader.shouldUpdateTransitively(newValue: newCache.value, oldValue: cache.value) else {
369369
// Record the atom to avoid downstream from being update.
370370
skippingFrom.insert(key)
371371
return
@@ -379,14 +379,14 @@ private extension StoreContext {
379379

380380
// Performs update of the given atom with the parent's context.
381381
func performUpdate(atom: some Atom, for key: AtomKey, dependency: some Atom) {
382-
dependency._loader.performPropagativeUpdate {
382+
dependency._loader.performTransitiveUpdate {
383383
update(atom: atom, for: key)
384384
}
385385
}
386386

387387
// Performs update of the given subscription with the parent's context.
388388
func performUpdate(subscription: Subscription, dependency: some Atom) {
389-
dependency._loader.performPropagativeUpdate(subscription.update)
389+
dependency._loader.performTransitiveUpdate(subscription.update)
390390
}
391391

392392
// Do not transitively update atoms that have parent recorded not to update downstream.
@@ -397,24 +397,24 @@ private extension StoreContext {
397397
return edge
398398
}
399399

400-
guard let omittedFrom = omitted[edge.to] else {
400+
guard let redundantFrom = redundant[edge.to] else {
401401
return nil
402402
}
403403

404-
guard let fromKey = omittedFrom.subtracting(skippingFrom).first else {
404+
guard let fromKey = redundantFrom.subtracting(skippingFrom).first else {
405405
return nil
406406
}
407407

408-
// Switch atom update transaction context (e.g. animation) to a non-skipped one on
409-
// a best-effort basis.
410-
// Topological sorting itself does not always produce an idempotent result when multiple
408+
// Convert edge's `from`, which represents a dependent atom, to a non-skipped one on
409+
// a best-effort basis to switch the update transaction context (e.g. animation).
410+
// Topological sorting itself does not guarantee idempotent result when multiple
411411
// dependencies of an atom update simultaneously and there's no valid update order rule to
412412
// determine which atom produced the transitive update, and thus here chooses a random
413-
// dependent atom from omitted ones.
413+
// dependent atom from redundant edges.
414414
return Edge(from: fromKey, to: edge.to)
415415
}
416416

417-
// Performs atom updates ahead of notifying updates to subscriptions.
417+
// Performs transitive update for dependent atoms s ahead of notifying updates to subscriptions.
418418
for edge in edges {
419419
switch edge.to {
420420
case .atom(let key):
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
internal enum Vertex: Hashable {
2+
case atom(key: AtomKey)
3+
case subscriber(key: SubscriberKey)
4+
}
5+
6+
internal struct Edge: Hashable {
7+
let from: AtomKey
8+
let to: Vertex
9+
}
10+
11+
/// DFS topological sorting.
12+
@MainActor
13+
internal func topologicalSort(key: AtomKey, store: AtomStore) -> (
14+
edges: ReversedCollection<[Edge]>,
15+
redundant: [Vertex: Set<AtomKey>] // key = vertex, value = dependencies
16+
) {
17+
var trace = Set<Vertex>()
18+
var edges = [Edge]()
19+
var redundant = [Vertex: Set<AtomKey>]()
20+
21+
func traverse(key: AtomKey, isRedundant: Bool) {
22+
if let children = store.graph.children[key] {
23+
for child in ContiguousArray(children) {
24+
// Do not stop traversing downstream even when edges are already traced
25+
// to analyze the redundant edges later.
26+
let isRedundant = isRedundant || trace.contains(.atom(key: child))
27+
traverse(key: child, from: key, isRedundant: isRedundant)
28+
}
29+
}
30+
31+
if let subscriptions = store.state.subscriptions[key] {
32+
for subscriberKey in ContiguousArray(subscriptions.keys) {
33+
let vertex = Vertex.subscriber(key: subscriberKey)
34+
35+
if isRedundant || trace.contains(.subscriber(key: subscriberKey)) {
36+
redundant[vertex, default: []].insert(key)
37+
}
38+
else {
39+
let edge = Edge(from: key, to: vertex)
40+
edges.append(edge)
41+
}
42+
43+
trace.insert(.subscriber(key: subscriberKey))
44+
}
45+
}
46+
}
47+
48+
func traverse(key: AtomKey, from fromKey: AtomKey, isRedundant: Bool) {
49+
trace.insert(.atom(key: key))
50+
traverse(key: key, isRedundant: isRedundant)
51+
52+
let vertex = Vertex.atom(key: key)
53+
54+
if isRedundant {
55+
redundant[vertex, default: []].insert(fromKey)
56+
}
57+
else {
58+
let edge = Edge(from: fromKey, to: vertex)
59+
edges.append(edge)
60+
}
61+
}
62+
63+
traverse(key: key, isRedundant: false)
64+
65+
return (
66+
edges: edges.reversed(),
67+
redundant: redundant
68+
)
69+
}

Sources/Atoms/Core/TopologicalSorting.swift

Lines changed: 0 additions & 84 deletions
This file was deleted.

Sources/Atoms/Modifier/AnimationModifier.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public struct AnimationModifier<T>: AtomModifier {
7070
value
7171
}
7272

73-
/// Performs atom update.
74-
public func performPropagativeUpdate(_ body: () -> Void) {
73+
/// Performs transitive update for dependent atoms .
74+
public func performTransitiveUpdate(_ body: () -> Void) {
7575
withAnimation(animation, body)
7676
}
7777
}

Sources/Atoms/Modifier/AtomModifier.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,19 @@ public protocol AtomModifier {
3636
/// Returns a boolean value indicating whether it should notify updates downstream
3737
/// by checking the equivalence of the given old value and new value.
3838
@MainActor
39-
func shouldPropagateUpdate(newValue: Value, oldValue: Value) -> Bool
39+
func shouldUpdateTransitively(newValue: Value, oldValue: Value) -> Bool
4040

41-
/// Performs atom update.
41+
/// Performs transitive update for dependent atoms .
4242
@MainActor
43-
func performPropagativeUpdate(_ body: () -> Void)
43+
func performTransitiveUpdate(_ body: () -> Void)
4444
}
4545

4646
public extension AtomModifier {
47-
func shouldPropagateUpdate(newValue: Value, oldValue: Value) -> Bool {
47+
func shouldUpdateTransitively(newValue: Value, oldValue: Value) -> Bool {
4848
true
4949
}
5050

51-
func performPropagativeUpdate(_ body: () -> Void) {
51+
func performTransitiveUpdate(_ body: () -> Void) {
5252
body()
5353
}
5454
}

Sources/Atoms/Modifier/ChangesModifier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public struct ChangesModifier<T: Equatable>: AtomModifier {
5959

6060
/// Returns a boolean value that determines whether it should notify the value update to
6161
/// watchers with comparing the given old value and the new value.
62-
public func shouldPropagateUpdate(newValue: Value, oldValue: Value) -> Bool {
62+
public func shouldUpdateTransitively(newValue: Value, oldValue: Value) -> Bool {
6363
newValue != oldValue
6464
}
6565
}

Sources/Atoms/Modifier/ChangesOfModifier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public struct ChangesOfModifier<BaseValue, T: Equatable>: AtomModifier {
6969

7070
/// Returns a boolean value that determines whether it should notify the value update to
7171
/// watchers with comparing the given old value and the new value.
72-
public func shouldPropagateUpdate(newValue: Value, oldValue: Value) -> Bool {
72+
public func shouldUpdateTransitively(newValue: Value, oldValue: Value) -> Bool {
7373
newValue != oldValue
7474
}
7575
}

Tests/AtomsTests/Core/StoreContextTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ final class StoreContextTests: XCTestCase {
666666
XCTAssertEqual(scoped2Context.watch(atom, subscriber: subscriber, subscription: Subscription()), 21)
667667
XCTAssertEqual(scoped2Context.watch(publisherAtom, subscriber: subscriber, subscription: Subscription()), .suspending)
668668

669-
// Should set the value and then propagate it to the dependent atoms..
669+
// Should set the value and then update the dependent atoms transitively.
670670
scoped2Context.set(20, for: dependency1Atom)
671671

672672
// Should set the value because the atom depends on the shared `dependency1Atom`.
@@ -1150,7 +1150,7 @@ final class StoreContextTests: XCTestCase {
11501150
}
11511151

11521152
@MainActor
1153-
func testUpdatePropagation() {
1153+
func testTransitiveUpdate() {
11541154
struct TestAtom1: StateAtom, Hashable {
11551155
func defaultValue(context: Context) -> Int {
11561156
0

0 commit comments

Comments
 (0)