EM_Task/CoreUObject/Private/UObject/MetaData.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

506 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UObject/MetaData.h"
#include "Misc/ConfigCacheIni.h"
#include "HAL/IConsoleManager.h"
#include "UObject/Package.h"
#include "UObject/EditorObjectVersion.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
DEFINE_LOG_CATEGORY_STATIC(LogMetaData, Log, All);
//////////////////////////////////////////////////////////////////////////
// FMetaDataUtilities
#if WITH_EDITOR
FAutoConsoleCommand FMetaDataUtilities::DumpAllConsoleCommand = FAutoConsoleCommand(
TEXT("Metadata.Dump"),
TEXT("Dump all MetaData"),
FConsoleCommandDelegate::CreateStatic(&FMetaDataUtilities::DumpAllMetaData));
void FMetaDataUtilities::DumpMetaData(UMetaData* Object)
{
UE_LOG(LogMetaData, Log, TEXT("METADATA %s"), *Object->GetPathName());
for (TMap<FWeakObjectPtr, TMap<FName, FString>>::TIterator It(Object->ObjectMetaDataMap); It; ++It)
{
TMap<FName, FString>& MetaDataValues = It.Value();
for (TMap<FName, FString>::TIterator MetaDataIt(MetaDataValues); MetaDataIt; ++MetaDataIt)
{
FName Key = MetaDataIt.Key();
if (Key != FName(TEXT("ToolTip")))
{
UE_LOG(LogMetaData, Log, TEXT("%s: %s=%s"), *It.Key().Get()->GetPathName(), *MetaDataIt.Key().ToString(), *MetaDataIt.Value());
}
}
}
for (TMap<FName, FString>::TIterator MetaDataIt(Object->RootMetaDataMap); MetaDataIt; ++MetaDataIt)
{
FName Key = MetaDataIt.Key();
if (Key != FName(TEXT("ToolTip")))
{
UE_LOG(LogMetaData, Log, TEXT("Root: %s=%s"), *MetaDataIt.Key().ToString(), *MetaDataIt.Value());
}
}
}
void FMetaDataUtilities::DumpAllMetaData()
{
for (TObjectIterator<UMetaData> It; It; ++It)
{
UMetaData* MetaData = *It;
FMetaDataUtilities::DumpMetaData(MetaData);
}
}
FMetaDataUtilities::FMoveMetadataHelperContext::FMoveMetadataHelperContext(UObject* SourceObject, bool bSearchChildren)
{
// We only want to actually move things if we're in the editor
if (GIsEditor)
{
check(SourceObject);
UPackage* Package = SourceObject->GetOutermost();
check(Package);
OldPackage = Package;
OldObject = SourceObject;
bShouldSearchChildren = bSearchChildren;
}
}
FMetaDataUtilities::FMoveMetadataHelperContext::~FMoveMetadataHelperContext()
{
// We only want to actually move things if we're in the editor
if (GIsEditor && (OldObject->GetOutermost() != OldPackage))
{
UMetaData* NewMetaData = OldObject->GetOutermost()->GetMetaData();
UMetaData* OldMetaData = OldPackage->GetMetaData();
TMap<FName, FString>* OldObjectMetaData = OldMetaData->ObjectMetaDataMap.Find(OldObject);
if (OldObjectMetaData != NULL)
{
NewMetaData->SetObjectValues(OldObject, *OldObjectMetaData);
}
if (bShouldSearchChildren)
{
TArray<UObject*> Children;
GetObjectsWithOuter(OldObject, Children, true);
for (auto ChildIt = Children.CreateConstIterator(); ChildIt; ++ChildIt)
{
UObject* Child = *ChildIt;
TMap<FName, FString>* OldObjectMetaDataPtr = OldMetaData->ObjectMetaDataMap.Find(Child);
if (OldObjectMetaDataPtr)
{
NewMetaData->SetObjectValues(Child, *OldObjectMetaDataPtr);
}
}
}
}
}
#endif // WITH_EDITOR
//////////////////////////////////////////////////////////////////////////
// UMetaData implementation.
TMap<FName, FName> UMetaData::KeyRedirectMap;
IMPLEMENT_CORE_INTRINSIC_CLASS(UMetaData, UObject,
{});
IMPLEMENT_FARCHIVE_SERIALIZER(UMetaData);
void UMetaData::Serialize(FStructuredArchive::FRecord Record)
{
FArchive& UnderlyingArchive = Record.GetUnderlyingArchive();
Super::Serialize(Record);
UnderlyingArchive.UsingCustomVersion(FEditorObjectVersion::GUID);
if (UnderlyingArchive.IsSaving())
{
// Remove entries belonging to destructed objects
for (TMap<FWeakObjectPtr, TMap<FName, FString>>::TIterator It(ObjectMetaDataMap); It; ++It)
{
if (!It.Key().IsValid())
{
It.RemoveCurrent();
}
}
}
if (!UnderlyingArchive.IsLoading())
{
Record << SA_VALUE(TEXT("ObjectMetaDataMap"), ObjectMetaDataMap);
Record << SA_VALUE(TEXT("RootMetaDataMap"), RootMetaDataMap);
}
else
{
{
TMap<FWeakObjectPtr, TMap<FName, FString>> TempMap;
Record << SA_VALUE(TEXT("ObjectMetaDataMap"), TempMap);
const bool bLoadFromLinker = (NULL != UnderlyingArchive.GetLinker());
if (bLoadFromLinker && HasAnyFlags(RF_LoadCompleted))
{
UE_LOG(LogMetaData, Verbose, TEXT("Metadata was already loaded by linker. %s"), *GetFullName());
}
else
{
if (bLoadFromLinker && ObjectMetaDataMap.Num())
{
UE_LOG(LogMetaData, Verbose, TEXT("Metadata: Some values, filled while serialization, may be lost. %s"), *GetFullName());
}
Swap(ObjectMetaDataMap, TempMap);
}
}
if (UnderlyingArchive.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::RootMetaDataSupport)
{
TMap<FName, FString> TempMap;
Record << SA_VALUE(TEXT("RootMetaDataMap"), TempMap);
const bool bLoadFromLinker = (NULL != UnderlyingArchive.GetLinker());
if (bLoadFromLinker && HasAnyFlags(RF_LoadCompleted))
{
UE_LOG(LogMetaData, Verbose, TEXT("Root metadata was already loaded by linker. %s"), *GetFullName());
}
else
{
if (bLoadFromLinker && RootMetaDataMap.Num())
{
UE_LOG(LogMetaData, Verbose, TEXT("Metadata: Some root values, filled while serialization, may be lost. %s"), *GetFullName());
}
Swap(RootMetaDataMap, TempMap);
}
}
// Run redirects on loaded keys
InitializeRedirectMap();
for (TMap<FWeakObjectPtr, TMap<FName, FString>>::TIterator ObjectIt(ObjectMetaDataMap); ObjectIt; ++ObjectIt)
{
TMap<FName, FString>& CurrentMap = ObjectIt.Value();
for (TMap<FName, FString>::TIterator PairIt(CurrentMap); PairIt; ++PairIt)
{
const FName OldKey = PairIt.Key();
const FName NewKey = KeyRedirectMap.FindRef(OldKey);
if (NewKey != NAME_None)
{
const FString Value = PairIt.Value();
PairIt.RemoveCurrent();
CurrentMap.Add(NewKey, Value);
UE_LOG(LogMetaData, Verbose, TEXT("Remapping old metadata key '%s' to new key '%s' on object '%s'."), *OldKey.ToString(), *NewKey.ToString(), *ObjectIt.Key().Get()->GetPathName());
}
}
}
for (TMap<FName, FString>::TIterator PairIt(RootMetaDataMap); PairIt; ++PairIt)
{
const FName OldKey = PairIt.Key();
const FName NewKey = KeyRedirectMap.FindRef(OldKey);
if (NewKey != NAME_None)
{
const FString Value = PairIt.Value();
PairIt.RemoveCurrent();
RootMetaDataMap.Add(NewKey, Value);
UE_LOG(LogMetaData, Verbose, TEXT("Remapping old metadata key '%s' to new key '%s' on root."), *OldKey.ToString(), *NewKey.ToString());
}
}
}
#if 0 && WITH_EDITOR
FMetaDataUtilities::DumpMetaData(this);
#endif
}
/**
* Return the value for the given key in the given property
* @param Object the object to lookup the metadata for
* @param Key The key to lookup
* @return The value if found, otherwise an empty string
*/
const FString& UMetaData::GetValue(const UObject* Object, FName Key)
{
// if not found, return a static empty string
static FString EmptyString;
// every key needs to be valid
if (Key == NAME_None)
{
return EmptyString;
}
// look up the existing map if we have it
TMap<FName, FString>* ObjectValues = ObjectMetaDataMap.Find(Object);
// if not, return empty
if (ObjectValues == NULL)
{
return EmptyString;
}
// look for the property
FString* ValuePtr = ObjectValues->Find(Key);
// if we didn't find it, return NULL
if (!ValuePtr)
{
return EmptyString;
}
// if we found it, return the pointer to the character data
return *ValuePtr;
}
/**
* Return the value for the given key in the given property
* @param Object the object to lookup the metadata for
* @param Key The key to lookup
* @return The value if found, otherwise an empty string
*/
const FString& UMetaData::GetValue(const UObject* Object, const TCHAR* Key)
{
// only find names, don't bother creating a name if it's not already there
// (GetValue will return an empty string if Key is NAME_None)
return GetValue(Object, FName(Key, FNAME_Find));
}
const FString* UMetaData::FindValue(const UObject* Object, FName Key)
{
// every key needs to be valid
if (Key == NAME_None)
{
return nullptr;
}
// look up the existing map if we have it
TMap<FName, FString>* ObjectValues = ObjectMetaDataMap.Find(Object);
// if not, return false
if (ObjectValues == nullptr)
{
return nullptr;
}
// if we had the map, see if we had the key
return ObjectValues->Find(Key);
}
const FString* UMetaData::FindValue(const UObject* Object, const TCHAR* Key)
{
// only find names, don't bother creating a name if it's not already there
// (HasValue will return false if Key is NAME_None)
return FindValue(Object, FName(Key, FNAME_Find));
}
/**
* Is there any metadata for this property?
* @param Object the object to lookup the metadata for
* @return TrUE if the property has any metadata at all
*/
bool UMetaData::HasObjectValues(const UObject* Object)
{
return ObjectMetaDataMap.Contains(Object);
}
/**
* Set the key/value pair in the Property's metadata
* @param Object the object to set the metadata for
* @Values The metadata key/value pairs
*/
void UMetaData::SetObjectValues(const UObject* Object, const TMap<FName, FString>& ObjectValues)
{
ObjectMetaDataMap.Add(const_cast<UObject*>(Object), ObjectValues);
}
/**
* Set the key/value pair in the Property's metadata
* @param Object the object to set the metadata for
* @Values The metadata key/value pairs
*/
void UMetaData::SetObjectValues(const UObject* Object, TMap<FName, FString>&& ObjectValues)
{
ObjectMetaDataMap.Add(const_cast<UObject*>(Object), MoveTemp(ObjectValues));
}
/**
* Set the key/value pair in the Property's metadata
* @param Object the object to set the metadata for
* @param Key A key to set the data for
* @param Value The value to set for the key
* @Values The metadata key/value pairs
*/
void UMetaData::SetValue(const UObject* Object, FName Key, const TCHAR* Value)
{
check(Key != NAME_None);
if (!HasAllFlags(RF_LoadCompleted))
{
UE_LOG(LogMetaData, Error, TEXT("MetaData::SetValue called before meta data is completely loaded. %s'"), *GetPathName());
}
// look up the existing map if we have it
TMap<FName, FString>* ObjectValues = ObjectMetaDataMap.Find(Object);
// if not, create an empty map
if (ObjectValues == NULL)
{
ObjectValues = &ObjectMetaDataMap.Add(const_cast<UObject*>(Object), TMap<FName, FString>());
}
// set the value for the key
ObjectValues->Add(Key, Value);
}
// Set the Key/Value pair in the Object's metadata
void UMetaData::SetValue(const UObject* Object, const TCHAR* Key, const TCHAR* Value)
{
SetValue(Object, FName(Key), Value);
}
void UMetaData::RemoveValue(const UObject* Object, const TCHAR* Key)
{
RemoveValue(Object, FName(Key));
}
void UMetaData::RemoveValue(const UObject* Object, FName Key)
{
check(Key != NAME_None);
TMap<FName, FString>* ObjectValues = ObjectMetaDataMap.Find(Object);
if (ObjectValues)
{
// set the value for the key
ObjectValues->Remove(Key);
}
}
TMap<FName, FString>* UMetaData::GetMapForObject(const UObject* Object)
{
check(Object);
UPackage* Package = Object->GetOutermost();
check(Package);
UMetaData* Metadata = Package->GetMetaData();
check(Metadata);
TMap<FName, FString>* Map = Metadata->ObjectMetaDataMap.Find(Object);
return Map;
}
void UMetaData::CopyMetadata(UObject* SourceObject, UObject* DestObject)
{
check(SourceObject);
check(DestObject);
// First get the source map
TMap<FName, FString>* SourceMap = GetMapForObject(SourceObject);
if (!SourceMap)
return;
// Then get the metadata for the destination
UPackage* DestPackage = DestObject->GetOutermost();
check(DestPackage);
UMetaData* DestMetadata = DestPackage->GetMetaData();
check(DestMetadata);
// Iterate through source map, setting each key/value pair in the destination
for (const auto& It: *SourceMap)
{
DestMetadata->SetValue(DestObject, *It.Key.ToString(), *It.Value);
}
}
/**
* Removes any metadata entries that are to objects not inside the same package as this UMetaData object.
*/
void UMetaData::RemoveMetaDataOutsidePackage()
{
TArray<FWeakObjectPtr> ObjectsToRemove;
// Get the package that this MetaData is in
UPackage* MetaDataPackage = GetOutermost();
// Iterate over all entries..
for (TMap<FWeakObjectPtr, TMap<FName, FString>>::TIterator It(ObjectMetaDataMap); It; ++It)
{
FWeakObjectPtr ObjPtr = It.Key();
// See if its package is not the same as the MetaData's, or is invalid
if (!ObjPtr.IsValid() || (ObjPtr.Get()->GetOutermost() != MetaDataPackage))
{
// Add to list of things to remove
ObjectsToRemove.Add(ObjPtr);
}
}
// Go through and remove any objects that need it
for (int32 i = 0; i < ObjectsToRemove.Num(); i++)
{
FWeakObjectPtr ObjPtr = ObjectsToRemove[i];
UObject* ObjectToRemove = ObjPtr.Get();
if ((ObjectToRemove != NULL) && (ObjectToRemove->GetOutermost() != GetTransientPackage()))
{
UE_LOG(LogMetaData, Log, TEXT("Removing '%s' ref from Metadata '%s'"), *ObjectToRemove->GetPathName(), *GetPathName());
}
ObjectMetaDataMap.Remove(ObjPtr);
}
}
bool UMetaData::NeedsLoadForEditorGame() const
{
return true;
}
void UMetaData::InitializeRedirectMap()
{
static bool bKeyRedirectMapInitialized = false;
if (!bKeyRedirectMapInitialized)
{
if (GConfig)
{
const FName MetadataRedirectsName(TEXT("MetadataRedirects"));
FConfigSection* PackageRedirects = GConfig->GetSectionPrivate(TEXT("CoreUObject.Metadata"), false, true, GEngineIni);
if (PackageRedirects)
{
for (FConfigSection::TIterator It(*PackageRedirects); It; ++It)
{
if (It.Key() == MetadataRedirectsName)
{
FName OldKey = NAME_None;
FName NewKey = NAME_None;
FParse::Value(*It.Value().GetValue(), TEXT("OldKey="), OldKey);
FParse::Value(*It.Value().GetValue(), TEXT("NewKey="), NewKey);
check(OldKey != NewKey);
check(OldKey != NAME_None);
check(NewKey != NAME_None);
check(!KeyRedirectMap.Contains(OldKey));
check(!KeyRedirectMap.Contains(NewKey));
KeyRedirectMap.Add(OldKey, NewKey);
}
}
}
bKeyRedirectMapInitialized = true;
}
}
}
FName UMetaData::GetRemappedKeyName(FName OldKey)
{
InitializeRedirectMap();
return KeyRedirectMap.FindRef(OldKey);
}