MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
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 * Returns true when called in the thread which is associated with the given ENamedThreads.
25 * If there's no such a thread or if this function is not called in that thread, return false.
26 */
27 MCRO_API bool IsInThread(ENamedThreads::Type threadName);
28
29 namespace Detail
30 {
31 MCRO_API auto GetThreadCheck(ENamedThreads::Type threadName) -> bool(*)();
32
33 template <CFunctionLike When>
34 requires (TFunction_ArgCount<When> == 0)
36 ENamedThreads::Type threadName,
37 TUniqueFunction<void()>&& func, When&& when
38 ) {
39 if (IsInThread(threadName)) func();
40 else AsyncTask(threadName, [when = MoveTemp(when), func = MoveTemp(func)]
41 {
42 if (auto keep = when()) func();
43 });
44 }
45
46 template <CFunctionLike When>
47 requires (TFunction_ArgCount<When> == 0)
48 void EnqueueRenderCommandBoilerplate(TUniqueFunction<void(FRHICommandListImmediate&)>&& func, When&& when)
49 {
50 if (IsInRenderingThread()) func(GetImmediateCommandList_ForRenderCommand());
51 else
52 {
53 ENQUEUE_RENDER_COMMAND(FMcroThreading)([when = MoveTemp(when), func = MoveTemp(func)](FRHICommandListImmediate& cmdList)
54 {
55 if (auto keep = when()) func(cmdList);
56 });
57 }
58 }
59
60 template <CFunctorObject Function, typename Result = TFunction_Return<Function>, CFunctionLike When>
61 requires (
62 TFunction_ArgCount<Function> == 0
63 && TFunction_ArgCount<When> == 0
64 )
66 ENamedThreads::Type threadName,
67 Function&& func,
68 When&& when
69 ) {
70 if (IsInThread(threadName))
71 return MakeFulfilledPromise<Result>(func()).GetFuture();
72
73 TPromise<Result> promise;
74 auto future = promise.GetFuture();
75
76 AsyncTask(threadName, [when = MoveTemp(when), func = MoveTemp(func), promise = MoveTemp(promise)]() mutable
77 {
78 if (auto keep = when()) promise.SetValue(func());
79 else promise.SetValue({});
80 });
81 return future;
82 }
83
84 template <CFunctorObject Function, typename Result = TFunction_Return<Function>, CFunctionLike When>
85 requires (
86 TFunction_ArgCount<Function> == 1
87 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
88 && TFunction_ArgCount<When> == 0
89 )
91 ENamedThreads::Type threadName,
92 Function&& func,
93 When&& when
94 ) {
95 if (IsInRenderingThread())
96 return MakeFulfilledPromise<Result>(func(GetImmediateCommandList_ForRenderCommand())).GetFuture();
97
98 TPromise<Result> promise;
99 auto future = promise.GetFuture();
100
101 ENQUEUE_RENDER_COMMAND(FMcroThreading)([when = MoveTemp(when), func = MoveTemp(func), promise = MoveTemp(promise)](FRHICommandListImmediate& cmdList) mutable
102 {
103 if (auto keep = when())
104 promise.SetValue(func(cmdList));
105 else promise.SetValue({});
106 });
107 return future;
108 }
109 }
110
111 /** Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread already */
112 MCRO_API void RunInThread(ENamedThreads::Type threadName, TUniqueFunction<void()>&& func);
113
114 /**
115 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
116 * already check the validity of a target object first before running on the selected thread.
117 */
118 MCRO_API void RunInThread(ENamedThreads::Type threadName, const UObject* boundToObject, TUniqueFunction<void()>&& func);
119
120 /**
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 FWeakObjectPtr& boundToObject, TUniqueFunction<void()>&& func);
125
126 /** Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already */
127 MCRO_API void RunInGameThread(TUniqueFunction<void()>&& func);
128
129 /**
130 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
131 * Check the validity of a target object first before running on the game thread.
132 */
133 MCRO_API void RunInGameThread(const UObject* boundToObject, TUniqueFunction<void()>&& func);
134
135 /**
136 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
137 * Check the validity of a target object first before running on the game thread.
138 */
139 MCRO_API void RunInGameThread(const FWeakObjectPtr& boundToObject, TUniqueFunction<void()>&& func);
140
141 /** Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already */
142 MCRO_API void EnqueueRenderCommand(TUniqueFunction<void(FRHICommandListImmediate&)>&& func);
143
144 /**
145 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
146 * Check the validity of a target object first before running on the render thread.
147 */
148 MCRO_API void EnqueueRenderCommand(const UObject* boundToObject, TUniqueFunction<void(FRHICommandListImmediate&)>&& func);
149
150 /**
151 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
152 * Check the validity of a target object first before running on the render thread.
153 */
154 MCRO_API void EnqueueRenderCommand(const FWeakObjectPtr& boundToObject, TUniqueFunction<void(FRHICommandListImmediate&)>&& func);
155
156 /**
157 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
158 * already Check the validity of a target object first before running on the selected thread.
159 */
160 template <CSharedOrWeak Object>
161 void RunInThread(ENamedThreads::Type threadName, const Object& boundToObject, TUniqueFunction<void()>&& func)
162 {
163 TWeakPtrFrom<Object> weakObject(boundToObject);
164 Detail::RunInThreadBoilerplate(threadName, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
165 {
166 return weakObject.Pin();
167 });
168 }
169
170 /**
171 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
172 * Check the validity of a target object first before running on the game thread.
173 */
174 template <CSharedOrWeak Object>
175 void RunInGameThread(const Object& boundToObject, TUniqueFunction<void()>&& func)
176 {
177 RunInThread<Object>(ENamedThreads::GameThread, boundToObject, MoveTemp(func));
178 }
179
180 /**
181 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
182 * Check the validity of a target object first before running on the game thread.
183 */
184 template <CSharedOrWeak Object>
185 void EnqueueRenderCommand(const Object& boundToObject, TUniqueFunction<void(FRHICommandListImmediate&)>&& func)
186 {
187 TWeakPtrFrom<Object> weakObject(boundToObject);
188 Detail::EnqueueRenderCommandBoilerplate(MoveTemp(func), [weakObject = MoveTemp(weakObject)]
189 {
190 return weakObject.Pin();
191 });
192 }
193
194 /**
195 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
196 * already This overload doesn't check object lifespans.
197 */
198 template <
199 CFunctorObject Function,
200 typename Result = TFunction_Return<Function>
201 >
202 requires (TFunction_ArgCount<Function> == 0)
203 TFuture<Result> PromiseInThread(ENamedThreads::Type threadName, Function&& func)
204 {
205 return Detail::PromiseInThreadBoilerplate(threadName, MoveTemp(func), []{ return true; });
206 }
207
208 /**
209 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
210 * already Check the validity of a target object first before running on the selected thread.
211 */
212 template <
213 CSharedOrWeak Object,
214 CFunctorObject Function,
215 typename Result = TFunction_Return<Function>
216 >
217 requires (TFunction_ArgCount<Function> == 0)
218 TFuture<Result> PromiseInThread(ENamedThreads::Type threadName, const Object& boundToObject, Function&& func)
219 {
220 TWeakPtrFrom<Object> weakObject(boundToObject);
221 return Detail::PromiseInThreadBoilerplate(threadName, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
222 {
223 return weakObject.Pin();
224 });
225 }
226
227 /**
228 * Simply run a lambda function on the selected thread but only use AsyncTask if it's not on the selected thread
229 * already Check the validity of a target object first before running on the selected thread.
230 */
231 template <
232 CUObject Object,
233 CFunctorObject Function,
234 typename Result = TFunction_Return<Function>
235 >
236 requires (TFunction_ArgCount<Function> == 0)
237 TFuture<Result> PromiseInThread(ENamedThreads::Type threadName, const Object* boundToObject, Function&& func)
238 {
239 return Detail::PromiseInThreadBoilerplate(threadName, MoveTemp(func), [boundToObject]
240 {
241 return IsValid(boundToObject) ? TStrongObjectPtr(boundToObject) : nullptr;
242 });
243 }
244
245 /**
246 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
247 * This overload doesn't check object lifespans.
248 */
249 template <
250 CFunctorObject Function,
251 typename Result = TFunction_Return<Function>
252 >
253 requires (TFunction_ArgCount<Function> == 0)
254 TFuture<Result> PromiseInGameThread(Function&& func)
255 {
256 return Detail::PromiseInThreadBoilerplate(ENamedThreads::GameThread, MoveTemp(func), []{ return true; });
257 }
258
259 /**
260 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
261 * Check the validity of a target object first before running on the game thread.
262 */
263 template <
264 CSharedOrWeak Object,
265 CFunctorObject Function,
266 typename Result = TFunction_Return<Function>
267 >
268 requires (TFunction_ArgCount<Function> == 0)
269 TFuture<Result> PromiseInGameThread(const Object& boundToObject, Function&& func)
270 {
271 TWeakPtrFrom<Object> weakObject(boundToObject);
272 return Detail::PromiseInThreadBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
273 {
274 return weakObject.Pin();
275 });
276 }
277
278 /**
279 * Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread already
280 * Check the validity of a target object first before running on the game thread.
281 */
282 template <
283 CUObject Object,
284 CFunctorObject Function,
285 typename Result = TFunction_Return<Function>
286 >
287 requires (TFunction_ArgCount<Function> == 0)
288 TFuture<Result> PromiseInGameThread(const Object* boundToObject, Function&& func)
289 {
290 return Detail::PromiseInThreadBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [boundToObject]
291 {
292 return IsValid(boundToObject) ? TStrongObjectPtr(boundToObject) : nullptr;
293 });
294 }
295
296 /**
297 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
298 * This overload doesn't check object lifespans.
299 */
300 template <
301 CFunctorObject Function,
302 typename Result = TFunction_Return<Function>
303 >
304 requires (
306 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
307 )
308 TFuture<Result> EnqueueRenderPromise(Function&& func)
309 {
310 return Detail::EnqueueRenderPromiseBoilerplate(ENamedThreads::GameThread, MoveTemp(func), []{ return true; });
311 }
312
313 /**
314 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
315 * Check the validity of a target object first before running on the render thread.
316 */
317 template <
318 CSharedOrWeak Object,
319 CFunctorObject Function,
320 typename Result = TFunction_Return<Function>
321 >
322 requires (
324 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
325 )
326 TFuture<Result> EnqueueRenderPromise(const Object& boundToObject, Function&& func)
327 {
328 TWeakPtrFrom<Object> weakObject(boundToObject);
329 return Detail::EnqueueRenderPromiseBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [weakObject = MoveTemp(weakObject)]
330 {
331 return weakObject.Pin();
332 });
333 }
334
335 /**
336 * Simply run a lambda function on the render thread but only use AsyncTask if it's not on the render thread already
337 * Check the validity of a target object first before running on the render thread.
338 */
339 template <
340 CUObject Object,
341 CFunctorObject Function,
342 typename Result = TFunction_Return<Function>
343 >
344 requires (
346 && CSameAs<FRHICommandListImmediate&, TFunction_Arg<Function, 0>>
347 )
348 TFuture<Result> EnqueueRenderPromise(const Object* boundToObject, Function&& func)
349 {
350 return Detail::EnqueueRenderPromiseBoilerplate(ENamedThreads::GameThread, MoveTemp(func), [boundToObject]
351 {
352 return IsValid(boundToObject) ? TStrongObjectPtr(boundToObject) : nullptr;
353 });
354 }
355}
typename TFunctionTraits< std::decay_t< T > >::Return TFunction_Return
constexpr size_t TFunction_ArgCount
TWeakPtr< typename T::ElementType, T::Mode > TWeakPtrFrom
void RunInThreadBoilerplate(ENamedThreads::Type threadName, TUniqueFunction< void()> &&func, When &&when)
Definition Threading.h:35
TFuture< Result > EnqueueRenderPromiseBoilerplate(ENamedThreads::Type threadName, Function &&func, When &&when)
Definition Threading.h:90
void EnqueueRenderCommandBoilerplate(TUniqueFunction< void(FRHICommandListImmediate &)> &&func, When &&when)
Definition Threading.h:48
TFuture< Result > PromiseInThreadBoilerplate(ENamedThreads::Type threadName, Function &&func, When &&when)
Definition Threading.h:65
MCRO_API auto GetThreadCheck(ENamedThreads::Type threadName) -> bool(*)()
MCRO_API void RunInGameThread(TUniqueFunction< void()> &&func)
MCRO_API bool IsInThread(ENamedThreads::Type threadName)
MCRO_API void EnqueueRenderCommand(TUniqueFunction< void(FRHICommandListImmediate &)> &&func)
TFuture< Result > EnqueueRenderPromise(Function &&func)
Definition Threading.h:308
TFuture< Result > PromiseInGameThread(Function &&func)
Definition Threading.h:254
TFuture< Result > PromiseInThread(ENamedThreads::Type threadName, Function &&func)
Definition Threading.h:203
MCRO_API void RunInThread(ENamedThreads::Type threadName, TUniqueFunction< void()> &&func)