Skip to content

Commit 17f0e1b

Browse files
committed
fix: reduce canvas and bitmap memory by lazy-caching grids and shrinking scroll buffer
- Remove eager bitmap.cache() from _createGrid() — 8 grids were each allocating a 1200x900x4 (~4.3 MB) backing canvas at startup even though at most 1 grid is visible at a time (~35 MB wasted) - Add cache(0,0,1200,900) in _show*() methods so grids are only cached when made visible, and uncache() in _hide*() to free the backing canvas immediately when hidden - Skip trashed blocks in clearCache() to avoid re-creating backing canvases for invisible blocks on every theme/resize event - Uncache trashed block containers in sendStackToTrash() and delete their blockArt/blockCollapseArt SVG strings to free memory - Cap trashStacks undo history at 100 entries to prevent unbounded growth during long editing sessions - Reduce scroll canvas from 3x to 2x viewport dimensions in doScrollXY(), saving ~40 MB at 1920x1080 (75 MB -> 33 MB) - Update scroll boundary clamps to match the new 2x canvas size Estimated RAM savings: ~70-120 MB depending on viewport size and number of trashed blocks.
1 parent f488f0b commit 17f0e1b

File tree

3 files changed

+56
-20
lines changed

3 files changed

+56
-20
lines changed

js/activity.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2849,13 +2849,15 @@ class Activity {
28492849

28502850
const bitmap = new createjs.Bitmap(img);
28512851
container.addChild(bitmap);
2852-
bitmap.cache(0, 0, 1200, 900);
2852+
// Do NOT cache the bitmap here. Each cached grid allocates a
2853+
// 1200x900x4 = ~4.3 MB backing canvas, and with 8 grids that
2854+
// totals ~35 MB even though at most 1 grid is visible at a time.
2855+
// Instead, we cache lazily in _show*() and uncache in _hide*().
28532856

28542857
bitmap.x = (this.canvas.width - 1200) / 2;
28552858
bitmap.y = (this.canvas.height - 900) / 2;
28562859
bitmap.scaleX = bitmap.scaleY = bitmap.scale = 1;
28572860
bitmap.visible = false;
2858-
bitmap.updateCache();
28592861

28602862
return bitmap;
28612863
};
@@ -4672,6 +4674,11 @@ class Activity {
46724674
*/
46734675
this.clearCache = () => {
46744676
this.blocks.blockList.forEach(block => {
4677+
// Skip trashed blocks — they are hidden and their backing
4678+
// canvases are freed in sendStackToTrash(). Re-caching them
4679+
// here would waste ~0.5–2 MB per trashed block.
4680+
if (block.trash) return;
4681+
46754682
if (block.container) {
46764683
block.container.uncache();
46774684
block.container.cache();
@@ -5855,7 +5862,7 @@ class Activity {
58555862
*/
58565863
this._hideCartesian = () => {
58575864
this.cartesianBitmap.visible = false;
5858-
this.cartesianBitmap.updateCache();
5865+
this.cartesianBitmap.uncache();
58595866
this.update = true;
58605867
};
58615868

@@ -5864,6 +5871,7 @@ class Activity {
58645871
*/
58655872
this._showCartesian = () => {
58665873
this.cartesianBitmap.visible = true;
5874+
this.cartesianBitmap.cache(0, 0, 1200, 900);
58675875
this.cartesianBitmap.updateCache();
58685876
this.update = true;
58695877
};
@@ -5873,7 +5881,7 @@ class Activity {
58735881
*/
58745882
this._hidePolar = () => {
58755883
this.polarBitmap.visible = false;
5876-
this.polarBitmap.updateCache();
5884+
this.polarBitmap.uncache();
58775885
this.update = true;
58785886
};
58795887

@@ -5882,6 +5890,7 @@ class Activity {
58825890
*/
58835891
this._showPolar = () => {
58845892
this.polarBitmap.visible = true;
5893+
this.polarBitmap.cache(0, 0, 1200, 900);
58855894
this.polarBitmap.updateCache();
58865895
this.update = true;
58875896
};
@@ -5942,7 +5951,7 @@ class Activity {
59425951
*/
59435952
this._hideTreble = () => {
59445953
this.trebleBitmap.visible = false;
5945-
this.trebleBitmap.updateCache();
5954+
this.trebleBitmap.uncache();
59465955
this._hideAccidentals();
59475956
this.update = true;
59485957
};
@@ -5952,6 +5961,7 @@ class Activity {
59525961
*/
59535962
this._showTreble = () => {
59545963
this.trebleBitmap.visible = true;
5964+
this.trebleBitmap.cache(0, 0, 1200, 900);
59555965
this.trebleBitmap.updateCache();
59565966
this._hideAccidentals();
59575967
// eslint-disable-next-line no-console
@@ -6001,7 +6011,7 @@ class Activity {
60016011
*/
60026012
this._hideGrand = () => {
60036013
this.grandBitmap.visible = false;
6004-
this.grandBitmap.updateCache();
6014+
this.grandBitmap.uncache();
60056015
this._hideAccidentals();
60066016
this.update = true;
60076017
};
@@ -6011,6 +6021,7 @@ class Activity {
60116021
*/
60126022
this._showGrand = () => {
60136023
this.grandBitmap.visible = true;
6024+
this.grandBitmap.cache(0, 0, 1200, 900);
60146025
this.grandBitmap.updateCache();
60156026
this._hideAccidentals();
60166027
// eslint-disable-next-line no-console
@@ -6059,7 +6070,7 @@ class Activity {
60596070
*/
60606071
this._hideSoprano = () => {
60616072
this.sopranoBitmap.visible = false;
6062-
this.sopranoBitmap.updateCache();
6073+
this.sopranoBitmap.uncache();
60636074
this.update = true;
60646075
};
60656076

@@ -6068,6 +6079,7 @@ class Activity {
60686079
*/
60696080
this._showSoprano = () => {
60706081
this.sopranoBitmap.visible = true;
6082+
this.sopranoBitmap.cache(0, 0, 1200, 900);
60716083
this.sopranoBitmap.updateCache();
60726084
this._hideAccidentals();
60736085
// eslint-disable-next-line no-console
@@ -6117,7 +6129,7 @@ class Activity {
61176129
*/
61186130
this._hideAlto = () => {
61196131
this.altoBitmap.visible = false;
6120-
this.altoBitmap.updateCache();
6132+
this.altoBitmap.uncache();
61216133
this._hideAccidentals();
61226134
this.update = true;
61236135
};
@@ -6131,6 +6143,7 @@ class Activity {
61316143
*/
61326144
this._showAlto = () => {
61336145
this.altoBitmap.visible = true;
6146+
this.altoBitmap.cache(0, 0, 1200, 900);
61346147
this.altoBitmap.updateCache();
61356148
this._hideAccidentals();
61366149
// eslint-disable-next-line no-console
@@ -6180,7 +6193,7 @@ class Activity {
61806193
*/
61816194
this._hideTenor = () => {
61826195
this.tenorBitmap.visible = false;
6183-
this.tenorBitmap.updateCache();
6196+
this.tenorBitmap.uncache();
61846197
this.update = true;
61856198
};
61866199

@@ -6189,6 +6202,7 @@ class Activity {
61896202
*/
61906203
this._showTenor = () => {
61916204
this.tenorBitmap.visible = true;
6205+
this.tenorBitmap.cache(0, 0, 1200, 900);
61926206
this.tenorBitmap.updateCache();
61936207
this._hideAccidentals();
61946208
// eslint-disable-next-line no-console
@@ -6238,7 +6252,7 @@ class Activity {
62386252
*/
62396253
this._hideBass = () => {
62406254
this.bassBitmap.visible = false;
6241-
this.bassBitmap.updateCache();
6255+
this.bassBitmap.uncache();
62426256
this._hideAccidentals();
62436257
this.update = true;
62446258
};
@@ -6248,6 +6262,7 @@ class Activity {
62486262
*/
62496263
this._showBass = () => {
62506264
this.bassBitmap.visible = true;
6265+
this.bassBitmap.cache(0, 0, 1200, 900);
62516266
this.bassBitmap.updateCache();
62526267
this._hideAccidentals();
62536268
// eslint-disable-next-line no-console

js/blocks.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6943,6 +6943,13 @@ class Blocks {
69436943
/** Add this block to the list of blocks in the trash so we can undo this action. */
69446944
this.trashStacks.push(thisBlock);
69456945

6946+
// Cap the undo history to prevent unbounded memory growth.
6947+
// Keep the 100 most recent trashed stacks.
6948+
const MAX_TRASH_UNDO = 100;
6949+
if (this.trashStacks.length > MAX_TRASH_UNDO) {
6950+
this.trashStacks = this.trashStacks.slice(-MAX_TRASH_UNDO);
6951+
}
6952+
69466953
/** Disconnect block. */
69476954
const parentBlock = myBlock.connections[0];
69486955
if (parentBlock != null) {
@@ -6988,6 +6995,21 @@ class Blocks {
69886995
const blk = this.dragGroup[b];
69896996
this.blockList[blk].trash = true;
69906997
this.blockList[blk].hide();
6998+
6999+
// Free the backing canvas memory for trashed blocks.
7000+
// Each cached block holds a bitmap canvas (~0.5-2 MB).
7001+
if (this.blockList[blk].container) {
7002+
this.blockList[blk].container.uncache();
7003+
}
7004+
7005+
// Clean up SVG art strings for trashed blocks to free memory.
7006+
if (this.blockArt[blk]) {
7007+
delete this.blockArt[blk];
7008+
}
7009+
if (this.blockCollapseArt[blk]) {
7010+
delete this.blockCollapseArt[blk];
7011+
}
7012+
69917013
const title = this.blockList[blk].protoblock.staticLabels[0];
69927014
closeBlkWidgets(_(title));
69937015
this.activity.refreshCanvas();

js/turtle-painter.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,14 +1366,16 @@ class Painter {
13661366
turtles.gx = this.turtle.ctx.canvas.width;
13671367
turtles.gy = this.turtle.ctx.canvas.height;
13681368
turtles.canvas1 = document.createElement("canvas");
1369-
turtles.canvas1.width = 3 * this.turtle.ctx.canvas.width;
1370-
turtles.canvas1.height = 3 * this.turtle.ctx.canvas.height;
1369+
// Use 2x viewport instead of 3x to save ~40 MB of memory.
1370+
// At 1920x1080: 2x = 3840x2160x4 = 33 MB vs 3x = 5760x3240x4 = 75 MB.
1371+
turtles.canvas1.width = 2 * this.turtle.ctx.canvas.width;
1372+
turtles.canvas1.height = 2 * this.turtle.ctx.canvas.height;
13711373
turtles.c1ctx = turtles.canvas1.getContext("2d", { willReadFrequently: true });
13721374
turtles.c1ctx.rect(
13731375
0,
13741376
0,
1375-
3 * this.turtle.ctx.canvas.width,
1376-
3 * this.turtle.ctx.canvas.height
1377+
2 * this.turtle.ctx.canvas.width,
1378+
2 * this.turtle.ctx.canvas.height
13771379
);
13781380
turtles.c1ctx.fillStyle = "#F9F9F9";
13791381
turtles.c1ctx.fill();
@@ -1383,15 +1385,12 @@ class Painter {
13831385

13841386
turtles.gy -= dy;
13851387
turtles.gx -= dx;
1388+
// Clamp to the bounds of the 2x scroll canvas (max offset = 1x viewport)
13861389
turtles.gx =
1387-
2 * this.turtle.ctx.canvas.width > turtles.gx
1388-
? turtles.gx
1389-
: 2 * this.turtle.ctx.canvas.width;
1390+
this.turtle.ctx.canvas.width > turtles.gx ? turtles.gx : this.turtle.ctx.canvas.width;
13901391
turtles.gx = 0 > turtles.gx ? 0 : turtles.gx;
13911392
turtles.gy =
1392-
2 * this.turtle.ctx.canvas.height > turtles.gy
1393-
? turtles.gy
1394-
: 2 * this.turtle.ctx.canvas.height;
1393+
this.turtle.ctx.canvas.height > turtles.gy ? turtles.gy : this.turtle.ctx.canvas.height;
13951394
turtles.gy = 0 > turtles.gy ? 0 : turtles.gy;
13961395

13971396
const newImgData = turtles.c1ctx.getImageData(

0 commit comments

Comments
 (0)