@@ -28,15 +28,24 @@ enum MutableContentEvents {
2828
2929const ContentChangeEventActions : Array < string > = [ MutableContentEvents . AddObject , MutableContentEvents . RemoveObject ] ;
3030
31- type MutableObjectConfig = { supportsUndo ?: boolean } ;
31+ type MutableObjectConfig = { supportsUndo ?: boolean , supportsCheckpoints ?: boolean , checkpointFreq ?: number } ;
32+
33+ type StateCheckpoint = {
34+ mutableObject : Hash ,
35+ terminalOpHashes : Array < Hash > ,
36+ allAppliedOps : Array < Hash > ,
37+ activeCascInvsPerOp : Array < [ Hash , Array < Hash > ] > ,
38+ exportedState : any
39+ } ;
3240
3341abstract class MutableObject extends HashedObject {
3442
3543 static controlLog = new Logger ( MutableObject . name , LogLevel . INFO )
3644 static prevOpsComputationLog = new Logger ( MutableObject . name , LogLevel . INFO ) ;
3745
3846 readonly _acceptedMutationOpClasses : Array < string > ;
39-
47+ readonly _supportsCheckpoints : boolean ;
48+
4049 _allAppliedOps : Set < Hash > ;
4150 _terminalOps : Map < Hash , MutationOp > ;
4251
@@ -68,6 +77,8 @@ abstract class MutableObject extends HashedObject {
6877 acceptedOpClasses . push ( CascadedInvalidateOp . className ) ;
6978 }
7079 }
80+
81+ this . _supportsCheckpoints = config ?. supportsCheckpoints || false ;
7182
7283 this . _acceptedMutationOpClasses = acceptedOpClasses ;
7384 this . _boundToStore = false ;
@@ -112,6 +123,17 @@ abstract class MutableObject extends HashedObject {
112123 abstract getMutableContents ( ) : MultiMap < Hash , HashedObject > ;
113124 abstract getMutableContentByHash ( hash : Hash ) : Set < HashedObject > ;
114125
126+ // override the following two to support checkpointing (and pass apropriate params in config to constructor)
127+
128+ exportMutableState ( ) : any {
129+ throw new Error ( this . getClassName ( ) + ': this class does not support state packing' )
130+ }
131+
132+ importMutableState ( state : any ) {
133+ state ;
134+ throw new Error ( this . getClassName ( ) + ': this class does not support applying packed state' )
135+ }
136+
115137 /*
116138 // override if appropiate
117139 async undo(op: MutationOp): Promise<boolean> {
@@ -224,6 +246,17 @@ abstract class MutableObject extends HashedObject {
224246
225247 await super . loadAllChanges ( batchSize , context ) ;
226248
249+ if ( this . _supportsCheckpoints ) {
250+ const checkpoint = await this . getStore ( ) . loadLastCheckpoint ( this . getLastHash ( ) ) ;
251+
252+ if ( checkpoint !== undefined ) {
253+ this . restoreCheckpoint ( checkpoint ) ;
254+
255+ // TODO: find a way to get the correct "start" parameter for loadByReference below
256+ // to make it ignore all the ops in the checkpoint
257+ }
258+ }
259+
227260 let results = await this . getStore ( )
228261 . loadByReference (
229262 'targetObject' ,
@@ -510,6 +543,55 @@ abstract class MutableObject extends HashedObject {
510543 this . _unsavedOps . push ( op ) ;
511544 }
512545
546+ // checkpointing
547+
548+ async saveCheckpoint ( ) {
549+
550+ await this . saveQueuedOps ( ) ;
551+
552+ const check : StateCheckpoint = {
553+ mutableObject : this . getLastHash ( ) ,
554+ terminalOpHashes : Array . from ( this . _terminalOps . keys ( ) ) ,
555+ allAppliedOps : Array . from ( this . _allAppliedOps ) ,
556+ activeCascInvsPerOp : Array . from ( this . _activeCascInvsPerOp . entries ( ) ) . map ( ( v : [ Hash , Set < Hash > ] ) => [ v [ 0 ] , Array . from ( v [ 1 ] . values ( ) ) ] ) ,
557+ exportedState : this . exportMutableState ( )
558+ } ;
559+
560+ await this . getStore ( ) . saveCheckpoint ( check ) ;
561+
562+ }
563+
564+ async restoreCheckpoint ( checkpoint : StateCheckpoint ) {
565+ if ( this . getLastHash ( ) !== checkpoint . mutableObject ) {
566+ throw new Error ( 'Trying to apply a state checkpoint to ' + this . getLastHash ( ) + ', but the checkpoint is for ' + checkpoint . mutableObject ) ;
567+ }
568+
569+ const terminalOps = new Map < Hash , MutationOp > ( ) ;
570+
571+ for ( const opHash of checkpoint . terminalOpHashes . values ( ) ) {
572+ const op = await this . loadOp ( opHash ) ;
573+
574+ if ( op === undefined ) {
575+ throw new Error ( 'Cannot apply checkpoint to ' + this . getLastHash ( ) + ', missing op: ' + opHash ) ;
576+ }
577+
578+ terminalOps . set ( opHash , op ) ;
579+ }
580+
581+ this . _terminalOps = terminalOps ;
582+ this . _allAppliedOps = new Set ( checkpoint . allAppliedOps . values ( ) ) ;
583+ this . _activeCascInvsPerOp = new MultiMap ( ) ;
584+
585+ for ( const [ k , vs ] of checkpoint . activeCascInvsPerOp ) {
586+ for ( const v of vs ) {
587+ this . _activeCascInvsPerOp . add ( k , v ) ;
588+ }
589+ }
590+
591+ this . importMutableState ( checkpoint . exportedState ) ;
592+ }
593+
594+
513595 literalizeInContext ( context : Context , path : string , flags ?: Array < string > ) : Hash {
514596
515597 if ( flags === undefined ) {
@@ -710,4 +792,5 @@ abstract class MutableObject extends HashedObject {
710792}
711793
712794export { MutableObject , MutableContentEvents } ;
713- export type { MutableObjectConfig } ;
795+ export type { MutableObjectConfig } ;
796+ export type { StateCheckpoint } ;
0 commit comments