UNPKG

playcanvas

Version:

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

348 lines (347 loc) 9.85 kB
import { path } from "../../core/path.js"; import { Tracing } from "../../core/tracing.js"; import { TRACEID_ASSETS } from "../../core/constants.js"; import { EventHandler } from "../../core/event-handler.js"; import { TagsCache } from "../../core/tags-cache.js"; import { standardMaterialTextureParameters } from "../../scene/materials/standard-material-parameters.js"; import { Asset } from "./asset.js"; class AssetRegistry extends EventHandler { static EVENT_LOAD = "load"; static EVENT_ADD = "add"; static EVENT_REMOVE = "remove"; static EVENT_ERROR = "error"; _assets = /* @__PURE__ */ new Set(); _loader; _idToAsset = /* @__PURE__ */ new Map(); _urlToAsset = /* @__PURE__ */ new Map(); _nameToAsset = /* @__PURE__ */ new Map(); _tags = new TagsCache("id"); prefix = null; bundles = null; constructor(loader) { super(); this._loader = loader; } get loader() { return this._loader; } list(filters = {}) { const assets = Array.from(this._assets); if (filters.preload !== void 0) { return assets.filter((asset) => asset.preload === filters.preload); } return assets; } add(asset) { if (this._assets.has(asset)) return; this._assets.add(asset); this._idToAsset.set(asset.id, asset); if (asset.file?.url) { this._urlToAsset.set(asset.file.url, asset); } if (!this._nameToAsset.has(asset.name)) { this._nameToAsset.set(asset.name, /* @__PURE__ */ new Set()); } this._nameToAsset.get(asset.name).add(asset); asset.on("name", this._onNameChange, this); asset.registry = this; this._tags.addItem(asset); asset.tags.on("add", this._onTagAdd, this); asset.tags.on("remove", this._onTagRemove, this); this.fire("add", asset); this.fire(`add:${asset.id}`, asset); if (asset.file?.url) { this.fire(`add:url:${asset.file.url}`, asset); } if (asset.preload) { this.load(asset); } } remove(asset) { if (!this._assets.has(asset)) return false; this._assets.delete(asset); this._idToAsset.delete(asset.id); if (asset.file?.url) { this._urlToAsset.delete(asset.file.url); } asset.off("name", this._onNameChange, this); if (this._nameToAsset.has(asset.name)) { const items = this._nameToAsset.get(asset.name); items.delete(asset); if (items.size === 0) { this._nameToAsset.delete(asset.name); } } this._tags.removeItem(asset); asset.tags.off("add", this._onTagAdd, this); asset.tags.off("remove", this._onTagRemove, this); asset.fire("remove", asset); this.fire("remove", asset); this.fire(`remove:${asset.id}`, asset); if (asset.file?.url) { this.fire(`remove:url:${asset.file.url}`, asset); } return true; } get(id) { return this._idToAsset.get(Number(id)); } getByUrl(url) { return this._urlToAsset.get(url); } load(asset, options) { if ((asset.loading || asset.loaded) && !options?.force) { return; } const file = asset.file; const _fireLoad = () => { this.fire("load", asset); this.fire(`load:${asset.id}`, asset); if (file && file.url) { this.fire(`load:url:${file.url}`, asset); } asset.fire("load", asset); }; const _opened = (resource) => { if (resource instanceof Array) { asset.resources = resource; } else { asset.resource = resource; } this._loader.patch(asset, this); if (asset.type === "bundle") { const assetIds = asset.data.assets; for (let i = 0; i < assetIds.length; i++) { const assetInBundle = this._idToAsset.get(assetIds[i]); if (assetInBundle && !assetInBundle.loaded) { this.load(assetInBundle, { force: true }); } } if (asset.resource.loaded) { _fireLoad(); } else { this.fire("load:start", asset); this.fire(`load:start:${asset.id}`, asset); if (file && file.url) { this.fire(`load:start:url:${file.url}`, asset); } asset.fire("load:start", asset); asset.resource.on("load", _fireLoad); } } else { _fireLoad(); } }; const _loaded = (err, resource, extra) => { asset.loaded = true; asset.loading = false; if (err) { this.fire("error", err, asset); this.fire(`error:${asset.id}`, err, asset); asset.fire("error", err, asset); } else { if (asset.type === "script") { const handler = this._loader.getHandler("script"); if (handler._cache[asset.id] && handler._cache[asset.id].parentNode === document.head) { document.head.removeChild(handler._cache[asset.id]); } if (extra) { handler._cache[asset.id] = extra; } } _opened(resource); } }; if (file || asset.type === "cubemap") { this.fire("load:start", asset); this.fire(`load:${asset.id}:start`, asset); asset.loading = true; const fileUrl = asset.getFileUrl(); if (asset.type === "bundle") { const assetIds = asset.data.assets; for (let i = 0; i < assetIds.length; i++) { const assetInBundle = this._idToAsset.get(assetIds[i]); if (!assetInBundle) { continue; } if (assetInBundle.loaded || assetInBundle.resource || assetInBundle.loading) { continue; } assetInBundle.loading = true; } } this._loader.load(fileUrl, asset.type, _loaded, asset, options); } else { const resource = this._loader.open(asset.type, asset.data); asset.loaded = true; _opened(resource); } } loadFromUrl(url, type, callback) { this.loadFromUrlAndFilename(url, null, type, callback); } loadFromUrlAndFilename(url, filename, type, callback) { const name = path.getBasename(filename || url); const file = { filename: filename || name, url }; let asset = this.getByUrl(url); if (!asset) { asset = new Asset(name, type, file); this.add(asset); } else if (asset.loaded) { callback(asset.loadFromUrlError || null, asset); return; } const startLoad = (asset2) => { asset2.once("load", (loadedAsset) => { if (type === "material") { this._loadTextures(loadedAsset, (err, textures) => { callback(err, loadedAsset); }); } else { callback(null, loadedAsset); } }); asset2.once("error", (err) => { if (err) { this.loadFromUrlError = err; } callback(err, asset2); }); this.load(asset2); }; if (asset.resource) { callback(null, asset); } else if (type === "model") { this._loadModel(asset, startLoad); } else { startLoad(asset); } } // private method used for engine-only loading of model data _loadModel(modelAsset, continuation) { const url = modelAsset.getFileUrl(); const ext = path.getExtension(url); if (ext === ".json" || ext === ".glb") { const dir = path.getDirectory(url); const basename = path.getBasename(url); const mappingUrl = path.join(dir, basename.replace(ext, ".mapping.json")); this._loader.load(mappingUrl, "json", (err, data) => { if (err) { modelAsset.data = { mapping: [] }; continuation(modelAsset); } else { this._loadMaterials(modelAsset, data, (e, materials) => { modelAsset.data = data; continuation(modelAsset); }); } }); } else { continuation(modelAsset); } } // private method used for engine-only loading of model materials _loadMaterials(modelAsset, mapping, callback) { const materials = []; let count = 0; const onMaterialLoaded = (err, materialAsset) => { this._loadTextures(materialAsset, (err2, textures) => { materials.push(materialAsset); if (materials.length === count) { callback(null, materials); } }); }; for (let i = 0; i < mapping.mapping.length; i++) { const path2 = mapping.mapping[i].path; if (path2) { count++; const url = modelAsset.getAbsoluteUrl(path2); this.loadFromUrl(url, "material", onMaterialLoaded); } } if (count === 0) { callback(null, materials); } } // private method used for engine-only loading of the textures referenced by // the material asset _loadTextures(materialAsset, callback) { const textures = []; let count = 0; const data = materialAsset.data; if (data.mappingFormat !== "path") { callback(null, textures); return; } const onTextureLoaded = (err, texture) => { if (err) console.error(`Failed to load material texture for "${materialAsset.name}": ${err?.message ?? err}`, err); textures.push(texture); if (textures.length === count) { callback(null, textures); } }; const texParams = standardMaterialTextureParameters; for (let i = 0; i < texParams.length; i++) { const path2 = data[texParams[i]]; if (path2 && typeof path2 === "string") { count++; const url = materialAsset.getAbsoluteUrl(path2); this.loadFromUrl(url, "texture", onTextureLoaded); } } if (count === 0) { callback(null, textures); } } _onTagAdd(tag, asset) { this._tags.add(tag, asset); } _onTagRemove(tag, asset) { this._tags.remove(tag, asset); } _onNameChange(asset, name, nameOld) { if (this._nameToAsset.has(nameOld)) { const items = this._nameToAsset.get(nameOld); items.delete(asset); if (items.size === 0) { this._nameToAsset.delete(nameOld); } } if (!this._nameToAsset.has(asset.name)) { this._nameToAsset.set(asset.name, /* @__PURE__ */ new Set()); } this._nameToAsset.get(asset.name).add(asset); } findByTag(...query) { return this._tags.find(query); } filter(callback) { return Array.from(this._assets).filter((asset) => callback(asset)); } find(name, type) { const items = this._nameToAsset.get(name); if (!items) return null; for (const asset of items) { if (!type || asset.type === type) { return asset; } } return null; } findAll(name, type) { const items = this._nameToAsset.get(name); if (!items) return []; const results = Array.from(items); if (!type) return results; return results.filter((asset) => asset.type === type); } log() { } } export { AssetRegistry };