diff --git a/appveyor.yml b/appveyor.yml index ca0ebf294..ab8e6e034 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ init: # What combinations to test environment: matrix: - - nodejs_version: '16' + - nodejs_version: '20' platform: - x64 install: diff --git a/karma.conf.js b/karma.conf.js index a5a993805..3d77f5789 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -5,8 +5,10 @@ const webpack = require('webpack'); process.env.CHROMIUM_BIN = require('puppeteer').executablePath(); process.env.CHROME_BIN = require('puppeteer').executablePath(); +console.log('Chromium', process.env.CHROMIUM_BIN); + const isAppveyor = process.env.APPVEYOR_BUILD_NUMBER ? true : false; -const karmaJasmineSeedReporter = function(baseReporterDecorator) { +const KarmaJasmineSeedReporter = function(baseReporterDecorator) { baseReporterDecorator(this); this.onBrowserComplete = function(browser, result) { @@ -20,8 +22,35 @@ const karmaJasmineSeedReporter = function(baseReporterDecorator) { }; const seedReporter = { - 'reporter:jasmine-seed': ['type', karmaJasmineSeedReporter] // 1. 'jasmine-seed' is a name that can be referenced in karma.conf.js + 'reporter:jasmine-seed': ['type', KarmaJasmineSeedReporter], // 1. 'jasmine-seed' is a name that can be referenced in karma.conf.js +}; + +const SlowSpecsReporter = function(baseReporterDecorator) { + baseReporterDecorator(this); + let slowSpecs = []; + this.specSuccess = this.specFailure = function (browser, result) { + const seconds = (result.time) / 1000; + slowSpecs.push({ + time: result.time, + name: result.fullName, + message:`Spec ${result.fullName} took ${seconds} seconds\n` + }); + }; + + this.onBrowserComplete = function(browser, result) { + this.write('\n') + slowSpecs.sort((a, b) => { + return b.time - a.time; + }) + for (const spec of slowSpecs.slice(0, 20)) { + this.write(spec.message); + } + slowSpecs.length = 0; + }; }; +const timingReporter = { + 'reporter:jasmine-slow': ['type', SlowSpecsReporter], // 1. +} module.exports = (config) => { config.set({ @@ -32,7 +61,9 @@ module.exports = (config) => { require('karma-webpack'), require('karma-chrome-launcher'), require('karma-coverage-istanbul-reporter'), - seedReporter + require('karma-spec-reporter'), + seedReporter, + timingReporter ], client: { // Excalibur logs / console logs suppressed when captureConsole = false; @@ -120,7 +151,7 @@ module.exports = (config) => { // i. e. stats: 'normal' }, - reporters: ['progress', 'coverage-istanbul', 'jasmine-seed'], + reporters: ['progress', /*'spec'*/, 'coverage-istanbul','jasmine-seed', 'jasmine-slow'], coverageReporter: { reporters: [ { type: 'html', dir: 'coverage/' }, @@ -145,7 +176,14 @@ module.exports = (config) => { }, ChromiumHeadless_with_audio: { base: 'ChromiumHeadless', - flags: ['--autoplay-policy=no-user-gesture-required', '--mute-audio', '--disable-gpu', '--no-sandbox'] + flags: [ + '--autoplay-policy=no-user-gesture-required', + '--mute-audio', + '--disable-gpu', + '--no-sandbox', + '--enable-precise-memory-info', + '--js-flags="--max_old_space_size=8192 --expose-gc"' + ] }, ChromiumHeadless_with_debug: { base: 'ChromiumHeadless', @@ -153,7 +191,7 @@ module.exports = (config) => { }, Chromium_with_debug: { base: 'Chromium', - flags: ['--remote-debugging-port=9334', '--no-sandbox'] + flags: ['--remote-debugging-address=0.0.0.0', '--remote-debugging-port=9222', '--disable-web-security', '--mute-audio', '--no-sandbox'] } } }); diff --git a/package-lock.json b/package-lock.json index dac39951d..16748d82b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "eslint-config-prettier": "9.1.0", "eslint-plugin-jsdoc": "46.10.1", "eslint-plugin-storybook": "0.6.15", - "excalibur-jasmine": "0.2.0", + "excalibur-jasmine": "0.3.2", "istanbul": "0.4.5", "istanbul-instrumenter-loader": "3.0.1", "jasmine": "5.1.0", @@ -49,6 +49,7 @@ "karma-coverage": "2.2.1", "karma-coverage-istanbul-reporter": "3.0.3", "karma-jasmine": "5.1.0", + "karma-spec-reporter": "0.0.36", "karma-summary-reporter": "3.1.1", "karma-webpack": "5.0.1", "puppeteer": "15.5.0", @@ -13315,6 +13316,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -15370,11 +15380,39 @@ "node": ">=0.8.x" } }, + "node_modules/excalibur": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/excalibur/-/excalibur-0.28.7.tgz", + "integrity": "sha512-ws4hT4xE4+EaICdxHvI9pZV3L+RdtJK+H36KUfCoYQK3uaZNlnIO4JoeqMvKYbcCOZybKzW73W9+DyIjAiBvnA==", + "dev": true, + "peer": true, + "dependencies": { + "core-js": "3.33.3" + } + }, "node_modules/excalibur-jasmine": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/excalibur-jasmine/-/excalibur-jasmine-0.2.0.tgz", - "integrity": "sha512-k7jGuPgnA7ajSGX0P4sZ3qhCBSGqJWgQV3KWBdnjx+fU5K7cS7tmJyKDBVop94YtDMzQVBRlT6op3+BOFCa8gQ==", - "dev": true + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/excalibur-jasmine/-/excalibur-jasmine-0.3.2.tgz", + "integrity": "sha512-EOv0hswqSZ/9h1pzUaC6WEzMxWcqGMCAtMHDP0tuLIjpUp0fgDjGWWW5qefqYyMgED4Ei4Q7pWL9iX5AlagmyQ==", + "dev": true, + "dependencies": { + "pixelmatch": "5.3.0" + }, + "peerDependencies": { + "excalibur": "0.28.7" + } + }, + "node_modules/excalibur/node_modules/core-js": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", + "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, "node_modules/execa": { "version": "5.1.1", @@ -16346,9 +16384,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/graphemer": { @@ -18440,6 +18478,18 @@ "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", "dev": true }, + "node_modules/karma-spec-reporter": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz", + "integrity": "sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==", + "dev": true, + "dependencies": { + "colors": "1.4.0" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, "node_modules/karma-summary-reporter": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/karma-summary-reporter/-/karma-summary-reporter-3.1.1.tgz", @@ -20132,6 +20182,18 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "dev": true, + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/pkg-dir": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", @@ -20160,6 +20222,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true, + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/polished": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", @@ -34347,6 +34418,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -35856,11 +35933,33 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, + "excalibur": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/excalibur/-/excalibur-0.28.7.tgz", + "integrity": "sha512-ws4hT4xE4+EaICdxHvI9pZV3L+RdtJK+H36KUfCoYQK3uaZNlnIO4JoeqMvKYbcCOZybKzW73W9+DyIjAiBvnA==", + "dev": true, + "peer": true, + "requires": { + "core-js": "3.33.3" + }, + "dependencies": { + "core-js": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", + "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", + "dev": true, + "peer": true + } + } + }, "excalibur-jasmine": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/excalibur-jasmine/-/excalibur-jasmine-0.2.0.tgz", - "integrity": "sha512-k7jGuPgnA7ajSGX0P4sZ3qhCBSGqJWgQV3KWBdnjx+fU5K7cS7tmJyKDBVop94YtDMzQVBRlT6op3+BOFCa8gQ==", - "dev": true + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/excalibur-jasmine/-/excalibur-jasmine-0.3.2.tgz", + "integrity": "sha512-EOv0hswqSZ/9h1pzUaC6WEzMxWcqGMCAtMHDP0tuLIjpUp0fgDjGWWW5qefqYyMgED4Ei4Q7pWL9iX5AlagmyQ==", + "dev": true, + "requires": { + "pixelmatch": "5.3.0" + } }, "execa": { "version": "5.1.1", @@ -36600,9 +36699,9 @@ } }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "graphemer": { @@ -38209,6 +38308,15 @@ } } }, + "karma-spec-reporter": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz", + "integrity": "sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==", + "dev": true, + "requires": { + "colors": "1.4.0" + } + }, "karma-summary-reporter": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/karma-summary-reporter/-/karma-summary-reporter-3.1.1.tgz", @@ -39456,6 +39564,15 @@ "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true }, + "pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "dev": true, + "requires": { + "pngjs": "^6.0.0" + } + }, "pkg-dir": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", @@ -39477,6 +39594,12 @@ } } }, + "pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true + }, "polished": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", diff --git a/package.json b/package.json index 92769c12e..ca5a8b185 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "eslint-config-prettier": "9.1.0", "eslint-plugin-jsdoc": "46.10.1", "eslint-plugin-storybook": "0.6.15", - "excalibur-jasmine": "0.2.0", + "excalibur-jasmine": "0.3.2", "istanbul": "0.4.5", "istanbul-instrumenter-loader": "3.0.1", "jasmine": "5.1.0", @@ -99,6 +99,7 @@ "karma-coverage": "2.2.1", "karma-coverage-istanbul-reporter": "3.0.3", "karma-jasmine": "5.1.0", + "karma-spec-reporter": "0.0.36", "karma-summary-reporter": "3.1.1", "karma-webpack": "5.0.1", "puppeteer": "15.5.0", diff --git a/sandbox/src/game.ts b/sandbox/src/game.ts index e4553eaf0..b3085aee4 100644 --- a/sandbox/src/game.ts +++ b/sandbox/src/game.ts @@ -892,10 +892,16 @@ var emitter = new ex.ParticleEmitter({ acceleration: new ex.Vector(0, 460), beginColor: ex.Color.Red, endColor: ex.Color.Yellow, - // particleSprite: blockSpriteLegacy, + particleSprite: blockSprite, particleRotationalVelocity: Math.PI / 10, randomRotation: true }); +const original = (ex.ParticleEmitter.prototype as any)._createParticle; +(ex.ParticleEmitter.prototype as any)._createParticle = function () { + const particle = original.call(this); + particle.graphics.onPostDraw = () => {}; + return particle; +} game.add(emitter); var exploding = false; diff --git a/src/engine/Director/Loader.ts b/src/engine/Director/Loader.ts index c9695fd8f..e7324481c 100644 --- a/src/engine/Director/Loader.ts +++ b/src/engine/Director/Loader.ts @@ -215,7 +215,11 @@ export class Loader extends DefaultLoader { await delay(500, this.engine?.clock); } else { const resizeHandler = () => { - this._positionPlayButton(); + try { + this._positionPlayButton(); + } catch { + // swallow if can't position + }; }; if (this.engine?.browser) { this.engine.browser.window.on('resize', resizeHandler); diff --git a/src/engine/Engine.ts b/src/engine/Engine.ts index e903f4bb6..7f47cc100 100644 --- a/src/engine/Engine.ts +++ b/src/engine/Engine.ts @@ -1013,6 +1013,19 @@ O|===|* >________________>\n\ this.input.pointers.init(); } + private _disposed = false; + public dispose() { + if (!this._disposed) { + this._disposed = true; + this.stop(); + this.input.toggleEnabled(false); + this.canvas = null; + this.screen.dispose(); + this.graphicsContext.dispose(); + this.graphicsContext = null; + } + } + /** * Returns a BoundingBox of the top left corner of the screen * and the bottom right corner of the screen. diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts index 56032bad4..a564e24f3 100644 --- a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts +++ b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts @@ -385,5 +385,7 @@ export interface ExcaliburGraphicsContext { beginDrawLifecycle(): void; - endDrawLifecycle(): void + endDrawLifecycle(): void; + + dispose(): void; } diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts index ea8b4ac3b..df9cd4cc7 100644 --- a/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts +++ b/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts @@ -79,6 +79,10 @@ class ExcaliburGraphicsContext2DCanvasDebug implements DebugDraw { } } +export interface ExcaliburGraphicsContext2DOptions extends ExcaliburGraphicsContextOptions { + context?: CanvasRenderingContext2D; +} + export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContext { /** * Meant for internal use only. Access the internal context at your own risk and no guarantees this will exist in the future. @@ -133,11 +137,14 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex this.__ctx.imageSmoothingEnabled = value; } - constructor(options: ExcaliburGraphicsContextOptions) { - const { canvasElement, enableTransparency, snapToPixel, antialiasing: smoothing, backgroundColor } = options; - this.__ctx = canvasElement.getContext('2d', { + constructor(options: ExcaliburGraphicsContext2DOptions) { + const { canvasElement, context, enableTransparency, snapToPixel, antialiasing: smoothing, backgroundColor } = options; + this.__ctx = context ?? canvasElement.getContext('2d', { alpha: enableTransparency ?? true }); + if (!this.__ctx) { + throw new Error('Cannot build new ExcaliburGraphicsContext2D for some reason!'); + } this.backgroundColor = backgroundColor ?? this.backgroundColor; this.snapToPixel = snapToPixel ?? this.snapToPixel; this.smoothing = smoothing ?? this.smoothing; @@ -359,4 +366,8 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex flush(): void { // pass } + + dispose(): void { + this.__ctx = null; + } } diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts index c50f4caa1..6f5c97d80 100644 --- a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts +++ b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts @@ -86,6 +86,10 @@ export interface WebGLGraphicsContextInfo { context: ExcaliburGraphicsContextWebGL; } +export interface ExcaliburGraphicsContextWebGLOptions extends ExcaliburGraphicsContextOptions { + context?: WebGL2RenderingContext +} + export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { private _logger = Logger.getInstance(); private _renderers: Map = new Map(); @@ -206,9 +210,10 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { public readonly samples?: number; public readonly transparency: boolean = true; - constructor(options: ExcaliburGraphicsContextOptions) { + constructor(options: ExcaliburGraphicsContextWebGLOptions) { const { canvasElement, + context, enableTransparency, antialiasing, uvPadding, @@ -219,7 +224,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { backgroundColor, useDrawSorting } = options; - this.__gl = canvasElement.getContext('webgl2', { + this.__gl = context ?? canvasElement.getContext('webgl2', { antialias: antialiasing ?? this.smoothing, premultipliedAlpha: false, alpha: enableTransparency ?? this.transparency, @@ -244,6 +249,21 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { this._init(); } + private _disposed = false; + public dispose() { + if (!this._disposed) { + this._disposed = true; + this.textureLoader.dispose(); + for (const renderer of this._renderers.values()) { + renderer.dispose(); + } + this._renderers.clear(); + this._drawCallPool.dispose(); + this._drawCalls.length = 0; + this.__gl = null; + } + } + private _init() { const gl = this.__gl; // Setup viewport and view matrix diff --git a/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts b/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts index 9e508e92a..e34463c06 100644 --- a/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts +++ b/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts @@ -64,6 +64,14 @@ export class CircleRenderer implements RendererPlugin { this._quads = new QuadIndexBuffer(gl, this._maxCircles, true); } + public dispose() { + this._buffer.dispose(); + this._quads.dispose(); + this._shader.dispose(); + this._context = null; + this._gl = null; + } + private _isFull() { if (this._circleCount >= this._maxCircles) { return true; diff --git a/src/engine/Graphics/Context/image-renderer/image-renderer.ts b/src/engine/Graphics/Context/image-renderer/image-renderer.ts index e0882ad4f..a520d79c7 100644 --- a/src/engine/Graphics/Context/image-renderer/image-renderer.ts +++ b/src/engine/Graphics/Context/image-renderer/image-renderer.ts @@ -92,6 +92,15 @@ export class ImageRenderer implements RendererPlugin { this._quads = new QuadIndexBuffer(gl, this._maxImages, true); } + public dispose() { + this._buffer.dispose(); + this._quads.dispose(); + this._shader.dispose(); + this._textures.length = 0; + this._context = null; + this._gl = null; + } + private _transformFragmentSource(source: string, maxTextures: number): string { let newSource = source.replace('%%count%%', maxTextures.toString()); let texturePickerBuilder = ''; diff --git a/src/engine/Graphics/Context/line-renderer/line-renderer.ts b/src/engine/Graphics/Context/line-renderer/line-renderer.ts index 8211d46e2..1b8df8bc2 100644 --- a/src/engine/Graphics/Context/line-renderer/line-renderer.ts +++ b/src/engine/Graphics/Context/line-renderer/line-renderer.ts @@ -48,6 +48,13 @@ export class LineRenderer implements RendererPlugin { }); } + public dispose() { + this._vertexBuffer.dispose(); + this._shader.dispose(); + this._context = null; + this._gl = null; + } + draw(start: Vector, end: Vector, color: Color): void { // Force a render if the batch is full if (this._isFull()) { diff --git a/src/engine/Graphics/Context/material-renderer/material-renderer.ts b/src/engine/Graphics/Context/material-renderer/material-renderer.ts index 6ddb5fcb5..46f114361 100644 --- a/src/engine/Graphics/Context/material-renderer/material-renderer.ts +++ b/src/engine/Graphics/Context/material-renderer/material-renderer.ts @@ -45,6 +45,14 @@ export class MaterialRenderer implements RendererPlugin { this._quads = new QuadIndexBuffer(gl, 1, true); } + public dispose() { + this._buffer.dispose(); + this._quads.dispose(); + this._textures.length = 0; + this._context = null; + this._gl = null; + } + draw(image: HTMLImageSource, sx: number, sy: number, diff --git a/src/engine/Graphics/Context/point-renderer/point-renderer.ts b/src/engine/Graphics/Context/point-renderer/point-renderer.ts index ac76b273e..23aadfef4 100644 --- a/src/engine/Graphics/Context/point-renderer/point-renderer.ts +++ b/src/engine/Graphics/Context/point-renderer/point-renderer.ts @@ -49,6 +49,13 @@ export class PointRenderer implements RendererPlugin { }); } + public dispose() { + this._buffer.dispose(); + this._shader.dispose(); + this._context = null; + this._gl = null; + } + draw(point: Vector, color: Color, size: number): void { // Force a render if the batch is full if (this._isFull()) { diff --git a/src/engine/Graphics/Context/quad-index-buffer.ts b/src/engine/Graphics/Context/quad-index-buffer.ts index bbcd946d8..8971b9e51 100644 --- a/src/engine/Graphics/Context/quad-index-buffer.ts +++ b/src/engine/Graphics/Context/quad-index-buffer.ts @@ -86,4 +86,10 @@ export class QuadIndexBuffer { const gl = this._gl; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffer); } + + public dispose() { + const gl = this._gl; + gl.deleteBuffer(this.buffer); + this._gl = null; + } } \ No newline at end of file diff --git a/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts b/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts index 52bbc214f..c1008a00c 100644 --- a/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts +++ b/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts @@ -65,6 +65,14 @@ export class RectangleRenderer implements RendererPlugin { this._quads = new QuadIndexBuffer(gl, this._maxRectangles, true); } + public dispose() { + this._buffer.dispose(); + this._quads.dispose(); + this._shader.dispose(); + this._context = null; + this._gl = null; + } + private _isFull() { if (this._rectangleCount >= this._maxRectangles) { return true; diff --git a/src/engine/Graphics/Context/renderer.ts b/src/engine/Graphics/Context/renderer.ts index ec9be8f88..861ea45c4 100644 --- a/src/engine/Graphics/Context/renderer.ts +++ b/src/engine/Graphics/Context/renderer.ts @@ -39,4 +39,9 @@ export interface RendererPlugin { * Flush any pending graphics draws to the screen */ flush(): void; + + /** + * Clear out any allocated memory + */ + dispose(): void; } diff --git a/src/engine/Graphics/Context/shader.ts b/src/engine/Graphics/Context/shader.ts index 97d29dd60..b7f6f6875 100644 --- a/src/engine/Graphics/Context/shader.ts +++ b/src/engine/Graphics/Context/shader.ts @@ -112,6 +112,12 @@ export class Shader { this.fragmentSource = fragmentSource; } + dispose() { + const gl = this._gl; + gl.deleteProgram(this.program); + this._gl = null; + } + /** * Binds the shader program */ diff --git a/src/engine/Graphics/Context/texture-loader.ts b/src/engine/Graphics/Context/texture-loader.ts index aef431c4f..e7e9a331a 100644 --- a/src/engine/Graphics/Context/texture-loader.ts +++ b/src/engine/Graphics/Context/texture-loader.ts @@ -13,6 +13,14 @@ export class TextureLoader { TextureLoader._MAX_TEXTURE_SIZE = gl.getParameter(gl.MAX_TEXTURE_SIZE); } + public dispose() { + for (const [image] of this._textureMap) { + this.delete(image); + } + this._textureMap.clear(); + this._gl = null; + } + /** * Sets the default filtering for the Excalibur texture loader, default [[ImageFiltering.Blended]] */ diff --git a/src/engine/Graphics/Context/vertex-buffer.ts b/src/engine/Graphics/Context/vertex-buffer.ts index df89f1a70..eaf6ac8ed 100644 --- a/src/engine/Graphics/Context/vertex-buffer.ts +++ b/src/engine/Graphics/Context/vertex-buffer.ts @@ -87,4 +87,10 @@ export class VertexBuffer { gl.bufferData(gl.ARRAY_BUFFER, this.bufferData, this.type === 'static' ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); } } + + dispose() { + const gl = this._gl; + gl.deleteBuffer(this.buffer); + this._gl = null; + } } \ No newline at end of file diff --git a/src/engine/Screen.ts b/src/engine/Screen.ts index f471faa31..27eb1bb2b 100644 --- a/src/engine/Screen.ts +++ b/src/engine/Screen.ts @@ -325,10 +325,14 @@ export class Screen { this._mediaQueryList.removeListener(this._pixelRatioChangeHandler); } this._canvas.removeEventListener('fullscreenchange', this._fullscreenChangeHandler); + this._canvas = null; } } private _fullscreenChangeHandler = () => { + if (this._isDisposed) { + return; + } this._isFullScreen = !this._isFullScreen; this._logger.debug('Fullscreen Change', this._isFullScreen); this.events.emit('fullscreen', { @@ -337,6 +341,9 @@ export class Screen { }; private _pixelRatioChangeHandler = () => { + if (this._isDisposed) { + return; + } this._logger.debug('Pixel Ratio Change', window.devicePixelRatio); this._listenForPixelRatio(); this._devicePixelRatio = this._calculateDevicePixelRatio(); @@ -347,6 +354,9 @@ export class Screen { }; private _resizeHandler = () => { + if (this._isDisposed) { + return; + } const parent = this.parent; this._logger.debug('View port resized'); this._setResolutionAndViewportByDisplayMode(parent); diff --git a/src/engine/Util/Clock.ts b/src/engine/Util/Clock.ts index 9413f75ff..1076680e1 100644 --- a/src/engine/Util/Clock.ts +++ b/src/engine/Util/Clock.ts @@ -209,6 +209,7 @@ export class StandardClock extends Clock { } public stop(): void { + window.cancelAnimationFrame(this._requestId); this._running = false; } } diff --git a/src/engine/Util/Pool.ts b/src/engine/Util/Pool.ts index c7b272226..46b734bfb 100644 --- a/src/engine/Util/Pool.ts +++ b/src/engine/Util/Pool.ts @@ -12,6 +12,10 @@ export class Pool { public maxObjects: number = 100 ) {} + dispose() { + this.objects.length = 0; + } + preallocate() { for (let i = 0; i < this.maxObjects; i++) { this.objects[i] = this.builder(); diff --git a/src/spec/ActionSpec.ts b/src/spec/ActionSpec.ts index 29cb3ecf1..aefc3b227 100644 --- a/src/spec/ActionSpec.ts +++ b/src/spec/ActionSpec.ts @@ -27,6 +27,7 @@ describe('Action', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts index 0ca794afa..c2f83b1df 100644 --- a/src/spec/ActorSpec.ts +++ b/src/spec/ActorSpec.ts @@ -45,6 +45,7 @@ describe('A game actor', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; }); @@ -712,14 +713,14 @@ describe('A game actor', () => { scene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/ActorSpec/zindex-blue-top.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/ActorSpec/zindex-blue-top.png'); green.z = 2; blue.z = 1; scene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/ActorSpec/zindex-green-top.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/ActorSpec/zindex-green-top.png'); }); it('can have a graphic drawn at an opacity', async () => { @@ -760,7 +761,7 @@ describe('A game actor', () => { scene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/SpriteSpec/opacity.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/SpriteSpec/opacity.png'); }); // it('will tick animations when drawing switched', async () => { diff --git a/src/spec/CameraSpec.ts b/src/spec/CameraSpec.ts index 2464f1059..cbbf81b8c 100644 --- a/src/spec/CameraSpec.ts +++ b/src/spec/CameraSpec.ts @@ -18,11 +18,10 @@ describe('A camera', () => { // mock engine engine = TestUtils.engine({ width: 500, - height: 500 + height: 500, + antialiasing: false }); - engine.setAntialiasing(false); - engine.backgroundColor = ex.Color.Blue; actor.pos.x = 250; @@ -33,10 +32,13 @@ describe('A camera', () => { engine.addScene('root', scene); Camera = new ex.Camera(); + Camera._initialize(engine); }); afterEach(() => { engine.stop(); + engine.dispose(); + engine = null; }); it('should be center screen by default (when loading not complete)', () => { @@ -381,9 +383,15 @@ describe('A camera', () => { describe('lifecycle overrides', () => { let camera: ex.Camera; + let engine: ex.Engine; beforeEach(() => { camera = new ex.Camera(); + engine = TestUtils.engine(); + }); + afterEach(() => { + engine.dispose(); + engine = null; }); it('can have onInitialize overridden safely', () => { diff --git a/src/spec/CanvasSpec.ts b/src/spec/CanvasSpec.ts index f27501af8..342426ab3 100644 --- a/src/spec/CanvasSpec.ts +++ b/src/spec/CanvasSpec.ts @@ -135,7 +135,7 @@ describe('A Canvas Graphic', () => { expect(sut.width).toBe(50); expect(sut.height).toBe(50); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/GraphicsCanvasSpec/centered.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/GraphicsCanvasSpec/centered.png'); }); }); diff --git a/src/spec/CollisionShapeSpec.ts b/src/spec/CollisionShapeSpec.ts index b0518c364..74a1677d6 100644 --- a/src/spec/CollisionShapeSpec.ts +++ b/src/spec/CollisionShapeSpec.ts @@ -377,7 +377,7 @@ describe('Collision Shape', () => { scene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/CollisionShapeSpec/circle.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/CollisionShapeSpec/circle.png'); }); it('can calculate the distance to another circle', () => { @@ -767,7 +767,7 @@ describe('Collision Shape', () => { engine.graphicsContext.restore(); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/CollisionShapeSpec/triangle.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/CollisionShapeSpec/triangle.png'); }); it('can be drawn with actor', async () => { @@ -781,7 +781,7 @@ describe('Collision Shape', () => { scene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/CollisionShapeSpec/triangle.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/CollisionShapeSpec/triangle.png'); }); it('can calculate the distance to another circle', () => { @@ -991,7 +991,7 @@ describe('Collision Shape', () => { engine.graphicsContext.restore(); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/CollisionShapeSpec/edge.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/CollisionShapeSpec/edge.png'); }); it('can be drawn with actor', async () => { @@ -1005,7 +1005,7 @@ describe('Collision Shape', () => { scene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/CollisionShapeSpec/edge.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/CollisionShapeSpec/edge.png'); }); it('can calculate the distance to another circle', () => { diff --git a/src/spec/ColorBlindCorrectorSpec.ts b/src/spec/ColorBlindCorrectorSpec.ts index b9c57bfcb..15fabd3d2 100644 --- a/src/spec/ColorBlindCorrectorSpec.ts +++ b/src/spec/ColorBlindCorrectorSpec.ts @@ -1,18 +1,6 @@ import * as ex from '@excalibur'; import { TestUtils } from './util/TestUtils'; -import { ExcaliburMatchers, ensureImagesLoaded } from 'excalibur-jasmine'; - -/** - * - */ -function flushWebGLCanvasTo2D(source: HTMLCanvasElement): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - canvas.width = source.width; - canvas.height = source.height; - const ctx = canvas.getContext('2d'); - ctx.drawImage(source, 0, 0); - return canvas; -} +import { ExcaliburAsyncMatchers, ExcaliburMatchers, ensureImagesLoaded } from 'excalibur-jasmine'; describe('A ColorBlindCorrector', () => { let bg: ex.ImageSource; @@ -21,6 +9,7 @@ describe('A ColorBlindCorrector', () => { beforeEach(async () => { jasmine.addMatchers(ExcaliburMatchers); + jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); engine = TestUtils.engine({ width: 800, height: 200 }, []); bg = new ex.ImageSource('src/spec/images/ColorBlindCorrectorSpec/actor.png'); @@ -31,6 +20,7 @@ describe('A ColorBlindCorrector', () => { afterEach(() => { engine.stop(); + engine.dispose(); }); it('is normal', (done) => { @@ -38,9 +28,7 @@ describe('A ColorBlindCorrector', () => { actor.graphics.use(bg.toSprite()); engine.add(actor); engine.once('postframe', (ev: ex.PostDrawEvent) => { - ensureImagesLoaded(flushWebGLCanvasTo2D(engine.canvas), - 'src/spec/images/ColorBlindCorrectorSpec/normal.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ColorBlindCorrectorSpec/normal.png').then(() => { done(); }); }); @@ -53,9 +41,7 @@ describe('A ColorBlindCorrector', () => { engine.add(actor); engine.debug.colorBlindMode.correct(ex.ColorBlindnessMode.Deuteranope); engine.once('postframe', (ev: ex.PostDrawEvent) => { - ensureImagesLoaded(flushWebGLCanvasTo2D(engine.canvas), - 'src/spec/images/ColorBlindCorrectorSpec/deuteranope_correct.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ColorBlindCorrectorSpec/deuteranope_correct.png').then(() => { done(); }); }); @@ -68,9 +54,7 @@ describe('A ColorBlindCorrector', () => { engine.add(actor); engine.debug.colorBlindMode.simulate(ex.ColorBlindnessMode.Deuteranope); engine.once('postframe', (ev: ex.PostDrawEvent) => { - ensureImagesLoaded(flushWebGLCanvasTo2D(engine.canvas), - 'src/spec/images/ColorBlindCorrectorSpec/deuteranope_simulate.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ColorBlindCorrectorSpec/deuteranope_simulate.png').then(() => { done(); }); }); @@ -83,9 +67,7 @@ describe('A ColorBlindCorrector', () => { engine.add(actor); engine.debug.colorBlindMode.correct(ex.ColorBlindnessMode.Protanope); engine.once('postframe', (ev: ex.PostDrawEvent) => { - ensureImagesLoaded(flushWebGLCanvasTo2D(engine.canvas), - 'src/spec/images/ColorBlindCorrectorSpec/protanope_correct.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ColorBlindCorrectorSpec/protanope_correct.png').then(() => { done(); }); }); @@ -98,9 +80,7 @@ describe('A ColorBlindCorrector', () => { engine.add(actor); engine.debug.colorBlindMode.simulate(ex.ColorBlindnessMode.Protanope); engine.once('postframe', (ev: ex.PostDrawEvent) => { - ensureImagesLoaded(flushWebGLCanvasTo2D(engine.canvas), - 'src/spec/images/ColorBlindCorrectorSpec/protanope_simulate.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ColorBlindCorrectorSpec/protanope_simulate.png').then(() => { done(); }); }); @@ -113,9 +93,7 @@ describe('A ColorBlindCorrector', () => { engine.add(actor); engine.debug.colorBlindMode.correct(ex.ColorBlindnessMode.Tritanope); engine.once('postframe', (ev: ex.PostDrawEvent) => { - ensureImagesLoaded(flushWebGLCanvasTo2D(engine.canvas), - 'src/spec/images/ColorBlindCorrectorSpec/tritanope_correct.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ColorBlindCorrectorSpec/tritanope_correct.png').then(() => { done(); }); }); @@ -128,9 +106,7 @@ describe('A ColorBlindCorrector', () => { engine.add(actor); engine.debug.colorBlindMode.simulate(ex.ColorBlindnessMode.Tritanope); engine.once('postframe', (ev: ex.PostDrawEvent) => { - ensureImagesLoaded(flushWebGLCanvasTo2D(engine.canvas), - 'src/spec/images/ColorBlindCorrectorSpec/tritanope_simulate.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ColorBlindCorrectorSpec/tritanope_simulate.png').then(() => { done(); }); }); diff --git a/src/spec/CrossFadeSpec.ts b/src/spec/CrossFadeSpec.ts index 3f846dfe4..ef776b8b5 100644 --- a/src/spec/CrossFadeSpec.ts +++ b/src/spec/CrossFadeSpec.ts @@ -85,6 +85,6 @@ describe('A CrossFade transition', () => { expect(engine.currentSceneName).toBe('newScene'); expect(onDeactivateSpy).toHaveBeenCalledTimes(1); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('/src/spec/images/CrossFadeSpec/crossfade.png'); + await expectAsync(engine.canvas).toEqualImage('/src/spec/images/CrossFadeSpec/crossfade.png'); }); }); \ No newline at end of file diff --git a/src/spec/DebugSystemSpec.ts b/src/spec/DebugSystemSpec.ts index 74d17a4d7..48a6547b0 100644 --- a/src/spec/DebugSystemSpec.ts +++ b/src/spec/DebugSystemSpec.ts @@ -75,7 +75,7 @@ describe('DebugSystem', () => { engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/DebugSystemSpec/transform.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/DebugSystemSpec/transform.png'); }); it('can show motion info', async () => { @@ -99,7 +99,7 @@ describe('DebugSystem', () => { engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/DebugSystemSpec/motion.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/DebugSystemSpec/motion.png'); }); it('can show body info', async () => { @@ -121,7 +121,7 @@ describe('DebugSystem', () => { engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/DebugSystemSpec/body.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/DebugSystemSpec/body.png'); }); it('can show collider info', async () => { @@ -142,7 +142,7 @@ describe('DebugSystem', () => { engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/DebugSystemSpec/collider.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/DebugSystemSpec/collider.png'); }); it('can show composite collider info', async () => { @@ -164,7 +164,7 @@ describe('DebugSystem', () => { engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/DebugSystemSpec/composite-collider.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/DebugSystemSpec/composite-collider.png'); }); it('can show graphics info', async () => { @@ -188,7 +188,7 @@ describe('DebugSystem', () => { engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/DebugSystemSpec/graphics.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/DebugSystemSpec/graphics.png'); }); it('can show DebugGraphicsComponent', async () => { @@ -208,7 +208,7 @@ describe('DebugSystem', () => { debugSystem.update(); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/DebugSystemSpec/debug-draw-component.png'); }); @@ -236,6 +236,6 @@ describe('DebugSystem', () => { debugSystem.update(); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/DebugSystemSpec/tilemap-debug.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/DebugSystemSpec/tilemap-debug.png'); }); }); diff --git a/src/spec/DebugTextSpec.ts b/src/spec/DebugTextSpec.ts index b952f45a0..89eb71c53 100644 --- a/src/spec/DebugTextSpec.ts +++ b/src/spec/DebugTextSpec.ts @@ -1,18 +1,6 @@ import * as ex from '@excalibur'; import { ExcaliburAsyncMatchers } from 'excalibur-jasmine'; -/** - * - */ -function flushWebGLCanvasTo2D(source: HTMLCanvasElement): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - canvas.width = source.width; - canvas.height = source.height; - const ctx = canvas.getContext('2d'); - ctx.drawImage(source, 0, 0); - return canvas; -} - describe('DebugText', () => { beforeAll(() => { jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); @@ -57,6 +45,6 @@ describe('DebugText', () => { ctx.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/DebugTextSpec/draw-webgl.png', .94); + await expectAsync(canvasElement).toEqualImage('src/spec/images/DebugTextSpec/draw-webgl.png', .94); }); }); diff --git a/src/spec/DirectorSpec.ts b/src/spec/DirectorSpec.ts index 8b49a1ad4..e1976c2ff 100644 --- a/src/spec/DirectorSpec.ts +++ b/src/spec/DirectorSpec.ts @@ -133,7 +133,7 @@ describe('A Director', () => { expect(sut.currentSceneName).toBe('scene1'); expect(sut.currentScene).toBe(scene1); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('/src/spec/images/DirectorSpec/fadein.png'); + await expectAsync(engine.canvas).toEqualImage('/src/spec/images/DirectorSpec/fadein.png'); }); it('will run the loader cycle on a scene only once', async () => { diff --git a/src/spec/EngineSpec.ts b/src/spec/EngineSpec.ts index 07555c739..85acfecb3 100644 --- a/src/spec/EngineSpec.ts +++ b/src/spec/EngineSpec.ts @@ -13,13 +13,13 @@ function flushWebGLCanvasTo2D(source: HTMLCanvasElement): HTMLCanvasElement { ctx.drawImage(source, 0, 0); return canvas; } - -describe('The engine', () => { // TODO timeout +describe('The engine', () => { let engine: ex.Engine; let scene: ex.Scene; const reset = () => { engine.stop(); + engine.dispose(); engine = null; (window).devicePixelRatio = 1; const playButton = document.getElementById('excalibur-play'); @@ -55,6 +55,7 @@ describe('The engine', () => { // TODO timeout clock.setFatalExceptionHandler(exceptionSpy); clock.start(); clock.step(100); + engine.dispose(); }; await boot(); @@ -78,7 +79,7 @@ describe('The engine', () => { // TODO timeout setTimeout(() => { // needed for the delay to work testClock.run(1, 100); engine.graphicsContext.flush(); - expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + expectAsync(engine.canvas) .toEqualImage('src/spec/images/EngineSpec/engine-load-complete.png').then(() => { done(); }); @@ -147,6 +148,7 @@ describe('The engine', () => { // TODO timeout clock.run(100, 1); expect(engine.useCanvas2DFallback).toHaveBeenCalled(); + engine.dispose(); }); it('can use a fixed update fps and can catch up', async () => { @@ -250,7 +252,7 @@ describe('The engine', () => { // TODO timeout // With suppress play there is another 500 ms delay in engine load() testClock.step(1); engine.graphicsContext.flush(); - expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + expectAsync(engine.canvas) .toEqualImage('src/spec/images/EngineSpec/engine-suppress-play.png').then(() => { done(); }); @@ -667,6 +669,7 @@ describe('The engine', () => { // TODO timeout expect(engine.currentScene).toBe(scene2); expect(scene1.actors.length).toBe(0); expect(scene2.actors.length).toBe(1); + engine.dispose(); }); it('can screen shot the game (in WebGL)', (done) => { @@ -687,6 +690,7 @@ describe('The engine', () => { // TODO timeout engine.screenshot().then((image) => { expectAsync(image).toEqualImage(flushWebGLCanvasTo2D(engine.canvas)).then(() => { done(); + engine.dispose(); }); }); clock.step(1); @@ -727,6 +731,7 @@ describe('The engine', () => { // TODO timeout expect(hidpiImage.width).toBe(1000); expect(hidpiImage.height).toBe(1000); await expectAsync(hidpiImage).toEqualImage(flushWebGLCanvasTo2D(engine.canvas)); + engine.dispose(); }); it('can screen shot and match the anti-aliasing with a half pixel when pixelRatio != 1.0', async () => { @@ -815,7 +820,7 @@ describe('The engine', () => { // TODO timeout clock.step(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/EngineSpec/snaptopixel.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/EngineSpec/snaptopixel.png'); }); it('can do subpixel AA on pixel art', async () => { @@ -881,7 +886,7 @@ describe('The engine', () => { // TODO timeout engine.currentScene.camera.pos = player.pos; clock.step(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/EngineSpec/pixelart.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/EngineSpec/pixelart.png'); }); describe('lifecycle overrides', () => { diff --git a/src/spec/ExcaliburGraphicsContextSpec.ts b/src/spec/ExcaliburGraphicsContextSpec.ts index 6ece4d247..aa9d2d982 100644 --- a/src/spec/ExcaliburGraphicsContextSpec.ts +++ b/src/spec/ExcaliburGraphicsContextSpec.ts @@ -1,22 +1,23 @@ import * as ex from '@excalibur'; import { TextureLoader } from '@excalibur'; import { ExcaliburAsyncMatchers, ExcaliburMatchers } from 'excalibur-jasmine'; -import { TestUtils } from './util/TestUtils'; - -/** - * - */ -function flushWebGLCanvasTo2D(source: HTMLCanvasElement): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - canvas.width = source.width; - canvas.height = source.height; - const ctx = canvas.getContext('2d'); - ctx.drawImage(source, 0, 0); - return canvas; -} + + describe('The ExcaliburGraphicsContext', () => { describe('2D', () => { + let testCanvasElement: HTMLCanvasElement; + let testContext: CanvasRenderingContext2D; + beforeAll(() => { + testCanvasElement = document.createElement('canvas'); + testContext = testCanvasElement.getContext('2d'); + }); + afterAll(() => { + testCanvasElement.width = 0; + testCanvasElement.height = 0; + testCanvasElement = null; + testContext = null; + }); beforeEach(() => { jasmine.addMatchers(ExcaliburMatchers); jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); @@ -27,16 +28,17 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can be constructed', () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; const context = new ex.ExcaliburGraphicsContext2DCanvas({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Red }); expect(context).toBeDefined(); }); it('has the same dimensions as the canvas', () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 123; canvas.height = 456; const context = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -48,7 +50,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can draw a graphic', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -70,7 +72,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can draw debug point', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -89,7 +91,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can draw debug line', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -107,7 +109,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can transform the context', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -135,7 +137,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can draw rectangle', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -152,7 +154,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can draw circle', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -169,7 +171,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can draw a line', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -186,7 +188,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can snap drawings to pixel', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -209,7 +211,7 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can handle drawing a zero dimension image', () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContext2DCanvas({ @@ -240,10 +242,29 @@ describe('The ExcaliburGraphicsContext', () => { }); describe('WebGL', () => { + let testCanvasElement: HTMLCanvasElement; + let testContext: WebGL2RenderingContext; + beforeAll(() => { + // testCanvasElement = document.createElement('canvas'); + // testContext = testCanvasElement.getContext('webgl2', { + // antialias: false, + // premultipliedAlpha: false, + // alpha: false, + // depth: false, + // powerPreference: 'high-performance' + // }); + }); + afterAll(() => { + testCanvasElement.width = 0; + testCanvasElement.height = 0; + testCanvasElement = null; + testContext = null; + }); beforeEach(() => { jasmine.addMatchers(ExcaliburMatchers); jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); TextureLoader.filtering = ex.ImageFiltering.Pixel; + testCanvasElement = document.createElement('canvas'); }); it('exists', () => { @@ -251,17 +272,20 @@ describe('The ExcaliburGraphicsContext', () => { }); it('can be constructed', () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Red }); expect(context).toBeDefined(); + context.dispose(); }); it('will throw if an invalid renderer is specified', () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Red }); expect(() => { @@ -270,23 +294,26 @@ describe('The ExcaliburGraphicsContext', () => { }); it('has the same dimensions as the canvas', () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 123; canvas.height = 456; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Red }); expect(context.width).toBe(canvas.width); expect(context.height).toBe(canvas.height); + context.dispose(); }); it('will snap rectangles to the nearest pixel', async () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 10; canvas.height = 10; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Black, antialiasing: false, multiSampleAntialiasing: false, @@ -302,15 +329,17 @@ describe('The ExcaliburGraphicsContext', () => { rect.draw(context, .5, .5);//1 - 0.001, 1 - 0.001); context.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/pixel-snap.png'); + await expectAsync(canvas).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/pixel-snap.png'); + context.dispose(); }); it('will snap rectangle graphics to the next pixel if close to the border', async () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 10; canvas.height = 10; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Black, antialiasing: false, multiSampleAntialiasing: false, @@ -326,17 +355,19 @@ describe('The ExcaliburGraphicsContext', () => { rect.draw(context, 1 - 0.0001, 1 - 0.0001); context.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)).toEqualImage( + await expectAsync(canvas).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/pixel-snap-next.png' ); + context.dispose(); }); it('will snap rectangles to the next pixel if close to the border', async () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 10; canvas.height = 10; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Black, antialiasing: false, multiSampleAntialiasing: false, @@ -347,17 +378,19 @@ describe('The ExcaliburGraphicsContext', () => { context.drawRectangle(ex.vec(1 - 0.0001, 1 - 0.0001), 2, 2, ex.Color.Red); context.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)).toEqualImage( + await expectAsync(canvas).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/pixel-snap-next.png' ); + context.dispose(); }); it('will snap lines to the next pixel if close to the border', async () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 10; canvas.height = 10; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Black, antialiasing: false, multiSampleAntialiasing: false, @@ -368,17 +401,19 @@ describe('The ExcaliburGraphicsContext', () => { context.drawLine(ex.vec(1 - 0.0001, 2 - 0.0001), ex.vec(5, 2 - 0.0001), ex.Color.Red, 2); context.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)).toEqualImage( + await expectAsync(canvas).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/pixel-snap-line-next.png' ); + context.dispose(); }); it('will snap circles to the next pixel if close to the border', async () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 10; canvas.height = 10; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Black, antialiasing: false, multiSampleAntialiasing: false, @@ -389,17 +424,19 @@ describe('The ExcaliburGraphicsContext', () => { context.drawCircle(ex.vec(5 - 0.0001, 5 - 0.0001), 3, ex.Color.Red); context.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)).toEqualImage( + await expectAsync(canvas).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/pixel-snap-circle-next.png' ); + context.dispose(); }); it('will snap points to the next pixel if close to the border', async () => { - const canvas = document.createElement('canvas'); + const canvas = testCanvasElement; canvas.width = 10; canvas.height = 10; const context = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvas, + context: testContext, backgroundColor: ex.Color.Black, antialiasing: false, multiSampleAntialiasing: false, @@ -413,17 +450,19 @@ describe('The ExcaliburGraphicsContext', () => { }); context.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)).toEqualImage( + await expectAsync(canvas).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/pixel-snap-point-next.png' ); + context.dispose(); }); it('can draw a graphic', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -438,17 +477,19 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawImage(rect._bitmap, 20, 20); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/2d-drawgraphic.png' ); + sut.dispose(); }); it('can draw debug point', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -460,21 +501,23 @@ describe('The ExcaliburGraphicsContext', () => { }); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/webgl-drawpoint.png' ); + sut.dispose(); }); it('will log a warning if you attempt to draw outside the lifecycle', () => { const logger = ex.Logger.getInstance(); spyOn(logger, 'warnOnce').and.callThrough(); - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -483,18 +526,20 @@ describe('The ExcaliburGraphicsContext', () => { expect(logger.warnOnce).toHaveBeenCalledWith( `Attempting to draw outside the the drawing lifecycle (preDraw/postDraw) is not supported and is a source of bugs/errors.\n`+ `If you want to do custom drawing, use Actor.graphics, or any onPreDraw or onPostDraw handler.`); + sut.dispose(); }); it('will not log a warning inside the lifecycle', () => { const logger = ex.Logger.getInstance(); spyOn(logger, 'warn').and.callThrough(); - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -502,14 +547,16 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawCircle(ex.vec(0, 0), 10, ex.Color.Blue); expect(logger.warn).not.toHaveBeenCalled(); sut.endDrawLifecycle(); + sut.dispose(); }); it('will preserve the painter order when switching renderer (no draw sorting)', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, antialiasing: false, multiSampleAntialiasing: false, @@ -552,18 +599,20 @@ describe('The ExcaliburGraphicsContext', () => { expect(circleRenderer.flush).toHaveBeenCalledTimes(1); expect(imageRenderer.flush).toHaveBeenCalledTimes(1); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/painter-order-circle-image-rect.png', .97 ); + sut.dispose(); }); it('will preserve the painter order when switching renderer (draw sorting)', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, antialiasing: false, multiSampleAntialiasing: false, @@ -603,18 +652,20 @@ describe('The ExcaliburGraphicsContext', () => { expect(circleRenderer.flush).toHaveBeenCalledTimes(1); expect(imageRenderer.flush).toHaveBeenCalledTimes(1); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/painter-order-circle-image-rect.png', .97 ); + sut.dispose(); }); it('can draw debug line', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, antialiasing: false, multiSampleAntialiasing: false, @@ -627,17 +678,19 @@ describe('The ExcaliburGraphicsContext', () => { }); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/webgl-drawline.png' ); + sut.dispose(); }); it('can draw debug rectangle', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, antialiasing: false, multiSampleAntialiasing: false, @@ -650,15 +703,17 @@ describe('The ExcaliburGraphicsContext', () => { }); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/webgl-rect.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/webgl-rect.png'); + sut.dispose(); }); it('can draw rectangle', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -667,17 +722,19 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawRectangle(ex.vec(10, 10), 80, 80, ex.Color.Blue); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/webgl-solid-rect.png' ); + sut.dispose(); }); it('can draw circle', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -686,15 +743,17 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawCircle(ex.vec(50, 50), 50, ex.Color.Blue); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/webgl-circle.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/webgl-circle.png'); + sut.dispose(); }); it('can draw circle with opacity', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -704,16 +763,20 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawCircle(ex.vec(50, 50), 50, ex.Color.Blue); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/webgl-circle-with-opacity.png'); + sut.dispose(); }); - it('can draw circles in batches (no draw sorting)', () => { - const canvasElement = document.createElement('canvas'); + // FIXME these batch tests really kill test performance + // I think there might be a better way to test this with configurable batch sizes + xit('can draw circles in batches (no draw sorting)', () => { + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -734,14 +797,16 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawCircle(ex.vec(50, 50), 50, ex.Color.Blue); } expect(circleRenderer.flush).toHaveBeenCalledTimes(2); + sut.dispose(); }); - it('can draw circles in batches (draw sorting)', () => { - const canvasElement = document.createElement('canvas'); + xit('can draw circles in batches (draw sorting)', () => { + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -761,14 +826,16 @@ describe('The ExcaliburGraphicsContext', () => { } sut.flush(); expect(circleRenderer.flush).toHaveBeenCalledTimes(3); + sut.dispose(); }); - it('can draw rectangles in batches (no draw sorting)', () => { - const canvasElement = document.createElement('canvas'); + xit('can draw rectangles in batches (no draw sorting)', () => { + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -791,14 +858,16 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawRectangle(ex.vec(50, 50), 50, 50, ex.Color.Blue); } expect(rectangleRenderer.flush).toHaveBeenCalledTimes(2); + sut.dispose(); }); - it('can draw rectangles in batches (draw sorting)', () => { - const canvasElement = document.createElement('canvas'); + xit('can draw rectangles in batches (draw sorting)', () => { + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -821,14 +890,16 @@ describe('The ExcaliburGraphicsContext', () => { sut.flush(); expect(rectangleRenderer.flush).toHaveBeenCalledTimes(3); + sut.dispose(); }); - it('can draw images in batches (no draw sorting)', async () => { - const canvasElement = document.createElement('canvas'); + xit('can draw images in batches (no draw sorting)', async () => { + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -853,14 +924,16 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawImage(tex.image, 0, 0); } expect(imageRenderer.flush).toHaveBeenCalledTimes(2); + sut.dispose(); }); - it('can draw images in batches (draw sorting)', async () => { - const canvasElement = document.createElement('canvas'); + xit('can draw images in batches (draw sorting)', async () => { + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, backgroundColor: ex.Color.White }); @@ -884,14 +957,16 @@ describe('The ExcaliburGraphicsContext', () => { } sut.flush(); expect(imageRenderer.flush).toHaveBeenCalledTimes(3); + sut.dispose(); }); it('can draw a line', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, antialiasing: false, multiSampleAntialiasing: false, @@ -902,16 +977,17 @@ describe('The ExcaliburGraphicsContext', () => { sut.clear(); sut.drawLine(ex.vec(0, 0), ex.vec(100, 100), ex.Color.Blue, 5); sut.flush(); - - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/webgl-line.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/webgl-line.png'); + sut.dispose(); }); it('can transform the context', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, antialiasing: false, multiSampleAntialiasing: false, @@ -934,17 +1010,19 @@ describe('The ExcaliburGraphicsContext', () => { sut.restore(); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/webgl-transform.png' ); + sut.dispose(); }); it('can snap drawings to pixel', async () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, snapToPixel: true, backgroundColor: ex.Color.White @@ -960,17 +1038,19 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawImage(rect._bitmap, 1.9, 1.9); sut.flush(); - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage( + await expectAsync(canvasElement).toEqualImage( 'src/spec/images/ExcaliburGraphicsContextSpec/2d-snap-to-pixel.png' ); + sut.dispose(); }); it('can handle drawing a zero dimension image', () => { - const canvasElement = document.createElement('canvas'); + const canvasElement = testCanvasElement; canvasElement.width = 100; canvasElement.height = 100; const sut = new ex.ExcaliburGraphicsContextWebGL({ canvasElement: canvasElement, + context: testContext, enableTransparency: false, snapToPixel: true, backgroundColor: ex.Color.White @@ -995,6 +1075,7 @@ describe('The ExcaliburGraphicsContext', () => { sut.drawImage(rect._bitmap, 0, 0, 10, 10, 0, 0, 0, 0); sut.flush(); }).not.toThrow(); + sut.dispose(); }); }); }); diff --git a/src/spec/FadeInOutSpec.ts b/src/spec/FadeInOutSpec.ts index 5f1c74026..b0fca7d88 100644 --- a/src/spec/FadeInOutSpec.ts +++ b/src/spec/FadeInOutSpec.ts @@ -54,8 +54,9 @@ describe('A FadeInOut transition', () => { setTimeout(() => { clock.step(1); expect(onDeactivateSpy).toHaveBeenCalledTimes(1); - expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('/src/spec/images/FadeInOutSpec/fadein.png').then(() => { + expectAsync(engine.canvas).toEqualImage('/src/spec/images/FadeInOutSpec/fadein.png').then(() => { done(); + engine.dispose(); }); }); }); @@ -92,8 +93,9 @@ describe('A FadeInOut transition', () => { }); setTimeout(() => { clock.step(1); - expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('/src/spec/images/FadeInOutSpec/fadeout.png').then(() => { + expectAsync(engine.canvas).toEqualImage('/src/spec/images/FadeInOutSpec/fadeout.png').then(() => { done(); + engine.dispose(); }); }); }); diff --git a/src/spec/GifSpec.ts b/src/spec/GifSpec.ts index f779d6094..c6a161c5f 100644 --- a/src/spec/GifSpec.ts +++ b/src/spec/GifSpec.ts @@ -42,7 +42,7 @@ describe('A Gif', () => { sprite.draw(engine.graphicsContext, 0, 0); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/GifSpec/frame1.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/GifSpec/frame1.png'); engine.graphicsContext.backgroundColor = ex.Color.Transparent; engine.graphicsContext.clear(); @@ -51,7 +51,7 @@ describe('A Gif', () => { sprite.draw(engine.graphicsContext, 0, 0); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/GifSpec/frame2.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/GifSpec/frame2.png'); }); it('should be read as a SpriteSheet', async () => { @@ -62,7 +62,7 @@ describe('A Gif', () => { sprite.draw(engine.graphicsContext, 0, 0); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/GifSpec/frame1.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/GifSpec/frame1.png'); }); it('should be read as an Animation', async () => { @@ -77,6 +77,6 @@ describe('A Gif', () => { frame2.graphic.draw(engine.graphicsContext, 0, 0); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/GifSpec/frame2.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/GifSpec/frame2.png'); }); }); diff --git a/src/spec/GraphicsSystemSpec.ts b/src/spec/GraphicsSystemSpec.ts index 57cc6ec0e..759944f54 100644 --- a/src/spec/GraphicsSystemSpec.ts +++ b/src/spec/GraphicsSystemSpec.ts @@ -101,7 +101,7 @@ describe('A Graphics ECS System', () => { expect(offscreenRect.draw).not.toHaveBeenCalled(); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/GraphicsSystemSpec/graphics-system.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/GraphicsSystemSpec/graphics-system.png'); }); it('will interpolate body graphics when fixed update is enabled', async () => { @@ -238,7 +238,7 @@ describe('A Graphics ECS System', () => { sut.update(1); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/GraphicsSystemSpec/graphics-context-opacity.png'); }); @@ -273,7 +273,7 @@ describe('A Graphics ECS System', () => { sut.update(1); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-horizontal.png'); }); @@ -308,7 +308,7 @@ describe('A Graphics ECS System', () => { sut.update(1); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-vertical.png'); }); @@ -344,7 +344,7 @@ describe('A Graphics ECS System', () => { sut.update(1); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-both.png'); }); @@ -381,7 +381,7 @@ describe('A Graphics ECS System', () => { sut.update(1); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-both-offset.png'); }); }); diff --git a/src/spec/IsometricMapSpec.ts b/src/spec/IsometricMapSpec.ts index cab7db45c..31bda8f8a 100644 --- a/src/spec/IsometricMapSpec.ts +++ b/src/spec/IsometricMapSpec.ts @@ -45,9 +45,7 @@ describe('A IsometricMap', () => { engine.add(sut); clock.step(100); - const canvas = TestUtils.flushWebGLCanvasTo2D(engine.canvas); - - await expectAsync(canvas).toEqualImage('src/spec/images/IsometricMapSpec/map.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/map.png'); }); it('can be drawn from the top', async () => { @@ -73,9 +71,7 @@ describe('A IsometricMap', () => { engine.add(sut); clock.step(100); - const canvas = TestUtils.flushWebGLCanvasTo2D(engine.canvas); - - await expectAsync(canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-top.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-top.png'); }); it('can be drawn from the bottom', async () => { @@ -100,9 +96,7 @@ describe('A IsometricMap', () => { engine.add(sut); clock.step(100); - const canvas = TestUtils.flushWebGLCanvasTo2D(engine.canvas); - - await expectAsync(canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-bottom.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-bottom.png'); }); it('can be debug drawn', async () => { @@ -132,9 +126,8 @@ describe('A IsometricMap', () => { engine.add(sut); clock.step(100); - const canvas = TestUtils.flushWebGLCanvasTo2D(engine.canvas); - await expectAsync(canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-debug.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/IsometricMapSpec/cube-map-debug.png'); }); it('can find a tile coordinate from a world position', async () => { diff --git a/src/spec/LineSpec.ts b/src/spec/LineSpec.ts index 524b19051..43746fcdc 100644 --- a/src/spec/LineSpec.ts +++ b/src/spec/LineSpec.ts @@ -88,7 +88,7 @@ describe('A Line', () => { sut.draw(ctx, 0, 0); ctx.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/LineSpec/line.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/LineSpec/line.png'); }); it('can draw a line when added to a graphics component', async () => { @@ -118,7 +118,7 @@ describe('A Line', () => { testClock.step(16); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(game.canvas)).toEqualImage('src/spec/images/LineSpec/line.png'); + await expectAsync(game.canvas).toEqualImage('src/spec/images/LineSpec/line.png'); }); }); \ No newline at end of file diff --git a/src/spec/LoaderSpec.ts b/src/spec/LoaderSpec.ts index 36d75c4bf..1742745fe 100644 --- a/src/spec/LoaderSpec.ts +++ b/src/spec/LoaderSpec.ts @@ -9,6 +9,10 @@ describe('A loader', () => { engine = TestUtils.engine(); }); + afterEach(() => { + engine.dispose(); + }); + it('exists', () => { expect(ex.Loader).toBeDefined(); }); @@ -237,7 +241,7 @@ describe('A loader', () => { expect(btnClickHandler).toHaveBeenCalled(); }); - it('updates the play button postion on resize', () => { + it('updates the play button position on resize', () => { const engine = new ex.Engine({width: 1000, height: 1000}); const loader = new ex.Loader([, , , ,]); loader.onInitialize(engine); diff --git a/src/spec/MaterialRendererSpec.ts b/src/spec/MaterialRendererSpec.ts index d49f30774..7a91e5b00 100644 --- a/src/spec/MaterialRendererSpec.ts +++ b/src/spec/MaterialRendererSpec.ts @@ -89,8 +89,9 @@ describe('A Material', () => { graphicsContext.restore(); expect(graphicsContext.material).toBe(null); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)) + await expectAsync(canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/material.png'); + graphicsContext.dispose(); }); it('can draw the screen texture', async () => { @@ -133,8 +134,9 @@ describe('A Material', () => { context.restore(); expect(context.material).toBe(null); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)) + await expectAsync(canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/multiply-comp.png'); + context.dispose(); }); it('can update uniforms with the .update()', async () => { @@ -181,7 +183,7 @@ describe('A Material', () => { context.restore(); expect(context.material).toBe(null); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)) + await expectAsync(canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/update-uniform.png'); }); @@ -237,7 +239,7 @@ describe('A Material', () => { graphicsContext.flush(); expect(graphicsContext.material).toBe(null); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/material-component.png'); }); @@ -308,7 +310,7 @@ describe('A Material', () => { graphicsContext.flush(); expect(graphicsContext.material).toBe(null); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + await expectAsync(engine.canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/multi-mat.png'); }); @@ -362,7 +364,7 @@ describe('A Material', () => { graphicsContext.restore(); expect(graphicsContext.material).toBe(null); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvas)) + await expectAsync(canvas) .toEqualImage('src/spec/images/MaterialRendererSpec/additional.png'); }); diff --git a/src/spec/ParallaxSpec.ts b/src/spec/ParallaxSpec.ts index 111af9e3f..8c757ef07 100644 --- a/src/spec/ParallaxSpec.ts +++ b/src/spec/ParallaxSpec.ts @@ -78,13 +78,13 @@ describe('A Parallax Component', () => { clock.step(16); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(game.canvas)).toEqualImage('src/spec/images/ParallaxSpec/tilemap.png'); + await expectAsync(game.canvas).toEqualImage('src/spec/images/ParallaxSpec/tilemap.png'); game.currentScene.camera.pos = ex.vec(250, -480); clock.step(16); // seems like there is an out of phase issue clock.step(16); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(game.canvas)).toEqualImage('src/spec/images/ParallaxSpec/tilemap2.png'); + await expectAsync(game.canvas).toEqualImage('src/spec/images/ParallaxSpec/tilemap2.png'); }); }); \ No newline at end of file diff --git a/src/spec/ParticleSpec.ts b/src/spec/ParticleSpec.ts index ae0636cc9..7f543ffb2 100644 --- a/src/spec/ParticleSpec.ts +++ b/src/spec/ParticleSpec.ts @@ -2,18 +2,6 @@ import { ExcaliburMatchers, ExcaliburAsyncMatchers } from 'excalibur-jasmine'; import * as ex from '@excalibur'; import { TestUtils } from './util/TestUtils'; -/** - * - */ -function flushWebGLCanvasTo2D(source: HTMLCanvasElement): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - canvas.width = source.width; - canvas.height = source.height; - const ctx = canvas.getContext('2d'); - ctx.drawImage(source, 0, 0); - return canvas; -} - describe('A particle', () => { let engine: ex.Engine; let scene: ex.Scene; @@ -138,7 +126,7 @@ describe('A particle', () => { engine.currentScene.update(engine, 100); engine.currentScene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/ParticleSpec/Particles.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/ParticleSpec/Particles.png'); }); it('can be parented', async () => { @@ -186,7 +174,7 @@ describe('A particle', () => { engine.currentScene.update(engine, 100); engine.currentScene.draw(engine.graphicsContext, 100); engine.graphicsContext.flush(); - await expectAsync(flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/ParticleSpec/parented.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/ParticleSpec/parented.png'); }); diff --git a/src/spec/ScaleSpec.ts b/src/spec/ScaleSpec.ts index fb180a23d..acaea850b 100644 --- a/src/spec/ScaleSpec.ts +++ b/src/spec/ScaleSpec.ts @@ -1,4 +1,4 @@ -import { ExcaliburMatchers, ensureImagesLoaded } from 'excalibur-jasmine'; +import { ExcaliburAsyncMatchers, ExcaliburMatchers } from 'excalibur-jasmine'; import * as ex from '@excalibur'; import { TestUtils } from './util/TestUtils'; import { Mocks } from './util/Mocks'; @@ -10,6 +10,7 @@ describe('A scaled and rotated actor', () => { beforeEach(() => { jasmine.addMatchers(ExcaliburMatchers); + jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); actor = new ex.ScreenElement({x: 50, y:50, width: 100, height: 50}); actor.color = ex.Color.Blue; @@ -22,6 +23,7 @@ describe('A scaled and rotated actor', () => { afterEach(() => { engine.stop(); + engine.dispose(); }); it('is drawn correctly scaled at 90 degrees', (done) => { @@ -44,9 +46,9 @@ describe('A scaled and rotated actor', () => { clock.step(1); - ensureImagesLoaded(TestUtils.flushWebGLCanvasTo2D(engine.canvas), 'src/spec/images/ScaleSpec/scale.png').then(([canvas, image]) => { - expect(canvas).toEqualImage(image); + expectAsync(engine.canvas).toEqualImage('src/spec/images/ScaleSpec/scale.png').then(() => { done(); + engine.dispose(); }); }); }); diff --git a/src/spec/SceneSpec.ts b/src/spec/SceneSpec.ts index 3f49c92b4..e51f52a7f 100644 --- a/src/spec/SceneSpec.ts +++ b/src/spec/SceneSpec.ts @@ -760,6 +760,7 @@ describe('A scene', () => { afterEach(() => { engine.stop(); + engine.dispose(); engine = null; scene = null; }); diff --git a/src/spec/ScreenElementSpec.ts b/src/spec/ScreenElementSpec.ts index b82aaaed8..31d45a095 100644 --- a/src/spec/ScreenElementSpec.ts +++ b/src/spec/ScreenElementSpec.ts @@ -1,6 +1,5 @@ import { ExcaliburMatchers, ensureImagesLoaded, ExcaliburAsyncMatchers } from 'excalibur-jasmine'; import * as ex from '@excalibur'; -import { Mocks } from './util/Mocks'; import { TestUtils } from './util/TestUtils'; import { ScreenElement } from '@excalibur'; @@ -142,6 +141,7 @@ describe('A ScreenElement', () => { game.add(screenElement); game.currentScene.draw(game.graphicsContext, 100); game.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(game.canvas)).toEqualImage('src/spec/images/ScreenElementSpec/emptyctor.png'); + await expectAsync(game.canvas).toEqualImage('src/spec/images/ScreenElementSpec/emptyctor.png'); + game.dispose(); }); }); diff --git a/src/spec/SpriteSheetSpec.ts b/src/spec/SpriteSheetSpec.ts index b78be1c84..7302e57de 100644 --- a/src/spec/SpriteSheetSpec.ts +++ b/src/spec/SpriteSheetSpec.ts @@ -11,7 +11,7 @@ describe('A SpriteSheet for Graphics', () => { canvasElement = document.createElement('canvas'); canvasElement.width = 120; canvasElement.height = 120; - ctx = new ex.ExcaliburGraphicsContext2DCanvas({ canvasElement, smoothing: false }); + ctx = new ex.ExcaliburGraphicsContext2DCanvas({ canvasElement, antialiasing: false }); }); it('exists', () => { diff --git a/src/spec/SpriteSpec.ts b/src/spec/SpriteSpec.ts index b92099dfa..007ef6733 100644 --- a/src/spec/SpriteSpec.ts +++ b/src/spec/SpriteSpec.ts @@ -138,7 +138,7 @@ describe('A Sprite Graphic', () => { sut.draw(ctx, 50 - sut.width / 2, 50 - sut.width / 2); ctx.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsSpriteSpec/source-view.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsSpriteSpec/source-view.png'); }); it('can draw an sprite image with a tint', async () => { @@ -153,7 +153,7 @@ describe('A Sprite Graphic', () => { sut.draw(ctx, 0, 0); ctx.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsSpriteSpec/icon-tint.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsSpriteSpec/icon-tint.png'); }); it('can specify the width and height of a sprite after construction', async () => { @@ -178,7 +178,7 @@ describe('A Sprite Graphic', () => { sut.draw(ctx, 50 - sut.width / 2, 50 - sut.width / 2); ctx.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsSpriteSpec/change-size.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsSpriteSpec/change-size.png'); }); it('can specify the width and height and scale', async () => { @@ -206,7 +206,7 @@ describe('A Sprite Graphic', () => { ctx.flush(); expect(sut.width).toBe(128); expect(sut.height).toBe(128); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)) + await expectAsync(canvasElement) .toEqualImage('src/spec/images/GraphicsSpriteSpec/change-size-and-scale.png'); }); @@ -240,7 +240,7 @@ describe('A Sprite Graphic', () => { sut.draw(ctx, 50 - sut.width / 2, 50 - sut.width / 2); ctx.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsSpriteSpec/source-view.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsSpriteSpec/source-view.png'); }); it('can specify a source view of an image and a dest view dimension is destination', async () => { @@ -277,7 +277,7 @@ describe('A Sprite Graphic', () => { sut.draw(ctx, 50 - sut.width / 2, 50 - sut.width / 2); ctx.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsSpriteSpec/dest-size.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsSpriteSpec/dest-size.png'); }); it('can specify only a dest view dimension, infers native size for source view', async () => { @@ -311,7 +311,7 @@ describe('A Sprite Graphic', () => { sut.draw(ctx, 50 - sut.width / 2, 50 - sut.width / 2); ctx.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsSpriteSpec/dest-view.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsSpriteSpec/dest-view.png'); }); it('will log one warning if the imagesource is not loaded', () => { diff --git a/src/spec/TextSpec.ts b/src/spec/TextSpec.ts index e98ee504b..52acb7e54 100644 --- a/src/spec/TextSpec.ts +++ b/src/spec/TextSpec.ts @@ -2,17 +2,6 @@ import * as ex from '@excalibur'; import { ExcaliburAsyncMatchers, ExcaliburMatchers } from 'excalibur-jasmine'; import { delay } from '../engine/Util/Util'; -/** - * - */ -function flushWebGLCanvasTo2D(source: HTMLCanvasElement): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - canvas.width = source.width; - canvas.height = source.height; - const ctx = canvas.getContext('2d'); - ctx.drawImage(source, 0, 0); - return canvas; -} /** * @@ -845,11 +834,11 @@ describe('A Text Graphic', () => { ctx.flush(); await runOnWindows(async () => { - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsTextSpec/long-text.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsTextSpec/long-text.png'); }); await runOnLinux(async () => { - await expectAsync(flushWebGLCanvasTo2D(canvasElement)).toEqualImage('src/spec/images/GraphicsTextSpec/long-text-linux.png'); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsTextSpec/long-text-linux.png'); }); }); diff --git a/src/spec/TileMapSpec.ts b/src/spec/TileMapSpec.ts index cb0567a92..95d21d76e 100644 --- a/src/spec/TileMapSpec.ts +++ b/src/spec/TileMapSpec.ts @@ -212,14 +212,14 @@ describe('A TileMap', () => { drawWithTransform(engine.graphicsContext, tm, 99); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/TileMapSpec/TileMapGraphicSquare.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/TileMapSpec/TileMapGraphicSquare.png'); tm.update(engine, 99); drawWithTransform(engine.graphicsContext, tm, 99); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/TileMapSpec/TileMapGraphicCircle.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/TileMapSpec/TileMapGraphicCircle.png'); }); it('should draw the correct proportions', async () => { @@ -249,7 +249,7 @@ describe('A TileMap', () => { drawWithTransform(engine.graphicsContext, tm, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/TileMapSpec/TileMap.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/TileMapSpec/TileMap.png'); }); it('should draw from the bottom', async () => { @@ -275,7 +275,7 @@ describe('A TileMap', () => { drawWithTransform(engine.graphicsContext, tm, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/TileMapSpec/tilemap-from-bottom.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/TileMapSpec/tilemap-from-bottom.png'); }); it('should draw from the top', async () => { @@ -302,7 +302,7 @@ describe('A TileMap', () => { drawWithTransform(engine.graphicsContext, tm, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/TileMapSpec/tilemap-from-top.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/TileMapSpec/tilemap-from-top.png'); }); it('should handle offscreen culling correctly with negative coords', async () => { @@ -334,7 +334,7 @@ describe('A TileMap', () => { drawWithTransform(engine.graphicsContext, tm, 100); engine.graphicsContext.flush(); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/TileMapSpec/TileMapCulling.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/TileMapSpec/TileMapCulling.png'); }); it('should handle offscreen culling correctly when scaled', async () => { @@ -386,7 +386,7 @@ describe('A TileMap', () => { }) ); - await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)).toEqualImage('src/spec/images/TileMapSpec/tilemap-scaled.png'); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/TileMapSpec/tilemap-scaled.png'); }); it('can return a tile by xy coord', () => { diff --git a/src/spec/WebAudioInstanceSpec.ts b/src/spec/WebAudioInstanceSpec.ts index 414b3bfd1..99736f35d 100644 --- a/src/spec/WebAudioInstanceSpec.ts +++ b/src/spec/WebAudioInstanceSpec.ts @@ -1,5 +1,4 @@ import * as ex from '@excalibur'; -import { TestUtils } from './util/TestUtils'; describe('A webaudio instance', () => { let webaudio: ex.WebAudioInstance; diff --git a/src/spec/_boot.ts b/src/spec/_boot.ts index 56ee53fb0..808f44b29 100644 --- a/src/spec/_boot.ts +++ b/src/spec/_boot.ts @@ -3,3 +3,34 @@ ex.Flags.enable('suppress-obsolete-message'); const testsContext = require.context('.', true, /Spec$/); testsContext.keys().forEach(testsContext); + +const MemoryReporter = { + previousMemory: (window.performance as any).memory, + largeMemory: [], + jasmineStarted: function(suiteInfo) { + this.previousMemory = (window.performance as any).memory; + }, + + + specDone: function(result) { + const currentMemory = (window.performance as any).memory; + const megabytes = (currentMemory.usedJSHeapSize - this.previousMemory.usedJSHeapSize) * 0.000001; + if (megabytes > 1) { + const message = `Spec ${result.fullName} MB: ${megabytes}`; + this.largeMemory.push({size: megabytes, message}); + } + this.previousMemory = currentMemory; + }, + + jasmineDone: function(result) { + this.largeMemory.sort((a, b) => { + return b.size - a.size; + }); + for (const test of this.largeMemory.slice(0, 20)){ + console.log(test.message); // eslint-disable-line + } + } +}; + +// jasmine.getEnv().addReporter(MemoryReporter); +