MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
Composition.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/Any.h"
16#include "Mcro/Ansi/Allocator.h"
17#include "Mcro/TextMacros.h"
18#include "Mcro/AssertMacros.h"
19#include "Mcro/Range.h"
20#include "Mcro/Range/Views.h"
22
23/** @brief Namespace containing utilities and base classes for type composition */
25{
26 using namespace Mcro::Any;
27 using namespace Mcro::Range;
28
29 class IComposable;
30
31 /**
32 * @brief
33 * Inherit from this empty interface to signal that the inheriting class knows that it's a component and that it
34 * can receive info about the composable class it is being registered to.
35 *
36 * Define the following functions in the inheriting class, where T is the expected type of the composable parent.
37 *
38 * - `OnCreatedAt(T& parent)` __(required)__
39 * - `OnCopiedAt(T& parent, <Self> const& from)` _(optional)_
40 * - `OnMovedAt(T& parent)` _(optional)_
41 *
42 * In case this component is registered to a composable class which is not convertible to T then `OnCreatedAt`
43 * or the others will be silently ignored.
44 *
45 * If `OnCopiedAt` is defined, it is called when the component got copied to its new parent, and the previous
46 * component boxed in `FAny`.
47 *
48 * If `OnMovedAt` is defined, it is similarly called when the component got moved to its new parent. Components
49 * however are not move constructed, simply they have their ownership transferred, for this reason there's no
50 * "source" component argument, as that would be redundant.
51 */
53 {
54 // OnCreatedAt(T& parent);
55
56 // and optionally:
57 // OnCopiedAt(T& parent, <Self> const& from);
58 // OnMovedAt(T& parent);
59 };
60
61 /**
62 * @brief
63 * Inherit from this empty interface to signal that the inheriting class knows that it's a component and that it
64 * can receive info about the composable class it is being registered to.
65 *
66 * Define the following functions in the inheriting class, where T is the expected type of the composable parent.
67 *
68 * - `OnCreatedAt(T& parent)` __(required)__
69 * - `OnCopiedAt(T& parent, <Self> const& from)` _(optional)_
70 * - `OnMovedAt(T& parent)` _(optional)_
71 *
72 * In case of `IStrictComponent`, it is a compile error to register this class to a composable class which is not
73 * convertible to T.
74 *
75 * If `OnCopiedAt` is defined, it is called when the component got copied to its new parent. The second argument is
76 * the source component.
77 *
78 * If `OnMovedAt` is defined, it is similarly called when the component got moved to its new parent. Components
79 * however are not move constructed, simply they have their ownership transferred, for this reason there's no
80 * "source" component argument, as that would be redundant.
81 */
83 {
84 // OnCreatedAt(T& parent);
85
86 // and optionally:
87 // OnCopiedAt(T& parent, <Self> const& from);
88 // OnMovedAt(T& parent);
89 };
90
91 template <typename T>
92 concept CComposable = CDerivedFrom<T, IComposable>;
93
94 template <typename T>
95 concept CExplicitComponent = CDerivedFrom<T, IComponent>;
96
97 template <typename T>
98 concept CStrictComponent = CDerivedFrom<T, IStrictComponent>;
99
100 template <typename T, typename Composition>
102 && requires(std::decay_t<T>& t, Composition&& parent) { t.OnCreatedAt(parent); }
103 ;
104
105 template <typename T, typename Composition>
107 && requires(std::decay_t<T>& t, Composition&& parent, T const& from) { t.OnCopiedAt(parent, from); }
108 ;
109
110 template <typename T, typename Composition>
112 && requires(std::decay_t<T>& t, Composition&& parent) { t.OnMovedAt(parent); }
113 ;
114
115 template <typename T, typename Composition>
117
118 template <typename T, typename Composition>
120
121 /**
122 * @brief A base class which can bring type based class-composition to a derived class
123 *
124 * This exists because indeed Unreal has its own composition model (actors / actor-components) or it has the
125 * subsystem architecture for non-actors, they still require to be used with UObjects. `IComposable` allows
126 * any C++ objects to have type-safe runtime managed optional components which can be configured separately for
127 * each instance.
128 *
129 * The only requirement for components is that they need to be copy and move constructible (as is the default with
130 * plain-old-C++ objects, if they don't have members where either constructors are deleted or inaccessible). This
131 * limitation is imposed by `FAny` only for easier interfacing, but the API for managing components will never move
132 * or copy them by itself.
133 * The composable class doesn't have that limitation.
134 *
135 * Usage:
136 * @code
137 * //// Given the following types we want to use as components:
138 *
139 * struct FSimpleComponent { int A = 0; };
140 *
141 * struct IBaseComponent { int B; };
142 *
143 * // | This allows us not repeating ourselves, more on this later
144 * // V
145 * struct FComponentImplementation : TInherit<IBaseComponent>
146 * {
147 * FComponentImplementation(int b, int c) : B(b), C(c) {}
148 * int C;
149 * };
150 *
151 * struct IRegularBase { int D; };
152 *
153 * // | We have to repeat this if we want to get components with this base class
154 * // V
155 * struct FRegularInherited : IRegularBase
156 * {
157 * FRegularInherited(int d, int e) : D(d), E(e) {}
158 * int E;
159 * };
160 *
161 * //// Given the following composable type:
162 *
163 * class FMyComposableType : public IComposable {};
164 *
165 * //// Declare their composition at construction:
166 *
167 * auto MyStuff = FMyComposableType()
168 * .With<FSimpleComponent>() // <- Simply add components with their types
169 * .With(new FComponentImplementation(1, 2)) // <- Or simply use new operator
170 * // (IComposable assumes ownership)
171 * // Because FComponentImplementation uses TInherit
172 * // IBaseComponent is not needed to be repeated here
173 * .With(new FRegularInherited(3, 4)) //
174 * .WithAlias<IRegularBase>() // <- FRegularInherited cannot tell automatically that it
175 * // inherits from IRegularBase so we need to specify
176 * // that here explicitly in case we want to get
177 * // FRegularInherited component via its base class
178 * ;
179 *
180 * //// Get components at runtime:
181 *
182 * int a = MyStuff.Get<FSimpleComponent>().A; //
183 * // -> 0 //
184 * int b = MyStuff.Get<IBaseComponent>().B; // <- We can get the component via base class here, only
185 * // -> 1 // because it was exposed via TInherit
186 * int d = MyStuff.Get<IRegularBase>().D; // <- We can get the component via base class here, because
187 * // -> 3 // we explicitly specified it during registration
188 * FVector* v = MyStuff.TryGet<FVector>(); // <- If there's any doubt that a component may not have
189 * // -> nullptr; FVector wasn't a component // been registered, use TryGet instead.
190 * @endcode
191 *
192 * As mentioned earlier, components are not required to have any arbitrary type traits, but if they inherit from
193 * `IComponent` or `IStrictComponent` they can receive extra information when they're registered for a composable
194 * class. The difference between the two is that `IComponent` doesn't mind if it's attached to a composable class
195 * it doesn't know about, however it is a compile error if an `IStrictComponent` is attempted to be attached to
196 * an incompatible class.
197 *
198 * For example
199 * @code
200 * //// Given the following types we want to use as components:
201 *
202 * struct FChillComponent : IComponent
203 * {
204 * void OnCreatedAt(FExpectedParent& to) {}
205 * };
206 *
207 * struct FStrictComponent : IStrictComponent
208 * {
209 * void OnCreatedAt(FExpectedParent& to) {}
210 * };
211 *
212 * //// Given the following composable types:
213 *
214 * class FExpectedParent : public IComposable {};
215 * class FSomeOtherParent : public IComposable {};
216 *
217 * //// Declare their composition at construction:
218 *
219 * auto MyOtherStuff = FExpectedParent()
220 * .With<FChillComponent>() // OK, and OnCreatedAt is called
221 * .With<FStrictComponent>() // OK, and OnCreatedAt is called
222 *
223 * auto MyStuff = FSomeOtherParent()
224 * .With<FChillComponent>() // OK, but OnCreatedAt won't be called.
225 *
226 * .With<FStrictComponent>() // COMPILE ERROR, CCompatibleComponent concept is not satisfied because
227 * // FSomeOtherParent is not convertible to FExpectedParent at
228 * // OnCreatedAt(FExpectedParent& to)
229 * ;
230 * @endcode
231 *
232 * Explicit components can explicitly support multiple composable classes via function overloading or templating
233 * (with deduced type parameters).
234 *
235 * If a component type uses `TInherit` template or has a `using Bases = TTypes<...>` member alias in a similar way:
236 * @code
237 * class FMyComponent : public TInherit<IFoo, IBar, IEtc>
238 * {
239 * // ...
240 * }
241 * @endcode
242 * Then the specified base classes will be automatically registered as component aliases. When this is used for
243 * explicit components, `IComponent` or `IStrictComponent` is strongly discouraged to be used in `TInherit`'s
244 * parameter pack. So declare inheritance the following way:
245 *
246 * @code
247 * class FMyComponent
248 * : public TInherit<IFoo, IBar, IEtc>
249 * , public IComponent
250 * {
251 * // ...
252 * }
253 * @endcode
254 *
255 * @todo
256 * OnCopiedAt and OnMovedAt doesn't seem reliable currently, the best would be if we could provide a safe way to
257 * keep components updated about their parents, with erasing the parent type on IComposable level, but keeping it
258 * fully typed with components.
259 *
260 * @todo
261 * C++ 26 has promising proposal for static value-based reflection, which can gather metadata from classes
262 * or even emit them. The best summary I found so far is a stack-overflow answer https://stackoverflow.com/a/77477029
263 * Once that's available we can gather base classes in compile time, and do dynamic casting of objects without
264 * the need for intrusive extra syntax, or extra work at construction.
265 * Currently GCC's `__bases` would be perfect for the job, but other popular compilers don't have similar
266 * intrinsics. Once such a feature becomes widely available base classes can be automatically added as aliases for
267 * registered components.
268 */
269 class MCRO_API IComposable
270 {
271 FTypeHash LastAddedComponentHash = 0;
272
273 struct FComponentLogistics
274 {
275 TFunction<void(IComposable* target, FAny const& targetComponent)> Copy;
276 TFunction<void(IComposable* target)> Move;
277 };
278
279 // Using ANSI allocators here because I've seen Unreal default allocators fail when moving or copying composable classes
280 mutable TMap<FTypeHash, FAny> Components;
281 mutable TMap<FTypeHash, FComponentLogistics> ComponentLogistics;
282 mutable TMap<FTypeHash, TArray<FTypeHash>> ComponentAliases;
283
284 bool HasExactComponent(FTypeHash typeHash) const;
285 bool HasComponentAliasUnchecked(FTypeHash typeHash) const;
286 bool HasComponentAlias(FTypeHash typeHash) const;
287 void AddComponentAlias(FTypeHash mainType, FTypeHash validAs);
288
289 void NotifyCopyComponents(IComposable const& other);
290 void NotifyMoveComponents(IComposable&& other);
291 void ResetComponents();
292
293 template <typename ValidAs>
294 void AddComponentAlias(FTypeHash mainType)
295 {
296 Components[mainType].WithAlias<ValidAs>();
297 AddComponentAlias(mainType, TTypeHash<ValidAs>);
298
299 if constexpr (CHasBases<ValidAs>)
300 {
301 ForEachExplicitBase<ValidAs>([&, this] <typename Base> ()
302 {
303 AddComponentAlias(mainType, TTypeHash<Base>);
304 });
305 }
306 }
307
308 ranges::any_view<FAny*> GetExactComponent(FTypeHash typeHash) const;
309 ranges::any_view<FAny*> GetAliasedComponents(FTypeHash typeHash) const;
310
311 protected:
312 /**
313 * @brief
314 * Override this function in your composable class to do custom logic when a component is added. A bit of
315 * dynamically typed programming is needed through the FAny API.
316 *
317 * This is executed in AddComponent before IComponent::OnCreatedAt and after automatic aliases has
318 * been set up (if they're available). This is not executed with subsequent setup of manual aliases.
319 *
320 * @param component The component being added. Query component type with the FAny API
321 */
322 TFunction<void(FAny&)> OnComponentAdded;
323
324 public:
325
326 IComposable() = default;
328 IComposable(IComposable&& other) noexcept;
329
330 /**
331 * @brief Get components determined at runtime
332 * @param typeHash The runtime determined type-hash the desired components are represented with
333 * @return A type erased range view for all the components matched with given type-hash
334 */
335 ranges::any_view<FAny*> GetComponentsDynamic(FTypeHash typeHash) const;
336
337 /**
338 * @brief
339 * Add a component to this composable class.
340 *
341 * @tparam MainType The exact component type (deduced from `newComponent`
342 * @tparam Self Deducing this
343 * @param self Deducing this
344 *
345 * @param newComponent
346 * A pointer to the new component being added. `IComposable` will assume ownership of the new component
347 * adhering to RAII. Make sure the lifespan of the provided object is not managed by something else or the
348 * stack, in fact better to stick with the `new` operator.
349 *
350 * @param facilities
351 * Customization point for object copy/move and delete methods. See `TAnyTypeFacilities`
352 */
353 template <typename MainType, typename Self>
355 void AddComponent(this Self&& self, MainType* newComponent, TAnyTypeFacilities<MainType> const& facilities = {})
356 {
357 ASSERT_CRASH(newComponent);
358 ASSERT_CRASH(!self.HasExactComponent(TTypeHash<MainType>),
359 ->WithMessageF(
360 TEXT_"{0} cannot be added because another component already exists under that type.",
361 TTypeName<MainType>
362 )
363 ->WithDetailsF(TEXT_
364 "Try wrapping your component in an empty derived type, and register it with its base type {0} as its"
365 " alias. Later on both the current and the already existing component can be accessed via"
366 " `GetComponents<{0}>()` which returns a range of all matching components.",
367 TTypeName<MainType>
368 )
369 );
370
371 FAny& boxedComponent = self.Components.Add(TTypeHash<MainType>, FAny(newComponent, facilities));
372 MainType* unboxedComponent = boxedComponent.TryGet<MainType>();
373
374 self.LastAddedComponentHash = TTypeHash<MainType>;
375 if constexpr (CHasBases<MainType>)
376 {
377 ForEachExplicitBase<MainType>([&] <typename Base> ()
378 {
379 // FAny also deals with CHasBases so we can skip explicitly registering them here
380 self.AddComponentAlias(TTypeHash<MainType>, TTypeHash<Base>);
381 });
382 }
383
384 if (self.OnComponentAdded) self.OnComponentAdded(boxedComponent);
385 if constexpr (CCompatibleExplicitComponent<MainType, Self>)
386 {
387 unboxedComponent->OnCreatedAt(self);
388 if constexpr (CCopyAwareComponent<MainType, Self> || CMoveAwareComponent<MainType, Self>)
389 {
390 self.ComponentLogistics.Add(TTypeHash<MainType>, {
391 .Copy = [unboxedComponent](IComposable* target, FAny const& targetBoxedComponent)
392 {
393 // TODO: Provide safe parent reference mechanism without smart pointers because this doesn't seem to work well
394 if constexpr (CCopyAwareComponent<MainType, Self>)
395 {
396 auto targetComponent = AsMutablePtr(targetBoxedComponent.TryGet<MainType>());
397 ASSERT_CRASH(targetComponent,
398 ->WithMessageF(
399 TEXT_"{0} component cannot be copied as its destination wrapper was incompatible.",
400 TTypeName<MainType>
401 )
402 );
403 targetComponent->OnCopiedAt(*static_cast<Self*>(target), *unboxedComponent);
404 }
405 },
406 .Move = [unboxedComponent](IComposable* target)
407 {
408 // TODO: Provide safe parent reference mechanism without smart pointers because this doesn't seem to work well
409 if constexpr (CMoveAwareComponent<MainType, Self>)
410 unboxedComponent->OnMovedAt(*static_cast<Self*>(target));
411 }
412 });
413 }
414 }
415 }
416
417 /**
418 * @brief
419 * Add a default constructed component to this composable class.
420 *
421 * @tparam MainType The exact component type
422 * @tparam Self Deducing this
423 * @param self Deducing this
424 *
425 * @param facilities
426 * Customization point for object copy/move and delete methods. See `TAnyTypeFacilities`
427 */
428 template <CDefaultInitializable MainType, typename Self>
429 requires CCompatibleComponent<MainType, Self>
430 void AddComponent(this Self&& self, TAnyTypeFacilities<MainType> const& facilities = {})
431 {
432 FWD(self).template AddComponent<MainType, Self>(new MainType(), facilities);
433 }
434
435 /**
436 * @brief
437 * Add a list of types the last added component is convertible to and may be used to get the last component
438 * among others which may list the same aliases.
439 *
440 * @warning
441 * Calling this function before adding a component may result in a runtime crash!
442 *
443 * @tparam ValidAs
444 * The list of other types the last added component is convertible to and may be used to get the last component
445 * among others which may list the same aliases.
446 */
447 template <typename... ValidAs>
448 void AddAlias()
449 {
450 ASSERT_CRASH(LastAddedComponentHash != 0 && Components.Contains(LastAddedComponentHash),
451 ->WithMessage(TEXT_"Component aliases were listed, but no components were added before.")
452 ->WithDetails(TEXT_"Make sure `AddAlias` or `WithAlias` is called after `AddComponent` / `With`.")
453 );
454 (AddComponentAlias<ValidAs>(LastAddedComponentHash), ...);
455 }
456
457 /**
458 * @brief
459 * Add a component to this composable class with a fluent API.
460 *
461 * This overload is available for composable classes which also inherit from `TSharedFromThis`.
462 *
463 * @tparam MainType The exact component type (deduced from `newComponent`
464 * @tparam Self Deducing this
465 * @param self Deducing this
466 *
467 * @param newComponent
468 * A pointer to the new component being added. `IComposable` will assume ownership of the new component
469 * adhering to RAII. Make sure the lifespan of the provided object is not managed by something else or the
470 * stack, in fact better to stick with the `new` operator.
471 *
472 * @param facilities
473 * Customization point for object copy/move and delete methods. See `TAnyTypeFacilities`
474 *
475 * @return
476 * If the composable class also inherits from `TSharedFromThis` return a shared ref.
477 */
478 template <typename MainType, CSharedFromThis Self>
480 auto With(this Self&& self, MainType* newComponent, TAnyTypeFacilities<MainType> const& facilities = {})
481 {
482 FWD(self).template AddComponent<MainType, Self>(newComponent, facilities);
483 return SharedSelf(&self);
484 }
485
486 /**
487 * @brief
488 * Add a component to this composable class with a fluent API, enforcing standard memory allocators.
489 *
490 * This overload is available for composable classes which also inherit from `TSharedFromThis`.
491 *
492 * @tparam MainType The exact component type (deduced from `newComponent`
493 * @tparam Self Deducing this
494 * @param self Deducing this
495 *
496 * @param newComponent
497 * A pointer to the new component being added. `IComposable` will assume ownership of the new component
498 * adhering to RAII. Make sure the lifespan of the provided object is not managed by something else or the
499 * stack, in fact better to stick with the `new` operator.
500 *
501 * @return
502 * If the composable class also inherits from `TSharedFromThis` return a shared ref.
503 */
504 template <typename MainType, CSharedFromThis Self>
505 requires CCompatibleComponent<MainType, Self>
506 auto WithAnsi(this Self&& self, MainType* newComponent)
507 {
508 FWD(self).template AddComponent<MainType, Self>(newComponent, AnsiAnyFacilities<MainType>);
509 return SharedSelf(&self);
510 }
511
512 /**
513 * @brief
514 * Add a default constructed component to this composable class with a fluent API.
515 *
516 * This overload is available for composable classes which also inherit from `TSharedFromThis`.
517 *
518 * @tparam MainType The exact component type
519 * @tparam Self Deducing this
520 * @param self Deducing this
521 *
522 * @param facilities
523 * Customization point for object copy/move and delete methods. See `TAnyTypeFacilities`
524 *
525 * @return
526 * If the composable class also inherits from `TSharedFromThis` return a shared ref.
527 */
528 template <CDefaultInitializable MainType, CSharedFromThis Self>
530 auto With(this Self&& self, TAnyTypeFacilities<MainType> const& facilities = {})
531 {
532 FWD(self).template AddComponent<MainType, Self>(facilities);
533 return SharedSelf(&self);
534 }
535
536 /**
537 * @brief
538 * Add a default constructed component to this composable class with a fluent API, enforcing standard memory
539 * allocators.
540 *
541 * This overload is available for composable classes which also inherit from `TSharedFromThis`.
542 *
543 * @tparam MainType The exact component type
544 * @tparam Self Deducing this
545 * @param self Deducing this
546 *
547 * @param facilities
548 * Customization point for object copy/move and delete methods. See `TAnyTypeFacilities`
549 *
550 * @return
551 * If the composable class also inherits from `TSharedFromThis` return a shared ref.
552 */
553 template <CDefaultInitializable MainType, CSharedFromThis Self>
554 requires CCompatibleComponent<MainType, Self>
555 auto WithAnsi(this Self&& self)
556 {
557 FWD(self).template AddComponent<MainType, Self>(Ansi::New<MainType>(), AnsiAnyFacilities<MainType>);
558 return SharedSelf(&self);
559 }
560
561 /**
562 * @brief
563 * Add a component to this composable class with a fluent API.
564 *
565 * This overload is available for composable classes which are not explicitly meant to be used with shared pointers.
566 *
567 * @tparam MainType The exact component type (deduced from `newComponent`
568 * @tparam Self Deducing this
569 * @param self Deducing this
570 *
571 * @param newComponent
572 * A pointer to the new component being added. `IComposable` will assume ownership of the new component
573 * adhering to RAII. Make sure the lifespan of the provided object is not managed by something else or the
574 * stack, in fact better to stick with the `new` operator.
575 *
576 * @param facilities
577 * Customization point for object copy/move and delete methods. See `TAnyTypeFacilities`
578 *
579 * @return
580 * Perfect-forwarded self.
581 */
582 template <typename MainType, typename Self>
583 requires (CCompatibleComponent<MainType, Self> && !CSharedFromThis<Self>)
584 decltype(auto) With(this Self&& self, MainType* newComponent, TAnyTypeFacilities<MainType> const& facilities = {})
585 {
586 FWD(self).template AddComponent<MainType, Self>(newComponent, facilities);
587 return FWD(self);
588 }
589
590 /**
591 * @brief
592 * Add a component to this composable class with a fluent API, enforcing standard memory allocators.
593 *
594 * This overload is available for composable classes which are not explicitly meant to be used with shared pointers.
595 *
596 * @tparam MainType The exact component type (deduced from `newComponent`
597 * @tparam Self Deducing this
598 * @param self Deducing this
599 *
600 * @param newComponent
601 * A pointer to the new component being added. `IComposable` will assume ownership of the new component
602 * adhering to RAII. Make sure the lifespan of the provided object is not managed by something else or the
603 * stack, in fact better to stick with the `new` operator.
604 *
605 * @return
606 * Perfect-forwarded self.
607 */
608 template <typename MainType, typename Self>
609 requires (CCompatibleComponent<MainType, Self> && !CSharedFromThis<Self>)
610 decltype(auto) WithAnsi(this Self&& self, MainType* newComponent)
611 {
612 FWD(self).template AddComponent<MainType, Self>(newComponent, AnsiAnyFacilities<MainType>);
613 return FWD(self);
614 }
615
616 /**
617 * @brief
618 * Add a default constructed component to this composable class with a fluent API.
619 *
620 * This overload is available for composable classes which are not explicitly meant to be used with shared pointers.
621 *
622 * @tparam MainType The exact component type
623 * @tparam Self Deducing this
624 * @param self Deducing this
625 *
626 * @param facilities
627 * Customization point for object copy/move and delete methods. See `TAnyTypeFacilities`
628 *
629 * @return
630 * Perfect-forwarded self.
631 */
632 template <CDefaultInitializable MainType, typename Self>
633 requires (CCompatibleComponent<MainType, Self> && !CSharedFromThis<Self>)
634 decltype(auto) With(this Self&& self, TAnyTypeFacilities<MainType> const& facilities = {})
635 {
636 FWD(self).template AddComponent<MainType, Self>(facilities);
637 return FWD(self);
638 }
639
640 /**
641 * @brief
642 * Add a default constructed component to this composable class with a fluent API, enforcing standard memory
643 * allocators.
644 *
645 * This overload is available for composable classes which are not explicitly meant to be used with shared pointers.
646 *
647 * @tparam MainType The exact component type
648 * @tparam Self Deducing this
649 * @param self Deducing this
650 *
651 * @return
652 * Perfect-forwarded self.
653 */
654 template <CDefaultInitializable MainType, typename Self>
655 requires (CCompatibleComponent<MainType, Self> && !CSharedFromThis<Self>)
656 decltype(auto) WithAnsi(this Self&& self)
657 {
658 FWD(self).template AddComponent<MainType, Self>(Ansi::New<MainType>(), AnsiAnyFacilities<MainType>);
659 return FWD(self);
660 }
661
662 /**
663 * @brief
664 * Add a type, the last added component is convertible to and may be used to get the last component among
665 * others which may list the same aliases.
666 *
667 * Only one alias may be specified this way because of templating syntax intricacies, but it may be called
668 * multiple times in a sequence to add multiple aliases for the same component.
669 *
670 * Usage:
671 * @code
672 * auto result = IComposable()
673 * .With<FMyComponent>()
674 * .WithAlias<FMyComponentBase>()
675 * .WithAlias<IMyComponentInterface>()
676 * ;
677 * @endcode
678 *
679 * For declaring multiple aliases in one go, use `With(TTypes<...>)` member template method.
680 *
681 * This overload is available for composable classes which also inherit from `TSharedFromThis`.
682 *
683 * @warning
684 * Calling this function before adding a component may result in a runtime crash!
685 *
686 * @tparam ValidAs
687 * A type, the last added component is convertible to and may be used to get the last component among others
688 * which may list the same aliases.
689 *
690 * @tparam Self Deducing this
691 * @param self Deducing this
692 *
693 * @return
694 * If the composable class also inherits from `TSharedFromThis` return a shared ref.
695 */
696 template <typename ValidAs, CSharedFromThis Self>
697 auto WithAlias(this Self&& self)
698 {
699 FWD(self).template AddAlias<ValidAs>();
700 return SharedSelf(&self);
701 }
702
703 /**
704 * @brief
705 * Add a type, the last added component is convertible to and may be used to get the last component among
706 * others which may list the same aliases.
707 *
708 * Only one alias may be specified this way because of templating syntax intricacies, but it may be called
709 * multiple times in a sequence to add multiple aliases for the same component.
710 *
711 * Usage:
712 * @code
713 * auto result = IComposable()
714 * .With<FMyComponent>()
715 * .WithAlias<FMyComponentBase>()
716 * .WithAlias<IMyComponentInterface>()
717 * ;
718 * @endcode
719 *
720 * For declaring multiple aliases in one go, use `With(TTypes<...>)` member template method.
721 *
722 * This overload is available for composable classes which are not explicitly meant to be used with shared pointers.
723 *
724 * @warning
725 * Calling this function before adding a component may result in a runtime crash!
726 *
727 * @tparam ValidAs
728 * A type, the last added component is convertible to and may be used to get the last component among others
729 * which may list the same aliases.
730 *
731 * @tparam Self Deducing this
732 * @param self Deducing this
733 *
734 * @return
735 * Perfect-forwarded self.
736 */
737 template <typename ValidAs, typename Self>
738 requires (!CSharedFromThis<Self>)
739 decltype(auto) WithAlias(this Self&& self)
740 {
741 FWD(self).template AddAlias<ValidAs>();
742 return FWD(self);
743 }
744
745 /**
746 * @brief
747 * Add a list of types the last added component is convertible to and may be used to get the last component
748 * among others which may list the same aliases.
749 *
750 * Usage:
751 * @code
752 * auto result = IComposable()
753 * .With<FMyComponent>().With(TAlias<
754 * FMyComponentBase,
755 * IMyComponentInterface
756 * >)
757 * ;
758 * @endcode
759 *
760 * This overload is available for composable classes which also inherit from `TSharedFromThis`.
761 *
762 * @warning
763 * Calling this function before adding a component may result in a runtime crash!
764 *
765 * @tparam ValidAs
766 * The list of other types the last added component is convertible to and may be used to get the last component
767 * among others which may list the same aliases.
768 *
769 * @tparam Self Deducing this
770 * @param self Deducing this
771 *
772 * @return
773 * If the composable class also inherits from `TSharedFromThis` return a shared ref.
774 */
775 template <CSharedFromThis Self, typename... ValidAs>
776 auto With(this Self&& self, TTypes<ValidAs...>&&)
777 {
778 FWD(self).template AddAlias<ValidAs...>();
779 return SharedSelf(&self);
780 }
781
782 /**
783 * @brief
784 * Add a list of types the last added component is convertible to and may be used to get the last component
785 * among others which may list the same aliases.
786 *
787 * Usage:
788 * @code
789 * auto result = IComposable()
790 * .With<FMyComponent>().With(TAlias<
791 * FMyComponentBase,
792 * IMyComponentInterface
793 * >)
794 * ;
795 * @endcode
796 *
797 * This overload is available for composable classes which are not explicitly meant to be used with shared pointers.
798 *
799 * @warning
800 * Calling this function before adding a component may result in a runtime crash!
801 *
802 * @tparam ValidAs
803 * The list of other types the last added component is convertible to and may be used to get the last component
804 * among others which may list the same aliases.
805 *
806 * @tparam Self Deducing this
807 * @param self Deducing this
808 *
809 * @return
810 * Perfect-forwarded self.
811 */
812 template <typename Self, typename... ValidAs>
813 requires (!CSharedFromThis<Self>)
814 decltype(auto) With(this Self&& self, TTypes<ValidAs...>&&)
815 {
816 FWD(self).template AddAlias<ValidAs...>();
817 return FWD(self);
818 }
819
820 /**
821 * @brief
822 * Modify a component inline, with a lambda function. The component type is inferred from the function's first
823 * argument, and a reference of that component is passed into it. The component must exist before calling this
824 * method, or if it doesn't, the application will crash.
825 *
826 * @tparam Self Deducing this
827 *
828 * @tparam Function
829 * Function type for modifying a component inline. The component type is deduced from the first parameter of the
830 * function. CV-ref qualifiers are not enforced but mutable-ref or const-ref are the only useful options.
831 * Function result is discarded when returning anything.
832 *
833 * @param self Deducing this
834 *
835 * @param function
836 * Function for modifying a component inline. The component type is deduced from the first parameter of the
837 * function. CV-ref qualifiers are not enforced but mutable-ref or const-ref are the only useful options.
838 * Function result is discarded when returning anything.
839 *
840 * @return
841 * If the composable class also inherits from `TSharedFromThis` return a shared ref.
842 */
843 template <
844 CSharedFromThis Self,
845 CFunctionLike Function
846 >
847 requires (TFunction_ArgCount<Function> == 1)
848 auto With(this Self&& self, Function&& function)
849 {
850 function(self.template Get<TFunction_ArgDecay<Function, 0>>());
851 return SharedSelf(&self);
852 }
853
854 /**
855 * @brief
856 * Modify a component inline, with a lambda function. The component type is inferred from the function's first
857 * argument, and a reference of that component is passed into it. The component must exist before calling this
858 * method, or if it doesn't, the application will crash.
859 *
860 * @tparam Self Deducing this
861 *
862 * @tparam Function
863 * Function type for modifying a component inline. The component type is deduced from the first parameter of the
864 * function. CV-ref qualifiers are not enforced but mutable-ref or const-ref are the only useful options.
865 * Function result is discarded when returning anything.
866 *
867 * @param self Deducing this
868 *
869 * @param function
870 * Function for modifying a component inline. The component type is deduced from the first parameter of the
871 * function. CV-ref qualifiers are not enforced but mutable-ref or const-ref are the only useful options.
872 * Function result is discarded when returning anything.
873 *
874 * @return
875 * Perfect-forwarded self.
876 */
877 template <
878 typename Self,
879 CFunctionLike Function
880 >
881 requires (!CSharedFromThis<Self> && TFunction_ArgCount<Function> == 1)
882 decltype(auto) With(this Self&& self, Function&& function)
883 {
884 function(self.template Get<TFunction_ArgDecay<Function, 0>>());
885 return FWD(self);
886 }
887
888 /**
889 * @brief
890 * Get all components added matching~ or aliased by the given type.
891 *
892 * @tparam T Desired component type.
893 *
894 * @return
895 * A range-view containing all the matched components. Components are provided as pointers to ensure they're
896 * not copied even under intricate object plumbing situations, but invalid pointers are never returned.
897 * (as long as the composable class is alive of course)
898 */
899 template <typename T>
900 ranges::any_view<T*> GetComponents() const
901 {
902 namespace rv = ranges::views;
903 return GetComponentsDynamic(TTypeHash<T>)
904 | rv::transform([](FAny* component) { return component->TryGet<T>(); })
905 | FilterValid();
906 }
907
908 /**
909 * @brief
910 * Get the first component matching~ or aliased by the given type.
911 *
912 * The order of components are non-deterministic so this method only make sense when it is trivial that only
913 * one component will be available for that particular type.
914 *
915 * @tparam T Desired component type.
916 *
917 * @return
918 * A pointer to the component if one at least exists, nullptr otherwise.
919 */
920 template <typename T>
921 const T* TryGet() const
922 {
923 return GetComponents<T>() | First(nullptr);
924 }
925
926 /**
927 * @brief
928 * Get the first component matching~ or aliased by the given type.
929 *
930 * The order of components are non-deterministic so this method only make sense when it is trivial that only
931 * one component will be available for that particular type.
932 *
933 * @tparam T Desired component type.
934 *
935 * @return
936 * A pointer to the component if one at least exists, nullptr otherwise.
937 */
938 template <typename T>
940 {
941 return GetComponents<T>() | First(nullptr);
942 }
943
944 /**
945 * @brief
946 * Get the first component matching~ or aliased by the given type.
947 *
948 * The order of components are non-deterministic so this method only make sense when it is trivial that only
949 * one component will be available for that particular type.
950 *
951 * @warning
952 * If there may be the slightest doubt that the given component may not exist on this composable class, use
953 * `TryGet` instead as this function can crash at runtime.
954 *
955 * @tparam T Desired component type.
956 *
957 * @return
958 * A reference to the desired component. It is a runtime crash if the component doesn't exist.
959 */
960 template <typename T>
961 T const& Get() const
962 {
963 const T* result = TryGet<T>();
964 ASSERT_CRASH(result, ->WithMessageF(TEXT_"Component {0} was unavailable.", TTypeName<T>));
965 return *result;
966 }
967
968 /**
969 * @brief
970 * Get the first component matching~ or aliased by the given type.
971 *
972 * The order of components are non-deterministic so this method only make sense when it is trivial that only
973 * one component will be available for that particular type.
974 *
975 * @warning
976 * If there may be the slightest doubt that the given component may not exist on this composable class, use
977 * `TryGet` instead as this function can crash at runtime.
978 *
979 * @tparam T Desired component type.
980 *
981 * @return
982 * A reference to the desired component. It is a runtime crash if the component doesn't exist.
983 */
984 template <typename T>
985 T& Get()
986 {
987 T* result = TryGet<T>();
988 ASSERT_CRASH(result, ->WithMessageF(TEXT_"Component {0} was unavailable.", TTypeName<T>));
989 return *result;
990 }
991 };
992}
#define ASSERT_CRASH(condition,...)
Use this instead of check macro if the checked expression shouldn't be ignored in shipping builds....
#define FWD(...)
Shorten forwarding expression with this macro so one may not need to specify explicit type.
Definition Macros.h:100
Bring modern declarative range operations like views and actions to the Unreal C++ arsenal....
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
A base class which can bring type based class-composition to a derived class.
decltype(auto) With(this Self &&self, TAnyTypeFacilities< MainType > const &facilities={})
Add a default constructed component to this composable class with a fluent API.
auto With(this Self &&self, MainType *newComponent, TAnyTypeFacilities< MainType > const &facilities={})
Add a component to this composable class with a fluent API.
decltype(auto) WithAnsi(this Self &&self, MainType *newComponent)
Add a component to this composable class with a fluent API, enforcing standard memory allocators.
auto WithAnsi(this Self &&self)
Add a default constructed component to this composable class with a fluent API, enforcing standard me...
auto WithAlias(this Self &&self)
Add a type, the last added component is convertible to and may be used to get the last component amon...
decltype(auto) WithAlias(this Self &&self)
Add a type, the last added component is convertible to and may be used to get the last component amon...
T & Get()
Get the first component matching~ or aliased by the given type.
decltype(auto) With(this Self &&self, MainType *newComponent, TAnyTypeFacilities< MainType > const &facilities={})
Add a component to this composable class with a fluent API.
decltype(auto) With(this Self &&self, TTypes< ValidAs... > &&)
Add a list of types the last added component is convertible to and may be used to get the last compon...
T const & Get() const
Get the first component matching~ or aliased by the given type.
void AddAlias()
Add a list of types the last added component is convertible to and may be used to get the last compon...
void AddComponent(this Self &&self, TAnyTypeFacilities< MainType > const &facilities={})
Add a default constructed component to this composable class.
ranges::any_view< T * > GetComponents() const
Get all components added matching~ or aliased by the given type.
IComposable(IComposable &&other) noexcept
IComposable(const IComposable &other)
decltype(auto) WithAnsi(this Self &&self)
Add a default constructed component to this composable class with a fluent API, enforcing standard me...
auto With(this Self &&self, TTypes< ValidAs... > &&)
Add a list of types the last added component is convertible to and may be used to get the last compon...
T * TryGet()
Get the first component matching~ or aliased by the given type.
ranges::any_view< FAny * > GetComponentsDynamic(FTypeHash typeHash) const
Get components determined at runtime.
auto With(this Self &&self, TAnyTypeFacilities< MainType > const &facilities={})
Add a default constructed component to this composable class with a fluent API.
const T * TryGet() const
Get the first component matching~ or aliased by the given type.
auto WithAnsi(this Self &&self, MainType *newComponent)
Add a component to this composable class with a fluent API, enforcing standard memory allocators.
TFunction< void(FAny &)> OnComponentAdded
Override this function in your composable class to do custom logic when a component is added....
decltype(auto) With(this Self &&self, Function &&function)
Modify a component inline, with a lambda function. The component type is inferred from the function's...
void AddComponent(this Self &&self, MainType *newComponent, TAnyTypeFacilities< MainType > const &facilities={})
Add a component to this composable class.
auto With(this Self &&self, Function &&function)
Modify a component inline, with a lambda function. The component type is inferred from the function's...
T * New(Args &&... args)
Force using the ANSI memory allocation behavior, instead of the Unreal default.
Definition New.h:32
Namespace containing utilities and base classes for type composition.
Definition Composition.h:25
typename TFunctionTraits< std::decay_t< T > >::template ArgDecay< I > TFunction_ArgDecay
Shorthand for getting a decayed type of a function argument at given position I.
decltype(auto) First(Input &&range, Value &&def)
Get's the first element of a range or return a provided default value. Same as *r....
Definition Views.h:67
FORCEINLINE auto FilterValid()
Definition Views.h:181
auto SharedSelf(const T *self) -> TSharedRef< T const, Mode >
Same as SharedThis(this) in TSharedFromThis.
uint64 FTypeHash
Definition TypeName.h:103
A simplistic but type-safe and RAII compliant storage for anything. Enclosed data is owned by this ty...
Definition Any.h:93
const T * TryGet() const
Definition Any.h:130
Give the opportunity to customize object lifespan operations for FAny by either specializing this tem...
Definition Any.h:42
Inherit from this empty interface to signal that the inheriting class knows that it's a component and...
Definition Composition.h:53
Inherit from this empty interface to signal that the inheriting class knows that it's a component and...
Definition Composition.h:83
This template is used to store pack of types in other templates, or to allow parameter pack inference...
Definition Templates.h:106