Files
UnrealEngine/Engine/Plugins/Chooser/Source/ProxyTable/Private/ProxyTable.cpp
2025-05-18 13:04:45 +08:00

291 lines
8.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ProxyTable.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Logging/LogMacros.h"
#include "Misc/StringBuilder.h"
#include "UObject/Package.h"
DEFINE_LOG_CATEGORY_STATIC(LogProxyTable,Log,All);
#if WITH_EDITORONLY_DATA
//////////////////////////////////////////////////////////////////////////////////////
/// Proxy Entry
// Gets the uint32 key that will be used as the unique identifier for the Proxy Entry
const FGuid FProxyEntry::GetGuid() const
{
if (Proxy)
{
return Proxy->Guid;
}
else
{
// fallback for old FName key content
FGuid Guid;
if (Key != NAME_None)
{
Guid.A = GetTypeHash(WriteToString<128>(Key).ToView()); // Make sure this matches UProxyTableFunctionLibrary::EvaluateProxyTable
}
return Guid;
}
}
// == operator for TArray::Contains
bool FProxyEntry::operator== (const FProxyEntry& Other) const
{
return GetGuid() == Other.GetGuid();
}
// < operator for Algo::BinarySearch, and TArray::StableSort
bool FProxyEntry::operator< (const FProxyEntry& Other) const
{
return GetGuid() < Other.GetGuid();
}
//////////////////////////////////////////////////////////////////////////////////////
/// Proxy Table
static void BuildRuntimeDataRecursive(UProxyTable* RootTable, UProxyTable* Table, TArray<FProxyEntry>& OutEntriesArray, TArray<TWeakObjectPtr<UProxyTable>>& OutDependencies)
{
if(Table == nullptr)
{
return;
}
if (Table != RootTable)
{
OutDependencies.Add(Table);
}
for (const FProxyEntry& Entry : Table->Entries)
{
if (Entry.Proxy)
{
Entry.Proxy->ConditionalPostLoad();
}
int32 FoundIndex = OutEntriesArray.Find(Entry);
if (FoundIndex == INDEX_NONE)
{
OutEntriesArray.Add(Entry);
}
else
{
// check for Guid collisions
if (Entry.Proxy && OutEntriesArray[FoundIndex].Proxy)
{
if (Entry.Proxy != OutEntriesArray[FoundIndex].Proxy)
{
UE_LOG(LogProxyTable, Error, TEXT("Proxy Assets %s, and %s have the same Guid. They may have been duplicated outside the editor."),
*Entry.Proxy.GetName(), *OutEntriesArray[FoundIndex].Proxy.GetName());
}
}
else
{
// fallback for FName based keys
if (Entry.Key != OutEntriesArray[FoundIndex].Key)
{
UE_LOG(LogProxyTable, Error, TEXT("Proxy Key %s, and %s have the same Hash."),
*Entry.Key.ToString(), *OutEntriesArray[FoundIndex].Key.ToString());
}
}
}
}
for (const TObjectPtr<UProxyTable>& ParentTable : Table->InheritEntriesFrom)
{
if (!OutDependencies.Contains(ParentTable))
{
BuildRuntimeDataRecursive(RootTable, ParentTable, OutEntriesArray, OutDependencies);
}
}
}
void UProxyTable::BuildRuntimeData()
{
// Unregister callbacks on current dependencies
for (TWeakObjectPtr<UProxyTable> Dependency : TableDependencies)
{
if (Dependency.IsValid())
{
Dependency->OnProxyTableChanged.RemoveAll(this);
}
}
TableDependencies.Empty();
ProxyDependencies.Empty();
TArray<FProxyEntry> RuntimeEntries;
BuildRuntimeDataRecursive(this, this, RuntimeEntries, TableDependencies);
// sort by Key
RuntimeEntries.StableSort();
// Copy to Key and Value arrays
int EntryCount = RuntimeEntries.Num();
Keys.Empty(EntryCount);
RuntimeValues.Empty();
for(const FProxyEntry& Entry : RuntimeEntries)
{
Keys.Add(Entry.GetGuid());
RuntimeValues.Add({Entry.Proxy, Entry.ValueStruct, Entry.OutputStructData});
}
// register callbacks on updated dependencies
for (TWeakObjectPtr<UProxyTable> Dependency : TableDependencies)
{
Dependency->OnProxyTableChanged.AddUObject(this, &UProxyTable::BuildRuntimeData);
}
// keep a copy of proxy assets just for debugging purposes (indexes match the Key and Value arrays)
for(const FProxyEntry& Entry : RuntimeEntries)
{
if (Entry.Proxy)
{
ProxyDependencies.Add(Entry.Proxy);
}
}
}
void UProxyTable::PostTransacted(const FTransactionObjectEvent& TransactionEvent)
{
UObject::PostTransacted(TransactionEvent);
BuildRuntimeData();
OnProxyTableChanged.Broadcast();
}
#endif
void UProxyTable::PostLoad()
{
Super::PostLoad();
#if WITH_EDITORONLY_DATA
if (!GetPackage()->HasAnyPackageFlags(PKG_Cooked))
{
BuildRuntimeData();
}
#endif
// compilation for property accesses
for(FRuntimeProxyValue& Entry : RuntimeValues)
{
if (FObjectChooserBase* Result = Entry.Value.GetMutablePtr<FObjectChooserBase>())
{
// need to compile results in case one is a LookupProxy
Result->Compile(Entry.ProxyAsset, false);
}
/// compile any struct output property references
for(FProxyStructOutput& StructOutput : Entry.OutputStructData)
{
StructOutput.Binding.Compile(Entry.ProxyAsset);
#if WITH_EDITORONLY_DATA
if (!StructOutput.Binding.CompileMessage.IsEmpty())
{
UE_LOG(LogChooser, Error, TEXT("ProxyTable Struct Output Compile Error (%s - %s): %s."), *GetName(), *Entry.ProxyAsset.GetName(), *StructOutput.Binding.CompileMessage.ToString());
}
#endif
}
}
}
void UProxyTable::BeginDestroy()
{
RuntimeValues.Empty();
#if WITH_EDITORONLY_DATA
Entries.Empty();
#endif
Super::BeginDestroy();
}
static void OutputStructData(const UProxyTable* ProxyTable, const FRuntimeProxyValue& EntryValueData, FChooserEvaluationContext& Context)
{
for (const FProxyStructOutput& StructOutput : EntryValueData.OutputStructData)
{
VALIDATE_CHOOSER_CONTEXT(EntryValueData.ProxyAsset, EntryValueData.ProxyAsset->ContextData, Context);
// copy each struct output value
void* TargetData = nullptr;
const UStruct* StructType = nullptr;
if (StructOutput.Binding.GetStructPtr(Context, TargetData, StructType))
{
if (StructType == StructOutput.Value.GetScriptStruct())
{
StructOutput.Value.GetScriptStruct()->CopyScriptStruct(TargetData, StructOutput.Value.GetMemory());
}
else
{
UE_LOG(LogChooser, Warning, TEXT("Proxy Table %s Struct Output Type mismatch for Proxy Asset: %s"),
ToCStr(ProxyTable->GetName()), ToCStr(EntryValueData.ProxyAsset->GetName()));
}
}
}
}
UObject* UProxyTable::FindProxyObject(const FGuid& Key, FChooserEvaluationContext& Context) const
{
const int FoundIndex = Algo::BinarySearch(Keys, Key);
if (FoundIndex != INDEX_NONE)
{
const FRuntimeProxyValue& EntryValueData = RuntimeValues[FoundIndex];
const FObjectChooserBase &EntryValue = EntryValueData.Value.Get<const FObjectChooserBase>();
UObject* Result = EntryValue.ChooseObject(Context);
OutputStructData(this, EntryValueData, Context);
return Result;
}
return nullptr;
}
FObjectChooserBase::EIteratorStatus UProxyTable::FindProxyObjectMulti(const FGuid& Key, FChooserEvaluationContext &Context, FObjectChooserBase::FObjectChooserIteratorCallback Callback) const
{
const int FoundIndex = Algo::BinarySearch(Keys, Key);
if (FoundIndex != INDEX_NONE)
{
const FRuntimeProxyValue& EntryValueData = RuntimeValues[FoundIndex];
const FObjectChooserBase &EntryValue = EntryValueData.Value.Get<const FObjectChooserBase>();
FObjectChooserBase::EIteratorStatus Result = EntryValue.ChooseMulti(Context, Callback);
OutputStructData(this, EntryValueData, Context);
return Result;
}
return FObjectChooserBase::EIteratorStatus::Continue;
}
FObjectChooserBase::EIteratorStatus UProxyTable::IterateProxyObjects(const FGuid& Key, FChooserEvaluationContext& Context, FObjectChooserBase::FObjectChooserIteratorCallback Callback) const
{
const int FoundIndex = Algo::BinarySearch(Keys, Key);
if (FoundIndex != INDEX_NONE)
{
const FRuntimeProxyValue& EntryValueData = RuntimeValues[FoundIndex];
const FObjectChooserBase& EntryValue = EntryValueData.Value.Get<const FObjectChooserBase>();
FObjectChooserBase::EIteratorStatus Result = EntryValue.IterateObjects(Context, Callback);
return Result;
}
return FObjectChooserBase::EIteratorStatus::Continue;
}
FObjectChooserBase::EIteratorStatus UProxyTable::IterateProxyObjects(const FGuid& Key, FObjectChooserBase::FObjectChooserIteratorCallback Callback) const
{
FChooserEvaluationContext Context;
return IterateProxyObjects(Key, Context, Callback);
}
UProxyTable::UProxyTable(const FObjectInitializer& Initializer)
:Super(Initializer)
{
}