Source: asset/asset-reference.js

Object.assign(pc, function () {
    /**
     * @name pc.AssetReference
     * @description An object that manages the case where an object holds a reference to an asset and needs to be notified when
     * changes occur in the asset. e.g. notifications include load, add and remove events.
     * @param {String} propertyName The name of the property that the asset is stored under, passed into callbacks to enable updating.
     * @param {pc.Asset|Object} parent The parent object that contains the asset reference, passed into callbacks to enable updating. Currently an asset, but could be component or other.
     * @param {pc.AssetRegistry} registry The asset registry that stores all assets.
     * @param {Object} callbacks A set of functions called when the asset state changes: load, add, remove.
     * @param {Object} [callbacks.load] The function called when the asset loads load(propertyName, parent, asset).
     * @param {Object} [callbacks.add] The function called when the asset is added to the registry add(propertyName, parent, asset).
     * @param {Object} [callbacks.remove] The function called when the asset is remove from the registry remove(propertyName, parent, asset).
     * @param {Object} scope The scope to call the callbacks in
     * @property {Number} id Get or set the asset id which this references. One of either id or url must be set to initialize an asset reference.
     * @property {String} url Get or set the asset url which this references. One of either id or url must be called to initialize an asset reference.
     * @example
     *
     * var reference = new pc.AssetReference('textureAsset', this, this.app.assets, {
     *     load: this.onTextureAssetLoad,
     *     add: this.onTextureAssetAdd,
     *     remove: this.onTextureAssetRemove
     * }, this);
     * reference.id = this.textureAsset.id;
     */
    var AssetReference = function (propertyName, parent, registry, callbacks, scope) {
        this.propertyName = propertyName;
        this.parent = parent;

        this._scope = scope;
        this._registry = registry;

        this.id = null;
        this.url = null;
        this.asset = null;

        this._onAssetLoad = callbacks.load;
        this._onAssetAdd = callbacks.add;
        this._onAssetRemove = callbacks.remove;
    };

    AssetReference.prototype._bind = function () {
        if (this.id) {
            if (this._onAssetLoad) this._registry.on("load:" + this.id, this._onLoad, this);
            if (this._onAssetAdd) this._registry.once("add:" + this.id, this._onAdd, this);
            if (this._onAssetRemove) this._registry.on("remove:" + this.id, this._onRemove, this);
        }

        if (this.url) {
            if (this._onAssetLoad) this._registry.on("load:url:" + this.url, this._onLoad, this);
            if (this._onAssetAdd) this._registry.once("add:url:" + this.url, this._onAdd, this);
            if (this._onAssetRemove) this._registry.on("remove:url:" + this.url, this._onRemove, this);
        }
    };

    AssetReference.prototype._unbind = function () {
        if (this.id) {
            if (this._onAssetLoad) this._registry.off('load:' + this.id, this._onLoad, this);
            if (this._onAssetAdd) this._registry.off('add:' + this.id, this._onAdd, this);
            if (this._onAssetRemove) this._registry.off('remove:' + this.id, this._onRemove, this);
        }
        if (this.url) {
            if (this._onAssetLoad) this._registry.off('load:' + this.url, this._onLoad, this);
            if (this._onAssetAdd) this._registry.off('add:' + this.url, this._onAdd, this);
            if (this._onAssetRemove) this._registry.off('remove:' + this.url, this._onRemove, this);
        }
    };

    AssetReference.prototype._onLoad = function (asset) {
        this._onAssetLoad.call(this._scope, this.propertyName, this.parent, asset);
    };

    AssetReference.prototype._onAdd = function (asset) {
        this._onAssetAdd.call(this._scope, this.propertyName, this.parent, asset);
    };

    AssetReference.prototype._onRemove = function (asset) {
        this._onAssetRemove.call(this._scope, this.propertyName, this.parent, asset);
    };

    Object.defineProperty(AssetReference.prototype, 'id', {
        get: function () {
            return this._id;
        },
        set: function (value) {
            if (this.url) throw Error("Can't set id and url");

            this._unbind();

            this._id = value;
            this.asset = this._registry.get(this._id);

            this._bind();
        }
    });

    Object.defineProperty(AssetReference.prototype, 'url', {
        get: function () {
            return this._url;
        },
        set: function (value) {
            if (this.id) throw Error("Can't set id and url");

            this._unbind();

            this._url = value;
            this.asset = this._registry.getByUrl(this._url);

            this._bind();
        }
    });

    return {
        AssetReference: AssetReference
    };
}());