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