RealtimeStyleTransferRuntime/Source/LyraGame/AbilitySystem/LyraGameplayCueManager.cpp

406 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraGameplayCueManager.h"
#include "LyraLogChannels.h"
#include "GameplayCueSet.h"
#include "AbilitySystemGlobals.h"
#include "HAL/IConsoleManager.h"
#include "GameplayTagsManager.h"
#include "UObject/UObjectThreadContext.h"
#include "System/LyraAssetManager.h"
#include "Async/Async.h"
#include "Algo/Transform.h"
//////////////////////////////////////////////////////////////////////
enum class ELyraEditorLoadMode
{
// Loads all cues upfront; longer loading speed in the editor but short PIE times and effects never fail to play
LoadUpfront,
// Outside of editor: Async loads as cue tag are registered
// In editor: Async loads when cues are invoked
// Note: This can cause some 'why didn't I see the effect for X' issues in PIE and is good for iteration speed but otherwise bad for designers
PreloadAsCuesAreReferenced_GameOnly,
// Async loads as cue tag are registered
PreloadAsCuesAreReferenced
};
namespace LyraGameplayCueManagerCvars
{
static FAutoConsoleCommand CVarDumpGameplayCues(
TEXT("Lyra.DumpGameplayCues"),
TEXT("Shows all assets that were loaded via LyraGameplayCueManager and are currently in memory."),
FConsoleCommandWithArgsDelegate::CreateStatic(ULyraGameplayCueManager::DumpGameplayCues));
static ELyraEditorLoadMode LoadMode = ELyraEditorLoadMode::LoadUpfront;
}
const bool bPreloadEvenInEditor = true;
//////////////////////////////////////////////////////////////////////
struct FGameplayCueTagThreadSynchronizeGraphTask : public FAsyncGraphTaskBase
{
TFunction<void()> TheTask;
FGameplayCueTagThreadSynchronizeGraphTask(TFunction<void()>&& Task) : TheTask(MoveTemp(Task)) { }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) { TheTask(); }
ENamedThreads::Type GetDesiredThread() { return ENamedThreads::GameThread; }
};
//////////////////////////////////////////////////////////////////////
ULyraGameplayCueManager::ULyraGameplayCueManager(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
ULyraGameplayCueManager* ULyraGameplayCueManager::Get()
{
return Cast<ULyraGameplayCueManager>(UAbilitySystemGlobals::Get().GetGameplayCueManager());
}
void ULyraGameplayCueManager::OnCreated()
{
Super::OnCreated();
UpdateDelayLoadDelegateListeners();
}
void ULyraGameplayCueManager::LoadAlwaysLoadedCues()
{
if (ShouldDelayLoadGameplayCues())
{
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
//@TODO: Try to collect these by filtering GameplayCue. tags out of native gameplay tags?
TArray<FName> AdditionalAlwaysLoadedCueTags;
for (const FName& CueTagName : AdditionalAlwaysLoadedCueTags)
{
FGameplayTag CueTag = TagManager.RequestGameplayTag(CueTagName, /*ErrorIfNotFound=*/ false);
if (CueTag.IsValid())
{
ProcessTagToPreload(CueTag, nullptr);
}
else
{
UE_LOG(LogLyra, Warning, TEXT("ULyraGameplayCueManager::AdditionalAlwaysLoadedCueTags contains invalid tag %s"), *CueTagName.ToString());
}
}
}
}
bool ULyraGameplayCueManager::ShouldAsyncLoadRuntimeObjectLibraries() const
{
switch (LyraGameplayCueManagerCvars::LoadMode)
{
case ELyraEditorLoadMode::LoadUpfront:
return true;
case ELyraEditorLoadMode::PreloadAsCuesAreReferenced_GameOnly:
#if WITH_EDITOR
if (GIsEditor)
{
return false;
}
#endif
break;
case ELyraEditorLoadMode::PreloadAsCuesAreReferenced:
break;
}
return !ShouldDelayLoadGameplayCues();
}
bool ULyraGameplayCueManager::ShouldSyncLoadMissingGameplayCues() const
{
return false;
}
bool ULyraGameplayCueManager::ShouldAsyncLoadMissingGameplayCues() const
{
return true;
}
void ULyraGameplayCueManager::DumpGameplayCues(const TArray<FString>& Args)
{
ULyraGameplayCueManager* GCM = Cast<ULyraGameplayCueManager>(UAbilitySystemGlobals::Get().GetGameplayCueManager());
if (!GCM)
{
UE_LOG(LogLyra, Error, TEXT("DumpGameplayCues failed. No ULyraGameplayCueManager found."));
return;
}
const bool bIncludeRefs = Args.Contains(TEXT("Refs"));
UE_LOG(LogLyra, Log, TEXT("=========== Dumping Always Loaded Gameplay Cue Notifies ==========="));
for (UClass* CueClass : GCM->AlwaysLoadedCues)
{
UE_LOG(LogLyra, Log, TEXT(" %s"), *GetPathNameSafe(CueClass));
}
UE_LOG(LogLyra, Log, TEXT("=========== Dumping Preloaded Gameplay Cue Notifies ==========="));
for (UClass* CueClass : GCM->PreloadedCues)
{
TSet<FObjectKey>* ReferencerSet = GCM->PreloadedCueReferencers.Find(CueClass);
int32 NumRefs = ReferencerSet ? ReferencerSet->Num() : 0;
UE_LOG(LogLyra, Log, TEXT(" %s (%d refs)"), *GetPathNameSafe(CueClass), NumRefs);
if (bIncludeRefs && ReferencerSet)
{
for (const FObjectKey& Ref : *ReferencerSet)
{
UObject* RefObject = Ref.ResolveObjectPtr();
UE_LOG(LogLyra, Log, TEXT(" ^- %s"), *GetPathNameSafe(RefObject));
}
}
}
UE_LOG(LogLyra, Log, TEXT("=========== Dumping Gameplay Cue Notifies loaded on demand ==========="));
int32 NumMissingCuesLoaded = 0;
if (GCM->RuntimeGameplayCueObjectLibrary.CueSet)
{
for (const FGameplayCueNotifyData& CueData : GCM->RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueData)
{
if (CueData.LoadedGameplayCueClass && !GCM->AlwaysLoadedCues.Contains(CueData.LoadedGameplayCueClass) && !GCM->PreloadedCues.Contains(CueData.LoadedGameplayCueClass))
{
NumMissingCuesLoaded++;
UE_LOG(LogLyra, Log, TEXT(" %s"), *CueData.LoadedGameplayCueClass->GetPathName());
}
}
}
UE_LOG(LogLyra, Log, TEXT("=========== Gameplay Cue Notify summary ==========="));
UE_LOG(LogLyra, Log, TEXT(" ... %d cues in always loaded list"), GCM->AlwaysLoadedCues.Num());
UE_LOG(LogLyra, Log, TEXT(" ... %d cues in preloaded list"), GCM->PreloadedCues.Num());
UE_LOG(LogLyra, Log, TEXT(" ... %d cues loaded on demand"), NumMissingCuesLoaded);
UE_LOG(LogLyra, Log, TEXT(" ... %d cues in total"), GCM->AlwaysLoadedCues.Num() + GCM->PreloadedCues.Num() + NumMissingCuesLoaded);
}
void ULyraGameplayCueManager::OnGameplayTagLoaded(const FGameplayTag& Tag)
{
FScopeLock ScopeLock(&LoadedGameplayTagsToProcessCS);
bool bStartTask = LoadedGameplayTagsToProcess.Num() == 0;
FUObjectSerializeContext* LoadContext = FUObjectThreadContext::Get().GetSerializeContext();
UObject* OwningObject = LoadContext ? LoadContext->SerializedObject : nullptr;
LoadedGameplayTagsToProcess.Emplace(Tag, OwningObject);
if (bStartTask)
{
TGraphTask<FGameplayCueTagThreadSynchronizeGraphTask>::CreateTask().ConstructAndDispatchWhenReady([]()
{
if (GIsRunning)
{
if (ULyraGameplayCueManager* StrongThis = Get())
{
// If we are garbage collecting we cannot call StaticFindObject (or a few other static uobject functions), so we'll just wait until the GC is over and process the tags then
if (IsGarbageCollecting())
{
StrongThis->bProcessLoadedTagsAfterGC = true;
}
else
{
StrongThis->ProcessLoadedTags();
}
}
}
});
}
}
void ULyraGameplayCueManager::HandlePostGarbageCollect()
{
if (bProcessLoadedTagsAfterGC)
{
ProcessLoadedTags();
}
bProcessLoadedTagsAfterGC = false;
}
void ULyraGameplayCueManager::ProcessLoadedTags()
{
TArray<FLoadedGameplayTagToProcessData> TaskLoadedGameplayTagsToProcess;
{
// Lock LoadedGameplayTagsToProcess just long enough to make a copy and clear
FScopeLock TaskScopeLock(&LoadedGameplayTagsToProcessCS);
TaskLoadedGameplayTagsToProcess = LoadedGameplayTagsToProcess;
LoadedGameplayTagsToProcess.Empty();
}
// This might return during shutdown, and we don't want to proceed if that is the case
if (GIsRunning)
{
if (RuntimeGameplayCueObjectLibrary.CueSet)
{
for (const FLoadedGameplayTagToProcessData& LoadedTagData : TaskLoadedGameplayTagsToProcess)
{
if (RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueDataMap.Contains(LoadedTagData.Tag))
{
if (!LoadedTagData.WeakOwner.IsStale())
{
ProcessTagToPreload(LoadedTagData.Tag, LoadedTagData.WeakOwner.Get());
}
}
}
}
else
{
UE_LOG(LogLyra, Warning, TEXT("ULyraGameplayCueManager::OnGameplayTagLoaded processed loaded tag(s) but RuntimeGameplayCueObjectLibrary.CueSet was null. Skipping processing."));
}
}
}
void ULyraGameplayCueManager::ProcessTagToPreload(const FGameplayTag& Tag, UObject* OwningObject)
{
switch (LyraGameplayCueManagerCvars::LoadMode)
{
case ELyraEditorLoadMode::LoadUpfront:
return;
case ELyraEditorLoadMode::PreloadAsCuesAreReferenced_GameOnly:
#if WITH_EDITOR
if (GIsEditor)
{
return;
}
#endif
break;
case ELyraEditorLoadMode::PreloadAsCuesAreReferenced:
break;
}
check(RuntimeGameplayCueObjectLibrary.CueSet);
int32* DataIdx = RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueDataMap.Find(Tag);
if (DataIdx && RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueData.IsValidIndex(*DataIdx))
{
const FGameplayCueNotifyData& CueData = RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueData[*DataIdx];
UClass* LoadedGameplayCueClass = FindObject<UClass>(nullptr, *CueData.GameplayCueNotifyObj.ToString());
if (LoadedGameplayCueClass)
{
RegisterPreloadedCue(LoadedGameplayCueClass, OwningObject);
}
else
{
bool bAlwaysLoadedCue = OwningObject == nullptr;
TWeakObjectPtr<UObject> WeakOwner = OwningObject;
StreamableManager.RequestAsyncLoad(CueData.GameplayCueNotifyObj, FStreamableDelegate::CreateUObject(this, &ThisClass::OnPreloadCueComplete, CueData.GameplayCueNotifyObj, WeakOwner, bAlwaysLoadedCue), FStreamableManager::DefaultAsyncLoadPriority, false, false, TEXT("GameplayCueManager"));
}
}
}
void ULyraGameplayCueManager::OnPreloadCueComplete(FSoftObjectPath Path, TWeakObjectPtr<UObject> OwningObject, bool bAlwaysLoadedCue)
{
if (bAlwaysLoadedCue || OwningObject.IsValid())
{
if (UClass* LoadedGameplayCueClass = Cast<UClass>(Path.ResolveObject()))
{
RegisterPreloadedCue(LoadedGameplayCueClass, OwningObject.Get());
}
}
}
void ULyraGameplayCueManager::RegisterPreloadedCue(UClass* LoadedGameplayCueClass, UObject* OwningObject)
{
check(LoadedGameplayCueClass);
const bool bAlwaysLoadedCue = OwningObject == nullptr;
if (bAlwaysLoadedCue)
{
AlwaysLoadedCues.Add(LoadedGameplayCueClass);
PreloadedCues.Remove(LoadedGameplayCueClass);
PreloadedCueReferencers.Remove(LoadedGameplayCueClass);
}
else if ((OwningObject != LoadedGameplayCueClass) && (OwningObject != LoadedGameplayCueClass->GetDefaultObject()) && !AlwaysLoadedCues.Contains(LoadedGameplayCueClass))
{
PreloadedCues.Add(LoadedGameplayCueClass);
TSet<FObjectKey>& ReferencerSet = PreloadedCueReferencers.FindOrAdd(LoadedGameplayCueClass);
ReferencerSet.Add(OwningObject);
}
}
void ULyraGameplayCueManager::HandlePostLoadMap(UWorld* NewWorld)
{
if (RuntimeGameplayCueObjectLibrary.CueSet)
{
for (UClass* CueClass : AlwaysLoadedCues)
{
RuntimeGameplayCueObjectLibrary.CueSet->RemoveLoadedClass(CueClass);
}
for (UClass* CueClass : PreloadedCues)
{
RuntimeGameplayCueObjectLibrary.CueSet->RemoveLoadedClass(CueClass);
}
}
for (auto CueIt = PreloadedCues.CreateIterator(); CueIt; ++CueIt)
{
TSet<FObjectKey>& ReferencerSet = PreloadedCueReferencers.FindChecked(*CueIt);
for (auto RefIt = ReferencerSet.CreateIterator(); RefIt; ++RefIt)
{
if (!RefIt->ResolveObjectPtr())
{
RefIt.RemoveCurrent();
}
}
if (ReferencerSet.Num() == 0)
{
PreloadedCueReferencers.Remove(*CueIt);
CueIt.RemoveCurrent();
}
}
}
void ULyraGameplayCueManager::UpdateDelayLoadDelegateListeners()
{
UGameplayTagsManager::Get().OnGameplayTagLoadedDelegate.RemoveAll(this);
FCoreUObjectDelegates::GetPostGarbageCollect().RemoveAll(this);
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
switch (LyraGameplayCueManagerCvars::LoadMode)
{
case ELyraEditorLoadMode::LoadUpfront:
return;
case ELyraEditorLoadMode::PreloadAsCuesAreReferenced_GameOnly:
#if WITH_EDITOR
if (GIsEditor)
{
return;
}
#endif
break;
case ELyraEditorLoadMode::PreloadAsCuesAreReferenced:
break;
}
UGameplayTagsManager::Get().OnGameplayTagLoadedDelegate.AddUObject(this, &ThisClass::OnGameplayTagLoaded);
FCoreUObjectDelegates::GetPostGarbageCollect().AddUObject(this, &ThisClass::HandlePostGarbageCollect);
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &ThisClass::HandlePostLoadMap);
}
bool ULyraGameplayCueManager::ShouldDelayLoadGameplayCues() const
{
const bool bClientDelayLoadGameplayCues = true;
return !IsRunningDedicatedServer() && bClientDelayLoadGameplayCues;
}
const FPrimaryAssetType UFortAssetManager_GameplayCueRefsType = TEXT("GameplayCueRefs");
const FName UFortAssetManager_GameplayCueRefsName = TEXT("GameplayCueReferences");
const FName UFortAssetManager_LoadStateClient = FName(TEXT("Client"));
void ULyraGameplayCueManager::RefreshGameplayCuePrimaryAsset()
{
TArray<FSoftObjectPath> CuePaths;
UGameplayCueSet* RuntimeGameplayCueSet = GetRuntimeCueSet();
if (RuntimeGameplayCueSet)
{
RuntimeGameplayCueSet->GetSoftObjectPaths(CuePaths);
}
FAssetBundleData BundleData;
BundleData.AddBundleAssetsTruncated(UFortAssetManager_LoadStateClient, CuePaths);
FPrimaryAssetId PrimaryAssetId = FPrimaryAssetId(UFortAssetManager_GameplayCueRefsType, UFortAssetManager_GameplayCueRefsName);
UAssetManager::Get().AddDynamicAsset(PrimaryAssetId, FSoftObjectPath(), BundleData);
}