Source: managers/BaseTextureManager.js

import { Utils } from '../common/Utils';
import { ImageFilterType, ImageWrapType } from '../const';

/**
 * @typedef LoadTextureResourceSamplerInfo
 * @property {ImageFilterType} minFilter The min filter type.
 * @property {ImageFilterType} magFilter The mag filter type.
 * @property {ImageWrapType} wrapS The horz wrap type.
 * @property {ImageWrapType} wrapT The vert wrap type.
 */

/**
 * When create texture.
 * @callback onCreateTextureCallback
 * @param {String} url The url.
 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
 * @returns {THING.BaseTexture}
 */

/**
 * When create texture in async mode.
 * @callback onCreateTextureAsyncCallback
 * @param {String} url The url.
 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
 * @returns {Promise<any>}
 */

const __ = {
	private: Symbol('private'),
}

const _defaultSampler = {
	minFilter: ImageFilterType.LinearMipmapLinearFilter,
	magFilter: ImageFilterType.LinearFilter,
	wrapS: ImageWrapType.Repeat,
	wrapT: ImageWrapType.Repeat
};

const _defaultOptions = {};

// #region Private Functions

// Build sampler key.
function _buildSamplerKey(sampler) {
	if (!sampler) {
		return '';
	}

	let minFilter = sampler.minFilter ? sampler.minFilter : _defaultSampler.minFilter;
	let magFilter = sampler.magFilter ? sampler.magFilter : _defaultSampler.magFilter;
	let wrapS = sampler.wrapS ? sampler.wrapS : _defaultSampler.wrapS;
	let wrapT = sampler.wrapT ? sampler.wrapT : _defaultSampler.wrapT;

	return `${minFilter};${magFilter};${wrapS};${wrapT}`;
}

// Build url key.
function _buildUrlKey(url, flipY) {
	if (Utils.isString(url)) {
		// gltf no url, so url is empty string
		if (url.length) {
			return url + ';' + flipY;
		}
		return '';
	}
	else if (Utils.isArray(url)) {
		return url.join(';') + flipY;
	}
	else {
		return '';
	}
}

// Get texture by sampler key.
function _getTexture(textures, samplerKey) {
	let texture = textures.get(samplerKey);
	if (!texture) {
		return null;
	}

	// Check whether it has been disposed
	if (texture.disposed) {
		textures.delete(samplerKey);

		return null;
	}

	return texture;
}

// Clear resources.
function _clearResources(resourcesMap) {
	resourcesMap.forEach(resources => {
		resources.forEach(resource => {
			resource.release();
		});
	});

	resourcesMap.clear();
}

// #endregion

/**
 * @class BaseTextureManager
 * The base texture manager.
 * @memberof THING
 */
class BaseTextureManager {

	/**
	 * The base texture manager.
	 * @private
	 */
	constructor() {
		/**
		 * When create texture.
		 * @member {OnCreateTextureCallback} onCreateTexture
		 * @memberof THING.BaseTextureManager
		 * @instance
		 * @private
		 */

		/**
		 * When create texture in async mode.
		 * @member {onCreateTextureAsyncCallback} onCreateTextureAsync
		 * @memberof THING.BaseTextureManager
		 * @instance
		 * @private
		 */

		this[__.private] = {};
		let _private = this[__.private];

		_private.texturesMap = new Map();
	}

	// #region Private

	_hookTexture(texture) {
		let _private = this[__.private];

		// Change map key when update sampler
		texture.onUpdateSampler = (scope, sampler) => {
			let urlKey = _buildUrlKey(scope.url, scope.flipY);

			// Delete the old texture cache
			let textures = _private.texturesMap.get(urlKey);
			if (!textures) {
				return;
			}

			for (let [key, texture] of textures) {
				if (texture == scope) {
					textures.delete(key);

					// Add texture with new sampler values into cache
					let samplerKey = _buildSamplerKey(sampler);
					textures.set(samplerKey, scope);

					return;
				}
			}
		};

		// Change map key when update url
		texture.onUpdateUrl = (scope) => {
			// Delete the old texture cache
			for (let [url, textures] of _private.texturesMap) {
				for (let [samplerKey, texture] of textures) {
					if (texture == scope) {
						textures.delete(samplerKey);

						let urlKey = _buildUrlKey(scope.url, scope.flipY);
						if (urlKey) {
							// Add texture with new url into cache
							let texturesCache = _private.texturesMap.get(urlKey);
							if (texturesCache) {
								texturesCache.set(samplerKey, scope);
							}
							else {
								texturesCache = new Map();
								texturesCache.set(samplerKey, scope);

								_private.texturesMap.set(urlKey, texturesCache);
							}
						}

						return;
					}
				}
			}
		};
	}

	_updateTexture(url, samplerKey, texture) {
		let _private = this[__.private];

		let textures = new Map();
		textures.set(samplerKey, texture);

		_private.texturesMap.set(url, textures);
	}

	_loadTexture(url, samplerKey, texture) {
		this._hookTexture(texture);

		this._updateTexture(url, samplerKey, texture);
	}

	_createTexture(url, urlKey, sampler, options) {
		let samplerKey = _buildSamplerKey(sampler);

		// Create texture
		if (this.onCreateTexture) {
			let texture = this.onCreateTexture(url, sampler, options);
			if (texture) {
				this._loadTexture(urlKey, samplerKey, texture);
			}

			return texture;
		}
		// Create texture in async mode
		else if (this.onCreateTextureAsync) {
			return this.onCreateTextureAsync(url, sampler, options).then((texture) => {
				if (texture) {
					this._loadTexture(urlKey, samplerKey, texture);
				}

				return texture;
			});
		}
		// Do not how to create texture, we should impl create texture interface at least
		else {
			return null;
		}
	}

	// #endregion

	/**
	 * Dispose.
	 * @private
	 */
	dispose() {
		let _private = this[__.private];

		_clearResources(_private.texturesMap);
	}

	/**
	 * Load texture from URL.
	 * @param {String} url The resource path.
	 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
	 * @param {Object} options The options.
	 * @returns {THING.BaseTexture|Promise<any>}
	 * @private
	 */
	load(url, sampler = _defaultSampler, options = _defaultOptions) {
		let _private = this[__.private];

		// Build the url key
		let urlKey = _buildUrlKey(url, Utils.parseValue(options['flipY'], true));
		if (!urlKey) {
			return null;
		}

		// Get cache map
		let textures = _private.texturesMap.get(urlKey);
		if (!textures) {
			return this._createTexture(url, urlKey, sampler, options);
		}

		// Build key for sampler info
		let samplerKey = _buildSamplerKey(sampler);
		let texture = _getTexture(textures, samplerKey);
		if (!texture) {
			return this._createTexture(url, urlKey, sampler, options);
		}

		// Hit cache !
		if (texture) {
			texture.addRef();

			return texture;
		}
	}

	/**
	 * Load texture from URL in async mode.
	 * @param {String} url The resource path.
	 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
	 * @param {Object} options The options.
	 * @returns {Promise<any>}
	 * @private
	 */
	loadAsync(url, sampler = _defaultSampler, options = _defaultOptions) {
		let texture = this.load(url, sampler, options);
		if (texture) {
			if (texture.isBaseTexture) {
				return texture.waitForComplete();
			}
			else {
				return texture.then((result) => {
					return result.waitForComplete();
				});
			}
		}
		else {
			return Promise.reject(`Load '${url}' texture failed`);
		}
	}

}

export { BaseTextureManager }