Эх сурвалжийг харах

introducing CircularList

Almost works already, has problems in the initial line
devmil 4 жил өмнө
parent
commit
fb96a9900e

+ 24 - 36
lib/buffer/buffer.dart

@@ -3,6 +3,7 @@ import 'dart:math' show max, min;
 import 'package:xterm/buffer/buffer_line.dart';
 import 'package:xterm/terminal/charset.dart';
 import 'package:xterm/terminal/terminal.dart';
+import 'package:xterm/utli/circular_list.dart';
 import 'package:xterm/utli/scroll_range.dart';
 import 'package:xterm/utli/unicode_v11.dart';
 
@@ -10,9 +11,13 @@ class Buffer {
   Buffer(this.terminal) {
     resetVerticalMargins();
 
-    lines = List.generate(
-      terminal.viewHeight,
-      (_) => _newEmptyLine(),
+    lines = CircularList(
+      terminal.maxLines,
+      (BufferLine? line) {
+        line?.clear();
+        line?.ensure(terminal.viewWidth);
+      },
+      (int) => _newEmptyLine(),
     );
   }
 
@@ -21,7 +26,7 @@ class Buffer {
 
   /// lines of the buffer. the length of [lines] should always be equal or
   /// greater than [Terminal.viewHeight].
-  late final List<BufferLine> lines;
+  late final CircularList<BufferLine> lines;
 
   int? _savedCursorX;
   int? _savedCursorY;
@@ -109,7 +114,7 @@ class Buffer {
   /// [Terminal.viewHeight].
   BufferLine getViewLine(int index) {
     index = index.clamp(0, terminal.viewHeight - 1);
-    return lines[convertViewLineToRawLine(index)];
+    return lines[convertViewLineToRawLine(index)]!;
   }
 
   BufferLine get currentLine {
@@ -168,7 +173,7 @@ class Buffer {
     for (var i = height - terminal.viewHeight; i < height; i++) {
       final y = i - scrollOffsetFromBottom;
       if (y >= 0 && y < height) {
-        result.add(lines[y]);
+        result.add(lines[y]!);
       }
     }
 
@@ -269,18 +274,12 @@ class Buffer {
     // the cursor is not in the scrollable region
     if (_cursorY >= terminal.viewHeight - 1) {
       // we are at the bottom so a new line is created.
-      lines.add(_newEmptyLine());
+      lines.addNew();
 
       // keep viewport from moving if user is scrolling.
       if (isUserScrolling) {
         _scrollOffsetFromBottom++;
       }
-
-      // clean extra lines if needed.
-      final maxLines = terminal.maxLines;
-      if (maxLines != null && lines.length > maxLines) {
-        lines.removeRange(0, lines.length - maxLines);
-      }
     } else {
       // there're still lines so we simply move cursor down.
       moveCursorY(1);
@@ -419,16 +418,13 @@ class Buffer {
       return;
     }
 
-    lines.removeRange(0, lines.length - terminal.viewHeight);
+    lines.trimStart(lines.length - terminal.viewHeight);
   }
 
   void clear() {
-    lines.clear();
-
-    lines.addAll(List.generate(
-      terminal.viewHeight,
-      (_) => _newEmptyLine(),
-    ));
+    for (int i = 0; i < terminal.viewHeight; i++) {
+      lines.addNew();
+    }
   }
 
   void insertBlankCharacters(int count) {
@@ -454,20 +450,12 @@ class Buffer {
     if (!isInScrollableRegion) {
       final index = convertViewLineToRawLine(_cursorX);
       final newLine = _newEmptyLine();
-      lines.insert(index, newLine);
-
-      final maxLines = terminal.maxLines;
-      if (maxLines != null && lines.length > maxLines) {
-        lines.removeRange(0, lines.length - maxLines);
-      }
+      lines.splice(index, 0, [newLine]);
     } else {
       final bottom = convertViewLineToRawLine(marginBottom);
 
-      final movedLines = lines.getRange(_cursorY, bottom - 1);
-      lines.setRange(_cursorY + 1, bottom, movedLines);
-
       final newLine = _newEmptyLine();
-      lines[_cursorY] = newLine;
+      lines.splice(_cursorY, 0, [newLine]);
     }
   }
 
@@ -490,21 +478,21 @@ class Buffer {
       return;
     }
 
-    lines.removeAt(index);
+    lines.splice(index, 1, []);
   }
 
   void resize(int newWidth, int newHeight) {
     if (newWidth > terminal.viewWidth) {
-      for (var line in lines) {
-        line.ensure(newWidth);
-      }
+      lines.forEach((item, index) {
+        item?.ensure(newWidth);
+      }, true);
     }
 
     if (newHeight > terminal.viewHeight) {
       // Grow larger
       for (var i = 0; i < newHeight - terminal.viewHeight; i++) {
         if (_cursorY < terminal.viewHeight - 1) {
-          lines.add(_newEmptyLine());
+          lines.addNew();
         } else {
           _cursorY++;
         }
@@ -513,7 +501,7 @@ class Buffer {
       // Shrink smaller
       for (var i = 0; i < terminal.viewHeight - newHeight; i++) {
         if (_cursorY < terminal.viewHeight - 1) {
-          lines.removeLast();
+          lines.pop();
         } else {
           _cursorY++;
         }

+ 1 - 1
lib/buffer/buffer_line.dart

@@ -85,7 +85,7 @@ class BufferLine {
   }
 
   void clear() {
-    _cells.buffer.asInt64List().clear();
+    removeRange(0, (_cells.lengthInBytes / _cellSize).floor());
   }
 
   void erase(Cursor cursor, int start, int end) {

+ 6 - 9
lib/terminal/terminal.dart

@@ -38,10 +38,8 @@ class Terminal with Observable {
     this.onIconChange = _defaultIconHandler,
     this.platform = PlatformBehaviors.unix,
     this.theme = TerminalThemes.defaultTheme,
-    int? maxLines,
-  }) {
-    _maxLines = maxLines;
-
+    required int maxLines,
+  }) : _maxLines = maxLines {
     _mainBuffer = Buffer(this);
     _altBuffer = Buffer(this);
     _buffer = _mainBuffer;
@@ -65,10 +63,9 @@ class Terminal with Observable {
     }
   }
 
-  int? _maxLines;
-  int? get maxLines {
-    if (_maxLines == null) return null;
-    return max(viewHeight, _maxLines!);
+  int _maxLines;
+  int get maxLines {
+    return max(viewHeight, _maxLines);
   }
 
   int _viewWidth = 80;
@@ -452,7 +449,7 @@ class Terminal with Observable {
         break;
       }
 
-      final line = buffer.lines[row];
+      final line = buffer.lines[row]!;
 
       var xStart = 0;
       var xEnd = viewWidth - 1;

+ 168 - 0
lib/utli/circular_list.dart

@@ -0,0 +1,168 @@
+import 'package:quiver/iterables.dart';
+
+class CircularList<T> {
+  List<T?> _array;
+  int _length = 0;
+  int _startIndex = 0;
+
+  Function(int num)? onTrimmed;
+  Function(T? item) _prepareItem;
+
+  CircularList(int maxLength, Function(T? item) prepareItem,
+      [T? Function(int index)? initialValueGenerator])
+      : _array = (initialValueGenerator == null)
+            ? List<T?>.filled(maxLength, null)
+            : List<T?>.generate(maxLength, initialValueGenerator),
+        _prepareItem = prepareItem;
+
+  // Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
+  // backing array to get the element associated with the regular index.
+  int _getCyclicIndex(int index) {
+    return (_startIndex + index) % _array.length;
+  }
+
+  int get maxLength {
+    return _array.length;
+  }
+
+  set maxLength(int value) {
+    if (value <= 0)
+      throw ArgumentError.value(
+          value, 'value', 'maxLength can\'t be negative!');
+
+    if (value == _array.length) return;
+
+    // Reconstruct array, starting at index 0. Only transfer values from the
+    // indexes 0 to length.
+    final newArray = List<T?>.generate(
+        value,
+        (index) =>
+            index < _array.length ? _array[_getCyclicIndex(index)] : null);
+    _startIndex = 0;
+    _array = newArray;
+  }
+
+  int get length {
+    return _length;
+  }
+
+  set length(int value) {
+    if (value > _length) {
+      for (int i = length; i < value; i++) {
+        _array[i] = null;
+      }
+    }
+    _length = value;
+  }
+
+  void forEach(void Function(T? item, int index) callback,
+      [bool includeBuffer = false]) {
+    final len = includeBuffer ? _array.length : _length;
+    for (int i = 0; i < len; i++) {
+      callback(_array[_getCyclicIndex(i)], i);
+    }
+  }
+
+  T? operator [](int index) {
+    return _array[_getCyclicIndex(index)];
+  }
+
+  operator []=(int index, T? value) {
+    _array[_getCyclicIndex(index)] = value;
+  }
+
+  void addNew() {
+    _prepareItem(_array[_getCyclicIndex(_length)]);
+    if (_length == _array.length) {
+      _startIndex++;
+      if (_startIndex == _array.length) {
+        _startIndex = 0;
+      }
+      onTrimmed?.call(1);
+    } else {
+      _length++;
+    }
+  }
+
+  T? recycle() {
+    if (length != maxLength) {
+      throw Exception('Can only recycle when the buffer is full');
+    }
+    _startIndex = ++_startIndex & maxLength;
+
+    return _array[_getCyclicIndex(length - 1)];
+  }
+
+  /// Removes and returns the last value on the list
+  T pop() {
+    return _array[_getCyclicIndex(_length-- - 1)]!;
+  }
+
+  /// Deletes and/or inserts items at a particular index (in that order).
+  void splice(int start, int deleteCount, List<T> items) {
+    // delete items
+    if (deleteCount > 0) {
+      for (int i = start; i < _length - deleteCount; i++)
+        _array[_getCyclicIndex(i)] = _array[_getCyclicIndex(i + deleteCount)];
+      length -= deleteCount;
+    }
+    if (items.length != 0) {
+      // add items
+      for (int i = _length - 1; i >= start; i--)
+        _array[_getCyclicIndex(i + items.length)] = _array[_getCyclicIndex(i)];
+      for (int i = 0; i < items.length; i++)
+        _array[_getCyclicIndex(start + i)] = items[i];
+    }
+
+    // Adjust length as needed
+    if (_length + items.length > _array.length) {
+      int countToTrim = _length + items.length - _array.length;
+      _startIndex += countToTrim;
+      length = _array.length;
+      onTrimmed?.call(countToTrim);
+    } else {
+      _length += items.length;
+    }
+  }
+
+  void trimStart(int count) {
+    if (count > _length) count = _length;
+
+    // TODO: perhaps bug in original code, this does not clamp the value of startIndex
+    _startIndex += count;
+    _length -= count;
+    onTrimmed?.call(count);
+  }
+
+  void shiftElements(int start, int count, int offset) {
+    if (count < 0) return;
+    if (start < 0 || start >= _length)
+      throw Exception('Start argument is out of range');
+    if (start + offset < 0)
+      throw Exception('Can not shift elements in list beyond index 0');
+    if (offset > 0) {
+      for (var i = count - 1; i >= 0; i--) {
+        this[start + i + offset] = this[start + i];
+      }
+      var expandListBy = (start + count + offset) - _length;
+      if (expandListBy > 0) {
+        _length += expandListBy;
+        while (_length > _array.length) {
+          length--;
+          _startIndex++;
+          onTrimmed?.call(1);
+        }
+      }
+    } else {
+      for (var i = 0; i < count; i++) {
+        this[start + i + offset] = this[start + i];
+      }
+    }
+  }
+
+  bool get isFull => length == maxLength;
+
+  List<T> toList() {
+    return List<T>.generate(length, (index) => this[index]!);
+  }
+}

+ 3 - 3
script/benchmark.dart

@@ -35,7 +35,7 @@ class BenchmarkWrite extends Benchmark {
   }
 
   void benchmark() {
-    final terminal = Terminal();
+    final terminal = Terminal(maxLines: 40000);
     for (var i = 0; i < cycle; i++) {
       terminal.write(data);
     }
@@ -51,7 +51,7 @@ class BenchmarkWrite2 extends Benchmark {
   }
 
   void benchmark() {
-    final terminal = Terminal();
+    final terminal = Terminal(maxLines: 40000);
     for (var i = 0; i < cycle; i++) {
       terminal.write(data);
     }
@@ -71,7 +71,7 @@ class BenchmarkWriteCMatrix extends Benchmark {
   }
 
   void benchmark() {
-    final terminal = Terminal();
+    final terminal = Terminal(maxLines: 40000);
     for (var i = 0; i < cycle; i++) {
       terminal.write(data);
     }