Implementing Multithreading in UE4

Implementing Multithreading in UE4

Implementing Multithreading in UE4

style

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:

mt inputs

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:

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 RateThis 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 RateThis 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.