turn getCanvas into a class CanvasPool

This commit is contained in:
Michael "Z" Goddard 2019-02-04 13:38:46 -05:00
parent c01175613b
commit d05a414439
No known key found for this signature in database
GPG key ID: 762CD40DD5349872

View file

@ -32,24 +32,51 @@ const loadVector_ = function (costume, runtime, rotationCenter, optVersion) {
}); });
}; };
const getCanvas = (function () { const canvasPool = (function () {
const _canvases = []; /**
let clearSoon = null; * A pool of canvas objects that can be reused to reduce memory
* allocations. And time spent in those allocations and the later garbage
* collection.
*/
class CanvasPool {
constructor () {
this.pool = [];
this.clearSoon = null;
}
return Object.assign(() => ( /**
_canvases.pop() || document.createElement('canvas') * After a short wait period clear the pool to let the VM collect
), { * garbage.
release (canvas) { */
if (!clearSoon) { clear () {
clearSoon = new Promise(resolve => setTimeout(resolve, 1000)) if (!this.clearSoon) {
this.clearSoon = new Promise(resolve => setTimeout(resolve, 1000))
.then(() => { .then(() => {
_canvases.length = 0; this.pool.length = 0;
clearSoon = null; this.clearSoon = null;
}); });
} }
_canvases.push(canvas);
} }
});
/**
* Return a canvas. Create the canvas if the pool is empty.
* @returns {HTMLCanvasElement} A canvas element.
*/
create () {
return this.pool.pop() || document.createElement('canvas');
}
/**
* Release the canvas to be reused.
* @param {HTMLCanvasElement} canvas A canvas element.
*/
release (canvas) {
this.clear();
this.pool.push(canvas);
}
}
return new CanvasPool();
}()); }());
/** /**
@ -101,7 +128,7 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) {
}); });
})) }))
.then(([baseImageElement, textImageElement]) => { .then(([baseImageElement, textImageElement]) => {
const mergeCanvas = getCanvas(); const mergeCanvas = canvasPool.create();
const scale = costume.bitmapResolution === 1 ? 2 : 1; const scale = costume.bitmapResolution === 1 ? 2 : 1;
mergeCanvas.width = baseImageElement.width; mergeCanvas.width = baseImageElement.width;
@ -112,6 +139,12 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) {
if (textImageElement) { if (textImageElement) {
ctx.drawImage(textImageElement, 0, 0); ctx.drawImage(textImageElement, 0, 0);
} }
// Track the canvas we merged the bitmaps onto separately from the
// canvas that we receive from resize if scale is not 1. We know
// resize treats mergeCanvas as read only data. We don't know when
// resize may use or modify the canvas. So we'll only release the
// mergeCanvas back into the canvas pool. Reusing the canvas from
// resize may cause errors.
let canvas = mergeCanvas; let canvas = mergeCanvas;
if (scale !== 1) { if (scale !== 1) {
canvas = runtime.v2BitmapAdapter.resize(mergeCanvas, canvas.width * scale, canvas.height * scale); canvas = runtime.v2BitmapAdapter.resize(mergeCanvas, canvas.width * scale, canvas.height * scale);
@ -181,7 +214,7 @@ const loadBitmap_ = function (costume, runtime, _rotationCenter) {
.then(({canvas, mergeCanvas, rotationCenter}) => { .then(({canvas, mergeCanvas, rotationCenter}) => {
// createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined... // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined...
costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter); costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter);
getCanvas.release(mergeCanvas); canvasPool.release(mergeCanvas);
const renderSize = runtime.renderer.getSkinSize(costume.skinId); const renderSize = runtime.renderer.getSkinSize(costume.skinId);
costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2 costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2