TMap FindChecked assertion with valid data

I have the following code set up in a UDataAsset subclass:

.h
UPROPERTY(EditDefaultsOnly)
TMap<USkill*, UMasteryLevel*> MaxSkillMastery;

.cpp
AssetRegistry.OnAssetAdded() {
    ...
    MaxSkillMastery.Add(Skill, nullptr);
    MaxSkillMastery.KeySort(Func);
}
AssetRegistry.OnAssetRemoved() {
    ...
    Iter.RemoveCurrent();
}

As objects of the Key type are created/removed, the map automatically updates itself to have 1 entry per object. The values are then set in a DataAsset in the editor. In my test case, I have 3 key objects and 3 value objects - in the DataAsset I assign 1 value per key.

227196-data-asset-map.jpg

I then have a unit test that runs in GameInstance::Init() with the following code:

UPROPERTY()
USkill* Chain;
UPROPERTY()
UMasteryLevel* Invalid;

static ConstructorHelpers::FObjectFinder<USkill> ChainFinder(TEXT("Skill'/Game/GameInfo/Skills/Chain.Chain'"));
Chain = ChainFinder.Object;

AssetRegistery.OnFilesLoaded() {
    ...
    Invalid = Cast<UMasteryLevel>(Asset.GetAsset());
}

I do this for all the skills/masteries, and the pointers are all valid and I can access them and everything is great.

Finally, during the unit test I make the following calls:

auto X = MaxSkillMastery[Alchemy]; // success
auto Y = MaxSkillMastery[Sword]; // success
auto Z = MaxSkillMastery[Chain]; // crash sometimes

Sometimes the call with Chain asserts false in FindChecked (the [] operator), sometimes it doesn’t. If I change the DataAsset in the editor from Invalid to Basic and back to Invalid, it seems to no longer crash, but some time later it will start crashing again consistently, until it decides it’s done crashing and it won’t do it again for hours. I’ve also ran the following code:

for (auto X : CharacterClass->MaxSkillMastery) {
    UE_LOG(LogTemp, Warning, TEXT("%p, %p, %i"), Skill, X.Key, X.Value->IntVal);
}

This code executes perfectly fine, and then immediately crash a few lines later when it gets to the [] operator. In fact, Skill and X.Key produce the same exact pointer value, and X.Value resolves properly without any nullptr issues. So I know that both the Key and Value are valid pointers, but for some reason FindChecked() is failing regardless.

Any thoughts at all would be appreciated.

ChainFinder.Object is the prototype of the class, it’s not supposed to be used. You’re supposed to create an instance of the class that you get back from FObjectFinder.

FYI there’s another option to FObjectFinder – which I used to myself before I learned about this: Change your Chain declaration to TSubclassOf<USkill>. It will show up in the Gameinstance blueprint where you can set it to what you want. Then you can use it in your C++ code.

Just like with FObjectFinder, you still need to create a new object from the class.

In this case, the skill is an immutable global object, so isn’t that what FObjectFinder is for? I’m not sure that explains why the map is asserting, since I was under the impression that the pointer itself would be hashed, so as long as the pointer is const, so too would the hash.

It looks like Chain is a BP derived from USkill. In that case you need to create an instance of Chain to use. FObjectFinder doesn’t instantiate a new object for you, it’s just returning the class of the BP at the path you provided.

Unless USkill is derived from UClass, I don’t see how what you’re doing now could be right. But I could be assuming something that isn’t true or not getting something on some level. If that’s the case, hopefully someone else can help.

That said, here’s what I’d do:

Add a member variable to the Gameinstance that is TSubclassOf<USkill>. Call it “ChainClass”. Open your Gameinstance blueprint and set that variable to the Chain BP class. Then in C++, use ChainClass to create the Chain object.

And if USkill is derived from UClass then you should still try what I’m suggesting: use TSubclassOf and set it in the BP. Then use that in your TMap. See if it works any better.

The “sometimes” crash may be because sometimes the UObject is loaded and sometimes is not;
Check to see if the Chain object is actually loaded:

if (ChainFinder.Succeeded()) {
        Chain = ChainFinder.Object;
} else {
        // meh! something wrong isn't right...
}