1
1
import * as emitter from "./emitter.js" ;
2
2
3
3
const entries = new WeakMap ( ) ;
4
+ const suspense = new WeakSet ( ) ;
5
+
4
6
export function getEntry ( target , key ) {
5
7
let targetMap = entries . get ( target ) ;
6
8
if ( ! targetMap ) {
@@ -15,11 +17,9 @@ export function getEntry(target, key) {
15
17
target,
16
18
key,
17
19
value : undefined ,
18
- contexts : undefined ,
19
- deps : undefined ,
20
- state : 0 ,
21
- checksum : 0 ,
22
- observed : false ,
20
+ contexts : new Set ( ) ,
21
+ deps : new Set ( ) ,
22
+ resolved : false ,
23
23
} ;
24
24
targetMap . set ( key , entry ) ;
25
25
}
@@ -38,100 +38,63 @@ export function getEntries(target) {
38
38
return result ;
39
39
}
40
40
41
- function calculateChecksum ( entry ) {
42
- let checksum = entry . state ;
43
- if ( entry . deps ) {
44
- entry . deps . forEach ( depEntry => {
45
- checksum += depEntry . state ;
46
- } ) ;
47
- }
48
-
49
- return checksum ;
50
- }
51
-
52
41
function dispatchDeep ( entry ) {
53
- if ( entry . observed ) emitter . dispatch ( entry ) ;
54
- if ( entry . contexts ) entry . contexts . forEach ( dispatchDeep ) ;
55
- }
56
-
57
- function restoreDeepDeps ( entry , deps ) {
58
- if ( deps ) {
59
- deps . forEach ( depEntry => {
60
- entry . deps . add ( depEntry ) ;
61
-
62
- if ( entry . observed ) {
63
- /* istanbul ignore if */
64
- if ( ! depEntry . contexts ) depEntry . contexts = new Set ( ) ;
65
- depEntry . contexts . add ( entry ) ;
66
- }
42
+ entry . resolved = false ;
67
43
68
- restoreDeepDeps ( entry , depEntry . deps ) ;
69
- } ) ;
70
- }
44
+ emitter . dispatch ( entry ) ;
45
+ entry . contexts . forEach ( dispatchDeep ) ;
71
46
}
72
47
73
- const contextStack = new Set ( ) ;
48
+ const contexts = [ ] ;
74
49
export function get ( target , key , getter , validate ) {
75
50
const entry = getEntry ( target , key ) ;
76
51
77
- if ( contextStack . size && contextStack . has ( entry ) ) {
52
+ if ( contexts . includes ( entry ) ) {
78
53
throw Error ( `Circular get invocation is forbidden: '${ key } '` ) ;
79
54
}
80
55
81
- contextStack . forEach ( context => {
82
- if ( ! context . deps ) context . deps = new Set ( ) ;
83
- context . deps . add ( entry ) ;
56
+ const context = contexts [ 0 ] ;
84
57
85
- if ( context . observed ) {
86
- if ( ! entry . contexts ) entry . contexts = new Set ( ) ;
87
- entry . contexts . add ( context ) ;
88
- }
89
- } ) ;
58
+ if ( context && ! suspense . has ( context ) ) {
59
+ context . deps . add ( entry ) ;
60
+ entry . contexts . add ( context ) ;
61
+ }
90
62
91
63
if (
92
- ( ( validate && validate ( entry . value ) ) || ! validate ) &&
93
- entry . checksum &&
94
- entry . checksum === calculateChecksum ( entry )
64
+ ! suspense . has ( target ) &&
65
+ entry . resolved &&
66
+ ( ( validate && validate ( entry . value ) ) || ! validate )
95
67
) {
96
68
return entry . value ;
97
69
}
98
70
99
71
try {
100
- contextStack . add ( entry ) ;
72
+ contexts . unshift ( entry ) ;
101
73
102
- if ( entry . observed && entry . deps && entry . deps . size ) {
103
- entry . deps . forEach ( depEntry => {
104
- /* istanbul ignore else */
105
- if ( depEntry . contexts ) depEntry . contexts . delete ( entry ) ;
106
- } ) ;
107
- }
74
+ entry . deps . forEach ( depEntry => {
75
+ depEntry . contexts . delete ( entry ) ;
76
+ } ) ;
77
+ entry . deps . clear ( ) ;
108
78
109
- entry . deps = undefined ;
110
79
const nextValue = getter ( target , entry . value ) ;
111
80
112
- if ( entry . deps ) {
113
- entry . deps . forEach ( depEntry => {
114
- restoreDeepDeps ( entry , depEntry . deps ) ;
115
- } ) ;
116
- }
117
-
118
81
if ( nextValue !== entry . value ) {
119
- entry . state += 1 ;
120
82
entry . value = nextValue ;
121
-
122
83
dispatchDeep ( entry ) ;
123
84
}
124
85
125
- entry . checksum = calculateChecksum ( entry ) ;
126
- contextStack . delete ( entry ) ;
86
+ entry . resolved = ! suspense . has ( target ) ;
87
+
88
+ contexts . shift ( ) ;
127
89
} catch ( e ) {
128
- entry . checksum = 0 ;
90
+ contexts . shift ( ) ;
91
+
92
+ entry . resolved = false ;
129
93
130
- contextStack . delete ( entry ) ;
131
- contextStack . forEach ( context => {
94
+ if ( context && ! suspense . has ( context ) ) {
132
95
context . deps . delete ( entry ) ;
133
- if ( context . observed ) entry . contexts . delete ( context ) ;
134
- } ) ;
96
+ entry . contexts . delete ( context ) ;
97
+ }
135
98
136
99
throw e ;
137
100
}
@@ -144,10 +107,7 @@ export function set(target, key, setter, value) {
144
107
const newValue = setter ( target , value , entry . value ) ;
145
108
146
109
if ( newValue !== entry . value ) {
147
- entry . checksum = 0 ;
148
- entry . state += 1 ;
149
110
entry . value = newValue ;
150
-
151
111
dispatchDeep ( entry ) ;
152
112
}
153
113
}
@@ -157,7 +117,11 @@ function deleteEntry(entry) {
157
117
if ( ! gcList . size ) {
158
118
requestAnimationFrame ( ( ) => {
159
119
gcList . forEach ( e => {
160
- if ( ! e . contexts || ( e . contexts && e . contexts . size === 0 ) ) {
120
+ if ( e . contexts . size === 0 ) {
121
+ e . deps . forEach ( depEntry => {
122
+ depEntry . contexts . delete ( e ) ;
123
+ } ) ;
124
+
161
125
const targetMap = entries . get ( e . target ) ;
162
126
targetMap . delete ( e . key ) ;
163
127
}
@@ -170,19 +134,19 @@ function deleteEntry(entry) {
170
134
}
171
135
172
136
function invalidateEntry ( entry , clearValue , deleteValue ) {
173
- entry . checksum = 0 ;
174
- entry . state += 1 ;
175
-
176
137
dispatchDeep ( entry ) ;
177
- if ( deleteValue ) deleteEntry ( entry ) ;
178
138
179
139
if ( clearValue ) {
180
140
entry . value = undefined ;
181
141
}
142
+
143
+ if ( deleteValue ) {
144
+ deleteEntry ( entry ) ;
145
+ }
182
146
}
183
147
184
148
export function invalidate ( target , key , clearValue , deleteValue ) {
185
- if ( contextStack . size ) {
149
+ if ( contexts . length ) {
186
150
throw Error (
187
151
`Invalidating property in chain of get calls is forbidden: '${ key } '` ,
188
152
) ;
@@ -193,7 +157,7 @@ export function invalidate(target, key, clearValue, deleteValue) {
193
157
}
194
158
195
159
export function invalidateAll ( target , clearValue , deleteValue ) {
196
- if ( contextStack . size ) {
160
+ if ( contexts . length ) {
197
161
throw Error (
198
162
"Invalidating all properties in chain of get calls is forbidden" ,
199
163
) ;
@@ -209,33 +173,50 @@ export function invalidateAll(target, clearValue, deleteValue) {
209
173
210
174
export function observe ( target , key , getter , fn ) {
211
175
const entry = getEntry ( target , key ) ;
212
- entry . observed = true ;
213
-
214
176
let lastValue ;
215
- const unsubscribe = emitter . subscribe ( entry , ( ) => {
216
- const value = get ( target , key , getter ) ;
217
- if ( value !== lastValue ) {
218
- fn ( target , value , lastValue ) ;
219
- lastValue = value ;
177
+
178
+ return emitter . subscribe ( entry , ( ) => {
179
+ if ( ! suspense . has ( target ) ) {
180
+ const value = get ( target , key , getter ) ;
181
+ if ( value !== lastValue ) {
182
+ fn ( target , value , lastValue ) ;
183
+ lastValue = value ;
184
+ }
220
185
}
221
186
} ) ;
187
+ }
222
188
223
- if ( entry . deps ) {
224
- entry . deps . forEach ( depEntry => {
225
- /* istanbul ignore else */
226
- if ( ! depEntry . contexts ) depEntry . contexts = new Set ( ) ;
227
- depEntry . contexts . add ( entry ) ;
189
+ const clearTargets = new Set ( ) ;
190
+ export function clear ( target ) {
191
+ if ( clearTargets . size === 0 ) {
192
+ requestAnimationFrame ( ( ) => {
193
+ clearTargets . forEach ( t => {
194
+ const targetMap = entries . get ( t ) ;
195
+ if ( targetMap ) {
196
+ targetMap . forEach ( entry => {
197
+ entry . resolved = false ;
198
+
199
+ entry . deps . forEach ( depEntry => {
200
+ depEntry . contexts . delete ( entry ) ;
201
+ } ) ;
202
+
203
+ entry . deps . clear ( ) ;
204
+ entry . contexts . clear ( ) ;
205
+ } ) ;
206
+ }
207
+ } ) ;
208
+
209
+ clearTargets . clear ( ) ;
228
210
} ) ;
229
211
}
212
+ clearTargets . add ( target ) ;
213
+ }
230
214
231
- return function unobserve ( ) {
232
- unsubscribe ( ) ;
233
- entry . observed = false ;
234
- if ( entry . deps && entry . deps . size ) {
235
- entry . deps . forEach ( depEntry => {
236
- /* istanbul ignore else */
237
- if ( depEntry . contexts ) depEntry . contexts . delete ( entry ) ;
238
- } ) ;
239
- }
240
- } ;
215
+ export function suspend ( target ) {
216
+ suspense . add ( target ) ;
217
+ }
218
+
219
+ export function unsuspend ( target ) {
220
+ suspense . delete ( target ) ;
221
+ clearTargets . delete ( target ) ;
241
222
}
0 commit comments