@@ -83,7 +83,7 @@ internal struct StoreContext {
8383 let key = AtomKey ( atom, scopeKey: scopeKey)
8484
8585 if let cache = lookupCache ( of: atom, for: key) {
86- update ( atom: atom, for: key, newValue: value, cache : cache)
86+ update ( atom: atom, for: key, newValue: value, oldValue : cache. value )
8787 }
8888 }
8989
@@ -95,7 +95,7 @@ internal struct StoreContext {
9595
9696 if let cache = lookupCache ( of: atom, for: key) {
9797 let newValue = mutating ( cache. value, body)
98- update ( atom: atom, for: key, newValue: newValue, cache : cache)
98+ update ( atom: atom, for: key, newValue: newValue, oldValue : cache. value )
9999 }
100100 }
101101
@@ -127,9 +127,9 @@ internal struct StoreContext {
127127 let scopeKey = lookupScopeKey ( of: atom, isScopedOverriden: override? . isScoped ?? false )
128128 let key = AtomKey ( atom, scopeKey: scopeKey)
129129 let cache = getCache ( of: atom, for: key, override: override)
130- let isNewSubscription = subscriber. subscribingKeys . insert ( key) . inserted
130+ let isNewSubscription = subscriber. subscribing . insert ( key) . inserted
131131
132- store. state. subscriptions [ key, default: [ : ] ] . updateValue ( subscription , forKey : subscriber. key)
132+ store. state. subscriptions [ key, default: [ : ] ] [ subscriber. key] = subscription
133133 subscriber. unsubscribe = { keys in
134134 unsubscribe ( keys, for: subscriber. key)
135135 }
@@ -164,7 +164,7 @@ internal struct StoreContext {
164164
165165 // Notify update unless it's cancelled or terminated by other operations.
166166 if !Task. isCancelled && !context. isTerminated {
167- update ( atom: atom, for: key, newValue: value, cache : cache)
167+ update ( atom: atom, for: key, newValue: value, oldValue : cache. value )
168168 }
169169
170170 return value
@@ -186,7 +186,7 @@ internal struct StoreContext {
186186
187187 // Notify update unless it's cancelled or terminated by other operations.
188188 if !Task. isCancelled && !transaction. isTerminated {
189- update ( atom: atom, for: key, newValue: value, cache : cache)
189+ update ( atom: atom, for: key, newValue: value, oldValue : cache. value )
190190 }
191191
192192 return value
@@ -201,7 +201,7 @@ internal struct StoreContext {
201201
202202 if let cache = lookupCache ( of: atom, for: key) {
203203 let newCache = makeCache ( of: atom, for: key, override: override)
204- update ( atom: atom, for: key, newValue: newCache. value, cache : cache)
204+ update ( atom: atom, for: key, newValue: newCache. value, oldValue : cache. value )
205205 }
206206 }
207207
@@ -233,7 +233,7 @@ internal struct StoreContext {
233233 let scopeKey = lookupScopeKey ( of: atom, isScopedOverriden: override? . isScoped ?? false )
234234 let key = AtomKey ( atom, scopeKey: scopeKey)
235235
236- subscriber. subscribingKeys . remove ( key)
236+ subscriber. subscribing . remove ( key)
237237 unsubscribe ( [ key] , for: subscriber. key)
238238 }
239239
@@ -327,7 +327,7 @@ private extension StoreContext {
327327 coordinator: state. coordinator
328328 ) { newValue in
329329 if let cache = lookupCache ( of: atom, for: key) {
330- update ( atom: atom, for: key, newValue: newValue, cache : cache)
330+ update ( atom: atom, for: key, newValue: newValue, oldValue : cache. value )
331331 }
332332 }
333333 }
@@ -336,43 +336,103 @@ private extension StoreContext {
336336 atom: Node ,
337337 for key: AtomKey ,
338338 newValue: Node . Loader . Value ,
339- cache : AtomCache < Node >
339+ oldValue : Node . Loader . Value
340340 ) {
341- let oldValue = cache . value
341+ store . state . caches [ key ] = AtomCache ( atom : atom , value: newValue )
342342
343- store. state. caches [ key] = mutating ( cache) {
344- $0. value = newValue
343+ // Check whether if the dependent atoms should be updated transitively.
344+ guard atom. _loader. shouldUpdateTransitively ( newValue: newValue, oldValue: oldValue) else {
345+ return
345346 }
346347
347- guard atom. _loader. shouldUpdate ( newValue: newValue, oldValue: oldValue) else {
348- return
348+ // Perform side effects first.
349+ let state = getState ( of: atom, for: key)
350+ let context = AtomCurrentContext ( store: self , coordinator: state. coordinator)
351+ atom. updated ( newValue: newValue, oldValue: oldValue, context: context)
352+
353+ // Calculate topological order for updating downstream efficiently.
354+ let ( edges, redundants) = topologicalSort ( key: key, store: store)
355+ var skippedDependencies = Set < AtomKey > ( )
356+
357+ // Updates the given atom.
358+ func update( for key: AtomKey , cache: some AtomCacheProtocol ) {
359+ let override = lookupOverride ( of: cache. atom)
360+ let newCache = makeCache ( of: cache. atom, for: key, override: override)
361+
362+ // Check whether if the dependent atoms should be updated transitively.
363+ guard cache. atom. _loader. shouldUpdateTransitively ( newValue: newCache. value, oldValue: cache. value) else {
364+ // Record the atom to avoid downstream from being update.
365+ skippedDependencies. insert ( key)
366+ return
367+ }
368+
369+ // Perform side effects before updating downstream.
370+ let state = getState ( of: cache. atom, for: key)
371+ let context = AtomCurrentContext ( store: self , coordinator: state. coordinator)
372+ cache. atom. updated ( newValue: newCache. value, oldValue: cache. value, context: context)
349373 }
350374
351- atom. _loader. performUpdate {
352- // Notifies update to view subscriptions first.
353- if let subscriptions = store. state. subscriptions [ key] {
354- for subscription in ContiguousArray ( subscriptions. values) {
355- subscription. update ( )
356- }
375+ // Performs update of the given atom with the dependency's context.
376+ func performUpdate( for key: AtomKey , cache: some AtomCacheProtocol , dependency: some Atom ) {
377+ dependency. _loader. performTransitiveUpdate {
378+ update ( for: key, cache: cache)
357379 }
380+ }
358381
359- // Notifies update to downstream atoms.
360- if let children = store. graph. children [ key] {
361- for child in ContiguousArray ( children) {
362- // Reset the atom value and then notifies downstream atoms.
363- if let cache = store. state. caches [ child] {
364- reset ( cache. atom)
365- }
366- }
382+ // Performs update of the given subscription with the dependency's context.
383+ func performUpdate( subscription: Subscription , dependency: some Atom ) {
384+ dependency. _loader. performTransitiveUpdate ( subscription. update)
385+ }
386+
387+ func validEdge( _ edge: Edge ) -> Edge ? {
388+ // Do not transitively update atoms that have dependency recorded not to update downstream.
389+ guard skippedDependencies. contains ( edge. from) else {
390+ return edge
367391 }
368392
369- // Notify value update to observers.
370- notifyUpdateToObservers ( )
393+ // If the topological sorting has marked the vertex as a redundant, the update still performed.
394+ guard let fromKey = redundants [ edge. to] ? . first ( where: { !skippedDependencies. contains ( $0) } ) else {
395+ return nil
396+ }
371397
372- let state = getState ( of: atom, for: key)
373- let context = AtomCurrentContext ( store: self , coordinator: state. coordinator)
374- atom. updated ( newValue: newValue, oldValue: oldValue, context: context)
398+ // Convert edge's `from`, which represents a dependency atom, to a non-skipped one to
399+ // change the update transaction context (e.g. animation).
400+ return Edge ( from: fromKey, to: edge. to)
401+ }
402+
403+ // Perform transitive update for dependent atoms ahead of notifying updates to subscriptions.
404+ for edge in edges {
405+ switch edge. to {
406+ case . atom( let key) :
407+ guard let edge = validEdge ( edge) else {
408+ // Record the atom to avoid downstream from being update.
409+ skippedDependencies. insert ( key)
410+ continue
411+ }
412+
413+ let cache = store. state. caches [ key]
414+ let dependencyCache = store. state. caches [ edge. from]
415+
416+ if let cache, let dependencyCache {
417+ performUpdate ( for: key, cache: cache, dependency: dependencyCache. atom)
418+ }
419+
420+ case . subscriber( let key) :
421+ guard let edge = validEdge ( edge) else {
422+ continue
423+ }
424+
425+ let subscription = store. state. subscriptions [ edge. from] ? [ key]
426+ let dependencyCache = store. state. caches [ edge. from]
427+
428+ if let subscription, let dependencyCache {
429+ performUpdate ( subscription: subscription, dependency: dependencyCache. atom)
430+ }
431+ }
375432 }
433+
434+ // Notify the observers after all updates are completed.
435+ notifyUpdateToObservers ( )
376436 }
377437
378438 func unsubscribe< Keys: Sequence < AtomKey > > ( _ keys: Keys , for subscriberKey: SubscriberKey ) {
0 commit comments