Merge pull request #9149 from ahocevar/label-consumers
Keep track of used labels
This commit is contained in:
@@ -107,6 +107,12 @@ class VectorRenderTile extends Tile {
|
|||||||
const canvas = this.context_[key].canvas;
|
const canvas = this.context_[key].canvas;
|
||||||
canvas.width = canvas.height = 0;
|
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);
|
this.setState(TileState.ABORT);
|
||||||
super.disposeInternal();
|
super.disposeInternal();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import {getFontFamilies} from '../css.js';
|
import {getFontFamilies} from '../css.js';
|
||||||
import {createCanvasContext2D} from '../dom.js';
|
import {createCanvasContext2D} from '../dom.js';
|
||||||
import {clear} from '../obj.js';
|
import {clear} from '../obj.js';
|
||||||
import LRUCache from '../structs/LRUCache.js';
|
|
||||||
import {create as createTransform} from '../transform.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
|
* The label cache for text rendering. To change the default cache size of 2048
|
||||||
* entries, use {@link module:ol/structs/LRUCache#setSize}.
|
* entries, use {@link module:ol/structs/LRUCache#setSize}.
|
||||||
* @type {LRUCache<HTMLCanvasElement>}
|
* @type {LabelCache}
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
export const labelCache = new LRUCache();
|
export const labelCache = new LabelCache();
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prune the label cache.
|
|
||||||
*/
|
|
||||||
export function pruneLabelCache() {
|
|
||||||
while (labelCache.canExpireCache()) {
|
|
||||||
const canvas = labelCache.pop();
|
|
||||||
canvas.width = canvas.height = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import {createCanvasContext2D} from '../../dom.js';
|
import {createCanvasContext2D} from '../../dom.js';
|
||||||
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
|
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
|
||||||
|
import Disposable from '../../Disposable.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +53,7 @@ const p3 = [];
|
|||||||
const p4 = [];
|
const p4 = [];
|
||||||
|
|
||||||
|
|
||||||
class Executor {
|
class Executor extends Disposable {
|
||||||
/**
|
/**
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
@@ -62,6 +63,7 @@ class Executor {
|
|||||||
* @param {SerializableInstructions} instructions The serializable instructions
|
* @param {SerializableInstructions} instructions The serializable instructions
|
||||||
*/
|
*/
|
||||||
constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree, instructions) {
|
constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree, instructions) {
|
||||||
|
super();
|
||||||
/**
|
/**
|
||||||
* @type {?}
|
* @type {?}
|
||||||
*/
|
*/
|
||||||
@@ -163,6 +165,14 @@ class Executor {
|
|||||||
this.widths_ = {};
|
this.widths_ = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
disposeInternal() {
|
||||||
|
labelCache.release(this);
|
||||||
|
super.disposeInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} text Text.
|
* @param {string} text Text.
|
||||||
@@ -230,7 +240,7 @@ class Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return labelCache.get(key);
|
return labelCache.get(key, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {isEmpty} from '../../obj.js';
|
|||||||
import BuilderType from './BuilderType.js';
|
import BuilderType from './BuilderType.js';
|
||||||
import {create as createTransform, compose as composeTransform} from '../../transform.js';
|
import {create as createTransform, compose as composeTransform} from '../../transform.js';
|
||||||
import Executor from './Executor.js';
|
import Executor from './Executor.js';
|
||||||
|
import Disposable from '../../Disposable.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const
|
* @const
|
||||||
@@ -25,7 +26,7 @@ const ORDER = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
class ExecutorGroup {
|
class ExecutorGroup extends Disposable {
|
||||||
/**
|
/**
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Max extent.
|
* @param {import("../../extent.js").Extent} maxExtent Max extent.
|
||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
@@ -36,9 +37,8 @@ class ExecutorGroup {
|
|||||||
* The serializable instructions.
|
* The serializable instructions.
|
||||||
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
||||||
*/
|
*/
|
||||||
constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree,
|
constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree, allInstructions, opt_renderBuffer) {
|
||||||
allInstructions, opt_renderBuffer) {
|
super();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declutter tree.
|
* Declutter tree.
|
||||||
* @private
|
* @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<BuilderType>} executors Executors.
|
* @param {Array<BuilderType>} executors Executors.
|
||||||
* @return {boolean} Has executors of the provided types.
|
* @return {boolean} Has executors of the provided types.
|
||||||
|
|||||||
69
src/ol/render/canvas/LabelCache.js
Normal file
69
src/ol/render/canvas/LabelCache.js
Normal file
@@ -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;
|
||||||
@@ -6,7 +6,7 @@ import {asColorLike} from '../../colorlike.js';
|
|||||||
import {intersects} from '../../extent.js';
|
import {intersects} from '../../extent.js';
|
||||||
import {matchingChunk} from '../../geom/flat/straightchunk.js';
|
import {matchingChunk} from '../../geom/flat/straightchunk.js';
|
||||||
import GeometryType from '../../geom/GeometryType.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 CanvasInstruction from './Instruction.js';
|
||||||
import CanvasBuilder from './Builder.js';
|
import CanvasBuilder from './Builder.js';
|
||||||
import TextPlacement from '../../style/TextPlacement.js';
|
import TextPlacement from '../../style/TextPlacement.js';
|
||||||
@@ -131,7 +131,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
|||||||
*/
|
*/
|
||||||
this.strokeKey_ = '';
|
this.strokeKey_ = '';
|
||||||
|
|
||||||
pruneLabelCache();
|
labelCache.prune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import rbush from 'rbush';
|
|||||||
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
|
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
|
||||||
import {labelCache} from '../../render/canvas.js';
|
import {labelCache} from '../../render/canvas.js';
|
||||||
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.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 CanvasLayerRenderer from './Layer.js';
|
||||||
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
||||||
import {toString as transformToString, makeScale, makeInverse} from '../../transform.js';
|
import {toString as transformToString, makeScale, makeInverse} from '../../transform.js';
|
||||||
@@ -290,6 +290,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.replayGroup_) {
|
||||||
|
this.replayGroup_.dispose();
|
||||||
|
}
|
||||||
this.replayGroup_ = null;
|
this.replayGroup_ = null;
|
||||||
|
|
||||||
this.dirty_ = false;
|
this.dirty_ = false;
|
||||||
@@ -335,7 +338,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const replayGroupInstructions = replayGroup.finish();
|
const replayGroupInstructions = replayGroup.finish();
|
||||||
const renderingExecutorGroup = new InstructionsGroupExecutor(extent, resolution,
|
const executorGroup = new ExecutorGroup(extent, resolution,
|
||||||
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_,
|
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_,
|
||||||
replayGroupInstructions, vectorLayer.getRenderBuffer());
|
replayGroupInstructions, vectorLayer.getRenderBuffer());
|
||||||
|
|
||||||
@@ -343,7 +346,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
this.renderedRevision_ = vectorLayerRevision;
|
this.renderedRevision_ = vectorLayerRevision;
|
||||||
this.renderedRenderOrder_ = vectorLayerRenderOrder;
|
this.renderedRenderOrder_ = vectorLayerRenderOrder;
|
||||||
this.renderedExtent_ = extent;
|
this.renderedExtent_ = extent;
|
||||||
this.replayGroup_ = renderingExecutorGroup;
|
this.replayGroup_ = executorGroup;
|
||||||
|
|
||||||
this.replayGroupChanged = true;
|
this.replayGroupChanged = true;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -248,6 +248,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
|
|
||||||
const sourceTiles = tile.load();
|
const sourceTiles = tile.load();
|
||||||
const layerUid = getUid(layer);
|
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] = [];
|
tile.executorGroups[layerUid] = [];
|
||||||
for (let t = 0, tt = sourceTiles.length; t < tt; ++t) {
|
for (let t = 0, tt = sourceTiles.length; t < tt; ++t) {
|
||||||
const sourceTile = sourceTiles[t];
|
const sourceTile = sourceTiles[t];
|
||||||
|
|||||||
@@ -114,9 +114,10 @@ class LRUCache extends EventTarget {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} key Key.
|
* @param {string} key Key.
|
||||||
|
* @param {*=} opt_options Options (reserverd for subclasses).
|
||||||
* @return {T} Value.
|
* @return {T} Value.
|
||||||
*/
|
*/
|
||||||
get(key) {
|
get(key, opt_options) {
|
||||||
const entry = this.entries_[key];
|
const entry = this.entries_[key];
|
||||||
assert(entry !== undefined,
|
assert(entry !== undefined,
|
||||||
15); // Tried to get a value for a key that does not exist in the cache
|
15); // Tried to get a value for a key that does not exist in the cache
|
||||||
|
|||||||
@@ -10,17 +10,6 @@ describe('ol.render.canvas', function() {
|
|||||||
font.rel = 'stylesheet';
|
font.rel = 'stylesheet';
|
||||||
const head = document.getElementsByTagName('head')[0];
|
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() {
|
describe('ol.render.canvas.checkFont()', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
|||||||
27
test/spec/ol/render/canvas/labelcache.test.js
Normal file
27
test/spec/ol/render/canvas/labelcache.test.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user