Files
UnrealEngine/Engine/Plugins/Runtime/StateTree/Source/StateTreeEditorModule/Private/StateTreePropertyBindingCompiler.cpp
2025-05-18 13:04:45 +08:00

476 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StateTreePropertyBindingCompiler.h"
#include "IPropertyAccessEditor.h"
#include "PropertyPathHelpers.h"
#include "StateTreeCompilerLog.h"
#include "StateTreeEditorPropertyBindings.h"
#include "Misc/EnumerateRange.h"
#include "StateTreePropertyBindings.h"
#include "StateTreePropertyRef.h"
#include "StateTreePropertyRefHelpers.h"
#include "StateTreePropertyHelpers.h"
#include "StateTreeDelegate.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(StateTreePropertyBindingCompiler)
bool FStateTreePropertyBindingCompiler::Init(FStateTreePropertyBindings& InPropertyBindings, FStateTreeCompilerLog& InLog)
{
Log = &InLog;
PropertyBindings = &InPropertyBindings;
PropertyBindings->Reset();
SourceStructs.Reset();
return true;
}
bool FStateTreePropertyBindingCompiler::CompileBatch(const FStateTreeBindableStructDesc& TargetStruct, TConstArrayView<FStateTreePropertyPathBinding> BatchPropertyBindings, FStateTreeIndex16 PropertyFuncsBegin, FStateTreeIndex16 PropertyFuncsEnd, int32& OutBatchIndex)
{
check(Log);
check(PropertyBindings);
OutBatchIndex = INDEX_NONE;
StoreSourceStructs();
struct FSortedBinding
{
FStateTreePropertyPathBinding Binding;
TArray<FPropertyBindingPathIndirection> TargetIndirections;
};
TArray<FSortedBinding> NewBindings;
for (const FStateTreePropertyPathBinding& Binding : BatchPropertyBindings)
{
if (Binding.GetTargetPath().GetStructID() != TargetStruct.ID)
{
continue;
}
// Source must be in the source array
const FStateTreeBindableStructDesc* SourceStruct = GetSourceStructDescByID(Binding.GetSourcePath().GetStructID());
if (!SourceStruct)
{
Log->Reportf(EMessageSeverity::Error, TargetStruct,
TEXT("Could not find a binding source."));
return false;
}
FString Error;
TArray<FPropertyBindingPathIndirection> SourceIndirections;
TArray<FPropertyBindingPathIndirection> TargetIndirections;
if (!Binding.GetSourcePath().ResolveIndirections(SourceStruct->Struct, SourceIndirections, &Error))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Resolving path in %s: %s"), *SourceStruct->ToString(), *Error);
return false;
}
if (!Binding.GetTargetPath().ResolveIndirections(TargetStruct.Struct, TargetIndirections, &Error))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Resolving path in %s: %s"), *TargetStruct.ToString(), *Error);
return false;
}
FPropertyBindingCopyInfo DummyCopy;
FPropertyBindingPathIndirection LastSourceIndirection = !SourceIndirections.IsEmpty() ? SourceIndirections.Last() : FPropertyBindingPathIndirection(SourceStruct->Struct);
FPropertyBindingPathIndirection LastTargetIndirection = !TargetIndirections.IsEmpty() ? TargetIndirections.Last() : FPropertyBindingPathIndirection(TargetStruct.Struct);
if (!PropertyBindings->FPropertyBindingBindingCollection::ResolveCopyType(LastSourceIndirection, LastTargetIndirection, DummyCopy, FStateTreeStructRef::StaticStruct()))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct,
TEXT("Cannot copy properties between %s and %s, properties are incompatible."),
*UE::StateTree::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()),
*UE::StateTree::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()));
return false;
}
FSortedBinding& NewBinding = NewBindings.AddDefaulted_GetRef();
NewBinding.Binding = FStateTreePropertyPathBinding(SourceStruct->DataHandle, Binding.GetSourcePath(), Binding.GetTargetPath());
NewBinding.TargetIndirections = MoveTemp(TargetIndirections);
}
if (!NewBindings.IsEmpty())
{
// Sort bindings base on copy target memory layout.
NewBindings.StableSort([](const FSortedBinding& A, const FSortedBinding& B)
{
const int32 MaxSegments = FMath::Min(A.TargetIndirections.Num(), B.TargetIndirections.Num());
for (int32 Index = 0; Index < MaxSegments; Index++)
{
// If property A is in struct before B, copy A first.
if (A.TargetIndirections[Index].GetPropertyOffset() < B.TargetIndirections[Index].GetPropertyOffset())
{
return true;
}
// If A and B points to the same property, choose the one that points to an earlier array item.
// Note: this assumes that INDEX_NONE = -1, which means that binding directly to an array comes before an array access,
// and non-array access will compare equal (both INDEX_NONE).
if (A.TargetIndirections[Index].GetPropertyOffset() == B.TargetIndirections[Index].GetPropertyOffset()
&& A.TargetIndirections[Index].GetArrayIndex() < B.TargetIndirections[Index].GetArrayIndex())
{
return true;
}
}
// We get here if the common path is the same, shorter path wins.
return A.TargetIndirections.Num() <= B.TargetIndirections.Num();
});
// Store bindings batch.
const int32 BindingsBegin = PropertyBindings->PropertyPathBindings.Num();
for (const FSortedBinding& NewBinding : NewBindings)
{
PropertyBindings->PropertyPathBindings.Add(NewBinding.Binding);
}
const int32 BindingsEnd = PropertyBindings->PropertyPathBindings.Num();
FPropertyBindingCopyInfoBatch& Batch = PropertyBindings->AddCopyBatch();
Batch.TargetStruct = TInstancedStruct<FStateTreeBindableStructDesc>::Make(TargetStruct);
Batch.BindingsBegin = FPropertyBindingIndex16(BindingsBegin);
Batch.BindingsEnd = FPropertyBindingIndex16(BindingsEnd);
Batch.PropertyFunctionsBegin = PropertyFuncsBegin;
Batch.PropertyFunctionsEnd = PropertyFuncsEnd;
OutBatchIndex = PropertyBindings->GetNumCopyBatches() - 1;
}
return true;
}
bool FStateTreePropertyBindingCompiler::CompileDelegateDispatchers(
const FStateTreeBindableStructDesc& SourceStruct,
TConstArrayView<FStateTreeEditorDelegateDispatcherCompiledBinding> PreviousCompiledDistpatchers,
TConstArrayView<FStateTreePropertyPathBinding> DelegateDispatcherBindings,
FStateTreeDataView InstanceDataView)
{
check(Log);
check(PropertyBindings);
StoreSourceStructs();
bool bSuccess = true;
for (const FStateTreePropertyPathBinding& Binding : DelegateDispatcherBindings)
{
if (Binding.GetSourcePath().GetStructID() != SourceStruct.ID)
{
continue;
}
// Source must be in the source array
const FStateTreeBindableStructDesc* DispatcherStruct = GetSourceStructDescByID(Binding.GetSourcePath().GetStructID());
if (!DispatcherStruct)
{
Log->Reportf(EMessageSeverity::Error, SourceStruct, TEXT("Could not find a binding source."));
bSuccess = false;
continue;
}
auto FindBySourcePathPredicate = [&Binding](const FStateTreeEditorDelegateDispatcherCompiledBinding& Other)
{
return Other.DispatcherPath == Binding.GetSourcePath();
};
if (!CompiledDelegateDispatchers.ContainsByPredicate(FindBySourcePathPredicate))
{
FString Error;
TArray<FPropertyBindingPathIndirection> DispatcherIndirections;
if (!Binding.GetSourcePath().ResolveIndirectionsWithValue(InstanceDataView, DispatcherIndirections, &Error))
{
Log->Reportf(EMessageSeverity::Error, SourceStruct, TEXT("Resolving path in %s: %s"), *DispatcherStruct->ToString(), *Error);
bSuccess = false;
continue;
}
const FPropertyBindingPathIndirection& DispatcherLeafIndirection = DispatcherIndirections.Last();
const FStructProperty* LeafAsStructProperty = CastField<FStructProperty>(DispatcherLeafIndirection.GetProperty());
if (LeafAsStructProperty == nullptr || LeafAsStructProperty->Struct != FStateTreeDelegateDispatcher::StaticStruct())
{
Log->Reportf(EMessageSeverity::Error, SourceStruct, TEXT("The source is not a valid delegate dispatcher."));
bSuccess = false;
continue;
}
if (DispatcherLeafIndirection.GetContainerAddress() == nullptr)
{
Log->Reportf(EMessageSeverity::Error, SourceStruct, TEXT("The dispatcher can't be initialized."));
bSuccess = false;
continue;
}
FStateTreeDelegateDispatcher* Dispatcher = reinterpret_cast<FStateTreeDelegateDispatcher*>(DispatcherLeafIndirection.GetMutablePropertyAddress());
if (Dispatcher == nullptr)
{
Log->Reportf(EMessageSeverity::Error, SourceStruct, TEXT("The dispatcher can't be initialized."));
bSuccess = false;
continue;
}
if (const FStateTreeEditorDelegateDispatcherCompiledBinding* PreviousCompiled = PreviousCompiledDistpatchers.FindByPredicate(FindBySourcePathPredicate))
{
// Reuse previous ID
*Dispatcher = PreviousCompiled->ID;
}
else
{
Dispatcher->ID = FGuid::NewGuid();
}
const bool bFoundID = CompiledDelegateDispatchers.ContainsByPredicate([ID = Dispatcher->ID](const FStateTreeEditorDelegateDispatcherCompiledBinding& Other)
{
return Other.ID.ID == ID;
});
ensureMsgf(!bFoundID, TEXT("The ID is already used by another delegate dispatcher."));
CompiledDelegateDispatchers.Add({ Binding.GetSourcePath(), *Dispatcher });
}
}
return bSuccess;
}
bool FStateTreePropertyBindingCompiler::CompileDelegateListeners(
const FStateTreeBindableStructDesc& TargetStruct,
TConstArrayView<FStateTreePropertyPathBinding> DelegateSourceBindings,
FStateTreeDataView InstanceDataView)
{
check(Log);
check(PropertyBindings);
StoreSourceStructs();
bool bSuccess = true;
for (const FStateTreePropertyPathBinding& Binding : DelegateSourceBindings)
{
if (Binding.GetTargetPath().GetStructID() != TargetStruct.ID)
{
continue;
}
// Source must be in the source array
const FStateTreeBindableStructDesc* DispatcherStruct = GetSourceStructDescByID(Binding.GetSourcePath().GetStructID());
if (!DispatcherStruct)
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Could not find a binding source."));
bSuccess = false;
continue;
}
FString Error;
TArray<FPropertyBindingPathIndirection> ListenerIndirections;
if (!Binding.GetTargetPath().ResolveIndirectionsWithValue(InstanceDataView, ListenerIndirections, &Error))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Resolving path in %s: %s"), *TargetStruct.ToString(), *Error);
bSuccess = false;
continue;
}
const FStateTreeDelegateDispatcher Dispatcher = GetDispatcherFromPath(Binding.GetSourcePath());
if (!Dispatcher.IsValid())
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Delegate Listener %s is bound to unknown dispatcher %s"), *TargetStruct.ToString(), *DispatcherStruct->ToString());
bSuccess = false;
continue;
}
FPropertyBindingPathIndirection& ListenerLeafIndirection = ListenerIndirections.Last();
const FStructProperty* LeafAsStructProperty = CastField<FStructProperty>(ListenerLeafIndirection.GetProperty());
check(LeafAsStructProperty && LeafAsStructProperty->Struct == FStateTreeDelegateListener::StaticStruct());
if (LeafAsStructProperty == nullptr || LeafAsStructProperty->Struct != FStateTreeDelegateListener::StaticStruct())
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("The target is not a valid delegate listener."));
bSuccess = false;
continue;
}
FStateTreeDelegateListener* Listener = reinterpret_cast<FStateTreeDelegateListener*>(ListenerLeafIndirection.GetMutablePropertyAddress());
Listener->Dispatcher = Dispatcher;
Listener->ID = ++ListenersNum;
}
return bSuccess;
}
bool FStateTreePropertyBindingCompiler::CompileReferences(const FStateTreeBindableStructDesc& TargetStruct, TConstArrayView<FStateTreePropertyPathBinding> PropertyReferenceBindings, FStateTreeDataView InstanceDataView, const TMap<FGuid, const FStateTreeDataView>& IDToStructValue)
{
for (const FStateTreePropertyPathBinding& Binding : PropertyReferenceBindings)
{
if (Binding.GetTargetPath().GetStructID() != TargetStruct.ID)
{
continue;
}
// Source must be in the source array
const FStateTreeBindableStructDesc* SourceStruct = GetSourceStructDescByID(Binding.GetSourcePath().GetStructID());
if (!SourceStruct)
{
Log->Reportf(EMessageSeverity::Error, TargetStruct,
TEXT("Could not find a binding source."));
return false;
}
const FStateTreeDataView* SourceDataView = IDToStructValue.Find(Binding.GetSourcePath().GetStructID());
if (!SourceDataView)
{
Log->Reportf(EMessageSeverity::Error, TargetStruct,
TEXT("Could not find a binding source data view."));
return false;
}
FString Error;
TArray<FPropertyBindingPathIndirection> SourceIndirections;
if (!Binding.GetSourcePath().ResolveIndirectionsWithValue(*SourceDataView, SourceIndirections, &Error))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Resolving path in %s: %s"), *SourceStruct->ToString(), *Error);
return false;
}
if (!UE::StateTree::PropertyRefHelpers::IsPropertyAccessibleForPropertyRef(SourceIndirections, *SourceStruct))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct,
TEXT("%s cannot reference non-output %s "),
*UE::StateTree::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()),
*UE::StateTree::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()));
return false;
}
TArray<FPropertyBindingPathIndirection> TargetIndirections;
if (!Binding.GetTargetPath().ResolveIndirectionsWithValue(InstanceDataView, TargetIndirections, &Error))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Resolving path in %s: %s"), *TargetStruct.ToString(), *Error);
return false;
}
FPropertyBindingPathIndirection& TargetLeafIndirection = TargetIndirections.Last();
FStateTreePropertyRef* PropertyRef = static_cast<FStateTreePropertyRef*>(const_cast<void*>(TargetLeafIndirection.GetPropertyAddress()));
check(PropertyRef);
FPropertyBindingPathIndirection& SourceLeafIndirection = SourceIndirections.Last();
if (!UE::StateTree::PropertyRefHelpers::IsPropertyRefCompatibleWithProperty(*TargetLeafIndirection.GetProperty(), *SourceLeafIndirection.GetProperty(), PropertyRef, SourceLeafIndirection.GetPropertyAddress()))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct,
TEXT("%s cannot reference %s, types are incompatible."),
*UE::StateTree::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()),
*UE::StateTree::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()));
return false;
}
FStateTreeIndex16 ReferenceIndex;
// Reuse the index if another PropertyRef already references the same property.
{
int32 IndexOfAlreadyExisting = PropertyBindings->PropertyReferencePaths.IndexOfByPredicate([&Binding](const FStateTreePropertyRefPath& RefPath)
{
return RefPath.GetSourcePath() == Binding.GetSourcePath();
});
if (IndexOfAlreadyExisting != INDEX_NONE)
{
ReferenceIndex = FStateTreeIndex16(IndexOfAlreadyExisting);
}
}
if (!ReferenceIndex.IsValid())
{
// If referencing another non global or subtree parameter PropertyRef, reuse it's index.
if (UE::StateTree::PropertyRefHelpers::IsPropertyRef(*SourceIndirections.Last().GetProperty())
&& SourceStruct->DataHandle.GetSource() != EStateTreeDataSourceType::GlobalParameterData
&& SourceStruct->DataHandle.GetSource() != EStateTreeDataSourceType::ExternalGlobalParameterData
&& SourceStruct->DataHandle.GetSource() != EStateTreeDataSourceType::SubtreeParameterData)
{
const FCompiledReference* ReferencedReference = CompiledReferences.FindByPredicate([&Binding](const FCompiledReference& CompiledReference)
{
return CompiledReference.Path == Binding.GetSourcePath();
});
if (ReferencedReference)
{
ReferenceIndex = ReferencedReference->Index;
}
else
{
if(!UE::StateTree::PropertyHelpers::HasOptionalMetadata(*TargetLeafIndirection.GetProperty()))
{
Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Referenced %s is not bound"), *UE::StateTree::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()));
return false;
}
return true;
}
}
}
if (!ReferenceIndex.IsValid())
{
ReferenceIndex = FStateTreeIndex16(PropertyBindings->PropertyReferencePaths.Num());
PropertyBindings->PropertyReferencePaths.Emplace(SourceStruct->DataHandle, Binding.GetSourcePath());
}
// Store index in instance data.
PropertyRef->RefAccessIndex = ReferenceIndex;
FCompiledReference& CompiledReference = CompiledReferences.AddDefaulted_GetRef();
CompiledReference.Path = Binding.GetTargetPath();
CompiledReference.Index = ReferenceIndex;
}
return true;
}
void FStateTreePropertyBindingCompiler::Finalize()
{
StoreSourceStructs();
CompiledDelegateDispatchers.Reset();
CompiledReferences.Reset();
}
int32 FStateTreePropertyBindingCompiler::AddSourceStruct(const FStateTreeBindableStructDesc& SourceStruct)
{
const FStateTreeBindableStructDesc* ExistingStruct = SourceStructs.FindByPredicate([&SourceStruct](const FStateTreeBindableStructDesc& Struct) { return (Struct.ID == SourceStruct.ID); });
if (ExistingStruct)
{
UE_LOG(LogStateTree, Error, TEXT("%s already exists as %s using ID '%s'"),
*SourceStruct.ToString(), *ExistingStruct->ToString(), *ExistingStruct->ID.ToString());
}
UE_CLOG(!SourceStruct.DataHandle.IsValid(), LogStateTree, Error, TEXT("%s does not have a valid data handle."), *SourceStruct.ToString());
SourceStructs.Add(SourceStruct);
return SourceStructs.Num() - 1;
}
FStateTreeDelegateDispatcher FStateTreePropertyBindingCompiler::GetDispatcherFromPath(const FPropertyBindingPath& PathToDispatcher) const
{
const FStateTreeEditorDelegateDispatcherCompiledBinding* FoundDispatcher = CompiledDelegateDispatchers.FindByPredicate(
[&PathToDispatcher](const FStateTreeEditorDelegateDispatcherCompiledBinding& Dispatcher)
{
return PathToDispatcher == Dispatcher.DispatcherPath;
});
if (FoundDispatcher)
{
return FoundDispatcher->ID;
}
return FStateTreeDelegateDispatcher();
}
TArray<FStateTreeEditorDelegateDispatcherCompiledBinding> FStateTreePropertyBindingCompiler::GetCompiledDelegateDispatchers() const
{
return CompiledDelegateDispatchers;
}
void FStateTreePropertyBindingCompiler::StoreSourceStructs()
{
// Check that existing structs are compatible
check(PropertyBindings->SourceStructs.Num() <= SourceStructs.Num());
for (int32 i = 0; i < PropertyBindings->SourceStructs.Num(); i++)
{
check(PropertyBindings->SourceStructs[i] == SourceStructs[i]);
}
// Add new
if (SourceStructs.Num() > PropertyBindings->SourceStructs.Num())
{
for (int32 i = PropertyBindings->SourceStructs.Num(); i < SourceStructs.Num(); i++)
{
PropertyBindings->SourceStructs.Add(SourceStructs[i]);
}
}
}