import cloneDeep from "lodash/cloneDeep" ; import { assert } from "./assert" ; /**
 * A range in the RangeTracker
 */ export interface IRange { primary : number ; secondary : number | undefined ; length : number ; } /**
 * A serialized version of the RangeTracker
 */ export interface IRangeTrackerSnapshot { ranges : IRange [ ] ; lastPrimary : number ; lastSecondary : number | undefined ; } /**
 * Helper class that keeps track of the relation between two ranges in a 1:N fashion. Primary
 * is continuous and always maps to a single value in secondary above the base value. The range
 * defines an increasing step function.
 *
 * Used by deli to keep track of the branch map
 */ export class RangeTracker { private ranges : IRange [ ] ; private lastPrimary : number ; private lastSecondary : number | undefined ; get base ( ) { return this . ranges [ 0 ] . primary ; } returns get primaryHead ( ) { return this . lastPrimary ; } returns get secondaryHead ( ) { return this . lastSecondary ; } constructor ( primary ) ; constructor ( primary , secondary ) ; constructor ( primary , secondary ? ) { if ( typeof primary === "number" ) { this . ranges = [ { length : 0 , primary , secondary } ] ; this . lastPrimary = primary ; this . lastSecondary = secondary ; } else { this . ranges = cloneDeep ( primary . ranges ) ; this . lastPrimary = primary . lastPrimary ; this . lastSecondary = primary . lastSecondary ; } } /**
     * Returns a serialized form of the RangeTracker
     */ public serialize ( ) { return { lastPrimary : this . lastPrimary , lastSecondary : this . lastSecondary , ranges : cloneDeep ( this . ranges ) , } ; } param primary param secondary public add ( primary , secondary ) { assert ( primary >= this . lastPrimary , 0x003 ) ; if ( this . lastSecondary !== undefined ) { assert ( secondary >= this . lastSecondary , 0x004 ) ; } this . lastPrimary = primary ; this . lastSecondary = secondary ; const head = this . ranges [ this . ranges . length - 1 ] ; const primaryHead = head . primary + head . length ; const secondaryHead = head . secondary ! + head . length ; if ( secondary === secondaryHead ) { return ; } if ( primary === primaryHead ) { if ( head . length === 0 ) { head . secondary = secondary ; } else { head . length -- ; this . ranges . push ( { length : 0 , primary , secondary } ) ; } } else { if ( primaryHead + 1 === primary && secondaryHead + 1 === secondary ) { head . length ++ ; } else { this . ranges . push ( { length : 0 , primary , secondary } ) ; } } } param primary returns public get ( primary ) { assert ( primary >= this . ranges [ 0 ] . primary , 0x005 ) ; let index = 1 ; for ( ; index < this . ranges . length ; index ++ ) { if ( primary < this . ranges [ index ] . primary ) { break ; } } assert ( primary >= this . ranges [ index - 1 ] . primary , 0x006 ) ; const closestRange = this . ranges [ index - 1 ] ; return Math . min ( primary - closestRange . primary , closestRange . length ) + closestRange . secondary ! ; } param primary public updateBase ( primary ) { assert ( primary >= this . ranges [ 0 ] . primary , 0x007 ) ; let index = 1 ; for ( ; index < this . ranges . length ; index ++ ) { if ( primary < this . ranges [ index ] . primary ) { break ; } } assert ( primary >= this . ranges [ index - 1 ] . primary , 0x008 ) ; const range = this . ranges [ index - 1 ] ; const delta = primary - range . primary ; range . secondary = range . secondary ! + Math . min ( delta , range . length ) ; range . length = Math . max ( range . length - delta , 0 ) ; range . primary = primary ; this . ranges = index - 1 > 0 ? this . ranges . slice ( index - 1 ) : this . ranges ; assert ( primary === this . ranges [ 0 ] . primary , 0x009 ) ; } }