Hello !
I find myself with several cases where I want to have a collection of UObject*, created via a collection of TSubclassOf so that it’s data driven ( using Properties )
It works well with a TArray.
My issue is that to access an UObject of a particular subclass I need to iterate over the vector, like it’s done for components, and test IsA(). I would prefer an more direct access with a hash map, as accessing an object by class is what i need 90% of the time.
Here is an example of what i’ve tried.
.h
TMap<TSubclassOf<class UState>, UState*> StatesMap; ( also tried TMap<UClass*, UState*> )
.cpp
// creation, object are added to the map at some point
StatesMap.Add(state->GetClass(), state);
// Access
// stateID here is often something like myState::StaticClass()
UState* UStateManager::GetState(const TSubclassOf<class UState>& stateID)
{
if (StatesMap.Contains(stateID))
{
return StatesMap[stateID];
}
return nullptr;
}
Contains never returns true.
TSubclassOf has operators to be converted to and from an UClass*, so i think it’s ok on that front.
I also noticed that if I have a MyState state, then state->GetClass() == MyState::StaticClass() always return false which can be the issue.
So what can I change/add to make it work ?
Do I need to override the == operator for UClass* and the ToHash(UClass*) method ? ( could not remember the exact name )
Thanks !
AKiwi.
It looks like you are comparing your MyState class with a MyState subclass, which will fail. I’m actually trying to do the same thing as you, so I don’t yet have the answer. But that is why your comparison is failing.
Hello !
Like I said, SubClassOf can automatically be casted as a Class so the test is ok, Contains gets a class as a parameter.
After digging a little more, i noticed that if i create a data object ( blueprint ) inheriting from MyState, then its class object has a name that’s actually generated.
Basically the hierarchy becomes : UState → MyState → MyBlueprintState.
Then I end up comparing a MyDataState Class to a MyState class which returns false, which is ok I guess.
What I really need then is a way to compare a Class to the first c++ class in the hierachy when dealing with blueprinted UObject.
Okay, I got it working for my case, but with caveats.
struct FClassMapKey
{
TWeakObjectPtr<UClass> Class;
FClassMapKey(const UClass* InClass) : Class(InClass) {}
};
inline bool operator==(const FClassMapKey& KeyA, const FClassMapKey& KeyB)
{
UClass* ClassA = KeyA.Class.Get();
UClass* ClassB = KeyB.Class.Get();
if (ClassA == nullptr)
{
return (ClassB != nullptr);
}
else if (ClassB == nullptr)
{
return false;
}
return ClassB->IsChildOf(ClassA);
}
inline uint32 GetTypeHash(const FClassMapKey& Key)
{
return GetTypeHash(Key.Class);
}
TMap<FClassMapKey, uint32> ClassMap;
ClassMap.FindOrAdd(AActor::StaticClass()) = 0;
uint32* Value = ClassMap.Find(APawn::StaticClass); // returns 0
Value = ClassMap.Find(AActor::StaticClass); // returns 0
Value = ClassMap.Find(UActorComponent::StaticClass); // returns nullptr
ClassMap.FindOrAdd(APawn::StaticClass()) = 5;
Value = ClassMap.Find(APawn::StaticClass); // returns 5
Value = ClassMap.Find(AActor::StaticClass); // returns 5
I make a wrapper struct for the UClass* key which, when compared, will return true for subclasses. This means that if I add the AActor class, I can look it up with AActor or APawn (or any other AActor subclass).
I also got it to work using a custom KeyFuncs subclass of TDefaultMapKeyFuncs as a template parameter to TMap, but this made the TMap declaration terrible. Also, I wanted to store the UClass* keys as TWeakObjectPtrs, as the TMaps won’t be serialized. This is especially useful if you are storing classes from a module that may be hotloaded or for Blueprint UClasses that may get unloaded out from under you.