From 592b6cf36283724448135ed78487177078fb51c1 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 22 Jan 2019 21:00:21 +0100 Subject: [PATCH] Keep track of used labels --- src/ol/VectorRenderTile.js | 6 ++ src/ol/render/canvas.js | 17 +---- src/ol/render/canvas/Executor.js | 14 +++- src/ol/render/canvas/ExecutorGroup.js | 21 ++++-- src/ol/render/canvas/LabelCache.js | 69 +++++++++++++++++++ src/ol/render/canvas/TextBuilder.js | 4 +- src/ol/renderer/canvas/VectorLayer.js | 9 ++- src/ol/renderer/canvas/VectorTileLayer.js | 6 ++ src/ol/structs/LRUCache.js | 3 +- test/spec/ol/render/canvas/index.test.js | 11 --- test/spec/ol/render/canvas/labelcache.test.js | 27 ++++++++ 11 files changed, 150 insertions(+), 37 deletions(-) create mode 100644 src/ol/render/canvas/LabelCache.js create mode 100644 test/spec/ol/render/canvas/labelcache.test.js diff --git a/src/ol/VectorRenderTile.js b/src/ol/VectorRenderTile.js index bc0c7ed4c7..7c427316f4 100644 --- a/src/ol/VectorRenderTile.js +++ b/src/ol/VectorRenderTile.js @@ -107,6 +107,12 @@ class VectorRenderTile extends Tile { const canvas = this.context_[key].canvas; canvas.width = canvas.height = 0; } + for (const key in this.executorGroups) { + const executorGroups = this.executorGroups[key]; + for (let i = 0, ii = executorGroups.length; i < ii; ++i) { + executorGroups[i].disposeInternal(); + } + } this.setState(TileState.ABORT); super.disposeInternal(); } diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js index 09a3c7696e..4e9f562ac8 100644 --- a/src/ol/render/canvas.js +++ b/src/ol/render/canvas.js @@ -4,8 +4,8 @@ import {getFontFamilies} from '../css.js'; import {createCanvasContext2D} from '../dom.js'; import {clear} from '../obj.js'; -import LRUCache from '../structs/LRUCache.js'; import {create as createTransform} from '../transform.js'; +import LabelCache from './canvas/LabelCache.js'; /** @@ -163,21 +163,10 @@ export const defaultLineWidth = 1; /** * The label cache for text rendering. To change the default cache size of 2048 * entries, use {@link module:ol/structs/LRUCache#setSize}. - * @type {LRUCache} + * @type {LabelCache} * @api */ -export const labelCache = new LRUCache(); - - -/** - * Prune the label cache. - */ -export function pruneLabelCache() { - while (labelCache.canExpireCache()) { - const canvas = labelCache.pop(); - canvas.width = canvas.height = 0; - } -} +export const labelCache = new LabelCache(); /** diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index 3f65dad8e0..4cae87c8e2 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -20,6 +20,7 @@ import { } from '../../transform.js'; import {createCanvasContext2D} from '../../dom.js'; import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js'; +import Disposable from '../../Disposable.js'; /** @@ -52,7 +53,7 @@ const p3 = []; const p4 = []; -class Executor { +class Executor extends Disposable { /** * @param {import("../../extent.js").Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. @@ -62,6 +63,7 @@ class Executor { * @param {SerializableInstructions} instructions The serializable instructions */ constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree, instructions) { + super(); /** * @type {?} */ @@ -163,6 +165,14 @@ class Executor { this.widths_ = {}; } + /** + * @inheritDoc + */ + disposeInternal() { + labelCache.release(this); + super.disposeInternal(); + } + /** * @param {string} text Text. @@ -230,7 +240,7 @@ class Executor { } } } - return labelCache.get(key); + return labelCache.get(key, this); } /** diff --git a/src/ol/render/canvas/ExecutorGroup.js b/src/ol/render/canvas/ExecutorGroup.js index ea3f925c55..a50558eb3c 100644 --- a/src/ol/render/canvas/ExecutorGroup.js +++ b/src/ol/render/canvas/ExecutorGroup.js @@ -10,6 +10,7 @@ import {isEmpty} from '../../obj.js'; import BuilderType from './BuilderType.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; import Executor from './Executor.js'; +import Disposable from '../../Disposable.js'; /** * @const @@ -25,7 +26,7 @@ const ORDER = [ ]; -class ExecutorGroup { +class ExecutorGroup extends Disposable { /** * @param {import("../../extent.js").Extent} maxExtent Max extent. * @param {number} resolution Resolution. @@ -36,9 +37,8 @@ class ExecutorGroup { * The serializable instructions. * @param {number=} opt_renderBuffer Optional rendering buffer. */ - constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree, - allInstructions, opt_renderBuffer) { - + constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree, allInstructions, opt_renderBuffer) { + super(); /** * Declutter tree. * @private @@ -130,6 +130,19 @@ class ExecutorGroup { } } + /** + * @inheritDoc + */ + disposeInternal() { + for (const z in this.executorsByZIndex_) { + const executors = this.executorsByZIndex_[z]; + for (const key in executors) { + executors[key].disposeInternal(); + } + } + super.disposeInternal(); + } + /** * @param {Array} executors Executors. * @return {boolean} Has executors of the provided types. diff --git a/src/ol/render/canvas/LabelCache.js b/src/ol/render/canvas/LabelCache.js new file mode 100644 index 0000000000..b2ea1493e0 --- /dev/null +++ b/src/ol/render/canvas/LabelCache.js @@ -0,0 +1,69 @@ +import {getUid} from '../../util.js'; +import LRUCache from '../../structs/LRUCache.js'; + +/** + * @module ol/render/canvas/LabelCache + */ + +/** + * @classdesc + * Cache of pre-rendered labels. + * @fires import("../events/Event.js").Event + */ +class LabelCache extends LRUCache { + + /** + * @inheritDoc + */ + constructor(opt_highWaterMark) { + super(opt_highWaterMark); + this.consumers = {}; + } + + clear() { + super.clear(); + this.consumers = {}; + } + + /** + * @override + * @param {string} key Label key. + * @param {import("./Executor.js").default} consumer Label consumer. + * @return {HTMLCanvasElement} Label. + */ + get(key, consumer) { + const canvas = super.get(key); + const consumerId = getUid(consumer); + if (!(consumerId in this.consumers)) { + this.consumers[consumerId] = {}; + } + this.consumers[consumerId][key] = true; + return canvas; + } + + prune() { + outer: + while (this.canExpireCache()) { + const key = this.peekLastKey(); + for (const consumerId in this.consumers) { + if (key in this.consumers[consumerId]) { + break outer; + } + } + const canvas = this.pop(); + canvas.width = canvas.height = 0; + for (const consumerId in this.consumers) { + delete this.consumers[consumerId][key]; + } + } + } + + /** + * @param {import("./Executor.js").default} consumer Label consumer. + */ + release(consumer) { + delete this.consumers[getUid(consumer)]; + } +} + +export default LabelCache; diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index d77b62edf1..e5850def1e 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -6,7 +6,7 @@ import {asColorLike} from '../../colorlike.js'; import {intersects} from '../../extent.js'; import {matchingChunk} from '../../geom/flat/straightchunk.js'; import GeometryType from '../../geom/GeometryType.js'; -import {pruneLabelCache, defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; +import {labelCache, defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; import CanvasBuilder from './Builder.js'; import TextPlacement from '../../style/TextPlacement.js'; @@ -131,7 +131,7 @@ class CanvasTextBuilder extends CanvasBuilder { */ this.strokeKey_ = ''; - pruneLabelCache(); + labelCache.prune(); } /** diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 6afdf753dc..2fcfcdf105 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -9,7 +9,7 @@ import rbush from 'rbush'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import {labelCache} from '../../render/canvas.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; -import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js'; +import ExecutorGroup from '../../render/canvas/ExecutorGroup.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; import {toString as transformToString, makeScale, makeInverse} from '../../transform.js'; @@ -290,6 +290,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { return true; } + if (this.replayGroup_) { + this.replayGroup_.dispose(); + } this.replayGroup_ = null; this.dirty_ = false; @@ -335,7 +338,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { } const replayGroupInstructions = replayGroup.finish(); - const renderingExecutorGroup = new InstructionsGroupExecutor(extent, resolution, + const executorGroup = new ExecutorGroup(extent, resolution, pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, replayGroupInstructions, vectorLayer.getRenderBuffer()); @@ -343,7 +346,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { this.renderedRevision_ = vectorLayerRevision; this.renderedRenderOrder_ = vectorLayerRenderOrder; this.renderedExtent_ = extent; - this.replayGroup_ = renderingExecutorGroup; + this.replayGroup_ = executorGroup; this.replayGroupChanged = true; return true; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 1c7952c7d7..34fe99637f 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -248,6 +248,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const sourceTiles = tile.load(); const layerUid = getUid(layer); + const executorGroups = tile.executorGroups[layerUid]; + if (executorGroups) { + for (let i = 0, ii = executorGroups.length; i < ii; ++i) { + executorGroups[i].dispose(); + } + } tile.executorGroups[layerUid] = []; for (let t = 0, tt = sourceTiles.length; t < tt; ++t) { const sourceTile = sourceTiles[t]; diff --git a/src/ol/structs/LRUCache.js b/src/ol/structs/LRUCache.js index d02241d405..f79ef36baa 100644 --- a/src/ol/structs/LRUCache.js +++ b/src/ol/structs/LRUCache.js @@ -114,9 +114,10 @@ class LRUCache extends EventTarget { /** * @param {string} key Key. + * @param {*=} opt_options Options (reserverd for subclasses). * @return {T} Value. */ - get(key) { + get(key, opt_options) { const entry = this.entries_[key]; assert(entry !== undefined, 15); // Tried to get a value for a key that does not exist in the cache diff --git a/test/spec/ol/render/canvas/index.test.js b/test/spec/ol/render/canvas/index.test.js index 2cc763e545..7f178a37be 100644 --- a/test/spec/ol/render/canvas/index.test.js +++ b/test/spec/ol/render/canvas/index.test.js @@ -10,17 +10,6 @@ describe('ol.render.canvas', function() { font.rel = 'stylesheet'; const head = document.getElementsByTagName('head')[0]; - it('pruneLabelCache()', function() { - const highWaterMark = render.labelCache.highWaterMark; - render.labelCache.highWaterMark = 1; - render.labelCache.set('foo', document.createElement('canvas')); - render.labelCache.set('bar', document.createElement('canvas')); - render.pruneLabelCache(); - expect(render.labelCache.getCount()).to.be(1); - render.labelCache.highWaterMark = highWaterMark; - render.labelCache.clear(); - }); - describe('ol.render.canvas.checkFont()', function() { beforeEach(function() { diff --git a/test/spec/ol/render/canvas/labelcache.test.js b/test/spec/ol/render/canvas/labelcache.test.js new file mode 100644 index 0000000000..38bfa79a2e --- /dev/null +++ b/test/spec/ol/render/canvas/labelcache.test.js @@ -0,0 +1,27 @@ +import LabelCache from '../../../../../src/ol/render/canvas/LabelCache'; + +describe('ol.render.canvas.LabelCache', function() { + + it('#prune()', function() { + const labelCache = new LabelCache(1); + labelCache.set('key1', document.createElement('canvas')); + labelCache.set('key2', document.createElement('canvas')); + labelCache.prune(); + expect(labelCache.getCount()).to.be(1); + }); + + it('#prune() leaves used labels untouched until consumer is released', function() { + const labelCache = new LabelCache(1); + labelCache.set('key1', document.createElement('canvas')); + labelCache.set('key2', document.createElement('canvas')); + const consumer = {}; + labelCache.get('key1', consumer); + labelCache.get('key2', consumer); + labelCache.prune(); + expect(labelCache.getCount()).to.be(2); + labelCache.release(consumer); + labelCache.prune(); + expect(labelCache.getCount()).to.be(1); + }); + +});