Some users report the loss of some items. By inspecting their GameSave, i saw that some SoftClassPtr were serialized as “None” at some point.
Problem :
there’s no line in code where we set or reset Items
Only Items.AddUnique() with non null softclass and Items.Contains functions are present in our whole codebase.
I dig a lot into serialization and can’t find anything that could provoke an overwriting like this.
My first hypothesis was about having issue items not being loaded yet, or object renamed … but that do not affect serialization (right ?)
I thought about type change, but only new field appears in this game save (and even if order does not affect archive loading, order was not changed, all new fields were added after this one.
And also, this bug seems totally random and can appear at some point, without any update or stuff like this.
I’m thinking about changing type to serialize the path as FString and do the path creation on our side, but maybe this bug is not about SoftClassPtr.
I tried to find some related stuff into support/forum/bug tracker without any success.
TSoftClassPtr is basically a glorified FName with some helper functions and checks; it should serialize just fine. I don’t see a problem with your implementation, except that you’re using a generic base class (UObject) as the template, rather than a more specific one tied to your game, such as UInventoryItem (name made up).
The reason that stands out to me is that I have a suspicion: consider that TSoftClassPtr is designed specifically for instantiateable classes, NOT for Data Assets. It has some extra functionality to make sure that Blueprint classes are handled correctly (pointing to the UBlueprintGeneratedClass, ending with “_C”, instead of the Blueprint class itself, which only exists in Editor time).
TSoftObjectPtr should instead be used for Data Assets, as they are never instantiated. Might that be your problem?
sorry if my explanation are not clear enough, it’s not a question of resolve / load (or is it? )
And if i’m not mistaken, the TSoftClassPtr internal (specifically AssetPath) should never be impacted by resolution or loading.
And to be precise, we never resolve this array, the only code using this array is :
//loop over a list of "Buyable" object
//Buyable object has a buyableClass member of type TSoftClassPtr<UObject>
for(auto& Buyable : Buyables)
{
//settingsSubsystem : a GameInstance Subsystem loading gameplay save
// the gameplay save contains the array of soft ptr
if (settingsSubsystem->pGameplaySave->Items.Contains(Buyable->buyableClass))
{
AllBuyableArray.Add(Buyable);
}
}
But i also suspected that (maybe at some point, someone did the resolution then removed the code later)
In fact my first check was to completely delete an asset referenced in a save, then loading that save.
Got no overwriting of the internal path to None. (was in editor, but checked about code specific and nothing seems to be different in shipping)
i’m currently looking at possible data race since the only weird behavior i can see clearly right now, is loading/saving this save multiple time on a frame. (well to be precise : multiple call to Serialize)
I’m suspecting the same. I would add some guard code around the call to save the game, which would iterate through the array and assert if there are any None properties to try to identify the source of the issue.
Ok, so they’re serializing correctly. And only turn to null when you try to resolve them? They might just not be loaded.
When you try to resolve a soft class pointer, it will only resolve to a class if that class is already loaded. Otherwise, it will resolve to null. That’s on purpose, as the whole point of soft pointers is not to keep things loaded until you explicitly load them. You’ll need to load the class before you can instantiate it.
[Image Removed](For EDC: a picture of a soft class pointer being resolved, with a comment saying “this will be NULL if the class isn’t already loaded”)
This is how you should use it:
[Image Removed](For EDC: a picture of a soft pointer being a resolved, and then checked for IsValid to see if it’s loaded. If not, the Async Load Class Asset function is run to load it)
I see. No, you’re right, that’s not a question of a resolve/load.
You have a clear and simple usage that I see no reason for it to fail
It should get serialized to an array of strings (basically). Saving and loading that should just work.
I don’t think calling SaveGame multiple times should make a difference, it will immediately serialize the object to a byte array and finish the save before returning from the function. Are you maybe calling the Async version of SaveGame multiple times and the threads are overriding each other?