A proper way to load unloaded assets? Error

Hi there, I’m loading an assets trough loadFromUrlAndFilename

Unloading:

modelAsset = nearPlacements2[i4].model.asset;
        var asset2 = this.app.assets.get(modelAsset);
        console.log(asset2);
          asset2.unload();
         // entities2.removeComponent('model');
                 //   entities2.removeComponent('collision');
                            //  entities2.removeComponent('rigidbody');

Trying to load it again

 if (nearPlacements[i3].model) {
                console.log('asset found');
                 modelAssetLoad = nearPlacements[i3].model.asset;
        var asset3 = this.app.assets.get(modelAssetLoad);
        console.log(asset3);
       this.app.assets.load(asset3);

I can console.log the asset after unload - prior
Once trying to re-load [playcanvas.dbg.js?version=1.53.4:38935]: Cannot read properties of undefined (reading 'loading') or [[playcanvas.dbg.js?version=1.53.4:35745]](https://launch.playcanvas.com/editor/scene/js/engine/playcanvas.dbg.js?version=1.53.4): Cannot read properties of null (reading 'load')

What did I miss?

Hi @Newbie_Coder,

Are you sure asset3 is defined and it’s a valid Asset? From there error it seems PlayCanvas is complaining it’s undefined.

Check that line if it’s indeed an asset ID, since the line next to that expects an ID.

If it’s not an asset ID, but a regular asset, you can skip getting it and try loading it directly like this:

this.app.assets.load(modelAssetLoad);

Tried that, tried ._id, and got the same results

Try then sharing your project or a repro sample project.

Here:
https://playcanvas.com/project/936240/overview/test
V to unload
C to re-load
Similar results

The issue here is that the actual asset is the GLB container and all the materials, model, render assets etc are part of the container. You cannot reload individual assets in the container but the container asset itself.

It’s also worth noting that the model asset is created on demand (not documented as the model component is deprecated) when asset.resource.model is called and therefore when the container is reloaded, it needs to be assigned again to the model component and collision. I think this might be true for all the assets in the container thinking about it :thinking:

It would look more like this: https://playcanvas.com/project/936253/overview/f-asset-loading-test

var Test = pc.createScript('test');

// initialize code called once per entity
Test.prototype.initialize = function () {
    this.glbEntity = new pc.Entity('tempPlacement');
    this.glbEntity.tags.add('Placement');
    this.glbEntity.setPosition(0, 0, 0);
    pc.app.root.addChild(this.glbEntity);
    this.containerAsset = null;
    this.app.assets.loadFromUrlAndFilename('https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Lantern/glTF-Binary/Lantern.glb', null, "container", 
        function (err, asset) {
            this.glbEntity.addComponent('model', {
                asset: asset.resource.model
            });

            this.glbEntity.addComponent('collision', {
                type: 'mesh',
                asset: asset.resource.model
            });

            this.glbEntity.addComponent('rigidbody', {
                type: pc.BODYTYPE_STATIC,
                friction: 1,
                restitution: 0
            });

            this.containerAsset = asset;
        }.bind(this)
    );
};

// update code called every frame
Test.prototype.update = function (dt) {

    if (this.app.keyboard.wasReleased(pc.KEY_V)) {
        this.containerAsset.unload();
    }

    if (this.app.keyboard.wasReleased(pc.KEY_C)) {
        this.containerAsset.ready(function(asset) {
            this.glbEntity.model.asset = asset.resource.model;
            this.glbEntity.collision.asset = asset.resource.model;
        }, this);
        this.app.assets.load(this.containerAsset);
    }
};

// swap method called for script hot-reloading
// inherit your script state here
// Test.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// https://developer.playcanvas.com/en/user-manual/scripting/
2 Likes

Hey, this indeed works, the problem is that this is used on hundreds of entities, I’ve discovered that asset is not entity asset model and asset IDs are different

I set each entities container asset ID as a tag, and load or unload it if needed, unloading somewhat works, again loading is a problem

https://playcanvas.com/project/936243/overview/test2

var Test = pc.createScript('test');

// initialize code called once per entity
Test.prototype.initialize = function() {

 entity = new pc.Entity('tempPlacement');
  entity.tags.add('Placement');
  entity.setPosition(0,0,0);
   pc.app.root.addChild(entity);
    this.app.assets.loadFromUrlAndFilename('https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Lantern/glTF-Binary/Lantern.glb', null, "container", function (err, asset) {
        entity.addComponent('model', {
            asset: asset.resource.model
        });

            entity.addComponent('collision', {
        type: 'mesh',
         asset: asset.resource.model
    });
    
 entity.addComponent('rigidbody', {
        type: pc.BODYTYPE_STATIC,
        friction: 1,
        restitution: 0
    });
     entity.tags.add(String(asset.id));
     console.log(String(asset.id));
    });

};

// update code called every frame
Test.prototype.update = function(dt) {

 if(this.app.keyboard.wasReleased(pc.KEY_V) ){
       var placement = this.app.root.findByName('tempPlacement');
      containerAsset = placement.tags._list[1];
    //    modelAsset = placement.model.asset;
     var asset2 = this.app.assets.get(containerAsset);
       console.log(asset2);
          asset2.unload();
               //    placement.removeComponent('model');
               //  placement.removeComponent('collision');
                            //placement.removeComponent('rigidbody');
}


 if(this.app.keyboard.wasReleased(pc.KEY_C) ){
        var placement2 = this.app.root.findByName('tempPlacement');
                if (placement2.model) {
                containerAssetLoad = placement2.tags._list[1];
            //    console.log(containerAssetLoad);
          asset3 = this.app.assets.get(containerAssetLoad);

          this.app.assets.load(asset3);
          console.log(asset3);
                 //   console.log(placement2.model);
}
 }


};

We are near, loading does nothing, no errors
Console logs the right glb container asset id, I think the problem occurs with existing model and collision components in this case

I would store the asset id as a new property on the entity as it’s s lot easier to get again.

Same project: https://playcanvas.com/project/936253/overview/f-asset-loading-test

var Test = pc.createScript('test');

// initialize code called once per entity
Test.prototype.initialize = function () {
    this.glbEntity = new pc.Entity('tempPlacement');
    this.glbEntity.tags.add('Placement');
    this.glbEntity.setPosition(0, 0, 0);
    pc.app.root.addChild(this.glbEntity);
    
    this.app.assets.loadFromUrlAndFilename('https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Lantern/glTF-Binary/Lantern.glb', null, "container", 
        function (err, asset) {
            this.glbEntity.addComponent('model', {
                asset: asset.resource.model
            });

            this.glbEntity.addComponent('collision', {
                type: 'mesh',
                asset: asset.resource.model
            });

            this.glbEntity.addComponent('rigidbody', {
                type: pc.BODYTYPE_STATIC,
                friction: 1,
                restitution: 0
            });

            this.glbEntity.containerAssetId = asset.id;
        }.bind(this)
    );
};

// update code called every frame
Test.prototype.update = function (dt) {
    if (this.glbEntity.containerAssetId) {
        var asset = this.app.assets.get(this.glbEntity.containerAssetId);
        if (this.app.keyboard.wasReleased(pc.KEY_V)) {
            asset.unload();
        }

        if (this.app.keyboard.wasReleased(pc.KEY_C)) {
            asset.ready(function(asset) {
                this.glbEntity.model.asset = asset.resource.model;
                this.glbEntity.collision.asset = asset.resource.model;
            }, this);
            this.app.assets.load(asset);
        }
    }
};

// swap method called for script hot-reloading
// inherit your script state here
// Test.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// https://developer.playcanvas.com/en/user-manual/scripting/

Please check the code where I reload the container to see what you need to do to assign the model asset back to the entity components again.

This is it:

var nearPlacements = [];
var closestDistance = 30;
playerPosition = this.entity.getPosition();
  var placements = this.app.root.findByTag('Test');
 placements.forEach( function(placementsEntity){
        var distance = playerPosition.distance(placementsEntity.getPosition());
        if (distance < closestDistance) {
nearPlacements.push(placementsEntity);
        }
 }.bind(this));
if (nearPlacements.length == 0) {
} else {
        for (var i3 = 0; i3 < nearPlacements.length; i3++) {
          if (modelLoads == false) {
if (nearPlacements[i3].state == null) {
modelLoads = true;
entit = nearPlacements[i3];
      this.app.assets.loadFromUrlAndFilename(entit.tags._list[0], null, "container", function (err, asset) {
        entit.addComponent('model', {
            asset: asset.resource.model
        });

               entit.addComponent('collision', {
        type: 'mesh',
         asset: asset.resource.model
    });
    
   entit.addComponent('rigidbody', {
        type: pc.BODYTYPE_STATIC,
        friction: 1,
        restitution: 0
    });
     entit.state = 'Loaded';
     entit.containerasset = asset.id;
      modelLoads = false;
      console.log(entit.name);
        });
}

else if (nearPlacements[i3].state == 'Unloaded') {
entit2 = nearPlacements[i3];
console.log(entit2);
assetToReload = this.app.assets.get(entit2.containerasset);
modelLoads = true;
        entit2.state = 'Reloaded';
 assetToReload.ready(function(asset0) {
                entit2.model.asset = asset0.resource.model;
                entit2.collision.asset = asset0.resource.model;
        
            }, this);
            this.app.assets.load(assetToReload);
modelLoads = false;
}

}}}




var nearPlacements2 = [];
var closestDistance2 = 30;
playerPosition = this.entity.getPosition();
  var placements2 = this.app.root.findByTag('Test');
 placements2.forEach( function(placements2Entity){
        var distance2 = playerPosition.distance(placements2Entity.getPosition());
        if (distance2 > closestDistance2) {
nearPlacements2.push(placements2Entity);
        }
 }.bind(this));
if (nearPlacements2.length == 0) {
} else {
        for (var i4 = 0; i4 < nearPlacements2.length; i4++) {
          if (modelLoads == false) {
if (nearPlacements2[i4].state == 'Loaded' || nearPlacements2[i4].state == 'Reloaded') {
  entit = nearPlacements2[i4];
  console.log(nearPlacements2[i4]);
  entit.state = 'Unloaded';
  modelLoads = true;
  assetToUnload = this.app.assets.get(entit.containerasset);
  assetToUnload.unload();
  modelLoads = false;

}
}}}

Thanks for your help! All works as it should, elements get reloade from inspector cache, vram gets released upon unloading, just one more question, how to handle entities that uses same model container but are in different locations? Since unloading for one, unloads for all, or should I just add a random text value to the end of url like model.glb?random hoping the engine would create different container asset

I’m not sure I fully understand the question. Are you asking what to do if two entities share the same model asset from a container?

If that’s the case, it’s down to the developer to manage that. You could have a manager that handles all the external assets being loaded and it reference counts how many entities are using certain resources.

Once no entities are using assets from a container then the manager can unload it.

You could also do something more simpler such as adding a tag of the asset id of the container it is using to the entity and using findByTags to see how many entities are left in the scene. That will tell you how many references to the container are being used and you can use that to make the decision whether to unload the container or not.

1 Like

Thanks for your help @yaustar
All clear now!