2021-01-26 11:51:28 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "EditorConfigSubsystem.h"
# include "Async/Async.h"
2023-06-22 09:59:51 -04:00
# include "Editor.h"
2021-02-04 05:47:39 -04:00
# include "Misc/App.h"
# include "Misc/Paths.h"
2022-10-31 07:21:37 -04:00
# include "Misc/ScopeLock.h"
2021-01-26 11:51:28 -04:00
2023-06-22 09:59:51 -04:00
TArray < TPair < UEditorConfigSubsystem : : ESearchDirectoryType , FString > > UEditorConfigSubsystem : : EarlyRegistredSearchDirectories ;
2021-01-26 11:51:28 -04:00
UEditorConfigSubsystem : : UEditorConfigSubsystem ( )
{
}
void UEditorConfigSubsystem : : Initialize ( FSubsystemCollectionBase & Collection )
{
2023-06-22 09:59:51 -04:00
SearchDirectories . Reserve ( 4 + EarlyRegistredSearchDirectories . Num ( ) ) ;
2022-02-02 02:16:18 -05:00
AddSearchDirectory ( ESearchDirectoryType : : Engine , FPaths : : Combine ( FPaths : : EngineConfigDir ( ) , TEXT ( " Editor " ) ) ) ; // Engine
AddSearchDirectory ( ESearchDirectoryType : : Project , FPaths : : Combine ( FPaths : : ProjectConfigDir ( ) , TEXT ( " Editor " ) ) ) ; // ProjectName
2022-08-11 17:45:14 -04:00
# ifdef UE_SAVED_DIR_OVERRIDE
AddSearchDirectory ( ESearchDirectoryType : : User , FPaths : : Combine ( FPlatformProcess : : UserSettingsDir ( ) , TEXT ( PREPROCESSOR_TO_STRING ( UE_SAVED_DIR_OVERRIDE ) ) , TEXT ( " Editor " ) ) ) ; // AppData
# else
2022-02-02 02:16:18 -05:00
AddSearchDirectory ( ESearchDirectoryType : : User , FPaths : : Combine ( FPlatformProcess : : UserSettingsDir ( ) , * FApp : : GetEpicProductIdentifier ( ) , TEXT ( " Editor " ) ) ) ; // AppData
2022-08-11 17:45:14 -04:00
# endif
2023-06-22 09:59:51 -04:00
for ( const TPair < ESearchDirectoryType , FString > & Pair : EarlyRegistredSearchDirectories )
{
AddSearchDirectory ( Pair . Key , Pair . Value ) ;
}
EarlyRegistredSearchDirectories . Empty ( ) ;
2021-01-26 11:51:28 -04:00
}
2021-11-18 14:37:34 -05:00
void UEditorConfigSubsystem : : Deinitialize ( )
2021-01-26 11:51:28 -04:00
{
2022-10-27 12:40:12 -04:00
FScopeLock Lock ( & SaveLock ) ;
2021-05-25 10:01:03 -04:00
2021-11-18 14:37:34 -05:00
// Synchronously save all Pending Saves on exit
2021-05-25 10:01:03 -04:00
for ( FPendingSave & Save : PendingSaves )
{
2021-11-18 14:37:34 -05:00
const FString * FilePath = LoadedConfigs . FindKey ( Save . Config ) ;
check ( FilePath ! = nullptr ) ;
Save . Config - > SaveToFile ( * FilePath ) ;
Save . Config - > OnSaved ( ) ;
}
}
void UEditorConfigSubsystem : : Tick ( float DeltaTime )
{
2022-10-27 12:40:12 -04:00
FScopeLock Lock ( & SaveLock ) ;
2021-11-18 14:37:34 -05:00
// Allows PendingSaves to be modified while iterating as
// the Async below might execute the task immediately
// when running in -nothreading mode.
2022-10-27 12:40:12 -04:00
for ( int32 Index = 0 ; Index < PendingSaves . Num ( ) ; + + Index )
2021-11-18 14:37:34 -05:00
{
FPendingSave & Save = PendingSaves [ Index ] ;
2021-05-25 10:01:03 -04:00
Save . TimeSinceQueued + = DeltaTime ;
const float SaveDelaySeconds = 3.0f ;
2022-11-01 15:07:25 -04:00
if ( Save . TimeSinceQueued > SaveDelaySeconds & &
! Save . WasSuccess . IsValid ( ) )
2021-05-25 10:01:03 -04:00
{
const FString * FilePath = LoadedConfigs . FindKey ( Save . Config ) ;
check ( FilePath ! = nullptr ) ;
2022-11-01 15:07:25 -04:00
Save . WasSuccess = Async ( EAsyncExecution : : TaskGraph ,
2021-05-25 10:01:03 -04:00
[ Config = Save . Config , File = * FilePath ] ( )
{
return Config - > SaveToFile ( File ) ;
} ,
[ this , Config = Save . Config ] ( )
{
OnSaveCompleted ( Config ) ;
} ) ;
}
}
2022-10-27 12:40:12 -04:00
for ( int32 Index = 0 ; Index < PendingSaves . Num ( ) ; + + Index )
{
if ( PendingSaves [ Index ] . WasSuccess . IsReady ( ) )
{
PendingSaves . RemoveAt ( Index ) ;
- - Index ;
}
}
2021-05-25 10:01:03 -04:00
}
TStatId UEditorConfigSubsystem : : GetStatId ( ) const
{
RETURN_QUICK_DECLARE_CYCLE_STAT ( UEditorConfigSubsystem , STATGROUP_Tickables ) ;
}
2021-05-26 08:27:40 -04:00
bool UEditorConfigSubsystem : : LoadConfigObject ( const UClass * Class , UObject * Object , FEditorConfig : : EPropertyFilter Filter )
2021-05-25 10:01:03 -04:00
{
const FString & EditorConfigName = Class - > GetMetaData ( " EditorConfig " ) ;
if ( ! ensureMsgf ( ! EditorConfigName . IsEmpty ( ) , TEXT ( " UEditorConfigSubsystem::LoadConfigObject - EditorConfig name is not set on class %s. " ) , * Class - > GetName ( ) ) )
{
return false ;
}
TSharedRef < FEditorConfig > EditorConfig = FindOrLoadConfig ( EditorConfigName ) ;
2021-05-26 08:27:40 -04:00
return EditorConfig - > TryGetRootUObject ( Class , Object , Filter ) ;
2021-05-25 10:01:03 -04:00
}
2021-05-26 08:27:40 -04:00
bool UEditorConfigSubsystem : : SaveConfigObject ( const UClass * Class , const UObject * Object , FEditorConfig : : EPropertyFilter Filter )
2021-05-25 10:01:03 -04:00
{
const FString & EditorConfigName = Class - > GetMetaData ( " EditorConfig " ) ;
2021-05-26 08:27:40 -04:00
if ( ! ensureMsgf ( ! EditorConfigName . IsEmpty ( ) , TEXT ( " UEditorConfigSubsystem::SaveConfigObject - EditorConfig name is not set on class %s. " ) , * Class - > GetName ( ) ) )
2021-05-25 10:01:03 -04:00
{
return false ;
}
TSharedRef < FEditorConfig > EditorConfig = FindOrLoadConfig ( EditorConfigName ) ;
2021-05-26 08:27:40 -04:00
EditorConfig - > SetRootUObject ( Class , Object , Filter ) ;
2021-05-25 10:01:03 -04:00
SaveConfig ( EditorConfig ) ;
return true ;
}
bool UEditorConfigSubsystem : : ReloadConfig ( TSharedRef < FEditorConfig > Config )
{
const FString * FilePath = LoadedConfigs . FindKey ( Config ) ;
if ( ! ensureMsgf ( FilePath ! = nullptr , TEXT ( " Could not find filename for given config in UEditorConfigSubsystem::ReloadConfig(). " ) ) )
{
return false ;
}
const FString ConfigName = FPaths : : GetBaseFilename ( * FilePath ) ;
TSharedPtr < FEditorConfig > Parent ;
2021-01-26 11:51:28 -04:00
2022-02-02 02:16:18 -05:00
for ( const TPair < ESearchDirectoryType , FString > & Dir : SearchDirectories )
2021-01-26 11:51:28 -04:00
{
2022-02-02 02:16:18 -05:00
const FString FullPath = FPaths : : Combine ( Dir . Value , ConfigName ) + TEXT ( " .json " ) ;
2021-01-26 11:51:28 -04:00
2021-05-25 10:01:03 -04:00
// find an existing config or create one
2021-01-26 11:51:28 -04:00
const TSharedPtr < FEditorConfig > * Existing = LoadedConfigs . Find ( FullPath ) ;
2021-05-25 10:01:03 -04:00
if ( Existing = = nullptr )
2021-01-26 11:51:28 -04:00
{
2021-05-25 10:01:03 -04:00
Existing = & LoadedConfigs . Add ( FullPath , MakeShared < FEditorConfig > ( ) ) ;
2021-01-26 11:51:28 -04:00
}
2021-05-25 10:01:03 -04:00
if ( ! ( * Existing ) - > LoadFromFile ( FullPath ) )
2021-01-26 11:51:28 -04:00
{
2021-05-25 10:01:03 -04:00
ensureMsgf ( false , TEXT ( " Failed to load editor config from file %s " ) , * FullPath ) ;
return false ;
2021-01-26 11:51:28 -04:00
}
2021-05-25 10:01:03 -04:00
if ( Parent . IsValid ( ) )
{
( * Existing ) - > SetParent ( Parent ) ;
}
Parent = ( * Existing ) ;
2021-01-26 11:51:28 -04:00
}
2021-05-25 10:01:03 -04:00
return true ;
}
TSharedRef < FEditorConfig > UEditorConfigSubsystem : : FindOrLoadConfig ( FStringView ConfigName )
{
checkf ( ! ConfigName . IsEmpty ( ) , TEXT ( " Config name cannot be empty! " ) ) ;
FString ConfigNameString ( ConfigName ) ;
// look for the config in the final search directory and return if it's loaded
// this assumes that the hierarchy of configs is unchanged
// ie. given search directories [Foo, Bar], the existence of Bar/X.json is taken to mean that Foo/X.json has been loaded
2022-02-02 02:16:18 -05:00
const FString FinalPath = FPaths : : Combine ( SearchDirectories . Last ( ) . Value , ConfigNameString ) + TEXT ( " .json " ) ;
2021-01-26 11:51:28 -04:00
const TSharedPtr < FEditorConfig > * FinalConfig = LoadedConfigs . Find ( FinalPath ) ;
if ( FinalConfig ! = nullptr )
{
2021-05-25 10:01:03 -04:00
return FinalConfig - > ToSharedRef ( ) ;
}
// find or load all configs in all search directories with the given name
TSharedPtr < FEditorConfig > Parent ;
2022-02-02 02:16:18 -05:00
for ( const TPair < ESearchDirectoryType , FString > & Dir : SearchDirectories )
2021-05-25 10:01:03 -04:00
{
2022-02-02 02:16:18 -05:00
const FString FullPath = FPaths : : Combine ( Dir . Value , ConfigNameString ) + TEXT ( " .json " ) ;
2021-05-25 10:01:03 -04:00
const TSharedPtr < FEditorConfig > * Existing = LoadedConfigs . Find ( FullPath ) ;
if ( Existing ! = nullptr & & Existing - > IsValid ( ) )
{
Parent = * Existing ;
}
else
{
// didn't exist yet, load now
TSharedRef < FEditorConfig > NewConfig = MakeShared < FEditorConfig > ( ) ;
if ( NewConfig - > LoadFromFile ( FullPath ) )
{
NewConfig - > OnEditorConfigDirtied ( ) . AddUObject ( this , & UEditorConfigSubsystem : : OnEditorConfigDirtied ) ;
if ( Parent . IsValid ( ) )
{
NewConfig - > SetParent ( Parent ) ;
}
LoadedConfigs . Add ( FullPath , NewConfig ) ;
Parent = NewConfig ;
}
}
}
FinalConfig = LoadedConfigs . Find ( FinalPath ) ;
if ( FinalConfig ! = nullptr )
{
return FinalConfig - > ToSharedRef ( ) ;
2021-01-26 11:51:28 -04:00
}
// no config in the last search directory, create one now
// this will be the config that changes are written to
2021-05-25 10:01:03 -04:00
TSharedRef < FEditorConfig > NewConfig = MakeShared < FEditorConfig > ( ) ;
NewConfig - > OnEditorConfigDirtied ( ) . AddUObject ( this , & UEditorConfigSubsystem : : OnEditorConfigDirtied ) ;
if ( Parent . IsValid ( ) )
2021-01-26 11:51:28 -04:00
{
// parent to the previous config
2021-05-25 10:01:03 -04:00
NewConfig - > SetParent ( Parent ) ;
2021-01-26 11:51:28 -04:00
}
LoadedConfigs . Add ( FinalPath , NewConfig ) ;
return NewConfig ;
}
2021-05-25 10:01:03 -04:00
void UEditorConfigSubsystem : : OnEditorConfigDirtied ( const FEditorConfig & Config )
{
for ( const TPair < FString , TSharedPtr < FEditorConfig > > & Pair : LoadedConfigs )
{
if ( Pair . Value . Get ( ) = = & Config )
{
SaveConfig ( Pair . Value . ToSharedRef ( ) ) ;
}
}
}
void UEditorConfigSubsystem : : SaveConfig ( TSharedRef < FEditorConfig > Config )
2021-01-26 11:51:28 -04:00
{
const FString * FilePath = LoadedConfigs . FindKey ( Config ) ;
2021-05-25 10:01:03 -04:00
if ( ! ensureMsgf ( FilePath ! = nullptr , TEXT ( " Saving config that was not loaded through FEditorConfigSubsystem::FindOrLoadConfig. System does not know filepath to save to. " ) ) )
2021-01-26 11:51:28 -04:00
{
return ;
}
2022-10-27 12:40:12 -04:00
FScopeLock Lock ( & SaveLock ) ;
2021-01-26 11:51:28 -04:00
2021-05-25 10:01:03 -04:00
FPendingSave * Existing = PendingSaves . FindByPredicate ( [ Config ] ( const FPendingSave & Element )
2021-01-26 11:51:28 -04:00
{
2021-05-25 10:01:03 -04:00
return Element . Config = = Config ;
} ) ;
if ( Existing ! = nullptr )
{
// reset the timer if we're saving within the grace period and no save is already being executed
2022-11-01 15:07:25 -04:00
if ( ! Existing - > WasSuccess . IsValid ( ) )
2021-05-25 10:01:03 -04:00
{
Existing - > TimeSinceQueued = 0 ;
2021-01-26 11:51:28 -04:00
}
}
2021-05-25 10:01:03 -04:00
else
{
FPendingSave & NewSave = PendingSaves . AddDefaulted_GetRef ( ) ;
NewSave . Config = Config ;
NewSave . FileName = * FilePath ;
NewSave . TimeSinceQueued = 0 ;
}
2021-01-26 11:51:28 -04:00
}
void UEditorConfigSubsystem : : OnSaveCompleted ( TSharedPtr < FEditorConfig > Config )
{
2022-10-27 12:40:12 -04:00
FScopeLock Lock ( & SaveLock ) ;
2021-01-26 11:51:28 -04:00
2021-05-25 10:01:03 -04:00
const int32 Index = PendingSaves . IndexOfByPredicate ( [ Config ] ( const FPendingSave & Element )
2021-01-26 11:51:28 -04:00
{
return Element . Config = = Config ;
} ) ;
if ( Index ! = INDEX_NONE )
{
const FPendingSave & PendingSave = PendingSaves [ Index ] ;
2021-05-25 10:01:03 -04:00
PendingSave . Config - > OnSaved ( ) ;
2021-01-26 11:51:28 -04:00
}
}
2022-02-02 02:16:18 -05:00
void UEditorConfigSubsystem : : AddSearchDirectory ( ESearchDirectoryType Type , FStringView SearchDir )
2021-01-26 11:51:28 -04:00
{
2022-02-02 02:16:18 -05:00
TPair < ESearchDirectoryType , FString > NewEntry ( Type , SearchDir ) ;
if ( ! SearchDirectories . Contains ( NewEntry ) )
{
for ( int32 i = 0 ; i < SearchDirectories . Num ( ) ; + + i )
{
if ( SearchDirectories [ i ] . Key > Type )
{
SearchDirectories . Insert ( NewEntry , i ) ;
return ;
}
}
SearchDirectories . Add ( NewEntry ) ;
}
2023-06-22 09:59:51 -04:00
}
void UEditorConfigSubsystem : : EarlyAddSearchDirectory ( ESearchDirectoryType Type , FStringView SearchDir )
{
EarlyRegistredSearchDirectories . Emplace ( Type , SearchDir ) ;
check ( ! GEditor | | ! GEditor - > IsInitialized ( ) ) ;
2021-01-26 11:51:28 -04:00
}