|
@@ -0,0 +1,1740 @@
|
|
|
+// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
+
|
|
|
+
|
|
|
+#include "EzAbilityCompiler.h"
|
|
|
+
|
|
|
+#include "EzAbilityAnyEnum.h"
|
|
|
+#include "EzAbilityEditorData.h"
|
|
|
+#include "EzAbilityPropertyHelpers.h"
|
|
|
+#include "EzAbilityPropertyRef.h"
|
|
|
+#include "Condition/EzAbilityCondition.h"
|
|
|
+#include "Evaluator/EzAbilityEvaluator.h"
|
|
|
+#include "Task/EzAbilityTask.h"
|
|
|
+
|
|
|
+namespace UE::EzAbility::Compiler
|
|
|
+{
|
|
|
+ // Helper archive that checks that the all instanced sub-objects have correct outer.
|
|
|
+ class FCheckOutersArchive : public FArchiveUObject
|
|
|
+ {
|
|
|
+ using Super = FArchiveUObject;
|
|
|
+ const UEzAbility& EzAbility;
|
|
|
+ const UEzAbilityEditorData& EditorData;
|
|
|
+ FEzAbilityCompilerLog& Log;
|
|
|
+ public:
|
|
|
+
|
|
|
+ FCheckOutersArchive(const UEzAbility& InEzAbility, const UEzAbilityEditorData& InEditorData, FEzAbilityCompilerLog& InLog)
|
|
|
+ : EzAbility(InEzAbility)
|
|
|
+ , EditorData(InEditorData)
|
|
|
+ , Log(InLog)
|
|
|
+ {
|
|
|
+ Super::SetIsSaving(true);
|
|
|
+ Super::SetIsPersistent(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ virtual bool ShouldSkipProperty(const FProperty* InProperty) const
|
|
|
+ {
|
|
|
+ // Skip editor data.
|
|
|
+ if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(InProperty))
|
|
|
+ {
|
|
|
+ if (ObjectProperty->PropertyClass == UEzAbilityEditorData::StaticClass())
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ virtual FArchive& operator<<(UObject*& Object) override
|
|
|
+ {
|
|
|
+ if (Object)
|
|
|
+ {
|
|
|
+ if (const FProperty* Property = GetSerializedProperty())
|
|
|
+ {
|
|
|
+ if (Property->HasAnyPropertyFlags(CPF_InstancedReference))
|
|
|
+ {
|
|
|
+ if (!Object->IsInOuter(&EzAbility))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TEXT("Compiled EzAbility contains instanced object %s (%s), which does not belong to the EzAbility. This is due to error in the State Tree node implementation."),
|
|
|
+ *GetFullNameSafe(Object), *GetFullNameSafe(Object->GetClass()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Object->IsInOuter(&EditorData))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TEXT("Compiled EzAbility contains instanced object %s (%s), which still belongs to the Editor data. This is due to error in the State Tree node implementation."),
|
|
|
+ *GetFullNameSafe(Object), *GetFullNameSafe(Object->GetClass()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return *this;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /** Scans Data for actors that are tied to some level and returns them. */
|
|
|
+ void ScanLevelActorReferences(FEzAbilityDataView Data, TSet<const UObject*>& Visited, TArray<const AActor*>& OutActors)
|
|
|
+ {
|
|
|
+ if (!Data.IsValid())
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (TPropertyValueIterator<FProperty> It(Data.GetStruct(), Data.GetMemory()); It; ++It)
|
|
|
+ {
|
|
|
+ const FProperty* Property = It->Key;
|
|
|
+ const void* ValuePtr = It->Value;
|
|
|
+
|
|
|
+ if (!ValuePtr)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (const FStructProperty* StructProperty = CastField<FStructProperty>(Property))
|
|
|
+ {
|
|
|
+ if (StructProperty->Struct == TBaseStructure<FInstancedStruct>::Get())
|
|
|
+ {
|
|
|
+ const FInstancedStruct& InstancedStruct = *static_cast<const FInstancedStruct*>(ValuePtr);
|
|
|
+ if (InstancedStruct.IsValid())
|
|
|
+ {
|
|
|
+ ScanLevelActorReferences(FEzAbilityDataView(const_cast<FInstancedStruct&>(InstancedStruct)), Visited, OutActors);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (const FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
|
|
|
+ {
|
|
|
+ if (const UObject* Object = ObjectProperty->GetObjectPropertyValue(ValuePtr))
|
|
|
+ {
|
|
|
+ if (const AActor* Actor = Cast<AActor>(Object))
|
|
|
+ {
|
|
|
+ const ULevel* Level = Actor->GetLevel();
|
|
|
+ if (Level != nullptr)
|
|
|
+ {
|
|
|
+ OutActors.Add(Actor);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Recurse into instanced object
|
|
|
+ if (Property->HasAnyPropertyFlags(CPF_InstancedReference))
|
|
|
+ {
|
|
|
+ if (!Visited.Contains(Object))
|
|
|
+ {
|
|
|
+ Visited.Add(Object);
|
|
|
+ ScanLevelActorReferences(FEzAbilityDataView(const_cast<UObject*>(Object)), Visited, OutActors);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ bool ValidateNoLevelActorReferences(FEzAbilityCompilerLog& Log, const FEzAbilityBindableStructDesc& NodeDesc, FEzAbilityDataView NodeView, FEzAbilityDataView InstanceView)
|
|
|
+ {
|
|
|
+ TSet<const UObject*> Visited;
|
|
|
+ TArray<const AActor*> LevelActors;
|
|
|
+ UE::EzAbility::Compiler::ScanLevelActorReferences(NodeView, Visited, LevelActors);
|
|
|
+ UE::EzAbility::Compiler::ScanLevelActorReferences(InstanceView, Visited, LevelActors);
|
|
|
+ if (!LevelActors.IsEmpty())
|
|
|
+ {
|
|
|
+ FStringBuilderBase AllActorsString;
|
|
|
+ for (const AActor* Actor : LevelActors)
|
|
|
+ {
|
|
|
+ if (AllActorsString.Len() > 0)
|
|
|
+ {
|
|
|
+ AllActorsString += TEXT(", ");
|
|
|
+ }
|
|
|
+ AllActorsString += *GetNameSafe(Actor);
|
|
|
+ }
|
|
|
+ Log.Reportf(EMessageSeverity::Error, NodeDesc,
|
|
|
+ TEXT("Level Actor references were found: %s. Direct Actor references are not allowed."),
|
|
|
+ *AllActorsString);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ void FValidationResult::Log(FEzAbilityCompilerLog& Log, const TCHAR* ContextText, const FEzAbilityBindableStructDesc& ContextStruct) const
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, ContextStruct, TEXT("The EzAbility is too complex. Compact index %s out of range %d/%d."), ContextText, Value, MaxValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ const UScriptStruct* GetBaseStructFromMetaData(const FProperty* Property, FString& OutBaseStructName)
|
|
|
+ {
|
|
|
+ static const FName NAME_BaseStruct = "BaseStruct";
|
|
|
+
|
|
|
+ const UScriptStruct* Result = nullptr;
|
|
|
+ OutBaseStructName = Property->GetMetaData(NAME_BaseStruct);
|
|
|
+
|
|
|
+ if (!OutBaseStructName.IsEmpty())
|
|
|
+ {
|
|
|
+ Result = UClass::TryFindTypeSlow<UScriptStruct>(OutBaseStructName);
|
|
|
+ if (!Result)
|
|
|
+ {
|
|
|
+ Result = LoadObject<UScriptStruct>(nullptr, *OutBaseStructName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return Result;
|
|
|
+ }
|
|
|
+
|
|
|
+}; // UE::EzAbility::Compiler
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::Compile(UEzAbility& InEzAbility)
|
|
|
+{
|
|
|
+ EzAbility = &InEzAbility;
|
|
|
+ EditorData = Cast<UEzAbilityEditorData>(EzAbility->EditorData);
|
|
|
+ if (!EditorData)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Cleanup existing state
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+
|
|
|
+ if (!BindingsCompiler.Init(EzAbility->PropertyBindings, Log))
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ EditorData->GetAllStructValues(IDToStructValue);
|
|
|
+
|
|
|
+ // Copy schema the EditorData
|
|
|
+ EzAbility->Schema = DuplicateObject(EditorData->Schema, EzAbility);
|
|
|
+
|
|
|
+ // Copy parameters from EditorData
|
|
|
+ EzAbility->Parameters = EditorData->RootParameters.Parameters;
|
|
|
+
|
|
|
+ // Mark parameters as binding source
|
|
|
+ const FEzAbilityBindableStructDesc ParametersDesc = {
|
|
|
+ TEXT("Parameters"),
|
|
|
+ EzAbility->Parameters.GetPropertyBagStruct(),
|
|
|
+ FEzAbilityDataHandle(EEzAbilityDataSourceType::GlobalParameterData),
|
|
|
+ EEzAbilityBindableStructSource::Parameter,
|
|
|
+ EditorData->RootParameters.ID
|
|
|
+ };
|
|
|
+ BindingsCompiler.AddSourceStruct(ParametersDesc);
|
|
|
+
|
|
|
+ if (!UE::EzAbility::Compiler::ValidateNoLevelActorReferences(Log, ParametersDesc, FEzAbilityDataView(), FEzAbilityDataView(EditorData->RootParameters.Parameters.GetMutableValue())))
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ int32 ContextDataIndex = 0;
|
|
|
+
|
|
|
+ // Mark all named external values as binding source
|
|
|
+ if (EzAbility->Schema)
|
|
|
+ {
|
|
|
+ EzAbility->ContextDataDescs = EzAbility->Schema->GetContextDataDescs();
|
|
|
+ for (FEzAbilityExternalDataDesc& Desc : EzAbility->ContextDataDescs)
|
|
|
+ {
|
|
|
+ const FEzAbilityBindableStructDesc ExtDataDesc = {
|
|
|
+ Desc.Name,
|
|
|
+ Desc.Struct,
|
|
|
+ FEzAbilityDataHandle(EEzAbilityDataSourceType::ContextData, ContextDataIndex++),
|
|
|
+ EEzAbilityBindableStructSource::Context,
|
|
|
+ Desc.ID
|
|
|
+ };
|
|
|
+ BindingsCompiler.AddSourceStruct(ExtDataDesc);
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(ContextDataIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("ExternalStructIndex"), ParametersDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Desc.Handle.DataHandle = ExtDataDesc.DataHandle;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ EzAbility->NumContextData = ContextDataIndex;
|
|
|
+
|
|
|
+ if (!CreateStates())
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Eval and Global task methods use InstanceStructs.Num() as ID generator.
|
|
|
+ check(InstanceStructs.Num() == 0);
|
|
|
+
|
|
|
+ if (!CreateEvaluators())
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!CreateGlobalTasks())
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 NumGlobalInstanceData = InstanceStructs.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(NumGlobalInstanceData); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("NumGlobalInstanceData"), ParametersDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ EzAbility->NumGlobalInstanceData = uint16(NumGlobalInstanceData);
|
|
|
+
|
|
|
+ if (!CreateStateTasksAndParameters())
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!CreateStateTransitions())
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ EzAbility->Nodes = Nodes;
|
|
|
+ EzAbility->DefaultInstanceData.Init(*EzAbility, InstanceStructs);
|
|
|
+ EzAbility->SharedInstanceData.Init(*EzAbility, SharedInstanceStructs);
|
|
|
+
|
|
|
+ BindingsCompiler.Finalize();
|
|
|
+
|
|
|
+ if (!EzAbility->Link())
|
|
|
+ {
|
|
|
+ EzAbility->ResetCompiled();
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TEXT("Unexpected failure to link the EzAbility asset. See log for more info."));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Store mapping between node unique ID and their compiled index. Used for debugging purposes.
|
|
|
+ for (const TPair<FGuid, int32>& ToNode : IDToNode)
|
|
|
+ {
|
|
|
+ EzAbility->IDToNodeMappings.Emplace(ToNode.Key, FEzAbilityIndex16(ToNode.Value));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Store mapping between state unique ID and state handle. Used for debugging purposes.
|
|
|
+ for (const TPair<FGuid, int32>& ToState : IDToState)
|
|
|
+ {
|
|
|
+ EzAbility->IDToStateMappings.Emplace(ToState.Key, FEzAbilityStateHandle(ToState.Value));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Store mapping between state transition identifier and compact transition index. Used for debugging purposes.
|
|
|
+ for (const TPair<FGuid, int32>& ToTransition: IDToTransition)
|
|
|
+ {
|
|
|
+ EzAbility->IDToTransitionMappings.Emplace(ToTransition.Key, FEzAbilityIndex16(ToTransition.Value));
|
|
|
+ }
|
|
|
+
|
|
|
+ UE::EzAbility::Compiler::FCheckOutersArchive CheckOuters(*EzAbility, *EditorData, Log);
|
|
|
+ EzAbility->Serialize(CheckOuters);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+FEzAbilityStateHandle FEzAbilityCompiler::GetStateHandle(const FGuid& StateID) const
|
|
|
+{
|
|
|
+ const int32* Idx = IDToState.Find(StateID);
|
|
|
+ if (Idx == nullptr)
|
|
|
+ {
|
|
|
+ return FEzAbilityStateHandle::Invalid;
|
|
|
+ }
|
|
|
+
|
|
|
+ return FEzAbilityStateHandle(uint16(*Idx));
|
|
|
+}
|
|
|
+
|
|
|
+UEzAbilityState* FEzAbilityCompiler::GetState(const FGuid& StateID) const
|
|
|
+{
|
|
|
+ const int32* Idx = IDToState.Find(StateID);
|
|
|
+ if (Idx == nullptr)
|
|
|
+ {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ return SourceStates[*Idx];
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateStates()
|
|
|
+{
|
|
|
+ check(EditorData);
|
|
|
+
|
|
|
+ // Create main tree (omit subtrees)
|
|
|
+ for (UEzAbilityState* SubTree : EditorData->SubTrees)
|
|
|
+ {
|
|
|
+ if (SubTree != nullptr
|
|
|
+ && SubTree->Type != EEzAbilityStateType::Subtree)
|
|
|
+ {
|
|
|
+ if (!CreateStateRecursive(*SubTree, FEzAbilityStateHandle::Invalid))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create Subtrees
|
|
|
+ for (UEzAbilityState* SubTree : EditorData->SubTrees)
|
|
|
+ {
|
|
|
+ TArray<UEzAbilityState*> Stack;
|
|
|
+ Stack.Push(SubTree);
|
|
|
+ while (!Stack.IsEmpty())
|
|
|
+ {
|
|
|
+ if (UEzAbilityState* State = Stack.Pop())
|
|
|
+ {
|
|
|
+ if (State->Type == EEzAbilityStateType::Subtree)
|
|
|
+ {
|
|
|
+ if (!CreateStateRecursive(*State, FEzAbilityStateHandle::Invalid))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Stack.Append(State->Children);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateStateRecursive(UEzAbilityState& State, const FEzAbilityStateHandle Parent)
|
|
|
+{
|
|
|
+ check(EzAbility);
|
|
|
+
|
|
|
+ FEzAbilityCompilerLogStateScope LogStateScope(&State, Log);
|
|
|
+
|
|
|
+ const int32 StateIdx = EzAbility->States.AddDefaulted();
|
|
|
+ FCompactEzAbilityState& CompactState = EzAbility->States[StateIdx];
|
|
|
+ CompactState.Name = State.Name;
|
|
|
+ CompactState.Parent = Parent;
|
|
|
+ CompactState.bEnabled = State.bEnabled;
|
|
|
+
|
|
|
+ CompactState.Type = State.Type;
|
|
|
+ CompactState.SelectionBehavior = State.SelectionBehavior;
|
|
|
+
|
|
|
+ SourceStates.Add(&State);
|
|
|
+ IDToState.Add(State.ID, StateIdx);
|
|
|
+
|
|
|
+ // Child states
|
|
|
+ const int32 ChildrenBegin = EzAbility->States.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(ChildrenBegin); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("ChildrenBegin"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactState.ChildrenBegin = uint16(ChildrenBegin);
|
|
|
+
|
|
|
+ for (UEzAbilityState* Child : State.Children)
|
|
|
+ {
|
|
|
+ if (Child != nullptr && Child->Type != EEzAbilityStateType::Subtree)
|
|
|
+ {
|
|
|
+ if (!CreateStateRecursive(*Child, FEzAbilityStateHandle((uint16)StateIdx)))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 ChildrenEnd = EzAbility->States.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(ChildrenEnd); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("ChildrenEnd"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ EzAbility->States[StateIdx].ChildrenEnd = uint16(ChildrenEnd); // Not using CompactState here because the array may have changed.
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateConditions(UEzAbilityState& State, TConstArrayView<FEzAbilityEditorNode> Conditions)
|
|
|
+{
|
|
|
+ for (int32 Index = 0; Index < Conditions.Num(); Index++)
|
|
|
+ {
|
|
|
+ const bool bIsFirst = Index == 0;
|
|
|
+ const FEzAbilityEditorNode& CondNode = Conditions[Index];
|
|
|
+ // First operand should be copy as we dont have a previous item to operate on.
|
|
|
+ const EEzAbilityConditionOperand Operand = bIsFirst ? EEzAbilityConditionOperand::Copy : CondNode.ConditionOperand;
|
|
|
+ // First indent must be 0 to make the parentheses calculation match.
|
|
|
+ const int32 CurrIndent = bIsFirst ? 0 : FMath::Clamp((int32)CondNode.ConditionIndent, 0, UE::EzAbility::MaxConditionIndent);
|
|
|
+ // Next indent, or terminate at zero.
|
|
|
+ const int32 NextIndent = Conditions.IsValidIndex(Index + 1) ? FMath::Clamp((int32)Conditions[Index + 1].ConditionIndent, 0, UE::EzAbility::MaxConditionIndent) : 0;
|
|
|
+
|
|
|
+ const int32 DeltaIndent = NextIndent - CurrIndent;
|
|
|
+
|
|
|
+ if (!CreateCondition(State, CondNode, Operand, (int8)DeltaIndent))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateEvaluators()
|
|
|
+{
|
|
|
+ check(EditorData);
|
|
|
+ check(EzAbility);
|
|
|
+
|
|
|
+ const int32 EvaluatorsBegin = Nodes.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(EvaluatorsBegin); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("EvaluatorsBegin"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ EzAbility->EvaluatorsBegin = uint16(EvaluatorsBegin);
|
|
|
+
|
|
|
+ for (FEzAbilityEditorNode& EvalNode : EditorData->Evaluators)
|
|
|
+ {
|
|
|
+ const int32 GlobalInstanceIndex = InstanceStructs.Num();
|
|
|
+ const FEzAbilityDataHandle EvalDataHandle(EEzAbilityDataSourceType::GlobalInstanceData, GlobalInstanceIndex);
|
|
|
+ if (!CreateEvaluator(EvalNode, EvalDataHandle))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 EvaluatorsNum = Nodes.Num() - EvaluatorsBegin;
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(EvaluatorsNum); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("EvaluatorsNum"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ EzAbility->EvaluatorsNum = uint16(EvaluatorsNum);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateGlobalTasks()
|
|
|
+{
|
|
|
+ check(EditorData);
|
|
|
+ check(EzAbility);
|
|
|
+
|
|
|
+ const int32 GlobalTasksBegin = Nodes.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(GlobalTasksBegin); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("GlobalTasksBegin"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ EzAbility->GlobalTasksBegin = uint16(GlobalTasksBegin);
|
|
|
+
|
|
|
+ EzAbility->bHasGlobalTransitionTasks = false;
|
|
|
+ for (FEzAbilityEditorNode& TaskNode : EditorData->GlobalTasks)
|
|
|
+ {
|
|
|
+ // Silently ignore empty nodes.
|
|
|
+ if (!TaskNode.Node.IsValid())
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 GlobalInstanceIndex = InstanceStructs.Num();
|
|
|
+ const FEzAbilityDataHandle TaskDataHandle(EEzAbilityDataSourceType::GlobalInstanceData, GlobalInstanceIndex);
|
|
|
+ if (!CreateTask(nullptr, TaskNode, TaskDataHandle))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const FEzAbilityTask& LastAddedTask = Nodes.Last().Get<FEzAbilityTask>();
|
|
|
+
|
|
|
+ EzAbility->bHasGlobalTransitionTasks |= LastAddedTask.bShouldAffectTransitions;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 GlobalTasksNum = Nodes.Num() - GlobalTasksBegin;
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(GlobalTasksNum); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("GlobalTasksNum"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ EzAbility->GlobalTasksNum = uint16(GlobalTasksNum);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateStateTasksAndParameters()
|
|
|
+{
|
|
|
+ check(EzAbility);
|
|
|
+
|
|
|
+ // Index of the first instance data per state. Accumulated depth first.
|
|
|
+ TArray<int32> FirstInstanceDataIndex;
|
|
|
+ FirstInstanceDataIndex.Init(0, EzAbility->States.Num());
|
|
|
+
|
|
|
+ for (int32 i = 0; i < EzAbility->States.Num(); i++)
|
|
|
+ {
|
|
|
+ FCompactEzAbilityState& CompactState = EzAbility->States[i];
|
|
|
+ const FEzAbilityStateHandle CompactStateHandle(i);
|
|
|
+ UEzAbilityState* State = SourceStates[i];
|
|
|
+ check(State != nullptr);
|
|
|
+
|
|
|
+ // Carry over instance data count from parent.
|
|
|
+ if (CompactState.Parent.IsValid())
|
|
|
+ {
|
|
|
+ const FCompactEzAbilityState& ParentCompactState = EzAbility->States[CompactState.Parent.Index];
|
|
|
+ const int32 InstanceDataBegin = FirstInstanceDataIndex[CompactState.Parent.Index] + (int32)ParentCompactState.InstanceDataNum;
|
|
|
+ FirstInstanceDataIndex[i] = InstanceDataBegin;
|
|
|
+ }
|
|
|
+
|
|
|
+ int32 InstanceDataIndex = FirstInstanceDataIndex[i];
|
|
|
+
|
|
|
+ FEzAbilityCompilerLogStateScope LogStateScope(State, Log);
|
|
|
+
|
|
|
+ // Create parameters
|
|
|
+
|
|
|
+ // Each state has their parameters as instance data.
|
|
|
+ FInstancedStruct& Instance = InstanceStructs.AddDefaulted_GetRef();
|
|
|
+ Instance.InitializeAs<FCompactEzAbilityParameters>(State->Parameters.Parameters);
|
|
|
+ FCompactEzAbilityParameters& CompactEzAbilityParameters = Instance.GetMutable<FCompactEzAbilityParameters>();
|
|
|
+
|
|
|
+ const int32 InstanceIndex = InstanceStructs.Num() - 1;
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceIndex"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactState.ParameterTemplateIndex = FEzAbilityIndex16(InstanceIndex);
|
|
|
+
|
|
|
+ if (State->Type == EEzAbilityStateType::Subtree)
|
|
|
+ {
|
|
|
+ CompactState.ParameterDataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::SubtreeParameterData, InstanceDataIndex++, CompactStateHandle);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ CompactState.ParameterDataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::StateParameterData, InstanceDataIndex++, CompactStateHandle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // @todo: We should be able to skip empty parameter data.
|
|
|
+
|
|
|
+ // Binding target
|
|
|
+ FEzAbilityBindableStructDesc LinkedParamsDesc = {
|
|
|
+ State->Name,
|
|
|
+ State->Parameters.Parameters.GetPropertyBagStruct(),
|
|
|
+ CompactState.ParameterDataHandle,
|
|
|
+ EEzAbilityBindableStructSource::State,
|
|
|
+ State->Parameters.ID
|
|
|
+ };
|
|
|
+
|
|
|
+ if (!UE::EzAbility::Compiler::ValidateNoLevelActorReferences(Log, LinkedParamsDesc, FEzAbilityDataView(), FEzAbilityDataView(CompactEzAbilityParameters.Parameters.GetMutableValue())))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add as binding source.
|
|
|
+ BindingsCompiler.AddSourceStruct(LinkedParamsDesc);
|
|
|
+
|
|
|
+ // Check that the bindings for this struct are still all valid.
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> CopyBindings;
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> ReferenceBindings;
|
|
|
+ if (!GetAndValidateBindings(LinkedParamsDesc, FEzAbilityDataView(CompactEzAbilityParameters.Parameters.GetMutableValue()), CopyBindings, ReferenceBindings))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ int32 BatchIndex = INDEX_NONE;
|
|
|
+ if (!BindingsCompiler.CompileBatch(LinkedParamsDesc, CopyBindings, BatchIndex))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!BindingsCompiler.CompileReferences(LinkedParamsDesc, ReferenceBindings, FEzAbilityDataView(CompactEzAbilityParameters.Parameters.GetMutableValue())))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("BatchIndex"), LinkedParamsDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ CompactState.ParameterBindingsBatch = FEzAbilityIndex16(BatchIndex);
|
|
|
+
|
|
|
+ // Create tasks
|
|
|
+ const int32 TasksBegin = Nodes.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(TasksBegin); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("TasksBegin"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactState.TasksBegin = uint16(TasksBegin);
|
|
|
+
|
|
|
+ TArrayView<FEzAbilityEditorNode> Tasks;
|
|
|
+ if (State->Tasks.Num())
|
|
|
+ {
|
|
|
+ Tasks = State->Tasks;
|
|
|
+ }
|
|
|
+ else if (State->SingleTask.Node.IsValid())
|
|
|
+ {
|
|
|
+ Tasks = TArrayView<FEzAbilityEditorNode>(&State->SingleTask, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ bool bStateHasTransitionTasks = false;
|
|
|
+ for (FEzAbilityEditorNode& TaskNode : Tasks)
|
|
|
+ {
|
|
|
+ // Silently ignore empty nodes.
|
|
|
+ if (!TaskNode.Node.IsValid())
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const FEzAbilityDataHandle TaskDataHandle(EEzAbilityDataSourceType::ActiveInstanceData, InstanceDataIndex++, CompactStateHandle);
|
|
|
+ if (!CreateTask(State, TaskNode, TaskDataHandle))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const FEzAbilityTask& LastAddedTask = Nodes.Last().Get<FEzAbilityTask>();
|
|
|
+
|
|
|
+ bStateHasTransitionTasks |= LastAddedTask.bShouldAffectTransitions;
|
|
|
+ }
|
|
|
+
|
|
|
+ CompactState.bHasTransitionTasks = bStateHasTransitionTasks;
|
|
|
+
|
|
|
+ const int32 TasksNum = Nodes.Num() - TasksBegin;
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount8(TasksNum); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("TasksNum"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 InstanceDataNum = InstanceDataIndex - FirstInstanceDataIndex[i];
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount8(InstanceDataNum); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceDataNum"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ CompactState.TasksNum = uint8(TasksNum);
|
|
|
+ CompactState.InstanceDataNum = uint8(InstanceDataNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateStateTransitions()
|
|
|
+{
|
|
|
+ check(EzAbility);
|
|
|
+
|
|
|
+ for (int32 i = 0; i < EzAbility->States.Num(); i++)
|
|
|
+ {
|
|
|
+ FCompactEzAbilityState& CompactState = EzAbility->States[i];
|
|
|
+ UEzAbilityState* SourceState = SourceStates[i];
|
|
|
+ check(SourceState != nullptr);
|
|
|
+
|
|
|
+ FEzAbilityCompilerLogStateScope LogStateScope(SourceState, Log);
|
|
|
+
|
|
|
+ // Enter conditions.
|
|
|
+ const int32 EnterConditionsBegin = Nodes.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(EnterConditionsBegin); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("EnterConditionsBegin"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactState.EnterConditionsBegin = uint16(EnterConditionsBegin);
|
|
|
+
|
|
|
+ if (!CreateConditions(*SourceState, SourceState->EnterConditions))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("Failed to create state enter condition."));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 EnterConditionsNum = Nodes.Num() - EnterConditionsBegin;
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount8(EnterConditionsNum); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("EnterConditionsNum"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactState.EnterConditionsNum = uint8(EnterConditionsNum);
|
|
|
+
|
|
|
+ // Linked state
|
|
|
+ if (SourceState->Type == EEzAbilityStateType::Linked)
|
|
|
+ {
|
|
|
+ // Make sure the linked state is not self or parent to this state.
|
|
|
+ const UEzAbilityState* LinkedParentState = nullptr;
|
|
|
+ for (const UEzAbilityState* State = SourceState; State != nullptr; State = State->Parent)
|
|
|
+ {
|
|
|
+ if (State->ID == SourceState->LinkedSubtree.ID)
|
|
|
+ {
|
|
|
+ LinkedParentState = State;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (LinkedParentState != nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("State is linked to it's parent subtree '%s', which will create infinite loop."),
|
|
|
+ *LinkedParentState->Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // The linked state must be a subtree.
|
|
|
+ const UEzAbilityState* TargetState = GetState(SourceState->LinkedSubtree.ID);
|
|
|
+ if (TargetState == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("Failed to resolve linked subtree '%s'."),
|
|
|
+ *SourceState->LinkedSubtree.Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (TargetState->Type != EEzAbilityStateType::Subtree)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("State '%s' is linked to subtree '%s', which is not a subtree."),
|
|
|
+ *SourceState->Name.ToString(), *TargetState->Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ CompactState.LinkedState = GetStateHandle(SourceState->LinkedSubtree.ID);
|
|
|
+
|
|
|
+ if (!CompactState.LinkedState.IsValid())
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("Failed to resolve linked subtree '%s'."),
|
|
|
+ *SourceState->LinkedSubtree.Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (SourceState->Type == EEzAbilityStateType::LinkedAsset)
|
|
|
+ {
|
|
|
+ // Do not allow to link to the same asset (might create recursion)
|
|
|
+ if (SourceState->LinkedAsset == EzAbility)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("It is not allowed to link to the same tree, as it might create infinite loop."));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ CompactState.LinkedAsset = SourceState->LinkedAsset;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Transitions
|
|
|
+ const int32 TransitionsBegin = EzAbility->Transitions.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(TransitionsBegin); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("TransitionsBegin"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactState.TransitionsBegin = uint16(TransitionsBegin);
|
|
|
+
|
|
|
+ for (FEzAbilityTransition& Transition : SourceState->Transitions)
|
|
|
+ {
|
|
|
+ IDToTransition.Add(Transition.ID, EzAbility->Transitions.Num());
|
|
|
+
|
|
|
+ FCompactEzAbilityTransition& CompactTransition = EzAbility->Transitions.AddDefaulted_GetRef();
|
|
|
+ CompactTransition.Trigger = Transition.Trigger;
|
|
|
+ CompactTransition.Priority = Transition.Priority;
|
|
|
+ CompactTransition.EventTag = Transition.EventTag;
|
|
|
+ CompactTransition.bTransitionEnabled = Transition.bTransitionEnabled;
|
|
|
+
|
|
|
+ if (Transition.State.LinkType == EEzAbilityTransitionType::NextSelectableState)
|
|
|
+ {
|
|
|
+ CompactTransition.Fallback = EEzAbilitySelectionFallback::NextSelectableSibling;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Transition.bDelayTransition)
|
|
|
+ {
|
|
|
+ CompactTransition.Delay.Set(Transition.DelayDuration, Transition.DelayRandomVariance);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (CompactState.SelectionBehavior == EEzAbilityStateSelectionBehavior::TryFollowTransitions
|
|
|
+ && Transition.bDelayTransition)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Warning,
|
|
|
+ TEXT("Transition to '%s' with delay will be ignored during state selection."),
|
|
|
+ *Transition.State.Name.ToString());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (EnumHasAnyFlags(Transition.Trigger, EEzAbilityTransitionTrigger::OnStateCompleted))
|
|
|
+ {
|
|
|
+ // Completion transitions dont have priority.
|
|
|
+ CompactTransition.Priority = EEzAbilityTransitionPriority::None;
|
|
|
+
|
|
|
+ // Completion transitions cannot have delay.
|
|
|
+ CompactTransition.Delay.Reset();
|
|
|
+
|
|
|
+ // Completion transitions must have valid target state.
|
|
|
+ if (Transition.State.LinkType == EEzAbilityTransitionType::None)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("State completion transition to '%s' must have transition to valid state, 'None' not accepted."),
|
|
|
+ *Transition.State.Name.ToString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ CompactTransition.State = FEzAbilityStateHandle::Invalid;
|
|
|
+ if (!ResolveTransitionState(SourceState, Transition.State, CompactTransition.State))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 ConditionsBegin = Nodes.Num();
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount16(ConditionsBegin); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("ConditionsBegin"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactTransition.ConditionsBegin = uint16(ConditionsBegin);
|
|
|
+
|
|
|
+ if (!CreateConditions(*SourceState, Transition.Conditions))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("Failed to create condition for transition to '%s'."),
|
|
|
+ *Transition.State.Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 ConditionsNum = Nodes.Num() - ConditionsBegin;
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount8(ConditionsNum); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("ConditionsNum"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactTransition.ConditionsNum = uint8(ConditionsNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ const int32 TransitionsNum = EzAbility->Transitions.Num() - TransitionsBegin;
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidCount8(TransitionsNum); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("TransitionsNum"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ CompactState.TransitionsNum = uint8(TransitionsNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ // @todo: Add test to check that all success/failure transition is possible (see editor).
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::ResolveTransitionState(const UEzAbilityState* SourceState, const FEzAbilityStateLink& Link, FEzAbilityStateHandle& OutTransitionHandle) const
|
|
|
+{
|
|
|
+ if (Link.LinkType == EEzAbilityTransitionType::GotoState)
|
|
|
+ {
|
|
|
+ // Warn if goto state points to another subtree.
|
|
|
+ if (const UEzAbilityState* TargetState = GetState(Link.ID))
|
|
|
+ {
|
|
|
+ if (SourceState && TargetState->GetRootState() != SourceState->GetRootState())
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Warning,
|
|
|
+ TEXT("Target state '%s' is in different subtree. Verify that this is intentional."),
|
|
|
+ *Link.Name.ToString());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (TargetState->SelectionBehavior == EEzAbilityStateSelectionBehavior::None)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("The target State '%s' is not selectable, it's selection behavior is set to None."),
|
|
|
+ *Link.Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ OutTransitionHandle = GetStateHandle(Link.ID);
|
|
|
+ if (!OutTransitionHandle.IsValid())
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("Failed to resolve transition to state '%s'."),
|
|
|
+ *Link.Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (Link.LinkType == EEzAbilityTransitionType::NextState || Link.LinkType == EEzAbilityTransitionType::NextSelectableState)
|
|
|
+ {
|
|
|
+ // Find next state.
|
|
|
+ const UEzAbilityState* NextState = SourceState ? SourceState->GetNextSelectableSiblingState() : nullptr;
|
|
|
+ if (NextState == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("Failed to resolve transition, there's no selectable next state."));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ OutTransitionHandle = GetStateHandle(NextState->ID);
|
|
|
+ if (!OutTransitionHandle.IsValid())
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error,
|
|
|
+ TEXT("Failed to resolve transition next state, no handle found for '%s'."),
|
|
|
+ *NextState->Name.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if(Link.LinkType == EEzAbilityTransitionType::Failed)
|
|
|
+ {
|
|
|
+ OutTransitionHandle = FEzAbilityStateHandle::Failed;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else if(Link.LinkType == EEzAbilityTransitionType::Succeeded)
|
|
|
+ {
|
|
|
+ OutTransitionHandle = FEzAbilityStateHandle::Succeeded;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else if(Link.LinkType == EEzAbilityTransitionType::None)
|
|
|
+ {
|
|
|
+ OutTransitionHandle = FEzAbilityStateHandle::Invalid;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateCondition(UEzAbilityState& State, const FEzAbilityEditorNode& CondNode, const EEzAbilityConditionOperand Operand, const int8 DeltaIndent)
|
|
|
+{
|
|
|
+ if (!CondNode.Node.IsValid())
|
|
|
+ {
|
|
|
+ // Empty line in conditions array, just silently ignore.
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ FEzAbilityBindableStructDesc StructDesc;
|
|
|
+ StructDesc.ID = CondNode.ID;
|
|
|
+ StructDesc.Name = CondNode.Node.GetScriptStruct()->GetFName();
|
|
|
+ StructDesc.DataSource = EEzAbilityBindableStructSource::Condition;
|
|
|
+
|
|
|
+ // Check that item has valid instance initialized.
|
|
|
+ if (!CondNode.Instance.IsValid() && CondNode.InstanceObject == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, StructDesc,
|
|
|
+ TEXT("Malformed condition, missing instance value."));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy the condition
|
|
|
+ IDToNode.Add(CondNode.ID, Nodes.Num());
|
|
|
+ FInstancedStruct& Node = Nodes.Add_GetRef(CondNode.Node);
|
|
|
+ InstantiateStructSubobjects(Node);
|
|
|
+
|
|
|
+ FEzAbilityCondition& Cond = Node.GetMutable<FEzAbilityCondition>();
|
|
|
+
|
|
|
+ Cond.Operand = Operand;
|
|
|
+ Cond.DeltaIndent = DeltaIndent;
|
|
|
+
|
|
|
+ FEzAbilityDataView InstanceDataView;
|
|
|
+
|
|
|
+ if (CondNode.Instance.IsValid())
|
|
|
+ {
|
|
|
+ // Struct instance
|
|
|
+ const int32 InstanceIndex = SharedInstanceStructs.Add(CondNode.Instance);
|
|
|
+ InstantiateStructSubobjects(SharedInstanceStructs[InstanceIndex]);
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ StructDesc.Struct = CondNode.Instance.GetScriptStruct();
|
|
|
+ StructDesc.Name = Cond.Name;
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Cond.InstanceTemplateIndex = FEzAbilityIndex16(InstanceIndex);
|
|
|
+ Cond.InstanceDataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::SharedInstanceData, InstanceIndex);
|
|
|
+ InstanceDataView = FEzAbilityDataView(SharedInstanceStructs[InstanceIndex]);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Object Instance
|
|
|
+ check(CondNode.InstanceObject != nullptr);
|
|
|
+
|
|
|
+ UObject* Instance = DuplicateObject(CondNode.InstanceObject, EzAbility);
|
|
|
+ FInstancedStruct Wrapper;
|
|
|
+ Wrapper.InitializeAs<FEzAbilityInstanceObjectWrapper>(Instance);
|
|
|
+ const int32 InstanceIndex = SharedInstanceStructs.Add(MoveTemp(Wrapper));
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ StructDesc.Struct = Instance->GetClass();
|
|
|
+ StructDesc.Name = Cond.Name;
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Cond.InstanceTemplateIndex = FEzAbilityIndex16(InstanceIndex);
|
|
|
+ Cond.InstanceDataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::SharedInstanceDataObject, InstanceIndex);
|
|
|
+ InstanceDataView = FEzAbilityDataView(Instance);
|
|
|
+ }
|
|
|
+
|
|
|
+ StructDesc.DataHandle = Cond.InstanceDataHandle;
|
|
|
+
|
|
|
+ if (!CompileAndValidateNode(&State, StructDesc, Node, InstanceDataView))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mark the struct as binding source.
|
|
|
+ BindingsCompiler.AddSourceStruct(StructDesc);
|
|
|
+
|
|
|
+ // Check that the bindings for this struct are still all valid.
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> CopyBindings;
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> ReferenceBindings;
|
|
|
+ if (!GetAndValidateBindings(StructDesc, InstanceDataView, CopyBindings, ReferenceBindings))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Compile batch copy for this struct, we pass in all the bindings, the compiler will pick up the ones for the target structs.
|
|
|
+ int32 BatchIndex = INDEX_NONE;
|
|
|
+ if (!BindingsCompiler.CompileBatch(StructDesc, CopyBindings, BatchIndex))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!BindingsCompiler.CompileReferences(StructDesc, ReferenceBindings, InstanceDataView))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("BatchIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Cond.BindingsBatch = FEzAbilityIndex16(BatchIndex);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CompileAndValidateNode(const UEzAbilityState* SourceState, const FEzAbilityBindableStructDesc& NodeDesc, FStructView NodeView, const FEzAbilityDataView InstanceData)
|
|
|
+{
|
|
|
+ if (!NodeView.IsValid())
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ FEzAbilityNodeBase& Node = NodeView.Get<FEzAbilityNodeBase>();
|
|
|
+ check(InstanceData.IsValid());
|
|
|
+
|
|
|
+ auto ValidateStateLinks = [this, SourceState](TPropertyValueIterator<FStructProperty> It)
|
|
|
+ {
|
|
|
+ for ( ; It; ++It)
|
|
|
+ {
|
|
|
+ if (It->Key->Struct == TBaseStructure<FEzAbilityStateLink>::Get())
|
|
|
+ {
|
|
|
+ FEzAbilityStateLink& StateLink = *static_cast<FEzAbilityStateLink*>(const_cast<void*>(It->Value));
|
|
|
+
|
|
|
+ if (!ResolveTransitionState(SourceState, StateLink, StateLink.StateHandle))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ // Validate any state links.
|
|
|
+ if (!ValidateStateLinks(TPropertyValueIterator<FStructProperty>(InstanceData.GetStruct(), InstanceData.GetMutableMemory())))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!ValidateStateLinks(TPropertyValueIterator<FStructProperty>(NodeView.GetScriptStruct(), NodeView.GetMemory())))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ TArray<FText> ValidationErrors;
|
|
|
+ const EDataValidationResult Result = Node.Compile(InstanceData, ValidationErrors);
|
|
|
+
|
|
|
+ if (Result == EDataValidationResult::Invalid && ValidationErrors.IsEmpty())
|
|
|
+ {
|
|
|
+ Log.Report(EMessageSeverity::Error, NodeDesc, TEXT("Node validation failed."));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ const EMessageSeverity::Type Severity = Result == EDataValidationResult::Invalid ? EMessageSeverity::Error : EMessageSeverity::Warning;
|
|
|
+ for (const FText& Error : ValidationErrors)
|
|
|
+ {
|
|
|
+ Log.Report(Severity, NodeDesc, Error.ToString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Make sure there's no level actor references in the data.
|
|
|
+ if (!UE::EzAbility::Compiler::ValidateNoLevelActorReferences(Log, NodeDesc, NodeView, InstanceData))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Result != EDataValidationResult::Invalid;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateTask(UEzAbilityState* State, const FEzAbilityEditorNode& TaskNode, const FEzAbilityDataHandle TaskDataHandle)
|
|
|
+{
|
|
|
+ if (!TaskNode.Node.IsValid())
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ FEzAbilityBindableStructDesc StructDesc;
|
|
|
+ StructDesc.ID = TaskNode.ID;
|
|
|
+ StructDesc.Name = TaskNode.Node.GetScriptStruct()->GetFName();
|
|
|
+ StructDesc.DataSource = EEzAbilityBindableStructSource::Task;
|
|
|
+
|
|
|
+ // Check that node has valid instance initialized.
|
|
|
+ if (!TaskNode.Instance.IsValid() && TaskNode.InstanceObject == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, StructDesc,
|
|
|
+ TEXT("Malformed task, missing instance value."));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy the task
|
|
|
+ IDToNode.Add(TaskNode.ID, Nodes.Num());
|
|
|
+ FInstancedStruct& Node = Nodes.Add_GetRef(TaskNode.Node);
|
|
|
+ InstantiateStructSubobjects(Node);
|
|
|
+
|
|
|
+ FEzAbilityTask& Task = Node.GetMutable<FEzAbilityTask>();
|
|
|
+ FEzAbilityDataView InstanceDataView;
|
|
|
+
|
|
|
+ if (TaskNode.Instance.IsValid())
|
|
|
+ {
|
|
|
+ // Struct Instance
|
|
|
+ const int32 InstanceIndex = InstanceStructs.Add(TaskNode.Instance);
|
|
|
+ InstantiateStructSubobjects(InstanceStructs[InstanceIndex]);
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ StructDesc.Struct = TaskNode.Instance.GetScriptStruct();
|
|
|
+ StructDesc.Name = Task.Name;
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Task.InstanceTemplateIndex = FEzAbilityIndex16(InstanceIndex);
|
|
|
+ Task.InstanceDataHandle = TaskDataHandle;
|
|
|
+ InstanceDataView = FEzAbilityDataView(InstanceStructs[InstanceIndex]);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Object Instance
|
|
|
+ check(TaskNode.InstanceObject != nullptr);
|
|
|
+
|
|
|
+ UObject* Instance = DuplicateObject(TaskNode.InstanceObject, EzAbility);
|
|
|
+ FInstancedStruct Wrapper;
|
|
|
+ Wrapper.InitializeAs<FEzAbilityInstanceObjectWrapper>(Instance);
|
|
|
+ const int32 InstanceIndex = InstanceStructs.Add(MoveTemp(Wrapper));
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ StructDesc.Struct = Instance->GetClass();
|
|
|
+ StructDesc.Name = Task.Name;
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Task.InstanceTemplateIndex = FEzAbilityIndex16(InstanceIndex);
|
|
|
+ Task.InstanceDataHandle = TaskDataHandle.ToObjectSource();
|
|
|
+ InstanceDataView = FEzAbilityDataView(Instance);
|
|
|
+ }
|
|
|
+
|
|
|
+ StructDesc.DataHandle = Task.InstanceDataHandle;
|
|
|
+
|
|
|
+ if (!CompileAndValidateNode(State, StructDesc, Node, InstanceDataView))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mark the instance as binding source.
|
|
|
+ BindingsCompiler.AddSourceStruct(StructDesc);
|
|
|
+
|
|
|
+ // Check that the bindings for this struct are still all valid.
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> CopyBindings;
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> ReferenceBindings;
|
|
|
+ if (!GetAndValidateBindings(StructDesc, InstanceDataView, CopyBindings, ReferenceBindings))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Compile batch copy for this struct, we pass in all the bindings, the compiler will pick up the ones for the target structs.
|
|
|
+ int32 BatchIndex = INDEX_NONE;
|
|
|
+ if (!BindingsCompiler.CompileBatch(StructDesc, CopyBindings, BatchIndex))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!BindingsCompiler.CompileReferences(StructDesc, ReferenceBindings, InstanceDataView))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("BatchIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Task.BindingsBatch = FEzAbilityIndex16(BatchIndex);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::CreateEvaluator(const FEzAbilityEditorNode& EvalNode, const FEzAbilityDataHandle EvalDataHandle)
|
|
|
+{
|
|
|
+ // Silently ignore empty nodes.
|
|
|
+ if (!EvalNode.Node.IsValid())
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ FEzAbilityBindableStructDesc StructDesc;
|
|
|
+ StructDesc.ID = EvalNode.ID;
|
|
|
+ StructDesc.Name = EvalNode.Node.GetScriptStruct()->GetFName();
|
|
|
+ StructDesc.DataSource = EEzAbilityBindableStructSource::Evaluator;
|
|
|
+
|
|
|
+ // Check that node has valid instance initialized.
|
|
|
+ if (!EvalNode.Instance.IsValid() && EvalNode.InstanceObject == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, StructDesc,
|
|
|
+ TEXT("Malformed evaluator, missing instance value."));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy the evaluator
|
|
|
+ IDToNode.Add(EvalNode.ID, Nodes.Num());
|
|
|
+ FInstancedStruct& Node = Nodes.Add_GetRef(EvalNode.Node);
|
|
|
+ InstantiateStructSubobjects(Node);
|
|
|
+
|
|
|
+ FEzAbilityEvaluator& Eval = Node.GetMutable<FEzAbilityEvaluator>();
|
|
|
+ FEzAbilityDataView InstanceDataView;
|
|
|
+
|
|
|
+ if (EvalNode.Instance.IsValid())
|
|
|
+ {
|
|
|
+ // Struct Instance
|
|
|
+ const int32 InstanceIndex = InstanceStructs.Add(EvalNode.Instance);
|
|
|
+ InstantiateStructSubobjects(InstanceStructs[InstanceIndex]);
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ StructDesc.Struct = EvalNode.Instance.GetScriptStruct();
|
|
|
+ StructDesc.Name = Eval.Name;
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Eval.InstanceTemplateIndex = FEzAbilityIndex16(InstanceIndex);
|
|
|
+ Eval.InstanceDataHandle = EvalDataHandle;
|
|
|
+ InstanceDataView = FEzAbilityDataView(InstanceStructs[InstanceIndex]);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Object Instance
|
|
|
+ check(EvalNode.InstanceObject != nullptr);
|
|
|
+
|
|
|
+ UObject* Instance = DuplicateObject(EvalNode.InstanceObject, EzAbility);
|
|
|
+ FInstancedStruct Wrapper;
|
|
|
+ Wrapper.InitializeAs<FEzAbilityInstanceObjectWrapper>(Instance);
|
|
|
+ const int32 InstanceIndex = InstanceStructs.Add(MoveTemp(Wrapper));
|
|
|
+
|
|
|
+ // Create binding source struct descriptor.
|
|
|
+ StructDesc.Struct = Instance->GetClass();
|
|
|
+ StructDesc.Name = Eval.Name;
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("InstanceIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Eval.InstanceTemplateIndex = FEzAbilityIndex16(InstanceIndex);
|
|
|
+ Eval.InstanceDataHandle = EvalDataHandle.ToObjectSource();
|
|
|
+ InstanceDataView = FEzAbilityDataView(Instance);
|
|
|
+ }
|
|
|
+
|
|
|
+ StructDesc.DataHandle = Eval.InstanceDataHandle;
|
|
|
+
|
|
|
+ if (!CompileAndValidateNode(nullptr, StructDesc, Node, InstanceDataView))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mark the instance as binding source.
|
|
|
+ BindingsCompiler.AddSourceStruct(StructDesc);
|
|
|
+
|
|
|
+ // Check that the bindings for this struct are still all valid.
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> CopyBindings;
|
|
|
+ TArray<FEzAbilityPropertyPathBinding> ReferenceBindings;
|
|
|
+ if (!GetAndValidateBindings(StructDesc, InstanceDataView, CopyBindings, ReferenceBindings))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Compile batch copy for this struct, we pass in all the bindings, the compiler will pick up the ones for the target structs.
|
|
|
+ int32 BatchIndex = INDEX_NONE;
|
|
|
+ if (!BindingsCompiler.CompileBatch(StructDesc, CopyBindings, BatchIndex))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!BindingsCompiler.CompileReferences(StructDesc, ReferenceBindings, InstanceDataView))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail())
|
|
|
+ {
|
|
|
+ Validation.Log(Log, TEXT("BatchIndex"), StructDesc);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Eval.BindingsBatch = FEzAbilityIndex16(BatchIndex);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::IsPropertyOfType(UScriptStruct& Type, const FEzAbilityBindableStructDesc& Struct, FEzAbilityPropertyPath Path) const
|
|
|
+{
|
|
|
+ TArray<FEzAbilityPropertyPathIndirection> Indirection;
|
|
|
+ const bool bResolved = Path.ResolveIndirections(Struct.Struct, Indirection);
|
|
|
+
|
|
|
+ if (bResolved && Indirection.Num() > 0)
|
|
|
+ {
|
|
|
+ check(Indirection.Last().GetProperty());
|
|
|
+ if (const FProperty* OwnerProperty = Indirection.Last().GetProperty()->GetOwnerProperty())
|
|
|
+ {
|
|
|
+ if (const FStructProperty* OwnerStructProperty = CastField<FStructProperty>(OwnerProperty))
|
|
|
+ {
|
|
|
+ return OwnerStructProperty->Struct == &Type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::ValidateStructRef(const FEzAbilityBindableStructDesc& SourceStruct, FEzAbilityPropertyPath SourcePath,
|
|
|
+ const FEzAbilityBindableStructDesc& TargetStruct, FEzAbilityPropertyPath TargetPath) const
|
|
|
+{
|
|
|
+ FString ResolveError;
|
|
|
+ TArray<FEzAbilityPropertyPathIndirection> TargetIndirection;
|
|
|
+ if (!TargetPath.ResolveIndirections(TargetStruct.Struct, TargetIndirection, &ResolveError))
|
|
|
+ {
|
|
|
+ // This will later be reported by the bindings compiler.
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Failed to resolve binding path in %s: %s"), *TargetStruct.ToString(), *ResolveError);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const FProperty* TargetLeafProperty = TargetIndirection.Num() > 0 ? TargetIndirection.Last().GetProperty() : nullptr;
|
|
|
+
|
|
|
+ // Early out if the target is not FEzAbilityStructRef.
|
|
|
+ const FStructProperty* TargetStructProperty = CastField<FStructProperty>(TargetLeafProperty);
|
|
|
+ if (TargetStructProperty == nullptr || TargetStructProperty->Struct != TBaseStructure<FEzAbilityStructRef>::Get())
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ FString TargetBaseStructName;
|
|
|
+ const UScriptStruct* TargetBaseStruct = UE::EzAbility::Compiler::GetBaseStructFromMetaData(TargetStructProperty, TargetBaseStructName);
|
|
|
+ if (TargetBaseStruct == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Could not find base struct type '%s' for target %s'."),
|
|
|
+ *TargetBaseStructName, *UE::EzAbility::GetDescAndPathAsString(TargetStruct, TargetPath));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ TArray<FEzAbilityPropertyPathIndirection> SourceIndirection;
|
|
|
+ if (!SourcePath.ResolveIndirections(SourceStruct.Struct, SourceIndirection, &ResolveError))
|
|
|
+ {
|
|
|
+ // This will later be reported by the bindings compiler.
|
|
|
+ Log.Reportf(EMessageSeverity::Error, SourceStruct, TEXT("Failed to resolve binding path in %s: %s"), *SourceStruct.ToString(), *ResolveError);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const FProperty* SourceLeafProperty = SourceIndirection.Num() > 0 ? SourceIndirection.Last().GetProperty() : nullptr;
|
|
|
+
|
|
|
+ // Exit if the source is not a struct property.
|
|
|
+ const FStructProperty* SourceStructProperty = CastField<FStructProperty>(SourceLeafProperty);
|
|
|
+ if (SourceStructProperty == nullptr)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (SourceStructProperty->Struct == TBaseStructure<FEzAbilityStructRef>::Get())
|
|
|
+ {
|
|
|
+ // Source is struct ref too, check the types match.
|
|
|
+ FString SourceBaseStructName;
|
|
|
+ const UScriptStruct* SourceBaseStruct = UE::EzAbility::Compiler::GetBaseStructFromMetaData(SourceStructProperty, SourceBaseStructName);
|
|
|
+ if (SourceBaseStruct == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Could not find base struct '%s' for binding source %s."),
|
|
|
+ *SourceBaseStructName, *UE::EzAbility::GetDescAndPathAsString(SourceStruct, SourcePath));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (SourceBaseStruct->IsChildOf(TargetBaseStruct) == false)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Type mismatch between source %s and target %s types, '%s' is not child of '%s'."),
|
|
|
+ *UE::EzAbility::GetDescAndPathAsString(SourceStruct, SourcePath),
|
|
|
+ *UE::EzAbility::GetDescAndPathAsString(TargetStruct, TargetPath),
|
|
|
+ *GetNameSafe(SourceBaseStruct), *GetNameSafe(TargetBaseStruct));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (!SourceStructProperty->Struct || SourceStructProperty->Struct->IsChildOf(TargetBaseStruct) == false)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Type mismatch between source %s and target %s types, '%s' is not child of '%s'."),
|
|
|
+ *UE::EzAbility::GetDescAndPathAsString(SourceStruct, SourcePath),
|
|
|
+ *UE::EzAbility::GetDescAndPathAsString(TargetStruct, TargetPath),
|
|
|
+ *GetNameSafe(SourceStructProperty->Struct), *GetNameSafe(TargetBaseStruct));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+bool FEzAbilityCompiler::GetAndValidateBindings(const FEzAbilityBindableStructDesc& TargetStruct, FEzAbilityDataView TargetValue, TArray<FEzAbilityPropertyPathBinding>& OutCopyBindings, TArray<FEzAbilityPropertyPathBinding>& OutReferenceBindings) const
|
|
|
+{
|
|
|
+ check(EditorData);
|
|
|
+
|
|
|
+ OutCopyBindings.Reset();
|
|
|
+ OutReferenceBindings.Reset();
|
|
|
+
|
|
|
+ // If target struct is not set, nothing to do.
|
|
|
+ if (TargetStruct.Struct == nullptr)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (FEzAbilityPropertyPathBinding& Binding : EditorData->EditorBindings.GetMutableBindings())
|
|
|
+ {
|
|
|
+ if (Binding.GetTargetPath().GetStructID() != TargetStruct.ID)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Source must be one of the source structs we have discovered in the tree.
|
|
|
+ const FGuid SourceStructID = Binding.GetSourcePath().GetStructID();
|
|
|
+ const FEzAbilityBindableStructDesc* SourceStruct = BindingsCompiler.GetSourceStructDescByID(SourceStructID);
|
|
|
+ if (!SourceStruct)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Failed to find binding source property '%s' for target %s."),
|
|
|
+ *Binding.GetSourcePath().ToString(), *UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update path instance types from latest data. E.g. binding may have been created for instanced object of type FooB, and changed to FooA.
|
|
|
+ // @todo: not liking how this mutates the Binding.TargetPath, but currently we dont track well the instanced object changes.
|
|
|
+
|
|
|
+ if (!Binding.GetMutableTargetPath().UpdateSegmentsFromValue(TargetValue))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Malformed target property path for binding source property '%s' for target %s."),
|
|
|
+ *Binding.GetSourcePath().ToString(), *UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Source must be accessible by the target struct via all execution paths.
|
|
|
+ TArray<FEzAbilityBindableStructDesc> AccessibleStructs;
|
|
|
+ EditorData->GetAccessibleStructs(Binding.GetTargetPath().GetStructID(), AccessibleStructs);
|
|
|
+
|
|
|
+ const bool bSourceAccessible = AccessibleStructs.ContainsByPredicate([SourceStructID](const FEzAbilityBindableStructDesc& Structs)
|
|
|
+ {
|
|
|
+ return (Structs.ID == SourceStructID);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!bSourceAccessible)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Property at %s cannot be bound to %s, because the binding source %s is not updated before %s in the tree."),
|
|
|
+ *UE::EzAbility::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()),
|
|
|
+ *UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()),
|
|
|
+ *SourceStruct->ToString(), *TargetStruct.ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!IDToStructValue.Contains(SourceStructID))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Failed to find value for binding source property '%s' for target %s."),
|
|
|
+ *Binding.GetSourcePath().ToString(), *UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the source structs only if we have value for it. For some sources (e.g. context structs) we know only type, and in that case there are no instance structs.
|
|
|
+ const FEzAbilityDataView SourceValue = IDToStructValue[SourceStructID];
|
|
|
+ if (SourceValue.IsValid())
|
|
|
+ {
|
|
|
+ if (!Binding.GetMutableSourcePath().UpdateSegmentsFromValue(SourceValue))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Malformed target property path for binding source property '%s' for source %s."),
|
|
|
+ *Binding.GetSourcePath().ToString(), *UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!SourceStruct->DataHandle.IsValid())
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Malformed source'%s for property binding property '%s'."),
|
|
|
+ *UE::EzAbility::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()), *Binding.GetSourcePath().ToString());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ FEzAbilityPropertyPathBinding BindingCopy(Binding);
|
|
|
+ BindingCopy.SetSourceDataHandle(SourceStruct->DataHandle);
|
|
|
+
|
|
|
+ // Special case fo AnyEnum. EzAbilityBindingExtension allows AnyEnums to bind to other enum types.
|
|
|
+ // The actual copy will be done via potential type promotion copy, into the value property inside the AnyEnum.
|
|
|
+ // We amend the paths here to point to the 'Value' property.
|
|
|
+ const bool bSourceIsAnyEnum = IsPropertyOfType(*TBaseStructure<FEzAbilityAnyEnum>::Get(), *SourceStruct, Binding.GetSourcePath());
|
|
|
+ const bool bTargetIsAnyEnum = IsPropertyOfType(*TBaseStructure<FEzAbilityAnyEnum>::Get(), TargetStruct, Binding.GetTargetPath());
|
|
|
+ if (bSourceIsAnyEnum || bTargetIsAnyEnum)
|
|
|
+ {
|
|
|
+ if (bSourceIsAnyEnum)
|
|
|
+ {
|
|
|
+ BindingCopy.GetMutableSourcePath().AddPathSegment(GET_MEMBER_NAME_STRING_CHECKED(FEzAbilityAnyEnum, Value));
|
|
|
+ }
|
|
|
+ if (bTargetIsAnyEnum)
|
|
|
+ {
|
|
|
+ BindingCopy.GetMutableTargetPath().AddPathSegment(GET_MEMBER_NAME_STRING_CHECKED(FEzAbilityAnyEnum, Value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (IsPropertyOfType(*FEzAbilityPropertyRef::StaticStruct(), TargetStruct, Binding.GetTargetPath()))
|
|
|
+ {
|
|
|
+ OutReferenceBindings.Add(BindingCopy);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ OutCopyBindings.Add(BindingCopy);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if the bindings is for struct ref and validate the types.
|
|
|
+ if (!ValidateStructRef(*SourceStruct, Binding.GetSourcePath(), TargetStruct, Binding.GetTargetPath()))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ auto IsPropertyBound = [](const FName& PropertyName, TConstArrayView<FEzAbilityPropertyPathBinding> Bindings)
|
|
|
+ {
|
|
|
+ return Bindings.ContainsByPredicate([&PropertyName](const FEzAbilityPropertyPathBinding& Binding)
|
|
|
+ {
|
|
|
+ // We're looping over just the first level of properties on the struct, so we assume that the path is just one item
|
|
|
+ // (or two in case of AnyEnum, because we expand the path to Property.Value, see code above).
|
|
|
+ return Binding.GetTargetPath().GetSegments().Num() >= 1 && Binding.GetTargetPath().GetSegments()[0].GetName() == PropertyName;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ bool bResult = true;
|
|
|
+
|
|
|
+ // Validate that Input and Context bindings
|
|
|
+ for (TFieldIterator<FProperty> It(TargetStruct.Struct); It; ++It)
|
|
|
+ {
|
|
|
+ const FProperty* Property = *It;
|
|
|
+ check(Property);
|
|
|
+ const FName PropertyName = Property->GetFName();
|
|
|
+ const bool bIsOptional = UE::EzAbility::PropertyHelpers::HasOptionalMetadata(*Property);
|
|
|
+
|
|
|
+ if (UE::EzAbility::PropertyRefHelpers::IsPropertyRef(*Property))
|
|
|
+ {
|
|
|
+ if (bIsOptional == false && !IsPropertyBound(PropertyName, OutReferenceBindings))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Property reference '%s' on % s is expected to have a binding."),
|
|
|
+ *PropertyName.ToString(), *TargetStruct.ToString());
|
|
|
+ bResult = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ const EEzAbilityPropertyUsage Usage = UE::EzAbility::GetUsageFromMetaData(Property);
|
|
|
+ if (Usage == EEzAbilityPropertyUsage::Input)
|
|
|
+ {
|
|
|
+ // Make sure that an Input property is bound unless marked optional.
|
|
|
+ if (bIsOptional == false && !IsPropertyBound(PropertyName, OutCopyBindings))
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Input property '%s' on %s is expected to have a binding."),
|
|
|
+ *PropertyName.ToString(), *TargetStruct.ToString());
|
|
|
+ bResult = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (Usage == EEzAbilityPropertyUsage::Context)
|
|
|
+ {
|
|
|
+ // Make sure that an Context property is manually or automatically bound.
|
|
|
+ const UStruct* ContextObjectType = nullptr;
|
|
|
+ if (const FStructProperty* StructProperty = CastField<FStructProperty>(Property))
|
|
|
+ {
|
|
|
+ ContextObjectType = StructProperty->Struct;
|
|
|
+ }
|
|
|
+ else if (const FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
|
|
|
+ {
|
|
|
+ ContextObjectType = ObjectProperty->PropertyClass;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ContextObjectType == nullptr)
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("The type of Context property '%s' on %s is expected to be Object Reference or Struct."),
|
|
|
+ *PropertyName.ToString(), *TargetStruct.ToString());
|
|
|
+ bResult = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const bool bIsBound = IsPropertyBound(PropertyName, OutCopyBindings);
|
|
|
+
|
|
|
+ if (!bIsBound)
|
|
|
+ {
|
|
|
+ const FEzAbilityBindableStructDesc Desc = EditorData->FindContextData(ContextObjectType, PropertyName.ToString());
|
|
|
+
|
|
|
+ if (Desc.IsValid())
|
|
|
+ {
|
|
|
+ // Add automatic binding to Context data.
|
|
|
+ OutCopyBindings.Emplace(FEzAbilityPropertyPath(Desc.ID), FEzAbilityPropertyPath(TargetStruct.ID, PropertyName));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Log.Reportf(EMessageSeverity::Error, TargetStruct,
|
|
|
+ TEXT("Could not find matching Context object for Context property '%s' on '%s'. Property must have manual binding."),
|
|
|
+ *PropertyName.ToString(), *TargetStruct.ToString());
|
|
|
+ bResult = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return bResult;
|
|
|
+}
|
|
|
+
|
|
|
+void FEzAbilityCompiler::InstantiateStructSubobjects(FStructView Struct)
|
|
|
+{
|
|
|
+ check(EzAbility);
|
|
|
+ check(EditorData);
|
|
|
+
|
|
|
+ // Empty struct, nothing to do.
|
|
|
+ if (!Struct.IsValid())
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (TPropertyValueIterator<FProperty> It(Struct.GetScriptStruct(), Struct.GetMemory()); It; ++It)
|
|
|
+ {
|
|
|
+ if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(It->Key))
|
|
|
+ {
|
|
|
+ // Duplicate instanced objects.
|
|
|
+ if (ObjectProperty->HasAnyPropertyFlags(CPF_InstancedReference))
|
|
|
+ {
|
|
|
+ if (UObject* Object = ObjectProperty->GetObjectPropertyValue(It->Value))
|
|
|
+ {
|
|
|
+ UObject* OuterObject = Object->GetOuter();
|
|
|
+ // If the instanced object was created as Editor Data as outer,
|
|
|
+ // change the outer to State Tree to prevent references to editor only data.
|
|
|
+ if (Object->IsInOuter(EditorData))
|
|
|
+ {
|
|
|
+ OuterObject = EzAbility;
|
|
|
+ }
|
|
|
+ UObject* DuplicatedObject = DuplicateObject(Object, OuterObject);
|
|
|
+ ObjectProperty->SetObjectPropertyValue(const_cast<void*>(It->Value), DuplicatedObject);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (const FStructProperty* StructProperty = CastField<FStructProperty>(It->Key))
|
|
|
+ {
|
|
|
+ // If we encounter instanced struct, recursively handle it too.
|
|
|
+ if (StructProperty->Struct == TBaseStructure<FInstancedStruct>::Get())
|
|
|
+ {
|
|
|
+ FInstancedStruct& InstancedStruct = *static_cast<FInstancedStruct*>(const_cast<void*>(It->Value));
|
|
|
+ InstantiateStructSubobjects(InstancedStruct);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|