Proper Map Hashing of structs in Blueprints without exposing internals

Hi sorry to bother I have a question regarding proper Map Hashing of structs in Blueprints without exposing internals

Currently I have a struct as shown below and as a test of this issue I have a function also shown below inside a UBlueprintFunctionLibrary

On The C++ side everything is working as expected.

TMap.Add calls the GetTypeHash function as expected and the C++ map grows every time CreateMyStructExample is called.

Meanwhile on the BP side when I try to add the FMyStructExample to the map GetTypeHash still gets called it prints the proper value on the C++ side, but the map only accepts one element and does not increase as I keep calling the Add function.

To summarize in this set up Blueprints call GetTypeHash when trying to add elements, internally in C++ the value called is correct based on the logs, but between that and actually adding the element something is preventing the element from being added.

Am I missing something or is this an engine bug?

`USTRUCT(BlueprintType)
struct MYPROJECT_API FMyStructExample
{
GENERATED_BODY()

FMyStructExample() = default;

explicit FMyStructExample( const uint32 InHandle ) : Handle( InHandle ) {}

bool IsValid() const { return Handle != 0; }

void Invalidate() { Handle = 0; }

bool operator==( const FMyStructExample& Other ) const
{
UE_LOG(LogTemp, Error, TEXT(“== overide called with %d vs %d”), Handle, Other.Handle);
return Handle == Other.Handle;
}

bool operator!=( const FMyStructExample& Other ) const
{
UE_LOG(LogTemp, Error, TEXT(“!= overide called with %d vs %d”), Handle, Other.Handle);
return !operator==( Other );
}

friend uint32 GetTypeHash( const FMyStructExample& InHandle )
{
UE_LOG(LogTemp, Error, TEXT(“HashFunction called with %d”), InHandle.Handle);
return InHandle.Handle;
}

FString ToString() const { return FString::Printf( TEXT( “%u” ), Handle ); }

private:
uint32 Handle = 0;
};

//…

UCLASS()
class UMyBPFunctions : public UBlueprintFunctionLibrary
{
GENERATED_BODY()

UFUNCTION(BlueprintCallable)
static FMyStructExample CreateMyStructExample(bool bAddToInternalMap, int& Size)
{
static int Counter = 0;
static TMap<FMyStructExample, int> Map;
const FMyStructExample Output = FMyStructExample(Counter++);

if (bAddToInternalMap)
{
Map.Add( Output, Map.Num() );
}

Size = Map.Num();
return Output;
}
};`

Steps to Reproduce

  • Open level and press play.
  • Press H
  • See the logs
  • Open the Level Blueprint and place a breakpoint on the Add function
  • Press H again in the level
  • Notice the new struct does not get added in the Map.

Hello, thanks for the repro project!

I see that the Handle of FMyStructExample isn’t marked as UPROPERTY. As a result that property value won’t be copied when the struct is copied in blueprint graphs. In blueprint when you pass the struct into a function (the map add), that struct will be copied but that copy has an uninitialized handle each time. I do believe when structs are copied the memory is zeroed out first so it will always have a value of zero.

If you mark the Handle as UPROPERTY that should fix your issue.

Glad to help!

Yes, that solved the issue I was under the impression I would not need to use UPROPERTY here because in the end the GetTypeHash should be called but as you said its probably being zeroed out during the copy.

Thank you so much for the swift answer!