While our game may be running without any issues in the editor or even in…
Implementing Multithreading in UE4
In this post we’re going to see how easy it is to achieve multithreading inside UE4 using C++. Taken directly from the corresponding wikipedia page, multithreading is the ability of a CPU, to execute multiple processes or threads concurrently.
Modern software applications are designed in a way to inform the user at any time about their state. For example, if an application is stuck somewhere, it will most likely display a corresponding indication (ie a progress bar or a loading ring). Usually, this is done by dividing the logic of the application into two (or more) threads. The main thread is always responsible for the UI of the application, since it displays or hides all the progress indications, while the other threads perform their logic in the “background”.
Since the main thread in software applications is responsible for the UI, you can imagine that the main thread inside Unreal Engine 4 is the one responsible for the rendering. From this point on, we will refer to it as the game thread.
If you try to perform heavy operations inside the game thread, you will most likely experience a game freeze (depending on your PC and the calculations you’re performing). In this post, I’ve created a simple function which finds the first N prime numbers (N is specified through the Editor). Moreover, I’ve created two inputs – one of them calls that function in the game thread, while the other one calls the same function in a different thread.
Before we get started, here is the end result:
Setting up our project
For this post I’ve created a Third Person C++ Project template. Inside the character’s header file I’ve added the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
protected: /*Calculates prime numbers in the game thread*/ UFUNCTION(BlueprintCallable, Category = MultiThreading) void CalculatePrimeNumbers(); /*Calculates prime numbers in a background thread*/ UFUNCTION(BlueprintCallable, Category = MultiThreading) void CalculatePrimeNumbersAsync(); /*The max prime number*/ UPROPERTY(EditAnywhere, Category = MultiThreading) int32 MaxPrime; |
Then, inside the header file of the character’s class declaration and right after it’s implementation, I’ve added the following namespace, which contains the function that performs the actual calculations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
namespace ThreadingTest { static void CalculatePrimeNumbers(int32 UpperLimit) { //Calculating the prime numbers... for (int32 i = 1; i <= UpperLimit; i++) { bool isPrime = true; for (int32 j = 2; j <= i / 2; j++) { if (FMath::Fmod(i, j) == 0) { isPrime = false; break; } } if (isPrime) GLog->Log("Prime number #" + FString::FromInt(i) + ": " + FString::FromInt(i)); } } } |
Later on, we will add one more class inside the header file of the character but not inside the character’s class. We declared a namespace which contains the static function CalculatePrimeNumbers in order to be able to access the same function from different code classes.
Having said that, here is the implementation of the CalculatePrimeNumbers function:
1 2 3 4 5 6 7 8 9 10 11 |
void AMultiThreadingCharacter::CalculatePrimeNumbers() { //Performing the prime numbers calculations in the game thread... ThreadingTest::CalculatePrimeNumbers(MaxPrime); GLog->Log("--------------------------------------------------------------------"); GLog->Log("End of prime numbers calculation on game thread"); GLog->Log("--------------------------------------------------------------------"); } |
Add an empty implementation of the CalculatePrimeNumbersAsync function and compile and then your code. Then, specify two key binds using your character’s Blueprint like the following image suggests:
Creating a Task
When it comes to multithreading, you will hear a lot about Tasks. To simplify things, a Task is a piece of code which runs on any thread. We are going to create a new class which will execute the CalculatePrimeNumbers function, in another thread.
To add this class, we don’t have to use the normal Add a C++ Class workflow through the UE4 Editor. We will add our class by hand!
So, which class are we going to inherit this time? Well, we need to locate a class that provides some built-in functionality, in order to create and use a Task. I’ve already searched the source code of the engine and located the necessary class for our case so you don’t have to worry!
Right after the declaration of your namespace, add the following class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/*PrimeCalculateAsyncTask is the name of our task FNonAbandonableTask is the name of the class I've located from the source code of the engine*/ class PrimeCalculationAsyncTask : public FNonAbandonableTask { int32 MaxPrime; public: /*Default constructor*/ PrimeCalculationAsyncTask(int32 MaxPrime) { this->MaxPrime = MaxPrime; } /*This function is needed from the API of the engine. My guess is that it provides necessary information about the thread that we occupy and the progress of our task*/ FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(PrimeCalculationAsyncTask, STATGROUP_ThreadPoolAsyncTasks); } /*This function is executed when we tell our task to execute*/ void DoWork() { ThreadingTest::CalculatePrimeNumbers(MaxPrime); GLog->Log("--------------------------------------------------------------------"); GLog->Log("End of prime numbers calculation on background thread"); GLog->Log("--------------------------------------------------------------------"); } }; |
When you’re done with the above code, add the following implementation inside the CalculatePrimeNumberAsync:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void AMultiThreadingCharacter::CalculatePrimeNumbersAsync() { /*Create a new Task and pass as a parameter our MaxPrime Then, tell that Task to execute in the background. The FAutoDeleteAsyncTask will make sure to delete the task when it's finished. Multithreading requires cautious handle of the available threads, in order to avoid race conditions and strange bugs that are not easy to solve Fortunately, UE4 contains a class (FAutoDeleteAsyncTask) which handles everything by itself and the programmer is able to perform async operations without any real effort.*/ (new FAutoDeleteAsyncTask<PrimeCalculationAsyncTask>(MaxPrime))->StartBackgroundTask(); } |
Compile and test your code! Don’t forget to edit the MaxPrime variable through your Editor to have similar results to the video demonstrated above!
In case you got confused, here is the whole header file of my character class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
UCLASS(config=Game) class AMultiThreadingCharacter : public ACharacter { GENERATED_BODY() /** Camera boom positioning the camera behind the character */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) class USpringArmComponent* CameraBoom; /** Follow camera */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) class UCameraComponent* FollowCamera; public: AMultiThreadingCharacter(); /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera) float BaseTurnRate; /** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera) float BaseLookUpRate; protected: /** Called for forwards/backward input */ void MoveForward(float Value); /** Called for side to side input */ void MoveRight(float Value); /** * Called via input to turn at a given rate. * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate */ void TurnAtRate(float Rate); /** * Called via input to turn look up/down at a given rate. * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate */ void LookUpAtRate(float Rate); /** Handler for when a touch input begins. */ void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location); /** Handler for when a touch input stops. */ void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location); protected: // APawn interface virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override; // End of APawn interface public: /** Returns CameraBoom subobject **/ FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; } /** Returns FollowCamera subobject **/ FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; } protected: /*Calculates prime numbers in the game thread*/ UFUNCTION(BlueprintCallable, Category = MultiThreading) void CalculatePrimeNumbers(); /*Calculates prime numbers in a background thread*/ UFUNCTION(BlueprintCallable, Category = MultiThreading) void CalculatePrimeNumbersAsync(); /*The max prime number*/ UPROPERTY(EditAnywhere, Category = MultiThreading) int32 MaxPrime; }; namespace ThreadingTest { static void CalculatePrimeNumbers(int32 UpperLimit) { //Calculating the prime numbers... for (int32 i = 1; i <= UpperLimit; i++) { bool isPrime = true; for (int32 j = 2; j <= i / 2; j++) { if (FMath::Fmod(i, j) == 0) { isPrime = false; break; } } if (isPrime) GLog->Log("Prime number #" + FString::FromInt(i) + ": " + FString::FromInt(i)); } } } /*PrimeCalculateAsyncTask is the name of our task FNonAbandonableTask is the name of the class I've located from the source code of the engine*/ class PrimeCalculationAsyncTask : public FNonAbandonableTask { int32 MaxPrime; public: /*Default constructor*/ PrimeCalculationAsyncTask(int32 MaxPrime) { this->MaxPrime = MaxPrime; } /*This function is needed from the API of the engine. My guess is that it provides necessary information about the thread that we occupy and the progress of our task*/ FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(PrimeCalculationAsyncTask, STATGROUP_ThreadPoolAsyncTasks); } /*This function is executed when we tell our task to execute*/ void DoWork() { ThreadingTest::CalculatePrimeNumbers(MaxPrime); GLog->Log("--------------------------------------------------------------------"); GLog->Log("End of prime numbers calculation on background thread"); GLog->Log("--------------------------------------------------------------------"); } }; |
PS: In case you want to know more about Multithreading inside UE4, I suggest to locate the file AsyncWork.h inside the source code of the engine.
I’m waiting for the Blueprints version…will this ever be possible in Blueprints only (no C++ at all)?
Well I’m not seeing something on the UE4 roadmap so my guess is that we won’t see this in the near future. Considering the work that has been done in Blueprints so far I believe that a Blueprintable version of this post is do-able (technically speaking). Maybe an employee of Epic can provide you with a solid answer on that!
-Orfeas
Thanks for your reply. I really do believe Blueprints is awesome, but still very limited. Adding multithreading would simply take it to the next level, and as you’re saying, there’s no reason why it wouldn’t be possible from a technical perspective.
Does this need to be in a character class? I added it to an actor class that I want to use it for calculating noise value in the background. I dropped the UFunctions in the protected section in my header. The namepspace and PrimeCalculationAsyncTask stuff I dropped below outside the actor class. Intellisense doesn’t give me any trouble, but when I compile I see LNK2001 errors. External sumbol “Protected”
Nevermind, I forgot to add the implementation for the CalculatePrimeNumbersAsync
What did you do to make this work, I don’t understand the add implementation bit
It is a pity that we can not parallelize code containing the transformation of the position and rotation
In ue4 ,whether or not need us to consider c++11 feature like “atomic operation” ,“lock” and “competition” ?
I haven’t explored if the engine uses that under the hood.
So, if I can implement the features I want with the systems provided, I wouldn’t bother (unless something goes awry).
-Orfeas
Hello! I started finding information about multithreading and found myself here. Your tutorial is very clear and very well commented. Thank you for your effort. My question is, how is this so different than Ramas version? How would one know, which one to implement to project? This is the other version: https://wiki.unrealengine.com/Multi-Threading:_How_to_Create_Threads_in_UE4
It is much more longer and very different from this one. They are doing the same thing however: Finding prime number. I just got confused of what is the difference of two, could you clarify for it? Thank you very much!
I am also curious about this
How do you use EnsureCompletion() ?
Simply define it in the task?