2022-05-23 18:41:30 +00:00
// 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"
2022-09-13 07:18:28 +00:00
# include "Algo/Transform.h"
2022-05-23 18:41:30 +00:00
//////////////////////////////////////////////////////////////////////
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 ;
2022-09-13 07:18:28 +00:00
BundleData . AddBundleAssetsTruncated ( UFortAssetManager_LoadStateClient , CuePaths ) ;
2022-05-23 18:41:30 +00:00
FPrimaryAssetId PrimaryAssetId = FPrimaryAssetId ( UFortAssetManager_GameplayCueRefsType , UFortAssetManager_GameplayCueRefsName ) ;
UAssetManager : : Get ( ) . AddDynamicAsset ( PrimaryAssetId , FSoftObjectPath ( ) , BundleData ) ;
}