|
#include "ActorSingleton.h" |
|
#include "Kismet/GameplayStatics.h" |
|
#include "Subsystems/EditorActorSubsystem.h" |
|
#include "Editor.h" |
|
|
|
DEFINE_LOG_CATEGORY(ActorSingleton); |
|
|
|
|
|
void AActorSingleton::OnConstruction(const FTransform& Transform) |
|
{ |
|
Super::OnConstruction(Transform); |
|
TryBecomeNewInstanceOrSelfDestroy(); |
|
} |
|
|
|
/* static */ AActorSingleton* AActorSingleton::Get(UWorld* WorldContext, TSubclassOf<AActorSingleton> Class) |
|
{ |
|
check(IsValid(WorldContext)) |
|
|
|
auto& InstancesMap = UActorSingletonManager::GetChecked(WorldContext)->Instances; |
|
|
|
if (!InstancesMap.Contains(Class)) |
|
{ |
|
return nullptr; |
|
} |
|
|
|
return InstancesMap[Class]; |
|
} |
|
|
|
template<class T> |
|
/* static */ static T* AActorSingleton::Get(UWorld* World) |
|
{ |
|
static_assert(TIsDerivedFrom<T, AActorSingleton>::IsDerived, "T must be derived from AActorSingleton"); |
|
return AActorSingleton::Get(World, T::StaticClass()) |
|
} |
|
|
|
template<class T> |
|
/* static */ static T* AActorSingleton::GetChecked(UWorld* World) |
|
{ |
|
T* Instance = AActorSingleton::Get<T>(World); |
|
check(Instance) |
|
return Instance; |
|
} |
|
|
|
void AActorSingleton::TryBecomeNewInstanceOrSelfDestroy() |
|
{ |
|
/* Do nothing, if 'this' is either... |
|
* ...not valid (such case has never happened but always worth cathing) |
|
* ...being destroyed (IsValid does NOT catch this in some cases) |
|
* ...marked as Transient (we omit "dummy" Actors that are often being used by the Editor) */ |
|
if( |
|
!ensure(IsValid(this)) |
|
|| this->IsActorBeingDestroyed() |
|
|| this->HasAnyFlags(EObjectFlags::RF_Transient) |
|
) |
|
{ |
|
return; |
|
} |
|
|
|
TSubclassOf<AActorSingleton> ThisClass = this->GetClass(); |
|
|
|
/* Do nothing, if 'this' is CDO */ |
|
if (this == ThisClass->GetDefaultObject()) |
|
{ |
|
return; |
|
} |
|
|
|
UWorld* ThisWorld = GetWorld(); |
|
auto* ActorSingletonManager = UActorSingletonManager::Get(ThisWorld); |
|
|
|
/* UActorSingletonManager::Get can fail (and this is expected) |
|
* There are cases where UActorSingletonManager might not be Initialized yet, |
|
* e.g. during AActor::OnConstruction when opening Map in the Editor. |
|
* We deal with said problem by re-firing this function later in the UActorSingletonManager::PostInitialize |
|
* (this is not an ideal solution but works fine for now, see UActorSingletonManager::PostInitialize) */ |
|
if(!ActorSingletonManager) |
|
{ |
|
return; |
|
} |
|
|
|
auto& InstancesMap = ActorSingletonManager->Instances; |
|
|
|
/* Go through the UClass::GetSuperClass chain, from 'ThisClass' to 'AActorSingleton', |
|
* and store said chain as we gonna traverse it backwards later. */ |
|
TArray<UClass*> ClassInheritanceChain; |
|
for (UClass* ItClass = ThisClass; ItClass != AActorSingleton::StaticClass(); ItClass = ItClass->GetSuperClass()) |
|
{ |
|
ClassInheritanceChain.Add(ItClass); |
|
} |
|
ClassInheritanceChain.Add(AActorSingleton::StaticClass()); |
|
|
|
/* Traverse throught ClassInheritanceChain from the back (top parrent) to the front (ThisClass) |
|
* We do this to find the highest parrent class that returns 'true' from TreatDerivedClassInstancesAsSame, |
|
* in such case we stop traversing and start treating the said class as new 'ThisClass'. */ |
|
int32 ItClassIndex = ClassInheritanceChain.Num() - 1; |
|
for (; ItClassIndex > 0; --ItClassIndex) |
|
{ |
|
const auto* ItCDO = static_cast<AActorSingleton*>(ClassInheritanceChain[ItClassIndex]->GetDefaultObject()); |
|
if (ItCDO->TreatDerivedClassInstancesAsSame()) |
|
{ |
|
break; |
|
} |
|
} |
|
ThisClass = ClassInheritanceChain[ItClassIndex]; |
|
|
|
if (!InstancesMap.Contains(ThisClass)) |
|
{ |
|
InstancesMap.Add(ThisClass, nullptr); |
|
} |
|
|
|
AActorSingleton*& CurrentInstance = InstancesMap[ThisClass]; |
|
|
|
if (this == CurrentInstance) |
|
{ |
|
return; |
|
} |
|
|
|
/* TODO: |
|
* 'ensure' is not a very elegant way to catch this problem, |
|
* it only fires once, so this won't work for multiple classes. |
|
* 'ensureAlways' also won't make a job as it will be hit too often causing a frame drop... |
|
* Maybe a custom error handling? Also, catching this error might be done in a diffrent place? */ |
|
ensureMsgf |
|
( |
|
//expression (lambda): |
|
[=](){ |
|
const auto* ThisCDO = static_cast<AActorSingleton*>(ThisClass->GetDefaultObject()); |
|
return ThisCDO->TreatDerivedClassInstancesAsSame(); |
|
}(), |
|
//text body: |
|
TEXT( |
|
"There is no class in the Inheritance Chain going from '%s' to '%s'," |
|
" which would return 'true' from 'TreatDerivedClassInstancesAsSame'!" |
|
" Please make sure to override 'AActorSingleton::TreatDerivedClassInstancesAsSame'!" |
|
" This function must return 'true' on the final base class!" |
|
), |
|
//text parameters: |
|
*ThisClass->GetFName().ToString(), |
|
*AActorSingleton::StaticClass()->GetFName().ToString() |
|
); |
|
|
|
if (!IsValid(CurrentInstance)) |
|
{ |
|
CurrentInstance = this; |
|
|
|
UE_LOG(ActorSingleton, Warning, TEXT("'%s' is now a singleton instance of class '%s' in the World '%s'! " |
|
"Adding/Spawning more instances of the same class is the same World will resul in them being instanently destroyed!"), |
|
*AActor::GetDebugName(this), *ThisClass->GetFName().ToString(), *ThisWorld->GetFName().ToString()); |
|
|
|
return; |
|
} |
|
|
|
UE_LOG(ActorSingleton, Error, TEXT("World '%s' can have only one instance of '%s'! Destroying '%s' ..."), |
|
*ThisWorld->GetFName().ToString(), *ThisClass->GetFName().ToString(), *AActor::GetDebugName(this)); |
|
|
|
/* In case of placing an Actor in the Level Viewport, we cannot simply Destroy it. |
|
* Instead, we must "tell" the Editor to delete it, which will fire some additional clean up logic. */ |
|
#if WITH_EDITOR |
|
if (ThisWorld->IsEditorWorld() && !ThisWorld->IsPlayInEditor()) |
|
{ |
|
auto* EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>(); |
|
check(EditorActorSubsystem) |
|
|
|
EditorActorSubsystem->ClearActorSelectionSet(); |
|
EditorActorSubsystem->SetActorSelectionState(this, true); |
|
EditorActorSubsystem->DeleteSelectedActors(ThisWorld); |
|
GEngine->ForceGarbageCollection(true); |
|
|
|
/* Show Dialogue Message */ |
|
{ |
|
const FText MessageTitle = FText::FromString("SingletonActor - Destroyed Duplicate"); |
|
const FText MessageBody = FText::FromString( |
|
"Duplicate instance of SingletonActor sub-class has been found and destroyed!" |
|
"\nThere can be only one instance of said class, and there is already one existing." |
|
"\n(check log for more detailed error)"); |
|
FMessageDialog::Debugf(MessageBody, &MessageTitle); |
|
} |
|
|
|
return; |
|
} |
|
#endif //WITH_EDITOR |
|
|
|
this->Destroy(true, true); |
|
} |
|
|
|
void UActorSingletonManager::PostInitialize() |
|
{ |
|
Super::PostInitialize(); |
|
FindInstancesAndDestroyDuplicates(); |
|
} |
|
|
|
/* static */ UActorSingletonManager* UActorSingletonManager::Get(UWorld* World) |
|
{ |
|
check(IsValid(World)) |
|
return World->GetSubsystem<UActorSingletonManager>(); |
|
} |
|
|
|
/* static */ UActorSingletonManager* UActorSingletonManager::GetChecked(UWorld* World) |
|
{ |
|
UActorSingletonManager* Instance = UActorSingletonManager::Get(World); |
|
check(Instance) |
|
return Instance; |
|
} |
|
|
|
void UActorSingletonManager::FindInstancesAndDestroyDuplicates() |
|
{ |
|
TArray<AActor*> PossibleInstances; |
|
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActorSingleton::StaticClass(), PossibleInstances); |
|
for (AActor* Actor : PossibleInstances) |
|
{ |
|
static_cast<AActorSingleton*>(Actor)->TryBecomeNewInstanceOrSelfDestroy(); |
|
} |
|
} |