diff --git a/README.md b/README.md index c440246..5458e47 100644 --- a/README.md +++ b/README.md @@ -18,30 +18,14 @@ Sample projects can be found under the @c /samples directory. Integrating Squid::Tasks ======================== -Including the Headers -------------------- -To integrate the Squid::Tasks library into your project, we recommend first copying the entire include directory into your project. You must then add the path of the include directory to the list of include directories in your project. +The steps for integrating Squid::Tasks into your game depends on how your game is built: -In __Visual Studio__, this is done by right-clicking your project and selecting Properties. Then navigate to Configuration Properties -> C/C++ -> General, and add the the path to the include directory to “Additional Include Directories”. + * [Integrating Squid::Tasks into an Unreal Engine 4 project](documentation/UE4IntegrationGuide.md) + * [Integrating Squid::Tasks into an Unreal Engine 5 project](documentation/UE4IntegrationGuide.md) + * [Integrating Squid::Tasks into a standalone Visual Studio project](documentation/StandaloneIntegrationGuide.md) -![Include Directory Settings in Visual Studio](images/setup01.png "Configuring Additional Include Directories in Visual Studio") - -Enabling Coroutines for C++14/17 (skip this step if using C++20) ----------------------------------------------------------------- -C++ coroutines were only formally added to the standard with C++20. In order to use them with earlier standards (C++14 or C++17), you must enable coroutines using a special compiler-specific compile flag. - -In __Visual Studio__, this is done by right-clicking your project and selecting Properties. Then navigate to Configuration Properties -> C/C++ -> Command Line, and add ```/await``` to “Additional Options”. - -(__IMPORTANT NOTE:__ If you are using C++17, you should instead add ```/await:strict``` to "Additional Options", as shown below.) - -![Enabling Coroutines in Visual Studio](images/setup02.png "Enabling Coroutines in Visual Studio") - -If you are using __Clang__, you will need to add -fcoroutines-ts to your compiler command-line compilation parameters. - -If you are using the __Clang Platform Toolset__ from within Visual Studio, you will need to add -Xclang -fcoroutines-ts to your compiler command-line compilation parameters. - -Configure Squid::Tasks with TasksConfig.h ------------------------------------------ +Configuring Squid::Tasks with TasksConfig.h +=========================================== The Squid::Tasks library can be configured in a variety of important ways. This is done by enabling and disabling preprocessor values within the include/TasksConfig.h file: - **SQUID_ENABLE_TASK_DEBUG**: Enables Task debug callstack tracking and debug names via Task::GetDebugStack() and Task::GetDebugName() diff --git a/documentation/StandaloneIntegrationGuide.md b/documentation/StandaloneIntegrationGuide.md new file mode 100644 index 0000000..7b3efe0 --- /dev/null +++ b/documentation/StandaloneIntegrationGuide.md @@ -0,0 +1,23 @@ +This guide will show you how to integrate Squid::Tasks into a standalone Visual Studio-based C++ project. + +Including the Headers +------------------- +To integrate the Squid::Tasks library into your project, we recommend first copying the entire include Squid::Tasks `include` directory into your project. + +You must then add the path of the include directory to the list of include directories in your project. In __Visual Studio__, this is done by right-clicking your project and selecting Properties. Then navigate to Configuration Properties -> C/C++ -> General, and add the the path to the include directory to “Additional Include Directories”. + +![Include Directory Settings in Visual Studio](images/setup01.png "Configuring Additional Include Directories in Visual Studio") + +Enabling Coroutines for C++14/17 (skip this step if using C++20) +---------------------------------------------------------------- +C++ coroutines were only formally added to the standard with C++20. In order to use them with earlier standards (C++14 or C++17), you must enable coroutines using a special compiler-specific compile flag. + +In __Visual Studio__, this is done by right-clicking your project and selecting Properties. Then navigate to Configuration Properties -> C/C++ -> Command Line, and add `/await:strict` to “Additional Options”. + +(__IMPORTANT NOTE:__ If you are using VS 16.10 or earlier, you should instead add `/await` to "Additional Options", as shown below.) + +![Enabling Coroutines in Visual Studio](images/setup02.png "Enabling Coroutines in Visual Studio") + +If you are using __Clang__, you will need to add -fcoroutines-ts to your compiler command-line compilation parameters. + +If you are using the __Clang Platform Toolset__ from within Visual Studio, you will need to add -Xclang -fcoroutines-ts to your compiler command-line compilation parameters. \ No newline at end of file diff --git a/documentation/UE4IntegrationGuide.md b/documentation/UE4IntegrationGuide.md new file mode 100644 index 0000000..f9f7a93 --- /dev/null +++ b/documentation/UE4IntegrationGuide.md @@ -0,0 +1,167 @@ +This guide will show you how to integrate Squid::Tasks into an Unreal Engine 4 game project. + +Prerequisites +------------- + +To use this guide, you must have: + + * An installation of Unreal Engine 4, built from source code. If you don't have this yet, [This Guide](https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/DownloadingSourceCode/) published by Epic will walk you through the process of downloading the source code, doing the initial setup, and compiling the source code. + * An Unreal Engine game project, configured to use C++. If you don't have this yet, [This Guide](https://docs.unrealengine.com/4.27/en-US/Basics/Projects/Browser/) published by Epic will guide you though the process of creating a new project. Remember to configure your project to use C++, not blueprints! + +Step 1: Enabling Compiler Support +--------------------------------- +(__NOTE: If you're compiling with C++20, you should skip this step and go directly to Step 2__. If you're not sure which language standard you're compiling with, it's best to assume you're using an earlier standard than C++20.) + +Before we can use C++ coroutines, we need to tell the compiler to enable support for coroutines. In Unreal Engine 5, this can be done easily through user configuration files, but in Unreal Engine 4, we will have to make a small edit to the engine source code. + +Find `/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs`, and look for the function `AppendCLArguments_Global`. + +We're going to add code to the beginning of this function so that it looks like this: + +```c# +protected virtual void AppendCLArguments_Global(CppCompileEnvironment CompileEnvironment, List Arguments) +{ + // Enable compilation of C++ coroutines. If we're using C++20, we don't need this + if (CompileEnvironment.CppStandard <= CppStandardVersion.Cpp17) + { + if (Target.WindowsPlatform.Compiler == WindowsCompiler.Clang) + { + Arguments.Add("-fcoroutines-ts"); + } + else + { + Arguments.Add("/await:strict"); + } + } + + // Leave the rest of AppendCLArguments_Global unchanged +``` + +Note that each platform has its own `___ToolChain.cs` file. For example, there's an `AndroidToolChain.cs`, a `LinuxToolChain.cs`, and a `TVOSToolChain.cs`, so if you intend to ship on multiple platforms, you will have to make similar changes in each of the files corresponding to your target platforms. + +One more note: The flag `/await:strict` is only fully supported in visual Studio 16.11 and newer. If you're using an older version of Visual Studio, use the `/await` flag instead. + +Step 2: Adding the Squid::Tasks Plugin +-------------------------------------- + +The Squid::Tasks source code is maintained in two different formats: One for standalone projects, and one as an Unreal Engine plugin. Since we're integrating into Unreal Engine 4, we'll use the Unreal Engine plugin. + + 1. Locate your game project's directory (It's the directory that contains the file `.uproject`). + 2. Within your project directory, find and open the directory called `Plugins`. If it doesn't exist yet, create it. + 3. In a separate window, open the SquidTasks source code and navigate into the `unreal/Plugins` directory. + 4. Copy the `SquidTasks` directory from the Squid::Tasks source into your project's `Plugins` directory. + +Step 3: Add SquidTasks as a dependency of our game module +--------------------------------------------------------- +Now that we've added our plugin, we need to tell our game's [module](https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/Modules/) to rely on it. + +The name of the file we need to edit is different from game to game. If our game project was called MyGame, then we'd need to edit a file called `MyGame.Build.cs`, and it will probably be located at `/Source/MyGame/MyGame.Build.cs`. More generally, the file will be called `_____.Build.cs`, and there may be more than one in your project. + +The contents of this file should look something like this, although again it will differ from game to game, and from module to module: + +```c# +using UnrealBuildTool; + +public class MyGame : ModuleRules +{ + public MyGame(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + } +} +``` + +To add a dependency to SquidTasks, all we need to do is add an entry to `PublicDependencyModuleNames` so that it looks like this: + +```c# +PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "SquidTasks" }); +``` + +Step 4: Write a Task +-------------------- +Squid::Tasks has now been integrated into our game module. To verify that it works, let's make a new actor and verify that tasks compile and run. + +In the same directory as the `____.Build.cs` file we just edited, make a new file called `TaskHelloWorld.h`, and paste in the following code: + +```c++ +#pragma once +#include "SquidTasks/Task.h" +#include "TaskHelloWorld.generated.h" + +UCLASS() +class ATaskHelloWorld : public AActor +{ + GENERATED_BODY() +public: + ATaskHelloWorld() { + PrimaryActorTick.bCanEverTick = true; + } + + virtual void BeginPlay() override { + Super::BeginPlay(); + MyTaskInstance = HelloWorldTask(); + } + + virtual void Tick(float DT) override { + Super::Tick(DT); + MyTaskInstance.Resume(); + } + +private: + Task<> MyTaskInstance; + + Task<> HelloWorldTask() { + UE_LOG(LogTemp, Log, TEXT("Hello world at the beginning of the task!")); + + co_await Suspend(); + + UE_LOG(LogTemp, Log, TEXT("Hello world on the next frame!")); + + while (true) { + UE_LOG(LogTemp, Log, TEXT("Hello world inside the while loop!")); + + co_await Suspend(); + } + } +}; +``` + +In this example code, the `HelloWorldTask()` member function is a C++ coroutine written using the Squid::Tasks library. + +In `BeginPlay()`, we call our `HelloWorldTask()` and store the task instance in the `MyTaskInstance` member variable. In `Tick()`, all we do is call `Resume()` on our stored task instance. + +We can now write stateful game code inside of `HelloWorldTask()`, and our actor will resume our task once per frame. + +Step 5: Test our Task +--------------------- + +To test our task, start up the Unreal Editor, and load the game project that contains your `ATaskHelloWorld` actor. + +First, open the "Place Actors" Window: + +![Navigating the UE4 Menu to find the Place Actors Window](images/PlaceActors_Dropdown_UE4.png "Navigating the UE4 Menu to find the Place Actors Window") + +Next, search for the "Task Hello World" actor, and drag one into the scene: + +![Searching for the Task Hello World actor within the Place Actors Window](images/PlaceActors_Dropdown_UE4.png "Searching for the Task Hello World actor within the Place Actors Window") + +In order to see the "hello" world text we're logging, we need to open the Output Log: + +![Navigating the UE4 Menu to find the Output Log Window](images/OutputLog_Dropdown_UE4.png "Navigating the UE4 Menu to find the Output Log Window") + +Finally, we're ready to hit Play to test our actor. We'll know it worked if we see this text in the Output Log: + +``` +LogTemp: Hello world at the beginning of the task! +LogTemp: Hello world on the next frame! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +``` + +Squid::Tasks has now been successfully integrated into our game project! \ No newline at end of file diff --git a/documentation/UE5IntegrationGuide.md b/documentation/UE5IntegrationGuide.md new file mode 100644 index 0000000..ac3f5c2 --- /dev/null +++ b/documentation/UE5IntegrationGuide.md @@ -0,0 +1,168 @@ +This guide will show you how to integrate Squid::Tasks into an Unreal Engine 4 game project. + +Prerequisites +------------- + +To use this guide, you must have: + + * An installation of Unreal Engine 5, built from source code. If you don't have this yet, [This Guide](https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/DownloadingSourceCode/) published by Epic will walk you through the process of downloading the source code, doing the initial setup, and compiling the source code. Note that this guide is for Unreal Engine 4. There isn't an official guide out for Unreal Engine 5 yet, because as of this writing UE5 has not officially released, so you'll need to do a little searching to find the UE5 source. + * An Unreal Engine game project, configured to use C++. If you don't have this yet, [This Guide](https://docs.unrealengine.com/4.27/en-US/Basics/Projects/Browser/) published by Epic will guide you though the process of creating a new project. Remember to configure your project to use C++, not blueprints! + +Step 1: Enabling Compiler Support +--------------------------------- +(__NOTE: If you're compiling with C++20, you should skip this step and go directly to Step 2__. If you're not sure which language standard you're compiling with, it's best to assume you're using an earlier standard than C++20.) + +Before we can use C++ coroutines, we need to tell the compiler to enable support for coroutines. In previous versions of Unreal, we would've needed to make edits to engine files, but in Unreal Engine 5, this can be done easily through user configuration files. Specifically, we will be editing the [Target](https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/BuildTools/UnrealBuildTool/TargetFiles/) files for our project. + +The name of the files we need to edit are different from game to game. If our game project was called MyGame, then we'd need to edit a file called `MyGame.Target.cs`, and a file called `MyGameEditor.Target.cs`, and they'll both probably be located at `/Source/MyGame[Editor].Target.cs` + +We'll document the process for `MyGameEditor.Target.cs`, and the process will be identical for `MyGame.Target.cs`. + +The contents of `MyGameEditor.Target.cs` should look something like this, although again it will differ from game to game: +```c# +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class MyGameEditorTarget : TargetRules +{ + public SurferEditorTarget( TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V2; + } +} +``` + +All we need to do is add a single line at the bottom of the constructor of `SurferEditorTarget`: + +`bEnableCppCoroutinesForEvaluation = true;` + +Once this is enabled, start a build. It may take a while, so it's a good idea to have this running while you work on the next steps. + + +Step 2: Adding the Squid::Tasks Plugin +-------------------------------------- + +The Squid::Tasks source code is maintained in two different formats: One for standalone projects, and one as an Unreal Engine plugin. Since we're integrating into Unreal Engine 5, we'll use the Unreal Engine plugin. + + 1. Locate your game project's directory (It's the directory that contains the file `.uproject`). + 2. Within your project directory, find and open the directory called `Plugins`. If it doesn't exist yet, create it. + 3. In a separate window, open the SquidTasks source code and navigate into the `unreal/Plugins` directory. + 4. Copy the `SquidTasks` directory from the Squid::Tasks source into your project's `Plugins` directory. + +Step 3: Add SquidTasks as a dependency of our game module +--------------------------------------------------------- +Now that we've added our plugin, we need to tell our game's [module](https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/Modules/) to rely on it. + +The name of the file we need to edit is different from game to game. If our game project was called MyGame, then we'd need to edit a file called `MyGame.Build.cs`, and it will probably be located at `/Source/MyGame/MyGame.Build.cs`. More generally, the file will be called `_____.Build.cs`, and there may be more than one in your project. + +The contents of this file should look something like this, although again it will differ from game to game, and from module to module: + +```c# +using UnrealBuildTool; + +public class MyGame : ModuleRules +{ + public MyGame(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + } +} +``` + +To add a dependency to SquidTasks, all we need to do is add an entry to `PublicDependencyModuleNames` so that it looks like this: + +```c# +PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "SquidTasks" }); +``` + +Step 4: Write a Task +-------------------- +Squid::Tasks has now been integrated into our game module. To verify that it works, let's make a new actor and verify that tasks compile and run. + +In the same directory as the `____.Build.cs` file we just edited, make a new file called `TaskHelloWorld.h`, and paste in the following code: + +```c++ +#pragma once +#include "SquidTasks/Task.h" +#include "TaskHelloWorld.generated.h" + +UCLASS() +class ATaskHelloWorld : public AActor +{ + GENERATED_BODY() +public: + ATaskHelloWorld() { + PrimaryActorTick.bCanEverTick = true; + } + + virtual void BeginPlay() override { + Super::BeginPlay(); + MyTaskInstance = HelloWorldTask(); + } + + virtual void Tick(float DT) override { + Super::Tick(DT); + MyTaskInstance.Resume(); + } + +private: + Task<> MyTaskInstance; + + Task<> HelloWorldTask() { + UE_LOG(LogTemp, Log, TEXT("Hello world at the beginning of the task!")); + + co_await Suspend(); + + UE_LOG(LogTemp, Log, TEXT("Hello world on the next frame!")); + + while (true) { + UE_LOG(LogTemp, Log, TEXT("Hello world inside the while loop!")); + + co_await Suspend(); + } + } +}; +``` + +In this example code, the `HelloWorldTask()` member function is a C++ coroutine written using the Squid::Tasks library. + +In `BeginPlay()`, we call our `HelloWorldTask()` and store the task instance in the `MyTaskInstance` member variable. In `Tick()`, all we do is call `Resume()` on our stored task instance. + +We can now write stateful game code inside of `HelloWorldTask()`, and our actor will resume our task once per frame. + +Step 5: Test our Task +--------------------- + +To test our task, start up the Unreal Editor, and load the game project that contains your `ATaskHelloWorld` actor. + +First, open the "Place Actors" Window: + +![Navigating the UE5 Menu to find the Place Actors Window](images/PlaceActors_Dropdown_UE5.png "Navigating the UE5 Menu to find the Place Actors Window") + +Next, search for the "Task Hello World" actor, and drag one into the scene: + +![Searching for the Task Hello World actor within the Place Actors Window](images/PlaceActors_Dropdown_UE5.png "Searching for the Task Hello World actor within the Place Actors Window") + +In order to see the "hello" world text we're logging, we need to open the Output Log: + +![Navigating the UE5 Menu to find the Output Log Window](images/OutputLog_Dropdown_UE5.png "Navigating the UE5 Menu to find the Output Log Window") + +Finally, we're ready to hit Play to test our actor. We'll know it worked if we see this text in the Output Log: + +``` +LogTemp: Hello world at the beginning of the task! +LogTemp: Hello world on the next frame! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +LogTemp: Hello world inside the while loop! +``` + +Squid::Tasks has now been successfully integrated into our game project! \ No newline at end of file diff --git a/documentation/images/OutputLog_Dropdown_UE4.png b/documentation/images/OutputLog_Dropdown_UE4.png new file mode 100644 index 0000000..27f2050 Binary files /dev/null and b/documentation/images/OutputLog_Dropdown_UE4.png differ diff --git a/documentation/images/OutputLog_Dropdown_UE5.png b/documentation/images/OutputLog_Dropdown_UE5.png new file mode 100644 index 0000000..2097a43 Binary files /dev/null and b/documentation/images/OutputLog_Dropdown_UE5.png differ diff --git a/documentation/images/PlaceActors_Dropdown_UE4.png b/documentation/images/PlaceActors_Dropdown_UE4.png new file mode 100644 index 0000000..0e3dcae Binary files /dev/null and b/documentation/images/PlaceActors_Dropdown_UE4.png differ diff --git a/documentation/images/PlaceActors_Dropdown_UE5.png b/documentation/images/PlaceActors_Dropdown_UE5.png new file mode 100644 index 0000000..9246cba Binary files /dev/null and b/documentation/images/PlaceActors_Dropdown_UE5.png differ diff --git a/documentation/images/PlaceActors_HelloWorld_UE4.png b/documentation/images/PlaceActors_HelloWorld_UE4.png new file mode 100644 index 0000000..f80e65b Binary files /dev/null and b/documentation/images/PlaceActors_HelloWorld_UE4.png differ diff --git a/documentation/images/PlaceActors_HelloWorld_UE5.png b/documentation/images/PlaceActors_HelloWorld_UE5.png new file mode 100644 index 0000000..2a9d35b Binary files /dev/null and b/documentation/images/PlaceActors_HelloWorld_UE5.png differ diff --git a/images/setup01.png b/documentation/images/setup01.png similarity index 100% rename from images/setup01.png rename to documentation/images/setup01.png diff --git a/images/setup02.png b/documentation/images/setup02.png similarity index 100% rename from images/setup02.png rename to documentation/images/setup02.png