Files
UnrealEngineUWP/Engine/Source/Editor/EditorConfig/Public/EditorConfig.h
sebastian nordgren 8eafe4f70e Editor configs now correctly diff against the CDO when serializing.
Previously, I had assumed that FProperty::InitializeValue() would initialize a simple member (eg. a float) to the correct value, but this appears not to be the case, rather values are zero-initialized and the CDO stores the correct default values. In the case of structs, InitializeStruct() does in fact initialize correctly.

However, this means that we can't use GetMutableDefault() to get an instance of the UObject, since then we're changing what we're diffing against and serialize no values. Changed the usages of this pattern to use singletons instead.

#jira UE-141150
#review-19900014 @lauren.barnes, @aditya.ravichandran
#preflight 6267fa44a021c91a50f26a65

[CL 19919719 by sebastian nordgren in ue5-main branch]
2022-04-26 10:20:56 -04:00

225 lines
7.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "JsonConfig.h"
#include "Concepts/EqualityComparable.h"
#include "Dom/JsonObject.h"
#include "Templates/IsPointer.h"
#include "Templates/Models.h"
#include "Templates/UnrealTemplate.h"
class EDITORCONFIG_API FEditorConfig
{
public:
enum class EPropertyFilter
{
All,
MetadataOnly
};
FEditorConfig();
void SetParent(TSharedPtr<FEditorConfig> InConfig);
bool LoadFromString(FStringView Content);
bool LoadFromFile(FStringView FilePath);
bool SaveToString(FString& OutResult) const;
bool IsValid() const { return JsonConfig.IsValid() && JsonConfig->IsValid(); }
TSharedPtr<FEditorConfig> GetParentConfig() const;
// UStruct & UObject
template <typename T>
bool TryGetStruct(FStringView Key, T& OutValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly) const;
template <typename T>
bool TryGetUObject(FStringView Key, T& OutValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly) const;
template <typename T>
bool TryGetRootStruct(T& OutValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly) const;
template <typename T>
bool TryGetRootUObject(T& OutValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly) const;
template <typename T>
bool TryGetRootUObject(T* OutValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly) const;
bool TryGetRootStruct(const UStruct* Class, void* OutValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly) const;
bool TryGetRootUObject(const UClass* Class, UObject* OutValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly) const;
template <typename T>
void SetStruct(FStringView Key, const T& InValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly);
template <typename T>
void SetUObject(FStringView Key, const T& InValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly);
template <typename T>
void SetRootStruct(const T& InValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly);
template <typename T>
void SetRootUObject(const T& InValue, EPropertyFilter Filter = EPropertyFilter::MetadataOnly);
void SetRootStruct(const UStruct* Class, const void* Instance, EPropertyFilter Filter = EPropertyFilter::MetadataOnly);
void SetRootUObject(const UClass* Class, const UObject* Instance, EPropertyFilter Filter = EPropertyFilter::MetadataOnly);
bool HasOverride(FStringView Key) const;
void OnSaved();
DECLARE_EVENT_OneParam(FEditorConfig, FOnEditorConfigDirtied, const FEditorConfig&);
FOnEditorConfigDirtied& OnEditorConfigDirtied() { return EditorConfigDirtiedEvent; }
private:
friend class UEditorConfigSubsystem; // for access to LoadFromFile and SaveToFile
static void ReadUObject(const TSharedPtr<FJsonObject>& JsonObject, const UClass* Class, UObject* Instance, EPropertyFilter Filter);
static void ReadStruct(const TSharedPtr<FJsonObject>& JsonObject, const UStruct* Struct, void* Instance, UObject* Owner, EPropertyFilter Filter);
static void ReadValue(const TSharedPtr<FJsonValue>& JsonValue, const FProperty* Property, void* DataPtr, UObject* Owner);
static TSharedPtr<FJsonObject> WriteStruct(const UStruct* Struct, const void* Instance, const void* Defaults, EPropertyFilter Filter);
static TSharedPtr<FJsonObject> WriteUObject(const UClass* Class, const UObject* Instance, EPropertyFilter Filter);
static TSharedPtr<FJsonValue> WriteArray(const FArrayProperty* ArrayProperty, const void* DataPtr);
static TSharedPtr<FJsonValue> WriteSet(const FSetProperty* Property, const void* DataPtr);
static TSharedPtr<FJsonValue> WriteMap(const FMapProperty* Property, const void* DataPtr);
static TSharedPtr<FJsonValue> WriteValue(const FProperty* Property, const void* DataPtr, const void* Defaults);
bool SaveToFile(FStringView FilePath) const;
void SetDirty();
private:
TSharedPtr<UE::FJsonConfig> JsonConfig;
TSharedPtr<FEditorConfig> ParentConfig;
FOnEditorConfigDirtied EditorConfigDirtiedEvent;
bool Dirty { false };
};
template <typename T>
bool FEditorConfig::TryGetStruct(FStringView Key, T& OutValue, EPropertyFilter Filter) const
{
if (!IsValid())
{
return false;
}
TSharedPtr<FJsonObject> StructData;
UE::FJsonPath Path(Key);
if (!JsonConfig->TryGetJsonObject(Path, StructData))
{
return false;
}
if (!StructData.IsValid())
{
return false;
}
const UStruct* Struct = T::StaticStruct();
ReadStruct(StructData, Struct, &OutValue, nullptr, Filter);
return true;
}
template <typename T>
bool FEditorConfig::TryGetUObject(FStringView Key, T& OutValue, EPropertyFilter Filter) const
{
static_assert(TIsDerivedFrom<T, UObject>::Value, "Type is not derived from UObject.");
if (!IsValid())
{
return false;
}
TSharedPtr<FJsonObject> UObjectData;
UE::FJsonPath Path(Key);
if (!JsonConfig->TryGetJsonObject(Path, UObjectData))
{
return false;
}
if (!UObjectData.IsValid())
{
return false;
}
const UClass* Class = T::StaticClass();
ReadUObject(UObjectData, Class, &OutValue, Filter);
return true;
}
template <typename T>
bool FEditorConfig::TryGetRootStruct(T& OutValue, EPropertyFilter Filter) const
{
return TryGetRootStruct(T::StaticStruct(), &OutValue, Filter);
}
template <typename T>
bool FEditorConfig::TryGetRootUObject(T& OutValue, EPropertyFilter Filter) const
{
static_assert(TIsDerivedFrom<T, UObject>::Value, "Type is not derived from UObject.");
return TryGetRootUObject(T::StaticClass(), &OutValue, Filter);
}
template <typename T>
bool FEditorConfig::TryGetRootUObject(T* OutValue, EPropertyFilter Filter) const
{
static_assert(TIsDerivedFrom<T, UObject>::Value, "Type is not derived from UObject.");
checkf(OutValue != nullptr, TEXT("Output value was null."));
return TryGetRootUObject(T::StaticClass(), OutValue, Filter);
}
template <typename T>
void FEditorConfig::SetStruct(FStringView Key, const T& InValue, EPropertyFilter Filter)
{
if (!IsValid())
{
return;
}
TSharedPtr<FJsonObject> JsonObject = WriteStruct(T::StaticStruct(), &InValue, nullptr, Filter);
JsonConfig->SetJsonObject(UE::FJsonPath(Key), JsonObject);
SetDirty();
}
template <typename T>
void FEditorConfig::SetUObject(FStringView Key, const T& InValue, EPropertyFilter Filter)
{
static_assert(TIsDerivedFrom<T, UObject>::Value, "Type is not derived from UObject.");
if (!IsValid())
{
return;
}
TSharedPtr<FJsonObject> JsonObject = WriteUObject(T::StaticClass(), &InValue);
JsonConfig->SetJsonObject(UE::FJsonPath(Key), JsonObject);
SetDirty();
}
template <typename T>
void FEditorConfig::SetRootStruct(const T& InValue, EPropertyFilter Filter)
{
SetRootStruct(T::StaticStruct(), &InValue, Filter);
}
template <typename T>
void FEditorConfig::SetRootUObject(const T& InValue, EPropertyFilter Filter)
{
if constexpr (TIsPointer<T>::Value)
{
static_assert(TIsDerivedFrom<typename TRemovePointer<T>::Type, UObject>::Value, "Type is not derived from UObject.");
checkf(InValue != nullptr, TEXT("Object value was null."));
SetRootUObject(TRemovePointer<T>::Type::StaticClass(), InValue, Filter);
}
else
{
static_assert(TIsDerivedFrom<typename TRemovePointer<T>::Type, UObject>::Value, "Type is not derived from UObject.");
SetRootUObject(T::StaticClass(), &InValue, Filter);
}
}