diff --git a/CHANGELOG.md b/CHANGELOG.md index 986a95b6..b91e045b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog ## v2.10.0 -*20 apr 2023* +*24 apr 2023* - Integrated `Vite` to replace `rollup` bundler and integrated `Vitest` for unit testing - Implemented word wrapping support on zero-width breaking spaces (#450) (docs: [Word Wrap in Non-Latin Based Languages](https://lightningjs.io/docs/#/lightning-core-reference/RenderEngine/Textures/Text?id=word-wrap-in-non-latin-based-languages) ) @@ -13,6 +13,12 @@ - Fixed TypeScript error with getByRef() when using generic type param as Ref value (#444) - Implemented default loose type configs for TypeScript. +## v2.9.1 + +*21 apr 2023* + +- 🔥 Hotfix for memory leak when `pauseRafLoopOnIdle` is enabled (introduced in v2.7.0) +- Implemented additional cleanup of Lightning code that gets stuck on the heap after calling `destroy` ## v2.9.0 *16 feb 2023* diff --git a/examples/destroy/index.html b/examples/destroy/index.html new file mode 100644 index 00000000..7c6167ae --- /dev/null +++ b/examples/destroy/index.html @@ -0,0 +1,96 @@ + + + + +
+ + + + + + + diff --git a/src/platforms/browser/ImageWorker.mjs b/src/platforms/browser/ImageWorker.mjs index 004f94ea..005a81ea 100644 --- a/src/platforms/browser/ImageWorker.mjs +++ b/src/platforms/browser/ImageWorker.mjs @@ -30,6 +30,12 @@ export default class ImageWorker { if (this._worker) { this._worker.terminate(); } + + this._items = null; + this._worker = null; + + delete this._items; + delete this._worker; } _initWorker() { diff --git a/src/platforms/browser/WebPlatform.mjs b/src/platforms/browser/WebPlatform.mjs index a7a588e5..0aa0ebf0 100644 --- a/src/platforms/browser/WebPlatform.mjs +++ b/src/platforms/browser/WebPlatform.mjs @@ -50,11 +50,17 @@ export default class WebPlatform { if (this._imageWorker) { this._imageWorker.destroy(); } + + clearInterval(this._loopHandler); + this._removeKeyHandler(); this._removeClickHandler(); this._removeHoverHandler(); this._removeScrollWheelHandler(); this._removeVisibilityChangeHandler(); + + this.stage = null; + delete this.stage; } startLoop() { diff --git a/src/renderer/c2d/C2dRenderer.mjs b/src/renderer/c2d/C2dRenderer.mjs index 01231cff..7f199535 100644 --- a/src/renderer/c2d/C2dRenderer.mjs +++ b/src/renderer/c2d/C2dRenderer.mjs @@ -38,6 +38,9 @@ export default class C2dRenderer extends Renderer { destroy() { this.tintManager.destroy(); + + this.tintManager = null; + delete this.tintManager; } _createDefaultShader(ctx) { diff --git a/src/renderer/c2d/C2dTextureTintManager.mjs b/src/renderer/c2d/C2dTextureTintManager.mjs index 3ad8715a..8406f9fb 100644 --- a/src/renderer/c2d/C2dTextureTintManager.mjs +++ b/src/renderer/c2d/C2dTextureTintManager.mjs @@ -27,6 +27,9 @@ export default class C2dTextureTintManager { destroy() { this.gc(true); + + this.stage = null; + delete this.stage; } _addMemoryUsage(delta) { diff --git a/src/renderer/webgl/WebGLCoreRenderExecutor.mjs b/src/renderer/webgl/WebGLCoreRenderExecutor.mjs index 175b5745..ac37535f 100644 --- a/src/renderer/webgl/WebGLCoreRenderExecutor.mjs +++ b/src/renderer/webgl/WebGLCoreRenderExecutor.mjs @@ -64,6 +64,9 @@ export default class WebGLCoreRenderExecutor extends CoreRenderExecutor { super.destroy(); this.gl.deleteBuffer(this._attribsBuffer); this.gl.deleteBuffer(this._quadsBuffer); + + this.gl = null; + delete this.gl; } _reset() { diff --git a/src/renderer/webgl/WebGLRenderer.mjs b/src/renderer/webgl/WebGLRenderer.mjs index 0e0728ea..1b142b81 100644 --- a/src/renderer/webgl/WebGLRenderer.mjs +++ b/src/renderer/webgl/WebGLRenderer.mjs @@ -46,6 +46,12 @@ export default class WebGLRenderer extends Renderer { destroy() { this.shaderPrograms.forEach(shaderProgram => shaderProgram.destroy()); + + this.shaderPrograms = null; + this._compressedTextureExtensions = null; + + delete this.shaderPrograms; + delete this._compressedTextureExtensions; } _createDefaultShader(ctx) { diff --git a/src/renderer/webgl/WebGLShaderProgram.mjs b/src/renderer/webgl/WebGLShaderProgram.mjs index 0947fe66..e6a78603 100644 --- a/src/renderer/webgl/WebGLShaderProgram.mjs +++ b/src/renderer/webgl/WebGLShaderProgram.mjs @@ -28,6 +28,7 @@ export default class WebGLShaderProgram { this.fragmentShaderSource = fragmentShaderSource; this._program = null; + this.gl = null; this._uniformLocations = new Map(); this._attributeLocations = new Map(); @@ -109,8 +110,24 @@ export default class WebGLShaderProgram { destroy() { if (this._program) { this.gl.deleteProgram(this._program); - this._program = null; } + + this._attributeLocations = null; + this._currentUniformValues = null; + this.fragmentShaderSource = null; + this._program = null; + this.gl = null; + this._uniformLocations = null; + this.vertexShaderSource = null; + + delete this.vertexShaderSource; + delete this._program; + delete this._currentUniformValues; + delete this.fragmentShaderSource; + delete this.gl; + delete this._uniformLocations; + delete this._attributeLocations; + } get glProgram() { diff --git a/src/tree/Stage.mjs b/src/tree/Stage.mjs index 001d37d0..dfcca18d 100644 --- a/src/tree/Stage.mjs +++ b/src/tree/Stage.mjs @@ -140,7 +140,7 @@ export default class Stage extends EventEmitter { try { return !!window.WebGLRenderingContext; - } catch(e) { + } catch (e) { return false; } } @@ -188,7 +188,7 @@ export default class Stage extends EventEmitter { opt('memoryPressure', 24e6); opt('bufferMemory', 2e6); opt('textRenderIssueMargin', 0); - opt('fontSharp',{precision:0.6666666667, fontSize: 24}) + opt('fontSharp', { precision: 0.6666666667, fontSize: 24 }) opt('clearColor', [0, 0, 0, 0]); opt('defaultFontFace', 'sans-serif'); opt('fixedDt', 0); @@ -238,6 +238,32 @@ export default class Stage extends EventEmitter { this.ctx.destroy(); this.textureManager.destroy(); this._renderer.destroy(); + + // clear last rendered frame + if (this.gl) { + this.gl.clearColor(0.0, 0.0, 0.0, 0.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + } else if (this.c2d) { + this.c2d.clearRect( + 0, 0, this.c2d.canvas.width, this.c2d.canvas.height + ); + } + + this.gl = null; + this.c2d = null; + this.ctx = null; + this._options = null; + this.platform = null; + this.textureManager = null; + this._renderer = null; + + delete this.gl; + delete this.c2d; + delete this.ctx; + delete this._options; + delete this.platform; + delete this.textureManager; + delete this._renderer; } stop() { @@ -511,10 +537,10 @@ export default class Stage extends EventEmitter { } } - getChildrenByPosition(x, y){ + getChildrenByPosition(x, y) { const children = []; this.root.core.update(); - this.root.core.collectAtCoord(x,y,children); + this.root.core.collectAtCoord(x, y, children); return children; } diff --git a/src/tree/TextureThrottler.mjs b/src/tree/TextureThrottler.mjs index e90f0a69..fe4c34f9 100644 --- a/src/tree/TextureThrottler.mjs +++ b/src/tree/TextureThrottler.mjs @@ -36,6 +36,11 @@ export default class TextureThrottler { destroy() { this._sources = []; this._data = []; + this.stage = null; + + delete this._sources; + delete this._data; + delete this.stage; } processSome() { diff --git a/src/tree/core/CoreContext.mjs b/src/tree/core/CoreContext.mjs index 73703525..4d91c615 100644 --- a/src/tree/core/CoreContext.mjs +++ b/src/tree/core/CoreContext.mjs @@ -49,6 +49,21 @@ export default class CoreContext { destroy() { this._renderTexturePool.forEach(texture => this._freeRenderTexture(texture)); this._usedMemory = 0; + + this.stage = null; + this.root = null; + + this.renderState = null; + this.renderExec = null; + this._renderTexturePool = null; + this._zSorts = null; + + delete this.stage; + delete this.root; + delete this.renderState; + delete this.renderExec; + delete this._renderTexturePool; + delete this._zSorts; } hasRenderUpdates() { diff --git a/src/tree/core/CoreRenderExecutor.mjs b/src/tree/core/CoreRenderExecutor.mjs index 8dfe77f8..b0c29662 100644 --- a/src/tree/core/CoreRenderExecutor.mjs +++ b/src/tree/core/CoreRenderExecutor.mjs @@ -29,6 +29,13 @@ export default class CoreRenderExecutor { } destroy() { + this.ctx = null; + this.renderState = null; + this.gl = null; + + delete this.ctx; + delete this.renderState; + delete this.gl; } _reset() {