// 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& OutEntriesArray, TArray>& 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& ParentTable : Table->InheritEntriesFrom) { if (!OutDependencies.Contains(ParentTable)) { BuildRuntimeDataRecursive(RootTable, ParentTable, OutEntriesArray, OutDependencies); } } } void UProxyTable::BuildRuntimeData() { // Unregister callbacks on current dependencies for (TWeakObjectPtr Dependency : TableDependencies) { if (Dependency.IsValid()) { Dependency->OnProxyTableChanged.RemoveAll(this); } } TableDependencies.Empty(); ProxyDependencies.Empty(); TArray 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 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()) { // 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(); 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(); 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(); 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) { }