@@ -2,65 +2,14 @@ import {IDerivation} from "./derivation";
22import { globalState } from "./globalstate" ;
33import { invariant } from "../utils/utils" ;
44
5- export class DerivationsSets {
6- list : IDerivation [ ] = [ ] ;
7- toDelete : IDerivation [ ] = [ ] ;
8-
9- get length ( ) {
10- return this . list . length - this . toDelete . length ;
11- }
12-
13- clearDeleted ( ) {
14- const toDelete = this . toDelete ;
15- if ( toDelete . length !== 0 ) {
16- const list = this . list ;
17- for ( let i = 0 ; i < toDelete . length ; i ++ ) {
18- toDelete [ i ] . diffValue ++ ;
19- }
20-
21- let i0 = 0 ;
22- for ( let i = 0 ; i < list . length ; i ++ ) {
23- if ( list [ i ] . diffValue === 0 ) {
24- if ( i !== i0 ) list [ i0 ] = list [ i ] ;
25- i0 ++ ;
26- } else {
27- list [ i ] . diffValue -- ;
28- }
29- }
30-
31- // invariant(toDelete.every(a => a.diffValue === 0), "INTERNAL ERROR, clear deleted should leave diffValue everywhere 0");
32- this . toDelete = [ ] ;
33- list . length = i0 ;
34- }
35- }
36-
37- asArray ( ) {
38- this . clearDeleted ( ) ;
39- return this . list ;
40- }
41-
42- add ( value : IDerivation ) {
43- // invariant(value.dependenciesState !== -1, "INTERNAL ERROR, can add only dependenciesState !== -1");
44- this . list . push ( value ) ;
45- }
46-
47- remove ( value : IDerivation ) {
48- // invariant(globalState.inBatch > 0, "INTERNAL ERROR, remove should be called only inside batch");
49- if ( this . toDelete . length === 0 ) {
50- globalState . pendingDeletions . push ( this ) ;
51- }
52- this . toDelete . push ( value ) ;
53- }
54- // move(targetState: number, value: IDerivation) {
55- // const m = value.__mapid;
56- // delete this["data" + value.dependenciesState][m];
57- // this["data" + targetState][m] = value;
58- // }
5+ export interface LegacyObservers {
6+ asArray ( ) : IDerivation [ ] ;
7+ length : number ;
598}
609
6110export interface IDepTreeNode {
6211 name : string ;
63- observers ?: DerivationsSets ;
12+ observers ?: LegacyObservers ;
6413 observing ?: IObservable [ ] ;
6514}
6615
@@ -73,26 +22,85 @@ export interface IObservable extends IDepTreeNode {
7322 */
7423 lastAccessedBy : number ;
7524
76- lowestObserverState : number ;
25+ lowestObserverState : number ; // to not repeat same propagations, see `invariantLOS`
7726 isPendingUnobservation : boolean ; // for effective unobserving
7827 isObserved : boolean ; // for unobserving only once ber observation
7928 // sets of observers grouped their state to only notify about changes.
80- observers : DerivationsSets ;
29+ observers : LegacyObservers ;
30+ _observers : IDerivation [ ] ; // a is observer if it occurs in _observers one time more than in _observersToDelete
31+ _observersToDelete : IDerivation [ ] ; // must be empty when nothing is running
8132
8233 onBecomeUnobserved ( ) ;
8334}
8435
36+ export function legacyObservers ( observable : IObservable ) : LegacyObservers {
37+ return {
38+ get length ( ) {
39+ return observable . _observers . length - observable . _observersToDelete . length ;
40+ } ,
41+ asArray ( ) {
42+ return getObservers ( observable ) ;
43+ }
44+ } ;
45+ }
46+
47+ export function isObjectObservable ( arg : any ) : arg is IObservable {
48+ return arg ? arg . lastAccessedBy !== undefined : false ;
49+ }
50+
51+ export function hasObservers ( observable : IObservable ) : boolean {
52+ return observable . _observers . length - observable . _observersToDelete . length > 0 ;
53+ }
54+
55+ // the trick is to only clear deleted elements when observers will be read whole anyway or when batch ends
56+ function clearDeletedObservers ( observable : IObservable ) {
57+ const _observersToDelete = observable . _observersToDelete ;
58+ if ( _observersToDelete . length !== 0 ) {
59+ const _observers = observable . _observers ;
60+ for ( let i = 0 ; i < _observersToDelete . length ; i ++ ) {
61+ _observersToDelete [ i ] . diffValue ++ ;
62+ }
63+
64+ let i0 = 0 ;
65+ for ( let i = 0 ; i < _observers . length ; i ++ ) {
66+ if ( _observers [ i ] . diffValue === 0 ) {
67+ if ( i !== i0 ) _observers [ i0 ] = _observers [ i ] ;
68+ i0 ++ ;
69+ } else {
70+ _observers [ i ] . diffValue -- ;
71+ }
72+ }
73+
74+ // invariant(_observersToDelete.every(a => a.diffValue === 0), "INTERNAL ERROR, clear deleted should leave diffValue everywhere 0");
75+ observable . _observersToDelete = [ ] ;
76+ _observers . length = i0 ;
77+ }
78+ }
79+
80+ export function getObservers ( observable : IObservable ) : IDerivation [ ] {
81+ clearDeletedObservers ( observable ) ;
82+ return observable . _observers ;
83+ }
84+
8585export function addObserver ( observable : IObservable , node : IDerivation ) {
86- observable . observers . add ( node ) ;
87- observable . lowestObserverState = Math . min ( observable . lowestObserverState , node . dependenciesState ) ;
86+ // invariant(node.dependenciesState !== -1, "INTERNAL ERROR, can add only dependenciesState !== -1");
87+ observable . _observers . push ( node ) ;
88+
89+ // observable.lowestObserverState = Math.min(observable.lowestObserverState, node.dependenciesState);
90+ if ( observable . lowestObserverState > node . dependenciesState ) observable . lowestObserverState = node . dependenciesState ;
8891 if ( ! observable . isObserved ) {
8992 observable . isObserved = true ;
9093 }
9194}
9295
9396export function removeObserver ( observable : IObservable , node : IDerivation ) {
94- observable . observers . remove ( node ) ;
95- if ( observable . isObserved && observable . observers . length === 0 ) {
97+ // invariant(globalState.inBatch > 0, "INTERNAL ERROR, remove should be called only inside batch");
98+ if ( observable . _observersToDelete . length === 0 ) {
99+ globalState . pendingDeletions . push ( observable ) ;
100+ }
101+ observable . _observersToDelete . push ( node ) ;
102+
103+ if ( observable . isObserved && ! hasObservers ( observable ) ) {
96104 if ( globalState . inBatch > 0 ) {
97105 /**
98106 * Wan't to observe/unobserve max once per observable during batch.
@@ -119,12 +127,12 @@ export function endBatch() {
119127 while ( globalState . pendingUnobservations . length > 0 ) {
120128 globalState . pendingUnobservations . splice ( 0 ) . forEach ( observable => {
121129 observable . isPendingUnobservation = false ;
122- if ( observable . isObserved && observable . observers . length === 0 ) {
130+ if ( observable . isObserved && ! hasObservers ( observable ) ) {
123131 observable . isObserved = false ;
124132 observable . onBecomeUnobserved ( ) ; // TODO: test if this happens only once, e.g. remove returns bool!
125133 }
126134 } ) ;
127- globalState . pendingDeletions . splice ( 0 ) . forEach ( d => d . clearDeleted ( ) ) ;
135+ globalState . pendingDeletions . splice ( 0 ) . forEach ( clearDeletedObservers ) ;
128136 }
129137 globalState . inBatch = 0 ;
130138 }
@@ -157,9 +165,8 @@ export function reportObserved(observable: IObservable) {
157165}
158166
159167function invariantLOS ( observable : IObservable , msg ) {
160- console . log ( "check invariantLOS for" , msg ) ;
161- const min = observable . observers . asArray ( ) . reduce ( ( a , b ) => Math . min ( a , b . dependenciesState ) , 2 ) ;
162- if ( min >= observable . lowestObserverState ) return ;
168+ const min = getObservers ( observable ) . reduce ( ( a , b ) => Math . min ( a , b . dependenciesState ) , 2 ) ;
169+ if ( min >= observable . lowestObserverState ) return ; // <- the only assumption about `lowestObserverState`
163170 throw new Error ( "lowestObserverState is wrong for " + msg + " because " + min + " < " + observable . lowestObserverState ) ;
164171}
165172
@@ -168,7 +175,7 @@ export function propagateChanged(observable: IObservable) {
168175 if ( observable . lowestObserverState === 2 ) return ;
169176 observable . lowestObserverState = 2 ;
170177
171- const observers = observable . observers . asArray ( ) ;
178+ const observers = getObservers ( observable ) ;
172179 for ( let i = 0 ; i < observers . length ; i ++ ) {
173180 const d = observers [ i ] ;
174181 if ( d . dependenciesState === 0 ) {
@@ -184,7 +191,7 @@ export function propagateChangeConfirmed(observable: IObservable) {
184191 if ( observable . lowestObserverState === 2 ) return ;
185192 observable . lowestObserverState = 2 ;
186193
187- const observers = observable . observers . asArray ( ) ;
194+ const observers = getObservers ( observable ) ;
188195 for ( let i = 0 ; i < observers . length ; i ++ ) {
189196 const d = observers [ i ] ;
190197 if ( d . dependenciesState === 1 ) {
@@ -201,7 +208,7 @@ export function propagateMaybeChanged(observable: IObservable) {
201208 if ( observable . lowestObserverState !== 0 ) return ;
202209 observable . lowestObserverState = 1 ;
203210
204- const observers = observable . observers . asArray ( ) ;
211+ const observers = getObservers ( observable ) ;
205212 for ( let i = 0 ; i < observers . length ; i ++ ) {
206213 const d = observers [ i ] ;
207214 if ( d . dependenciesState === 0 ) {
0 commit comments