SoftObjectPtrs are backed by a weak pointer, so if you load it, it can get unloaded any time by the garbage collector.
I wish there was some kind of LazyObjectPtr that automatically stays as a hard reference instead once loaded. Right now I’m having to have a second property to store the same pointer so it stays as a hard reference.
I could create a THardObjectPtr but then it’s hard to expose that as a UPROPERTY without heavily modifying UHT and things.
If you only load and unload from C++ and you only use one type, you could try what’s below (replace UWorld with your own type). I haven’t tested it though. Wish there was a way to make the functions accessible through BP, but you could make a helper for that I suppose.
You can move the implementation to the .cpp file and remove ‘inline’ keyword.
As long as the handle is alive, the asset will remain in memory. You’ll need to place an instance of FStreamableManager in your game instance or wherever you see fit (you’ll need to update the code below if you place it somewhere other than the game instance).
Or if Unreal itself had a HardObjectPtr type of thing built right into the API, all they’d have to do is change the SoftObjectPtr to a TObjectPtr and it would retain the reference forever.
Yeah, I hear ya. I tried a template and it works fine except you can’t use it as a property as you’ve mentioned above. So using a struct isn’t just to expose it to blueprints, but to allow it to be used as a property. I think you’d have to modify the source to get templates to work. It’d actually be relatively easy to modify TSoftObjectPtr I think. Otherwise, you need a custom FProperty implementation and I don’t know enough about that.
I’m reading that if you turn your asset into a PrimaryAsset, the asset manager will keep it in memory permanently until you manually unload it. You can call LoadPrimaryAsset() and UnloadPrimaryAsset() on the asset manager.
Levels (UWorld) are already primary assets. To turn secondary assets into primary assets, you need to override this in your UObject.
Then you need to register the base class in your Project Settings under Asset Manager.
Not sure, but this might do what you want.
edit: When you use LoadPrimaryAsset(), you get a FStreamableHandle that you can use to wait (and get the data, Get() on the soft pointer might return the proper type). But the docs say you don’t need to keep the handle to keep the asset in memory. The AssetManager automatically keeps a copy of it and you can query it later if you want it back.
Somewhat related to AlienRenders suggestion that you turn your asset into a PrimaryAsset (since that’s not always a possibility) would be using the StreamableManager.
Using the AssetManager you can get at the StreamableManager (UAssetManager::GetStreamableManager()). The StreamabaleManager has functions for doing asynchronous loads (RequestAsyncLoad) as well as synchronous loads (RequestSyncLoad). The useful part is that both of these functions return an FStreamableHandle that for as long as you keep it valid, the thing you loaded will stay loaded. When you don’t need the resource anymore you just reset it and it will unload eventually.
That may not sound that much better than just assigning the results of LoadSynchronous to your own property and for one property it might not be. Where it really starts to shine is that both of the loading functions have versions that take arrays and still return a single handle. So you can request a bunch of assets of different types and keep them loaded with a single member.
Another option if your soft pointer is on a primary asset instead of referring to one, would be the use of AssetBundles. This would allow you to request the loading of soft references (using the Asset Manager) that have some UPROPERTY markup. Similar to LoadPrimaryAsset, the AssetManager would keep those resources loaded for you until you unloaded the asset bundle.