Use ILogService in TaskQueue

This commit is contained in:
Daniel Imms
2026-02-05 06:40:50 -08:00
parent 5741e450e6
commit 2e776c0820
17 changed files with 77 additions and 50 deletions
+3 -1
View File
@@ -8,6 +8,7 @@ import { ITerminalOptions, Terminal } from '@xterm/xterm';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from './Types';
import { generateConfig, configEquals } from './CharAtlasUtils';
import type { ILogService } from 'common/services/Services';
interface ITextureAtlasCacheEntry {
atlas: ITextureAtlas;
@@ -67,8 +68,9 @@ export function acquireTextureAtlas(
}
const core: ITerminal = (terminal as any)._core;
const logService = (core as any)._logService as ILogService;
const newEntry: ITextureAtlasCacheEntry = {
atlas: new TextureAtlas(document, newConfig, core.unicodeService),
atlas: new TextureAtlas(document, newConfig, core.unicodeService, logService),
config: newConfig,
ownedBy: [terminal]
};
+4 -3
View File
@@ -14,7 +14,7 @@ import { IdleTaskQueue } from 'common/TaskQueue';
import { IColor } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
import { IUnicodeService } from 'common/services/Services';
import { ILogService, IUnicodeService } from 'common/services/Services';
import { Emitter } from 'common/Event';
/**
@@ -88,7 +88,8 @@ export class TextureAtlas implements ITextureAtlas {
constructor(
private readonly _document: Document,
private readonly _config: ICharAtlasConfig,
private readonly _unicodeService: IUnicodeService
private readonly _unicodeService: IUnicodeService,
private readonly _logService: ILogService
) {
this._createNewPage();
this._tmpCanvas = createCanvas(
@@ -119,7 +120,7 @@ export class TextureAtlas implements ITextureAtlas {
private _doWarmUp(): void {
// Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks
const queue = new IdleTaskQueue();
const queue = new IdleTaskQueue(this._logService);
for (let i = 33; i < 126; i++) {
queue.enqueue(() => {
if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) {
+5 -2
View File
@@ -9,7 +9,7 @@ import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { DebouncedIdleTask } from 'common/TaskQueue';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { IBufferService, ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
import { Emitter } from 'common/Event';
interface ISelectionState {
@@ -27,7 +27,7 @@ export class RenderService extends Disposable implements IRenderService {
private _renderer: MutableDisposable<IRenderer> = this._register(new MutableDisposable());
private _renderDebouncer: IRenderDebouncerWithCallback;
private _pausedResizeTask = new DebouncedIdleTask();
private _pausedResizeTask: DebouncedIdleTask;
private _observerDisposable = this._register(new MutableDisposable());
private _isPaused: boolean = false;
@@ -58,6 +58,7 @@ export class RenderService extends Disposable implements IRenderService {
private _rowCount: number,
screenElement: HTMLElement,
@IOptionsService private readonly _optionsService: IOptionsService,
@ILogService private readonly _logService: ILogService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@ICoreService private readonly _coreService: ICoreService,
@IDecorationService decorationService: IDecorationService,
@@ -67,6 +68,8 @@ export class RenderService extends Disposable implements IRenderService {
) {
super();
this._pausedResizeTask = new DebouncedIdleTask(this._logService);
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end), this._coreBrowserService);
this._register(this._renderDebouncer);
+2 -2
View File
@@ -107,10 +107,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this._instantiationService = new InstantiationService();
this.optionsService = this._register(new OptionsService(options));
this._instantiationService.setService(IOptionsService, this.optionsService);
this._bufferService = this._register(this._instantiationService.createInstance(BufferService));
this._instantiationService.setService(IBufferService, this._bufferService);
this._logService = this._register(this._instantiationService.createInstance(LogService));
this._instantiationService.setService(ILogService, this._logService);
this._bufferService = this._register(this._instantiationService.createInstance(BufferService));
this._instantiationService.setService(IBufferService, this._bufferService);
this.coreService = this._register(this._instantiationService.createInstance(CoreService));
this._instantiationService.setService(ICoreService, this.coreService);
this.coreMouseService = this._register(this._instantiationService.createInstance(CoreMouseService));
+3 -3
View File
@@ -65,7 +65,7 @@ describe('InputHandler', () => {
beforeEach(() => {
optionsService = new MockOptionsService();
bufferService = new BufferService(optionsService);
bufferService = new BufferService(optionsService, new MockLogService());
bufferService.resize(80, 30);
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
oscLinkService = new OscLinkService(bufferService);
@@ -2458,7 +2458,7 @@ describe('InputHandler', () => {
beforeEach(() => {
optionsService = new MockOptionsService({ vtExtensions: { kittyKeyboard: true } });
bufferService = new BufferService(optionsService);
bufferService = new BufferService(optionsService, new MockLogService());
bufferService.resize(80, 30);
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
@@ -2508,7 +2508,7 @@ describe('InputHandler', () => {
beforeEach(() => {
optionsService = new MockOptionsService();
bufferService = new BufferService(optionsService);
bufferService = new BufferService(optionsService, new MockLogService());
bufferService.resize(80, 30);
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
coreService.onData(data => { console.log(data); });
+3 -2
View File
@@ -5,6 +5,7 @@
import { assert } from 'chai';
import { SortedList } from 'common/SortedList';
import { MockLogService } from 'common/TestUtils.test';
const deepStrictEqual = assert.deepStrictEqual;
@@ -15,7 +16,7 @@ describe('SortedList', () => {
}
beforeEach(() => {
list = new SortedList<number>(e => e);
list = new SortedList<number>(e => e, new MockLogService());
});
describe('insert', () => {
@@ -90,7 +91,7 @@ describe('SortedList', () => {
assertList([]);
});
it('custom key', () => {
const customList = new SortedList<{ key: number }>(e => e.key);
const customList = new SortedList<{ key: number }>(e => e.key, new MockLogService());
customList.insert({ key: 5 });
customList.insert({ key: 2 });
customList.insert({ key: 10 });
+7 -3
View File
@@ -4,6 +4,7 @@
*/
import { IdleTaskQueue } from 'common/TaskQueue';
import type { ILogService } from 'common/services/Services';
// Work variables to avoid garbage collection.
let i = 0;
@@ -18,16 +19,19 @@ export class SortedList<T> {
private _array: T[] = [];
private readonly _insertedValues: T[] = [];
private readonly _flushInsertedTask = new IdleTaskQueue();
private readonly _flushInsertedTask: InstanceType<typeof IdleTaskQueue>;
private _isFlushingInserted = false;
private readonly _deletedIndices: number[] = [];
private readonly _flushDeletedTask = new IdleTaskQueue();
private readonly _flushDeletedTask: InstanceType<typeof IdleTaskQueue>;
private _isFlushingDeleted = false;
constructor(
private readonly _getKey: (value: T) => number
private readonly _getKey: (value: T) => number,
logService: ILogService
) {
this._flushInsertedTask = new IdleTaskQueue(logService);
this._flushDeletedTask = new IdleTaskQueue(logService);
}
public clear(): void {
+9 -3
View File
@@ -4,6 +4,7 @@
*/
import { isNode } from 'common/Platform';
import type { ILogService } from 'common/services/Services';
interface ITaskQueue {
/**
@@ -34,6 +35,11 @@ abstract class TaskQueue implements ITaskQueue {
private _tasks: (() => boolean | void)[] = [];
private _idleCallback?: number;
private _i = 0;
protected readonly _logService: ILogService;
constructor(logService: ILogService) {
this._logService = logService;
}
protected abstract _requestCallback(callback: CallbackWithDeadline): number;
protected abstract _cancelCallback(identifier: number): void;
@@ -90,7 +96,7 @@ abstract class TaskQueue implements ITaskQueue {
// Warn when the time exceeding the deadline is over 20ms, if this happens in practice the
// task should be split into sub-tasks to ensure the UI remains responsive.
if (lastDeadlineRemaining - taskDuration < -20) {
console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(lastDeadlineRemaining - taskDuration))}ms`);
this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(lastDeadlineRemaining - taskDuration))}ms`);
}
this._start();
return;
@@ -151,8 +157,8 @@ export const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? Idle
export class DebouncedIdleTask {
private _queue: ITaskQueue;
constructor() {
this._queue = new IdleTaskQueue();
constructor(logService: ILogService) {
this._queue = new IdleTaskQueue(logService);
}
public set(task: () => boolean | void): void {
+1 -1
View File
@@ -27,7 +27,7 @@ export class MockBufferService implements IBufferService {
public rows: number,
optionsService: IOptionsService = new MockOptionsService()
) {
this.buffers = new BufferSet(optionsService, this);
this.buffers = new BufferSet(optionsService, this, new MockLogService());
// Listen to buffer activation events and automatically fire scroll events
this.buffers.onBufferActivate(e => {
this._onScroll.fire(e.activeBuffer.ydisp);
+7 -7
View File
@@ -6,7 +6,7 @@
import { assert } from 'chai';
import { Buffer } from 'common/buffer/Buffer';
import { CircularList } from 'common/CircularList';
import { MockOptionsService, MockBufferService } from 'common/TestUtils.test';
import { MockOptionsService, MockBufferService, MockLogService } from 'common/TestUtils.test';
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { ExtendedAttrs } from 'common/buffer/AttributeData';
@@ -23,7 +23,7 @@ describe('Buffer', () => {
beforeEach(() => {
optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK });
bufferService = new MockBufferService(INIT_COLS, INIT_ROWS);
buffer = new Buffer(true, optionsService, bufferService);
buffer = new Buffer(true, optionsService, bufferService, new MockLogService());
});
describe('constructor', () => {
@@ -151,7 +151,7 @@ describe('Buffer', () => {
describe('no scrollback', () => {
it('should trim from the top of the buffer when the cursor reaches the bottom', () => {
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService);
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
assert.equal(buffer.lines.maxLength, INIT_ROWS);
buffer.y = INIT_ROWS - 1;
buffer.fillViewportRows();
@@ -1054,7 +1054,7 @@ describe('Buffer', () => {
describe('buffer marked to have no scrollback', () => {
it('should always have a scrollback of 0', () => {
// Test size on initialization
buffer = new Buffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService);
buffer = new Buffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService, new MockLogService());
buffer.fillViewportRows();
assert.equal(buffer.lines.maxLength, INIT_ROWS);
// Test size on buffer increase
@@ -1068,7 +1068,7 @@ describe('Buffer', () => {
describe('addMarker', () => {
it('should adjust a marker line when the buffer is trimmed', () => {
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService);
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
buffer.fillViewportRows();
const marker = buffer.addMarker(buffer.lines.length - 1);
assert.equal(marker.line, buffer.lines.length - 1);
@@ -1076,7 +1076,7 @@ describe('Buffer', () => {
assert.equal(marker.line, buffer.lines.length - 2);
});
it('should dispose of a marker if it is trimmed off the buffer', () => {
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService);
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
buffer.fillViewportRows();
assert.equal(buffer.markers.length, 0);
const marker = buffer.addMarker(0);
@@ -1088,7 +1088,7 @@ describe('Buffer', () => {
});
it('should call onDispose', () => {
const eventStack: string[] = [];
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService);
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
buffer.fillViewportRows();
assert.equal(buffer.markers.length, 0);
const marker = buffer.addMarker(0);
+6 -5
View File
@@ -14,7 +14,7 @@ import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR,
import { Marker } from 'common/buffer/Marker';
import { IBuffer } from 'common/buffer/Types';
import { DEFAULT_CHARSET } from 'common/data/Charsets';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { IBufferService, ILogService, IOptionsService } from 'common/services/Services';
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
@@ -48,11 +48,14 @@ export class Buffer implements IBuffer {
private _cols: number;
private _rows: number;
private _isClearing: boolean = false;
private _memoryCleanupQueue: InstanceType<typeof IdleTaskQueue>;
private _memoryCleanupPosition = 0;
constructor(
private _hasScrollback: boolean,
private _optionsService: IOptionsService,
private _bufferService: IBufferService
private _bufferService: IBufferService,
private readonly _logService: ILogService
) {
this._cols = this._bufferService.cols;
this._rows = this._bufferService.rows;
@@ -60,6 +63,7 @@ export class Buffer implements IBuffer {
this.scrollTop = 0;
this.scrollBottom = this._rows - 1;
this.setupTabStops();
this._memoryCleanupQueue = new IdleTaskQueue(this._logService);
}
public getNullCell(attr?: IAttributeData): ICellData {
@@ -277,9 +281,6 @@ export class Buffer implements IBuffer {
}
}
private _memoryCleanupQueue = new IdleTaskQueue();
private _memoryCleanupPosition = 0;
private _batchedMemoryCleanup(): boolean {
let normalRun = true;
if (this._memoryCleanupPosition >= this.lines.length) {
+3 -2
View File
@@ -6,7 +6,7 @@
import { assert } from 'chai';
import { BufferSet } from 'common/buffer/BufferSet';
import { Buffer } from 'common/buffer/Buffer';
import { MockOptionsService, MockBufferService } from 'common/TestUtils.test';
import { MockOptionsService, MockBufferService, MockLogService } from 'common/TestUtils.test';
describe('BufferSet', () => {
let bufferSet: BufferSet;
@@ -14,7 +14,8 @@ describe('BufferSet', () => {
beforeEach(() => {
bufferSet = new BufferSet(
new MockOptionsService({ scrollback: 1000 }),
new MockBufferService(80, 24)
new MockBufferService(80, 24),
new MockLogService()
);
});
+5 -4
View File
@@ -7,7 +7,7 @@ import { Disposable } from 'common/Lifecycle';
import { IAttributeData } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { IBufferService, ILogService, IOptionsService } from 'common/services/Services';
import { Emitter } from 'common/Event';
/**
@@ -27,7 +27,8 @@ export class BufferSet extends Disposable implements IBufferSet {
*/
constructor(
private readonly _optionsService: IOptionsService,
private readonly _bufferService: IBufferService
private readonly _bufferService: IBufferService,
private readonly _logService: ILogService
) {
super();
this.reset();
@@ -36,12 +37,12 @@ export class BufferSet extends Disposable implements IBufferSet {
}
public reset(): void {
this._normal = new Buffer(true, this._optionsService, this._bufferService);
this._normal = new Buffer(true, this._optionsService, this._bufferService, this._logService);
this._normal.fillViewportRows();
// The alt buffer should never have scrollback.
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
this._alt = new Buffer(false, this._optionsService, this._bufferService);
this._alt = new Buffer(false, this._optionsService, this._bufferService, this._logService);
this._activeBuffer = this._normal;
this._onBufferActivate.fire({
activeBuffer: this._normal,
+6 -3
View File
@@ -7,7 +7,7 @@ import { Disposable } from 'common/Lifecycle';
import { IAttributeData, IBufferLine } from 'common/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IBufferService, IOptionsService, type IBufferResizeEvent } from 'common/services/Services';
import { IBufferService, ILogService, IOptionsService, type IBufferResizeEvent } from 'common/services/Services';
import { Emitter } from 'common/Event';
export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
@@ -32,11 +32,14 @@ export class BufferService extends Disposable implements IBufferService {
/** An IBufferline to clone/copy from for new blank lines */
private _cachedBlankLine: IBufferLine | undefined;
constructor(@IOptionsService optionsService: IOptionsService) {
constructor(
@IOptionsService optionsService: IOptionsService,
@ILogService logService: ILogService
) {
super();
this.cols = Math.max(optionsService.rawOptions.cols || 0, MINIMUM_COLS);
this.rows = Math.max(optionsService.rawOptions.rows || 0, MINIMUM_ROWS);
this.buffers = this._register(new BufferSet(optionsService, this));
this.buffers = this._register(new BufferSet(optionsService, this, logService));
this._register(this.buffers.onBufferActivate(e => {
this._onScroll.fire(e.activeBuffer.ydisp);
}));
@@ -8,6 +8,7 @@ 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';
function createFakeMarker(line: number): IMarker {
return Object.freeze(new class extends Disposable {
@@ -22,7 +23,7 @@ const fakeMarker: IMarker = createFakeMarker(1);
describe('DecorationService', () => {
it('should set isDisposed to true after dispose', () => {
const service = new DecorationService();
const service = new DecorationService(new MockLogService());
const decoration = service.registerDecoration({
marker: fakeMarker
});
@@ -34,7 +35,7 @@ describe('DecorationService', () => {
describe('forEachDecorationAtCell', () => {
it('should find decoration at its marker line', () => {
const service = new DecorationService();
const service = new DecorationService(new MockLogService());
const decoration = service.registerDecoration({
marker: createFakeMarker(5),
width: 10
@@ -47,7 +48,7 @@ describe('DecorationService', () => {
});
it('should find decoration with height > 1 on subsequent lines', () => {
const service = new DecorationService();
const service = new DecorationService(new MockLogService());
const decoration = service.registerDecoration({
marker: createFakeMarker(5),
width: 10,
@@ -73,7 +74,7 @@ describe('DecorationService', () => {
});
it('should not find decoration outside its x range', () => {
const service = new DecorationService();
const service = new DecorationService(new MockLogService());
const decoration = service.registerDecoration({
marker: createFakeMarker(5),
x: 5,
@@ -102,7 +103,7 @@ describe('DecorationService', () => {
describe('getDecorationsAtCell', () => {
it('should find decoration with height > 1 on subsequent lines', () => {
const service = new DecorationService();
const service = new DecorationService(new MockLogService());
const decoration = service.registerDecoration({
marker: createFakeMarker(5),
width: 10,
+5 -3
View File
@@ -5,7 +5,7 @@
import { css } from 'common/Color';
import { Disposable, DisposableStore, toDisposable } from 'common/Lifecycle';
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
import { IDecorationService, IInternalDecoration, ILogService } from 'common/services/Services';
import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
import { IDecoration, IDecorationOptions, IMarker } from '@xterm/xterm';
@@ -25,7 +25,7 @@ export class DecorationService extends Disposable implements IDecorationService
* while marker line values do change, they should all change by the same amount so this should
* never become out of order.
*/
private readonly _decorations: SortedList<IInternalDecoration> = new SortedList(e => e?.marker.line);
private readonly _decorations: SortedList<IInternalDecoration>;
private readonly _onDecorationRegistered = this._register(new Emitter<IInternalDecoration>());
public readonly onDecorationRegistered = this._onDecorationRegistered.event;
@@ -34,9 +34,11 @@ export class DecorationService extends Disposable implements IDecorationService
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
constructor() {
constructor(@ILogService private readonly _logService: ILogService) {
super();
this._decorations = new SortedList(e => e?.marker.line, this._logService);
this._register(toDisposable(() => this.reset()));
}
+2 -1
View File
@@ -9,6 +9,7 @@ import { BufferService } from 'common/services/BufferService';
import { OptionsService } from 'common/services/OptionsService';
import { OscLinkService } from 'common/services/OscLinkService';
import { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';
import { MockLogService } from 'common/TestUtils.test';
describe('OscLinkService', () => {
describe('constructor', () => {
@@ -17,7 +18,7 @@ describe('OscLinkService', () => {
let oscLinkService: IOscLinkService;
beforeEach(() => {
optionsService = new OptionsService({ rows: 3, cols: 10 });
bufferService = new BufferService(optionsService);
bufferService = new BufferService(optionsService, new MockLogService());
oscLinkService = new OscLinkService(bufferService);
});