diff --git a/packages/maptalks/src/core/ResourceCacheManager.ts b/packages/maptalks/src/core/ResourceCacheManager.ts new file mode 100644 index 0000000000..54dbe96e98 --- /dev/null +++ b/packages/maptalks/src/core/ResourceCacheManager.ts @@ -0,0 +1,287 @@ +import Browser from './Browser'; +import { isSVG, isImageBitMap, isString, hasOwn } from './util'; + +type ResourceUrl = string | string[]; + +type ResourceCacheItem = { + image: ImageBitmap; + width: number; + height: number; + refCnt: number; +} + +/** + * resouce key support ImageBitMap by Map + * this.resources.set(imagebitmap,cache); + * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map + */ + + +class ResourceCacheMap { + resources: Map; + + //@internal + _errors: Map; + + constructor() { + this.resources = new Map(); + this._errors = new Map(); + } + + + addResource(url: [string, number | string, number | string], img) { + const imgUrl = this._getImgUrl(url as any); + this.resources.set(imgUrl, { + image: img, + width: +url[1], + height: +url[2], + refCnt: 0 + }) + //is Image + if (img && img.width && img.height && !img.close && Browser.imageBitMap && !Browser.safari && !Browser.iosWeixin) { + if (img.src && isSVG(img.src)) { + return; + } + createImageBitmap(img).then(imageBitmap => { + if (!this.resources.has(imgUrl)) { + //removed + return; + } + this.resources.get(imgUrl).image = imageBitmap; + }); + } + } + + isResourceLoaded(url: ResourceUrl, checkSVG?: boolean) { + if (!url) { + return false; + } + const imgUrl = this._getImgUrl(url); + if (this._errors.has(imgUrl)) { + return true; + } + const img = this.resources.get(imgUrl); + if (!img) { + return false; + } + if (checkSVG && isSVG(url[0]) && (+url[1] > img.width || +url[2] > img.height)) { + return false; + } + return true; + } + + login(url: string) { + const res = this.resources.get(url); + if (res) { + res.refCnt++; + } + } + + logout(url: string) { + const res = this.resources.get(url); + if (res && res.refCnt-- <= 0) { + if (res.image && res.image.close) { + res.image.close(); + } + this.resources.delete(url); + } + } + + getImage(url: ResourceUrl) { + const imgUrl = this._getImgUrl(url); + if (!this.isResourceLoaded(url) || this._errors.has(imgUrl)) { + return null; + } + return this.resources.get(imgUrl).image; + } + + markErrorResource(url: ResourceUrl) { + const imgUrl = this._getImgUrl(url); + this._errors.set(imgUrl, 1); + } + + merge(res: ResourceCacheMap) { + if (!res) { + return this; + } + const otherResource = res.resources; + if (otherResource) { + otherResource.forEach((value, key) => { + const img = value; + this.addResource([key as string, img.width, img.height], img.image); + }) + } + return this; + } + + forEach(fn: (key: string | ImageBitmap, value: any) => void) { + if (!this.resources) { + return this; + } + this.resources.forEach((value, key) => { + fn(key, value); + }) + return this; + } + + //@internal + _getImgUrl(url: ResourceUrl) { + if (isImageBitMap(url)) { + return url; + } + let imgUrl; + if (Array.isArray(url)) { + imgUrl = url[0]; + } else if (isString(url)) { + imgUrl = url; + } + if (!imgUrl) { + console.error(`get url key error,the url is:`, url); + } + return imgUrl; + } + + remove() { + if (!this.resources) { + return this; + } + this.resources.forEach((value) => { + if (value && value.image && value.image.close) { + value.image.close(); + } + }) + this.resources.clear(); + return this; + } +} + + +export class ResourceCache { + resources: any; + + //@internal + _errors: any; + + constructor() { + this.resources = {}; + this._errors = {}; + } + + addResource(url: [string, number | string, number | string], img) { + this.resources[url[0]] = { + image: img, + width: +url[1], + height: +url[2], + refCnt: 0 + }; + if (img && img.width && img.height && !img.close && Browser.imageBitMap && !Browser.safari && !Browser.iosWeixin) { + if (img.src && isSVG(img.src)) { + return; + } + createImageBitmap(img).then(imageBitmap => { + if (!this.resources[url[0]]) { + //removed + return; + } + this.resources[url[0]].image = imageBitmap; + }); + } + } + + isResourceLoaded(url: ResourceUrl, checkSVG?: boolean) { + if (!url) { + return false; + } + const imgUrl = this._getImgUrl(url); + if (this._errors[imgUrl]) { + return true; + } + const img = this.resources[imgUrl]; + if (!img) { + return false; + } + if (checkSVG && isSVG(url[0]) && (+url[1] > img.width || +url[2] > img.height)) { + return false; + } + return true; + } + + login(url: string) { + const res = this.resources[url]; + if (res) { + res.refCnt++; + } + } + + logout(url: string) { + const res = this.resources[url]; + if (res && res.refCnt-- <= 0) { + if (res.image && res.image.close) { + res.image.close(); + } + delete this.resources[url]; + } + } + + getImage(url: ResourceUrl) { + const imgUrl = this._getImgUrl(url); + if (!this.isResourceLoaded(url) || this._errors[imgUrl]) { + return null; + } + return this.resources[imgUrl].image; + } + + markErrorResource(url: ResourceUrl) { + this._errors[this._getImgUrl(url)] = 1; + } + + merge(res: any) { + if (!res) { + return this; + } + for (const p in res.resources) { + const img = res.resources[p]; + this.addResource([p, img.width, img.height], img.image); + } + return this; + } + + forEach(fn: (key: string, value: any) => void) { + if (!this.resources) { + return this; + } + for (const p in this.resources) { + if (hasOwn(this.resources, p)) { + fn(p, this.resources[p]); + } + } + return this; + } + + //@internal + _getImgUrl(url: ResourceUrl) { + if (!Array.isArray(url)) { + return url; + } + return url[0]; + } + + remove() { + for (const p in this.resources) { + const res = this.resources[p]; + if (res && res.image && res.image.close) { + // close bitmap + res.image.close(); + } + } + this.resources = {}; + } +} + +// Dynamically obtain resource cache instances +export function getResouceCacheInstance(): ResourceCache { + if (Browser.decodeImageInWorker) { + return new ResourceCacheMap() as ResourceCache; + } + return new ResourceCache(); +} \ No newline at end of file diff --git a/packages/maptalks/src/core/util/draw.ts b/packages/maptalks/src/core/util/draw.ts index c8c1601f80..053883413e 100644 --- a/packages/maptalks/src/core/util/draw.ts +++ b/packages/maptalks/src/core/util/draw.ts @@ -3,8 +3,8 @@ import PointExtent from '../../geo/PointExtent'; import { isGradient } from './style'; import { isNumber } from './common'; import Canvas from '../Canvas'; -import { ResourceCache } from '../../renderer/layer/LayerAbstractRenderer' import { BBOX } from './bbox'; +import { ResourceCache } from '../ResourceCacheManager'; export function drawImageMarker(ctx: CanvasRenderingContext2D, image, point, symbol) { let w = symbol && symbol['markerWidth']; diff --git a/packages/maptalks/src/core/util/marker.ts b/packages/maptalks/src/core/util/marker.ts index 7b35f271c5..97c374b0e9 100644 --- a/packages/maptalks/src/core/util/marker.ts +++ b/packages/maptalks/src/core/util/marker.ts @@ -6,7 +6,7 @@ import Point from '../../geo/Point'; import PointExtent from '../../geo/PointExtent'; import Size from '../../geo/Size'; import { type MarkerType } from './draw' -import { ResourceCache } from '../../renderer/layer/LayerAbstractRenderer' +import { ResourceCache } from '../ResourceCacheManager'; export const DEFAULT_MARKER_SYMBOLS = { markerWidth: 10, diff --git a/packages/maptalks/src/geometry/ext/Geometry.Drag.ts b/packages/maptalks/src/geometry/ext/Geometry.Drag.ts index 6ed1643094..74fcb0fcf7 100644 --- a/packages/maptalks/src/geometry/ext/Geometry.Drag.ts +++ b/packages/maptalks/src/geometry/ext/Geometry.Drag.ts @@ -6,9 +6,9 @@ import Handler from '../../handler/Handler'; import Geometry from '../Geometry'; import DragHandler from '../../handler/Drag'; import { ConnectorLine } from '../ConnectorLine'; -import { ResourceCache } from '../../renderer/layer/LayerAbstractRenderer'; import Point from '../../geo/Point'; import Coordinate from '../../geo/Coordinate'; +import { getResouceCacheInstance } from '../../core/ResourceCacheManager'; const DRAG_STAGE_LAYER_ID = INTERNAL_LAYER_PREFIX + '_drag_stage'; @@ -193,7 +193,7 @@ class GeometryDragHandler extends Handler { }); map.addLayer(this._dragStageLayer); //copy resources to avoid repeat resource loading. - const resources = new ResourceCache(); + const resources = getResouceCacheInstance(); resources.merge(layer._getRenderer().resources); this._dragStageLayer._getRenderer().resources = resources; } @@ -415,7 +415,7 @@ class GeometryDragHandler extends Handler { delete this._shadowConnectors; } if (this._dragStageLayer) { - this._dragStageLayer._getRenderer().resources = new ResourceCache(); + this._dragStageLayer._getRenderer().resources = getResouceCacheInstance(); this._dragStageLayer.remove(); } } diff --git a/packages/maptalks/src/layer/ImageLayer.ts b/packages/maptalks/src/layer/ImageLayer.ts index f70c1831d6..9a9c197eab 100644 --- a/packages/maptalks/src/layer/ImageLayer.ts +++ b/packages/maptalks/src/layer/ImageLayer.ts @@ -4,10 +4,10 @@ import Browser from '../core/Browser'; import Point from '../geo/Point'; import ImageGLRenderable from '../renderer/layer/ImageGLRenderable'; import CanvasRenderer from '../renderer/layer/CanvasRenderer'; -import { ResourceCache } from '../renderer/layer/LayerAbstractRenderer'; import Extent from '../geo/Extent'; import Layer, { LayerOptionsType } from './Layer'; import { PointExtent } from '../geo'; +import { getResouceCacheInstance } from '../core/ResourceCacheManager'; /** @@ -152,7 +152,7 @@ export class ImageLayerCanvasRenderer extends CanvasRenderer { let urls = layer._imageData.map(img => [img.url, null, null]); if (this.resources) { const unloaded = []; - const resources = new ResourceCache(); + const resources = getResouceCacheInstance(); urls.forEach(url => { if (this.resources.isResourceLoaded(url)) { const img = this.resources.getImage(url); diff --git a/packages/maptalks/src/renderer/edit/EditHandle.ts b/packages/maptalks/src/renderer/edit/EditHandle.ts index c28358cc51..085691b52e 100644 --- a/packages/maptalks/src/renderer/edit/EditHandle.ts +++ b/packages/maptalks/src/renderer/edit/EditHandle.ts @@ -1,7 +1,6 @@ import Eventable from '../../core/Eventable'; import Class from '../../core/Class'; import Point, { type PointJson } from '../../geo/Point'; -import { ResourceCache } from '../layer/LayerAbstractRenderer'; import { drawVectorMarker } from '../../core/util/draw'; import { isNil } from '../../core/util/'; import { getSymbolHash } from '../../core/util'; @@ -10,8 +9,9 @@ import DragHandler from '../../handler/Drag'; import { BBOX, bufferBBOX, getDefaultBBOX } from '../../core/util/bbox'; import type Map from '../../map/Map'; import type GeometryEditor from '../../geometry/editor/GeometryEditor'; +import { getResouceCacheInstance } from '../../core/ResourceCacheManager'; -const resources = new ResourceCache(); +const resources = getResouceCacheInstance(); let prevX, prevY; type EventParams = any; diff --git a/packages/maptalks/src/renderer/geometry/CollectionPainter.ts b/packages/maptalks/src/renderer/geometry/CollectionPainter.ts index 58f24236e8..e3c955eb2d 100644 --- a/packages/maptalks/src/renderer/geometry/CollectionPainter.ts +++ b/packages/maptalks/src/renderer/geometry/CollectionPainter.ts @@ -3,8 +3,8 @@ import PointExtent from '../../geo/PointExtent'; import { BBOX, getDefaultBBOX, resetBBOX, setBBOX, validateBBOX } from '../../core/util/bbox'; import Painter from './Painter'; import Extent from '../../geo/Extent'; -import { ResourceCache } from '../layer/LayerAbstractRenderer'; import { Geometries } from '../../geometry' +import { ResourceCache } from '../../core/ResourceCacheManager'; const TEMP_EXTENT = new PointExtent(); diff --git a/packages/maptalks/src/renderer/geometry/Painter.ts b/packages/maptalks/src/renderer/geometry/Painter.ts index 4fbb81ab23..754b264a4e 100644 --- a/packages/maptalks/src/renderer/geometry/Painter.ts +++ b/packages/maptalks/src/renderer/geometry/Painter.ts @@ -11,9 +11,9 @@ import { BBOX, getDefaultBBOX, resetBBOX, setBBOX, validateBBOX } from '../../co import Map from '../../map/Map' import { DebugSymbolizer } from './symbolizers'; import Extent from '../../geo/Extent'; -import { ResourceCache } from '../layer/LayerAbstractRenderer'; import type { WithUndef } from '../../types/typings'; import { Geometries } from '../../geometry' +import { ResourceCache } from '../../core/ResourceCacheManager'; //registered symbolizers //the latter will paint at the last diff --git a/packages/maptalks/src/renderer/geometry/symbolizers/CanvasSymbolizer.ts b/packages/maptalks/src/renderer/geometry/symbolizers/CanvasSymbolizer.ts index 34fb27baca..f8c4eb9127 100644 --- a/packages/maptalks/src/renderer/geometry/symbolizers/CanvasSymbolizer.ts +++ b/packages/maptalks/src/renderer/geometry/symbolizers/CanvasSymbolizer.ts @@ -2,7 +2,7 @@ import { isArrayHasData, isNumber } from '../../../core/util'; import { loadGeoSymbol, isFunctionDefinition, interpolated, } from '../../../core/mapbox'; import Symbolizer from './Symbolizer'; import Canvas from '../../../core/Canvas'; -import { ResourceCache } from '../../layer/LayerAbstractRenderer'; +import { ResourceCache } from '../../../core/ResourceCacheManager'; /** *所有基于 HTML5 Canvas2D 的symbolizer类 diff --git a/packages/maptalks/src/renderer/geometry/symbolizers/ImageMarkerSymbolizer.ts b/packages/maptalks/src/renderer/geometry/symbolizers/ImageMarkerSymbolizer.ts index d600fa1d9e..c1f4fb0da5 100644 --- a/packages/maptalks/src/renderer/geometry/symbolizers/ImageMarkerSymbolizer.ts +++ b/packages/maptalks/src/renderer/geometry/symbolizers/ImageMarkerSymbolizer.ts @@ -8,7 +8,7 @@ import Canvas from '../../../core/Canvas'; import PointSymbolizer from './PointSymbolizer'; import { Geometry } from '../../../geometry'; import Painter from '../Painter'; -import { ResourceCache } from '../../layer/LayerAbstractRenderer'; +import { ResourceCache } from '../../../core/ResourceCacheManager'; const TEMP_SIZE = new Size(1, 1); const TEMP_EXTENT = new PointExtent(); diff --git a/packages/maptalks/src/renderer/geometry/symbolizers/StrokeAndFillSymbolizer.ts b/packages/maptalks/src/renderer/geometry/symbolizers/StrokeAndFillSymbolizer.ts index e4718cf19e..0e63f2c503 100644 --- a/packages/maptalks/src/renderer/geometry/symbolizers/StrokeAndFillSymbolizer.ts +++ b/packages/maptalks/src/renderer/geometry/symbolizers/StrokeAndFillSymbolizer.ts @@ -1,4 +1,4 @@ -import { ResourceCache } from '../..'; +import { ResourceCache } from '../../../core/ResourceCacheManager'; import { getValueOrDefault } from '../../../core/util'; import { isGradient as checkGradient } from '../../../core/util/style'; import Coordinate from '../../../geo/Coordinate'; diff --git a/packages/maptalks/src/renderer/geometry/symbolizers/TextMarkerSymbolizer.ts b/packages/maptalks/src/renderer/geometry/symbolizers/TextMarkerSymbolizer.ts index aa76e2701c..848af22c83 100644 --- a/packages/maptalks/src/renderer/geometry/symbolizers/TextMarkerSymbolizer.ts +++ b/packages/maptalks/src/renderer/geometry/symbolizers/TextMarkerSymbolizer.ts @@ -9,8 +9,8 @@ import PointSymbolizer from './PointSymbolizer'; import { replaceVariable, describeText, getFont } from '../../../core/util/strings'; import { Geometry } from '../../../geometry'; import Painter from '../Painter'; -import { ResourceCache } from '../..'; import { clipLine } from '../../../core/util/path'; +import { ResourceCache } from '../../../core/ResourceCacheManager'; const TEMP_EXTENT = new PointExtent(); diff --git a/packages/maptalks/src/renderer/geometry/symbolizers/VectorMarkerSymbolizer.ts b/packages/maptalks/src/renderer/geometry/symbolizers/VectorMarkerSymbolizer.ts index 279ee943b8..9a8bdda222 100644 --- a/packages/maptalks/src/renderer/geometry/symbolizers/VectorMarkerSymbolizer.ts +++ b/packages/maptalks/src/renderer/geometry/symbolizers/VectorMarkerSymbolizer.ts @@ -12,8 +12,8 @@ import { getDefaultVAlign, getDefaultHAlign, DEFAULT_MARKER_SYMBOLS } from '../. import { Geometry } from '../../../geometry'; import Painter from '../Painter'; import { Extent } from '../../../geo'; -import { ResourceCache } from '../../layer/LayerAbstractRenderer'; import { getDefaultBBOX, resetBBOX } from '../../../core/util/bbox'; +import { ResourceCache } from '../../../core/ResourceCacheManager'; const MARKER_SIZE: [number, number] = [0, 0]; const TEMP_EXTENT = new PointExtent(); diff --git a/packages/maptalks/src/renderer/geometry/symbolizers/VectorPathMarkerSymbolizer.ts b/packages/maptalks/src/renderer/geometry/symbolizers/VectorPathMarkerSymbolizer.ts index 3693479193..4ef2445d25 100644 --- a/packages/maptalks/src/renderer/geometry/symbolizers/VectorPathMarkerSymbolizer.ts +++ b/packages/maptalks/src/renderer/geometry/symbolizers/VectorPathMarkerSymbolizer.ts @@ -3,7 +3,7 @@ import Browser from '../../../core/Browser'; import { getMarkerPathBase64 } from '../../../core/util/resource'; import ImageMarkerSymbolizer from './ImageMarkerSymbolizer'; import { isPathSymbol } from '../../../core/util/marker'; -import { ResourceCache } from '../../layer/LayerAbstractRenderer'; +import { ResourceCache } from '../../../core/ResourceCacheManager'; // import { ResourceProxy } from '../../../core/ResourceProxy'; export default class VectorPathMarkerSymbolizer extends ImageMarkerSymbolizer { diff --git a/packages/maptalks/src/renderer/index.ts b/packages/maptalks/src/renderer/index.ts index 59e67bc745..4cb32655b1 100644 --- a/packages/maptalks/src/renderer/index.ts +++ b/packages/maptalks/src/renderer/index.ts @@ -1,6 +1,6 @@ /** @namespace renderer */ -export { ResourceCache } from './layer/LayerAbstractRenderer'; +export { ResourceCache } from './../core/ResourceCacheManager'; export { default as CanvasRenderer } from './layer/CanvasRenderer'; export { default as LayerAbstractRenderer } from './layer/LayerAbstractRenderer'; export { default as ImageGLRenderable } from './layer/ImageGLRenderable'; diff --git a/packages/maptalks/src/renderer/layer/LayerAbstractRenderer.ts b/packages/maptalks/src/renderer/layer/LayerAbstractRenderer.ts index 5cce9bbd65..fec5793039 100644 --- a/packages/maptalks/src/renderer/layer/LayerAbstractRenderer.ts +++ b/packages/maptalks/src/renderer/layer/LayerAbstractRenderer.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-types */ -import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, hasOwn, getImageBitMap, isImageBitMap } from '../../core/util'; +import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, getImageBitMap, isImageBitMap } from '../../core/util'; import Class from '../../core/Class'; import Browser from '../../core/Browser'; import Canvas2D from '../../core/Canvas'; @@ -11,6 +11,7 @@ import { imageFetchWorkerKey } from '../../core/worker/CoreWorkers'; import { registerWorkerAdapter } from '../../core/worker/Worker'; import { formatResourceUrl } from '../../core/ResourceProxy'; import { TileRenderingCanvas, ImageType } from '../types'; +import { getResouceCacheInstance, ResourceCache } from '../../core/ResourceCacheManager'; const EMPTY_ARRAY = []; class ResourceWorkerConnection extends Actor { @@ -102,7 +103,7 @@ class LayerAbstractRenderer extends Class { } if (!this.resources) { /* eslint-disable no-use-before-define */ - this.resources = new ResourceCache(); + this.resources = getResouceCacheInstance(); /* eslint-enable no-use-before-define */ } this.checkAndDraw(this._tryToDraw, framestamp); @@ -390,10 +391,17 @@ class LayerAbstractRenderer extends Class { const cache = {}; for (let i = resourceUrls.length - 1; i >= 0; i--) { const url = resourceUrls[i]; - if (!url || !url.length || cache[url.join('-')]) { + if (!url || !url.length) { continue; } - cache[url.join('-')] = 1; + const key = url.join('-'); + //Exclude ImageBitmap + if (!isImageBitMap(url[0])) { + if (cache[key]) { + continue; + } + cache[key] = 1; + } if (!resources.isResourceLoaded(url, true)) { //closure it to preserve url's value promises.push(new Promise(this._promiseResource(url))); @@ -750,129 +758,6 @@ class LayerAbstractRenderer extends Class { export default LayerAbstractRenderer; -export type ResourceUrl = string | string[] - -export class ResourceCache { - resources: any; - - //@internal - _errors: any; - - constructor() { - this.resources = {}; - this._errors = {}; - } - - addResource(url: [string, number | string, number | string], img) { - this.resources[url[0]] = { - image: img, - width: +url[1], - height: +url[2], - refCnt: 0 - }; - if (img && img.width && img.height && !img.close && Browser.imageBitMap && !Browser.safari && !Browser.iosWeixin) { - if (img.src && isSVG(img.src)) { - return; - } - createImageBitmap(img).then(imageBitmap => { - if (!this.resources[url[0]]) { - //removed - return; - } - this.resources[url[0]].image = imageBitmap; - }); - } - } - - isResourceLoaded(url: ResourceUrl, checkSVG?: boolean) { - if (!url) { - return false; - } - const imgUrl = this._getImgUrl(url); - if (this._errors[imgUrl]) { - return true; - } - const img = this.resources[imgUrl]; - if (!img) { - return false; - } - if (checkSVG && isSVG(url[0]) && (+url[1] > img.width || +url[2] > img.height)) { - return false; - } - return true; - } - - login(url: string) { - const res = this.resources[url]; - if (res) { - res.refCnt++; - } - } - - logout(url: string) { - const res = this.resources[url]; - if (res && res.refCnt-- <= 0) { - if (res.image && res.image.close) { - res.image.close(); - } - delete this.resources[url]; - } - } - - getImage(url: ResourceUrl) { - const imgUrl = this._getImgUrl(url); - if (!this.isResourceLoaded(url) || this._errors[imgUrl]) { - return null; - } - return this.resources[imgUrl].image; - } - - markErrorResource(url: ResourceUrl) { - this._errors[this._getImgUrl(url)] = 1; - } - - merge(res: any) { - if (!res) { - return this; - } - for (const p in res.resources) { - const img = res.resources[p]; - this.addResource([p, img.width, img.height], img.image); - } - return this; - } - - forEach(fn: Function) { - if (!this.resources) { - return this; - } - for (const p in this.resources) { - if (hasOwn(this.resources, p)) { - fn(p, this.resources[p]); - } - } - return this; - } - - //@internal - _getImgUrl(url: ResourceUrl) { - if (!Array.isArray(url)) { - return url; - } - return url[0]; - } - - remove() { - for (const p in this.resources) { - const res = this.resources[p]; - if (res && res.image && res.image.close) { - // close bitmap - res.image.close(); - } - } - this.resources = {}; - } -} const workerSource = ` function (exports) {