Created
May 21, 2021 17:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "CoreMinimal.h" | |
#include "Engine/World.h" | |
#include "EngineUtils.h" | |
DECLARE_LOG_CATEGORY_EXTERN(LogWorldSingleton, Log, All); | |
/* | |
* TWorldSingleton is a weak pointer to a actor in a level, which is only a | |
* singleton by convention of only one actor of that type being in a UWorld. | |
* Unlike code based singletons, it is therefore okay to have multiple | |
* TWorldSingleton's pointing to the same actor, thought this is typically | |
* unnecessary and less performant since each instance of TWorldSingleton must | |
* do an actor search the first time it is used. | |
* | |
* Example Use Case | |
* If you have an single inventory catalog actor in your level that all other | |
* actors should be able to find, you can use TWorldSingleton to access it. | |
*/ | |
template <typename ActorType> | |
class TWorldSingleton | |
{ | |
private: | |
TWeakObjectPtr<ActorType> Singleton; | |
TWeakObjectPtr<UWorld> SingletonWorld; | |
public: | |
// Get the actor instance in this world, if any, logging errors if there is more than one. | |
// This function should only be called after all actors are loaded. You wouldn't want to use it, | |
// for example, in a constructor. | |
ActorType* Get(UWorld* World) | |
{ | |
if (!World) | |
{ | |
// Although Singleton.Get() could be returned here, don't. Rather, | |
// error out on null World now, so that anyone passing in a null | |
// world know about it immediately. Otherwise, if another call site is correctly called | |
// Get() with a valid world before the incorrect call, the incorrect call would succeed | |
// (in spite of the null world), making the bug hard to track down. | |
return nullptr; | |
} | |
if (SingletonWorld.Get() != World && World && SingletonWorld.IsValid() && | |
Singleton.IsValid()) | |
{ | |
// Both SingletonWorld and World are non-null and different. Log a warning | |
// here since this is probably not what the caller wants. That said, this class handles | |
// this situation, but it isn't as performant since the singleton will be looked up | |
// whenever this happens. This situation is common in PIE since there are multiple | |
// worlds created for each player. | |
UE_CLOG( | |
!World->IsPlayInEditor(), | |
LogWorldSingleton, | |
Warning, | |
TEXT("Cached singleton %s (%p) is in valid world %s (%p), but caller is trying to " | |
"get a singleton in world %s (%p)."), | |
*GetNameSafe(Singleton.Get()), | |
Singleton.Get(), | |
*GetNameSafe(SingletonWorld.Get()), | |
SingletonWorld.Get(), | |
*GetNameSafe(World), | |
World); | |
Singleton = nullptr; | |
SingletonWorld = nullptr; | |
} | |
if (!Singleton.IsValid()) | |
{ | |
for (TActorIterator<ActorType> It(World); It; ++It) | |
{ | |
if (!Singleton.IsValid()) | |
{ | |
Singleton = *It; | |
SingletonWorld = World; | |
} | |
else | |
{ | |
UE_LOG( | |
LogWorldSingleton, | |
Error, | |
TEXT("TWorldSingleton::Get found another actor of class %s named %s. The " | |
"actor being used as the singleton is %s. To fix, delete all but one " | |
"of the actors."), | |
*GetNameSafe(ActorType::StaticClass()), | |
*GetNameSafe(*It), | |
*GetNameSafe(Singleton.Get())); | |
} | |
} | |
} | |
return Singleton.Get(); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
An example use of this singleton would be something like this:
And then using the singleton: