Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 38 additions & 34 deletions js/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -2849,13 +2849,15 @@ class Activity {

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

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

return bitmap;
};
Expand Down Expand Up @@ -4264,6 +4266,19 @@ class Activity {
const blk = this.blocks.dragGroup[b];
this.blocks.blockList[blk].trash = false;
this.blocks.moveBlockRelative(blk, dx, dy);

// Re-cache the container if it was uncached to save
// memory in sendStackToTrash().
const block = this.blocks.blockList[blk];
if (block.container && !block.container.bitmapCache) {
block.container.cache(
0,
0,
Math.max(block.width, 1),
Math.max(block.height, 1)
);
}

this.blocks.blockList[blk].show();
}

Expand Down Expand Up @@ -4672,6 +4687,11 @@ class Activity {
*/
this.clearCache = () => {
this.blocks.blockList.forEach(block => {
// Skip trashed blocks — they are hidden and their backing
// canvases are freed in sendStackToTrash(). Re-caching them
// here would waste ~0.5–2 MB per trashed block.
if (block.trash) return;

if (block.container) {
block.container.uncache();
block.container.cache();
Expand Down Expand Up @@ -5855,7 +5875,7 @@ class Activity {
*/
this._hideCartesian = () => {
this.cartesianBitmap.visible = false;
this.cartesianBitmap.updateCache();
this.cartesianBitmap.uncache();
this.update = true;
};

Expand All @@ -5864,6 +5884,7 @@ class Activity {
*/
this._showCartesian = () => {
this.cartesianBitmap.visible = true;
this.cartesianBitmap.cache(0, 0, 1200, 900);
this.cartesianBitmap.updateCache();
this.update = true;
};
Expand All @@ -5873,7 +5894,7 @@ class Activity {
*/
this._hidePolar = () => {
this.polarBitmap.visible = false;
this.polarBitmap.updateCache();
this.polarBitmap.uncache();
this.update = true;
};

Expand All @@ -5882,6 +5903,7 @@ class Activity {
*/
this._showPolar = () => {
this.polarBitmap.visible = true;
this.polarBitmap.cache(0, 0, 1200, 900);
this.polarBitmap.updateCache();
this.update = true;
};
Expand All @@ -5894,45 +5916,33 @@ class Activity {
for (let i = 0; i < 7; i++) {
this.grandSharpBitmap[i].visible = false;
this.grandSharpBitmap[i].x = newX;
this.grandSharpBitmap[i].updateCache();
this.grandFlatBitmap[i].visible = false;
this.grandFlatBitmap[i].x = newX;
this.grandFlatBitmap[i].updateCache();

this.trebleSharpBitmap[i].visible = false;
this.trebleSharpBitmap[i].x = newX;
this.trebleSharpBitmap[i].updateCache();
this.trebleFlatBitmap[i].visible = false;
this.trebleFlatBitmap[i].x = newX;
this.trebleFlatBitmap[i].updateCache();

this.sopranoSharpBitmap[i].visible = false;
this.sopranoSharpBitmap[i].x = newX;
this.sopranoSharpBitmap[i].updateCache();
this.sopranoFlatBitmap[i].visible = false;
this.sopranoFlatBitmap[i].x = newX;
this.sopranoFlatBitmap[i].updateCache();

this.altoSharpBitmap[i].visible = false;
this.altoSharpBitmap[i].x = newX;
this.altoSharpBitmap[i].updateCache();
this.altoFlatBitmap[i].visible = false;
this.altoFlatBitmap[i].x = newX;
this.altoFlatBitmap[i].updateCache();

this.tenorSharpBitmap[i].visible = false;
this.tenorSharpBitmap[i].x = newX;
this.tenorSharpBitmap[i].updateCache();
this.tenorFlatBitmap[i].visible = false;
this.tenorFlatBitmap[i].x = newX;
this.tenorFlatBitmap[i].updateCache();

this.bassSharpBitmap[i].visible = false;
this.bassSharpBitmap[i].x = newX;
this.bassSharpBitmap[i].updateCache();
this.bassFlatBitmap[i].visible = false;
this.bassFlatBitmap[i].x = newX;
this.bassFlatBitmap[i].updateCache();
}
this.update = true;
};
Expand All @@ -5942,7 +5952,7 @@ class Activity {
*/
this._hideTreble = () => {
this.trebleBitmap.visible = false;
this.trebleBitmap.updateCache();
this.trebleBitmap.uncache();
this._hideAccidentals();
this.update = true;
};
Expand All @@ -5952,6 +5962,7 @@ class Activity {
*/
this._showTreble = () => {
this.trebleBitmap.visible = true;
this.trebleBitmap.cache(0, 0, 1200, 900);
this.trebleBitmap.updateCache();
this._hideAccidentals();
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -5982,13 +5993,11 @@ class Activity {
if (scale.includes(_sharps[i])) {
this.trebleSharpBitmap[i].x += dx;
this.trebleSharpBitmap[i].visible = true;
this.trebleSharpBitmap[i].updateCache();
dx += 15;
}
if (scale.includes(_flats[i])) {
this.trebleFlatBitmap[i].x += dx;
this.trebleFlatBitmap[i].visible = true;
this.trebleFlatBitmap[i].updateCache();
dx += 15;
}
}
Expand All @@ -6001,7 +6010,7 @@ class Activity {
*/
this._hideGrand = () => {
this.grandBitmap.visible = false;
this.grandBitmap.updateCache();
this.grandBitmap.uncache();
this._hideAccidentals();
this.update = true;
};
Expand All @@ -6011,6 +6020,7 @@ class Activity {
*/
this._showGrand = () => {
this.grandBitmap.visible = true;
this.grandBitmap.cache(0, 0, 1200, 900);
this.grandBitmap.updateCache();
this._hideAccidentals();
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -6041,13 +6051,11 @@ class Activity {
if (scale.includes(_sharps[i])) {
this.grandSharpBitmap[i].x += dx;
this.grandSharpBitmap[i].visible = true;
this.grandSharpBitmap[i].updateCache();
dx += 15;
}
if (scale.includes(_flats[i])) {
this.grandFlatBitmap[i].x += dx;
this.grandFlatBitmap[i].visible = true;
this.grandFlatBitmap[i].updateCache();
dx += 15;
}
}
Expand All @@ -6059,7 +6067,7 @@ class Activity {
*/
this._hideSoprano = () => {
this.sopranoBitmap.visible = false;
this.sopranoBitmap.updateCache();
this.sopranoBitmap.uncache();
this.update = true;
};

Expand All @@ -6068,6 +6076,7 @@ class Activity {
*/
this._showSoprano = () => {
this.sopranoBitmap.visible = true;
this.sopranoBitmap.cache(0, 0, 1200, 900);
this.sopranoBitmap.updateCache();
this._hideAccidentals();
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -6098,13 +6107,11 @@ class Activity {
if (scale.includes(_sharps[i])) {
this.sopranoSharpBitmap[i].x += dx;
this.sopranoSharpBitmap[i].visible = true;
this.sopranoSharpBitmap[i].updateCache();
dx += 15;
}
if (scale.includes(_flats[i])) {
this.sopranoFlatBitmap[i].x += dx;
this.sopranoFlatBitmap[i].visible = true;
this.sopranoFlatBitmap[i].updateCache();
dx += 15;
}
}
Expand All @@ -6117,7 +6124,7 @@ class Activity {
*/
this._hideAlto = () => {
this.altoBitmap.visible = false;
this.altoBitmap.updateCache();
this.altoBitmap.uncache();
this._hideAccidentals();
this.update = true;
};
Expand All @@ -6131,6 +6138,7 @@ class Activity {
*/
this._showAlto = () => {
this.altoBitmap.visible = true;
this.altoBitmap.cache(0, 0, 1200, 900);
this.altoBitmap.updateCache();
this._hideAccidentals();
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -6161,13 +6169,11 @@ class Activity {
if (scale.includes(_sharps[i])) {
this.altoSharpBitmap[i].x += dx;
this.altoSharpBitmap[i].visible = true;
this.altoSharpBitmap[i].updateCache();
dx += 15;
}
if (scale.includes(_flats[i])) {
this.altoFlatBitmap[i].x += dx;
this.altoFlatBitmap[i].visible = true;
this.altoFlatBitmap[i].updateCache();
dx += 15;
}
}
Expand All @@ -6180,7 +6186,7 @@ class Activity {
*/
this._hideTenor = () => {
this.tenorBitmap.visible = false;
this.tenorBitmap.updateCache();
this.tenorBitmap.uncache();
this.update = true;
};

Expand All @@ -6189,6 +6195,7 @@ class Activity {
*/
this._showTenor = () => {
this.tenorBitmap.visible = true;
this.tenorBitmap.cache(0, 0, 1200, 900);
this.tenorBitmap.updateCache();
this._hideAccidentals();
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -6219,13 +6226,11 @@ class Activity {
if (scale.includes(_sharps[i])) {
this.tenorSharpBitmap[i].x += dx;
this.tenorSharpBitmap[i].visible = true;
this.tenorSharpBitmap[i].updateCache();
dx += 15;
}
if (scale.includes(_flats[i])) {
this.tenorFlatBitmap[i].x += dx;
this.tenorFlatBitmap[i].visible = true;
this.tenorFlatBitmap[i].updateCache();
dx += 15;
}
}
Expand All @@ -6238,7 +6243,7 @@ class Activity {
*/
this._hideBass = () => {
this.bassBitmap.visible = false;
this.bassBitmap.updateCache();
this.bassBitmap.uncache();
this._hideAccidentals();
this.update = true;
};
Expand All @@ -6248,6 +6253,7 @@ class Activity {
*/
this._showBass = () => {
this.bassBitmap.visible = true;
this.bassBitmap.cache(0, 0, 1200, 900);
this.bassBitmap.updateCache();
this._hideAccidentals();
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -6278,13 +6284,11 @@ class Activity {
if (scale.includes(_sharps[i])) {
this.bassSharpBitmap[i].x += dx;
this.bassSharpBitmap[i].visible = true;
this.bassSharpBitmap[i].updateCache();
dx += 15;
}
if (scale.includes(_flats[i])) {
this.bassFlatBitmap[i].x += dx;
this.bassFlatBitmap[i].visible = true;
this.bassFlatBitmap[i].updateCache();
dx += 15;
}
}
Expand Down
7 changes: 7 additions & 0 deletions js/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,13 @@ class Block {
updateCache() {
const that = this;
return new Promise((resolve, reject) => {
// If the container has no active bitmap cache (e.g., trashed
// blocks whose cache was freed), skip the update silently.
if (that.container && !that.container.bitmapCache) {
resolve();
return;
}

let loopCount = 0;
const MAX_RETRIES = 15;
const INITIAL_DELAY = 100;
Expand Down
22 changes: 22 additions & 0 deletions js/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6943,6 +6943,13 @@ class Blocks {
/** Add this block to the list of blocks in the trash so we can undo this action. */
this.trashStacks.push(thisBlock);

// Cap the undo history to prevent unbounded memory growth.
// Keep the 100 most recent trashed stacks.
const MAX_TRASH_UNDO = 100;
if (this.trashStacks.length > MAX_TRASH_UNDO) {
this.trashStacks = this.trashStacks.slice(-MAX_TRASH_UNDO);
}

/** Disconnect block. */
const parentBlock = myBlock.connections[0];
if (parentBlock != null) {
Expand Down Expand Up @@ -6988,6 +6995,21 @@ class Blocks {
const blk = this.dragGroup[b];
this.blockList[blk].trash = true;
this.blockList[blk].hide();

// Free the backing canvas memory for trashed blocks.
// Each cached block holds a bitmap canvas (~0.5-2 MB).
if (this.blockList[blk].container) {
this.blockList[blk].container.uncache();
}

// Clean up SVG art strings for trashed blocks to free memory.
if (this.blockArt[blk]) {
delete this.blockArt[blk];
}
if (this.blockCollapseArt[blk]) {
delete this.blockCollapseArt[blk];
}

const title = this.blockList[blk].protoblock.staticLabels[0];
closeBlkWidgets(_(title));
this.activity.refreshCanvas();
Expand Down
Loading
Loading