@@ -53,7 +53,6 @@ export interface Document {
53
53
readonly tree : MarkdownTree ;
54
54
readonly metadata : Readonly < DocumentMetadata > ;
55
55
readonly storedMetadata : Readonly < DocumentMetadata > ;
56
- readonly dirty : boolean ;
57
56
updateMetadata ( updater : ( metadata : DocumentMetadata ) => boolean ) : void ;
58
57
}
59
58
@@ -345,6 +344,27 @@ export class IdbLibrary
345
344
new TypedCustomEvent ( 'post-edit-update' , { detail : { node, change} } ) ,
346
345
) ;
347
346
}
347
+
348
+ #writeQueue: ( ( ) => Promise < void > ) [ ] = [ ] ;
349
+ #writing = false ;
350
+ async enqueueWrite ( write : ( ) => Promise < void > ) {
351
+ this . #writeQueue. push ( write ) ;
352
+ if ( this . #writing) return ;
353
+ this . #writing = true ;
354
+ while ( true ) {
355
+ const write = cast ( this . #writeQueue. pop ( ) ) ;
356
+ await write ( ) ;
357
+ if ( ! this . #writeQueue. length ) {
358
+ this . #writing = false ;
359
+ return ;
360
+ }
361
+ let preIdle = NaN ;
362
+ do {
363
+ preIdle = this . #writeQueue. length ;
364
+ await new Promise ( ( resolve ) => requestIdleCallback ( resolve ) ) ;
365
+ } while ( preIdle != this . #writeQueue. length ) ;
366
+ }
367
+ }
348
368
}
349
369
350
370
class IdbDocument implements Document {
@@ -369,15 +389,14 @@ class IdbDocument implements Document {
369
389
return this . #metadata;
370
390
}
371
391
readonly tree : MarkdownTree ;
372
- dirty = false ;
373
392
updateMetadata (
374
393
updater : ( metadata : DocumentMetadata ) => boolean ,
375
- markDirty = true ,
394
+ scheduleSave = true ,
376
395
) {
377
396
const newMetadata = structuredClone ( this . metadata ) ;
378
397
if ( ! updater ( newMetadata ) ) return ;
379
398
this . #metadata = newMetadata ;
380
- if ( markDirty ) this . metadataChanged ( ) ;
399
+ if ( scheduleSave ) this . metadataChanged ( ) ;
381
400
}
382
401
get name ( ) {
383
402
return (
@@ -400,7 +419,7 @@ class IdbDocument implements Document {
400
419
this . tree . connect ( ) ;
401
420
this . tree . setRoot ( this . tree . add < DocumentNode > ( root ) , false ) ;
402
421
}
403
- noAwait ( this . markDirty ( ) ) ;
422
+ noAwait ( this . scheduleSave ( ) ) ;
404
423
}
405
424
async save ( ) {
406
425
const { root, caches} = this . tree . serializeWithCaches ( ) ;
@@ -428,7 +447,7 @@ class IdbDocument implements Document {
428
447
} ) ;
429
448
}
430
449
private metadataChanged ( ) {
431
- noAwait ( this . markDirty ( ) ) ;
450
+ noAwait ( this . scheduleSave ( ) ) ;
432
451
}
433
452
private treeChanged ( change : TreeChange ) {
434
453
if ( change === 'edit' ) {
@@ -438,34 +457,44 @@ class IdbDocument implements Document {
438
457
return true ;
439
458
} , false ) ;
440
459
}
441
- noAwait ( this . markDirty ( ) ) ;
460
+ noAwait ( this . scheduleSave ( ) ) ;
442
461
}
443
- private pendingModifications = 0 ;
444
- private async markDirty ( ) {
445
- this . dirty = true ;
446
- if ( this . pendingModifications ++ ) return ;
447
- while ( true ) {
448
- const preSave = this . pendingModifications ;
449
- // Save immediately on the fist iteration, may help keep tests fast.
450
- const oldMetadata = this . #storedMetadata;
451
- await this . save ( ) ;
452
- this . library . dispatchEvent (
453
- new TypedCustomEvent ( 'document-change' , {
454
- detail : { document : this , oldMetadata} ,
455
- } ) ,
456
- ) ;
457
- if ( this . pendingModifications === preSave ) {
458
- this . pendingModifications = 0 ;
459
- this . dirty = false ;
462
+
463
+ #pendinSaveOldMetadata: DocumentMetadata | undefined ;
464
+ #pendingSaveClock: number | undefined ;
465
+ private async scheduleSave ( ) {
466
+ const writeClock = this . #metadata. clock ?? 0 ;
467
+ if ( this . #pendingSaveClock === undefined ) {
468
+ this . #pendinSaveOldMetadata = this . #storedMetadata;
469
+ } else {
470
+ assert ( this . #pendinSaveOldMetadata) ;
471
+ if ( writeClock === this . #pendingSaveClock) {
472
+ // If the clock has not changed, we can join the scheduled write.
460
473
return ;
461
474
}
462
- // Wait for an idle period with no modifications.
463
- let preIdle = NaN ;
464
- do {
465
- preIdle = this . pendingModifications ;
466
- // TODO: maybe a timeout is better?
467
- await new Promise ( ( resolve ) => requestIdleCallback ( resolve ) ) ;
468
- } while ( preIdle != this . pendingModifications ) ;
469
475
}
476
+ this . #pendingSaveClock = writeClock ;
477
+ noAwait (
478
+ this . library . enqueueWrite ( async ( ) => {
479
+ if ( writeClock !== this . #pendingSaveClock) {
480
+ assert (
481
+ this . #pendingSaveClock !== undefined &&
482
+ this . #pendingSaveClock > writeClock ,
483
+ ) ;
484
+ // A write for a newer clock will supersede this.
485
+ return ;
486
+ }
487
+ this . #pendingSaveClock = undefined ;
488
+ await this . save ( ) ;
489
+ if ( this . #pendingSaveClock !== undefined ) return ;
490
+ const oldMetadata = cast ( this . #pendinSaveOldMetadata) ;
491
+ this . #pendinSaveOldMetadata = undefined ;
492
+ this . library . dispatchEvent (
493
+ new TypedCustomEvent ( 'document-change' , {
494
+ detail : { document : this , oldMetadata} ,
495
+ } ) ,
496
+ ) ;
497
+ } ) ,
498
+ ) ;
470
499
}
471
500
}
0 commit comments