From 1965daff504a2ec1ff8c32827e76e80d11b728e2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 24 Aug 2023 06:24:25 -0700 Subject: [PATCH 01/19] Warn on eslint rule @typescript-eslint/no-useless-constructor --- .eslintrc.json | 1 + addons/xterm-addon-fit/src/FitAddon.ts | 2 -- addons/xterm-addon-serialize/src/SerializeAddon.ts | 2 -- src/browser/Linkifier2.test.ts | 4 ---- src/common/Lifecycle.ts | 3 --- src/common/public/AddonManager.ts | 3 --- 6 files changed, 1 insertion(+), 14 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 7e8be09c..d206927e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -127,6 +127,7 @@ { "selector": "typeLike", "format": ["PascalCase"] }, { "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] } ], + "@typescript-eslint/no-useless-constructor": "warn", "@typescript-eslint/prefer-namespace-keyword": "warn", "@typescript-eslint/type-annotation-spacing": "warn", "@typescript-eslint/quotes": [ diff --git a/addons/xterm-addon-fit/src/FitAddon.ts b/addons/xterm-addon-fit/src/FitAddon.ts index 6b3df6f0..f7245462 100644 --- a/addons/xterm-addon-fit/src/FitAddon.ts +++ b/addons/xterm-addon-fit/src/FitAddon.ts @@ -24,8 +24,6 @@ const MINIMUM_ROWS = 1; export class FitAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - constructor() {} - public activate(terminal: Terminal): void { this._terminal = terminal; } diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index ace31315..7a2db089 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -414,8 +414,6 @@ class StringSerializeHandler extends BaseSerializeHandler { export class SerializeAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - constructor() { } - public activate(terminal: Terminal): void { this._terminal = terminal; } diff --git a/src/browser/Linkifier2.test.ts b/src/browser/Linkifier2.test.ts index c1a0cf6c..0af74c28 100644 --- a/src/browser/Linkifier2.test.ts +++ b/src/browser/Linkifier2.test.ts @@ -10,10 +10,6 @@ import { MockBufferService } from 'common/TestUtils.test'; import { ILink } from 'browser/Types'; class TestLinkifier2 extends Linkifier2 { - constructor(bufferService: IBufferService) { - super(bufferService); - } - public set currentLink(link: any) { this._currentLink = link; } diff --git a/src/common/Lifecycle.ts b/src/common/Lifecycle.ts index 659403de..6e5ef27d 100644 --- a/src/common/Lifecycle.ts +++ b/src/common/Lifecycle.ts @@ -13,9 +13,6 @@ export abstract class Disposable implements IDisposable { protected _disposables: IDisposable[] = []; protected _isDisposed: boolean = false; - constructor() { - } - /** * Disposes the object, triggering the `dispose` method on all registered IDisposables. */ diff --git a/src/common/public/AddonManager.ts b/src/common/public/AddonManager.ts index 06c78121..af04a269 100644 --- a/src/common/public/AddonManager.ts +++ b/src/common/public/AddonManager.ts @@ -14,9 +14,6 @@ export interface ILoadedAddon { export class AddonManager implements IDisposable { protected _addons: ILoadedAddon[] = []; - constructor() { - } - public dispose(): void { for (let i = this._addons.length - 1; i >= 0; i--) { this._addons[i].instance.dispose(); From 2fe082beea9a10a3cad0e890253dd19d72f44e86 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 24 Aug 2023 06:33:11 -0700 Subject: [PATCH 02/19] Warn on eslint rule @typescript-eslint/no-confusing-void-expression --- .eslintrc.json | 4 ++++ addons/xterm-addon-fit/test/FitAddon.api.ts | 8 ++------ src/browser/public/Terminal.ts | 2 +- src/headless/public/Terminal.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 7e8be09c..822dd9af 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -127,6 +127,10 @@ { "selector": "typeLike", "format": ["PascalCase"] }, { "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] } ], + "@typescript-eslint/no-confusing-void-expression": [ + "warn", + { "ignoreArrowShorthand": true } + ], "@typescript-eslint/prefer-namespace-keyword": "warn", "@typescript-eslint/type-annotation-spacing": "warn", "@typescript-eslint/quotes": [ diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/xterm-addon-fit/test/FitAddon.api.ts index ef4618ea..36b44211 100644 --- a/addons/xterm-addon-fit/test/FitAddon.api.ts +++ b/addons/xterm-addon-fit/test/FitAddon.api.ts @@ -41,9 +41,7 @@ describe('FitAddon', () => { }); describe('proposeDimensions', () => { - afterEach(async () => { - return await unloadFit(); - }); + afterEach(() => unloadFit()); it('default', async function(): Promise { await loadFit(); @@ -82,9 +80,7 @@ describe('FitAddon', () => { }); describe('fit', () => { - afterEach(async () => { - return await unloadFit(); - }); + afterEach(() => unloadFit()); it('default', async function(): Promise { await loadFit(); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 4db5f7aa..2c75d7b8 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -236,7 +236,7 @@ export class Terminal extends Disposable implements ITerminalApi { this._core.clearTextureAtlas(); } public loadAddon(addon: ITerminalAddon): void { - return this._addonManager.loadAddon(this, addon); + this._addonManager.loadAddon(this, addon); } public static get strings(): ILocalizableStrings { return Strings; diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 5eeeb361..b018d37c 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -182,7 +182,7 @@ export class Terminal extends Disposable implements ITerminalApi { } public loadAddon(addon: ITerminalAddon): void { // TODO: This could cause issues if the addon calls renderer apis - return this._addonManager.loadAddon(this as any, addon); + this._addonManager.loadAddon(this as any, addon); } private _verifyIntegers(...values: number[]): void { From bd76cdbd20b5cf919a88d25764062a01f786aa30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 24 Aug 2023 17:24:25 +0200 Subject: [PATCH 03/19] resort wasm to xterm-wasm-parts --- .../out/base64.wasm.js/decode/definition.json | 1 - .../out/base64.wasm.js/decode/final.wasm | Bin 654 -> 0 bytes .../out/base64.wasm.js/decode/final.wat | 210 ---------- addons/xterm-addon-image/package.json | 7 +- addons/xterm-addon-image/src/IIPHandler.ts | 2 +- .../xterm-addon-image/src/base64.benchmark.ts | 80 ---- addons/xterm-addon-image/src/base64.test.ts | 156 -------- addons/xterm-addon-image/src/base64.wasm.ts | 372 ------------------ package.json | 2 - 9 files changed, 4 insertions(+), 826 deletions(-) delete mode 100644 addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/definition.json delete mode 100644 addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wasm delete mode 100644 addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wat delete mode 100644 addons/xterm-addon-image/src/base64.benchmark.ts delete mode 100644 addons/xterm-addon-image/src/base64.test.ts delete mode 100644 addons/xterm-addon-image/src/base64.wasm.ts diff --git a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/definition.json b/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/definition.json deleted file mode 100644 index b4a652c8..00000000 --- a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/definition.json +++ /dev/null @@ -1 +0,0 @@ -{"def":{"name":"decode","type":0,"mode":1,"srctype":"Clang-C","imports":{"env":{"memory":{}}},"exports":{},"compile":{"switches":["-Wl,-z,stack-size=0","-Wl,--stack-first"]},"code":"\n typedef struct {\n unsigned int wp;\n unsigned int sp;\n unsigned int dp;\n unsigned int e_size;\n unsigned int b_size;\n unsigned int dummy[3];\n unsigned char data[0];\n } State;\n\n unsigned int *D0 = (unsigned int *) 1024;\n unsigned int *D1 = (unsigned int *) 2048;\n unsigned int *D2 = (unsigned int *) 3072;\n unsigned int *D3 = (unsigned int *) 4096;\n State *state = (State *) 5120;\n\n __attribute__((noinline)) int dec() {\n unsigned int nsp = (state->wp - 1) & ~3;\n unsigned char *src = state->data + state->sp;\n unsigned char *end = state->data + nsp;\n unsigned char *dst = state->data + state->dp;\n unsigned int accu;\n\n while (src < end) {\n if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1;\n *((unsigned int *) dst) = accu;\n dst += 3;\n src += 4;\n }\n state->sp = nsp;\n state->dp = dst - state->data;\n return 0;\n }\n\n int end() {\n int rem = state->wp - state->sp;\n if (rem > 4 && dec()) return 1;\n rem = state->wp - state->sp;\n if (rem < 2) return 1;\n\n unsigned char *src = state->data + state->sp;\n unsigned int accu = D0[src[0]] | D1[src[1]];\n int dp = 1;\n if (rem > 2 && src[2] != 61) {\n accu |= D2[src[2]];\n dp++;\n }\n if (rem == 4 && src[3] != 61) {\n accu |= D3[src[3]];\n dp++;\n }\n if (accu >> 24) return 1;\n *((unsigned int *) (state->data + state->dp)) = accu;\n state->dp += dp;\n return state->dp != state->b_size;\n }\n "},"memorySettings":{"descriptor":{"initial":1,"shared":false},"mode":"imported"},"srcDef":"{\n name: 'decode',\n type: 0 /* OutputType.INSTANCE */,\n mode: 1 /* OutputMode.SYNC */,\n srctype: 'Clang-C',\n imports: {\n env: { memory: new WebAssembly.Memory({ initial: 1 }) }\n },\n exports: {\n dec: () => 0,\n end: () => 0\n },\n compile: {\n switches: ['-Wl,-z,stack-size=0', '-Wl,--stack-first']\n },\n code: `\n typedef struct {\n unsigned int wp;\n unsigned int sp;\n unsigned int dp;\n unsigned int e_size;\n unsigned int b_size;\n unsigned int dummy[3];\n unsigned char data[0];\n } State;\n\n unsigned int *D0 = (unsigned int *) ${256 /* P32.D0 */ * 4};\n unsigned int *D1 = (unsigned int *) ${512 /* P32.D1 */ * 4};\n unsigned int *D2 = (unsigned int *) ${768 /* P32.D2 */ * 4};\n unsigned int *D3 = (unsigned int *) ${1024 /* P32.D3 */ * 4};\n State *state = (State *) ${1280 /* P32.STATE */ * 4};\n\n __attribute__((noinline)) int dec() {\n unsigned int nsp = (state->wp - 1) & ~3;\n unsigned char *src = state->data + state->sp;\n unsigned char *end = state->data + nsp;\n unsigned char *dst = state->data + state->dp;\n unsigned int accu;\n\n while (src < end) {\n if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1;\n *((unsigned int *) dst) = accu;\n dst += 3;\n src += 4;\n }\n state->sp = nsp;\n state->dp = dst - state->data;\n return 0;\n }\n\n int end() {\n int rem = state->wp - state->sp;\n if (rem > 4 && dec()) return 1;\n rem = state->wp - state->sp;\n if (rem < 2) return 1;\n\n unsigned char *src = state->data + state->sp;\n unsigned int accu = D0[src[0]] | D1[src[1]];\n int dp = 1;\n if (rem > 2 && src[2] != 61) {\n accu |= D2[src[2]];\n dp++;\n }\n if (rem == 4 && src[3] != 61) {\n accu |= D3[src[3]];\n dp++;\n }\n if (accu >> 24) return 1;\n *((unsigned int *) (state->data + state->dp)) = accu;\n state->dp += dp;\n return state->dp != state->b_size;\n }\n `\n}","hash":""} \ No newline at end of file diff --git a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wasm b/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wasm deleted file mode 100644 index c1ac6e8ad7ef12e6efe50ac5a5979513c0932ee6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654 zcmZQbEY4+QU|?WmWlUgTtY_k9WKPX1W6Mp=%`d8CVqj!uW@2Dq=Vf9}Nlj*8U{1|T zVPNFi$jr2kk&(6Du|q?HiNSG!MwTL@V+)9_#NgNfq8%Bt9cv1em^@e<6qvvYm=z!j z8JHau7<3t!9hpirm>Lv7Y$ga>0>oy7umwPD1_+y@sHjMZ#qt0D|Loo@4vvic+zN~e zEM`m$3XG1-S&EDb42~>WN(>6jp1cg)+>R|83QT5942~Td3XF~mG_pZ#2JU~1jBNEx z4iMKV!Cjl}$m$0&Q;}JKftL{?3-gO21GgiiBC`Uc0zoMdKVVPT$<*>XppGMpu=C1Sd^Yx5}%fuSW;S)TFj))pIcgz an3R*Mo1T-Ol$cYj%~710o~N5yQ33#*5{exF diff --git a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wat b/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wat deleted file mode 100644 index d7832f45..00000000 --- a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wat +++ /dev/null @@ -1,210 +0,0 @@ -(module - (type (;0;) (func (result i32))) - (import "env" "memory" (memory (;0;) 1)) - (func (;0;) (type 0) (result i32) - (local i32 i32 i32 i32 i32) - i32.const 5128 - i32.load - i32.const 5152 - i32.add - local.set 1 - i32.const 5124 - i32.load - local.tee 0 - i32.const 5120 - i32.load - i32.const 1 - i32.sub - i32.const -4 - i32.and - local.tee 2 - i32.lt_s - if ;; label = @1 - local.get 2 - i32.const 5152 - i32.add - local.set 3 - local.get 0 - i32.const 5152 - i32.add - local.set 0 - loop ;; label = @2 - local.get 0 - i32.load8_u offset=3 - i32.const 2 - i32.shl - i32.load offset=4096 - local.get 0 - i32.load8_u offset=2 - i32.const 2 - i32.shl - i32.load offset=3072 - local.get 0 - i32.load8_u offset=1 - i32.const 2 - i32.shl - i32.load offset=2048 - local.get 0 - i32.load8_u - i32.const 2 - i32.shl - i32.load offset=1024 - i32.or - i32.or - i32.or - local.tee 4 - i32.const 16777215 - i32.gt_u - if ;; label = @3 - i32.const 1 - return - end - local.get 1 - local.get 4 - i32.store - local.get 1 - i32.const 3 - i32.add - local.set 1 - local.get 0 - i32.const 4 - i32.add - local.tee 0 - local.get 3 - i32.lt_u - br_if 0 (;@2;) - end - end - i32.const 5124 - local.get 2 - i32.store - i32.const 5128 - local.get 1 - i32.const 5152 - i32.sub - i32.store - i32.const 0) - (func (;1;) (type 0) (result i32) - (local i32 i32 i32 i32 i32 i32) - block ;; label = @1 - i32.const 5120 - i32.load - local.tee 1 - i32.const 5124 - i32.load - local.tee 0 - i32.sub - i32.const 5 - i32.ge_s - if ;; label = @2 - i32.const 1 - local.set 3 - call 0 - br_if 1 (;@1;) - i32.const 5120 - i32.load - local.set 1 - i32.const 5124 - i32.load - local.set 0 - end - i32.const 1 - local.set 3 - local.get 1 - local.get 0 - i32.sub - local.tee 4 - i32.const 2 - i32.lt_s - br_if 0 (;@1;) - local.get 0 - i32.const 5153 - i32.add - i32.load8_u - i32.const 2 - i32.shl - i32.load offset=2048 - local.get 0 - i32.const 5152 - i32.add - i32.load8_u - i32.const 2 - i32.shl - i32.load offset=1024 - i32.or - local.set 1 - block ;; label = @2 - local.get 4 - i32.const 2 - i32.eq - if ;; label = @3 - i32.const 1 - local.set 2 - br 1 (;@2;) - end - i32.const 1 - local.set 2 - local.get 0 - i32.load8_u offset=5154 - local.tee 5 - i32.const 61 - i32.ne - if ;; label = @3 - i32.const 2 - local.set 2 - local.get 5 - i32.const 2 - i32.shl - i32.load offset=3072 - local.get 1 - i32.or - local.set 1 - end - local.get 4 - i32.const 4 - i32.ne - br_if 0 (;@2;) - local.get 0 - i32.load8_u offset=5155 - local.tee 0 - i32.const 61 - i32.eq - br_if 0 (;@2;) - local.get 2 - i32.const 1 - i32.add - local.set 2 - local.get 0 - i32.const 2 - i32.shl - i32.load offset=4096 - local.get 1 - i32.or - local.set 1 - end - local.get 1 - i32.const 16777215 - i32.gt_u - br_if 0 (;@1;) - i32.const 5128 - i32.load - i32.const 5152 - i32.add - local.get 1 - i32.store - i32.const 5128 - i32.const 5128 - i32.load - local.get 2 - i32.add - local.tee 0 - i32.store - local.get 0 - i32.const 5136 - i32.load - i32.ne - local.set 3 - end - local.get 3) - (export "dec" (func 0)) - (export "end" (func 1))) diff --git a/addons/xterm-addon-image/package.json b/addons/xterm-addon-image/package.json index 69b87b38..8de858b5 100644 --- a/addons/xterm-addon-image/package.json +++ b/addons/xterm-addon-image/package.json @@ -17,8 +17,7 @@ "xterm.js" ], "scripts": { - "inwasm": "inwasm out/*.wasm.js", - "prepackage": "../../node_modules/.bin/tsc -p . && inwasm -f out/*.wasm.js", + "prepackage": "../../node_modules/.bin/tsc -p .", "package": "../../node_modules/.bin/webpack", "prepublishOnly": "npm run package" }, @@ -26,7 +25,7 @@ "xterm": "^5.2.0" }, "devDependencies": { - "inwasm": "^0.0.13", - "sixel": "^0.16.0" + "sixel": "^0.16.0", + "xterm-wasm-parts": "^0.1.0" } } diff --git a/addons/xterm-addon-image/src/IIPHandler.ts b/addons/xterm-addon-image/src/IIPHandler.ts index a8a851e6..ae62100d 100644 --- a/addons/xterm-addon-image/src/IIPHandler.ts +++ b/addons/xterm-addon-image/src/IIPHandler.ts @@ -5,7 +5,7 @@ import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types'; import { ImageRenderer } from './ImageRenderer'; import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage'; -import { Base64Decoder } from './base64.wasm'; +import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm'; import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser'; import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics'; diff --git a/addons/xterm-addon-image/src/base64.benchmark.ts b/addons/xterm-addon-image/src/base64.benchmark.ts deleted file mode 100644 index 6870295d..00000000 --- a/addons/xterm-addon-image/src/base64.benchmark.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { ThroughputRuntimeCase, perfContext } from 'xterm-benchmark'; -import { Base64Decoder } from './base64.wasm'; - -// eslint-disable-next-line -declare const Buffer: any; - -function toBytes(s: string): Uint8Array { - const bytes = new Uint8Array(s.length); - for (let i = 0; i < s.length; ++i) { - bytes[i] = s.charCodeAt(i) & 0xFF; - } - return bytes; -} - -const d256 = 'ABCD'.repeat(64); -const d4096 = 'ABCD'.repeat(64 * 16); -const d65536 = 'ABCD'.repeat(64 * 16 * 16); -const d1M = 'ABCD'.repeat(64 * 16 * 16 * 16); -const b256 = toBytes(d256); -const b4096 = toBytes(d4096); -const b65536 = toBytes(d65536); -const b1M = toBytes(d1M); -const dec = new Base64Decoder(4000000); - - -const RUNS = 100; - -perfContext('Base64', () => { - perfContext('Node - Buffer', () => { - new ThroughputRuntimeCase('decode - 256', () => { - Buffer.from(d256, 'base64'); - return { payloadSize: d256.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 4096', () => { - Buffer.from(d4096, 'base64'); - return { payloadSize: d4096.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 65536', () => { - Buffer.from(d65536, 'base64'); - return { payloadSize: d65536.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 1048576', () => { - Buffer.from(d1M, 'base64'); - return { payloadSize: d1M.length }; - }, { repeat: RUNS }).showAverageThroughput(); - }); - - perfContext('Base64Decoder', () => { - new ThroughputRuntimeCase('decode - 256', () => { - dec.init(192); - dec.put(b256, 0, b256.length); - dec.end(); - return { payloadSize: b256.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 4096', () => { - dec.init(3072); - dec.put(b4096, 0, b4096.length); - dec.end(); - return { payloadSize: b4096.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 65536', () => { - dec.init(49152); - dec.put(b65536, 0, b65536.length); - dec.end(); - return { payloadSize: b65536.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 1048576', () => { - dec.init(786432); - dec.put(b1M, 0, b1M.length); - dec.end(); - return { payloadSize: b1M.length }; - }, { repeat: RUNS }).showAverageThroughput(); - }); -}); diff --git a/addons/xterm-addon-image/src/base64.test.ts b/addons/xterm-addon-image/src/base64.test.ts deleted file mode 100644 index 2d4e6d76..00000000 --- a/addons/xterm-addon-image/src/base64.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { assert } from 'chai'; -import { Base64Decoder } from './base64.wasm'; - -// eslint-disable-next-line -declare const Buffer: any; - - -// some helpers -function toBs(bytes: Uint8Array): string { - let bs = ''; - for (let i = 0; i < bytes.length; ++i) bs += String.fromCharCode(bytes[i]); - return bs; -} -function fromBs(bs: string): Uint8Array { - const r = new Uint8Array(bs.length); - for (let i = 0; i < r.length; ++i) r[i] = bs.charCodeAt(i); - return r; -} -function encNative(bytes: Uint8Array): string { - return typeof Buffer !== 'undefined' ? Buffer.from(bytes).toString('base64') : btoa(toBs(bytes)); -} -function rtrim(x: string, c: string): string { - let end = x.length - 1; - while (c.indexOf(x[end]) >= 0) end -= 1; - return x.slice(0, end + 1); -} -const MAP = new Uint8Array( - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - .split('') - .map(el => el.charCodeAt(0)) -); - - -describe('Base64Decoder', () => { - describe('decoding', () => { - it('single bytes', function() { - this.timeout(20000); - const dec = new Base64Decoder(0); - for (let i = 0; i < 256; ++i) { - dec.init(1); - const inp = new Uint8Array([i]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - for (let a = 0; a < 256; ++a) { - it(`1+2 bytes (${a})`, function() { - const dec = new Base64Decoder(0); - for (let b = 0; b < 256; ++b) { - dec.init(2); - const inp = new Uint8Array([a, b]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - } - for (let a = 0; a < 256; ++a) { - it(`2+3 bytes (${a})`, function() { - const dec = new Base64Decoder(0); - for (let b = 0; b < 256; ++b) { - dec.init(3); - const inp = new Uint8Array([0, a, b]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - } - for (let a = 0; a < 256; ++a) { - it(`3+4 bytes (${a})`, function() { - const dec = new Base64Decoder(0); - for (let b = 0; b < 256; ++b) { - dec.init(4); - const inp = new Uint8Array([0, 0, a, b]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - } - it('padding', () => { - const dec = new Base64Decoder(0); - const d = fromBs('Hello, here comes the mouse'); - const encData = []; - const encDataTrimmed = []; - for (let i = 1; i < d.length; ++i) { - encData.push(encNative(d.slice(0, i))); - encDataTrimmed.push(rtrim(encNative(d.slice(0, i)), '=')); - } - for (let i = 0; i < encData.length; ++i) { - // with padding - dec.init(i + 1); - let enc = fromBs(encData[i]); - assert.strictEqual(dec.put(enc, 0, enc.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, d.slice(0, i + 1)); - // w'o padding - dec.init(i + 1); - enc = fromBs(encDataTrimmed[i]); - assert.strictEqual(dec.put(enc, 0, enc.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, d.slice(0, i + 1)); - } - }); - it('exit on false byte', function() { - this.timeout(20000); - const dec = new Base64Decoder(0); - for (let pos = 0; pos < 8; ++pos) { - const inp = new Uint8Array([65, 65, 65, 65, 65, 65, 65, 65]); - for (let i = 0; i < 256; ++i) { - dec.release(); - dec.init(6); - inp[pos] = i; - dec.put(inp, 0, 8); - assert.strictEqual(dec.end(), MAP.includes(i) ? 0 : 1); - } - } - }); - }); - describe('memory', () => { - it('always release (keepSize 0)', () => { - const dec = new Base64Decoder(0); - dec.init(16); - dec.put(fromBs('A'.repeat(16)), 0, 16); - dec.end(); - assert.strictEqual(dec.data8.length, 12); - dec.release(); - assert.strictEqual(dec.data8.length, 0); - assert.isNull((dec as any)._mem); - }); - it('keep 1 page (keepSize 65536)', () => { - const dec = new Base64Decoder(65536); - dec.init(384); - dec.put(fromBs('A'.repeat(512)), 0, 512); - dec.end(); - assert.strictEqual(dec.data8.length, 384); - dec.release(); - assert.strictEqual(dec.data8.length, 0); - assert.isNotNull((dec as any)._mem); - // grow to 2 pages + free afterwards - dec.init(65536); - dec.put(fromBs('A'.repeat(65536)), 0, 65536); - dec.end(); - assert.strictEqual(dec.data8.length, 49152); - dec.release(); - assert.strictEqual(dec.data8.length, 0); - assert.isNull((dec as any)._mem); - }); - }); -}); diff --git a/addons/xterm-addon-image/src/base64.wasm.ts b/addons/xterm-addon-image/src/base64.wasm.ts deleted file mode 100644 index 02965c2d..00000000 --- a/addons/xterm-addon-image/src/base64.wasm.ts +++ /dev/null @@ -1,372 +0,0 @@ -/** - * Copyright (c) 2023 The xterm.js authors. All rights reserved. - * @license MIT - */ -import { InWasm, IWasmInstance, OutputMode, OutputType } from 'inwasm'; - - -// memory addresses in uint32 -const enum P32 { - D0 = 256, - D1 = 512, - D2 = 768, - D3 = 1024, - STATE = 1280, - STATE_WP = 1280, - STATE_SP = 1281, - STATE_DP = 1282, - STATE_ESIZE = 1283, - STATE_BSIZE = 1284, - STATE_DATA = 1288 // 16 aligned -} - -/** - * wasm base64 decoder. - */ -const wasmDecode = InWasm({ - name: 'decode', - type: OutputType.INSTANCE, - mode: OutputMode.SYNC, - srctype: 'Clang-C', - imports: { - env: { memory: new WebAssembly.Memory({ initial: 1 }) } - }, - exports: { - dec: () => 0, - end: () => 0 - }, - compile: { - switches: ['-Wl,-z,stack-size=0', '-Wl,--stack-first'] - }, - code: ` - typedef struct { - unsigned int wp; - unsigned int sp; - unsigned int dp; - unsigned int e_size; - unsigned int b_size; - unsigned int dummy[3]; - unsigned char data[0]; - } State; - - unsigned int *D0 = (unsigned int *) ${P32.D0*4}; - unsigned int *D1 = (unsigned int *) ${P32.D1*4}; - unsigned int *D2 = (unsigned int *) ${P32.D2*4}; - unsigned int *D3 = (unsigned int *) ${P32.D3*4}; - State *state = (State *) ${P32.STATE*4}; - - __attribute__((noinline)) int dec() { - unsigned int nsp = (state->wp - 1) & ~3; - unsigned char *src = state->data + state->sp; - unsigned char *end = state->data + nsp; - unsigned char *dst = state->data + state->dp; - unsigned int accu; - - while (src < end) { - if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1; - *((unsigned int *) dst) = accu; - dst += 3; - src += 4; - } - state->sp = nsp; - state->dp = dst - state->data; - return 0; - } - - int end() { - int rem = state->wp - state->sp; - if (rem > 4 && dec()) return 1; - rem = state->wp - state->sp; - if (rem < 2) return 1; - - unsigned char *src = state->data + state->sp; - unsigned int accu = D0[src[0]] | D1[src[1]]; - int dp = 1; - if (rem > 2 && src[2] != 61) { - accu |= D2[src[2]]; - dp++; - } - if (rem == 4 && src[3] != 61) { - accu |= D3[src[3]]; - dp++; - } - if (accu >> 24) return 1; - *((unsigned int *) (state->data + state->dp)) = accu; - state->dp += dp; - return state->dp != state->b_size; - } - ` -}); - -// SIMD version - commented out for now due to missing Safari support -// const wasmDecode = InWasm({ -// name: 'decode', -// type: OutputType.INSTANCE, -// mode: OutputMode.SYNC, -// srctype: 'Clang-C', -// imports: { -// env: { memory: new WebAssembly.Memory({ initial: 1 }) } -// }, -// exports: { -// dec: () => 0, -// end: () => 0 -// }, -// compile: { -// switches: ['-msimd128', '-Wl,-z,stack-size=0', '-Wl,--stack-first'] -// }, -// code: ` -// #include -// typedef struct { -// unsigned int wp; -// unsigned int sp; -// unsigned int dp; -// unsigned int e_size; -// unsigned int b_size; -// unsigned int dummy[3]; -// unsigned char data[0]; -// } State; -// -// unsigned int *D0 = (unsigned int *) ${P32.D0*4}; -// unsigned int *D1 = (unsigned int *) ${P32.D1*4}; -// unsigned int *D2 = (unsigned int *) ${P32.D2*4}; -// unsigned int *D3 = (unsigned int *) ${P32.D3*4}; -// State *state = (State *) ${P32.STATE*4}; -// -// #define packed_byte(x) wasm_i8x16_splat((char) x) -// #define packed_dword(x) wasm_i32x4_splat(x) -// #define masked(x, mask) wasm_v128_and(x, wasm_i32x4_splat(mask)) -// -// int dec4() { -// unsigned int nsp = (state->wp - 1) & ~3; -// unsigned char *src = state->data + state->sp; -// unsigned char *end = state->data + nsp; -// unsigned char *dst = state->data + state->dp; -// unsigned int accu; -// -// while (src < end) { -// if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1; -// *((unsigned int *) dst) = accu; -// dst += 3; -// src += 4; -// } -// state->sp = nsp; -// state->dp = dst - state->data; -// return 0; -// } -// -// int dec() { -// unsigned int nsp = (state->wp - 1) & ~15; -// unsigned char *src = state->data + state->sp; -// unsigned char *end = state->data + nsp; -// unsigned char *dst = state->data + state->dp; -// unsigned int accu; -// -// v128_t err = wasm_i8x16_splat(0); -// -// while (src < end) { -// v128_t data = wasm_v128_load((v128_t *) src); -// -// // wasm-simd rewrite of http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html#vector-lookup-pshufb -// const v128_t higher_nibble = wasm_u32x4_shr(data, 4) & packed_byte(0x0f); -// const char linv = 1; -// const char hinv = 0; -// -// const v128_t lower_bound_LUT = wasm_i8x16_make( -// /* 0 */ linv, /* 1 */ linv, /* 2 */ 0x2b, /* 3 */ 0x30, -// /* 4 */ 0x41, /* 5 */ 0x50, /* 6 */ 0x61, /* 7 */ 0x70, -// /* 8 */ linv, /* 9 */ linv, /* a */ linv, /* b */ linv, -// /* c */ linv, /* d */ linv, /* e */ linv, /* f */ linv -// ); -// const v128_t upper_bound_LUT = wasm_i8x16_make( -// /* 0 */ hinv, /* 1 */ hinv, /* 2 */ 0x2b, /* 3 */ 0x39, -// /* 4 */ 0x4f, /* 5 */ 0x5a, /* 6 */ 0x6f, /* 7 */ 0x7a, -// /* 8 */ hinv, /* 9 */ hinv, /* a */ hinv, /* b */ hinv, -// /* c */ hinv, /* d */ hinv, /* e */ hinv, /* f */ hinv -// ); -// // the difference between the shift and lower bound -// const v128_t shift_LUT = wasm_i8x16_make( -// /* 0 */ 0x00, /* 1 */ 0x00, /* 2 */ 0x3e - 0x2b, /* 3 */ 0x34 - 0x30, -// /* 4 */ 0x00 - 0x41, /* 5 */ 0x0f - 0x50, /* 6 */ 0x1a - 0x61, /* 7 */ 0x29 - 0x70, -// /* 8 */ 0x00, /* 9 */ 0x00, /* a */ 0x00, /* b */ 0x00, -// /* c */ 0x00, /* d */ 0x00, /* e */ 0x00, /* f */ 0x00 -// ); -// -// const v128_t upper_bound = wasm_i8x16_swizzle(upper_bound_LUT, higher_nibble); -// const v128_t lower_bound = wasm_i8x16_swizzle(lower_bound_LUT, higher_nibble); -// -// const v128_t below = wasm_i8x16_lt(data, lower_bound); -// const v128_t above = wasm_i8x16_gt(data, upper_bound); -// const v128_t eq_2f = wasm_i8x16_eq(data, packed_byte(0x2f)); -// -// // in_range = not (below or above) or eq_2f -// // outside = not in_range = below or above and not eq_2f (from deMorgan law) -// const v128_t outside = wasm_v128_andnot(eq_2f, above | below); -// err = wasm_v128_or(err, outside); -// -// const v128_t shift = wasm_i8x16_swizzle(shift_LUT, higher_nibble); -// const v128_t t0 = wasm_i8x16_add(data, shift); -// v128_t v = wasm_i8x16_add(t0, wasm_v128_and(eq_2f, packed_byte(-3))); -// -// // pack bytes -// const v128_t ca = masked(v, 0x003f003f); -// const v128_t db = masked(v, 0x3f003f00); -// const v128_t t00 = wasm_v128_or(wasm_u32x4_shr(db, 8), wasm_i32x4_shl(ca, 6)); -// v128_t res = wasm_v128_or(wasm_u32x4_shr(t00, 16), wasm_i32x4_shl(t00, 12)); -// res = wasm_i8x16_swizzle(res, wasm_i8x16_const(2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, 16, 16, 16, 16)); -// -// wasm_v128_store((v128_t *) dst, res); -// dst += 12; -// src += 16; -// } -// -// if (wasm_i8x16_bitmask(err) != 0) return 1; -// -// state->sp = nsp; -// state->dp = dst - state->data; -// return 0; -// } -// -// int end() { -// int rem = state->wp - state->sp; -// if (rem > 4 && dec4()) return 1; -// rem = state->wp - state->sp; -// if (rem < 2) return 1; -// -// unsigned char *src = state->data + state->sp; -// unsigned int accu = D0[src[0]] | D1[src[1]]; -// int dp = 1; -// if (rem > 2 && src[2] != 61) { -// accu |= D2[src[2]]; -// dp++; -// } -// if (rem == 4 && src[3] != 61) { -// accu |= D3[src[3]]; -// dp++; -// } -// if (accu >> 24) return 1; -// *((unsigned int *) (state->data + state->dp)) = accu; -// state->dp += dp; -// return state->dp != state->b_size; -// } -// ` -// }); - -// FIXME: currently broken in inwasm -type ExtractDefinition = Type extends () => IWasmInstance ? X : never; -type DecodeDefinition = ExtractDefinition; - -// base64 map -const MAP = new Uint8Array( - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - .split('') - .map(el => el.charCodeAt(0)) -); - -// init decoder maps in LE order -const D = new Uint32Array(1024); -D.fill(0xFF000000); -for (let i = 0; i < MAP.length; ++i) D[MAP[i]] = i << 2; -for (let i = 0; i < MAP.length; ++i) D[256 + MAP[i]] = i >> 4 | ((i << 4) & 0xFF) << 8; -for (let i = 0; i < MAP.length; ++i) D[512 + MAP[i]] = (i >> 2) << 8 | ((i << 6) & 0xFF) << 16; -for (let i = 0; i < MAP.length; ++i) D[768 + MAP[i]] = i << 16; - -const EMPTY = new Uint8Array(0); - -/** - * base64 streamline inplace decoder. - * - * Features / assumptions: - * - optimized uint32 read/write (only LE support!) - * - lazy chunkwise decoding - * - errors out on any non base64 chars (no support for NL formatted base64) - * - decodes in wasm - * - inplace decoding to save memory - * - supports a keepSize for lazy memory release - */ -export class Base64Decoder { - private _d!: Uint8Array; - private _m32!: Uint32Array; - private _inst!: IWasmInstance; - private _mem!: WebAssembly.Memory; - - constructor(public keepSize: number) {} - - /** - * Currently decoded bytes (borrowed). - * Must be accessed before calling `release` or `init`. - */ - public get data8(): Uint8Array { - return this._inst ? this._d.subarray(0, this._m32[P32.STATE_DP]) : EMPTY; - } - - /** - * Release memory conditionally based on `keepSize`. - * If memory gets released, also the wasm instance will be freed and recreated on next `init`, - * otherwise the instance will be reused. - */ - public release(): void { - if (!this._inst) return; - if (this._mem.buffer.byteLength > this.keepSize) { - this._inst = this._m32 = this._d = this._mem = null!; - } else { - this._m32[P32.STATE_WP] = 0; - this._m32[P32.STATE_SP] = 0; - this._m32[P32.STATE_DP] = 0; - } - } - - /** - * Initializes the decoder for new base64 data. - * Must be called before doing any decoding attempts. - * `size` is the amount of decoded bytes to be expected. - * The method will either spawn a new wasm instance or grow - * the needed memory of an existing instance. - */ - public init(size: number): void { - let m = this._m32; - const bytes = (Math.ceil(size / 3) + P32.STATE_DATA) * 4; - if (!this._inst) { - this._mem = new WebAssembly.Memory({ initial: Math.ceil(bytes / 65536) }); - this._inst = wasmDecode({ env: { memory: this._mem } }); - m = new Uint32Array(this._mem.buffer, 0); - m.set(D, P32.D0); - this._d = new Uint8Array(this._mem.buffer, P32.STATE_DATA * 4); - } else if (this._mem.buffer.byteLength < bytes) { - this._mem.grow(Math.ceil((bytes - this._mem.buffer.byteLength) / 65536)); - m = new Uint32Array(this._mem.buffer, 0); - this._d = new Uint8Array(this._mem.buffer, P32.STATE_DATA * 4); - } - m[P32.STATE_BSIZE] = size; - m[P32.STATE_ESIZE] = Math.ceil(size / 3) * 4; - m[P32.STATE_WP] = 0; - m[P32.STATE_SP] = 0; - m[P32.STATE_DP] = 0; - this._m32 = m; - } - - /** - * Put bytes in `data` from `start` to `end` (exclusive) into the decoder. - * Also decodes base64 data inplace once the payload exceeds 2^17 bytes. - * Returns 1 on error, else 0. - */ - public put(data: Uint8Array | Uint16Array | Uint32Array, start: number, end: number): number { - if (!this._inst) return 1; - const m = this._m32; - if (end - start + m[P32.STATE_WP] > m[P32.STATE_ESIZE]) return 1; - this._d.set(data.subarray(start, end), m[P32.STATE_WP]); - m[P32.STATE_WP] += end - start; - // max chunk in input handler is 2^17, try to run in "tandem mode" - // also assures that we dont run into illegal offsets in the wasm part - return m[P32.STATE_WP] - m[P32.STATE_SP] >= 131072 ? this._inst.exports.dec() : 0; - } - - /** - * End the current decoding. - * Decodes leftover payload and finally checks for the correct amount of - * decoded bytes by comparing to the value given to `init`. - * Returns 1 on error, else 0. - */ - public end(): number { - return this._inst ? this._inst.exports.end() : 1; - } -} diff --git a/package.json b/package.json index de823832..70214933 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,8 @@ "install-addons": "node ./bin/install-addons.js", "presetup": "npm run install-addons", "setup": "npm run build", - "postsetup": "npm run inwasm", "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", - "inwasm": "cd addons/xterm-addon-image && npm run inwasm -- -S", "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json", "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js", "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js", From 7c164efdcc7833b038875511345f9edaa279c9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 24 Aug 2023 17:30:52 +0200 Subject: [PATCH 04/19] fix CI scripts --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd465c01..75337542 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,6 @@ jobs: ./addons/xterm-addon-canvas/out-test/* \ ./addons/xterm-addon-fit/out/* \ ./addons/xterm-addon-fit/out-test/* \ - ./addons/xterm-addon-image/inwasm-builds/out/* \ ./addons/xterm-addon-image/out/* \ ./addons/xterm-addon-image/out-test/* \ ./addons/xterm-addon-ligatures/out/* \ From 9e768a4c7ccf099fc502330d8ebaf3ea508f00f1 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 25 Aug 2023 18:37:48 +0800 Subject: [PATCH 05/19] Update circle line custom powerline glyph. --- src/browser/renderer/shared/CustomGlyphs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index cface515..d0d9a528 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -366,11 +366,11 @@ export const powerlineDefinitions: { [index: string]: IVectorShape } = { // Right semi-circle solid '\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL, rightPadding: 1 }, // Right semi-circle line - '\u{E0B5}': { d: 'M0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.STROKE, rightPadding: 1 }, + '\u{E0B5}': { d: 'M.4,1 C.622,1,1,.676,.88,.5 C1,.324,0.622,0,.4,0', type: VectorType.STROKE, rightPadding: 1 }, // Left semi-circle solid '\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL, leftPadding: 1 }, // Left semi-circle line - '\u{E0B7}': { d: 'M1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.STROKE, leftPadding: 1 }, + '\u{E0B7}': { d: 'M.6,1 C0.378,1,0,.676,.12,.5 C0,0.324,0.378,0,0.6,0', type: VectorType.STROKE, leftPadding: 1 }, // Lower left triangle '\u{E0B8}': { d: 'M-.5,-.5 L1.5,1.5 L-.5,1.5', type: VectorType.FILL }, // Backslash separator From 797a71695fd1d2d5d74dae9972221a4405071047 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 26 Aug 2023 09:37:08 -0700 Subject: [PATCH 06/19] Improve texture atlas utilization - Changed the threshold at which pages start to merge from MAX/2 to MAX. - If a glyph is about to create a new page that will be merged, check if the existing row fits when ignoring the row pixel threshold. This will improve texture utilization by using the available space before the page is merged and becomes static. --- src/browser/renderer/shared/TextureAtlas.ts | 34 +++++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index c7b85a7e..4444d8f6 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -151,7 +151,7 @@ export class TextureAtlas implements ITextureAtlas { // microtask to ensure it does not interrupt textures that will be rendered in the current // animation frame which would result in blank rendered areas. This is actually not that // expensive relative to drawing the glyphs, so there is no need to wait for an idle callback. - if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages / 2)) { + if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) { queueMicrotask(() => { // Find the set of the largest 4 images, below the maximum size, with the highest // percentages used @@ -756,13 +756,13 @@ export class TextureAtlas implements ITextureAtlas { } } - // Create a new one if too much vertical space would be wasted or there is not enough room + // Create a new page if too much vertical space would be wasted or there is not enough room // left in the page. The previous active row will become fixed in the process as it now has a // fixed height if (activeRow.y + rasterizedGlyph.size.y >= activePage.canvas.height || activeRow.height > rasterizedGlyph.size.y + Constants.ROW_PIXEL_THRESHOLD) { // Create the new fixed height row, creating a new page if there isn't enough room on the // current page - let wasNewPageCreated = false; + let wasPageAndRowFound = false; if (activePage.currentRow.y + activePage.currentRow.height + rasterizedGlyph.size.y >= activePage.canvas.height) { // Find the first page with room to create the new row on let candidatePage: AtlasPage | undefined; @@ -775,15 +775,29 @@ export class TextureAtlas implements ITextureAtlas { if (candidatePage) { activePage = candidatePage; } else { - // Create a new page if there is no room - const newPage = this._createNewPage(); - activePage = newPage; - activeRow = newPage.currentRow; - activeRow.height = rasterizedGlyph.size.y; - wasNewPageCreated = true; + // Before creating a new atlas page that would trigger a page merge, check if the + // current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will + // improve texture utilization by using the available space before the page is merged + // and becomes static. + if ( + TextureAtlas.maxAtlasPages === this._pages.length && + activeRow.y + rasterizedGlyph.size.y <= activePage.canvas.height && + activeRow.height >= rasterizedGlyph.size.y && + activeRow.x + rasterizedGlyph.size.x <= activePage.canvas.width + ) { + // activePage and activeRow is already valid + wasPageAndRowFound = true; + } else { + // Create a new page if there is no room + const newPage = this._createNewPage(); + activePage = newPage; + activeRow = newPage.currentRow; + activeRow.height = rasterizedGlyph.size.y; + wasPageAndRowFound = true; + } } } - if (!wasNewPageCreated) { + if (!wasPageAndRowFound) { // Fix the current row as the new row is being added below if (activePage.currentRow.height > 0) { activePage.fixedRows.push(activePage.currentRow); From aba16687b0723a6cc5f13f921dd93011ca69c050 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 26 Aug 2023 10:23:09 -0700 Subject: [PATCH 07/19] Fix high pressure texture atlas corruption The corruption was caused by confusion with page indexes when merging pages. The fix is to simplify it by deleting all pages and then just adding the new page. Fixes #4534 Fixes #4351 --- src/browser/renderer/shared/TextureAtlas.ts | 83 ++++++++++----------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 4444d8f6..dd059574 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -152,49 +152,47 @@ export class TextureAtlas implements ITextureAtlas { // animation frame which would result in blank rendered areas. This is actually not that // expensive relative to drawing the glyphs, so there is no need to wait for an idle callback. if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) { - queueMicrotask(() => { - // Find the set of the largest 4 images, below the maximum size, with the highest - // percentages used - const pagesBySize = this._pages.filter(e => { - return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE); - }).sort((a, b) => { - if (b.canvas.width !== a.canvas.width) { - return b.canvas.width - a.canvas.width; - } - return b.percentageUsed - a.percentageUsed; - }); - let sameSizeI = -1; - let size = 0; - for (let i = 0; i < pagesBySize.length; i++) { - if (pagesBySize[i].canvas.width !== size) { - sameSizeI = i; - size = pagesBySize[i].canvas.width; - } else if (i - sameSizeI === 3) { - break; - } + // Find the set of the largest 4 images, below the maximum size, with the highest + // percentages used + const pagesBySize = this._pages.filter(e => { + return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE); + }).sort((a, b) => { + if (b.canvas.width !== a.canvas.width) { + return b.canvas.width - a.canvas.width; } - - // Gather details of the merge - const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4); - const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1); - const mergedPageIndex = sortedMergingPagesIndexes[0]; - - // Merge into the new page - const mergedPage = this._mergePages(mergingPages, mergedPageIndex); - mergedPage.version++; - - // Replace the first _merging_ page with the _merged_ page - this._pages[mergedPageIndex] = mergedPage; - - // Delete the other 3 pages, shifting glyph texture pages as needed - for (let i = sortedMergingPagesIndexes.length - 1; i >= 1; i--) { - this._deletePage(sortedMergingPagesIndexes[i]); - } - - // Request the model to be cleared to refresh all texture pages. - this._requestClearModel = true; - this._onAddTextureAtlasCanvas.fire(mergedPage.canvas); + return b.percentageUsed - a.percentageUsed; }); + let sameSizeI = -1; + let size = 0; + for (let i = 0; i < pagesBySize.length; i++) { + if (pagesBySize[i].canvas.width !== size) { + sameSizeI = i; + size = pagesBySize[i].canvas.width; + } else if (i - sameSizeI === 3) { + break; + } + } + + // Gather details of the merge + const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4); + const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1); + const mergedPageIndex = this.pages.length - mergingPages.length; + + // Merge into the new page + const mergedPage = this._mergePages(mergingPages, mergedPageIndex); + mergedPage.version++; + + // Delete the pages, shifting glyph texture pages as needed + for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) { + this._deletePage(sortedMergingPagesIndexes[i]); + } + + // Add the new merged page to the end + this.pages.push(mergedPage); + + // Request the model to be cleared to refresh all texture pages. + this._requestClearModel = true; + this._onAddTextureAtlasCanvas.fire(mergedPage.canvas); } // All new atlas pages are created small as they are highly dynamic @@ -780,7 +778,8 @@ export class TextureAtlas implements ITextureAtlas { // improve texture utilization by using the available space before the page is merged // and becomes static. if ( - TextureAtlas.maxAtlasPages === this._pages.length && + TextureAtlas.maxAtlasPages && + this._pages.length >= TextureAtlas.maxAtlasPages && activeRow.y + rasterizedGlyph.size.y <= activePage.canvas.height && activeRow.height >= rasterizedGlyph.size.y && activeRow.x + rasterizedGlyph.size.x <= activePage.canvas.width From 1e1ccba238d256eea15b46162263cc052f9cca45 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 06:44:11 -0700 Subject: [PATCH 08/19] Use MutableDisposable in BaseRenderLayer --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ff29bbf1..6c1335c8 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,24 +3,24 @@ * @license MIT */ +import { ReadonlyColorSet } from 'browser/Types'; +import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; +import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { ReadonlyColorSet } from 'browser/Types'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; +import { isSafari } from 'common/Platform'; +import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ICellData, IDisposable } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; -import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; -import { Disposable, toDisposable } from 'common/Lifecycle'; -import { isSafari } from 'common/Platform'; -import { EventEmitter, forwardEvent } from 'common/EventEmitter'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -37,7 +37,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer private _bitmapGenerator: (BitmapGenerator | undefined)[] = []; protected _charAtlas!: ITextureAtlas; - private _charAtlasDisposable?: IDisposable; + protected _charAtlasDisposable = this.register(new MutableDisposable()); public get canvas(): HTMLCanvasElement { return this._canvas; } public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; } @@ -122,9 +122,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) { return; } - this._charAtlasDisposable?.dispose(); this._charAtlas = acquireTextureAtlas(this._terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr); - this._charAtlasDisposable = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); + this._charAtlasDisposable.value = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); this._charAtlas.warmUp(); for (let i = 0; i < this._charAtlas.pages.length; i++) { this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas); From 94ed3e800a2cb9d3afd0309c2165ba4d1b8a2652 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 06:44:48 -0700 Subject: [PATCH 09/19] Don't dispose of texture atlas in canvas addon This would cause problems if the atlas is shared between multiple terminals. --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 6c1335c8..8c199336 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -74,7 +74,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this.register(toDisposable(() => { this._canvas.remove(); - this._charAtlas?.dispose(); })); } From ec02bab97c40e3c9d70a4cd7710967437cfd53b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 06:46:49 -0700 Subject: [PATCH 10/19] Use MutableDisposable in CursorRenderLayer --- .../src/CursorRenderLayer.ts | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 9f4ac896..c5174f86 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -7,7 +7,7 @@ import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStat import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; -import { toDisposable } from 'common/Lifecycle'; +import { MutableDisposable } from 'common/Lifecycle'; import { isFirefox } from 'common/Platform'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; @@ -26,7 +26,7 @@ interface ICursorState { export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void}; - private _cursorBlinkStateManager: CursorBlinkStateManager | undefined; + private _cursorBlinkStateManager: MutableDisposable = this.register(new MutableDisposable()); private _cell: ICellData = new CellData(); constructor( @@ -57,10 +57,6 @@ export class CursorRenderLayer extends BaseRenderLayer { }; this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this._handleOptionsChanged(); - this.register(toDisposable(() => { - this._cursorBlinkStateManager?.dispose(); - this._cursorBlinkStateManager = undefined; - })); } public resize(dim: IRenderDimensions): void { @@ -77,28 +73,27 @@ export class CursorRenderLayer extends BaseRenderLayer { public reset(): void { this._clearCursor(); - this._cursorBlinkStateManager?.restartBlinkAnimation(); + this._cursorBlinkStateManager.value?.restartBlinkAnimation(); this._handleOptionsChanged(); } public handleBlur(): void { - this._cursorBlinkStateManager?.pause(); + this._cursorBlinkStateManager.value?.pause(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public handleFocus(): void { - this._cursorBlinkStateManager?.resume(); + this._cursorBlinkStateManager.value?.resume(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } private _handleOptionsChanged(): void { if (this._optionsService.rawOptions.cursorBlink) { - if (!this._cursorBlinkStateManager) { - this._cursorBlinkStateManager = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); + if (!this._cursorBlinkStateManager.value) { + this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); } } else { - this._cursorBlinkStateManager?.dispose(); - this._cursorBlinkStateManager = undefined; + this._cursorBlinkStateManager.clear(); } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal @@ -106,14 +101,14 @@ export class CursorRenderLayer extends BaseRenderLayer { } public handleCursorMove(): void { - this._cursorBlinkStateManager?.restartBlinkAnimation(); + this._cursorBlinkStateManager.value?.restartBlinkAnimation(); } public handleGridChanged(startRow: number, endRow: number): void { - if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) { this._render(false); } else { - this._cursorBlinkStateManager.restartBlinkAnimation(); + this._cursorBlinkStateManager.value.restartBlinkAnimation(); } } @@ -159,7 +154,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } // Don't draw the cursor if it's blinking - if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + if (this._cursorBlinkStateManager.value && !this._cursorBlinkStateManager.value.isCursorVisible) { this._clearCursor(); return; } From 57db72d287f76632f0b4464b7668e7fe317fa6d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 06:55:16 -0700 Subject: [PATCH 11/19] More adoption of MutableDisposable --- addons/xterm-addon-search/src/SearchAddon.ts | 19 +++----- addons/xterm-addon-webgl/src/WebglRenderer.ts | 45 +++++++++---------- .../src/renderLayer/BaseRenderLayer.ts | 1 - src/browser/Terminal.ts | 13 +++--- 4 files changed, 34 insertions(+), 44 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index cbde1a1b..10fc1ecc 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -5,7 +5,7 @@ import { Terminal, IDisposable, ITerminalAddon, IDecoration } from 'xterm'; import { EventEmitter } from 'common/EventEmitter'; -import { Disposable, toDisposable, disposeArray } from 'common/Lifecycle'; +import { Disposable, toDisposable, disposeArray, MutableDisposable } from 'common/Lifecycle'; export interface ISearchOptions { regex?: boolean; @@ -66,7 +66,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { private _cachedSearchTerm: string | undefined; private _highlightedLines: Set = new Set(); private _highlightDecorations: IHighlight[] = []; - private _selectedDecoration: IHighlight | undefined; + private _selectedDecoration: MutableDisposable = this.register(new MutableDisposable()); private _highlightLimit: number; private _lastSearchOptions: ISearchOptions | undefined; private _highlightTimeout: number | undefined; @@ -110,7 +110,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } public clearDecorations(retainCachedSearchTerm?: boolean): void { - this.clearActiveDecoration(); + this._selectedDecoration.clear(); disposeArray(this._highlightDecorations); this._highlightDecorations = []; this._highlightedLines.clear(); @@ -119,11 +119,6 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } } - public clearActiveDecoration(): void { - this._selectedDecoration?.dispose(); - this._selectedDecoration = undefined; - } - /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -320,8 +315,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon { private _fireResults(searchOptions?: ISearchOptions): void { if (searchOptions?.decorations) { let resultIndex = -1; - if (this._selectedDecoration) { - const selectedMatch = this._selectedDecoration.match; + if (this._selectedDecoration.value) { + const selectedMatch = this._selectedDecoration.value.match; for (let i = 0; i < this._highlightDecorations.length; i++) { const match = this._highlightDecorations[i].match; if (match.row === selectedMatch.row && match.col === selectedMatch.col && match.size === selectedMatch.size) { @@ -642,7 +637,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { */ private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; - this.clearActiveDecoration(); + this._selectedDecoration.clear(); if (!result) { terminal.clearSelection(); return false; @@ -666,7 +661,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { disposables.push(marker); disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true))); disposables.push(decoration.onDispose(() => disposeArray(disposables))); - this._selectedDecoration = { decoration, match: result, dispose() { decoration.dispose(); } }; + this._selectedDecoration.value = { decoration, match: result, dispose() { decoration.dispose(); } }; } } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 20db5faf..c57565a3 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -41,8 +41,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; - private _rectangleRenderer?: RectangleRenderer; - private _glyphRenderer?: GlyphRenderer; + private _rectangleRenderer: MutableDisposable = this.register(new MutableDisposable()); + private _glyphRenderer: MutableDisposable = this.register(new MutableDisposable()); public readonly dimensions: IRenderDimensions; @@ -128,7 +128,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.appendChild(this._canvas); - [this._rectangleRenderer, this._glyphRenderer] = this._initializeWebGLState(); + [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState(); this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!); @@ -182,10 +182,10 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.style.width = `${this.dimensions.css.canvas.width}px`; this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`; - this._rectangleRenderer?.setDimensions(this.dimensions); - this._rectangleRenderer?.handleResize(); - this._glyphRenderer?.setDimensions(this.dimensions); - this._glyphRenderer?.handleResize(); + this._rectangleRenderer.value?.setDimensions(this.dimensions); + this._rectangleRenderer.value?.handleResize(); + this._glyphRenderer.value?.setDimensions(this.dimensions); + this._glyphRenderer.value?.handleResize(); this._refreshCharAtlas(); @@ -241,17 +241,14 @@ export class WebglRenderer extends Disposable implements IRenderer { * Initializes members dependent on WebGL context state. */ private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] { - // Dispose any previous rectangle and glyph renderers before creating new ones. - this._rectangleRenderer?.dispose(); - this._glyphRenderer?.dispose(); - - this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService)); - this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions)); + this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService); + this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions); // Update dimensions and acquire char atlas this.handleCharSizeChanged(); - return [this._rectangleRenderer, this._glyphRenderer]; + return [this._rectangleRenderer.value, this._glyphRenderer.value + ]; } /** @@ -284,7 +281,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } this._charAtlas = atlas; this._charAtlas.warmUp(); - this._glyphRenderer?.setAtlas(this._charAtlas); + this._glyphRenderer.value?.setAtlas(this._charAtlas); } /** @@ -340,14 +337,14 @@ export class WebglRenderer extends Disposable implements IRenderer { l.handleGridChanged(this._terminal, start, end); } - if (!this._glyphRenderer || !this._rectangleRenderer) { + if (!this._glyphRenderer.value || !this._rectangleRenderer.value) { return; } // Tell renderer the frame is beginning // upon a model clear also refresh the full viewport model // (also triggered by an atlas page merge, part of #4480) - if (this._glyphRenderer.beginFrame()) { + if (this._glyphRenderer.value.beginFrame()) { this._clearModel(true); this._updateModel(0, this._terminal.rows - 1); } else { @@ -356,10 +353,10 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Render - this._rectangleRenderer?.renderBackgrounds(); - this._glyphRenderer?.render(this._model); + this._rectangleRenderer.value.renderBackgrounds(); + this._glyphRenderer.value.render(this._model); if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible) { - this._rectangleRenderer?.renderCursor(); + this._rectangleRenderer.value.renderCursor(); } } @@ -502,7 +499,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); + this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); if (isJoined) { // Restore work cell @@ -511,7 +508,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x < lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); + this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; @@ -521,9 +518,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } } if (modelUpdated) { - this._rectangleRenderer!.updateBackgrounds(this._model); + this._rectangleRenderer.value!.updateBackgrounds(this._model); } - this._rectangleRenderer!.updateCursor(this._model); + this._rectangleRenderer.value!.updateCursor(this._model); } /** diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index f5449c4d..3aaac435 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -49,7 +49,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer })); this.register(toDisposable(() => { this._canvas.remove(); - this._charAtlas?.dispose(); })); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 24dace2f..a092e1bc 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -44,7 +44,7 @@ import { ThemeService } from 'browser/services/ThemeService'; import { color, rgba } from 'common/Color'; import { CoreTerminal } from 'common/CoreTerminal'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; -import { toDisposable } from 'common/Lifecycle'; +import { MutableDisposable, toDisposable } from 'common/Lifecycle'; import * as Browser from 'common/Platform'; import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, ScrollSource, SpecialColorIndex } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -118,7 +118,7 @@ export class Terminal extends CoreTerminal implements ITerminal { public linkifier2: ILinkifier2; public viewport: IViewport | undefined; private _compositionHelper: ICompositionHelper | undefined; - private _accessibilityManager: AccessibilityManager | undefined; + private _accessibilityManager: MutableDisposable = this.register(new MutableDisposable()); private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; @@ -252,12 +252,11 @@ export class Terminal extends CoreTerminal implements ITerminal { private _handleScreenReaderModeOptionChange(value: boolean): void { if (value) { - if (!this._accessibilityManager && this._renderService) { - this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this); + if (!this._accessibilityManager.value && this._renderService) { + this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); } } else { - this._accessibilityManager?.dispose(); - this._accessibilityManager = undefined; + this._accessibilityManager.clear(); } } @@ -535,7 +534,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.options.screenReaderMode) { // Note that this must be done *after* the renderer is created in order to // ensure the correct order of the dprchange event - this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this); + this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); } this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e))); From 37b0e4018cd73d3b32ab89cfcd2be62a40585944 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 07:03:27 -0700 Subject: [PATCH 12/19] More adoption of MutableDisposable --- addons/xterm-addon-image/src/ImageRenderer.ts | 6 +- src/browser/services/RenderService.ts | 58 +++++++++---------- src/common/CoreTerminal.ts | 16 ++--- 3 files changed, 35 insertions(+), 45 deletions(-) diff --git a/addons/xterm-addon-image/src/ImageRenderer.ts b/addons/xterm-addon-image/src/ImageRenderer.ts index d225315e..392816dc 100644 --- a/addons/xterm-addon-image/src/ImageRenderer.ts +++ b/addons/xterm-addon-image/src/ImageRenderer.ts @@ -6,6 +6,7 @@ import { toRGBA8888 } from 'sixel/lib/Colors'; import { IDisposable } from 'xterm'; import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types'; +import { MutableDisposable } from 'common/Lifecycle'; const PLACEHOLDER_LENGTH = 4096; @@ -22,7 +23,7 @@ export class ImageRenderer implements IDisposable { private _ctx: CanvasRenderingContext2D | null | undefined; private _placeholder: HTMLCanvasElement | undefined; private _placeholderBitmap: ImageBitmap | undefined; - private _optionsRefresh: IDisposable | undefined; + private _optionsRefresh = new MutableDisposable(); private _oldOpen: ((parent: HTMLElement) => void) | undefined; private _renderService: IRenderService | undefined; private _oldSetRenderer: ((renderer: any) => void) | undefined; @@ -77,7 +78,7 @@ export class ImageRenderer implements IDisposable { this._open(); } // hack to spot fontSize changes - this._optionsRefresh = this._terminal._core.optionsService.onOptionChange(option => { + this._optionsRefresh.value = this._terminal._core.optionsService.onOptionChange(option => { if (option === 'fontSize') { this.rescaleCanvas(); this._renderService?.refreshRows(0, this._terminal.rows); @@ -87,7 +88,6 @@ export class ImageRenderer implements IDisposable { public dispose(): void { - this._optionsRefresh?.dispose(); this.removeLayerFromDom(); if (this._terminal._core && this._oldOpen) { this._terminal._core.open = this._oldOpen; diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 41f0c9b5..0f18a233 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -10,7 +10,7 @@ import { IRenderDebouncerWithCallback } from 'browser/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { EventEmitter } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable } from 'common/Lifecycle'; import { DebouncedIdleTask } from 'common/TaskQueue'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -23,7 +23,7 @@ interface ISelectionState { export class RenderService extends Disposable implements IRenderService { public serviceBrand: undefined; - private _renderer: IRenderer | undefined; + private _renderer: MutableDisposable = this.register(new MutableDisposable()); private _renderDebouncer: IRenderDebouncerWithCallback; private _screenDprMonitor: ScreenDprMonitor; private _pausedResizeTask = new DebouncedIdleTask(); @@ -49,7 +49,7 @@ export class RenderService extends Disposable implements IRenderService { private readonly _onRefreshRequest = this.register(new EventEmitter<{ start: number, end: number }>()); public readonly onRefreshRequest = this._onRefreshRequest.event; - public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; } + public get dimensions(): IRenderDimensions { return this._renderer.value!.dimensions; } constructor( private _rowCount: number, @@ -63,8 +63,6 @@ export class RenderService extends Disposable implements IRenderService { ) { super(); - this.register({ dispose: () => this._renderer?.dispose() }); - this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); @@ -73,7 +71,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(this._screenDprMonitor); this.register(bufferService.onResize(() => this._fullRefresh())); - this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear())); + this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear())); this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this.register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged())); @@ -148,7 +146,7 @@ export class RenderService extends Disposable implements IRenderService { } private _renderRows(start: number, end: number): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } @@ -159,11 +157,11 @@ export class RenderService extends Disposable implements IRenderService { end = Math.min(end, this._rowCount - 1); // Render - this._renderer.renderRows(start, end); + this._renderer.value.renderRows(start, end); // Update selection if needed if (this._needsSelectionRefresh) { - this._renderer.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); + this._renderer.value.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); this._needsSelectionRefresh = false; } @@ -181,7 +179,7 @@ export class RenderService extends Disposable implements IRenderService { } private _handleOptionsChanged(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } this.refreshRows(0, this._rowCount - 1); @@ -189,25 +187,23 @@ export class RenderService extends Disposable implements IRenderService { } private _fireOnCanvasResize(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } // Don't fire the event if the dimensions haven't changed - if (this._renderer.dimensions.css.canvas.width === this._canvasWidth && this._renderer.dimensions.css.canvas.height === this._canvasHeight) { + if (this._renderer.value.dimensions.css.canvas.width === this._canvasWidth && this._renderer.value.dimensions.css.canvas.height === this._canvasHeight) { return; } - this._onDimensionsChange.fire(this._renderer.dimensions); + this._onDimensionsChange.fire(this._renderer.value.dimensions); } public hasRenderer(): boolean { - return !!this._renderer; + return !!this._renderer.value; } public setRenderer(renderer: IRenderer): void { - // TODO: RenderService should be the only one to dispose the renderer - this._renderer?.dispose(); - this._renderer = renderer; - this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); + this._renderer.value = renderer; + this._renderer.value.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); // Force a refresh this._needsSelectionRefresh = true; @@ -227,10 +223,10 @@ export class RenderService extends Disposable implements IRenderService { } public clearTextureAtlas(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } - this._renderer.clearTextureAtlas?.(); + this._renderer.value.clearTextureAtlas?.(); this._fullRefresh(); } @@ -239,50 +235,50 @@ export class RenderService extends Disposable implements IRenderService { // when devicePixelRatio changes this._charSizeService.measure(); - if (!this._renderer) { + if (!this._renderer.value) { return; } - this._renderer.handleDevicePixelRatioChange(); + this._renderer.value.handleDevicePixelRatioChange(); this.refreshRows(0, this._rowCount - 1); } public handleResize(cols: number, rows: number): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } if (this._isPaused) { - this._pausedResizeTask.set(() => this._renderer!.handleResize(cols, rows)); + this._pausedResizeTask.set(() => this._renderer.value!.handleResize(cols, rows)); } else { - this._renderer.handleResize(cols, rows); + this._renderer.value.handleResize(cols, rows); } this._fullRefresh(); } // TODO: Is this useful when we have onResize? public handleCharSizeChanged(): void { - this._renderer?.handleCharSizeChanged(); + this._renderer.value?.handleCharSizeChanged(); } public handleBlur(): void { - this._renderer?.handleBlur(); + this._renderer.value?.handleBlur(); } public handleFocus(): void { - this._renderer?.handleFocus(); + this._renderer.value?.handleFocus(); } public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._selectionState.start = start; this._selectionState.end = end; this._selectionState.columnSelectMode = columnSelectMode; - this._renderer?.handleSelectionChanged(start, end, columnSelectMode); + this._renderer.value?.handleSelectionChanged(start, end, columnSelectMode); } public handleCursorMove(): void { - this._renderer?.handleCursorMove(); + this._renderer.value?.handleCursorMove(); } public clear(): void { - this._renderer?.clear(); + this._renderer.value?.clear(); } } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 89d9f5e5..47f77406 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { Disposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; @@ -57,7 +57,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected _inputHandler: InputHandler; private _writeBuffer: WriteBuffer; - private _windowsWrappingHeuristics: IDisposable | undefined; + private _windowsWrappingHeuristics = this.register(new MutableDisposable()); private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; @@ -144,11 +144,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult))); this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); - - this.register(toDisposable(() => { - this._windowsWrappingHeuristics?.dispose(); - this._windowsWrappingHeuristics = undefined; - })); } public write(data: string | Uint8Array, callback?: () => void): void { @@ -267,20 +262,19 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { if (value) { this._enableWindowsWrappingHeuristics(); } else { - this._windowsWrappingHeuristics?.dispose(); - this._windowsWrappingHeuristics = undefined; + this._windowsWrappingHeuristics.clear(); } } protected _enableWindowsWrappingHeuristics(): void { - if (!this._windowsWrappingHeuristics) { + if (!this._windowsWrappingHeuristics.value) { const disposables: IDisposable[] = []; disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); disposables.push(this.registerCsiHandler({ final: 'H' }, () => { updateWindowsModeWrappedState(this._bufferService); return false; })); - this._windowsWrappingHeuristics = toDisposable(() => { + this._windowsWrappingHeuristics.value = toDisposable(() => { for (const d of disposables) { d.dispose(); } From 2eaa9b3b691d35ebf8bcef32b1fe1f7fa86df966 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 07:15:51 -0700 Subject: [PATCH 13/19] Fix remaining issues with MutableDisposable usage --- addons/xterm-addon-image/src/ImageRenderer.ts | 43 +++++++++---------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 12 +++--- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/addons/xterm-addon-image/src/ImageRenderer.ts b/addons/xterm-addon-image/src/ImageRenderer.ts index 392816dc..9b528efe 100644 --- a/addons/xterm-addon-image/src/ImageRenderer.ts +++ b/addons/xterm-addon-image/src/ImageRenderer.ts @@ -6,7 +6,7 @@ import { toRGBA8888 } from 'sixel/lib/Colors'; import { IDisposable } from 'xterm'; import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types'; -import { MutableDisposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; const PLACEHOLDER_LENGTH = 4096; @@ -18,12 +18,12 @@ const PLACEHOLDER_HEIGHT = 24; * - add canvas layer to DOM (browser only for now) * - draw image tiles onRender */ -export class ImageRenderer implements IDisposable { +export class ImageRenderer extends Disposable implements IDisposable { public canvas: HTMLCanvasElement | undefined; private _ctx: CanvasRenderingContext2D | null | undefined; private _placeholder: HTMLCanvasElement | undefined; private _placeholderBitmap: ImageBitmap | undefined; - private _optionsRefresh = new MutableDisposable(); + private _optionsRefresh = this.register(new MutableDisposable()); private _oldOpen: ((parent: HTMLElement) => void) | undefined; private _renderService: IRenderService | undefined; private _oldSetRenderer: ((renderer: any) => void) | undefined; @@ -69,6 +69,7 @@ export class ImageRenderer implements IDisposable { constructor(private _terminal: ITerminalExt) { + super(); this._oldOpen = this._terminal._core.open; this._terminal._core.open = (parent: HTMLElement): void => { this._oldOpen?.call(this._terminal._core, parent); @@ -84,25 +85,23 @@ export class ImageRenderer implements IDisposable { this._renderService?.refreshRows(0, this._terminal.rows); } }); - } - - - public dispose(): void { - this.removeLayerFromDom(); - if (this._terminal._core && this._oldOpen) { - this._terminal._core.open = this._oldOpen; - this._oldOpen = undefined; - } - if (this._renderService && this._oldSetRenderer) { - this._renderService.setRenderer = this._oldSetRenderer; - this._oldSetRenderer = undefined; - } - this._renderService = undefined; - this.canvas = undefined; - this._ctx = undefined; - this._placeholderBitmap?.close(); - this._placeholderBitmap = undefined; - this._placeholder = undefined; + this.register(toDisposable(() => { + this.removeLayerFromDom(); + if (this._terminal._core && this._oldOpen) { + this._terminal._core.open = this._oldOpen; + this._oldOpen = undefined; + } + if (this._renderService && this._oldSetRenderer) { + this._renderService.setRenderer = this._oldSetRenderer; + this._oldSetRenderer = undefined; + } + this._renderService = undefined; + this.canvas = undefined; + this._ctx = undefined; + this._placeholderBitmap?.close(); + this._placeholderBitmap = undefined; + this._placeholder = undefined; + })); } /** diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index c57565a3..9a8f8ee3 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -20,7 +20,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { traceCall } from 'common/services/LogService'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { IDisposable, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; import { GlyphRenderer } from './GlyphRenderer'; import { RectangleRenderer } from './RectangleRenderer'; import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL, RenderModel } from './RenderModel'; @@ -31,7 +31,7 @@ import { IRenderLayer } from './renderLayer/Types'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; private _cursorBlinkStateManager: MutableDisposable = new MutableDisposable(); - private _charAtlasDisposable: IDisposable | undefined; + private _charAtlasDisposable = this.register(new MutableDisposable()); private _charAtlas: ITextureAtlas | undefined; private _devicePixelRatio: number; @@ -247,8 +247,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update dimensions and acquire char atlas this.handleCharSizeChanged(); - return [this._rectangleRenderer.value, this._glyphRenderer.value - ]; + return [this._rectangleRenderer.value, this._glyphRenderer.value]; } /** @@ -272,9 +271,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._coreBrowserService.dpr ); if (this._charAtlas !== atlas) { - this._charAtlasDisposable?.dispose(); this._onChangeTextureAtlas.fire(atlas.pages[0].canvas); - this._charAtlasDisposable = getDisposeArrayDisposable([ + this._charAtlasDisposable.value = getDisposeArrayDisposable([ forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas), forwardEvent(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas) ]); @@ -292,7 +290,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _clearModel(clearGlyphRenderer: boolean): void { this._model.clear(); if (clearGlyphRenderer) { - this._glyphRenderer?.clear(); + this._glyphRenderer.value?.clear(); } } From 38c6af9b46ae6c721f6bed5c939ce33244246a2b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 07:22:08 -0700 Subject: [PATCH 14/19] Fix _renderer access in api test --- addons/xterm-addon-webgl/test/WebglRenderer.api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 5d9cc0f0..24485cb2 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -1106,7 +1106,7 @@ describe('WebGL Renderer Integration Tests', async () => { async function getCellColor(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderService._renderer._gl; + window.gl = window.term._core._renderService._renderer.value._gl; window.result = new Uint8Array(4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( @@ -1120,7 +1120,7 @@ async function getCellColor(col: number, row: number): Promise { async function getCellPixels(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderService._renderer._gl; + window.gl = window.term._core._renderService._renderer.value._gl; window.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( From 12a862c27702dde7d20ed72d06d80f2899ff5227 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 07:28:04 -0700 Subject: [PATCH 15/19] Bring back unit test coverage report --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75337542..99ca8331 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,27 @@ jobs: - name: Lint API run: yarn lint-api + coverage: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: 'yarn' + - name: Install dependencies + run: | + yarn --frozen-lockfile + yarn install-addons + - name: Unit test coverage + run: | + yarn test-unit-coverage --forbid-only + EXIT_CODE=$? + ./node_modules/.bin/nyc report --reporter=cobertura + exit $EXIT_CODE + test-unit-parallel: timeout-minutes: 20 strategy: From 03c1906f132625002deb72f933e73f20af16a3a3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 07:33:46 -0700 Subject: [PATCH 16/19] Have coverage depend on build --- .github/workflows/ci.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99ca8331..e067459a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,20 +72,32 @@ jobs: - name: Lint API run: yarn lint-api - coverage: + test-unit-coverage: + needs: build runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - - name: Use Node.js 18.x + - name: Use Node.js ${{ matrix.node-version }}.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: ${{ matrix.node-version }}.x cache: 'yarn' - name: Install dependencies run: | yarn --frozen-lockfile yarn install-addons + - uses: actions/download-artifact@v3 + with: + name: build-artifacts + - name: Unzip artifacts (Linux, macOS) + if: runner.os != 'Windows' + run: unzip -o compressed-build.zip + - name: Unzip artifacts (Windows) + if: runner.os == 'Windows' + run: 7z x compressed-build.zip -aoa -o${{ github.workspace }} + - name: Print directory structure + run: ls -R - name: Unit test coverage run: | yarn test-unit-coverage --forbid-only From 923c28cb7711a266b9ca9bcdced6b988926a4179 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 27 Aug 2023 07:43:11 -0700 Subject: [PATCH 17/19] Use node 18 in coverage --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e067459a..2d3808c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,10 +78,10 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }}.x + - name: Use Node.js 18.x uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }}.x + node-version: 18.x cache: 'yarn' - name: Install dependencies run: | From 7fecec65992a96a629467a6007e2f14260f7a6a2 Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 28 Aug 2023 16:54:27 +0800 Subject: [PATCH 18/19] Offset outward --- src/browser/renderer/shared/CustomGlyphs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index d0d9a528..cfb7bff8 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -366,11 +366,11 @@ export const powerlineDefinitions: { [index: string]: IVectorShape } = { // Right semi-circle solid '\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL, rightPadding: 1 }, // Right semi-circle line - '\u{E0B5}': { d: 'M.4,1 C.622,1,1,.676,.88,.5 C1,.324,0.622,0,.4,0', type: VectorType.STROKE, rightPadding: 1 }, + '\u{E0B5}': { d: 'M.4,1 C.622,1,1,.826,.98,.5 C1,.174,0.622,0,.4,0', type: VectorType.STROKE, rightPadding: 1 }, // Left semi-circle solid '\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL, leftPadding: 1 }, // Left semi-circle line - '\u{E0B7}': { d: 'M.6,1 C0.378,1,0,.676,.12,.5 C0,0.324,0.378,0,0.6,0', type: VectorType.STROKE, leftPadding: 1 }, + '\u{E0B7}': { d: 'M.6,1 C0.378,1,0,.826,.02,.5 C0,0.174,0.378,0,0.6,0', type: VectorType.STROKE, leftPadding: 1 }, // Lower left triangle '\u{E0B8}': { d: 'M-.5,-.5 L1.5,1.5 L-.5,1.5', type: VectorType.FILL }, // Backslash separator From f7ac0d878b42e8ec57cd67c447743ae74479b187 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 28 Aug 2023 02:20:31 -0700 Subject: [PATCH 19/19] Center new semi-circle line glyphs --- src/browser/renderer/shared/CustomGlyphs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index cfb7bff8..c08bc4b1 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -366,11 +366,11 @@ export const powerlineDefinitions: { [index: string]: IVectorShape } = { // Right semi-circle solid '\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL, rightPadding: 1 }, // Right semi-circle line - '\u{E0B5}': { d: 'M.4,1 C.622,1,1,.826,.98,.5 C1,.174,0.622,0,.4,0', type: VectorType.STROKE, rightPadding: 1 }, + '\u{E0B5}': { d: 'M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0', type: VectorType.STROKE, rightPadding: 1 }, // Left semi-circle solid '\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL, leftPadding: 1 }, // Left semi-circle line - '\u{E0B7}': { d: 'M.6,1 C0.378,1,0,.826,.02,.5 C0,0.174,0.378,0,0.6,0', type: VectorType.STROKE, leftPadding: 1 }, + '\u{E0B7}': { d: 'M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0', type: VectorType.STROKE, leftPadding: 1 }, // Lower left triangle '\u{E0B8}': { d: 'M-.5,-.5 L1.5,1.5 L-.5,1.5', type: VectorType.FILL }, // Backslash separator