Skip to content

Commit

Permalink
Optimize AssetRegistry class (#5457)
Browse files Browse the repository at this point in the history
* Optimize AssetRegistry class

* Update src/framework/asset/asset-registry.js

Co-authored-by: Donovan Hutchence <slimbuck7@gmail.com>

* Update src/framework/asset/asset-registry.js

Co-authored-by: Donovan Hutchence <slimbuck7@gmail.com>

* if consistency

* null -> undefined

* Lint fixes

* Improve docs

* Restore return of null for find

---------

Co-authored-by: Donovan Hutchence <slimbuck7@gmail.com>
  • Loading branch information
willeastcott and slimbuck committed Jul 4, 2023
1 parent 63c3512 commit 7e68163
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 124 deletions.
213 changes: 90 additions & 123 deletions src/framework/asset/asset-registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ import { Asset } from './asset.js';
* @augments EventHandler
*/
class AssetRegistry extends EventHandler {
/**
* @type {Set<Asset>}
* @private
*/
_assets = new Set();

/**
* @type {Map<number, Asset>}
* @private
*/
_idToAsset = new Map();

/**
* @type {Map<string, Asset>}
* @private
*/
_urlToAsset = new Map();

/**
* Index for looking up by tags.
*
* @private
*/
_tags = new TagsCache('_id');

/**
* A URL prefix that will be added to all asset loading requests.
*
* @type {string|null}
*/
prefix = null;

/**
* Create an instance of an AssetRegistry.
*
Expand All @@ -43,19 +75,6 @@ class AssetRegistry extends EventHandler {
super();

this._loader = loader;

this._assets = []; // list of all assets
this._cache = {}; // index for looking up assets by id
this._names = {}; // index for looking up assets by name
this._tags = new TagsCache('_id'); // index for looking up by tags
this._urls = {}; // index for looking up assets by url

/**
* A URL prefix that will be added to all asset loading requests.
*
* @type {string}
*/
this.prefix = null;
}

/**
Expand Down Expand Up @@ -192,15 +211,12 @@ class AssetRegistry extends EventHandler {
* @param {object} filters - Properties to filter on, currently supports: 'preload: true|false'.
* @returns {Asset[]} The filtered list of assets.
*/
list(filters) {
filters = filters || {};
return this._assets.filter((asset) => {
let include = true;
if (filters.preload !== undefined) {
include = (asset.preload === filters.preload);
}
return include;
});
list(filters = {}) {
const assets = Array.from(this._assets);
if (filters.preload !== undefined) {
return assets.filter(asset => asset.preload === filters.preload);
}
return assets;
}

/**
Expand All @@ -214,20 +230,16 @@ class AssetRegistry extends EventHandler {
* app.assets.add(asset);
*/
add(asset) {
const index = this._assets.push(asset) - 1;
let url;

// id cache
this._cache[asset.id] = index;
if (!this._names[asset.name])
this._names[asset.name] = [];

// name cache
this._names[asset.name].push(index);
if (asset.file) {
url = asset.file.url;
this._urls[url] = index;
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);
}

asset.registry = this;

// tags cache
Expand All @@ -237,8 +249,9 @@ class AssetRegistry extends EventHandler {

this.fire('add', asset);
this.fire('add:' + asset.id, asset);
if (url)
this.fire('add:url:' + url, asset);
if (asset.file?.url) {
this.fire('add:url:' + asset.file.url, asset);
}

if (asset.preload)
this.load(asset);
Expand All @@ -254,79 +267,53 @@ class AssetRegistry extends EventHandler {
* app.assets.remove(asset);
*/
remove(asset) {
const idx = this._cache[asset.id];
const url = asset.file ? asset.file.url : null;
if (!this._assets.has(asset)) return false;

if (idx !== undefined) {
// remove from list
this._assets.splice(idx, 1);
this._assets.delete(asset);

// remove id -> index cache
delete this._cache[asset.id];

// name cache needs to be completely rebuilt
this._names = {};

// urls cache needs to be completely rebuilt
this._urls = [];

// update id cache and rebuild name cache
for (let i = 0, l = this._assets.length; i < l; i++) {
const a = this._assets[i];

this._cache[a.id] = i;
if (!this._names[a.name]) {
this._names[a.name] = [];
}
this._names[a.name].push(i);

if (a.file) {
this._urls[a.file.url] = i;
}
}
this._idToAsset.delete(asset.id);

// tags cache
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 (url)
this.fire('remove:url:' + url, asset);
if (asset.file?.url) {
this._urlToAsset.delete(asset.file.url);
}

return true;
// tags cache
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);
}

// asset not in registry
return false;
return true;
}

/**
* Retrieve an asset from the registry by its id field.
*
* @param {number} id - The id of the asset to get.
* @returns {Asset} The asset.
* @returns {Asset|undefined} The asset.
* @example
* const asset = app.assets.get(100);
*/
get(id) {
const idx = this._cache[id];
return this._assets[idx];
return this._idToAsset.get(id);
}

/**
* Retrieve an asset from the registry by its file's URL field.
*
* @param {string} url - The url of the asset to get.
* @returns {Asset} The asset.
* @returns {Asset|undefined} The asset.
* @example
* const asset = app.assets.getByUrl("../path/to/image.jpg");
*/
getByUrl(url) {
const idx = this._urls[url];
return this._assets[idx];
return this._urlToAsset.get(url);
}

/**
Expand Down Expand Up @@ -445,7 +432,7 @@ class AssetRegistry extends EventHandler {
* @param {LoadAssetCallback} callback - Function called when asset is loaded, passed (err,
* asset), where err is null if no errors were encountered.
* @example
* const file = magicallyAttainAFile();
* const file = magicallyObtainAFile();
* app.assets.loadFromUrlAndFilename(URL.createObjectURL(file), "texture.png", "texture", function (err, asset) {
* const texture = asset.resource;
* });
Expand Down Expand Up @@ -590,35 +577,6 @@ class AssetRegistry extends EventHandler {
}
}

/**
* Return all Assets with the specified name and type found in the registry.
*
* @param {string} name - The name of the Assets to find.
* @param {string} [type] - The type of the Assets to find.
* @returns {Asset[]} A list of all Assets found.
* @example
* const assets = app.assets.findAll("myTextureAsset", "texture");
* console.log("Found " + assets.length + " assets called " + name);
*/
findAll(name, type) {
const idxs = this._names[name];
if (idxs) {
const assets = idxs.map((idx) => {
return this._assets[idx];
});

if (type) {
return assets.filter((asset) => {
return (asset.type === type);
});
}

return assets;
}

return [];
}

_onTagAdd(tag, asset) {
this._tags.add(tag, asset);
}
Expand Down Expand Up @@ -659,13 +617,11 @@ class AssetRegistry extends EventHandler {
* Return `true` to include an asset in the returned array.
* @returns {Asset[]} A list of all Assets found.
* @example
* const assets = app.assets.filter(function (asset) {
* return asset.name.indexOf('monster') !== -1;
* });
* console.log("Found " + assets.length + " assets, where names contains 'monster'");
* const assets = app.assets.filter(asset => asset.name.includes('monster'));
* console.log(`Found ${assets.length} assets with a name containing 'monster'`);
*/
filter(callback) {
return this._assets.filter(asset => callback(asset));
return Array.from(this._assets).filter(asset => callback(asset));
}

/**
Expand All @@ -678,10 +634,21 @@ class AssetRegistry extends EventHandler {
* const asset = app.assets.find("myTextureAsset", "texture");
*/
find(name, type) {
// findAll returns an empty array the if the asset cannot be found so `asset` is
// never null/undefined
const asset = this.findAll(name, type);
return asset.length > 0 ? asset[0] : null;
return Array.from(this._assets).find(asset => asset.name === name && (!type || asset.type === type)) ?? null;
}

/**
* Return all Assets with the specified name and type found in the registry.
*
* @param {string} name - The name of the Assets to find.
* @param {string} [type] - The type of the Assets to find.
* @returns {Asset[]} A list of all Assets found.
* @example
* const assets = app.assets.findAll('brick', 'texture');
* console.log(`Found ${assets.length} texture assets named 'brick'`);
*/
findAll(name, type) {
return Array.from(this._assets).filter(asset => asset.name === name && (!type || asset.type === type));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/framework/asset/asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class Asset extends EventHandler {
/**
* The asset registry that this Asset belongs to.
*
* @type {import('./asset-registry.js').AssetRegistry}
* @type {import('./asset-registry.js').AssetRegistry|null}
*/
this.registry = null;

Expand Down

0 comments on commit 7e68163

Please sign in to comment.