From ee24de21f26fa8cb5bfbddb68a3a88e4127a0690 Mon Sep 17 00:00:00 2001 From: ra1028 Date: Tue, 3 Jun 2025 20:04:27 +0900 Subject: [PATCH 1/2] Simplify store state structure --- Sources/Atoms/AtomStore.swift | 9 +- Sources/Atoms/Core/Graph.swift | 4 - Sources/Atoms/Core/StoreContext.swift | 93 ++--- Sources/Atoms/Core/StoreState.swift | 10 - Sources/Atoms/Core/TopologicalSort.swift | 4 +- Sources/Atoms/Snapshot.swift | 14 +- .../AtomsTests/Attribute/KeepAliveTests.swift | 24 +- .../Attribute/RefreshableTests.swift | 16 +- .../Attribute/ResettableTests.swift | 8 +- Tests/AtomsTests/Attribute/ScopedTests.swift | 32 +- .../Context/AtomTransactionContextTests.swift | 4 +- .../Context/AtomViewContextTests.swift | 18 +- Tests/AtomsTests/Core/EnvironmentTests.swift | 2 +- Tests/AtomsTests/Core/StoreContextTests.swift | 317 +++++++++--------- .../Core/TopologicalSortTests.swift | 24 +- Tests/AtomsTests/SnapshotTests.swift | 28 +- 16 files changed, 293 insertions(+), 314 deletions(-) delete mode 100644 Sources/Atoms/Core/Graph.swift delete mode 100644 Sources/Atoms/Core/StoreState.swift diff --git a/Sources/Atoms/AtomStore.swift b/Sources/Atoms/AtomStore.swift index 3de4ea69..d9ef3a7b 100644 --- a/Sources/Atoms/AtomStore.swift +++ b/Sources/Atoms/AtomStore.swift @@ -1,8 +1,13 @@ /// An object that stores the state of atoms and its dependency graph. @MainActor public final class AtomStore { - internal var graph = Graph() - internal var state = StoreState() + internal var dependencies = [AtomKey: Set]() + internal var children = [AtomKey: Set]() + internal var caches = [AtomKey: any AtomCacheProtocol]() + internal var states = [AtomKey: any AtomStateProtocol]() + internal var subscriptions = [AtomKey: [SubscriberKey: Subscription]]() + internal var subscribed = [SubscriberKey: Set]() + internal var scopes = [ScopeKey: Scope]() /// Creates a new store. public nonisolated init() {} diff --git a/Sources/Atoms/Core/Graph.swift b/Sources/Atoms/Core/Graph.swift deleted file mode 100644 index 62385dc5..00000000 --- a/Sources/Atoms/Core/Graph.swift +++ /dev/null @@ -1,4 +0,0 @@ -internal struct Graph: Equatable { - var dependencies = [AtomKey: Set]() - var children = [AtomKey: Set]() -} diff --git a/Sources/Atoms/Core/StoreContext.swift b/Sources/Atoms/Core/StoreContext.swift index eb3e51cf..8bd3548b 100644 --- a/Sources/Atoms/Core/StoreContext.swift +++ b/Sources/Atoms/Core/StoreContext.swift @@ -98,8 +98,8 @@ internal struct StoreContext { let value = cache?.value ?? initialize(of: atom, for: key, override: override) // Add an `Edge` from the upstream to downstream. - store.graph.dependencies[transactionState.key, default: []].insert(key) - store.graph.children[key, default: []].insert(transactionState.key) + store.dependencies[transactionState.key, default: []].insert(key) + store.children[key, default: []].insert(transactionState.key) return value } @@ -113,10 +113,10 @@ internal struct StoreContext { let (key, override) = lookupAtomKeyAndOverride(of: atom) let cache = lookupCache(of: atom, for: key) let value = cache?.value ?? initialize(of: atom, for: key, override: override) - let isNewSubscription = store.state.subscribed[subscriber.key, default: []].insert(key).inserted + let isNewSubscription = store.subscribed[subscriber.key, default: []].insert(key).inserted if isNewSubscription { - store.state.subscriptions[key, default: [:]][subscriber.key] = subscription + store.subscriptions[key, default: [:]][subscriber.key] = subscription subscriber.unsubscribe = { unsubscribeAll(for: subscriber.key) } @@ -224,7 +224,7 @@ internal struct StoreContext { func unwatch(_ atom: some Atom, subscriber: Subscriber) { let (key, _) = lookupAtomKeyAndOverride(of: atom) - store.state.subscribed[subscriber.key]?.remove(key) + store.subscribed[subscriber.key]?.remove(key) unsubscribe([key], for: subscriber.key) } @@ -232,14 +232,14 @@ internal struct StoreContext { func registerScope(state: ScopeState) { let key = state.token.key - withUnsafeMutablePointer(to: &store.state.scopes[key]) { scope in + withUnsafeMutablePointer(to: &store.scopes[key]) { scope in if scope.pointee == nil { scope.pointee = Scope() } } state.unregister = { - let scope = store.state.scopes.removeValue(forKey: key) + let scope = store.scopes.removeValue(forKey: key) if let scope { for key in scope.atoms { @@ -252,9 +252,10 @@ internal struct StoreContext { @usableFromInline func snapshot() -> Snapshot { Snapshot( - graph: store.graph, - caches: store.state.caches, - subscriptions: store.state.subscriptions + dependencies: store.dependencies, + children: store.children, + caches: store.caches, + subscriptions: store.subscriptions ) } @@ -264,13 +265,13 @@ internal struct StoreContext { var disusedDependencies = [AtomKey: Set]() for key in keys { - let oldDependencies = store.graph.dependencies[key] - let newDependencies = snapshot.graph.dependencies[key] + let oldDependencies = store.dependencies[key] + let newDependencies = snapshot.dependencies[key] // Update atom values and the graph. - store.state.caches[key] = snapshot.caches[key] - store.graph.dependencies[key] = newDependencies - store.graph.children[key] = snapshot.graph.children[key] + store.caches[key] = snapshot.caches[key] + store.dependencies[key] = newDependencies + store.children[key] = snapshot.children[key] disusedDependencies[key] = oldDependencies?.subtracting(newDependencies ?? []) } @@ -281,13 +282,13 @@ internal struct StoreContext { // Release dependencies that are no longer dependent. if let dependencies = disusedDependencies[key] { for dependency in dependencies { - store.graph.children[dependency]?.remove(key) + store.children[dependency]?.remove(key) checkAndRelease(for: dependency) } } // Notify updates only for the subscriptions of restored atoms. - if let subscriptions = store.state.subscriptions[key] { + if let subscriptions = store.subscriptions[key] { for subscription in subscriptions.values { subscription.update() } @@ -310,10 +311,10 @@ private extension StoreContext { state.effect.initializing(context: currentContext) let value = getValue(of: atom, for: key, override: override) - store.state.caches[key] = AtomCache(atom: atom, value: value, scopeValues: currentScopeValues) + store.caches[key] = AtomCache(atom: atom, value: value, scopeValues: currentScopeValues) if let scopeKey = key.scopeKey { - store.state.scopes[scopeKey]?.atoms.insert(key) + store.scopes[scopeKey]?.atoms.insert(key) } state.effect.initialized(context: currentContext) @@ -326,7 +327,7 @@ private extension StoreContext { cache: AtomCache, newValue: Node.Produced ) { - store.state.caches[key] = cache.updated(value: newValue) + store.caches[key] = cache.updated(value: newValue) // Check whether if the dependent atoms should be updated transitively. guard atom.producer.shouldUpdate(cache.value, newValue) else { @@ -354,7 +355,7 @@ private extension StoreContext { // Overridden atoms don't get updated transitively. let newValue = localContext.getValue(of: cache.atom, for: key, override: nil) - store.state.caches[key] = cache.updated(value: newValue) + store.caches[key] = cache.updated(value: newValue) // Check whether if the dependent atoms should be updated transitively. guard cache.atom.producer.shouldUpdate(cache.value, newValue) else { @@ -403,8 +404,8 @@ private extension StoreContext { continue } - let cache = store.state.caches[key] - let dependencyCache = store.state.caches[edge.from] + let cache = store.caches[key] + let dependencyCache = store.caches[edge.from] if let cache, let dependencyCache { performUpdate(dependency: dependencyCache.atom) { @@ -417,8 +418,8 @@ private extension StoreContext { continue } - let subscription = store.state.subscriptions[edge.from]?[key] - let dependencyCache = store.state.caches[edge.from] + let subscription = store.subscriptions[edge.from]?[key] + let dependencyCache = store.caches[edge.from] if let subscription, let dependencyCache { performUpdate(dependency: dependencyCache.atom, body: subscription.update) @@ -431,20 +432,20 @@ private extension StoreContext { } func release(for key: AtomKey) { - let dependencies = store.graph.dependencies.removeValue(forKey: key) - let state = store.state.states.removeValue(forKey: key) - let cache = store.state.caches.removeValue(forKey: key) + let dependencies = store.dependencies.removeValue(forKey: key) + let state = store.states.removeValue(forKey: key) + let cache = store.caches.removeValue(forKey: key) - store.graph.children.removeValue(forKey: key) - store.state.subscriptions.removeValue(forKey: key) + store.children.removeValue(forKey: key) + store.subscriptions.removeValue(forKey: key) if let scopeKey = key.scopeKey { - store.state.scopes[scopeKey]?.atoms.remove(key) + store.scopes[scopeKey]?.atoms.remove(key) } if let dependencies { for dependency in dependencies { - store.graph.children[dependency]?.remove(key) + store.children[dependency]?.remove(key) checkAndRelease(for: dependency) } } @@ -465,7 +466,7 @@ private extension StoreContext { // 2. It has no downstream atoms. // 3. It has no subscriptions from views. lazy var shouldKeepAlive = { - guard let cache = store.state.caches[key], cache.shouldKeepAlive else { + guard let cache = store.caches[key], cache.shouldKeepAlive else { return false } @@ -474,10 +475,10 @@ private extension StoreContext { } // It should keep alive untile the scope is unregistered. - return store.state.scopes[scopeKey]?.atoms.contains(key) ?? false + return store.scopes[scopeKey]?.atoms.contains(key) ?? false }() - lazy var isChildrenEmpty = store.graph.children[key]?.isEmpty ?? true - lazy var isSubscriptionEmpty = store.state.subscriptions[key]?.isEmpty ?? true + lazy var isChildrenEmpty = store.children[key]?.isEmpty ?? true + lazy var isSubscriptionEmpty = store.subscriptions[key]?.isEmpty ?? true guard !shouldKeepAlive && isChildrenEmpty && isSubscriptionEmpty else { return @@ -488,11 +489,11 @@ private extension StoreContext { func detachDependencies(for key: AtomKey) -> Set { // Remove current dependencies. - let dependencies = store.graph.dependencies.removeValue(forKey: key) ?? [] + let dependencies = store.dependencies.removeValue(forKey: key) ?? [] // Detatch the atom from its children. for dependency in dependencies { - store.graph.children[dependency]?.remove(key) + store.children[dependency]?.remove(key) } return dependencies @@ -500,16 +501,16 @@ private extension StoreContext { func attachDependencies(_ dependencies: Set, for key: AtomKey) { // Set dependencies. - store.graph.dependencies[key] = dependencies + store.dependencies[key] = dependencies // Attach the atom to its children. for dependency in dependencies { - store.graph.children[dependency]?.insert(key) + store.children[dependency]?.insert(key) } } func unsubscribeAll(for subscriberKey: SubscriberKey) { - let keys = store.state.subscribed.removeValue(forKey: subscriberKey) + let keys = store.subscribed.removeValue(forKey: subscriberKey) if let keys { unsubscribe(keys, for: subscriberKey) @@ -518,7 +519,7 @@ private extension StoreContext { func unsubscribe(_ keys: some Sequence, for subscriberKey: SubscriberKey) { for key in keys { - store.state.subscriptions[key]?.removeValue(forKey: subscriberKey) + store.subscriptions[key]?.removeValue(forKey: subscriberKey) checkAndRelease(for: key) } @@ -533,7 +534,7 @@ private extension StoreContext { let oldDependencies = detachDependencies(for: key) return { - let dependencies = store.graph.dependencies[key] ?? [] + let dependencies = store.dependencies[key] ?? [] let disusedDependencies = oldDependencies.subtracting(dependencies) // Release disused dependencies if no longer used. @@ -584,12 +585,12 @@ private extension StoreContext { let currentContext = AtomCurrentContext(store: self) let effect = atom.effect(context: currentContext) let state = AtomState(effect: effect) - store.state.states[key] = state + store.states[key] = state return state } func lookupState(of atom: Node, for key: AtomKey) -> AtomState? { - guard let baseState = store.state.states[key] else { + guard let baseState = store.states[key] else { return nil } @@ -616,7 +617,7 @@ private extension StoreContext { } func lookupCache(of atom: Node, for key: AtomKey) -> AtomCache? { - guard let baseCache = store.state.caches[key] else { + guard let baseCache = store.caches[key] else { return nil } diff --git a/Sources/Atoms/Core/StoreState.swift b/Sources/Atoms/Core/StoreState.swift deleted file mode 100644 index c67e5313..00000000 --- a/Sources/Atoms/Core/StoreState.swift +++ /dev/null @@ -1,10 +0,0 @@ -@MainActor -internal final class StoreState { - var caches = [AtomKey: any AtomCacheProtocol]() - var states = [AtomKey: any AtomStateProtocol]() - var subscriptions = [AtomKey: [SubscriberKey: Subscription]]() - var subscribed = [SubscriberKey: Set]() - var scopes = [ScopeKey: Scope]() - - nonisolated init() {} -} diff --git a/Sources/Atoms/Core/TopologicalSort.swift b/Sources/Atoms/Core/TopologicalSort.swift index 0e3a1756..911e3be9 100644 --- a/Sources/Atoms/Core/TopologicalSort.swift +++ b/Sources/Atoms/Core/TopologicalSort.swift @@ -19,13 +19,13 @@ internal extension AtomStore { var redundantDependencies = [Vertex: ContiguousArray]() func traverse(key: AtomKey, isRedundant: Bool) { - if let children = graph.children[key] { + if let children = children[key] { for child in children { traverse(key: child, from: key, isRedundant: isRedundant) } } - if let subscriptions = state.subscriptions[key] { + if let subscriptions = subscriptions[key] { for subscriberKey in subscriptions.keys { traverse(key: subscriberKey, from: key, isRedundant: isRedundant) } diff --git a/Sources/Atoms/Snapshot.swift b/Sources/Atoms/Snapshot.swift index dbfcee0b..2a8d1639 100644 --- a/Sources/Atoms/Snapshot.swift +++ b/Sources/Atoms/Snapshot.swift @@ -1,15 +1,18 @@ /// A snapshot structure that captures specific set of values of atoms and their dependency graph. public struct Snapshot: CustomStringConvertible { - internal let graph: Graph + internal let dependencies: [AtomKey: Set] + internal let children: [AtomKey: Set] internal let caches: [AtomKey: any AtomCacheProtocol] internal let subscriptions: [AtomKey: [SubscriberKey: Subscription]] internal init( - graph: Graph, + dependencies: [AtomKey: Set], + children: [AtomKey: Set], caches: [AtomKey: any AtomCacheProtocol], subscriptions: [AtomKey: [SubscriberKey: Subscription]] ) { - self.graph = graph + self.dependencies = dependencies + self.children = children self.caches = caches self.subscriptions = subscriptions } @@ -18,7 +21,8 @@ public struct Snapshot: CustomStringConvertible { public var description: String { """ Snapshot - - graph: \(graph) + - dependencies: \(dependencies) + - children: \(children) - caches: \(caches) """ } @@ -71,7 +75,7 @@ public struct Snapshot: CustomStringConvertible { for key in caches.keys { statements.insert(key.description.quoted) - if let children = graph.children[key] { + if let children = children[key] { for child in children { statements.insert("\(key.description.quoted) -> \(child.description.quoted)") } diff --git a/Tests/AtomsTests/Attribute/KeepAliveTests.swift b/Tests/AtomsTests/Attribute/KeepAliveTests.swift index 85977bb4..b0a105d7 100644 --- a/Tests/AtomsTests/Attribute/KeepAliveTests.swift +++ b/Tests/AtomsTests/Attribute/KeepAliveTests.swift @@ -31,10 +31,10 @@ final class KeepAliveTests: XCTestCase { let subscriber = Subscriber(subscriberState) _ = context.watch(atom, subscriber: subscriber, subscription: Subscription()) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) context.unwatch(atom, subscriber: subscriber) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) } XCTContext.runActivity(named: "Should not be released when not scoped") { _ in @@ -47,10 +47,10 @@ final class KeepAliveTests: XCTestCase { let subscriber = Subscriber(subscriberState) _ = context.watch(atom, subscriber: subscriber, subscription: Subscription()) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) context.unwatch(atom, subscriber: subscriber) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) } XCTContext.runActivity(named: "Should not be released until scope is released when overridden in scope") { _ in @@ -75,13 +75,13 @@ final class KeepAliveTests: XCTestCase { scopedContext.registerScope(state: scopeState) _ = scopedContext.watch(atom, subscriber: subscriber, subscription: Subscription()) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) scopedContext.unwatch(atom, subscriber: subscriber) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) scopeState = nil - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.caches[key]) } XCTContext.runActivity(named: "Should not be released until scope is released when scoped") { _ in @@ -101,13 +101,13 @@ final class KeepAliveTests: XCTestCase { scopedContext.registerScope(state: scopeState) _ = scopedContext.watch(atom, subscriber: subscriber, subscription: Subscription()) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) scopedContext.unwatch(atom, subscriber: subscriber) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) scopeState = nil - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.caches[key]) } XCTContext.runActivity(named: "Should be released when scope is already released when scoped") { _ in @@ -128,10 +128,10 @@ final class KeepAliveTests: XCTestCase { scopeState = nil _ = scopedContext.watch(atom, subscriber: subscriber, subscription: Subscription()) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) scopedContext.unwatch(atom, subscriber: subscriber) - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.caches[key]) } } } diff --git a/Tests/AtomsTests/Attribute/RefreshableTests.swift b/Tests/AtomsTests/Attribute/RefreshableTests.swift index 41a0d213..3e99e1d1 100644 --- a/Tests/AtomsTests/Attribute/RefreshableTests.swift +++ b/Tests/AtomsTests/Attribute/RefreshableTests.swift @@ -30,8 +30,8 @@ final class RefreshableTests: XCTestCase { let value0 = await context.refresh(atom) XCTAssertEqual(value0, 1) - XCTAssertNil(store.state.caches[key]) - XCTAssertNil(store.state.states[key]) + XCTAssertNil(store.caches[key]) + XCTAssertNil(store.states[key]) XCTAssertTrue(snapshots.isEmpty) var updateCount = 0 @@ -48,8 +48,8 @@ final class RefreshableTests: XCTestCase { snapshots.removeAll() let value2 = await context.refresh(atom) XCTAssertEqual(value2, 1) - XCTAssertNotNil(store.state.states[key]) - XCTAssertEqual((store.state.caches[key] as? AtomCache>)?.value, 1) + XCTAssertNotNil(store.states[key]) + XCTAssertEqual((store.caches[key] as? AtomCache>)?.value, 1) XCTAssertEqual(updateCount, 1) XCTAssertEqual( snapshots.map { $0.caches.mapValues { $0.value as? Int } }, @@ -79,9 +79,9 @@ final class RefreshableTests: XCTestCase { let value1 = await scopedContext.refresh(atom) XCTAssertEqual(value1, 1) - XCTAssertNotNil(store.state.states[overrideAtomKey]) + XCTAssertNotNil(store.states[overrideAtomKey]) XCTAssertEqual( - (store.state.caches[overrideAtomKey] as? AtomCache>)?.value, + (store.caches[overrideAtomKey] as? AtomCache>)?.value, 1 ) } @@ -92,8 +92,8 @@ final class RefreshableTests: XCTestCase { let value = await context.refresh(atom) XCTAssertEqual(value, 1) - XCTAssertNil(store.state.states[key]) - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.states[key]) + XCTAssertNil(store.caches[key]) } } diff --git a/Tests/AtomsTests/Attribute/ResettableTests.swift b/Tests/AtomsTests/Attribute/ResettableTests.swift index 26bfb989..2a11019a 100644 --- a/Tests/AtomsTests/Attribute/ResettableTests.swift +++ b/Tests/AtomsTests/Attribute/ResettableTests.swift @@ -95,15 +95,15 @@ final class ResettableTests: XCTestCase { XCTAssertEqual(scopedContext.read(atom), 2) XCTAssertEqual(counter, Counter(value: 0, update: 0, reset: 1)) - XCTAssertNotNil(store.state.states[overrideAtomKey]) - XCTAssertEqual((store.state.caches[overrideAtomKey] as? AtomCache>)?.value, 2) + XCTAssertNotNil(store.states[overrideAtomKey]) + XCTAssertEqual((store.caches[overrideAtomKey] as? AtomCache>)?.value, 2) } XCTContext.runActivity(named: "Should not make new state and cache") { _ in context.reset(atom) - XCTAssertNil(store.state.states[key]) - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.states[key]) + XCTAssertNil(store.caches[key]) } } diff --git a/Tests/AtomsTests/Attribute/ScopedTests.swift b/Tests/AtomsTests/Attribute/ScopedTests.swift index 09042c17..e6e3a288 100644 --- a/Tests/AtomsTests/Attribute/ScopedTests.swift +++ b/Tests/AtomsTests/Attribute/ScopedTests.swift @@ -41,25 +41,25 @@ final class ScopedTests: XCTestCase { 0 ) XCTAssertEqual( - store.state.caches[scoped1AtomKey] as? AtomCache>, + store.caches[scoped1AtomKey] as? AtomCache>, AtomCache(atom: atom, value: 0) ) - XCTAssertNil(store.state.caches[scoped2AtomKey]) + XCTAssertNil(store.caches[scoped2AtomKey]) scoped1Context.unwatch(atom, subscriber: subscriber) - XCTAssertNil(store.state.caches[scoped1AtomKey]) + XCTAssertNil(store.caches[scoped1AtomKey]) XCTAssertEqual( scoped2Context.watch(atom, subscriber: subscriber, subscription: Subscription()), 0 ) XCTAssertEqual( - store.state.caches[scoped2AtomKey] as? AtomCache>, + store.caches[scoped2AtomKey] as? AtomCache>, AtomCache(atom: atom, value: 0) ) scoped2Context.unwatch(atom, subscriber: subscriber) - XCTAssertNil(store.state.caches[scoped2AtomKey]) + XCTAssertNil(store.caches[scoped2AtomKey]) } XCTContext.runActivity(named: "Should be scoped in particular scope") { _ in @@ -84,13 +84,13 @@ final class ScopedTests: XCTestCase { 0 ) XCTAssertEqual( - store.state.caches[scoped1AtomKey] as? AtomCache>, + store.caches[scoped1AtomKey] as? AtomCache>, AtomCache(atom: atom, value: 0) ) - XCTAssertNil(store.state.caches[scoped2AtomKey]) + XCTAssertNil(store.caches[scoped2AtomKey]) scoped2Context.unwatch(atom, subscriber: subscriber) - XCTAssertNil(store.state.caches[scoped1AtomKey]) + XCTAssertNil(store.caches[scoped1AtomKey]) } XCTContext.runActivity(named: "Modified atoms should also be scoped") { _ in @@ -120,21 +120,21 @@ final class ScopedTests: XCTestCase { 0 ) XCTAssertEqual( - (store.state.caches[scoped1BaseAtomKey] as? AtomCache>)?.value, + (store.caches[scoped1BaseAtomKey] as? AtomCache>)?.value, 0 ) XCTAssertEqual( - (store.state.caches[scoped1AtomKey] as? AtomCache, ChangesModifier>>)?.value, + (store.caches[scoped1AtomKey] as? AtomCache, ChangesModifier>>)?.value, 0 ) - XCTAssertNil(store.state.caches[scoped2BaseAtomKey]) - XCTAssertNil(store.state.caches[scoped2AtomKey]) - XCTAssertNil(store.state.caches[baseAtomKey]) - XCTAssertNil(store.state.caches[atomKey]) + XCTAssertNil(store.caches[scoped2BaseAtomKey]) + XCTAssertNil(store.caches[scoped2AtomKey]) + XCTAssertNil(store.caches[baseAtomKey]) + XCTAssertNil(store.caches[atomKey]) scoped2Context.unwatch(atom, subscriber: subscriber) - XCTAssertNil(store.state.caches[scoped1BaseAtomKey]) - XCTAssertNil(store.state.caches[scoped1AtomKey]) + XCTAssertNil(store.caches[scoped1BaseAtomKey]) + XCTAssertNil(store.caches[scoped1AtomKey]) } } } diff --git a/Tests/AtomsTests/Context/AtomTransactionContextTests.swift b/Tests/AtomsTests/Context/AtomTransactionContextTests.swift index 679b08b6..9cf62493 100644 --- a/Tests/AtomsTests/Context/AtomTransactionContextTests.swift +++ b/Tests/AtomsTests/Context/AtomTransactionContextTests.swift @@ -155,7 +155,7 @@ final class AtomTransactionContextTests: XCTestCase { let value = context.watch(atom1) XCTAssertEqual(value, 200) - XCTAssertEqual(store.graph.children, [AtomKey(atom1): [AtomKey(atom0)]]) - XCTAssertEqual(store.graph.dependencies, [AtomKey(atom0): [AtomKey(atom1)]]) + XCTAssertEqual(store.children, [AtomKey(atom1): [AtomKey(atom0)]]) + XCTAssertEqual(store.dependencies, [AtomKey(atom0): [AtomKey(atom1)]]) } } diff --git a/Tests/AtomsTests/Context/AtomViewContextTests.swift b/Tests/AtomsTests/Context/AtomViewContextTests.swift index 9b0a1dd8..9d6e6b02 100644 --- a/Tests/AtomsTests/Context/AtomViewContextTests.swift +++ b/Tests/AtomsTests/Context/AtomViewContextTests.swift @@ -199,22 +199,22 @@ final class AtomViewContextTests: XCTestCase { let key1 = AtomKey(atom1) let key2 = AtomKey(atom2) - let graph = Graph( - dependencies: [key0: [key1, key2]], - children: [key1: [key0], key2: [key0]] - ) + let dependencies: [AtomKey: Set] = [key0: [key1, key2]] + let children: [AtomKey: Set] = [key1: [key0], key2: [key0]] let caches = [ key0: AtomCache(atom: atom0, value: 0), key1: AtomCache(atom: atom1, value: 1), key2: AtomCache(atom: atom2, value: 2), ] - store.graph = graph - store.state.caches = caches + store.dependencies = dependencies + store.children = children + store.caches = caches let snapshot = context.snapshot() - XCTAssertEqual(snapshot.graph, graph) + XCTAssertEqual(snapshot.dependencies, dependencies) + XCTAssertEqual(snapshot.children, children) XCTAssertEqual( snapshot.caches.mapValues { $0 as? AtomCache> }, caches @@ -235,9 +235,9 @@ final class AtomViewContextTests: XCTestCase { ) context.watch(atom) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) subscriberState = nil - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.caches[key]) } } diff --git a/Tests/AtomsTests/Core/EnvironmentTests.swift b/Tests/AtomsTests/Core/EnvironmentTests.swift index 91292cc0..91fc3c0f 100644 --- a/Tests/AtomsTests/Core/EnvironmentTests.swift +++ b/Tests/AtomsTests/Core/EnvironmentTests.swift @@ -11,7 +11,7 @@ final class EnvironmentTests: XCTestCase { let atom = TestValueAtom(value: 0) var environment = EnvironmentValues() - store.state.caches = [AtomKey(atom): AtomCache(atom: atom, value: 100)] + store.caches = [AtomKey(atom): AtomCache(atom: atom, value: 100)] environment.store = .root(store: store, scopeKey: scopeToken.key) XCTAssertEqual(environment.store?.read(atom), 100) diff --git a/Tests/AtomsTests/Core/StoreContextTests.swift b/Tests/AtomsTests/Core/StoreContextTests.swift index 6612ad8c..d6c97058 100644 --- a/Tests/AtomsTests/Core/StoreContextTests.swift +++ b/Tests/AtomsTests/Core/StoreContextTests.swift @@ -20,16 +20,16 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual(context.read(atom), 0) - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.caches[key]) XCTAssertTrue(snapshots.isEmpty) snapshots.removeAll() - store.graph.children[key] = [AtomKey(TestAtom(value: 1))] + store.children[key] = [AtomKey(TestAtom(value: 1))] XCTAssertEqual(context.read(atom), 0) XCTAssertTrue(snapshots.isEmpty) snapshots.removeAll() - store.state.caches[key] = AtomCache(atom: atom, value: 1) + store.caches[key] = AtomCache(atom: atom, value: 1) XCTAssertEqual(context.read(atom), 1) XCTAssertTrue(snapshots.isEmpty) } @@ -53,14 +53,14 @@ final class StoreContextTests: XCTestCase { context.set(1, for: atom) XCTAssertEqual(updateCount, 0) - XCTAssertNil(store.state.states[key]) - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.states[key]) + XCTAssertNil(store.caches[key]) XCTAssertTrue(snapshots.isEmpty) snapshots.removeAll() - store.state.caches[key] = AtomCache(atom: atom, value: 0) - store.state.states[key] = AtomState(effect: TestEffect()) - store.state.subscriptions[key, default: [:]][subscriberToken.key] = Subscription( + store.caches[key] = AtomCache(atom: atom, value: 0) + store.states[key] = AtomState(effect: TestEffect()) + store.subscriptions[key, default: [:]][subscriberToken.key] = Subscription( location: SourceLocation(), update: { updateCount += 1 } ) @@ -70,15 +70,15 @@ final class StoreContextTests: XCTestCase { [[key: 2]] ) XCTAssertEqual(updateCount, 1) - XCTAssertNil(store.state.states[key]?.transactionState) - XCTAssertEqual((store.state.caches[key] as? AtomCache>)?.value, 2) + XCTAssertNil(store.states[key]?.transactionState) + XCTAssertEqual((store.caches[key] as? AtomCache>)?.value, 2) snapshots.removeAll() context.set(3, for: atom) XCTAssertEqual(updateCount, 2) - XCTAssertNotNil(store.state.states[key]) - XCTAssertNil(store.state.states[key]?.transactionState) - XCTAssertEqual((store.state.caches[key] as? AtomCache>)?.value, 3) + XCTAssertNotNil(store.states[key]) + XCTAssertNil(store.states[key]?.transactionState) + XCTAssertEqual((store.caches[key] as? AtomCache>)?.value, 3) XCTAssertEqual( snapshots.map { $0.caches.mapValues { $0.value as? Int } }, [[key: 3]] @@ -104,21 +104,21 @@ final class StoreContextTests: XCTestCase { context.modify(atom) { $0 = 1 } XCTAssertEqual(updateCount, 0) - XCTAssertNil(store.state.states[key]) - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.states[key]) + XCTAssertNil(store.caches[key]) XCTAssertTrue(snapshots.isEmpty) snapshots.removeAll() - store.state.caches[key] = AtomCache(atom: atom, value: 0) - store.state.states[key] = AtomState(effect: TestEffect()) - store.state.subscriptions[key, default: [:]][subscriberToken.key] = Subscription( + store.caches[key] = AtomCache(atom: atom, value: 0) + store.states[key] = AtomState(effect: TestEffect()) + store.subscriptions[key, default: [:]][subscriberToken.key] = Subscription( location: SourceLocation(), update: { updateCount += 1 } ) context.modify(atom) { $0 = 2 } XCTAssertEqual(updateCount, 1) - XCTAssertNil(store.state.states[key]?.transactionState) - XCTAssertEqual((store.state.caches[key] as? AtomCache>)?.value, 2) + XCTAssertNil(store.states[key]?.transactionState) + XCTAssertEqual((store.caches[key] as? AtomCache>)?.value, 2) XCTAssertEqual( snapshots.map { $0.caches.mapValues { $0.value as? Int } }, [[key: 2]] @@ -127,9 +127,9 @@ final class StoreContextTests: XCTestCase { snapshots.removeAll() context.modify(atom) { $0 = 3 } XCTAssertEqual(updateCount, 2) - XCTAssertNotNil(store.state.states[key]) - XCTAssertNil(store.state.states[key]?.transactionState) - XCTAssertEqual((store.state.caches[key] as? AtomCache>)?.value, 3) + XCTAssertNotNil(store.states[key]) + XCTAssertNil(store.states[key]?.transactionState) + XCTAssertEqual((store.caches[key] as? AtomCache>)?.value, 3) XCTAssertEqual( snapshots.map { $0.caches.mapValues { $0.value as? Int } }, [[key: 3]] @@ -157,29 +157,19 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual(context.watch(dependency0, in: transactionState), 0) - XCTAssertEqual( - store.graph, - Graph( - dependencies: [key: [dependency0Key]], - children: [dependency0Key: [key]] - ) - ) - XCTAssertEqual((store.state.caches[dependency0Key] as? AtomCache>)?.value, 0) - XCTAssertNotNil(store.state.states[dependency0Key]) + XCTAssertEqual(store.dependencies, [key: [dependency0Key]]) + XCTAssertEqual(store.children, [dependency0Key: [key]]) + XCTAssertEqual((store.caches[dependency0Key] as? AtomCache>)?.value, 0) + XCTAssertNotNil(store.states[dependency0Key]) XCTAssertTrue(snapshots.flatMap(\.caches).isEmpty) transactionState.terminate() XCTAssertEqual(context.watch(dependency1, in: transactionState), 1) - XCTAssertEqual( - store.graph, - Graph( - dependencies: [key: [dependency0Key]], - children: [dependency0Key: [key]] - ) - ) - XCTAssertNil(store.state.caches[dependency1Key]) - XCTAssertNil(store.state.states[dependency1Key]) + XCTAssertEqual(store.dependencies, [key: [dependency0Key]]) + XCTAssertEqual(store.children, [dependency0Key: [key]]) + XCTAssertNil(store.caches[dependency1Key]) + XCTAssertNil(store.states[dependency1Key]) XCTAssertTrue(snapshots.isEmpty) } @@ -224,27 +214,27 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual(initialValue, 0) - XCTAssertTrue(store.state.subscribed[subscriber.key]?.contains(key) ?? false) - XCTAssertNotNil(store.state.subscriptions[key]?[subscriber.key]) - XCTAssertEqual((store.state.caches[key] as? AtomCache)?.value, 0) - XCTAssertEqual((store.state.caches[dependencyKey] as? AtomCache)?.value, 0) + XCTAssertTrue(store.subscribed[subscriber.key]?.contains(key) ?? false) + XCTAssertNotNil(store.subscriptions[key]?[subscriber.key]) + XCTAssertEqual((store.caches[key] as? AtomCache)?.value, 0) + XCTAssertEqual((store.caches[dependencyKey] as? AtomCache)?.value, 0) XCTAssertEqual( snapshots.map { $0.caches.mapValues { $0.value as? Int } }, [[key: 0, dependencyKey: 0]] ) snapshots.removeAll() - store.state.subscriptions[key]?[subscriber.key]?.update() + store.subscriptions[key]?[subscriber.key]?.update() subscriberState = nil XCTAssertEqual(updateCount, 1) - XCTAssertNil(store.state.subscribed[subscriber.key]) - XCTAssertNil(store.state.caches[key]) - XCTAssertNil(store.state.states[key]) - XCTAssertNil(store.state.subscriptions[key]) - XCTAssertNil(store.state.caches[dependencyKey]) - XCTAssertNil(store.state.states[dependencyKey]) - XCTAssertNil(store.state.subscriptions[dependencyKey]) + XCTAssertNil(store.subscribed[subscriber.key]) + XCTAssertNil(store.caches[key]) + XCTAssertNil(store.states[key]) + XCTAssertNil(store.subscriptions[key]) + XCTAssertNil(store.caches[dependencyKey]) + XCTAssertNil(store.states[dependencyKey]) + XCTAssertNil(store.subscriptions[dependencyKey]) XCTAssertEqual( snapshots.map { $0.caches.mapValues { $0.value as? Int } }, [[:]] @@ -270,8 +260,8 @@ final class StoreContextTests: XCTestCase { let phase0 = await context.refresh(atom) XCTAssertEqual(phase0.value, 0) - XCTAssertNil(store.state.caches[key]) - XCTAssertNil(store.state.states[key]) + XCTAssertNil(store.caches[key]) + XCTAssertNil(store.states[key]) XCTAssertTrue(snapshots.isEmpty) var updateCount = 0 @@ -289,8 +279,8 @@ final class StoreContextTests: XCTestCase { let phase2 = await context.refresh(atom) XCTAssertEqual(phase2.value, 0) - XCTAssertNotNil(store.state.states[key]) - XCTAssertEqual((store.state.caches[key] as? AtomCache>>)?.value, .success(0)) + XCTAssertNotNil(store.states[key]) + XCTAssertEqual((store.caches[key] as? AtomCache>>)?.value, .success(0)) XCTAssertEqual(updateCount, 1) XCTAssertEqual( snapshots.map { $0.caches.mapValues { $0.value as? AsyncPhase } }, @@ -314,9 +304,9 @@ final class StoreContextTests: XCTestCase { let phase4 = await scopedContext.refresh(atom) XCTAssertEqual(phase4.value, 1) - XCTAssertNotNil(store.state.states[overrideAtomKey]) + XCTAssertNotNil(store.states[overrideAtomKey]) XCTAssertEqual( - (store.state.caches[overrideAtomKey] as? AtomCache>>)?.value, + (store.caches[overrideAtomKey] as? AtomCache>>)?.value, .success(1) ) } @@ -362,8 +352,8 @@ final class StoreContextTests: XCTestCase { ) context.reset(atom) - XCTAssertNil(store.state.caches[key]) - XCTAssertNil(store.state.states[key]) + XCTAssertNil(store.caches[key]) + XCTAssertNil(store.states[key]) XCTAssertTrue(snapshots.isEmpty) var updateCount = 0 @@ -404,24 +394,24 @@ final class StoreContextTests: XCTestCase { _ = context.watch(atom, subscriber: subscriber, subscription: Subscription()) XCTAssertEqual( - store.state.caches.mapValues { $0.value as? Int }, + store.caches.mapValues { $0.value as? Int }, [AtomKey(atom): 0] ) XCTAssertEqual( - store.state.subscribed, + store.subscribed, [subscriber.key: [AtomKey(atom)]] ) context.unwatch(atom, subscriber: subscriber) XCTAssertEqual( - store.state.caches.mapValues { $0.value as? Int }, + store.caches.mapValues { $0.value as? Int }, [:] ) XCTAssertEqual( - store.state.subscribed, + store.subscribed, [subscriber.key: []] ) } @@ -436,24 +426,24 @@ final class StoreContextTests: XCTestCase { let atom1 = TestAtom(value: 1) let key0 = AtomKey(atom0) let key1 = AtomKey(atom1) - let graph = Graph( - dependencies: [key0: [key1]], - children: [key1: [key0]] - ) + let dependencies: [AtomKey: Set] = [key0: [key1]] + let children: [AtomKey: Set] = [key1: [key0]] let caches = [ key0: AtomCache(atom: atom0, value: 0), key1: AtomCache(atom: atom1, value: 1), ] let subscription = Subscription() - store.graph = graph - store.state.caches = caches - store.state.subscriptions[key0, default: [:]][subscriberToken.key] = subscription - store.state.subscriptions[key1, default: [:]][subscriberToken.key] = subscription + store.dependencies = dependencies + store.children = children + store.caches = caches + store.subscriptions[key0, default: [:]][subscriberToken.key] = subscription + store.subscriptions[key1, default: [:]][subscriberToken.key] = subscription let snapshot = context.snapshot() - XCTAssertEqual(snapshot.graph, graph) + XCTAssertEqual(snapshot.dependencies, dependencies) + XCTAssertEqual(snapshot.children, children) XCTAssertEqual( snapshot.caches.mapValues { $0 as? AtomCache> }, caches @@ -503,7 +493,7 @@ final class StoreContextTests: XCTestCase { XCTAssertEqual(scoped2Context.watch(atom0, subscriber: subscriber, subscription: Subscription()), 30) XCTAssertEqual(scoped2Context.watch(atom1, subscriber: subscriber, subscription: Subscription()), 30) XCTAssertEqual( - store.state.caches.compactMapValues { $0 as? AtomCache> }, + store.caches.compactMapValues { $0 as? AtomCache> }, [ AtomKey(atom0): AtomCache(atom: atom0, value: 10), AtomKey(atom1, scopeKey: scope1Token.key): AtomCache(atom: atom1, value: 20), @@ -663,39 +653,39 @@ final class StoreContextTests: XCTestCase { XCTAssertEqual(scoped2Context.watch(atom, in: transactionState), 21) XCTAssertEqual( - store.graph, - Graph( - dependencies: [ - AtomKey(transactionAtom): [ - AtomKey(atom) - ], - AtomKey(atom): [ - AtomKey(dependency1Atom), - AtomKey(dependency2Atom, scopeKey: scope2Token.key), - ], - AtomKey(publisherAtom): [ - AtomKey(dependency1Atom), - AtomKey(dependency2Atom, scopeKey: scope2Token.key), - ], + store.dependencies, + [ + AtomKey(transactionAtom): [ + AtomKey(atom) ], - children: [ - AtomKey(atom): [ - AtomKey(transactionAtom) - ], - AtomKey(dependency1Atom): [ - AtomKey(atom), - AtomKey(publisherAtom), - ], - AtomKey(dependency2Atom, scopeKey: scope2Token.key): [ - AtomKey(atom), - AtomKey(publisherAtom), - ], - ] - ) + AtomKey(atom): [ + AtomKey(dependency1Atom), + AtomKey(dependency2Atom, scopeKey: scope2Token.key), + ], + AtomKey(publisherAtom): [ + AtomKey(dependency1Atom), + AtomKey(dependency2Atom, scopeKey: scope2Token.key), + ], + ] ) - XCTAssertEqual( - store.state.caches.mapValues { $0 as? AtomCache }, + store.children, + [ + AtomKey(atom): [ + AtomKey(transactionAtom) + ], + AtomKey(dependency1Atom): [ + AtomKey(atom), + AtomKey(publisherAtom), + ], + AtomKey(dependency2Atom, scopeKey: scope2Token.key): [ + AtomKey(atom), + AtomKey(publisherAtom), + ], + ] + ) + XCTAssertEqual( + store.caches.mapValues { $0 as? AtomCache }, [ AtomKey(publisherAtom): nil, AtomKey(atom): AtomCache(atom: atom, value: 21), @@ -704,7 +694,7 @@ final class StoreContextTests: XCTestCase { ] ) XCTAssertEqual( - store.state.caches.mapValues { $0 as? AtomCache }, + store.caches.mapValues { $0 as? AtomCache }, [ AtomKey(publisherAtom): AtomCache(atom: publisherAtom, value: .success(21)), AtomKey(atom): nil, @@ -713,7 +703,7 @@ final class StoreContextTests: XCTestCase { ] ) XCTAssertEqual( - store.state.caches.mapValues { $0 as? AtomCache }, + store.caches.mapValues { $0 as? AtomCache }, [ AtomKey(publisherAtom): nil, AtomKey(atom): nil, @@ -722,7 +712,7 @@ final class StoreContextTests: XCTestCase { ] ) XCTAssertEqual( - store.state.caches.mapValues { $0 as? AtomCache }, + store.caches.mapValues { $0 as? AtomCache }, [ AtomKey(publisherAtom): nil, AtomKey(atom): nil, @@ -732,11 +722,11 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual( - store.state.subscribed, + store.subscribed, [ subscriber.key: [ - AtomKey((atom)), - AtomKey((publisherAtom)), + AtomKey(atom), + AtomKey(publisherAtom), ] ] ) @@ -839,10 +829,10 @@ final class StoreContextTests: XCTestCase { let subscriber = Subscriber(subscriberState) _ = context.watch(atom, subscriber: subscriber, subscription: Subscription()) - XCTAssertNotNil(store.state.caches[key]) + XCTAssertNotNil(store.caches[key]) context.unwatch(atom, subscriber: subscriber) - XCTAssertNil(store.state.caches[key]) + XCTAssertNil(store.caches[key]) } @MainActor @@ -1058,31 +1048,31 @@ final class StoreContextTests: XCTestCase { overrideContainer: OverrideContainer() ) - let expectedGraph = Graph( - dependencies: [ - AtomKey(TestAtom()): [ - AtomKey(TestDependency1Atom()), - AtomKey(TestDependency2Atom()), - AtomKey(TestDependency3Atom(), scopeKey: scopeToken.key), - ] - ], - children: [ - AtomKey(TestDependency1Atom()): [AtomKey(TestAtom())], - AtomKey(TestDependency2Atom()): [AtomKey(TestAtom())], - AtomKey(TestDependency3Atom(), scopeKey: scopeToken.key): [AtomKey(TestAtom())], + let expectedDependencies: [AtomKey: Set] = [ + AtomKey(TestAtom()): [ + AtomKey(TestDependency1Atom()), + AtomKey(TestDependency2Atom()), + AtomKey(TestDependency3Atom(), scopeKey: scopeToken.key), ] - ) + ] + let expectedChildren: [AtomKey: Set] = [ + AtomKey(TestDependency1Atom()): [AtomKey(TestAtom())], + AtomKey(TestDependency2Atom()): [AtomKey(TestAtom())], + AtomKey(TestDependency3Atom(), scopeKey: scopeToken.key): [AtomKey(TestAtom())], + ] // Initialize the atom state in the scoped context. _ = scopedContext.watch(atom, subscriber: subscriber, subscription: Subscription()) // The scoped observer should recive the update event. - XCTAssertEqual(snapshots.map(\.graph), [expectedGraph]) + XCTAssertEqual(snapshots.map(\.dependencies), [expectedDependencies]) + XCTAssertEqual(snapshots.map(\.children), [expectedChildren]) context.set(20, for: TestDependency2Atom()) // The scoped observer should recive the update event. - XCTAssertEqual(snapshots.map(\.graph), [expectedGraph, expectedGraph]) + XCTAssertEqual(snapshots.map(\.dependencies), [expectedDependencies, expectedDependencies]) + XCTAssertEqual(snapshots.map(\.children), [expectedChildren, expectedChildren]) } @MainActor @@ -1096,20 +1086,18 @@ final class StoreContextTests: XCTestCase { let location = SourceLocation() let subscriberToken = SubscriberKey.Token() - store.graph = Graph( - dependencies: [AtomKey(atom0): [AtomKey(atom1)]], - children: [AtomKey(atom1): [AtomKey(atom0)]] - ) - store.state.caches = [ + store.dependencies = [AtomKey(atom0): [AtomKey(atom1)]] + store.children = [AtomKey(atom1): [AtomKey(atom0)]] + store.caches = [ AtomKey(atom0): AtomCache(atom: atom0, value: 0), AtomKey(atom1): AtomCache(atom: atom1, value: 1), ] let snapshot = context.snapshot() - store.graph = Graph() - store.state = StoreState() - store.state.caches = [ + store.dependencies = [:] + store.children = [:] + store.caches = [ AtomKey(atom2): AtomCache(atom: atom2, value: 2) ] @@ -1118,7 +1106,7 @@ final class StoreContextTests: XCTestCase { let subscription1 = Subscription(location: location) { updated.insert(AtomKey(atom1)) } let subscription2 = Subscription(location: location) { updated.insert(AtomKey(atom2)) } - store.state.subscriptions = [ + store.subscriptions = [ AtomKey(atom0): [subscriberToken.key: subscription0], AtomKey(atom1): [subscriberToken.key: subscription1], AtomKey(atom2): [subscriberToken.key: subscription2], @@ -1128,16 +1116,12 @@ final class StoreContextTests: XCTestCase { // Notifies updated only for the subscriptions of the atoms that are restored. XCTAssertEqual(updated, [AtomKey(atom0), AtomKey(atom1)]) - XCTAssertEqual( - store.graph, - Graph( - dependencies: [AtomKey(atom0): [AtomKey(atom1)]], - children: [AtomKey(atom1): [AtomKey(atom0)]] - ) - ) + XCTAssertEqual(store.dependencies, [AtomKey(atom0): [AtomKey(atom1)]]) + XCTAssertEqual(store.children, [AtomKey(atom1): [AtomKey(atom0)]]) + // Do not delete caches added after the snapshot was taken. XCTAssertEqual( - store.state.caches.mapValues { $0 as? AtomCache> }, + store.caches.mapValues { $0 as? AtomCache> }, [ AtomKey(atom0): AtomCache(atom: atom0, value: 0), AtomKey(atom1): AtomCache(atom: atom1, value: 1), @@ -1146,16 +1130,17 @@ final class StoreContextTests: XCTestCase { ) // Restore with no subscriptions. - store.state.subscriptions.removeAll() + store.subscriptions.removeAll() context.restore(snapshot) - XCTAssertEqual(store.graph, Graph()) + XCTAssertEqual(store.dependencies, [:]) + XCTAssertEqual(store.children, [:]) // Caches added after the snapshot was taken are not forcibly released by restore, // but this is not a problem since the cache should originally be released // when the subscription is released. XCTAssertEqual( - store.state.caches.mapValues { $0 as? AtomCache> }, + store.caches.mapValues { $0 as? AtomCache> }, [ AtomKey(atom2): AtomCache(atom: atom2, value: 2) ] @@ -1177,7 +1162,7 @@ final class StoreContextTests: XCTestCase { _ = context.watch(atom, subscriber: subscriber, subscription: Subscription()) _ = context.watch(upstreamAtom, in: TransactionState(key: key)) - XCTAssertTrue((store.state.states[key]?.effect as? TestEffect) === effect) + XCTAssertTrue((store.states[key]?.effect as? TestEffect) === effect) XCTAssertEqual(effect.initializingCount, 1) XCTAssertEqual(effect.initializedCount, 1) XCTAssertEqual(effect.updatedCount, 0) @@ -1185,7 +1170,7 @@ final class StoreContextTests: XCTestCase { context.set(1, for: atom) - XCTAssertTrue((store.state.states[key]?.effect as? TestEffect) === effect) + XCTAssertTrue((store.states[key]?.effect as? TestEffect) === effect) XCTAssertEqual(effect.initializingCount, 1) XCTAssertEqual(effect.initializedCount, 1) XCTAssertEqual(effect.updatedCount, 1) @@ -1195,7 +1180,7 @@ final class StoreContextTests: XCTestCase { context.set(3, for: atom) context.set(4, for: atom) - XCTAssertTrue((store.state.states[key]?.effect as? TestEffect) === effect) + XCTAssertTrue((store.states[key]?.effect as? TestEffect) === effect) XCTAssertEqual(effect.initializingCount, 1) XCTAssertEqual(effect.initializedCount, 1) XCTAssertEqual(effect.updatedCount, 4) @@ -1203,7 +1188,7 @@ final class StoreContextTests: XCTestCase { context.set("Updated", for: upstreamAtom) - XCTAssertTrue((store.state.states[key]?.effect as? TestEffect) === effect) + XCTAssertTrue((store.states[key]?.effect as? TestEffect) === effect) XCTAssertEqual(effect.initializingCount, 1) XCTAssertEqual(effect.initializedCount, 1) XCTAssertEqual(effect.updatedCount, 5) @@ -1211,7 +1196,7 @@ final class StoreContextTests: XCTestCase { context.unwatch(atom, subscriber: subscriber) - XCTAssertNil(store.state.states[key]) + XCTAssertNil(store.states[key]) XCTAssertEqual(effect.initializingCount, 1) XCTAssertEqual(effect.initializedCount, 1) XCTAssertEqual(effect.updatedCount, 5) @@ -1219,7 +1204,7 @@ final class StoreContextTests: XCTestCase { context.set(5, for: atom) - XCTAssertNil(store.state.states[key]) + XCTAssertNil(store.states[key]) XCTAssertEqual(effect.initializingCount, 1) XCTAssertEqual(effect.initializedCount, 1) XCTAssertEqual(effect.updatedCount, 5) @@ -1775,7 +1760,7 @@ final class StoreContextTests: XCTestCase { wait(for: [expectation], timeout: 0.5) XCTAssertNil(context.lookup(TestAtom1())) XCTAssertNil(context.lookup(TestAtom2())) - XCTAssertTrue(store.state.subscriptions.isEmpty) + XCTAssertTrue(store.subscriptions.isEmpty) } } @@ -1888,13 +1873,13 @@ final class StoreContextTests: XCTestCase { let value = await atomStore.watch(atom, subscriber: subscriber, subscription: Subscription()).value XCTAssertEqual(value, 0) - XCTAssertNil(store.graph.children[AtomKey(d)]) + XCTAssertNil(store.children[AtomKey(d)]) XCTAssertEqual( - store.graph.dependencies[AtomKey(atom)], + store.dependencies[AtomKey(atom)], [AtomKey(phase), AtomKey(a), AtomKey(b), AtomKey(c)] ) - let state = store.state.states[AtomKey(atom)] + let state = store.states[AtomKey(atom)] // TestAtom's Task cancellation XCTAssertNotNil(state?.transactionState?.onTermination) @@ -1917,13 +1902,13 @@ final class StoreContextTests: XCTestCase { XCTAssertEqual(before, 0) XCTAssertEqual(after, 1) - XCTAssertNil(store.graph.children[AtomKey(c)]) + XCTAssertNil(store.children[AtomKey(c)]) XCTAssertEqual( - store.graph.dependencies[AtomKey(atom)], + store.dependencies[AtomKey(atom)], [AtomKey(phase), AtomKey(a), AtomKey(d), AtomKey(b)] ) - let state = store.state.states[AtomKey(atom)] + let state = store.states[AtomKey(atom)] // TestAtom's Task cancellation XCTAssertNotNil(state?.transactionState?.onTermination) @@ -1945,13 +1930,13 @@ final class StoreContextTests: XCTestCase { XCTAssertEqual(before, 1) XCTAssertEqual(after, 3) - XCTAssertNil(store.graph.children[AtomKey(a)]) + XCTAssertNil(store.children[AtomKey(a)]) XCTAssertEqual( - store.graph.dependencies[AtomKey(atom)], + store.dependencies[AtomKey(atom)], [AtomKey(phase), AtomKey(b), AtomKey(c), AtomKey(d)] ) - let state = store.state.states[AtomKey(atom)] + let state = store.states[AtomKey(atom)] // TestAtom's Task cancellation XCTAssertNotNil(state?.transactionState?.onTermination) @@ -1961,8 +1946,8 @@ final class StoreContextTests: XCTestCase { subscriberState = nil let key = AtomKey(atom) - XCTAssertNil(store.state.caches[key]) - XCTAssertNil(store.state.states[key]) + XCTAssertNil(store.caches[key]) + XCTAssertNil(store.states[key]) } } } diff --git a/Tests/AtomsTests/Core/TopologicalSortTests.swift b/Tests/AtomsTests/Core/TopologicalSortTests.swift index b9d4355f..53c77fc8 100644 --- a/Tests/AtomsTests/Core/TopologicalSortTests.swift +++ b/Tests/AtomsTests/Core/TopologicalSortTests.swift @@ -12,19 +12,17 @@ final class TopologicalSortTests: XCTestCase { let key3 = AtomKey(TestAtom(value: 3)) let token = SubscriberKey.Token() - store.graph = Graph( - dependencies: [ - key1: [key0], - key2: [key0, key1], - key3: [key2], - ], - children: [ - key0: [key1, key2], - key1: [key2], - key2: [key3], - ] - ) - store.state.subscriptions = [ + store.dependencies = [ + key1: [key0], + key2: [key0, key1], + key3: [key2], + ] + store.children = [ + key0: [key1, key2], + key1: [key2], + key2: [key3], + ] + store.subscriptions = [ key2: [token.key: Subscription()], key3: [token.key: Subscription()], ] diff --git a/Tests/AtomsTests/SnapshotTests.swift b/Tests/AtomsTests/SnapshotTests.swift index adc39028..0e9350fc 100644 --- a/Tests/AtomsTests/SnapshotTests.swift +++ b/Tests/AtomsTests/SnapshotTests.swift @@ -11,7 +11,8 @@ final class SnapshotTests: XCTestCase { AtomKey(atom0): AtomCache(atom: atom0, value: 0) ] let snapshot = Snapshot( - graph: Graph(), + dependencies: [:], + children: [:], caches: atomCache, subscriptions: [:] ) @@ -22,7 +23,8 @@ final class SnapshotTests: XCTestCase { func testEmptyGraphDescription() { let snapshot = Snapshot( - graph: Graph(), + dependencies: [:], + children: [:], caches: [:], subscriptions: [:] ) @@ -56,18 +58,16 @@ final class SnapshotTests: XCTestCase { update: {} ) let snapshot = Snapshot( - graph: Graph( - dependencies: [ - key1: [key0], - key2: [key1], - key3: [key2], - ], - children: [ - key0: [key1], - key1: [key2], - key2: [key3], - ] - ), + dependencies: [ + key1: [key0], + key2: [key1], + key3: [key2], + ], + children: [ + key0: [key1], + key1: [key2], + key2: [key3], + ], caches: [ key0: AtomCache(atom: atom0, value: Value0()), key1: AtomCache(atom: atom1, value: Value1()), From ff36434a3ebe6b067ff6af2cbf5e99ba0943a3bb Mon Sep 17 00:00:00 2001 From: ra1028 Date: Wed, 4 Jun 2025 18:32:03 +0900 Subject: [PATCH 2/2] Refactoring --- Sources/Atoms/AtomStore.swift | 2 +- Sources/Atoms/Core/StoreContext.swift | 6 +++--- Tests/AtomsTests/Core/StoreContextTests.swift | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Atoms/AtomStore.swift b/Sources/Atoms/AtomStore.swift index d9ef3a7b..770da5e1 100644 --- a/Sources/Atoms/AtomStore.swift +++ b/Sources/Atoms/AtomStore.swift @@ -6,7 +6,7 @@ public final class AtomStore { internal var caches = [AtomKey: any AtomCacheProtocol]() internal var states = [AtomKey: any AtomStateProtocol]() internal var subscriptions = [AtomKey: [SubscriberKey: Subscription]]() - internal var subscribed = [SubscriberKey: Set]() + internal var subscribes = [SubscriberKey: Set]() internal var scopes = [ScopeKey: Scope]() /// Creates a new store. diff --git a/Sources/Atoms/Core/StoreContext.swift b/Sources/Atoms/Core/StoreContext.swift index 8bd3548b..299fa80d 100644 --- a/Sources/Atoms/Core/StoreContext.swift +++ b/Sources/Atoms/Core/StoreContext.swift @@ -113,7 +113,7 @@ internal struct StoreContext { let (key, override) = lookupAtomKeyAndOverride(of: atom) let cache = lookupCache(of: atom, for: key) let value = cache?.value ?? initialize(of: atom, for: key, override: override) - let isNewSubscription = store.subscribed[subscriber.key, default: []].insert(key).inserted + let isNewSubscription = store.subscribes[subscriber.key, default: []].insert(key).inserted if isNewSubscription { store.subscriptions[key, default: [:]][subscriber.key] = subscription @@ -224,7 +224,7 @@ internal struct StoreContext { func unwatch(_ atom: some Atom, subscriber: Subscriber) { let (key, _) = lookupAtomKeyAndOverride(of: atom) - store.subscribed[subscriber.key]?.remove(key) + store.subscribes[subscriber.key]?.remove(key) unsubscribe([key], for: subscriber.key) } @@ -510,7 +510,7 @@ private extension StoreContext { } func unsubscribeAll(for subscriberKey: SubscriberKey) { - let keys = store.subscribed.removeValue(forKey: subscriberKey) + let keys = store.subscribes.removeValue(forKey: subscriberKey) if let keys { unsubscribe(keys, for: subscriberKey) diff --git a/Tests/AtomsTests/Core/StoreContextTests.swift b/Tests/AtomsTests/Core/StoreContextTests.swift index d6c97058..cd78c334 100644 --- a/Tests/AtomsTests/Core/StoreContextTests.swift +++ b/Tests/AtomsTests/Core/StoreContextTests.swift @@ -214,7 +214,7 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual(initialValue, 0) - XCTAssertTrue(store.subscribed[subscriber.key]?.contains(key) ?? false) + XCTAssertTrue(store.subscribes[subscriber.key]?.contains(key) ?? false) XCTAssertNotNil(store.subscriptions[key]?[subscriber.key]) XCTAssertEqual((store.caches[key] as? AtomCache)?.value, 0) XCTAssertEqual((store.caches[dependencyKey] as? AtomCache)?.value, 0) @@ -228,7 +228,7 @@ final class StoreContextTests: XCTestCase { subscriberState = nil XCTAssertEqual(updateCount, 1) - XCTAssertNil(store.subscribed[subscriber.key]) + XCTAssertNil(store.subscribes[subscriber.key]) XCTAssertNil(store.caches[key]) XCTAssertNil(store.states[key]) XCTAssertNil(store.subscriptions[key]) @@ -399,7 +399,7 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual( - store.subscribed, + store.subscribes, [subscriber.key: [AtomKey(atom)]] ) @@ -411,7 +411,7 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual( - store.subscribed, + store.subscribes, [subscriber.key: []] ) } @@ -722,7 +722,7 @@ final class StoreContextTests: XCTestCase { ) XCTAssertEqual( - store.subscribed, + store.subscribes, [ subscriber.key: [ AtomKey(atom),