UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

239 lines (238 loc) 7.31 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../../core/debug.js"; import { Asset } from "../../asset/asset.js"; import { GSplatAssetLoaderBase } from "../../../scene/gsplat-unified/gsplat-asset-loader-base.js"; class GSplatAssetLoader extends GSplatAssetLoaderBase { /** * Create a new GSplatAssetLoader. * * @param {AssetRegistry} registry - The asset registry to use for loading assets. */ constructor(registry) { super(); /** * Map of URL to Asset instances that this loader has created. * * @type {Map<string, Asset>} * @private */ __publicField(this, "_urlToAsset", /* @__PURE__ */ new Map()); /** * The asset registry to use for loading assets. * * @type {AssetRegistry} * @private */ __publicField(this, "_registry"); /** * Maximum number of assets that can be loading concurrently. * * @private */ __publicField(this, "maxConcurrentLoads", 2); /** * Maximum number of retry attempts for failed loads. * * @private */ __publicField(this, "maxRetries", 2); /** * Set of URLs currently being loaded. * * @type {Set<string>} * @private */ __publicField(this, "_currentlyLoading", /* @__PURE__ */ new Set()); /** * Queue of URLs waiting to be loaded. * * @type {string[]} * @private */ __publicField(this, "_loadQueue", []); /** * Map tracking retry attempts per URL. * * @type {Map<string, number>} * @private */ __publicField(this, "_retryCount", /* @__PURE__ */ new Map()); /** * Whether this asset loader has been destroyed. * * @private */ __publicField(this, "_destroyed", false); this._registry = registry; } /** * Destroys the asset loader and force-unloads all tracked assets, ignoring ref counts. * This is used when the octree resource itself is being destroyed. */ destroy() { this._destroyed = true; for (const asset of this._urlToAsset.values()) { asset.fire("unload", asset); asset.off("load"); asset.off("error"); this._registry.remove(asset); asset.unload(); } this._urlToAsset.clear(); this._loadQueue.length = 0; this._currentlyLoading.clear(); this._retryCount.clear(); } /** * Checks if the loader can start new loads. Returns false if the 'gsplat' handler * has been removed from the registry (e.g., during app destruction). * * @returns {boolean} True if loading is possible, false otherwise. * @private */ _canLoad() { return !!this._registry.loader?.getHandler("gsplat"); } /** * Initiates loading of a gsplat asset. This is a fire-and-forget operation that starts * the loading process. Use getResource() later to check if the asset has finished loading. * * @param {string} url - The URL of the gsplat file to load. */ load(url) { Debug.assert(url); const asset = this._urlToAsset.get(url); if (asset?.loaded || this._currentlyLoading.has(url)) { return; } if (this._loadQueue.includes(url)) { return; } if (this._currentlyLoading.size < this.maxConcurrentLoads) { this._startLoading(url); } else { this._loadQueue.push(url); } } /** * Starts loading an asset immediately. * * @param {string} url - The URL of the gsplat file to load. * @private */ _startLoading(url) { this._currentlyLoading.add(url); let asset = this._urlToAsset.get(url); if (!asset) { asset = new Asset(url, "gsplat", { url }); Debug.assert( !this._registry.getByUrl(url), `Asset with URL ${url} already exists in registry but not tracked by GSplatAssetLoader` ); this._registry.add(asset); this._urlToAsset.set(url, asset); } asset.once("load", () => this._onAssetLoadSuccess(url, asset)); asset.once("error", (err) => this._onAssetLoadError(url, asset, err)); if (!asset.loaded && !asset.loading) { this._registry.load(asset); } } /** * Called when an asset successfully loads. * * @param {string} url - The URL of the loaded asset. * @param {Asset} asset - The loaded asset. * @private */ _onAssetLoadSuccess(url, asset) { if (this._destroyed || !this._urlToAsset.has(url)) { return; } this._currentlyLoading.delete(url); this._retryCount.delete(url); this._processQueue(); } /** * Called when an asset fails to load. * * @param {string} url - The URL of the failed asset. * @param {Asset} asset - The asset that failed to load. * @param {string|Error} err - The error that occurred. * @private */ _onAssetLoadError(url, asset, err) { if (this._destroyed || !this._canLoad() || !this._urlToAsset.has(url)) { return; } const retryCount = this._retryCount.get(url) || 0; if (retryCount < this.maxRetries) { this._retryCount.set(url, retryCount + 1); asset.loaded = false; asset.loading = false; Debug.warn(`GSplatAssetLoader: Retrying load for ${url} (attempt ${retryCount + 1}/${this.maxRetries})`); this._registry.load(asset); } else { Debug.error(`GSplatAssetLoader: Failed to load ${url} after ${this.maxRetries} retries: ${err}`); this._currentlyLoading.delete(url); this._retryCount.delete(url); this._processQueue(); } } /** * Processes the next item in the load queue if there's capacity. * * @private */ _processQueue() { if (this._destroyed || !this._canLoad()) { return; } while (this._currentlyLoading.size < this.maxConcurrentLoads && this._loadQueue.length > 0) { const url = this._loadQueue.shift(); if (url) { this._startLoading(url); } } } /** * Unloads an asset that was previously loaded by this loader. The asset resource will be * destroyed and freed from memory. * * @param {string} url - The URL of the asset to unload. */ unload(url) { this._currentlyLoading.delete(url); const queueIndex = this._loadQueue.indexOf(url); if (queueIndex !== -1) { this._loadQueue.splice(queueIndex, 1); } this._retryCount.delete(url); const asset = this._urlToAsset.get(url); if (asset) { asset.fire("unload", asset); asset.off("load"); asset.off("error"); this._registry.remove(asset); asset.unload(); this._urlToAsset.delete(url); } this._processQueue(); } /** * Gets the resource for a given URL if it has been loaded by this loader. * Use this when you just need the loaded resource data. * * @param {string} url - The URL of the asset to retrieve the resource from. * @returns {object|undefined} The loaded resource if found and loaded, undefined otherwise. */ getResource(url) { const asset = this._urlToAsset.get(url); return asset?.resource; } } export { GSplatAssetLoader };