playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
238 lines (237 loc) • 6.14 kB
JavaScript
import { path } from "../../core/path.js";
import { Tags } from "../../core/tags.js";
import { EventHandler } from "../../core/event-handler.js";
import { findAvailableLocale } from "../i18n/utils.js";
import { ABSOLUTE_URL } from "./constants.js";
import { AssetFile } from "./asset-file.js";
import { getApplication } from "../globals.js";
import { http } from "../../platform/net/http.js";
let assetIdCounter = -1;
const VARIANT_SUPPORT = {
pvr: "extCompressedTexturePVRTC",
dxt: "extCompressedTextureS3TC",
etc2: "extCompressedTextureETC",
etc1: "extCompressedTextureETC1",
basis: "canvas"
// dummy, basis is always supported
};
const VARIANT_DEFAULT_PRIORITY = ["pvr", "dxt", "etc2", "etc1", "basis"];
class Asset extends EventHandler {
static EVENT_LOAD = "load";
static EVENT_UNLOAD = "unload";
static EVENT_REMOVE = "remove";
static EVENT_ERROR = "error";
static EVENT_CHANGE = "change";
static EVENT_PROGRESS = "progress";
static EVENT_ADDLOCALIZED = "add:localized";
static EVENT_REMOVELOCALIZED = "remove:localized";
_file = null;
_i18n = {};
_preload = false;
_resources = [];
id = assetIdCounter--;
loaded = false;
loading = false;
options = {};
registry = null;
tags = new Tags(this);
type;
urlObject = null;
constructor(name, type, file, data = {}, options = {}) {
super();
this._name = name || "";
this.type = type;
this._data = data || {};
this.options = options || {};
if (file) this.file = file;
}
set name(value) {
if (this._name === value) {
return;
}
const old = this._name;
this._name = value;
this.fire("name", this, this._name, old);
}
get name() {
return this._name;
}
set file(value) {
if (value && value.variants && ["texture", "textureatlas", "bundle"].indexOf(this.type) !== -1) {
const app = this.registry?._loader?._app || getApplication();
const device = app?.graphicsDevice;
if (device) {
for (let i = 0, len = VARIANT_DEFAULT_PRIORITY.length; i < len; i++) {
const variant = VARIANT_DEFAULT_PRIORITY[i];
if (value.variants[variant] && device[VARIANT_SUPPORT[variant]]) {
value = value.variants[variant];
break;
}
if (app.enableBundles) {
const bundles = app.bundles.listBundlesForAsset(this);
if (bundles && bundles.find((b) => {
return b?.file?.variants[variant];
})) {
break;
}
}
}
}
}
const oldFile = this._file;
const newFile = value ? new AssetFile(value.url, value.filename, value.hash, value.size, value.opt, value.contents) : null;
if (!!newFile !== !!oldFile || newFile && !newFile.equals(oldFile)) {
this._file = newFile;
this.fire("change", this, "file", newFile, oldFile);
this.reload();
}
}
get file() {
return this._file;
}
set data(value) {
const old = this._data;
this._data = value;
if (value !== old) {
this.fire("change", this, "data", value, old);
if (this.loaded) {
this.registry._loader.patch(this, this.registry);
}
}
}
get data() {
return this._data;
}
set resource(value) {
const _old = this._resources[0];
this._resources[0] = value;
this.fire("change", this, "resource", value, _old);
}
get resource() {
return this._resources[0];
}
set resources(value) {
const _old = this._resources;
this._resources = value;
this.fire("change", this, "resources", value, _old);
}
get resources() {
return this._resources;
}
set preload(value) {
value = !!value;
if (this._preload === value) {
return;
}
this._preload = value;
if (this._preload && !this.loaded && !this.loading && this.registry) {
this.registry.load(this);
}
}
get preload() {
return this._preload;
}
set loadFaces(value) {
value = !!value;
if (!this.hasOwnProperty("_loadFaces") || value !== this._loadFaces) {
this._loadFaces = value;
if (this.loaded) {
this.registry._loader.patch(this, this.registry);
}
}
}
get loadFaces() {
return this._loadFaces;
}
getFileUrl() {
const file = this.file;
if (!file || !file.url) {
return null;
}
let url = file.url;
if (this.registry && this.registry.prefix && !ABSOLUTE_URL.test(url)) {
url = this.registry.prefix + url;
}
if (this.type !== "script" && file.hash) {
const separator = url.indexOf("?") !== -1 ? "&" : "?";
url += `${separator}t=${file.hash}`;
}
return url;
}
getAbsoluteUrl(relativePath) {
if (relativePath.startsWith("blob:") || relativePath.startsWith("data:")) {
return relativePath;
}
const base = path.getDirectory(this.file.url);
return path.join(base, relativePath);
}
getLocalizedAssetId(locale) {
locale = findAvailableLocale(locale, this._i18n);
return this._i18n[locale] || null;
}
addLocalizedAssetId(locale, assetId) {
this._i18n[locale] = assetId;
this.fire("add:localized", locale, assetId);
}
removeLocalizedAssetId(locale) {
const assetId = this._i18n[locale];
if (assetId) {
delete this._i18n[locale];
this.fire("remove:localized", locale, assetId);
}
}
ready(callback, scope) {
scope = scope || this;
if (this.loaded) {
callback.call(scope, this);
} else {
this.once("load", (asset) => {
callback.call(scope, asset);
});
}
}
reload() {
if (this.loaded) {
this.loaded = false;
this.registry.load(this);
}
}
unload() {
if (!this.loaded && this._resources.length === 0) {
return;
}
this.fire("unload", this);
this.registry.fire(`unload:${this.id}`, this);
const old = this._resources;
if (this.urlObject) {
URL.revokeObjectURL(this.urlObject);
this.urlObject = null;
}
this.resources = [];
this.loaded = false;
if (this.file) {
this.registry._loader.clearCache(this.getFileUrl(), this.type);
}
for (let i = 0; i < old.length; ++i) {
old[i]?.destroy?.();
}
}
static fetchArrayBuffer(loadUrl, callback, asset, maxRetries = 0) {
if (asset?.file?.contents) {
setTimeout(() => {
callback(null, asset.file.contents);
});
} else {
http.get(loadUrl, {
cache: true,
responseType: "arraybuffer",
retry: maxRetries > 0,
maxRetries,
progress: asset
}, callback);
}
}
}
export {
Asset
};