Speed up decoration cell lookup with a per-line index
Refs #5176. forEachDecorationAtCell and getDecorationsAtCell previously scanned every registered decoration on each call. With thousands of search highlights this becomes O(total decorations) per cell and blocks rendering during viewport refreshes. Maintain a Map of buffer line -> decorations that cover that line. Multi-line decorations are indexed on every line in their span, so mixed single-line and multi-line workloads stay fast without falling back to a full scan. Keep the index aligned with buffer mutations: - Register and unregister decorations in the line buckets on add/dispose. - On trim, shift line keys synchronously (before marker handlers run). - On insert/delete, re-index affected decorations in a microtask after marker line updates are applied. Inject IBufferService so line listeners follow the active buffer. Add unit tests for trim, dispose-on-trim, insert, and dense single-line plus multi-line lookup. Add DecorationService.benchmark.ts to measure sparse hits and viewport-sized grid scans with 20k decorations. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,7 +8,9 @@ import { DecorationService } from './DecorationService';
|
||||
import { IMarker } from 'common/Types';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { Emitter } from 'common/Event';
|
||||
import { MockLogService } from 'common/TestUtils.test';
|
||||
import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
|
||||
function createFakeMarker(line: number): IMarker {
|
||||
return Object.freeze(new class extends Disposable {
|
||||
@@ -19,11 +21,16 @@ function createFakeMarker(line: number): IMarker {
|
||||
}());
|
||||
}
|
||||
|
||||
function createDecorationService(): DecorationService {
|
||||
const bufferService = new MockBufferService(80, 24, new MockOptionsService());
|
||||
return new DecorationService(new MockLogService(), bufferService);
|
||||
}
|
||||
|
||||
const fakeMarker: IMarker = createFakeMarker(1);
|
||||
|
||||
describe('DecorationService', () => {
|
||||
it('should set isDisposed to true after dispose', () => {
|
||||
const service = new DecorationService(new MockLogService());
|
||||
const service = createDecorationService();
|
||||
const decoration = service.registerDecoration({
|
||||
marker: fakeMarker
|
||||
});
|
||||
@@ -35,7 +42,7 @@ describe('DecorationService', () => {
|
||||
|
||||
describe('forEachDecorationAtCell', () => {
|
||||
it('should find decoration at its marker line', () => {
|
||||
const service = new DecorationService(new MockLogService());
|
||||
const service = createDecorationService();
|
||||
const decoration = service.registerDecoration({
|
||||
marker: createFakeMarker(5),
|
||||
width: 10
|
||||
@@ -48,7 +55,7 @@ describe('DecorationService', () => {
|
||||
});
|
||||
|
||||
it('should find decoration with height > 1 on subsequent lines', () => {
|
||||
const service = new DecorationService(new MockLogService());
|
||||
const service = createDecorationService();
|
||||
const decoration = service.registerDecoration({
|
||||
marker: createFakeMarker(5),
|
||||
width: 10,
|
||||
@@ -74,7 +81,7 @@ describe('DecorationService', () => {
|
||||
});
|
||||
|
||||
it('should not find decoration outside its x range', () => {
|
||||
const service = new DecorationService(new MockLogService());
|
||||
const service = createDecorationService();
|
||||
const decoration = service.registerDecoration({
|
||||
marker: createFakeMarker(5),
|
||||
x: 5,
|
||||
@@ -99,11 +106,35 @@ describe('DecorationService', () => {
|
||||
service.forEachDecorationAtCell(8, 5, undefined, d => foundAtX8.push(d));
|
||||
assert.strictEqual(foundAtX8.length, 0);
|
||||
});
|
||||
|
||||
it('should find multi-line decoration when single-line decorations exist on other lines', () => {
|
||||
const bufferService = new MockBufferService(80, 24, new MockOptionsService());
|
||||
const serviceWithBuffer = new DecorationService(new MockLogService(), bufferService);
|
||||
const buffer = bufferService.buffer;
|
||||
(buffer as Buffer).fillViewportRows();
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
serviceWithBuffer.registerDecoration({
|
||||
marker: buffer.addMarker(i),
|
||||
width: 5
|
||||
});
|
||||
}
|
||||
const multiLine = serviceWithBuffer.registerDecoration({
|
||||
marker: buffer.addMarker(10),
|
||||
width: 10,
|
||||
height: 3
|
||||
});
|
||||
assert.ok(multiLine);
|
||||
|
||||
const found: typeof multiLine[] = [];
|
||||
serviceWithBuffer.forEachDecorationAtCell(0, 11, undefined, d => found.push(d));
|
||||
assert.include(found, multiLine);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDecorationsAtCell', () => {
|
||||
it('should find decoration with height > 1 on subsequent lines', () => {
|
||||
const service = new DecorationService(new MockLogService());
|
||||
const service = createDecorationService();
|
||||
const decoration = service.registerDecoration({
|
||||
marker: createFakeMarker(5),
|
||||
width: 10,
|
||||
@@ -117,4 +148,66 @@ describe('DecorationService', () => {
|
||||
assert.strictEqual([...service.getDecorationsAtCell(0, 8)].length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('line index maintenance', () => {
|
||||
it('should keep lookups correct after buffer trim', () => {
|
||||
const bufferService = new MockBufferService(80, 5, new MockOptionsService({ scrollback: 0 }));
|
||||
const service = new DecorationService(new MockLogService(), bufferService);
|
||||
const buffer = bufferService.buffer;
|
||||
(buffer as Buffer).fillViewportRows();
|
||||
|
||||
const marker = buffer.addMarker(buffer.lines.length - 1);
|
||||
const decoration = service.registerDecoration({ marker, width: 10 });
|
||||
assert.ok(decoration);
|
||||
|
||||
buffer.lines.onTrimEmitter.fire(1);
|
||||
|
||||
const found: typeof decoration[] = [];
|
||||
service.forEachDecorationAtCell(0, marker.line, undefined, d => found.push(d));
|
||||
assert.strictEqual(found.length, 1);
|
||||
});
|
||||
|
||||
it('should remove decoration from line index when marker is trimmed off buffer', () => {
|
||||
const bufferService = new MockBufferService(80, 5, new MockOptionsService({ scrollback: 0 }));
|
||||
const service = new DecorationService(new MockLogService(), bufferService);
|
||||
const buffer = bufferService.buffer;
|
||||
(buffer as Buffer).fillViewportRows();
|
||||
|
||||
const marker = buffer.addMarker(0);
|
||||
const decoration = service.registerDecoration({ marker, width: 10 });
|
||||
assert.ok(decoration);
|
||||
|
||||
buffer.lines.onTrimEmitter.fire(1);
|
||||
assert.isTrue(marker.isDisposed);
|
||||
assert.isTrue(decoration!.isDisposed);
|
||||
|
||||
const found: typeof decoration[] = [];
|
||||
service.forEachDecorationAtCell(0, 0, undefined, d => found.push(d));
|
||||
assert.strictEqual(found.length, 0);
|
||||
});
|
||||
|
||||
it('should keep multi-line decoration indexed after line insert', async () => {
|
||||
const bufferService = new MockBufferService(80, 10, new MockOptionsService({ scrollback: 100 }));
|
||||
const service = new DecorationService(new MockLogService(), bufferService);
|
||||
const buffer = bufferService.buffer;
|
||||
(buffer as Buffer).fillViewportRows();
|
||||
|
||||
const marker = buffer.addMarker(3);
|
||||
const decoration = service.registerDecoration({ marker, width: 10, height: 3 });
|
||||
assert.ok(decoration);
|
||||
|
||||
buffer.lines.splice(5, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));
|
||||
await new Promise<void>(resolve => queueMicrotask(resolve));
|
||||
|
||||
const foundOnSpan: typeof decoration[] = [];
|
||||
for (let line = marker.line; line < marker.line + 3; line++) {
|
||||
service.forEachDecorationAtCell(0, line, undefined, d => foundOnSpan.push(d));
|
||||
}
|
||||
assert.include(foundOnSpan, decoration);
|
||||
|
||||
const foundOutsideSpan: typeof decoration[] = [];
|
||||
service.forEachDecorationAtCell(0, marker.line + 3, undefined, d => foundOutsideSpan.push(d));
|
||||
assert.strictEqual(foundOutsideSpan.length, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,18 +4,17 @@
|
||||
*/
|
||||
|
||||
import { css } from 'common/Color';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'common/Lifecycle';
|
||||
import { IDecorationService, IInternalDecoration, ILogService } from 'common/services/Services';
|
||||
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IDecorationService, IInternalDecoration, ILogService } from 'common/services/Services';
|
||||
import { SortedList } from 'common/SortedList';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IDecoration, IDecorationOptions, IMarker } from '@xterm/xterm';
|
||||
import { Emitter } from 'common/Event';
|
||||
import type { IDeleteEvent, IInsertEvent } from 'common/CircularList';
|
||||
|
||||
// Work variables to avoid garbage collection
|
||||
let $xmin = 0;
|
||||
let $xmax = 0;
|
||||
let $ymin = 0;
|
||||
let $ymax = 0;
|
||||
|
||||
export class DecorationService extends Disposable implements IDecorationService {
|
||||
public serviceBrand: any;
|
||||
@@ -27,6 +26,14 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
*/
|
||||
private readonly _decorations: SortedList<IInternalDecoration>;
|
||||
|
||||
/**
|
||||
* Decorations indexed by buffer line. Multi-line decorations are present in every line bucket
|
||||
* they span so cell lookup only iterates decorations relevant to that line.
|
||||
*/
|
||||
private readonly _decorationsByLine: Map<number, IInternalDecoration[]> = new Map();
|
||||
|
||||
private readonly _bufferLineListeners = this._register(new MutableDisposable<DisposableStore>());
|
||||
|
||||
private readonly _onDecorationRegistered = this._register(new Emitter<IInternalDecoration>());
|
||||
public readonly onDecorationRegistered = this._onDecorationRegistered.event;
|
||||
private readonly _onDecorationRemoved = this._register(new Emitter<IInternalDecoration>());
|
||||
@@ -34,12 +41,17 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
|
||||
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
|
||||
|
||||
constructor(@ILogService private readonly _logService: ILogService) {
|
||||
constructor(
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._decorations = new SortedList(e => e?.marker.line, this._logService);
|
||||
|
||||
this._register(toDisposable(() => this.reset()));
|
||||
this._register(this._bufferService.buffers.onBufferActivate(() => this._attachBufferLineListeners()));
|
||||
this._attachBufferLineListeners();
|
||||
}
|
||||
|
||||
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
|
||||
@@ -53,12 +65,14 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
listener.dispose();
|
||||
if (decoration) {
|
||||
if (this._decorations.delete(decoration)) {
|
||||
this._removeFromLineIndex(decoration);
|
||||
this._onDecorationRemoved.fire(decoration);
|
||||
}
|
||||
markerDispose.dispose();
|
||||
}
|
||||
});
|
||||
this._decorations.insert(decoration);
|
||||
this._addToLineIndex(decoration);
|
||||
this._onDecorationRegistered.fire(decoration);
|
||||
}
|
||||
return decoration;
|
||||
@@ -69,19 +83,17 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
d.dispose();
|
||||
}
|
||||
this._decorations.clear();
|
||||
this._decorationsByLine.clear();
|
||||
}
|
||||
|
||||
public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration> {
|
||||
const bucket = this._decorationsByLine.get(line);
|
||||
if (!bucket) {
|
||||
return;
|
||||
}
|
||||
let xmin = 0;
|
||||
let xmax = 0;
|
||||
let ymin = 0;
|
||||
let ymax = 0;
|
||||
for (const d of this._decorations.values()) {
|
||||
ymin = d.marker.line;
|
||||
ymax = ymin + (d.options.height ?? 1);
|
||||
if (line < ymin || line >= ymax) {
|
||||
continue;
|
||||
}
|
||||
for (const d of bucket) {
|
||||
xmin = d.options.x ?? 0;
|
||||
xmax = xmin + (d.options.width ?? 1);
|
||||
if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
|
||||
@@ -91,12 +103,11 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
}
|
||||
|
||||
public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void {
|
||||
for (const d of this._decorations.values()) {
|
||||
$ymin = d.marker.line;
|
||||
$ymax = $ymin + (d.options.height ?? 1);
|
||||
if (line < $ymin || line >= $ymax) {
|
||||
continue;
|
||||
}
|
||||
const bucket = this._decorationsByLine.get(line);
|
||||
if (!bucket) {
|
||||
return;
|
||||
}
|
||||
for (const d of bucket) {
|
||||
$xmin = d.options.x ?? 0;
|
||||
$xmax = $xmin + (d.options.width ?? 1);
|
||||
if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
|
||||
@@ -104,12 +115,147 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _attachBufferLineListeners(): void {
|
||||
const store = new DisposableStore();
|
||||
this._bufferLineListeners.value = store;
|
||||
const lines = this._bufferService.buffer.lines;
|
||||
store.add(lines.onTrim(amount => this._handleBufferLinesTrim(amount)));
|
||||
store.add(lines.onInsert(event => this._handleBufferLinesInsert(event)));
|
||||
store.add(lines.onDelete(event => this._handleBufferLinesDelete(event)));
|
||||
}
|
||||
|
||||
private _getDecorationHeight(decoration: IInternalDecoration): number {
|
||||
return decoration.options.height ?? 1;
|
||||
}
|
||||
|
||||
private _addToLineIndex(decoration: IInternalDecoration): void {
|
||||
const start = decoration.marker.line;
|
||||
if (start < 0) {
|
||||
return;
|
||||
}
|
||||
decoration._indexedStartLine = start;
|
||||
const height = this._getDecorationHeight(decoration);
|
||||
for (let line = start; line < start + height; line++) {
|
||||
let bucket = this._decorationsByLine.get(line);
|
||||
if (!bucket) {
|
||||
bucket = [];
|
||||
this._decorationsByLine.set(line, bucket);
|
||||
}
|
||||
bucket.push(decoration);
|
||||
}
|
||||
}
|
||||
|
||||
private _removeFromLineIndex(decoration: IInternalDecoration): void {
|
||||
const start = decoration._indexedStartLine;
|
||||
const height = this._getDecorationHeight(decoration);
|
||||
for (let line = start; line < start + height; line++) {
|
||||
const bucket = this._decorationsByLine.get(line);
|
||||
if (!bucket) {
|
||||
continue;
|
||||
}
|
||||
const index = bucket.indexOf(decoration);
|
||||
if (index !== -1) {
|
||||
bucket.splice(index, 1);
|
||||
}
|
||||
if (bucket.length === 0) {
|
||||
this._decorationsByLine.delete(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _reindexDecoration(decoration: IInternalDecoration): void {
|
||||
this._removeFromLineIndex(decoration);
|
||||
if (!decoration.marker.isDisposed && decoration.marker.line >= 0) {
|
||||
this._addToLineIndex(decoration);
|
||||
}
|
||||
}
|
||||
|
||||
private _lineIndexSyncCallbacks: (() => void)[] = [];
|
||||
|
||||
/** Re-index after marker line updates (buffer listeners may run before markers). */
|
||||
private _scheduleLineIndexSync(callback: () => void): void {
|
||||
this._lineIndexSyncCallbacks.push(callback);
|
||||
if (this._lineIndexSyncCallbacks.length > 1) {
|
||||
return;
|
||||
}
|
||||
queueMicrotask(() => {
|
||||
const callbacks = this._lineIndexSyncCallbacks;
|
||||
this._lineIndexSyncCallbacks = [];
|
||||
for (const cb of callbacks) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _handleBufferLinesTrim(amount: number): void {
|
||||
if (amount <= 0) {
|
||||
return;
|
||||
}
|
||||
const newMap = new Map<number, IInternalDecoration[]>();
|
||||
for (const [line, bucket] of this._decorationsByLine) {
|
||||
const newLine = line - amount;
|
||||
if (newLine < 0) {
|
||||
continue;
|
||||
}
|
||||
const existing = newMap.get(newLine);
|
||||
if (existing) {
|
||||
existing.push(...bucket);
|
||||
} else {
|
||||
newMap.set(newLine, bucket.slice());
|
||||
}
|
||||
}
|
||||
this._decorationsByLine.clear();
|
||||
for (const [line, bucket] of newMap) {
|
||||
this._decorationsByLine.set(line, bucket);
|
||||
}
|
||||
for (const d of this._decorations.values()) {
|
||||
if (!d.marker.isDisposed) {
|
||||
d._indexedStartLine -= amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handleBufferLinesInsert(event: IInsertEvent): void {
|
||||
this._scheduleLineIndexSync(() => {
|
||||
for (const d of this._decorations.values()) {
|
||||
if (d.marker.isDisposed) {
|
||||
continue;
|
||||
}
|
||||
const height = this._getDecorationHeight(d);
|
||||
if (d.marker.line + height > event.index) {
|
||||
this._reindexDecoration(d);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _handleBufferLinesDelete(event: IDeleteEvent): void {
|
||||
const deleteEnd = event.index + event.amount;
|
||||
this._scheduleLineIndexSync(() => {
|
||||
for (const d of this._decorations.values()) {
|
||||
if (d.marker.isDisposed) {
|
||||
continue;
|
||||
}
|
||||
const start = d.marker.line;
|
||||
const end = start + this._getDecorationHeight(d);
|
||||
if (start >= deleteEnd) {
|
||||
this._reindexDecoration(d);
|
||||
} else if (start < event.index && end > event.index) {
|
||||
this._reindexDecoration(d);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Decoration extends DisposableStore implements IInternalDecoration {
|
||||
public readonly marker: IMarker;
|
||||
public element: HTMLElement | undefined;
|
||||
|
||||
/** Start line used for line-index removal when marker.line is cleared on dispose. */
|
||||
public _indexedStartLine: number;
|
||||
|
||||
public readonly onRenderEmitter = this.add(new Emitter<HTMLElement>());
|
||||
public readonly onRender = this.onRenderEmitter.event;
|
||||
private readonly _onDispose = this.add(new Emitter<void>());
|
||||
@@ -144,6 +290,7 @@ class Decoration extends DisposableStore implements IInternalDecoration {
|
||||
) {
|
||||
super();
|
||||
this.marker = options.marker;
|
||||
this._indexedStartLine = options.marker.line;
|
||||
if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) {
|
||||
this.options.overviewRulerOptions.position = 'full';
|
||||
}
|
||||
|
||||
@@ -396,4 +396,6 @@ export interface IInternalDecoration extends IDecoration {
|
||||
readonly backgroundColorRGB: IColor | undefined;
|
||||
readonly foregroundColorRGB: IColor | undefined;
|
||||
readonly onRenderEmitter: Emitter<HTMLElement>;
|
||||
/** @internal Start line for line-index removal; kept in sync on buffer line shifts. */
|
||||
_indexedStartLine: number;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { perfContext, before, RuntimeCase } from 'xterm-benchmark';
|
||||
import { DecorationService } from 'common/services/DecorationService';
|
||||
import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
|
||||
const enum Constants {
|
||||
COLS = 80,
|
||||
ROWS = 30,
|
||||
SINGLE_LINE_DECORATION_COUNT = 20_000,
|
||||
MULTI_LINE_DECORATION_COUNT = 19_999,
|
||||
MULTI_LINE_HEIGHT = 2,
|
||||
VIEWPORT_SCAN_ITERATIONS = 20
|
||||
}
|
||||
|
||||
function registerSingleLineDecorations(service: DecorationService, bufferService: MockBufferService, count: number): void {
|
||||
const buffer = bufferService.buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const line = i % 5000;
|
||||
const marker = buffer.addMarker(line);
|
||||
service.registerDecoration({ marker, width: Constants.COLS });
|
||||
}
|
||||
}
|
||||
|
||||
function registerMixedDecorations(service: DecorationService, bufferService: MockBufferService): void {
|
||||
const buffer = bufferService.buffer;
|
||||
for (let i = 0; i < Constants.MULTI_LINE_DECORATION_COUNT; i++) {
|
||||
const line = i % 5000;
|
||||
const marker = buffer.addMarker(line);
|
||||
service.registerDecoration({ marker, width: Constants.COLS });
|
||||
}
|
||||
const multiLineMarker = buffer.addMarker(10);
|
||||
service.registerDecoration({
|
||||
marker: multiLineMarker,
|
||||
width: Constants.COLS,
|
||||
height: Constants.MULTI_LINE_HEIGHT
|
||||
});
|
||||
}
|
||||
|
||||
function scanVisibleGrid(service: DecorationService, rows: number, cols: number): number {
|
||||
let hitCount = 0;
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
service.forEachDecorationAtCell(col, row, undefined, () => {
|
||||
hitCount++;
|
||||
});
|
||||
}
|
||||
}
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
perfContext('DecorationService.forEachDecorationAtCell', () => {
|
||||
perfContext('single-line dense / sparse line hit', () => {
|
||||
let service: DecorationService;
|
||||
let bufferService: MockBufferService;
|
||||
before(() => {
|
||||
bufferService = new MockBufferService(Constants.COLS, Constants.ROWS, new MockOptionsService());
|
||||
service = new DecorationService(new MockLogService(), bufferService);
|
||||
registerSingleLineDecorations(service, bufferService, Constants.SINGLE_LINE_DECORATION_COUNT);
|
||||
});
|
||||
new RuntimeCase('', () => {
|
||||
let hitCount = 0;
|
||||
for (let i = 0; i < Constants.VIEWPORT_SCAN_ITERATIONS; i++) {
|
||||
service.forEachDecorationAtCell(0, 0, undefined, () => {
|
||||
hitCount++;
|
||||
});
|
||||
}
|
||||
return { payloadSize: Constants.VIEWPORT_SCAN_ITERATIONS, hitCount };
|
||||
}, { fork: false }).showAverageRuntime();
|
||||
});
|
||||
|
||||
perfContext('mixed single-line + multi-line / sparse line hit', () => {
|
||||
let service: DecorationService;
|
||||
let bufferService: MockBufferService;
|
||||
before(() => {
|
||||
bufferService = new MockBufferService(Constants.COLS, Constants.ROWS, new MockOptionsService());
|
||||
service = new DecorationService(new MockLogService(), bufferService);
|
||||
registerMixedDecorations(service, bufferService);
|
||||
});
|
||||
new RuntimeCase('', () => {
|
||||
let hitCount = 0;
|
||||
for (let i = 0; i < Constants.VIEWPORT_SCAN_ITERATIONS; i++) {
|
||||
service.forEachDecorationAtCell(0, 10, undefined, () => {
|
||||
hitCount++;
|
||||
});
|
||||
}
|
||||
return { payloadSize: Constants.VIEWPORT_SCAN_ITERATIONS, hitCount };
|
||||
}, { fork: false }).showAverageRuntime();
|
||||
});
|
||||
|
||||
perfContext('viewport grid scan', () => {
|
||||
let service: DecorationService;
|
||||
let bufferService: MockBufferService;
|
||||
before(() => {
|
||||
bufferService = new MockBufferService(Constants.COLS, Constants.ROWS, new MockOptionsService());
|
||||
service = new DecorationService(new MockLogService(), bufferService);
|
||||
registerSingleLineDecorations(service, bufferService, Constants.SINGLE_LINE_DECORATION_COUNT);
|
||||
});
|
||||
new RuntimeCase('', () => {
|
||||
let totalHits = 0;
|
||||
for (let i = 0; i < Constants.VIEWPORT_SCAN_ITERATIONS; i++) {
|
||||
totalHits += scanVisibleGrid(service, Constants.ROWS, Constants.COLS);
|
||||
}
|
||||
return { payloadSize: Constants.VIEWPORT_SCAN_ITERATIONS * Constants.ROWS * Constants.COLS, totalHits };
|
||||
}, { fork: false }).showAverageRuntime();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user