MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
Modules.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#include "CoreMinimal.h"
14#include "Modules/ModuleInterface.h"
15#include "Modules/ModuleManager.h"
16#include "Mcro/AssertMacros.h"
17#include "Mcro/TextMacros.h"
18#include "Mcro/TypeName.h"
20#include "Mcro/Error.h"
21#include "Mcro/Enums.h"
22
23/** @brief Namespace for utilities handling Unreal modules */
25{
26 using namespace Mcro::Delegates;
27 using namespace Mcro::Concepts;
28 using namespace Mcro::TypeName;
29 using namespace Mcro::Error;
30 using namespace Mcro::Enums;
31
32 /**
33 * @brief
34 * Infer the module name from an input type. This exists because there's a very reliable naming convention for
35 * class names representing modules. The inference is removing the first letter Hungarian type notation and
36 * removing "Module" or "ModuleInterface" from the end.
37 *
38 * @tparam M the supposed type of the module
39 * @return The module name inferrable from its type name
40 */
41 template <CDerivedFrom<IModuleInterface> M>
43 {
44 auto moduleName = TTypeString<M>().Mid(1);
45 moduleName.RemoveFromEnd(TEXT_"Module");
46 moduleName.RemoveFromEnd(TEXT_"ModuleInterface");
47 return moduleName;
48 }
49
50 /**
51 * @brief
52 * Try to load a module and return an IError when that fails for any reason. Module name is inferred from type
53 * described in @see InferModuleName.
54 */
55 template <CDerivedFrom<IModuleInterface> M>
57 {
58 auto loadResult = EModuleLoadResult::Success;
59 auto name = InferModuleName<M>();
60 auto moduleInterface = FModuleManager::Get().LoadModuleWithFailureReason(*name, loadResult);
61 ASSERT_RETURN(loadResult == EModuleLoadResult::Success && moduleInterface)
62 ->AsFatal()
63 ->WithMessageF(TEXT_"Couldn't load module {0} inferred from type {1}",
64 name, TTypeName<M>
65 )
66 ->WithAppendix(TEXT_"EModuleLoadResult", EnumToStringCopy(loadResult));
67 return static_cast<M*>(moduleInterface);
68 }
69
70 /**
71 * @brief
72 * Load a module or crash with IError if it cannot be done for any reason Module name is inferred from type
73 * described in @see InferModuleName.
74 */
75 template <CDerivedFrom<IModuleInterface> M>
77 {
78 auto result = TryLoadUnrealModule<M>();
79 ASSERT_CRASH(result,
80 ->WithError(result.GetErrorRef())
81 );
82 return *result.GetValue();
83 }
84
85 /** @brief Shorthand for FModuleManager::GetModulePtr with the name inferred from given type. @see InferModuleName */
86 template <CDerivedFrom<IModuleInterface> M>
88 {
89 return FModuleManager::GetModulePtr<M>(*InferModuleName<M>());
90 }
91
92 /** @brief Get an already loaded unreal module. If for any reason it's not loaded, crash the app. @see InferModuleName */
93 template <CDerivedFrom<IModuleInterface> M>
95 {
96 M* result = FModuleManager::GetModulePtr<M>(*InferModuleName<M>());
97 ASSERT_CRASH(result,
98 ->WithMessageF(TEXT_"Couldn't get module {0} inferred from type {1}",
101 )
102 );
103 return *result;
104 }
105
106 /** @brief Add this interface to your module class if other things can listen to module startup or shutdown */
107 class MCRO_API IObservableModule : public IModuleInterface
108 {
109 public:
110
111 /**
112 * @brief
113 * Event broadcasted on module startup or immediately executed upon subscription if module has already been
114 * started up.
115 */
117
118 /**
119 * @brief
120 * Event broadcasted on module shutdown or immediately executed upon subscription if module has already been
121 * shut down.
122 */
124
125 virtual void StartupModule() override;
126 virtual void ShutdownModule() override;
127 };
128
129 template <typename T>
130 concept CObservableModule = CDerivedFrom<T, IModuleInterface>
131 && requires(T&& t)
132 {
133 { t.OnStartupModule } -> CSameAsDecayed<TBelatedEventDelegate<void()>>;
134 { t.OnShutdownModule } -> CSameAsDecayed<TBelatedEventDelegate<void()>>;
135 }
136 ;
137
138 /** @brief A record for the module event listeners */
140 {
141 TFunction<void()> OnStartup;
142 TFunction<void()> OnShutdown;
143 };
144
145 /** @brief Use this in global variables to automatically do things on module startup or shutdown */
146 template <CObservableModule M>
148 {
149 /**
150 * @brief
151 * Default constructor will try to infer module name from type name. Given convention
152 * `(F|I)Foobar(Module(Interface)?)?` the extracted name will be `Foobar`. If your module doesn't follow this
153 * naming use the constructor accepting an FName
154 */
156 {
157 BindListeners(FWD(listeners));
158
159 ObserveModule(*InferModuleName<M>());
160
161 }
162
163 /** @brief This constructor provides an explicit FName for getting the module */
164 TObserveModule(FName const& moduleName, FObserveModuleListener&& listeners)
165 {
166 BindListeners(FWD(listeners));
167 ObserveModule(moduleName);
168 }
169
170 /**
171 * @brief
172 * Event broadcasted on module startup or immediately executed upon subscription if module has already been
173 * started up.
174 */
176
177 /**
178 * @brief
179 * Event broadcasted on module shutdown or immediately executed upon subscription if module has already been
180 * shut down.
181 */
183
184 /** @brief Specify function to be executed on startup */
185 TObserveModule& OnStartup(TFunction<void()>&& func)
186 {
188 return *this;
189 }
190
191 /** @brief Specify function to be executed on shutdown */
192 TObserveModule& OnShutdown(TFunction<void()>&& func)
193 {
195 return *this;
196 }
197
198 private:
199 M* Module = nullptr;
200
201 void BindListeners(FObserveModuleListener&& listeners)
202 {
203 if (listeners.OnStartup) OnStartupModule.Add(InferDelegate::From(listeners.OnStartup));
204 if (listeners.OnShutdown) OnShutdownModule.Add(InferDelegate::From(listeners.OnShutdown));
205 }
206
207 void ObserveModule(FName const& moduleName)
208 {
209 decltype(auto) manager = FModuleManager::Get();
210 M* module = static_cast<M*>(manager.GetModule(moduleName));
211 if (!module)
212 {
213 manager.OnModulesChanged().AddLambda([this, moduleName](FName name, EModuleChangeReason changeReason)
214 {
215 if (changeReason == EModuleChangeReason::ModuleLoaded && moduleName == name)
216 ObserveModule(moduleName);
217 });
218 }
219 else
220 {
221 Module = module;
222 module->OnStartupModule.Add(OnStartupModule.Delegation());
223 module->OnShutdownModule.Add(OnShutdownModule.Delegation());
224 }
225 }
226 };
227
228 /** @brief A wrapper around a given object which lifespan is bound to given module. */
229 template <CObservableModule M, typename T>
231 {
232 using StorageType = std::conditional_t<
233 CSharedFromThis<T>,
234 TSharedPtr<T>,
235 TUniquePtr<T>
236 >;
237
239 {
240 TFunction<T*()> Create;
241 TFunction<void(T&)> OnAfterCreated;
242 TFunction<void(T&)> OnShutdown;
243 };
244
246 : Observer({
247 [this, factory]
248 {
249 T* newObject = !factory.Create ? new T() : factory.Create();
250 CreateObject(newObject);
251 if (factory.OnAfterCreated) factory.OnAfterCreated(*Storage.Get());
252 },
253 [this, factory]
254 {
255 if (factory.OnShutdown) factory.OnShutdown(*Storage.Get());
256 Storage.Reset();
257 }
258 })
259 {}
260
262 {
263 ASSERT_CRASH(Storage,
264 ->WithMessage(TEXT_"Module bound object was not available")
265 ->WithAppendix(TEXT_"Module type", TTypeString<M>())
266 ->WithAppendix(TEXT_"Object type", TTypeString<T>())
267 );
268 return *Storage.Get();
269 }
270
271 T const& GetChecked() const
272 {
273 ASSERT_CRASH(Storage,
274 ->WithMessage(TEXT_"Module bound object was not available")
275 ->WithAppendix(TEXT_"Module type", TTypeString<M>())
276 ->WithAppendix(TEXT_"Object type", TTypeString<T>())
277 );
278 return *Storage.Get();
279 }
280
281 T* TryGet() { return Storage.Get(); }
282 const T* TryGet() const { return Storage.Get(); }
283
284 private:
285
286 void CreateObject(T* newObject)
287 {
288 if constexpr (CSharedInitializeable<T>)
289 Storage = MakeShareableInit(newObject);
290 else if constexpr (CSharedFromThis<T>)
291 Storage = MakeShareable<T>(newObject);
292 else
293 Storage = TUniquePtr<T>(newObject);
294 }
295
296 StorageType Storage {};
297 TObserveModule<M> Observer;
298 };
299}
#define ASSERT_CRASH(condition,...)
Use this instead of check macro if the checked expression shouldn't be ignored in shipping builds....
#define ASSERT_RETURN(...)
Similar to check() macro, but return an error instead of crashing.
Definition Error.h:735
#define FWD(...)
Shorten forwarding expression with this macro so one may not need to specify explicit type.
Definition Macros.h:100
Use leading TEXT_ without parenthesis for Unreal compatible text literals.
#define TEXT_
A convenience alternative to Unreal's own TEXT macro but this one doesn't require parenthesis around ...
Definition TextMacros.h:53
Convert types to string.
"Extension" of a common TMulticastDelegate. It allows to define optional "flags" when adding a bindin...
Add this interface to your module class if other things can listen to module startup or shutdown.
Definition Modules.h:108
virtual void ShutdownModule() override
virtual void StartupModule() override
TBelatedEventDelegate< void()> OnStartupModule
Event broadcasted on module startup or immediately executed upon subscription if module has already b...
Definition Modules.h:116
TBelatedEventDelegate< void()> OnShutdownModule
Event broadcasted on module shutdown or immediately executed upon subscription if module has already ...
Definition Modules.h:123
TInferredDelegate< Function, Captures... > From(Function func, Captures &&... captures)
Instead of specifying manually a delegate type, infer it from the input function and the extra captur...
Contains utilities for handling enums as strings or vice-versa.
Definition Enums.h:15
FString EnumToStringCopy(Enum input)
Definition Enums.h:20
Contains utilities for structured error handling.
Definition Error.Fwd.h:19
Namespace for utilities handling Unreal modules.
Definition Modules.h:25
FString InferModuleName()
Infer the module name from an input type. This exists because there's a very reliable naming conventi...
Definition Modules.h:42
M & LoadUnrealModule()
Load a module or crash with IError if it cannot be done for any reason Module name is inferred from t...
Definition Modules.h:76
M & GetUnrealModule()
Get an already loaded unreal module. If for any reason it's not loaded, crash the app.
Definition Modules.h:94
M * GetUnrealModulePtr()
Shorthand for FModuleManager::GetModulePtr with the name inferred from given type.
Definition Modules.h:87
TMaybe< M * > TryLoadUnrealModule()
Try to load a module and return an IError when that fails for any reason. Module name is inferred fro...
Definition Modules.h:56
TSharedRef< T, Mode > MakeShareableInit(T *newObject, Args &&... args)
A wrapper around MakeShareable that automatically calls an initializer method Initialize on the insta...
constexpr FStringView TTypeName
Get a friendly string of an input type without using typeid(T).name().
Definition TypeName.h:161
FString TTypeString()
Same as TTypeName converted to FString. This is not cached and a new FString is created every time th...
Definition TypeName.h:189
A TValueOrError alternative for IError which allows implicit conversion from values and errors (no ne...
Definition Error.h:590
A record for the module event listeners.
Definition Modules.h:140
A wrapper around a given object which lifespan is bound to given module.
Definition Modules.h:231
std::conditional_t< CSharedFromThis< T >, TSharedPtr< T >, TUniquePtr< T > > StorageType
Definition Modules.h:232
TModuleBoundObject(FObjectFactory &&factory={})
Definition Modules.h:245
T const & GetChecked() const
Definition Modules.h:271
Use this in global variables to automatically do things on module startup or shutdown.
Definition Modules.h:148
TObserveModule & OnStartup(TFunction< void()> &&func)
Specify function to be executed on startup.
Definition Modules.h:185
TBelatedEventDelegate< void()> OnShutdownModule
Event broadcasted on module shutdown or immediately executed upon subscription if module has already ...
Definition Modules.h:182
TObserveModule & OnShutdown(TFunction< void()> &&func)
Specify function to be executed on shutdown.
Definition Modules.h:192
TObserveModule(FName const &moduleName, FObserveModuleListener &&listeners)
This constructor provides an explicit FName for getting the module.
Definition Modules.h:164
TObserveModule(FObserveModuleListener &&listeners)
Default constructor will try to infer module name from type name. Given convention (F|I)Foobar(Module...
Definition Modules.h:155
TBelatedEventDelegate< void()> OnStartupModule
Event broadcasted on module startup or immediately executed upon subscription if module has already b...
Definition Modules.h:175