MCRO
C++23 utilities for Unreal Engine.
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages Concepts
Threading.h
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * This Source Code is subject to the terms of the Mozilla Public License, v2.0.
5 * If a copy of the MPL was not distributed with this file You can obtain one at
6 * https://mozilla.org/MPL/2.0/
7 *
8 * @author David Mórász
9 * @date 2025
10 */
11
12#pragma once
13
14#include "CoreMinimal.h"
15#include "Mcro/FunctionTraits.h"
16#include "Mcro/SharedObjects.h"
17
19{
20 using namespace Mcro::FunctionTraits;
21 using namespace Mcro::SharedObjects;
22
23 /**
24 * @brief
25 * Returns true when called in the thread which is associated with the given ENamedThreads.
26 * If there's no such a thread or if this function is not called in that thread, return false.
27 */
28 MCRO_API bool IsInThread(ENamedThreads::Type threadName);
29
30 namespace Detail
31 {
32 MCRO_API auto GetThreadCheck(ENamedThreads::Type threadName) -> bool(*)();
33
34 template <CFunctionLike When>
35 requires (TFunction_ArgCount<When> == 0)
37 ENamedThreads::Type threadName,
38 TUniqueFunction<void()>&& func, When&& when
39 ) {
40 if (IsInThread(threadName)) func();
41 else AsyncTask(threadName, [when = MoveTemp(when), func = MoveTemp(func)]
42 {
43 if (auto keep = when()) func();
44 });
45 }
46
47 template <CFunctionLike When>
48 requires (TFunction_ArgCount<When> == 0)
49 void EnqueueRenderCommandBoilerplate(TUniqueFunction<void(FRHICommandListImmediate&)>&& func, When&& when)
50 {
51 if (IsInRenderingThread()) func(GetImmediateCommandList_ForRenderCommand());
52 else
53 {
54 ENQUEUE_RENDER_COMMAND(FMcroThreading)([when = MoveTemp(when), func = MoveTemp(func)](FRHICommandListImmediate& cmdList)
55 {
56 if (auto keep = when()) func(cmdList);
57 });
58 }
59 }
60
61 template <CFunctorObject Function, typename Result = TFunction_Return<Function>, CFunctionLike When>
62 requires (
63 TFunction_ArgCount<Function> == 0
64 && TFunction_ArgCount<When> == 0
65 )
67 ENamedThreads::Type threadName,
68 Function&& func,
69 When&& when
70 ) {
71 if (IsInThread(threadName))
72 return MakeFulfilledPromise<Result>(func()).GetFuture();
73
74 TPromise<Result> promise;
75 auto future = promise.GetFuture();
76
77 AsyncTask(threadName, [when = MoveTemp(when), func = MoveTemp(func), promise = MoveTemp(promise)]() mutable
78 {
79 if (auto keep = when()) promise.SetValue(func());
80 else promise.SetValue({});
81 });
82 return future;
83 }
84
85 template <CFunctorObject Function, typename Result = TFunction_Return<Function>, CFunctionLike When>
86 requires (
87 TFunction_ArgCount<Function> == 1
88 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
89 && TFunction_ArgCount<When> == 0
90 )
92 ENamedThreads::Type threadName,
93 Function&& func,
94 When&& when
95 ) {
96 if (IsInRenderingThread())
97 return MakeFulfilledPromise<Result>(func(GetImmediateCommandList_ForRenderCommand())).GetFuture();
98
99 TPromise<Result> promise;
100 auto future = promise.GetFuture();
101
102 ENQUEUE_RENDER_COMMAND(FMcroThreading)([when = MoveTemp(when), func = MoveTemp(func), promise = MoveTemp(promise)](FRHICommandListImmediate& cmdList) mutable
103 {
104 if (auto keep = when())
105 promise.SetValue(func(cmdList));
106 else promise.SetValue({});
107 });
108 return future;
109 }
110 }
111
112 /**
113 * @brief
114 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
115 * already
116 */
117 MCRO_API void RunInThread(ENamedThreads::Type threadName, TUniqueFunction<void()>&& func);
118
119 /**
120 * @brief
121 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
122 * already check the validity of a target object first before running on the selected thread.
123 */
124 MCRO_API void RunInThread(ENamedThreads::Type threadName, const UObject* boundToObject, TUniqueFunction<void()>&& func);
125
126 /**
127 * @brief
128 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
129 * already check the validity of a target object first before running on the selected thread.
130 */
131 MCRO_API void RunInThread(ENamedThreads::Type threadName, const FWeakObjectPtr& boundToObject, TUniqueFunction<void()>&& func);
132
133 /**
134 * @brief
135 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
136 */
137 MCRO_API void RunInGameThread(TUniqueFunction<void()>&& func);
138
139 /**
140 * @brief
141 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
142 * Check the validity of a target object first before running on the game thread.
143 */
144 MCRO_API void RunInGameThread(const UObject* boundToObject, TUniqueFunction<void()>&& func);
145
146 /**
147 * @brief
148 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
149 * Check the validity of a target object first before running on the game thread.
150 */
151 MCRO_API void RunInGameThread(const FWeakObjectPtr& boundToObject, TUniqueFunction<void()>&& func);
152
153 /**
154 * @brief
155 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
156 */
157 MCRO_API void EnqueueRenderCommand(TUniqueFunction<void(FRHICommandListImmediate&)>&& func);
158
159 /**
160 * @brief
161 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
162 * Check the validity of a target object first before running on the render thread.
163 */
164 MCRO_API void EnqueueRenderCommand(const UObject* boundToObject, TUniqueFunction<void(FRHICommandListImmediate&)>&& func);
165
166 /**
167 * @brief
168 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
169 * Check the validity of a target object first before running on the render thread.
170 */
171 MCRO_API void EnqueueRenderCommand(const FWeakObjectPtr& boundToObject, TUniqueFunction<void(FRHICommandListImmediate&)>&& func);
172
173 /**
174 * @brief
175 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
176 * already Check the validity of a target object first before running on the selected thread.
177 */
178 template <CSharedOrWeak Object>
179 void RunInThread(ENamedThreads::Type threadName, const Object& boundToObject, TUniqueFunction<void()>&& func)
180 {
181 TWeakPtrFrom<Object> weakObject(boundToObject);
182 Detail::RunInThreadBoilerplate(threadName, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
183 {
184 return weakObject.Pin();
185 });
186 }
187
188 /**
189 * @brief
190 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
191 * Check the validity of a target object first before running on the game thread.
192 */
193 template <CSharedOrWeak Object>
194 void RunInGameThread(const Object& boundToObject, TUniqueFunction<void()>&& func)
195 {
196 RunInThread<Object>(ENamedThreads::GameThread, boundToObject, MoveTemp(func));
197 }
198
199 /**
200 * @brief
201 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
202 * Check the validity of a target object first before running on the game thread.
203 */
204 template <CSharedOrWeak Object>
205 void EnqueueRenderCommand(const Object& boundToObject, TUniqueFunction<void(FRHICommandListImmediate&)>&& func)
206 {
207 TWeakPtrFrom<Object> weakObject(boundToObject);
208 Detail::EnqueueRenderCommandBoilerplate(MoveTemp(func), [weakObject = MoveTemp(weakObject)]
209 {
210 return weakObject.Pin();
211 });
212 }
213
214 /**
215 * @brief
216 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
217 * already This overload doesn't check object lifespans.
218 */
219 template <
220 CFunctorObject Function,
221 typename Result = TFunction_Return<Function>
222 >
223 requires (TFunction_ArgCount<Function> == 0)
224 TFuture<Result> PromiseInThread(ENamedThreads::Type threadName, Function&& func)
225 {
226 return Detail::PromiseInThreadBoilerplate(threadName, MoveTemp(func), []{ return true; });
227 }
228
229 /**
230 * @brief
231 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
232 * already Check the validity of a target object first before running on the selected thread.
233 */
234 template <
235 CSharedOrWeak Object,
236 CFunctorObject Function,
237 typename Result = TFunction_Return<Function>
238 >
239 requires (TFunction_ArgCount<Function> == 0)
240 TFuture<Result> PromiseInThread(ENamedThreads::Type threadName, const Object& boundToObject, Function&& func)
241 {
242 TWeakPtrFrom<Object> weakObject(boundToObject);
243 return Detail::PromiseInThreadBoilerplate(threadName, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
244 {
245 return weakObject.Pin();
246 });
247 }
248
249 /**
250 * @brief
251 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
252 * already Check the validity of a target object first before running on the selected thread.
253 */
254 template <
255 CUObject Object,
256 CFunctorObject Function,
257 typename Result = TFunction_Return<Function>
258 >
259 requires (TFunction_ArgCount<Function> == 0)
260 TFuture<Result> PromiseInThread(ENamedThreads::Type threadName, const Object* boundToObject, Function&& func)
261 {
262 return Detail::PromiseInThreadBoilerplate(threadName, MoveTemp(func), [boundToObject]
263 {
264 return IsValid(boundToObject) ? TStrongObjectPtr(boundToObject) : nullptr;
265 });
266 }
267
268 /**
269 * @brief
270 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
271 * This overload doesn't check object lifespans.
272 */
273 template <
274 CFunctorObject Function,
275 typename Result = TFunction_Return<Function>
276 >
277 requires (TFunction_ArgCount<Function> == 0)
278 TFuture<Result> PromiseInGameThread(Function&& func)
279 {
280 return Detail::PromiseInThreadBoilerplate(ENamedThreads::GameThread, MoveTemp(func), []{ return true; });
281 }
282
283 /**
284 * @brief
285 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
286 * Check the validity of a target object first before running on the game thread.
287 */
288 template <
289 CSharedOrWeak Object,
290 CFunctorObject Function,
291 typename Result = TFunction_Return<Function>
292 >
293 requires (TFunction_ArgCount<Function> == 0)
294 TFuture<Result> PromiseInGameThread(const Object& boundToObject, Function&& func)
295 {
296 TWeakPtrFrom<Object> weakObject(boundToObject);
297 return Detail::PromiseInThreadBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
298 {
299 return weakObject.Pin();
300 });
301 }
302
303 /**
304 * @brief
305 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
306 * Check the validity of a target object first before running on the game thread.
307 */
308 template <
309 CUObject Object,
310 CFunctorObject Function,
311 typename Result = TFunction_Return<Function>
312 >
313 requires (TFunction_ArgCount<Function> == 0)
314 TFuture<Result> PromiseInGameThread(const Object* boundToObject, Function&& func)
315 {
316 return Detail::PromiseInThreadBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [boundToObject]
317 {
318 return IsValid(boundToObject) ? TStrongObjectPtr(boundToObject) : nullptr;
319 });
320 }
321
322 /**
323 * @brief
324 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
325 * This overload doesn't check object lifespans.
326 */
327 template <
328 CFunctorObject Function,
329 typename Result = TFunction_Return<Function>
330 >
331 requires (
333 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
334 )
335 TFuture<Result> EnqueueRenderPromise(Function&& func)
336 {
337 return Detail::EnqueueRenderPromiseBoilerplate(ENamedThreads::GameThread, MoveTemp(func), []{ return true; });
338 }
339
340 /**
341 * @brief
342 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
343 * Check the validity of a target object first before running on the render thread.
344 */
345 template <
346 CSharedOrWeak Object,
347 CFunctorObject Function,
348 typename Result = TFunction_Return<Function>
349 >
350 requires (
352 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
353 )
354 TFuture<Result> EnqueueRenderPromise(const Object& boundToObject, Function&& func)
355 {
356 TWeakPtrFrom<Object> weakObject(boundToObject);
357 return Detail::EnqueueRenderPromiseBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
358 {
359 return weakObject.Pin();
360 });
361 }
362
363 /**
364 * @brief
365 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
366 * Check the validity of a target object first before running on the render thread.
367 */
368 template <
369 CUObject Object,
370 CFunctorObject Function,
371 typename Result = TFunction_Return<Function>
372 >
373 requires (
375 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
376 )
377 TFuture<Result> EnqueueRenderPromise(const Object* boundToObject, Function&& func)
378 {
379 return Detail::EnqueueRenderPromiseBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [boundToObject]
380 {
381 return IsValid(boundToObject) ? TStrongObjectPtr(boundToObject) : nullptr;
382 });
383 }
384}
typename TFunctionTraits< std::decay_t< T > >::Return TFunction_Return
Shorthand for getting a function return type.
constexpr size_t TFunction_ArgCount
Shorthand for getting a function argument count.
Utilities for TSharedPtr/Ref and related.
TWeakPtr< typename T::ElementType, T::Mode > TWeakPtrFrom
Copy thread-safety from other shared object type.
void RunInThreadBoilerplate(ENamedThreads::Type threadName, TUniqueFunction< void()> &&func, When &&when)
Definition Threading.h:36
TFuture< Result > EnqueueRenderPromiseBoilerplate(ENamedThreads::Type threadName, Function &&func, When &&when)
Definition Threading.h:91
void EnqueueRenderCommandBoilerplate(TUniqueFunction< void(FRHICommandListImmediate &)> &&func, When &&when)
Definition Threading.h:49
TFuture< Result > PromiseInThreadBoilerplate(ENamedThreads::Type threadName, Function &&func, When &&when)
Definition Threading.h:66
MCRO_API auto GetThreadCheck(ENamedThreads::Type threadName) -> bool(*)()
MCRO_API void RunInGameThread(TUniqueFunction< void()> &&func)
Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread...
MCRO_API bool IsInThread(ENamedThreads::Type threadName)
Returns true when called in the thread which is associated with the given ENamedThreads....
MCRO_API void EnqueueRenderCommand(TUniqueFunction< void(FRHICommandListImmediate &)> &&func)
Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render th...
TFuture< Result > EnqueueRenderPromise(Function &&func)
Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render th...
Definition Threading.h:335
TFuture< Result > PromiseInGameThread(Function &&func)
Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread...
Definition Threading.h:278
TFuture< Result > PromiseInThread(ENamedThreads::Type threadName, Function &&func)
Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selecte...
Definition Threading.h:224
MCRO_API void RunInThread(ENamedThreads::Type threadName, TUniqueFunction< void()> &&func)
Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selecte...