MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
Subsystems.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/Concepts.h"
16#include "Mcro/AssertMacros.h"
17#include "Kismet/GameplayStatics.h"
18
20{
21 using namespace Mcro::Concepts;
22 using namespace Mcro::TypeName;
23
24 template<typename T>
25 concept CSubsystem = CDerivedFrom<T, USubsystem>;
26
27 template<typename T>
28 concept CEngineSubsystem = CDerivedFrom<T, UEngineSubsystem>;
29
30 template<typename T>
31 concept CGameInstanceSubsystem = CDerivedFrom<T, UGameInstanceSubsystem>;
32
33 template<typename T>
34 concept CLocalPlayerSubsystem = CDerivedFrom<T, ULocalPlayerSubsystem>;
35
36 template<typename T>
37 concept CWorldSubsystem = CDerivedFrom<T, UWorldSubsystem>;
38
44
45 namespace Subsystems
46 {
47 /**
48 * Helper for getting a subsystem, the internal boilerplate will be chosen based on the type of subsystem T
49 * inherits from. Required arguments may vary depending on the type of subsystem.
50 *
51 * @tparam T UEngineSubsystem derivative
52 * @return Subsystem if exists or nullptr
53 */
54 template<CEngineSubsystem T>
55 T* Get()
56 {
57 return GEngine ? GEngine->GetEngineSubsystem<T>() : nullptr;
58 }
59
60 /**
61 * Helper for getting a subsystem, the internal boilerplate will be chosen based on the type of subsystem T
62 * inherits from. Required arguments may vary depending on the type of subsystem.
63 *
64 * @tparam T UGameInstanceSubsystem derivative
65 * @return Subsystem if exists or nullptr
66 */
67 template<CGameInstanceSubsystem T>
68 T* Get(
69 const UObject* worldContextObject = nullptr,
71 EGetWorldErrorMode errorMode = EGetWorldErrorMode::LogAndReturnNull
72 )
73 {
74 if (!GEngine)
75 {
76 return nullptr;
77 }
78 if (worldContextObject == nullptr)
79 {
80 if (IsRunningDedicatedServer() || GEngine->GameViewport == nullptr)
81 {
83 }
84 switch (fallback)
85 {
86 default:
88 {
89 const FWorldContext* worldContext = GEngine->GameViewport
90 ? GEngine->GetWorldContextFromGameViewport(GEngine->GameViewport)
91 : nullptr;
92 const UGameInstance* owningGameInstance = worldContext ? worldContext->OwningGameInstance : nullptr;
93 return owningGameInstance ? owningGameInstance->GetSubsystem<T>() : nullptr;
94 }
96 {
97 for (auto it = GEngine->GetWorldContexts().CreateConstIterator(); it; ++it)
98 {
99 if (T* result = it->OwningGameInstance ? it->OwningGameInstance->GetSubsystem<T>() : nullptr)
100 {
101 return result;
102 }
103 }
104 return nullptr;
105 }
106 }
107 }
108 if (const UWorld* world = GEngine->GetWorldFromContextObject(worldContextObject, errorMode))
109 {
110 return UGameInstance::GetSubsystem<T>(world->GetGameInstance());
111 }
112 return nullptr;
113 }
114
115 /**
116 * Helper for getting a subsystem, the internal boilerplate will be chosen based on the type of subsystem T
117 * inherits from. Required arguments may vary depending on the type of subsystem.
118 *
119 * @tparam T ULocalPlayerSubsystem derivative
120 * @return Subsystem if exists or nullptr
121 */
122 template<CLocalPlayerSubsystem T>
123 T* Get(const UObject* worldContext)
124 {
125 const UWorld* world = GEngine->GetWorldFromContextObject(worldContext, EGetWorldErrorMode::LogAndReturnNull);
126 APlayerController* controller = world && !world->IsNetMode(NM_DedicatedServer)
127 ? UGameplayStatics::GetPlayerController(worldContext, 0)
128 : nullptr;
129 return controller ? ULocalPlayer::GetSubsystem<T>(controller->GetLocalPlayer()) : nullptr;
130 }
131
132 /**
133 * Helper for getting a subsystem, the internal boilerplate will be chosen based on the type of subsystem T
134 * inherits from. Required arguments may vary depending on the type of subsystem.
135 *
136 * @tparam T UWorldSubsystem derivative
137 * @return Subsystem if exists or nullptr
138 */
139 template<CWorldSubsystem T>
140 T* Get(const UObject* worldContext, EGetWorldErrorMode errorMode = EGetWorldErrorMode::LogAndReturnNull)
141 {
142 if (const UWorld* world = GEngine ? GEngine->GetWorldFromContextObject(worldContext, errorMode) : nullptr)
143 {
144 return world->GetSubsystem<T>();
145 }
146 return nullptr;
147 }
148
149 /**
150 * Helper for getting a subsystem, the internal boilerplate will be chosen based on the type of subsystem T
151 * inherits from. Required arguments may vary depending on the type of subsystem. This is a checked version so
152 * if any steps fail to produce the target subsystem the program may crash.
153 *
154 * @tparam T type of the subsystem
155 * @return Guaranteed valid reference to a subsystem
156 */
157 template<CSubsystem T, typename... Args>
158 T& GetChecked(Args... args)
159 {
160 T* result = Get<T>(args...);
161
162 ASSERT_CRASH(result,
163 ->WithMessageF(TEXT("Couldn't find required subsystem %s"), *TTypeString<T>)
164 )
165
166 return *result;
167 }
168
169 /**
170 * Helper for getting a subsystem, the internal boilerplate will be chosen based on the type of subsystem T
171 * inherits from. Required arguments may vary depending on the type of subsystem. This is an ensured version so
172 * if any steps fail to produce the target subsystem an ensure may be hit.
173 *
174 * @tparam T type of the subsystem
175 * @return Subsystem if exists or nullptr
176 */
177 template<CSubsystem T, typename... Args>
178 T* GetEnsured(Args... args)
179 {
180 T* result = Get<T>(args...);
181 ensure(result);
182 return result;
183 }
184
185 /**
186 * Helper for checking if some other subsystem should be created. Useful when you want to make a subsystem
187 * which should only be created if some other subsystem should be also created.
188 *
189 * @tparam T type of the subsystem
190 *
191 * @param outer
192 * be sure to pass outer argument which type aligns with checked subsystem outer. This may fail for example
193 * if you pass game instance as outer to check for world subsystem existence as ShouldCreateSubsystem of that
194 * subsystem may expect the outer to be World.
195 *
196 * @return True if the target subsystem should be created
197 */
198 template<CSubsystem T>
199 bool ShouldCreateSubsystem(UObject* outer)
200 {
201 const USubsystem* cdo = T::StaticClass()->template GetDefaultObject<USubsystem>();
202 return cdo->ShouldCreateSubsystem(outer);
203 }
204 }
205}
#define ASSERT_CRASH(condition,...)
bool ShouldCreateSubsystem(UObject *outer)
Definition Subsystems.h:199
T & GetChecked(Args... args)
Definition Subsystems.h:158
T * GetEnsured(Args... args)
Definition Subsystems.h:178
const FString TTypeString
Definition TypeName.h:146