[Tutorial] Shuffle TArray with RandomStream (generic blueprint node)

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 :).

I did a blog post detailing it and I want to avoid double posting so I’ll just paste the link here: http://masterkenth.com/ue4-shuffle-array-by-randomstream-c/.

For completeness sake though, here is the final code:

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|ArrayProperty"), Category = "Utility")
		static void ShuffleArrayWithStream(const TArray<int32>& TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream); // Stub function
 
		DECLARE_FUNCTION(execShuffleArrayWithStream)
		{
			Stack.StepCompiledIn<UArrayProperty>(NULL);
			void* ArrayAddr = Stack.MostRecentPropertyAddress;
 
			P_GET_OBJECT(UArrayProperty, ArrayProperty);
			P_GET_STRUCT_REF(FRandomStream, Stream);
			P_FINISH;
 
			ShuffleArrayWithStream_impl(ArrayAddr, ArrayProperty, Stream);
		}
};

Cpp file


#include "ProjectSVS.h"
#include "CppToBPFunctionLibrary.h"

void UCppToBPFunctionLibrary::ShuffleArrayWithStream(const TArray<int32>& TargetArray, const UArrayProperty* ArrayProperty, 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);
		}
	}
}

I needed same thing…Just stumbled across this…

Thanks for sharing

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);
		}
	}
}