├── .gitignore ├── Config └── FilterPlugin.ini ├── Example01.png ├── Example02.png ├── Example03.png ├── Example04.png ├── Example05.png ├── LICENSE.MD ├── README.MD ├── Resources └── Icon128.png ├── SimpleAssetStreaming.uplugin └── Source └── SimpleAssetStreaming ├── Private ├── AssetStreamingCallback.cpp ├── AssetStreamingSubsystem.cpp └── SimpleAssetStreaming.cpp ├── Public ├── AssetHandlePair.h ├── AssetStreamingCallback.h ├── AssetStreamingSubsystem.h └── SimpleAssetStreaming.h └── SimpleAssetStreaming.Build.cs /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all folders but scan them 2 | * 3 | !*/ 4 | 5 | # Don't ignore git files 6 | !/.git* 7 | 8 | # Don't ignore lfs files. 9 | !/.lfsconfig 10 | 11 | # Don't ignore MD files 12 | !/*.MD 13 | 14 | # Don't ignore plugin files 15 | !SimpleAssetStreaming.uplugin 16 | 17 | # Don't ignore Config, Source and Resources 18 | !/Config/** 19 | !/Resources/** 20 | !/Source/** 21 | 22 | # Don't ignore .png files 23 | !*.png 24 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | -------------------------------------------------------------------------------- /Example01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erlite/UE4-SimpleAssetStreaming/fb0caa226bd0f97ac88a82cb50de570d062b6ca7/Example01.png -------------------------------------------------------------------------------- /Example02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erlite/UE4-SimpleAssetStreaming/fb0caa226bd0f97ac88a82cb50de570d062b6ca7/Example02.png -------------------------------------------------------------------------------- /Example03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erlite/UE4-SimpleAssetStreaming/fb0caa226bd0f97ac88a82cb50de570d062b6ca7/Example03.png -------------------------------------------------------------------------------- /Example04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erlite/UE4-SimpleAssetStreaming/fb0caa226bd0f97ac88a82cb50de570d062b6ca7/Example04.png -------------------------------------------------------------------------------- /Example05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erlite/UE4-SimpleAssetStreaming/fb0caa226bd0f97ac88a82cb50de570d062b6ca7/Example05.png -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Younes AIT AMER MEZIANE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Simple Asset Streaming 2 | Optimize your Unreal Engine 4 project's memory usage by implementing a simple request-and-release asset streaming solution, for Unreal Engine 4.22 and higher. 3 | 4 | Feel free to contribute to the plugin by opening issues or pull requests! A wiki will be added soon to remove all of the clutter below. 5 | ### What is asset streaming? 6 | Asset streaming is the technique of loading and unloading assets based on demand. This means you only load an asset when it is required, and unload it once nothing needs it. 7 | 8 | Once unloaded, the asset will be valid until garbage collected, which only happens when no references to the assets are active. By default, the GC collects once every minute. I request changing it to about three seconds during development for testing (**Project Settings -> Garbage Collection -> Time Between Purging Pending Kill Objects)**. 9 | 10 | ### Why use asset streaming? Doesn't UE4 already have it? 11 | Asset streaming reduces your memory usage since you won't just load all assets in memory at game launch. By default, UE4 loads all assets referenced by classes through direct references (i.e. pointers). While this means all assets can easily be accessed, imagine what happens when your classes reference multiple gigabytes of assets. 12 | 13 | ### Why use this over the AssetManager provided by UE4? 14 | It uses the same construct as the AssetManager: the StreamableManager. 15 | This is a simple wrapper around it, allowing your objects to "request" assets, and release them once they don't need it. It will automatically asynchronously load the asset the first time you request it and unload the asset once nothing references it. 16 | 17 | ### Alright, how do I install this? 18 | 19 | Clone this repository and copy the folder into the Plugins directory of your engine version: 20 | ``` 21 | C:\Program Files\Epic Games\UE_4.XX\Engine\Plugins\ 22 | ``` 23 | or your project's Plugins directory 24 | ``` 25 | \YourProject\Plugins\ 26 | ``` 27 | Update your project's `Build.cs` file by adding the `SimpleAssetStreaming` module: 28 | ```csharp 29 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "SimpleAssetStreaming" }); 30 | ``` 31 | You can now compile your project to gain access to the system in C++ and Blueprints. 32 | 33 | ## Preparing your project to use asset streaming. 34 | You'll need to modify how you reference your assets in order to implement asset streaming. It takes as little as one direct reference to an asset for it to be loaded immediately, making asset streaming useless for it. 35 | 36 | ### C++: using `TSoftObjectPtr<>` 37 | In C++, all references made to assets need to be wrapped using `TSoftObjectPtr`. 38 | ```cpp 39 | UPROPERTY(EditAnywhere, Category = "Item Assets") 40 | TSoftObjectPtr ItemIcon; 41 | 42 | UPROPERTY(EditAnywhere, Category = "Item Assets") 43 | TSoftObjectPtr ItemMesh; 44 | ``` 45 | These will appear in the editor as simple asset pickers depending on the type you provide to the soft object pointer. 46 | 47 | ### Blueprints: using soft object references. 48 | In Blueprints, all references made to assets need to be set to `Soft Object Reference`. 49 | 50 | ![Setting your blueprint asset references to soft object references](https://github.com/Erlite/UE4-SimpleAssetStreaming/blob/master/Example01.png) 51 | 52 | ### Accessing the AssetStreamingSubsystem 53 | Introduced in UE4.22, subsystems are classes that are automatically instantiated by the engine, and [their lifetime depends on their type.](https://docs.unrealengine.com/en-US/Programming/Subsystems/index.html) 54 | The `UAssetStreamingSubsystem` is a game instance subsystem, meaning it is instantiated right after the game instance, and unloaded right before the game instance is destroyed (i.e. your game is closing). 55 | 56 | You can access it in C++ by using the static singleton instance: 57 | ```cpp 58 | UAssetStreamingSubsystem* AssetStreaming = UAssetStreamingSubsystem::Get() 59 | ``` 60 | or through the game instance: 61 | ```cpp 62 | auto* AssetStreaming = GetGameInstance()->GetSubsystem(); 63 | ``` 64 | 65 | In Blueprints, the subsystem can simply be accessed using the `Get AssetStreamingSubsystem` node: 66 | 67 | ![Get AssetStreamingSubsystem node](https://github.com/Erlite/UE4-SimpleAssetStreaming/blob/master/Example02.png) 68 | 69 | ## Implementing asset streaming 70 | After you've prepared your project accordingly, it's time to implement the plugin. 71 | 72 | ### How it works: 73 | Let's say you have an item, that has an icon (`UTexture2D`) and a mesh (`UStaticMesh`). 74 | 75 | - When the item is created in the world, you tell the asset streaming subsystem you need these assets. 76 | - The asset streaming subsystem gives you a request guid. 77 | - When you don't need the assets anymore (i.e. the item is destroyed), you tell the system to release the assets attributed to the request guid it gave you. 78 | 79 | For objects that reference the same assets during their lifetime, you can use `BeginPlay` to request the assets and `BeginDestroy` to release them. 80 | 81 | For more complex scenarios, you can request and release assets as much as you want. 82 | 83 | ## C++ Implementation 84 | ### Requesting and releasing assets. 85 | You can request assets by using `UAssetStreamingSubsystem::RequestAssetStreaming()` with either a `TSoftObjectPtr<>` or a `TArray>`: 86 | ```cpp 87 | FGuid SingleRequestGuid; 88 | FGuid ArrayRequestGuid; 89 | if (auto* Subsystem = UAssetStreamingSubsystem::Get()) 90 | { 91 | Subsystem->RequestAssetStreaming(MySingleAsset, nullptr, SingleRequestGuid); 92 | Subsystem->RequestAssetStreaming(MyArrayOfAssets, nullptr, ArrayRequestGuid); 93 | } 94 | ``` 95 | These functions will return a boolean, true if the request was successful. If the request wasn't successful, the request guid will also be invalidated. 96 | Releasing assets is done by using `UAssetStreaming::ReleaseAssets()`, passing in the request guid. 97 | ```cpp 98 | Subsystem->ReleaseAssets(SingleRequestGuid); 99 | ``` 100 | The request guild will be invalidated, and the assets will be "released". They will not be unloaded if other objects need them. 101 | 102 | ### Getting a callback when an asset is loaded. 103 | 104 | You can get a callback when the asset is loaded by using the `IAssetStreamingCallback` interface, and passing it an object that implements it in your streaming request. 105 | ```cpp 106 | // Assuming 'this' implements the IAssetStreamingCallback interface. 107 | Subsystem->RequestAssetStreaming(MySingleAsset, this, MyRequestGuid); 108 | 109 | // IAssetStreamingCallback implementation 110 | void OnAssetLoaded_Implementation(const TSoftObjectPtr& LoadedAsset, const bool bAlreadyLoaded) 111 | { 112 | // Your asset is loaded, hurray! 113 | } 114 | ``` 115 | ## Blueprint Implementation 116 | ### Requesting and releasing assets. 117 | You can request assets by using the `Request Asset Streaming` node or `Request Multiple Asset Streaming`, that take a `Soft Object Reference` or an array of `Soft Object Reference` respectively. 118 | They will return a boolean (true on a successful request) and a request guid (invalid guid if the request wasn't successful). 119 | 120 | ![Request asset blueprint example](https://github.com/Erlite/UE4-SimpleAssetStreaming/blob/master/Example03.png) 121 | 122 | You can then release the assets by using the `Release Assets` node and the request guid that was given for the request. 123 | 124 | ![Release asset blueprint example](https://github.com/Erlite/UE4-SimpleAssetStreaming/blob/master/Example04.png) 125 | 126 | ### Getting a callback when an asset is loaded. 127 | You can implement the `AssetStreamingCallback` interface in your object's `Class Settings` and then use the `OnAssetLoaded` event which will be called for every asset requested by your object when they're loaded: 128 | 129 | ![Asset callback example](https://github.com/Erlite/UE4-SimpleAssetStreaming/blob/master/Example05.png) 130 | 131 | You'll need to use the nodes that have `w/Callback` in their name to pass in an asset streaming callback in the request. 132 | 133 | ## Do's and Dont's 134 | 135 | ### Do 136 | - Release assets when you don't need them anymore. It takes one reference to keep them active. 137 | - Check that the AssetStreamingSubsystem is valid, especially in `Destroyed/BeginDestroy` functions or events as these can be called when the game is closing and the subsystem will have been destroyed. 138 | - Make sure the request guid you received isn't invalid before trying to release assets. 139 | - Use the IAssetStreamingCallback interface to setup anything dependent on the loaded assets. 140 | 141 | ### Don't 142 | - Load a lot of assets for no reason. 143 | - Request/release assets for another object than your own. 144 | - Set the GC time before purging pending kill objects too low outside of testing, as it can cause hitches in performance. 145 | - Use assets without making a request, even if they're valid. They could be valid until the next GC cycle hits, which will cull them out of memory, unless a reference is made by the subsystem. 146 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erlite/UE4-SimpleAssetStreaming/fb0caa226bd0f97ac88a82cb50de570d062b6ca7/Resources/Icon128.png -------------------------------------------------------------------------------- /SimpleAssetStreaming.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Simple Asset Streaming", 6 | "Description": "Optimize your memory usage using this simple subsystem to stream assets in and out of memory only when needed.", 7 | "Category": "Performance", 8 | "CreatedBy": "Younes AIT AMER MEZIANE", 9 | "CreatedByURL": "https://github.com/Erlite/", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "Installed": false, 16 | "Modules": [ 17 | { 18 | "Name": "SimpleAssetStreaming", 19 | "Type": "Runtime", 20 | "LoadingPhase": "Default" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/Private/AssetStreamingCallback.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MIT License 4 | 5 | Copyright (c) 2020 Younes AIT AMER MEZIANE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | */ 26 | 27 | #include "AssetStreamingCallback.h" 28 | 29 | void IAssetStreamingCallback::OnAssetLoaded_Implementation(const TSoftObjectPtr& LoadedAsset, const bool bWasAlreadyLoaded) 30 | { 31 | } -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/Private/AssetStreamingSubsystem.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MIT License 4 | 5 | Copyright (c) 2020 Younes AIT AMER MEZIANE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | */ 26 | 27 | #include "AssetStreamingSubsystem.h" 28 | #include "AssetStreamingCallback.h" 29 | #include "Engine/World.h" 30 | #include "TimerManager.h" 31 | #include "UObject/SoftObjectPtr.h" 32 | #include "SimpleAssetStreaming.h" 33 | 34 | // Singleton instance initialization. 35 | UAssetStreamingSubsystem* UAssetStreamingSubsystem::Instance = nullptr; 36 | 37 | void UAssetStreamingSubsystem::Initialize(FSubsystemCollectionBase& Collection) 38 | { 39 | Super::Initialize(Collection); 40 | 41 | // Set the singleton instance to this instance. 42 | Instance = this; 43 | 44 | // Sanity check on the asset unload delay. 45 | if (UnloadDelaySeconds < 0.0f) 46 | { 47 | UE_LOG(LogAssetStreaming, Error, TEXT("UnloadDelaySeconds cannot be a negative number. Setting it to 5 seconds.")); 48 | UnloadDelaySeconds = 5.0f; 49 | } 50 | } 51 | 52 | void UAssetStreamingSubsystem::Deinitialize() 53 | { 54 | Instance = nullptr; 55 | } 56 | 57 | bool UAssetStreamingSubsystem::RequestAssetStreaming(const TArray>& AssetsToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId) 58 | { 59 | CheckThis(); 60 | 61 | // Invalidate the request id if there is nothing to stream. 62 | if (AssetsToStream.Num() == 0) 63 | { 64 | OutAssetRequestId.Invalidate(); 65 | return false; 66 | } 67 | 68 | // Assign a new guid to the request. 69 | OutAssetRequestId = FGuid::NewGuid(); 70 | 71 | UE_LOG(LogAssetStreaming, Verbose, TEXT("Request to stream %s asset(s) received. Request Id: %s"), *FString::FromInt(AssetsToStream.Num()), *OutAssetRequestId.ToString()); 72 | for (TSoftObjectPtr Asset : AssetsToStream) 73 | { 74 | StreamAsset(Asset, OutAssetRequestId, AssetLoadedCallback); 75 | } 76 | 77 | // Any asset streaming operation that passes assertions but still isn't valid will cause the request id to invalidate. 78 | return OutAssetRequestId.IsValid(); 79 | } 80 | 81 | bool UAssetStreamingSubsystem::RequestAssetStreaming(const TSoftObjectPtr& AssetToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId) 82 | { 83 | CheckThis(); 84 | 85 | // Assign a new guid to the request. 86 | OutAssetRequestId = FGuid::NewGuid(); 87 | StreamAsset(AssetToStream, OutAssetRequestId, AssetLoadedCallback); 88 | 89 | return OutAssetRequestId.IsValid(); 90 | } 91 | 92 | bool UAssetStreamingSubsystem::ReleaseAssets(FGuid& RequestId) 93 | { 94 | CheckThis(); 95 | 96 | if (!RequestId.IsValid()) 97 | { 98 | UE_LOG(LogAssetStreaming, Warning, TEXT("Attempted to release assets using an invalid Guid.")); 99 | return false; 100 | } 101 | 102 | if (!RegisteredAssets.Contains(RequestId)) 103 | { 104 | UE_LOG(LogAssetStreaming, Warning, TEXT("Attempted to release assets using id '%s' but it leads to no assets."), *RequestId.ToString()); 105 | RequestId.Invalidate(); 106 | return false; 107 | } 108 | 109 | FAssetHandleArray Assets = RegisteredAssets[RequestId]; 110 | 111 | // Decrement the amount of references to each of these assets. 112 | for (int Index = Assets.Num() - 1; Index >= 0; Index--) 113 | { 114 | FAssetHandlePair& Pair = Assets[Index]; 115 | TSoftObjectPtr Asset = Pair.Asset; 116 | FSoftObjectPath AssetPath = Asset.ToSoftObjectPath(); 117 | 118 | checkf(AssetRequestCount.Contains(AssetPath), TEXT("Attempted to release asset '%s' but we're not tracking it's count."), *Asset.GetAssetName()); 119 | checkf(!Asset.IsNull(), TEXT("Attempted to release null asset.")); 120 | checkf(Pair.Handle.Get(), TEXT("Asset handle is null.")); 121 | 122 | AssetRequestCount[AssetPath]--; 123 | 124 | // If we still have references to this asset, remove it from the array since we're going to re-use it to schedule unloading. 125 | if (AssetRequestCount[AssetPath] > 0) Assets.RemoveAt(Index); 126 | // If not, we just remove it from the asset request map. 127 | else AssetRequestCount.Remove(AssetPath); 128 | 129 | // If the handle of this asset isn't the one to keep alive, cancel it immediately. 130 | if (Pair.Handle != KeepAlive[AssetPath]) 131 | { 132 | UE_LOG(LogAssetStreaming, VeryVerbose, TEXT("Handle to release isn't keep-alive, cancelling it.")); 133 | Pair.Handle.Get()->CancelHandle(); 134 | } 135 | else 136 | { 137 | UE_LOG(LogAssetStreaming, VeryVerbose, TEXT("Handle to release is keep-alive, skipping it.")); 138 | } 139 | } 140 | 141 | // Remove the registered assets for this request id. 142 | RegisteredAssets.Remove(RequestId); 143 | 144 | // Any asset remaining in the assets array needs to be scheduled for unloading. 145 | if (Assets.Num() == 0) 146 | { 147 | UE_LOG(LogAssetStreaming, Verbose, TEXT("Finished releasing assets without any need for unloading. Request id: '%s'"), *RequestId.ToString()); 148 | RequestId.Invalidate(); 149 | return true; 150 | } 151 | 152 | // Schedule all assets for unloading. Unload will happen after a delay, to verify that no other objects need the assets one more time. 153 | ScheduleAssetUnloading(Assets); 154 | 155 | UE_LOG(LogAssetStreaming, Verbose, TEXT("Scheduled %s assets for unloading. Request id: '%s'"), *FString::FromInt(Assets.Num()), *RequestId.ToString()); 156 | RequestId.Invalidate(); 157 | 158 | return true; 159 | } 160 | 161 | bool UAssetStreamingSubsystem::K2_RequestMultipleAssetStreaming(const TArray>& AssetsToStream, FGuid& OutAssetRequestId) 162 | { 163 | return RequestAssetStreaming(AssetsToStream, nullptr, OutAssetRequestId); 164 | } 165 | 166 | bool UAssetStreamingSubsystem::K2_RequestMultipleAssetStreamingWithCallback(const TArray>& AssetsToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId) 167 | { 168 | return RequestAssetStreaming(AssetsToStream, AssetLoadedCallback, OutAssetRequestId); 169 | } 170 | 171 | bool UAssetStreamingSubsystem::K2_RequestAssetStreaming(const TSoftObjectPtr& AssetToStream, FGuid& OutAssetRequestId) 172 | { 173 | return RequestAssetStreaming(AssetToStream, nullptr, OutAssetRequestId); 174 | } 175 | 176 | bool UAssetStreamingSubsystem::K2_RequestAssetStreamingWithCallback(const TSoftObjectPtr& AssetToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId) 177 | { 178 | return RequestAssetStreaming(AssetToStream, AssetLoadedCallback, OutAssetRequestId); 179 | } 180 | 181 | bool UAssetStreamingSubsystem::K2_ReleaseAssets(UPARAM(Ref) FGuid& RequestId) 182 | { 183 | return ReleaseAssets(RequestId); 184 | } 185 | 186 | void UAssetStreamingSubsystem::StreamAsset(const TSoftObjectPtr& AssetToStream, const FGuid& RequestId, const TScriptInterface& AssetLoadedCallback) 187 | { 188 | checkf(!AssetToStream.IsNull(), TEXT("Attempted to stream null soft object pointer.")); 189 | 190 | // Request an asynchronous load of the asset, even if the asset is already loaded. We'll keep the handle. 191 | FStreamableDelegate OnLoaded; 192 | const bool bIsAssetLoaded = AssetToStream.IsValid(); 193 | OnLoaded.BindLambda([this, AssetToStream, AssetLoadedCallback, bIsAssetLoaded]() { HandleAssetLoaded(AssetToStream, AssetLoadedCallback, bIsAssetLoaded); }); 194 | TSharedPtr Handle = StreamableManager.RequestAsyncLoad(AssetToStream.ToSoftObjectPath(), OnLoaded, FStreamableManager::DefaultAsyncLoadPriority, true); 195 | 196 | // Register the asset and its handle to the request Id. 197 | RegisterAssetToId(AssetToStream, Handle, RequestId); 198 | 199 | // We need to keep one handle alive at all times so that we choose when to unload the asset. 200 | // To do this, we keep the first handle for each asset and never unload it until we really want to release the asset. 201 | if (!KeepAlive.Contains(AssetToStream.ToSoftObjectPath())) 202 | { 203 | KeepAlive.Add(AssetToStream.ToSoftObjectPath(), Handle); 204 | } 205 | 206 | // Increment the number of references for the asset. 207 | IncrementAssetReference(AssetToStream); 208 | } 209 | 210 | void UAssetStreamingSubsystem::RegisterAssetToId(const TSoftObjectPtr& Asset, const TSharedPtr Handle, const FGuid& Id) 211 | { 212 | FAssetHandlePair AssetPair = FAssetHandlePair(Asset, Handle); 213 | 214 | if (RegisteredAssets.Contains(Id)) 215 | { 216 | if (RegisteredAssets[Id].Contains(AssetPair)) 217 | { 218 | UE_LOG(LogAssetStreaming, Error, TEXT("Attempted to register asset '%s' to Id '%s' but it already exists there."), *Asset.GetAssetName(), *Id.ToString()); 219 | return; 220 | } 221 | 222 | RegisteredAssets[Id].Add(AssetPair); 223 | } 224 | else 225 | { 226 | FAssetHandleArray Array; 227 | Array.Add(AssetPair); 228 | RegisteredAssets.Add(Id, Array); 229 | } 230 | UE_LOG(LogAssetStreaming, Verbose, TEXT("Registered asset '%s' to Id '%s'."), *Asset.GetAssetName(), *Id.ToString()); 231 | } 232 | 233 | void UAssetStreamingSubsystem::IncrementAssetReference(const TSoftObjectPtr& Asset) 234 | { 235 | checkf(!Asset.IsNull(), TEXT("Cannot increment asset reference of null asset.")); 236 | FSoftObjectPath AssetPath = Asset.ToSoftObjectPath(); 237 | 238 | if (AssetRequestCount.Contains(AssetPath)) 239 | { 240 | AssetRequestCount[AssetPath]++; 241 | } 242 | else 243 | { 244 | AssetRequestCount.Add(AssetPath, 1); 245 | } 246 | } 247 | 248 | void UAssetStreamingSubsystem::HandleAssetLoaded(const TSoftObjectPtr& LoadedAsset, const TScriptInterface& AssetLoadedCallback, const bool& bAlreadyLoaded) 249 | { 250 | if (LoadedAsset.IsValid() && AssetLoadedCallback.GetObject()->IsValidLowLevel()) 251 | { 252 | IAssetStreamingCallback::Execute_OnAssetLoaded(AssetLoadedCallback.GetObject(), LoadedAsset, bAlreadyLoaded); 253 | } 254 | } 255 | 256 | void UAssetStreamingSubsystem::ScheduleAssetUnloading(const FAssetHandleArray& Assets) 257 | { 258 | if (Assets.Num() == 0) 259 | { 260 | UE_LOG(LogAssetStreaming, Warning, TEXT("Attempted to schedule asset unloading with an empty array.")); 261 | return; 262 | } 263 | 264 | FTimerManager& TimerManager = GetWorld()->GetTimerManager(); 265 | FTimerHandle Handle; 266 | FTimerDelegate Delegate; 267 | Delegate.BindLambda([this, Assets]() { FinalUnloadAssets(Assets); }); 268 | 269 | TimerManager.SetTimer(Handle, Delegate, UnloadDelaySeconds, false); 270 | } 271 | 272 | void UAssetStreamingSubsystem::FinalUnloadAssets(const FAssetHandleArray& Assets) 273 | { 274 | // If this somehow runs while the game is quitting, ignore. 275 | if (!this) return; 276 | 277 | int32 UnloadedAssetsCount = 0; 278 | for (FAssetHandlePair Pair : Assets) 279 | { 280 | TSoftObjectPtr Asset = Pair.Asset; 281 | checkf(!Asset.IsNull(), TEXT("Attempted to unload null asset pointer.")); 282 | 283 | FSoftObjectPath AssetPath = Asset.ToSoftObjectPath(); 284 | 285 | // Check if a new request to this asset was made. If so, we won't unload it. 286 | if (AssetRequestCount.Contains(AssetPath)) continue; 287 | 288 | UE_LOG(LogAssetStreaming, VeryVerbose, TEXT("Unloading asset '%s'."), *Asset.GetAssetName()); 289 | 290 | // Remove the handle from the KeepAlive array. 291 | KeepAlive.Remove(AssetPath); 292 | 293 | // Get the active handles for the asset and cancel them. Normally, we should only find one. 294 | // Cancelling will also stop them from completing if they haven't been loaded yet. The callback won't be called. 295 | TArray> ActiveHandles; 296 | if (StreamableManager.GetActiveHandles(AssetPath, ActiveHandles, true)) 297 | { 298 | for (TSharedRef Handle : ActiveHandles) 299 | { 300 | Handle.Get().CancelHandle(); 301 | } 302 | 303 | UnloadedAssetsCount++; 304 | } 305 | else 306 | { 307 | UE_LOG(LogAssetStreaming, Error, TEXT("Attempted to unload asset '%s' but no active handles were found. We should at least find one?"), *Asset.GetAssetName()); 308 | } 309 | } 310 | 311 | UE_LOG(LogAssetStreaming, Verbose, TEXT("Finally unloaded %s assets."), *FString::FromInt(UnloadedAssetsCount)); 312 | } 313 | -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/Private/SimpleAssetStreaming.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "SimpleAssetStreaming.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FSimpleAssetStreamingModule" 6 | 7 | void FSimpleAssetStreamingModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FSimpleAssetStreamingModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FSimpleAssetStreamingModule, SimpleAssetStreaming) 21 | DEFINE_LOG_CATEGORY(LogAssetStreaming); -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/Public/AssetHandlePair.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MIT License 4 | 5 | Copyright (c) 2020 Younes AIT AMER MEZIANE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | */ 26 | 27 | #pragma once 28 | 29 | #include "CoreMinimal.h" 30 | #include "Engine/StreamableManager.h" 31 | #include "AssetHandlePair.generated.h" 32 | 33 | USTRUCT() 34 | struct SIMPLEASSETSTREAMING_API FAssetHandlePair 35 | { 36 | GENERATED_BODY() 37 | 38 | public: 39 | 40 | FAssetHandlePair() 41 | : Asset(nullptr) 42 | , Handle(nullptr) 43 | {} 44 | 45 | FAssetHandlePair(const TSoftObjectPtr& InAsset, const TSharedPtr& InHandle) 46 | : Asset(InAsset) 47 | , Handle(InHandle) 48 | {} 49 | 50 | FORCEINLINE bool operator==(const FAssetHandlePair& RHS) const 51 | { 52 | return Asset == RHS.Asset; 53 | } 54 | 55 | TSoftObjectPtr Asset; 56 | 57 | TSharedPtr Handle; 58 | }; -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/Public/AssetStreamingCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MIT License 4 | 5 | Copyright (c) 2020 Younes AIT AMER MEZIANE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | */ 26 | 27 | #pragma once 28 | 29 | #include "CoreMinimal.h" 30 | #include "UObject/Interface.h" 31 | #include "AssetStreamingCallback.generated.h" 32 | 33 | class UObject; 34 | template struct TSoftObjectPtr; 35 | 36 | #define CheckThis() checkf(this, TEXT("Attempted to access asset streaming subsystem but it has already been destroyed. Check the pointer first.")) 37 | 38 | // This class does not need to be modified. 39 | UINTERFACE(MinimalAPI) 40 | class UAssetStreamingCallback : public UInterface 41 | { 42 | GENERATED_BODY() 43 | }; 44 | 45 | /** 46 | * Interface used to receive callbacks when an asset is loaded by the asset streaming subsystem. 47 | * Callbacks will be called each time a requested asset is loaded, or directly if it was already loaded. 48 | */ 49 | class SIMPLEASSETSTREAMING_API IAssetStreamingCallback 50 | { 51 | GENERATED_BODY() 52 | 53 | public: 54 | 55 | UFUNCTION(BlueprintNativeEvent, DisplayName = "On Asset Loaded") 56 | void OnAssetLoaded(const TSoftObjectPtr& LoadedAsset, const bool bWasAlreadyLoaded); 57 | virtual void OnAssetLoaded_Implementation(const TSoftObjectPtr& LoadedAsset, const bool bWasAlreadyLoaded); 58 | }; -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/Public/AssetStreamingSubsystem.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MIT License 4 | 5 | Copyright (c) 2020 Younes AIT AMER MEZIANE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | */ 26 | 27 | #pragma once 28 | 29 | #include "CoreMinimal.h" 30 | #include "AssetHandlePair.h" 31 | #include "Engine/StreamableManager.h" 32 | #include "Subsystems/GameInstanceSubsystem.h" 33 | #include "AssetStreamingSubsystem.generated.h" 34 | 35 | class UObject; 36 | class IAssetStreamingCallback; 37 | typedef TArray FAssetHandleArray; 38 | typedef TArray> FStreamableHandleArray; 39 | 40 | /** 41 | * Subsystem used to asynchronously load and unload assets when required. 42 | */ 43 | UCLASS() 44 | class SIMPLEASSETSTREAMING_API UAssetStreamingSubsystem : public UGameInstanceSubsystem 45 | { 46 | GENERATED_BODY() 47 | 48 | public: 49 | 50 | UAssetStreamingSubsystem() 51 | : StreamableManager() 52 | , UnloadDelaySeconds(5.0f) // Modify this to change the delay before assets are finally unloaded. Cannot be negative. 53 | , RegisteredAssets() 54 | , AssetRequestCount() 55 | , KeepAlive() 56 | {} 57 | 58 | // Returns the singleton instance of the asset streaming subsystem. 59 | FORCEINLINE static UAssetStreamingSubsystem* Get() { return Instance; } 60 | 61 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 62 | 63 | virtual void Deinitialize() override; 64 | 65 | /** 66 | * Request streaming of multiple assets. 67 | * Each asset will be streamed one by one. 68 | * @param AssetsToStream The assets to asynchronously stream. 69 | * @param AssetLoadedCallback The callback to call when an asset is loaded. Will be called once by asset loaded. 70 | * @param OutAssetRequestId The request id assigned for your request. Use it to release the assets you streamed. Invalidated if the request was invalid. 71 | * @returns True if the request was successful. 72 | */ 73 | bool RequestAssetStreaming(const TArray>& AssetsToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId); 74 | 75 | /** 76 | * Request streaming of a single asset. 77 | * @param AssetToStream The asset to asynchronously stream. 78 | * @param AssetLoadedCallback The callback to call when an asset is loaded. 79 | * @param OutAssetRequestId The request id assigned for your request. Use it to release the asset you streamed. Invalidated if the request was invalid. 80 | * @returns True if the request was successful. 81 | */ 82 | bool RequestAssetStreaming(const TSoftObjectPtr& AssetToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId); 83 | 84 | /** 85 | * Release the asset you streamed. 86 | * Warning: must be called when you don't need the streamed assets anymore! 87 | * @param RequestId The id returned by the streaming request. Will be invalidated after releasing assets. 88 | * @returns True if releasing was successful. 89 | */ 90 | bool ReleaseAssets(FGuid& RequestId); 91 | 92 | protected: 93 | 94 | /** 95 | * Request streaming of multiple assets. 96 | * Each asset will be streamed one by one. 97 | * @param AssetsToStream The assets to asynchronously stream. 98 | * @param OutAssetRequestId The request id assigned for your request. Use it to release the assets you streamed. Invalidated if the request was invalid. 99 | * @returns True if the request was successful. 100 | */ 101 | UFUNCTION(BlueprintCallable, DisplayName = "Request Multiple Assets", Category = "Asset Streaming Functions") 102 | bool K2_RequestMultipleAssetStreaming(const TArray>& AssetsToStream, FGuid& OutAssetRequestId); 103 | 104 | /** 105 | * Request streaming of multiple assets. 106 | * Each asset will be streamed one by one. 107 | * @param AssetsToStream The assets to asynchronously stream. 108 | * @param AssetLoadedCallback The callback to call when an asset is loaded. Will be called once by asset loaded. 109 | * @param OutAssetRequestId The request id assigned for your request. Use it to release the assets you streamed. Invalidated if the request was invalid. 110 | * @returns True if the request was successful. 111 | */ 112 | UFUNCTION(BlueprintCallable, DisplayName = "Request Multiple Assets w/Callback", Category = "Asset Streaming Functions") 113 | bool K2_RequestMultipleAssetStreamingWithCallback(const TArray>& AssetsToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId); 114 | 115 | /** 116 | * Request streaming of a single asset. 117 | * @param AssetToStream The asset to asynchronously stream. 118 | * @param OutAssetRequestId The request id assigned for your request. Use it to release the asset you streamed. Invalidated if the request was invalid. 119 | * @returns True if the request was successful. 120 | */ 121 | UFUNCTION(BlueprintCallable, DisplayName = "Request Asset Streaming", Category = "Asset Streaming Functions") 122 | bool K2_RequestAssetStreaming(const TSoftObjectPtr& AssetToStream, FGuid& OutAssetRequestId); 123 | 124 | 125 | /** 126 | * Request streaming of a single asset. 127 | * @param AssetToStream The asset to asynchronously stream. 128 | * @param AssetLoadedCallback The callback to call when an asset is loaded. 129 | * @param OutAssetRequestId The request id assigned for your request. Use it to release the asset you streamed. Invalidated if the request was invalid. 130 | * @returns True if the request was successful. 131 | */ 132 | UFUNCTION(BlueprintCallable, DisplayName = "Request Asset Streaming w/Callback", Category = "Asset Streaming Functions") 133 | bool K2_RequestAssetStreamingWithCallback(const TSoftObjectPtr& AssetToStream, const TScriptInterface& AssetLoadedCallback, FGuid& OutAssetRequestId); 134 | 135 | /** 136 | * Release the asset you streamed. 137 | * Warning: must be called when you don't need the streamed assets anymore! 138 | * @param RequestId The id returned by the streaming request. Will be invalidated. 139 | * @returns True if releasing was successful. 140 | */ 141 | UFUNCTION(BlueprintCallable, DisplayName = "Release Assets", Category = "Asset Streaming Functions") 142 | bool K2_ReleaseAssets(UPARAM(Ref) FGuid& RequestId); 143 | 144 | private: 145 | 146 | void StreamAsset(const TSoftObjectPtr& AssetToStream, const FGuid& RequestId, const TScriptInterface& AssetLoadedCallback); 147 | 148 | void RegisterAssetToId(const TSoftObjectPtr& Asset, const TSharedPtr Handle, const FGuid& Id); 149 | 150 | void IncrementAssetReference(const TSoftObjectPtr& Asset); 151 | 152 | void HandleAssetLoaded(const TSoftObjectPtr& LoadedAsset, const TScriptInterface& AssetLoadedCallback, const bool& bAlreadyLoaded); 153 | 154 | void ScheduleAssetUnloading(const FAssetHandleArray& Assets); 155 | 156 | void FinalUnloadAssets(const FAssetHandleArray& Assets); 157 | 158 | // Singleton instance. 159 | static UAssetStreamingSubsystem* Instance; 160 | 161 | // The manager used to load/unload assets from memory. 162 | FStreamableManager StreamableManager; 163 | 164 | // The amount of time to wait before finally unloading an asset when its references drop to zero. 165 | float UnloadDelaySeconds; 166 | 167 | // Maps request guid to the requested assets and their handle. 168 | TMap RegisteredAssets; 169 | 170 | // Maps asset paths to the number of requests they have. 171 | TMap AssetRequestCount; 172 | 173 | // Handles to keep alive until we finally unload the asset. 174 | TMap> KeepAlive; 175 | }; 176 | -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/Public/SimpleAssetStreaming.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FSimpleAssetStreamingModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | 17 | DECLARE_LOG_CATEGORY_EXTERN(LogAssetStreaming, Log, All); -------------------------------------------------------------------------------- /Source/SimpleAssetStreaming/SimpleAssetStreaming.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class SimpleAssetStreaming : ModuleRules 6 | { 7 | public SimpleAssetStreaming(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Slate", 40 | "SlateCore", 41 | // ... add private dependencies that you statically link with here ... 42 | } 43 | ); 44 | 45 | 46 | DynamicallyLoadedModuleNames.AddRange( 47 | new string[] 48 | { 49 | // ... add any modules that your module loads dynamically here ... 50 | } 51 | ); 52 | } 53 | } 54 | --------------------------------------------------------------------------------