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 "Once.h"
15#include "Async/Future.h"
16#include "Mcro/TypeName.h"
17#include "Mcro/Concepts.h"
18
19DECLARE_LOG_CATEGORY_CLASS(LogAutoModularFeature, Log, Log);
20
22{
23 using namespace Mcro::Concepts;
24 using namespace Mcro::TypeName;
25
26 /** Tagging an auto feature (DO NOT USE MANUALLY, inherited by TAutoModularFeature) */
28 /** Tagging an implementation of a feature */
30
31 /**
32 * Auto Modular Features are a workflow with Modular Features where the developer doesn't have to rely on string
33 * identifiers. TAutoModularFeature and TFeatureImplementation templates take care of naming the feature and
34 * introduces some common functionality, like getter functions and runtime validations.
35 *
36 * @remarks
37 * First a feature is defined with its interface class like so:
38 * @code
39 * class IMyModularFeature : public TAutoModularFeature<IMyModularFeature>
40 * {
41 * // ...
42 * }
43 * @endcode
44 * Then each implementations of this feature are defined like so:
45 * @code
46 * class FMyFeatureImplementation : public IMyModularFeature, public IFeatureImplementation
47 * {
48 * FMyFeatureImplementation()
49 * {
50 * // See the inline docs for why this needs to be done
51 * Register()
52 * }
53 * }
54 * @endcode
55 * Then instantiate the feature implementation when needed:
56 * @code
57 * class FMyModule
58 * {
59 * TPimplPtr<FMyFeatureImplementation> MyImplementation;
60 * }
61 *
62 * if (...)
63 * {
64 * MyImplementation = MakePimpl<FMyFeatureImplementation>();
65 * }
66 * @endcode
67 * To access the feature implementations then just use
68 * @code
69 * if (IMyModularFeature::ImplementationCount() > 0)
70 * {
71 * IMyModularFeature::Get().MyStuff();
72 * }
73 * @endcode
74 * Internally the feature name will be identical to the class name. In this case IMyModularFeature will register
75 * as "IMyModularFeature". Technically one can get it via
76 * @code
77 * IModularFeatures::Get().GetModularFeature<IMyModularFeature>(TEXT("IMyModularFeature"))
78 * @endcode
79 * but it is strongly discouraged for type safety and keeping code simple.
80 *
81 * @remarks
82 * IMyModularFeature::FeatureName() and TTypeName<FMyFeatureImplementation>() can be used for runtime
83 * comparison / validation. See TFeatureImplementation::CastChecked which helps handling implementation specific
84 * structures and their runtime polymorphism.
85 *
86 * @tparam FeatureIn Curiously Recurring Template argument of the feature
87 */
88 template<typename FeatureIn>
89 class TAutoModularFeature : public IAutoModularFeature, public IModularFeature
90 {
91 public:
92 using Feature = FeatureIn;
94
95 /** Gert the name of the feature */
96 static FORCEINLINE const FName& FeatureName()
97 {
99 }
100
101 /**
102 * @return The number of implementations created for this feature
103 */
104 static FORCEINLINE int32 ImplementationCount()
105 {
106 return IModularFeatures::Get().GetModularFeatureImplementationCount(FeatureName());
107 }
108
109 /**
110 * Get the first existing implementation of this feature. If there are no implementations a check will fail.
111 */
112 static FORCEINLINE Feature& Get()
113 {
114 return IModularFeatures::Get().GetModularFeature<Feature>(FeatureName());
115 }
116
117 /**
118 * Get the first existing implementation of this feature. Return nullptr If there are no implementations.
119 */
120 static FORCEINLINE Feature* TryGet(const int32 index)
121 {
122 return static_cast<Feature*>(IModularFeatures::Get().GetModularFeatureImplementation(FeatureName(), index));
123 }
124
125 /**
126 * @return An array of all implementations of this feature
127 */
128 static FORCEINLINE TArray<Feature*> GetAll()
129 {
130 return IModularFeatures::Get().GetModularFeatureImplementations<Feature>(FeatureName());
131 }
132
133 /**
134 * Call this function in implementation constructors. This is a necessary boilerplate to maintain polymorphism
135 * of implementations. Otherwise, if the native registration function would be called directly in
136 * TAutoModularFeature default constructor, virtual function overrides are not yet known, and "deducing this"
137 * is not meant for constructors.
138 *
139 * @tparam Implementation Derived type of the implementation
140 * @param self Pointer to implementation registering itself
141 */
142 template<typename Implementation> requires CDerivedFrom<Implementation, Feature>
143 void Register(this Implementation&& self)
144 {
145 UE_LOG(
146 LogAutoModularFeature, Log,
147 TEXT("Registering %s as %s feature"),
150 );
151 IModularFeatures::Get().RegisterModularFeature(FeatureName(), &self);
152 }
153
155 {
156 IModularFeatures::Get().UnregisterModularFeature(FeatureName(), this);
157 }
158
159 /**
160 * Get the first implementation once it is registered, or return the first implementation immediately if
161 * there's already one registered.
162 *
163 * @return A future completed when the first implementation becomes available, or there's already one
164 */
165 static FORCEINLINE TFuture<Feature*> GetBelated()
166 {
167 using namespace Mcro::Once;
168
170 {
171 return MakeFulfilledPromise<Feature*>(&Get()).GetFuture();
172 }
173
174 // Shared promise is required as delegate lambdas are copyable
175 TSharedRef<TPromise<Feature*>> promise = MakeShared<TPromise<Feature*>>();
176 TFuture<Feature*> result = promise->GetFuture();
177 IModularFeatures::Get().OnModularFeatureRegistered().AddLambda(
178 [promise, once = FOnce()](const FName& type, IModularFeature*) mutable
179 {
180 if (type == FeatureName() && once)
181 promise->SetValue(&Get());
182 }
183 );
184 return result;
185 }
186 };
187}
DECLARE_LOG_CATEGORY_CLASS(LogAutoModularFeature, Log, Log)
static FORCEINLINE TArray< Feature * > GetAll()
static FORCEINLINE TFuture< Feature * > GetBelated()
static FORCEINLINE const FName & FeatureName()
static FORCEINLINE Feature * TryGet(const int32 index)
const FString TTypeString
Definition TypeName.h:146
const FName TTypeFName
Definition TypeName.h:142