Hello,
I was wondering for some time now how I could create custom UE generic types like TArray<>.
To be specific I would like to create a :
- TOptional<> generic type to avoid sentinel values (-1, null, etc…).
TOptional seems to be doable since i don’t see a very big implementation difference with a TArray<>.
How would one start to implement this ? I remember to have read another post (here) that there would be a need for a type-erasure implementation that received UObject as arguments and that I would need to create a blueprint-compiled function / CustomThunk that uses those type-erased function ?
Do any of you have any experience or knowledge in this and direct me to a few ressources, failed/successful attempts or documentation / header files ?
Thanks in advance and have a great day !
Ps: if you also have some idea on how to implemente a TVariant<> on blueprint i am really interested but since its a templated-list of arguments i’d suppose you cant except by making a variant1, variant2, variant3, variant4, variant5, variant6, variant7, etc… implementation
EDIT 1 :
Right now i have understood how unreal engine generifies its function call but not how to create the data structure inside unreal itself
I recommend looking at Engine\Source\Runtime\Engine\Classes\Kismet\KismetArrayLibrary.h
Ex: adding a “RemoveFirst” Function
- Create a blueprintfunctionlibrary with the function Array_RemoveFirst(const TArray& TargetArray) and BlueprintCallable, CustomThunk, ArrayParm = “TargetArray” metaspecifier
// Note that const and int32 dont mean const and int32, i suppose they are simply placeholder - create an implementation that errors when its called, since we specified CustomThunk its going to call the following glue code :
- Create the CustomThunk glue code like the following which basically fetches array data (i suppose as blueprint represents it) and inserts them into a generic function which we are going to create
DECLARE_FUNCTION(execArray_Add)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(NULL);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_FINISH;
P_NATIVE_BEGIN;
*(bool*)RESULT_PARAM = GenericArray_RemoveFirst (ArrayAddr, ArrayProperty);
P_NATIVE_END;
}
- Create the declaration GenericArray_RemoveFirst but with different parameters, in our case void GenericArray_RemoveFirst (void* TargetArray, const FArrayProperty* ArrayProp);
- Create the function implementation like the following:
if(!TargetArray )
return;
FScriptArrayHelper ArrayHelper(ArrayProp, TargetArray);
if(!ArrayHelper.IsValidIndex(0))
{
FFrame::KismetExecutionMessage(
*FString::Printf(TEXT("Attempted to remove an item from an invalid index from array %s [/%d]!"),
*ArrayProp->GetName(), GetLastIndex(ArrayHelper)),
ELogVerbosity::Warning, RemoveOutOfBoundsWarning);
return;
}
ArrayHelper.RemoveValues(0, 1);
Looking forward from that knowledge, I believe I must continue looking into how to create a custom unreal engine type (such as FArrayProperty) and blueprint node (such as ArrayParm specifier)
EDIT 2 : Alright after debugging a lot in the generated cpp code, understanding a bit better how the interpreter works I seem to have found a interesting fact.
I checked deepter and deepter in definitions (thx Rider) and I tried to debug code from a Test Function with a const TArray<ATarget*>& parameter called form blueprint (inherited from APawn)
I believe that the interpreter’s Array is not the same as the c++ TArray, or at least is some way. Interpreter value are “script” reference by the XXXProperty mentionned above.
(ex: \Engine\Source\Runtime\Core\Public\Containers\ScriptArray.h)
And when you call a function, it create a native c++ array and copies its data from the script to the native (using CopyCompleteValueToScriptVM function) even when its a TArray reference). There might be a small doubt on the fact that it copies only the underlying pointer data but i wouldn’t believe that.
So basicaly, considering the initial problem, I believe that there are 2 problems
→ creating an optional script and being able to use it in blueprint
→ creating a bridge between it and the optional object