Download

Implementing a custom generic array function for Blueprint like "Array Remove" requires CustomThunk

I need to remove some data from an array (the data is in sequence). So I like to have an Array::RemoveAt function that takes in a count of objects to remove.

Writing a standard UFUNCTION like


UFUNCTION(BlueprintCallable,...)
static void RemoveAtRange(TArray<int32>& Array, const int32 Index, const int32 Count);

within a library does not work at this point as it would fix the array element type (in this case to int32).

Having a look at Unreal implementation for generic array functions, we get:

KismetArrayLibrary.h

A UFUNCTION that creates the Blueprint node, but is not executed (note the UFUNCTION(…, CustomThunk,…)


    /*
     *Remove item at the given index from the array.
     *
     *@param    TargetArray        The array to remove from
     *@param    IndexToRemove    The index into the array to remove from
    */
    UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove Index", CompactNodeTitle = "REMOVE INDEX", ArrayParm = "TargetArray"), Category="Utilities|Array")
    static void Array_Remove(const TArray<int32>& TargetArray, int32 IndexToRemove)
    {
          check(0); // should never execute
     }

The actual function beeing executed by the CustomThunk (below) that does the removal and error checks



    static void GenericArray_Remove(void* TargetArray, const UArrayProperty* ArrayProp, int32 IndexToRemove);


And the user generated CustomThunk that operates on the StackFrame (FFrame in Stack.h)


    DECLARE_FUNCTION(execArray_Remove)
    {
        Stack.MostRecentProperty = nullptr;
        Stack.StepCompiledIn<UArrayProperty>(NULL);
        void* ArrayAddr = Stack.MostRecentPropertyAddress;
        UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);
        if (!ArrayProperty)
        {
            Stack.bArrayContextFailed = true;
            return;
        }

        P_GET_PROPERTY(UIntProperty, Index);
        P_FINISH;
        P_NATIVE_BEGIN;
        GenericArray_Remove(ArrayAddr, ArrayProperty, Index);
        P_NATIVE_END;
    }

My issue is the CustomThunk. There are also more complex CustomThunks for basically every other generic array function. Can anybody give some insight about those and the operations on the Stack?

The CustomThunk is basically what the BP VM would usually do for you when calling a function. In this case it’s popping parameters off the stack, and then calling a generic version of the function that operates on a type-erased version of the array via FScriptArrayHelper.

Your RemoveAtRange function would likely look similar to the Remove example, except that you’d have to pop off an extra property for your count.

As an aside, I’d like to replace CustomThunk with first-class support for creating generic functions in UHT. My personal motivation for this is that CustomThunk requires BP Bytecode, so won’t work with Python. They also don’t really work with BP nativization, as you can find nativized versions of every CustomThunk function in FCustomThunkTemplates (so you won’t be able to use BP navization if you add a your own CustomThunk function and don’t modify that struct).

Thank you Jamie. So you need to retrieve the function parameters from the stack in the order the function defines them with FFrame::StepCompiledIn.

I’ll take the cope for Swap and comment what I gues is going on or what I don’t understand.



    DECLARE_FUNCTION(execArray_Swap)
    {
        Stack.MostRecentProperty = nullptr; // Why must this be done?
        Stack.StepCompiledIn<UArrayProperty>(nullptr); // Steps over the first property, which is the array and sets MostRecentPropertyAddress to point to
// the property stepped over (the array). It is passed nullptr as we are not interessted in the property itself at this point.
// StepCompiledIn just executes Step in case property 'Code' is valid. What is CompiledIn?

        void* ArrayAddr = Stack.MostRecentPropertyAddress;      // Why not pass 'ArrayAddr' to StepCompiledIn?
        UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);  // Why not pass 'ArrayProperty' to StepCompiledIn?
        if (!ArrayProperty)
        {
            Stack.bArrayContextFailed = true;    // What is this flag, what is it used for? 
            return;
        }

       // P_GET_PROPERTY internally just declates a variable and calls StepCompiledIn to set that variable. Just a convenience macro.
        P_GET_PROPERTY(UIntProperty, First); // Pop the second function parameter
        P_GET_PROPERTY(UIntProperty, Second); // Pop the third function parameter
...
}