X157 Dev Notes

One simulant attempts to share insight with others.

Things to know about FName

FName includes an optional instance number

Most FName in UE don’t contain numbers, but an optional number IS supported as a part of the FName.

Significantly, notice that the internal Number is always 1 greater than the number represented in the string. This allows for FName("Foo") to be distinctly different to FName("Foo_0").

The operator== considers both the PlainString part of the FName and the Number part of the name, such that FName("Foo_1") != FName("Foo_2")

If you want to compare only the PlainString part of the name, such that Foo_1 will be considered equivalent to Foo_2, the most efficient way to do so is to use GetComparisonIndex(), like:

if (FName("Foo_1").ComparisonIndex() == FName("Foo_2").ComparisonIndex())
{
    // This is true.
    // "Foo" == "Foo"; the "_1" and "_2" are ignored by ComparisonIndex()
}

Sample code

Run this code to help understand how FName handles the optional Number data:

FName Foo  ("Foo");     // same as FName("Foo", 0)
FName Foo1 ("Foo", 1);  // same as FName("Foo_0")
FName Foo2 ("Foo_1");   // same as FName("Foo", 2)

UE_LOG(LogTemp, Log, TEXT("Foo  PlainString=\"%s\", Number=%d, ToString=\"%s\""), *Foo.GetPlainNameString(), Foo.GetNumber(), *Foo.ToString());
UE_LOG(LogTemp, Log, TEXT("Foo1 PlainString=\"%s\", Number=%d, ToString=\"%s\""), *Foo1.GetPlainNameString(), Foo1.GetNumber(), *Foo1.ToString());
UE_LOG(LogTemp, Log, TEXT("Foo2 PlainString=\"%s\", Number=%d, ToString=\"%s\""), *Foo2.GetPlainNameString(), Foo2.GetNumber(), *Foo2.ToString());

// All these comparison methods include both the PlainString AND the Number
UE_LOG(LogTemp, Log, TEXT("Foo == Foo1 ? %s"), Foo == Foo1 ? TEXT("true") : TEXT("false"));

// Optimal way to compare JUST the PlainString part, ignoring the Number part
UE_LOG(LogTemp, Log, TEXT("Foo.GetComparisonIndex() == Foo1.GetComparisonIndex() ? %s"), Foo.GetComparisonIndex() == Foo1.GetComparisonIndex() ? TEXT("true") : TEXT("false"));

Result

LogTemp: Foo  PlainString="Foo", Number=0, ToString="Foo"
LogTemp: Foo1 PlainString="Foo", Number=1, ToString="Foo_0"
LogTemp: Foo2 PlainString="Foo", Number=2, ToString="Foo_1"

LogTemp: Foo == Foo1 ? false

LogTemp: Foo.GetComparisonIndex() == Foo1.GetComparisonIndex() ? true

Thanks!

Thanks to Dirtsleeper on the Unreal Source Discord for bringing this non-obvious behavior to my attention.