Hi all, I recently had the need to be able to shuffle an arbitrary array with FRandomStream to get deterministic ordering, however I couldn’t find a pre-built solution. So I spent a few hours studying the built-in array shuffle implementation and managed to create a blueprint node which takes a generic array as input. Throughout it I learned quite the bit about custom thunks and whatnot and I want to share this with everyone else in case you need to do something similar :).
hey, thanks, seems this is quite old but I need this, I’m using 4.26 and this line crashes in .cpp: FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
anyone has any ideas?
For those finding this later (CC @Tbjbu1), below are the updated changes in 4.26+. The changes are:
Remove ArrayProperty argument from the blueprint-exposed function
Re-port engine implementation for the DECLARE_FUNCTION generator block
Header File
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "CppToBPFunctionLibrary.generated.h"
UCLASS()
class PROJECTSVS_API UCppToBPFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
private:
static void ShuffleArrayWithStream_impl(void* TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream); // Real implementation
public:
UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Shuffle by Stream", CompactNodeTitle = "SHUFFLESTREAM", ArrayParm = "TargetArray"), Category = "Utility")
static void ShuffleArrayWithStream(const TArray<int32>& TargetArray, const FRandomStream& Stream); // Stub function
DECLARE_FUNCTION(execShuffleArrayWithStream)
{
// Implementation referenced from the original Shuffle Array implementation at
// Engine\Source\Runtime\Engine\Classes\Kismet\KismetArrayLibrary.h:334
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(NULL);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_STRUCT_REF(FRandomStream, Stream);
P_FINISH;
P_NATIVE_BEGIN;
// Commented out the below line from the original implementation
// because I didn't put the time into figuring out where the declaration
// of the macro is.
// MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
ShuffleArrayWithStream_impl(ArrayAddr, ArrayProperty, Stream);
P_NATIVE_END;
}
};
Source File
#include "ProjectSVS.h"
#include "CppToBPFunctionLibrary.h"
void UCppToBPFunctionLibrary::ShuffleArrayWithStream(const TArray<int32>& TargetArray, const FRandomStream& Stream)
{
UE_LOG(LogTemp, Error, TEXT("Stub shuffle func called - should not happen"));
check(0);
}
void UCppToBPFunctionLibrary::ShuffleArrayWithStream_impl(void* TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream)
{
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
int32 LastIndex = ArrayHelper.Num() - 1;
for (int32 i = 0; i < LastIndex; ++i)
{
int32 Index = Stream.RandRange(0, LastIndex);
if (i != Index)
{
ArrayHelper.SwapValues(i, Index);
}
}
}