@@ -40,6 +40,9 @@ export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
4040// Work variables to avoid garbage collection
4141let $startIndex = 0 ;
4242
43+ /** Factor when to cleanup underlying array buffer after shrinking. */
44+ const CLEANUP_THRESHOLD = 2 ;
45+
4346/**
4447 * Typed array based bufferline implementation.
4548 *
@@ -333,42 +336,69 @@ export class BufferLine implements IBufferLine {
333336 }
334337 }
335338
336- public resize ( cols : number , fillCellData : ICellData ) : void {
339+ /**
340+ * Resize BufferLine to `cols` filling excess cells with `fillCellData`.
341+ * The underlying array buffer will not change if there is still enough space
342+ * to hold the new buffer line data.
343+ * Returns a boolean indicating, whether a `cleanupMemory` call would free
344+ * excess memory (true after shrinking > CLEANUP_THRESHOLD).
345+ */
346+ public resize ( cols : number , fillCellData : ICellData ) : boolean {
337347 if ( cols === this . length ) {
338- return ;
348+ return this . _data . length * 4 * CLEANUP_THRESHOLD < this . _data . buffer . byteLength ;
339349 }
350+ const uint32Cells = cols * CELL_SIZE ;
340351 if ( cols > this . length ) {
341- const data = new Uint32Array ( cols * CELL_SIZE ) ;
342- if ( this . length ) {
343- if ( cols * CELL_SIZE < this . _data . length ) {
344- data . set ( this . _data . subarray ( 0 , cols * CELL_SIZE ) ) ;
345- } else {
346- data . set ( this . _data ) ;
347- }
352+ if ( this . _data . buffer . byteLength >= uint32Cells * 4 ) {
353+ // optimization: avoid alloc and data copy if buffer has enough room
354+ this . _data = new Uint32Array ( this . _data . buffer , 0 , uint32Cells ) ;
355+ } else {
356+ // slow path: new alloc and full data copy
357+ const data = new Uint32Array ( uint32Cells ) ;
358+ data . set ( this . _data ) ;
359+ this . _data = data ;
348360 }
349- this . _data = data ;
350361 for ( let i = this . length ; i < cols ; ++ i ) {
351362 this . setCell ( i , fillCellData ) ;
352363 }
353364 } else {
354- if ( cols ) {
355- const data = new Uint32Array ( cols * CELL_SIZE ) ;
356- data . set ( this . _data . subarray ( 0 , cols * CELL_SIZE ) ) ;
357- this . _data = data ;
358- // Remove any cut off combined data, FIXME: repeat this for extended attrs
359- const keys = Object . keys ( this . _combined ) ;
360- for ( let i = 0 ; i < keys . length ; i ++ ) {
361- const key = parseInt ( keys [ i ] , 10 ) ;
362- if ( key >= cols ) {
363- delete this . _combined [ key ] ;
364- }
365+ // optimization: just shrink the view on existing buffer
366+ this . _data = this . _data . subarray ( 0 , uint32Cells ) ;
367+ // Remove any cut off combined data
368+ const keys = Object . keys ( this . _combined ) ;
369+ for ( let i = 0 ; i < keys . length ; i ++ ) {
370+ const key = parseInt ( keys [ i ] , 10 ) ;
371+ if ( key >= cols ) {
372+ delete this . _combined [ key ] ;
373+ }
374+ }
375+ // remove any cut off extended attributes
376+ const extKeys = Object . keys ( this . _extendedAttrs ) ;
377+ for ( let i = 0 ; i < extKeys . length ; i ++ ) {
378+ const key = parseInt ( extKeys [ i ] , 10 ) ;
379+ if ( key >= cols ) {
380+ delete this . _extendedAttrs [ key ] ;
365381 }
366- } else {
367- this . _data = new Uint32Array ( 0 ) ;
368- this . _combined = { } ;
369382 }
370383 }
371384 this . length = cols ;
385+ return uint32Cells * 4 * CLEANUP_THRESHOLD < this . _data . buffer . byteLength ;
386+ }
387+
388+ /**
389+ * Cleanup underlying array buffer.
390+ * A cleanup will be triggered if the array buffer exceeds the actual used
391+ * memory by a factor of CLEANUP_THRESHOLD.
392+ * Returns 0 or 1 indicating whether a cleanup happened.
393+ */
394+ public cleanupMemory ( ) : number {
395+ if ( this . _data . length * 4 * CLEANUP_THRESHOLD < this . _data . buffer . byteLength ) {
396+ const data = new Uint32Array ( this . _data . length ) ;
397+ data . set ( this . _data ) ;
398+ this . _data = data ;
399+ return 1 ;
400+ }
401+ return 0 ;
372402 }
373403
374404 /** fill a line with fillCharData */
0 commit comments