@@ -16,6 +16,7 @@ export default function Runtime(builtins = new Library, global = window_global)
16
16
Object . defineProperties ( this , {
17
17
_dirty : { value : new Set } ,
18
18
_updates : { value : new Set } ,
19
+ _precomputes : { value : [ ] , writable : true } ,
19
20
_computing : { value : null , writable : true } ,
20
21
_init : { value : null , writable : true } ,
21
22
_modules : { value : new Map } ,
@@ -34,6 +35,7 @@ Object.defineProperties(Runtime, {
34
35
} ) ;
35
36
36
37
Object . defineProperties ( Runtime . prototype , {
38
+ _precompute : { value : runtime_precompute , writable : true , configurable : true } ,
37
39
_compute : { value : runtime_compute , writable : true , configurable : true } ,
38
40
_computeSoon : { value : runtime_computeSoon , writable : true , configurable : true } ,
39
41
_computeNow : { value : runtime_computeNow , writable : true , configurable : true } ,
@@ -72,24 +74,32 @@ function runtime_module(define, observer = noop) {
72
74
return module ;
73
75
}
74
76
77
+ function runtime_precompute ( callback ) {
78
+ this . _precomputes . push ( callback ) ;
79
+ this . _compute ( ) ;
80
+ }
81
+
75
82
function runtime_compute ( ) {
76
83
return this . _computing || ( this . _computing = this . _computeSoon ( ) ) ;
77
84
}
78
85
79
86
function runtime_computeSoon ( ) {
80
- var runtime = this ;
81
- return new Promise ( function ( resolve ) {
82
- frame ( function ( ) {
83
- resolve ( ) ;
84
- runtime . _disposed || runtime . _computeNow ( ) ;
85
- } ) ;
86
- } ) ;
87
+ return new Promise ( frame ) . then ( ( ) => this . _disposed ? undefined : this . _computeNow ( ) ) ;
87
88
}
88
89
89
- function runtime_computeNow ( ) {
90
+ async function runtime_computeNow ( ) {
90
91
var queue = [ ] ,
91
92
variables ,
92
- variable ;
93
+ variable ,
94
+ precomputes = this . _precomputes ;
95
+
96
+ // If there are any paused generators, resume them before computing so they
97
+ // can update (if synchronous) before computing downstream variables.
98
+ if ( precomputes . length ) {
99
+ this . _precomputes = [ ] ;
100
+ for ( const callback of precomputes ) callback ( ) ;
101
+ await runtime_defer ( 3 ) ;
102
+ }
93
103
94
104
// Compute the reachability of the transitive closure of dirty variables.
95
105
// Any newly-reachable variable must also be recomputed.
@@ -159,6 +169,16 @@ function runtime_computeNow() {
159
169
}
160
170
}
161
171
172
+ // We want to give generators, if they’re defined synchronously, a chance to
173
+ // update before computing downstream variables. This creates a synchronous
174
+ // promise chain of the given depth that we’ll await before recomputing
175
+ // downstream variables.
176
+ function runtime_defer ( depth = 0 ) {
177
+ let p = Promise . resolve ( ) ;
178
+ for ( let i = 0 ; i < depth ; ++ i ) p = p . then ( ( ) => { } ) ;
179
+ return p ;
180
+ }
181
+
162
182
function variable_circular ( variable ) {
163
183
const inputs = new Set ( variable . _inputs ) ;
164
184
for ( const i of inputs ) {
@@ -206,10 +226,21 @@ function variable_compute(variable) {
206
226
variable . _invalidate ( ) ;
207
227
variable . _invalidate = noop ;
208
228
variable . _pending ( ) ;
209
- var value0 = variable . _value ,
210
- version = ++ variable . _version ,
211
- invalidation = null ,
212
- promise = variable . _promise = Promise . all ( variable . _inputs . map ( variable_value ) ) . then ( function ( inputs ) {
229
+
230
+ const value0 = variable . _value ;
231
+ const version = ++ variable . _version ;
232
+
233
+ // Lazily-constructed invalidation variable; only constructed if referenced as an input.
234
+ let invalidation = null ;
235
+
236
+ // If the variable doesn’t have any inputs, we can optimize slightly.
237
+ const promise = variable . _promise = ( variable . _inputs . length
238
+ ? Promise . all ( variable . _inputs . map ( variable_value ) ) . then ( define )
239
+ : new Promise ( resolve => resolve ( variable . _definition . call ( value0 ) ) ) )
240
+ . then ( generate ) ;
241
+
242
+ // Compute the initial value of the variable.
243
+ function define ( inputs ) {
213
244
if ( variable . _version !== version ) return ;
214
245
215
246
// Replace any reference to invalidation with the promise, lazily.
@@ -227,64 +258,77 @@ function variable_compute(variable) {
227
258
}
228
259
}
229
260
230
- // Compute the initial value of the variable.
231
261
return variable . _definition . apply ( value0 , inputs ) ;
232
- } ) . then ( function ( value ) {
233
- // If the value is a generator, then retrieve its first value,
234
- // and dispose of the generator if the variable is invalidated.
235
- // Note that the cell may already have been invalidated here,
236
- // in which case we need to terminate the generator immediately!
262
+ }
263
+
264
+ // If the value is a generator, then retrieve its first value, and dispose of
265
+ // the generator if the variable is invalidated. Note that the cell may
266
+ // already have been invalidated here, in which case we need to terminate the
267
+ // generator immediately!
268
+ function generate ( value ) {
237
269
if ( generatorish ( value ) ) {
238
270
if ( variable . _version !== version ) return void value . return ( ) ;
239
271
( invalidation || variable_invalidator ( variable ) ) . then ( variable_return ( value ) ) ;
240
- return variable_precompute ( variable , version , promise , value ) ;
272
+ return variable_generate ( variable , version , value ) ;
241
273
}
242
274
return value ;
243
- } ) ;
244
- promise . then ( function ( value ) {
275
+ }
276
+
277
+ promise . then ( ( value ) => {
245
278
if ( variable . _version !== version ) return ;
246
279
variable . _value = value ;
247
280
variable . _fulfilled ( value ) ;
248
- } , function ( error ) {
281
+ } , ( error ) => {
249
282
if ( variable . _version !== version ) return ;
250
283
variable . _value = undefined ;
251
284
variable . _rejected ( error ) ;
252
285
} ) ;
253
286
}
254
287
255
- function variable_precompute ( variable , version , promise , generator ) {
288
+ function variable_generate ( variable , version , generator ) {
289
+ const runtime = variable . _module . _runtime ;
290
+
291
+ // Retrieve the next value from the generator; if successful, invoke the
292
+ // specified callback. The returned promise resolves to the yielded value, or
293
+ // to undefined if the generator is done.
294
+ function compute ( onfulfilled ) {
295
+ return new Promise ( resolve => resolve ( generator . next ( ) ) ) . then ( ( { done, value} ) => {
296
+ return done ? undefined : ( value = Promise . resolve ( value ) , value . then ( onfulfilled ) , value ) ;
297
+ } ) ;
298
+ }
299
+
300
+ // Retrieve the next value from the generator; if successful, fulfill the
301
+ // variable, compute downstream variables, and schedule the next value to be
302
+ // pulled from the generator at the start of the next animation frame. If not
303
+ // successful, reject the variable, compute downstream variables, and return.
256
304
function recompute ( ) {
257
- var promise = new Promise ( function ( resolve ) {
258
- resolve ( generator . next ( ) ) ;
259
- } ) . then ( function ( next ) {
260
- return next . done ? undefined : Promise . resolve ( next . value ) . then ( function ( value ) {
261
- if ( variable . _version !== version ) return ;
262
- variable_postrecompute ( variable , value , promise ) . then ( recompute ) ;
263
- variable . _fulfilled ( value ) ;
264
- return value ;
265
- } ) ;
305
+ const promise = compute ( ( value ) => {
306
+ if ( variable . _version !== version ) return ;
307
+ postcompute ( value , promise ) . then ( ( ) => runtime . _precompute ( recompute ) ) ;
308
+ variable . _fulfilled ( value ) ;
266
309
} ) ;
267
- promise . catch ( function ( error ) {
310
+ promise . catch ( ( error ) => {
268
311
if ( variable . _version !== version ) return ;
269
- variable_postrecompute ( variable , undefined , promise ) ;
312
+ postcompute ( undefined , promise ) ;
270
313
variable . _rejected ( error ) ;
271
314
} ) ;
272
315
}
273
- return new Promise ( function ( resolve ) {
274
- resolve ( generator . next ( ) ) ;
275
- } ) . then ( function ( next ) {
276
- if ( next . done ) return ;
277
- promise . then ( recompute ) ;
278
- return next . value ;
279
- } ) ;
280
- }
281
316
282
- function variable_postrecompute ( variable , value , promise ) {
283
- var runtime = variable . _module . _runtime ;
284
- variable . _value = value ;
285
- variable . _promise = promise ;
286
- variable . _outputs . forEach ( runtime . _updates . add , runtime . _updates ) ; // TODO Cleaner?
287
- return runtime . _compute ( ) ;
317
+ // After the generator fulfills or rejects, set its current value, promise,
318
+ // and schedule any downstream variables for update.
319
+ function postcompute ( value , promise ) {
320
+ variable . _value = value ;
321
+ variable . _promise = promise ;
322
+ variable . _outputs . forEach ( runtime . _updates . add , runtime . _updates ) ;
323
+ return runtime . _compute ( ) ;
324
+ }
325
+
326
+ // When retrieving the first value from the generator, the promise graph is
327
+ // already established, so we only need to queue the next pull.
328
+ return compute ( ( ) => {
329
+ if ( variable . _version !== version ) return ;
330
+ runtime . _precompute ( recompute ) ;
331
+ } ) ;
288
332
}
289
333
290
334
function variable_error ( variable , error ) {
0 commit comments