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