Remove more terminal from render layers and cache

This commit is contained in:
Daniel Imms
2019-07-14 12:07:43 -07:00
parent 60ae9add3f
commit cdefb3e7f2
10 changed files with 55 additions and 49 deletions
+1 -2
View File
@@ -38,7 +38,6 @@ import { SoundService } from 'browser/services/SoundService';
import { MouseZoneManager } from 'browser/MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
import { DomRenderer } from './renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
@@ -264,8 +263,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._windowsMode.dispose();
this._windowsMode = undefined;
}
this._renderService.dispose();
this._customKeyEventHandler = null;
removeTerminalFromCache(this);
this.write = () => {};
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
+14 -15
View File
@@ -8,13 +8,14 @@ import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { DynamicCharAtlas } from 'browser/renderer/atlas/DynamicCharAtlas';
import { ICharAtlasConfig } from 'browser/renderer/atlas/Types';
import { IColorSet } from 'browser/Types';
import { ITerminalOptions } from 'common/services/Services';
interface ICharAtlasCacheEntry {
atlas: BaseCharAtlas;
config: ICharAtlasConfig;
// N.B. This implementation potentially holds onto copies of the terminal forever, so
// this may cause memory leaks.
ownedBy: any[];
ownedBy: number[];
}
const charAtlasCache: ICharAtlasCacheEntry[] = [];
@@ -22,26 +23,25 @@ const charAtlasCache: ICharAtlasCacheEntry[] = [];
/**
* Acquires a char atlas, either generating a new one or returning an existing
* one that is in use by another terminal.
* @param terminal The terminal.
* @param colors The colors to use.
*/
export function acquireCharAtlas(
terminal: any,
options: ITerminalOptions,
rendererId: number,
colors: IColorSet,
scaledCharWidth: number,
scaledCharHeight: number
): BaseCharAtlas {
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal.optionsService.options, colors);
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors);
// Check to see if the terminal already owns this config
// Check to see if the renderer already owns this config
for (let i = 0; i < charAtlasCache.length; i++) {
const entry = charAtlasCache[i];
const ownedByIndex = entry.ownedBy.indexOf(terminal);
const ownedByIndex = entry.ownedBy.indexOf(rendererId);
if (ownedByIndex >= 0) {
if (configEquals(entry.config, newConfig)) {
return entry.atlas;
}
// The configs differ, release the terminal from the entry
// The configs differ, release the renderer from the entry
if (entry.ownedBy.length === 1) {
entry.atlas.dispose();
charAtlasCache.splice(i, 1);
@@ -56,8 +56,8 @@ export function acquireCharAtlas(
for (let i = 0; i < charAtlasCache.length; i++) {
const entry = charAtlasCache[i];
if (configEquals(entry.config, newConfig)) {
// Add the terminal to the cache entry and return
entry.ownedBy.push(terminal);
// Add the renderer to the cache entry and return
entry.ownedBy.push(rendererId);
return entry.atlas;
}
}
@@ -68,7 +68,7 @@ export function acquireCharAtlas(
newConfig
),
config: newConfig,
ownedBy: [terminal]
ownedBy: [rendererId]
};
charAtlasCache.push(newEntry);
return newEntry.atlas;
@@ -76,14 +76,13 @@ export function acquireCharAtlas(
/**
* Removes a terminal reference from the cache, allowing its memory to be freed.
* @param terminal The terminal to remove.
*/
export function removeTerminalFromCache(terminal: any): void {
export function removeTerminalFromCache(rendererId: number): void {
for (let i = 0; i < charAtlasCache.length; i++) {
const index = charAtlasCache[i].ownedBy.indexOf(terminal);
const index = charAtlasCache[i].ownedBy.indexOf(rendererId);
if (index !== -1) {
if (charAtlasCache[i].ownedBy.length === 1) {
// Remove the cache entry if it's the only terminal
// Remove the cache entry if it's the only renderer
charAtlasCache[i].atlas.dispose();
charAtlasCache.splice(i, 1);
} else {
+4
View File
@@ -103,6 +103,10 @@ export class RenderService extends Disposable implements IRenderService {
this._onDimensionsChange.fire(this._renderer.dimensions);
}
public dispose(): void {
this._renderer.dispose();
}
public setRenderer(renderer: IRenderer): void {
// TODO: RenderCoordinator should be the only one to dispose the renderer
this._renderer.dispose();
+2 -1
View File
@@ -8,6 +8,7 @@ import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/re
import { IColorSet } from 'browser/Types';
import { ISelectionRedrawRequestEvent } from 'browser/selection/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDisposable } from 'common/Types';
export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
export interface ICharSizeService {
@@ -31,7 +32,7 @@ export interface IMouseService {
}
export const IRenderService = createDecorator<IRenderService>('RenderService');
export interface IRenderService {
export interface IRenderService extends IDisposable {
serviceBrand: any;
onDimensionsChange: IEvent<IRenderDimensions>;
+2 -3
View File
@@ -5,7 +5,6 @@
import { IRenderLayer } from './Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { ITerminal } from '../Types';
import { ICellData } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
@@ -48,7 +47,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
zIndex: number,
private _alpha: boolean,
protected _colors: IColorSet,
protected _terminal: ITerminal,
private _rendererId: number,
protected readonly _bufferService: IBufferService,
protected readonly _optionsService: IOptionsService
) {
@@ -112,7 +111,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
return;
}
this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);
this._charAtlas = acquireCharAtlas(this._optionsService.options, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight);
this._charAtlas.warmUp();
}
+3 -2
View File
@@ -34,11 +34,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
container: HTMLElement,
zIndex: number,
colors: IColorSet,
terminal: ITerminal,
private _terminal: ITerminal,
rendererId: number,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
) {
super(container, 'cursor', zIndex, true, colors, terminal, bufferService, optionsService);
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
this._state = {
x: null,
y: null,
+2 -3
View File
@@ -3,7 +3,6 @@
* @license MIT
*/
import { ITerminal } from '../Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
@@ -18,12 +17,12 @@ export class LinkRenderLayer extends BaseRenderLayer {
container: HTMLElement,
zIndex: number,
colors: IColorSet,
terminal: ITerminal,
rendererId: number,
linkifier: ILinkifier,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
) {
super(container, 'link', zIndex, true, colors, terminal, bufferService, optionsService);
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
linkifier.onLinkHover(e => this._onLinkHover(e));
linkifier.onLinkLeave(e => this._onLinkLeave(e));
}
+10 -4
View File
@@ -15,8 +15,13 @@ import { Disposable } from 'common/Lifecycle';
import { IColorSet } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
let nextRendererId = 1;
export class Renderer extends Disposable implements IRenderer {
private _id = nextRendererId++;
private _renderLayers: IRenderLayer[];
private _devicePixelRatio: number;
private _characterJoinerRegistry: ICharacterJoinerRegistry;
@@ -35,10 +40,10 @@ export class Renderer extends Disposable implements IRenderer {
this._characterJoinerRegistry = new CharacterJoinerRegistry(bufferService);
this._renderLayers = [
new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._terminal, bufferService, optionsService),
new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._terminal, bufferService, optionsService),
new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal, this._terminal.linkifier, bufferService, optionsService),
new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, bufferService, optionsService)
new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, bufferService, optionsService),
new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, bufferService, optionsService),
new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, bufferService, optionsService),
new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, bufferService, optionsService)
];
this.dimensions = {
scaledCharWidth: null,
@@ -62,6 +67,7 @@ export class Renderer extends Disposable implements IRenderer {
public dispose(): void {
super.dispose();
this._renderLayers.forEach(l => l.dispose());
removeTerminalFromCache(this._id);
}
public onDevicePixelRatioChange(): void {
+7 -8
View File
@@ -3,7 +3,6 @@
* @license MIT
*/
import { ITerminal } from '../Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { IColorSet } from 'browser/Types';
@@ -23,11 +22,11 @@ export class SelectionRenderLayer extends BaseRenderLayer {
container: HTMLElement,
zIndex: number,
colors: IColorSet,
terminal: ITerminal,
rendererId: number,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
) {
super(container, 'selection', zIndex, true, colors, terminal, bufferService, optionsService);
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
this._clearState();
}
@@ -72,10 +71,10 @@ export class SelectionRenderLayer extends BaseRenderLayer {
const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
// No need to draw the selection
if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
return;
}
@@ -89,17 +88,17 @@ export class SelectionRenderLayer extends BaseRenderLayer {
} else {
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
// Draw middle rows
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
this._fillCells(0, viewportCappedStartRow + 1, this._terminal.cols, middleRowsCount);
this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
this._fillCells(0, viewportCappedEndRow, endCol, 1);
}
}
+10 -11
View File
@@ -4,7 +4,6 @@
*/
import { ICharacterJoinerRegistry, IRenderDimensions } from 'browser/renderer/Types';
import { ITerminal } from '../Types';
import { CharData, ICellData } from 'common/Types';
import { GridCache } from 'browser/renderer/GridCache';
import { BaseRenderLayer } from './BaseRenderLayer';
@@ -36,11 +35,11 @@ export class TextRenderLayer extends BaseRenderLayer {
colors: IColorSet,
characterJoinerRegistry: ICharacterJoinerRegistry,
alpha: boolean,
terminal: ITerminal,
rendererId: number,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
) {
super(container, 'text', zIndex, alpha, colors, terminal, bufferService, optionsService);
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService);
this._state = new GridCache<CharData>();
this._characterJoinerRegistry = characterJoinerRegistry;
}
@@ -57,7 +56,7 @@ export class TextRenderLayer extends BaseRenderLayer {
}
// Resizing the canvas discards the contents of the canvas so clear state
this._state.clear();
this._state.resize(this._terminal.cols, this._terminal.rows);
this._state.resize(this._bufferService.cols, this._bufferService.rows);
}
public reset(): void {
@@ -76,10 +75,10 @@ export class TextRenderLayer extends BaseRenderLayer {
) => void
): void {
for (let y = firstRow; y <= lastRow; y++) {
const row = y + this._terminal.buffer.ydisp;
const line = this._terminal.buffer.lines.get(row);
const row = y + this._bufferService.buffer.ydisp;
const line = this._bufferService.buffer.lines.get(row);
const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : [];
for (let x = 0; x < this._terminal.cols; x++) {
for (let x = 0; x < this._bufferService.cols; x++) {
line.loadCell(x, this._workCell);
let cell = this._workCell;
@@ -154,7 +153,7 @@ export class TextRenderLayer extends BaseRenderLayer {
*/
private _drawBackground(firstRow: number, lastRow: number): void {
const ctx = this._ctx;
const cols = this._terminal.cols;
const cols = this._bufferService.cols;
let startX: number = 0;
let startY: number = 0;
let prevFillStyle: string | null = null;
@@ -235,7 +234,7 @@ export class TextRenderLayer extends BaseRenderLayer {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
} else {
let fg = cell.getFgColor();
if (this._terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
fg += 8;
}
this._ctx.fillStyle = this._colors.ansi[fg].css;
@@ -258,13 +257,13 @@ export class TextRenderLayer extends BaseRenderLayer {
this._charAtlas.beginFrame();
}
this._clearCells(0, firstRow, this._terminal.cols, lastRow - firstRow + 1);
this._clearCells(0, firstRow, this._bufferService.cols, lastRow - firstRow + 1);
this._drawBackground(firstRow, lastRow);
this._drawForeground(firstRow, lastRow);
}
public onOptionsChanged(): void {
this._setTransparency(this._terminal.options.allowTransparency);
this._setTransparency(this._optionsService.options.allowTransparency);
}
/**