MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
AutoModularFeature.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 "Features/IModularFeatures.h"
15#include "Async/Future.h"
16#include "Mcro/TypeName.h"
18
19DECLARE_LOG_CATEGORY_CLASS(LogAutoModularFeature, Log, Log);
20
22{
23 using namespace Mcro::Delegates;
24 using namespace Mcro::TypeName;
25
26 /** @brief Tagging an auto feature (DO NOT USE MANUALLY, inherited by TAutoModularFeature) */
28
29 /** @brief Tagging an implementation of a feature */
31
32 /**
33 * @brief
34 * Auto Modular Features are a workflow with Modular Features where the developer doesn't have to rely on string
35 * identifiers. TAutoModularFeature template take care of naming the feature and introduces some common
36 * functionality, like getter functions and runtime validations.
37 *
38 * First a feature is defined with its interface class like so:
39 * @code
40 * class IMyModularFeature : public TAutoModularFeature<IMyModularFeature>
41 * {
42 * // ...
43 * }
44 * @endcode
45 * Then each implementation of this feature are defined like so:
46 * @code
47 * class FMyFeatureImplementation : public IMyModularFeature, public IFeatureImplementation
48 * {
49 * FMyFeatureImplementation()
50 * {
51 * // See the inline docs for why this needs to be done
52 * Register()
53 * }
54 * }
55 * @endcode
56 * Then instantiate the feature implementation when needed:
57 * @code
58 * class FMyModule
59 * {
60 * TPimplPtr<FMyFeatureImplementation> MyImplementation;
61 * }
62 *
63 * if (...)
64 * {
65 * MyImplementation = MakePimpl<FMyFeatureImplementation>();
66 * }
67 * @endcode
68 * To access the feature implementations then just use
69 * @code
70 * if (IMyModularFeature::ImplementationCount() > 0)
71 * {
72 * IMyModularFeature::Get().MyStuff();
73 * }
74 * @endcode
75 *
76 * Internally the feature name will be identical to the class name. In this case `IMyModularFeature` will register
77 * as "IMyModularFeature". Technically one can get it via
78 * @code
79 * IModularFeatures::Get().GetModularFeature<IMyModularFeature>(TEXT_"IMyModularFeature")
80 * @endcode
81 * but it is strongly discouraged for type safety and keeping code simple.
82 *
83 * For globally available features you may use just simply a global variable, or if it's important to have the
84 * owning module fully initialized, `TModuleBoundObject` is recommended.
85 * @code
86 * TModuleBoundObject<FFoobarModule, FMyFeatureImplementation> GMyFeatureImplementation;
87 * @endcode
88 *
89 * @remarks
90 * IMyModularFeature::FeatureName() and TTypeFName<FMyFeatureImplementation>() can be used for runtime
91 * comparison / validation.
92 *
93 * @tparam FeatureIn Curiously Recurring Template argument of the feature
94 */
95 template<typename FeatureIn>
96 class TAutoModularFeature : public IAutoModularFeature, public IModularFeature
97 {
98 public:
99 using Feature = FeatureIn;
101
102 /** @brief This event is triggered when an implementation of this feature is created */
104 {
105 static TBelatedEventDelegate<void(Feature*)> event;
106 return event;
107 }
108
109 /** @brief Get the name of the feature */
110 static FORCEINLINE FName FeatureName()
111 {
112 return TTypeFName<Feature>();
113 }
114
115 /** @return The number of implementations created for this feature */
116 static FORCEINLINE int32 ImplementationCount()
117 {
118 return IModularFeatures::Get().GetModularFeatureImplementationCount(FeatureName());
119 }
120
121 /**
122 * @brief
123 * Get the first existing implementation of this feature. If there are no implementations a check will fail.
124 */
125 static FORCEINLINE Feature& Get()
126 {
127 return IModularFeatures::Get().GetModularFeature<Feature>(FeatureName());
128 }
129
130 /** @brief Get the first existing implementation of this feature. Return nullptr If there are no implementations. */
131 static FORCEINLINE Feature* TryGet(const int32 index)
132 {
133 return static_cast<Feature*>(IModularFeatures::Get().GetModularFeatureImplementation(FeatureName(), index));
134 }
135
136 /** @return An array of all implementations of this feature */
137 static FORCEINLINE TArray<Feature*> GetAll()
138 {
139 return IModularFeatures::Get().GetModularFeatureImplementations<Feature>(FeatureName());
140 }
141
142 /**
143 * @brief
144 * Call this function in implementation constructors.
145 *
146 * This is a necessary boilerplate to maintain polymorphism of implementations. Otherwise, if the native
147 * registration function would be called directly in TAutoModularFeature default constructor, virtual function
148 * overrides are not yet known, and "deducing this" is not meant for constructors.
149 *
150 * @tparam Implementation Derived type of the implementation
151 * @param self Pointer to implementation registering itself
152 */
153 template<typename Implementation> requires CDerivedFrom<Implementation, Feature>
154 void Register(this Implementation&& self)
155 {
156 UE_LOG(
157 LogAutoModularFeature, Log,
158 TEXT_"Registering %s as %s feature",
161 );
162 IModularFeatures::Get().RegisterModularFeature(FeatureName(), &self);
163 OnRegistered().Broadcast(&self);
164 }
165
167 {
168 IModularFeatures::Get().UnregisterModularFeature(FeatureName(), this);
169 }
170
171 /**
172 * @brief
173 * Get the first implementation once it is registered, or return the first implementation immediately if
174 * there's already one registered.
175 *
176 * @return A future completed when the first implementation becomes available, or there's already one
177 *
178 * @warning
179 * If no implementations were created throughout the entire execution of the program, it will crash on exit
180 * with an unfulfilled promise error. When using this function either handle this scenario early, or
181 * use `OnRegistered()` if your particular feature may never be created.
182 */
183 static FORCEINLINE TFuture<Feature*> GetBelated()
184 {
185 // Shared promise is required as delegate lambdas are copyable
186 TSharedRef<TPromise<Feature*>> promise = MakeShared<TPromise<Feature*>>();
187 TFuture<Feature*> result = promise->GetFuture();
188
189 OnRegistered().Add(InferDelegate::From([promise](Feature* feature) mutable
190 {
191 promise->SetValue(feature);
192 }), {.Once = true});
193
194 return result;
195 }
196 };
197}
DECLARE_LOG_CATEGORY_CLASS(LogAutoModularFeature, Log, Log)
#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.
Tagging an auto feature (DO NOT USE MANUALLY, inherited by TAutoModularFeature)
Tagging an implementation of a feature.
Auto Modular Features are a workflow with Modular Features where the developer doesn't have to rely o...
static FORCEINLINE Feature & Get()
Get the first existing implementation of this feature. If there are no implementations a check will f...
static auto OnRegistered() -> TBelatedEventDelegate< void(Feature *)> &
This event is triggered when an implementation of this feature is created.
static FORCEINLINE TArray< Feature * > GetAll()
void Register(this Implementation &&self)
Call this function in implementation constructors.
static FORCEINLINE TFuture< Feature * > GetBelated()
Get the first implementation once it is registered, or return the first implementation immediately if...
static FORCEINLINE Feature * TryGet(const int32 index)
Get the first existing implementation of this feature. Return nullptr If there are no implementations...
static FORCEINLINE FName FeatureName()
Get the name of the feature.
"Extension" of a common TMulticastDelegate. It allows to define optional "flags" when adding a bindin...
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...
FName TTypeFName()
Same as TTypeName converted to FName. This is not cached and a new FName is created every time this i...
Definition TypeName.h:179
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