Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
HARP-12700 - Further image cache refactoring (#1964)
Browse files Browse the repository at this point in the history
* HARP-12700: Extract loadImage out of ImageCache as free function.

* HARP-12700: Refactor ImageItem.
  • Loading branch information
atomicsulfate authored Nov 12, 2020
1 parent 4bee8dc commit cce64e6
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 175 deletions.
101 changes: 89 additions & 12 deletions @here/harp-mapview/lib/image/Image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { LoggerManager } from "@here/harp-utils";
import * as THREE from "three";

import { MipMapGenerator } from "./MipMapGenerator";

/** Any type supported by WebGLRenderingContext.texImage2D() for texture creation */
export type TexturizableImage =
| HTMLImageElement
Expand All @@ -12,28 +17,100 @@ export type TexturizableImage =
| ImageData
| ImageBitmap;

const logger = LoggerManager.instance.create("loadImage");
const mipMapGenerator = new MipMapGenerator();

/**
* `ImageItem` is used to identify an image in the {@link ImageCache}.
*/
export interface ImageItem {
/** URL of the image, or unique identifier. */
url: string;
image?: TexturizableImage;
export class ImageItem {
/** Mip maps for image data */
mipMaps?: ImageData[];
/** Turns to `true` when the data has finished loading. */
loaded: boolean;
/** Turns to `true` if the loading has been cancelled. */
cancelled?: boolean;
/** `loadingPromise` is only used during loading/generating the image. */
loadingPromise?: Promise<ImageItem | undefined>;
}
private loadingPromise?: Promise<ImageItem>;

export namespace ImageItem {
/**
* Missing Typedoc
* Create the `ImageItem`.
*
* @param url - URL of the image, or unique identifier.
* @param image - Optional image if already loaded.
*/
export function isLoading(imageItem: ImageItem): boolean {
return imageItem.loadingPromise !== undefined;
constructor(readonly url: string, public image?: TexturizableImage) {}

get loaded(): boolean {
return this.image !== undefined && this.mipMaps !== undefined;
}

get loading(): boolean {
return this.loadingPromise !== undefined;
}

/**
* Load an {@link ImageItem}.
*
* @remarks
* If the loading process is already running, it returns the current promise.
*
* @param imageItem - `ImageItem` containing the URL to load image from.
* @returns An {@link ImageItem} if the image has already been loaded, a promise otherwise.
*/
loadImage(): Promise<ImageItem> {
if (this.loaded) {
return Promise.resolve(this);
}

if (this.loading) {
return this.loadingPromise!;
}

this.loadingPromise = new Promise((resolve, reject) => {
if (this.image) {
const image = this.image;
if (image instanceof HTMLImageElement && !image.complete) {
image.addEventListener("load", this.finalizeImage.bind(this, image, resolve));
image.addEventListener("error", reject);
} else {
this.finalizeImage(this.image, resolve);
}
return;
}

logger.debug(`Loading image: ${this.url}`);
if (this.cancelled === true) {
logger.debug(`Cancelled loading image: ${this.url}`);
resolve(undefined);
} else {
new THREE.ImageLoader().load(
this.url,
(image: HTMLImageElement) => {
if (this.cancelled === true) {
logger.debug(`Cancelled loading image: ${this.url}`);
resolve(undefined);
return;
}

this.finalizeImage(image, resolve);
},
undefined,
errorEvent => {
logger.error(`... loading image failed: ${this.url} : ${errorEvent}`);

this.loadingPromise = undefined;
reject(`... loading image failed: ${this.url} : ${errorEvent}`);
}
);
}
});

return this.loadingPromise;
}

private finalizeImage(image: TexturizableImage, resolve: (item: ImageItem) => void) {
this.image = image;
this.mipMaps = mipMapGenerator.generateTextureAtlasMipMap(this);
this.loadingPromise = undefined;
resolve(this);
}
}
83 changes: 2 additions & 81 deletions @here/harp-mapview/lib/image/ImageCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@
* Licensed under Apache 2.0, see full license in LICENSE
* SPDX-License-Identifier: Apache-2.0
*/
import { LoggerManager } from "@here/harp-utils";
import * as THREE from "three";

import { ImageItem, TexturizableImage } from "./Image";
import { MipMapGenerator } from "./MipMapGenerator";

const logger = LoggerManager.instance.create("ImageCache");
const mipMapGenerator = new MipMapGenerator();

/**
* Combines an {@link ImageItem} with a list of owners (which can be any object) that reference it.
Expand Down Expand Up @@ -90,11 +83,7 @@ export class ImageCache {
}

imageCacheItem = {
imageItem: {
url,
image,
loaded: false
},
imageItem: new ImageItem(url, image),
owners: [owner]
};

Expand Down Expand Up @@ -155,74 +144,6 @@ export class ImageCache {
return this.m_images.size;
}

/**
* Load an {@link ImageItem}.
*
* @remarks
* If the loading process is already running, it returns the current promise.
*
* @param imageItem - `ImageItem` containing the URL to load image from.
* @returns An {@link ImageItem} if the image has already been loaded, a promise otherwise.
*/
loadImage(imageItem: ImageItem): ImageItem | Promise<ImageItem | undefined> {
const finalizeImage = (image: TexturizableImage, resolve: (item: ImageItem) => void) => {
imageItem.image = image;
imageItem.mipMaps = mipMapGenerator.generateTextureAtlasMipMap(imageItem);
imageItem.loadingPromise = undefined;
imageItem.loaded = true;
resolve(imageItem);
};

if (imageItem.loaded) {
return imageItem;
}

if (imageItem.loadingPromise !== undefined) {
return imageItem.loadingPromise;
}

imageItem.loadingPromise = new Promise((resolve, reject) => {
if (imageItem.image) {
const image = imageItem.image;
if (image instanceof HTMLImageElement && !image.complete) {
image.addEventListener("load", finalizeImage.bind(this, image, resolve));
image.addEventListener("error", reject);
} else {
finalizeImage(imageItem.image, resolve);
}
return;
}

logger.debug(`Loading image: ${imageItem.url}`);
if (imageItem.cancelled === true) {
logger.debug(`Cancelled loading image: ${imageItem.url}`);
resolve(undefined);
} else {
new THREE.ImageLoader().load(
imageItem.url,
(image: HTMLImageElement) => {
if (imageItem.cancelled === true) {
logger.debug(`Cancelled loading image: ${imageItem.url}`);
resolve(undefined);
return;
}

finalizeImage(image, resolve);
},
undefined,
errorEvent => {
logger.error(`... loading image failed: ${imageItem.url} : ${errorEvent}`);

imageItem.loadingPromise = undefined;
reject(`... loading image failed: ${imageItem.url} : ${errorEvent}`);
}
);
}
});

return imageItem.loadingPromise;
}

/**
* Find the cached {@link ImageItem} by URL.
*
Expand All @@ -238,7 +159,7 @@ export class ImageCache {
* @param imageItem - Item to cancel loading.
*/
private cancelLoading(imageItem: ImageItem) {
if (imageItem.loadingPromise !== undefined) {
if (imageItem.loading) {
// Notify that we are cancelling.
imageItem.cancelled = true;
}
Expand Down
11 changes: 1 addition & 10 deletions @here/harp-mapview/lib/image/MapViewImageCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class MapViewImageCache {
const url = urlOrImage;
const imageItem = this.registerImage(name, url);

return startLoading ? ImageCache.instance.loadImage(imageItem) : imageItem;
return startLoading ? imageItem.loadImage() : imageItem;
}

const image = urlOrImage;
Expand Down Expand Up @@ -119,15 +119,6 @@ export class MapViewImageCache {
return ImageCache.instance.findImage(url);
}

/**
* Load an {@link ImageItem}. Returns a promise or a loaded {@link ImageItem}.
*
* @param imageItem - ImageItem to load.
*/
loadImage(imageItem: ImageItem): ImageItem | Promise<ImageItem | undefined> {
return ImageCache.instance.loadImage(imageItem);
}

/**
* Remove all {@link ImageItem}s from the cache.
*
Expand Down
Loading

0 comments on commit cce64e6

Please sign in to comment.