playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
239 lines (238 loc) • 7.31 kB
JavaScript
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
};