diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93853468..5da1e24c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 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/package-lock.json b/package-lock.json
index 5ae4a44e..41dd4d80 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@lightningjs/core",
- "version": "2.9.0",
+ "version": "2.9.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@lightningjs/core",
- "version": "2.9.0",
+ "version": "2.9.1",
"license": "Apache-2.0",
"devDependencies": {
"@babel/core": "^7.8.3",
diff --git a/package.json b/package.json
index 3093714f..da518413 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"author": "Metrological, Bas van Meurs ",
"name": "@lightningjs/core",
- "version": "2.9.0",
+ "version": "2.9.1",
"license": "Apache-2.0",
"type": "module",
"types": "dist/index.d.ts",
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() {