孟宇 3 kuukautta sitten
vanhempi
commit
c89400d3a0
17 muutettua tiedostoa jossa 3358 lisäystä ja 6 poistoa
  1. 4 0
      Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityAnyEnum.cpp
  2. 4 0
      Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityLinker.cpp
  3. 330 0
      Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbility_Replicate.cpp
  4. 9 0
      Ability/Plugins/EzAbility/Source/EzAbility/Public/Condition/EzAbilityCondition.h
  5. 53 1
      Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbility.h
  6. 39 0
      Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityAnyEnum.h
  7. 14 0
      Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityExecutionTypes.h
  8. 104 0
      Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityLinker.h
  9. 11 2
      Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityNodeBase.h
  10. 67 3
      Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityTypes.h
  11. 8 0
      Ability/Plugins/EzAbility/Source/EzAbility/Public/Task/EzAbilityTask.h
  12. 1740 0
      Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EzAbilityCompiler.cpp
  13. 83 0
      Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EzAbilityCompilerLog.cpp
  14. 493 0
      Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EzAbilityPropertyBindingCompiler.cpp
  15. 155 0
      Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/EzAbilityCompiler.h
  16. 122 0
      Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/EzAbilityCompilerLog.h
  17. 122 0
      Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/EzAbilityPropertyBindingCompiler.h

+ 4 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityAnyEnum.cpp

@@ -0,0 +1,4 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "EzAbilityAnyEnum.h"

+ 4 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityLinker.cpp

@@ -0,0 +1,4 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "EzAbilityLinker.h"

+ 330 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbility_Replicate.cpp

@@ -1,4 +1,9 @@
 #include "EzAbility.h"
+#include "EzAbilityLinker.h"
+#include "EzAbilityLog.h"
+#include "EzAbilityNodeBase.h"
+#include "Condition/EzAbilityCondition.h"
+#include "Misc/EnumerateRange.h"
 
 bool UEzAbility::ShouldReplicate(const FEzAbilityInstance& Instance) const
 {
@@ -73,3 +78,328 @@ FGuid UEzAbility::GetNodeIdFromIndex(const FEzAbilityIndex16 NodeIndex) const
 			: nullptr;
 	return Entry != nullptr ? Entry->Id : FGuid();
 }
+
+bool UEzAbility::Link()
+{
+	// Initialize the instance data default value.
+	// This data will be used to allocate runtime instance on all EzAbility users.
+	ResetLinked();
+
+	// Resolves nodes references to other EzAbility data
+	FEzAbilityLinker Linker(Schema);
+
+	for (int32 Index = 0; Index < Nodes.Num(); Index++)
+	{
+		FStructView Node = Nodes[Index];
+		if (FEzAbilityNodeBase* NodePtr = Node.GetPtr<FEzAbilityNodeBase>())
+		{
+			const bool bLinkSucceeded = NodePtr->Link(Linker);
+			if (!bLinkSucceeded || Linker.GetStatus() == EEzAbilityLinkerStatus::Failed)
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%s: node '%s' failed to resolve its references."), *GetFullName(), *NodePtr->StaticStruct()->GetName());
+				return false;
+			}
+		}
+	}
+
+	ExternalDataDescs = Linker.GetExternalDataDescs();
+
+	if (States.Num() > 0 && Nodes.Num() > 0)
+	{
+		// Check that all nodes are valid.
+		for (FConstStructView Node : Nodes)
+		{
+			if (!Node.IsValid())
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%s: State Tree asset was not properly loaded (missing node). See log for loading failures, or recompile the EzAbility asset."), *GetFullName());
+				return false;
+			}
+		}
+	}
+
+	if (!DefaultInstanceData.AreAllInstancesValid())
+	{
+		UE_LOG(LogEzAbility, Error, TEXT("%s: State Tree asset was not properly loaded (missing instance data). See log for loading failures, or recompile the EzAbility asset."), *GetFullName());
+		return false;
+	}
+
+	if (!SharedInstanceData.AreAllInstancesValid())
+	{
+		UE_LOG(LogEzAbility, Error, TEXT("%s: State Tree asset was not properly loaded (missing shared instance data). See log for loading failures, or recompile the EzAbility asset."), *GetFullName());
+		return false;
+	}
+	
+	if (!PatchBindings())
+	{
+		return false;
+	}
+
+	// Resolves property paths used by bindings a store property pointers
+	if (!PropertyBindings.ResolvePaths())
+	{
+		return false;
+	}
+
+	// Link succeeded, setup tree to be ready to run
+	bIsLinked = true;
+	
+	return true;
+}
+
+void UEzAbility::ResetLinked()
+{
+	bIsLinked = false;
+	ExternalDataDescs.Reset();
+
+	FWriteScopeLock WriteLock(PerThreadSharedInstanceDataLock);
+	PerThreadSharedInstanceData.Reset();
+}
+
+bool UEzAbility::PatchBindings()
+{
+	const TArrayView<FEzAbilityBindableStructDesc> SourceStructs = PropertyBindings.SourceStructs;
+	const TArrayView<FEzAbilityPropertyCopyBatch> CopyBatches = PropertyBindings.CopyBatches;
+	const TArrayView<FEzAbilityPropertyPathBinding> PropertyPathBindings = PropertyBindings.PropertyPathBindings;
+
+	// Make mapping from data handle to source struct.
+	TMap<FEzAbilityDataHandle, int32> SourceStructByHandle;
+	for (TConstEnumerateRef<FEzAbilityBindableStructDesc> SourceStruct : EnumerateRange(SourceStructs))
+	{
+		SourceStructByHandle.Add(SourceStruct->DataHandle, SourceStruct.GetIndex());
+	}
+
+	auto GetSourceStructByHandle = [&SourceStructByHandle, &SourceStructs](const FEzAbilityDataHandle DataHandle) -> FEzAbilityBindableStructDesc*
+	{
+		if (int32* Index = SourceStructByHandle.Find(DataHandle))
+		{
+			return &SourceStructs[*Index];
+		}
+		return nullptr;
+	};
+	
+	// Reconcile out of date classes.
+	for (FEzAbilityBindableStructDesc& SourceStruct : SourceStructs)
+	{
+		if (const UClass* SourceClass = Cast<UClass>(SourceStruct.Struct))
+		{
+			if (SourceClass->HasAnyClassFlags(CLASS_NewerVersionExists))
+			{
+				SourceStruct.Struct = SourceClass->GetAuthoritativeClass();
+			}
+		}
+	}
+	for (FEzAbilityPropertyCopyBatch& CopyBatch : CopyBatches)
+	{
+		if (const UClass* TargetClass = Cast<UClass>(CopyBatch.TargetStruct.Struct))
+		{
+			if (TargetClass->HasAnyClassFlags(CLASS_NewerVersionExists))
+			{
+				CopyBatch.TargetStruct.Struct = TargetClass->GetAuthoritativeClass();
+			}
+		}
+	}
+
+	auto PatchPropertyPath = [](FEzAbilityPropertyPath& PropertyPath)
+	{
+		for (FEzAbilityPropertyPathSegment& Segment : PropertyPath.GetMutableSegments())
+		{
+			if (const UClass* InstanceStruct = Cast<UClass>(Segment.GetInstanceStruct()))
+			{
+				if (InstanceStruct->HasAnyClassFlags(CLASS_NewerVersionExists))
+				{
+					Segment.SetInstanceStruct(InstanceStruct->GetAuthoritativeClass());
+				}
+			}
+		}
+	};
+
+	for (FEzAbilityPropertyPathBinding& PropertyPathBinding : PropertyPathBindings)
+	{
+		PatchPropertyPath(PropertyPathBinding.GetMutableSourcePath());
+		PatchPropertyPath(PropertyPathBinding.GetMutableTargetPath());
+	}
+
+	// Update property bag structs before resolving binding.
+	if (FEzAbilityBindableStructDesc* RootParamsDesc = GetSourceStructByHandle(FEzAbilityDataHandle(EEzAbilityDataSourceType::GlobalParameterData)))
+	{
+		RootParamsDesc->Struct = Parameters.GetPropertyBagStruct();
+	}
+
+	// Refresh state parameter descs and bindings batches.
+	for (const FCompactEzAbilityState& State : States)
+	{
+		// For subtrees and linked states, the parameters must exists.
+		if (State.Type == EEzAbilityStateType::Subtree
+			|| State.Type == EEzAbilityStateType::Linked
+			|| State.Type == EEzAbilityStateType::LinkedAsset)
+		{
+			if (!State.ParameterTemplateIndex.IsValid())
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%s: Data for state '%s' is malformed. Please recompile the EzAbility asset."), *GetFullName(), *State.Name.ToString());
+				return false;
+			}
+		}
+
+		if (State.ParameterTemplateIndex.IsValid())
+		{
+			// Subtree is a bind source, update bag struct.
+			const FCompactEzAbilityParameters& Params = DefaultInstanceData.GetMutableStruct(State.ParameterTemplateIndex.Get()).Get<FCompactEzAbilityParameters>();
+			FEzAbilityBindableStructDesc* Desc = GetSourceStructByHandle(State.ParameterDataHandle);
+			if (!Desc)
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%s: Data for state '%s' is malformed. Please recompile the EzAbility asset."), *GetFullName(), *State.Name.ToString());
+				return false;
+			}
+			Desc->Struct = Params.Parameters.GetPropertyBagStruct();
+
+			if (State.ParameterBindingsBatch.IsValid())
+			{
+				FEzAbilityPropertyCopyBatch& Batch = CopyBatches[State.ParameterBindingsBatch.Get()];
+				Batch.TargetStruct.Struct = Params.Parameters.GetPropertyBagStruct();
+			}
+		}
+	}
+
+	// Check linked state property bags consistency
+	for (const FCompactEzAbilityState& State : States)
+	{
+		if (State.Type == EEzAbilityStateType::Linked && State.LinkedState.IsValid())
+		{
+			const FCompactEzAbilityState& LinkedState = States[State.LinkedState.Index];
+
+			if (State.ParameterTemplateIndex.IsValid() == false
+				|| LinkedState.ParameterTemplateIndex.IsValid() == false)
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%s: Data for state '%s' is malformed. Please recompile the EzAbility asset."), *GetFullName(), *State.Name.ToString());
+				return false;
+			}
+
+			// Check that the bag in linked state matches.
+			const FCompactEzAbilityParameters& Params = DefaultInstanceData.GetMutableStruct(State.ParameterTemplateIndex.Get()).Get<FCompactEzAbilityParameters>();
+			const FCompactEzAbilityParameters& LinkedStateParams = DefaultInstanceData.GetMutableStruct(LinkedState.ParameterTemplateIndex.Get()).Get<FCompactEzAbilityParameters>();
+
+			if (LinkedStateParams.Parameters.GetPropertyBagStruct() != Params.Parameters.GetPropertyBagStruct())
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%s: The parameters on state '%s' does not match the linked state parameters in state '%s'. Please recompile the EzAbility asset."), *GetFullName(), *State.Name.ToString(), *LinkedState.Name.ToString());
+				return false;
+			}
+		}
+		else if (State.Type == EEzAbilityStateType::LinkedAsset && State.LinkedAsset)
+		{
+			// Check that the bag in linked state matches.
+			const FInstancedPropertyBag& TargetTreeParameters = State.LinkedAsset->Parameters;
+			const FCompactEzAbilityParameters& Params = DefaultInstanceData.GetMutableStruct(State.ParameterTemplateIndex.Get()).Get<FCompactEzAbilityParameters>();
+
+			if (TargetTreeParameters.GetPropertyBagStruct() != Params.Parameters.GetPropertyBagStruct())
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%s: The parameters on state '%s' does not match the linked asset parameters '%s'. Please recompile the EzAbility asset."),
+					*GetFullName(), *State.Name.ToString(), *State.LinkedAsset->GetFullName());
+				return false;
+			}
+		}
+	}
+
+
+	TMap<FEzAbilityDataHandle, FEzAbilityDataView> DataViews;
+	TMap<FEzAbilityIndex16, FEzAbilityDataView> BindingBatchDataView;
+
+	// Tree parameters
+	DataViews.Add(FEzAbilityDataHandle(EEzAbilityDataSourceType::GlobalParameterData), Parameters.GetMutableValue());
+
+	// Setup data views for context data. Since the external data is passed at runtime, we can only provide the type.
+	for (const FEzAbilityExternalDataDesc& DataDesc : ContextDataDescs)
+	{
+		DataViews.Add(DataDesc.Handle.DataHandle, FEzAbilityDataView(DataDesc.Struct, nullptr));
+	}
+	
+	// Setup data views for state parameters.
+	for (FCompactEzAbilityState& State : States)
+	{
+		if (State.ParameterDataHandle.IsValid())
+		{
+			FCompactEzAbilityParameters& Params = DefaultInstanceData.GetMutableStruct(State.ParameterTemplateIndex.Get()).Get<FCompactEzAbilityParameters>();
+			DataViews.Add(State.ParameterDataHandle, Params.Parameters.GetMutableValue());
+			if (State.ParameterBindingsBatch.IsValid())
+			{
+				BindingBatchDataView.Add(State.ParameterBindingsBatch, Params.Parameters.GetMutableValue());
+			}
+		}
+	}
+
+	// Setup data views for all nodes.
+	for (FConstStructView NodeView : Nodes)
+	{
+		const FEzAbilityNodeBase& Node = NodeView.Get<const FEzAbilityNodeBase>();
+		
+		FEzAbilityInstanceData* SourceInstanceData = &DefaultInstanceData;
+		if (NodeView.GetPtr<const FEzAbilityCondition>())
+		{
+			// Conditions are stored in shared instance data.
+			SourceInstanceData = &SharedInstanceData;
+		}
+
+		FEzAbilityDataView NodeDataView = Node.InstanceDataHandle.IsObjectSource()
+						? FEzAbilityDataView(SourceInstanceData->GetMutableObject(Node.InstanceTemplateIndex.Get()))
+						: FEzAbilityDataView(SourceInstanceData->GetMutableStruct(Node.InstanceTemplateIndex.Get()));
+
+		DataViews.Add(Node.InstanceDataHandle, NodeDataView);
+
+		if (Node.BindingsBatch.IsValid())
+		{
+			BindingBatchDataView.Add(Node.BindingsBatch, NodeDataView);
+		}
+	}
+	
+	auto GetDataSourceView = [&DataViews](const FEzAbilityDataHandle Handle) -> FEzAbilityDataView
+	{
+		if (const FEzAbilityDataView* ViewPtr = DataViews.Find(Handle))
+		{
+			return *ViewPtr;
+		}
+		return FEzAbilityDataView();
+	};
+
+	auto GetBindingBatchDataView = [&BindingBatchDataView](const FEzAbilityIndex16 Index) -> FEzAbilityDataView
+	{
+		if (const FEzAbilityDataView* ViewPtr = BindingBatchDataView.Find(Index))
+		{
+			return *ViewPtr;
+		}
+		return FEzAbilityDataView();
+	};
+
+
+	for (int32 BatchIndex = 0; BatchIndex < CopyBatches.Num(); ++BatchIndex)
+	{
+		const FEzAbilityPropertyCopyBatch& Batch = CopyBatches[BatchIndex];
+
+		// Find data view for the binding target.
+		FEzAbilityDataView TargetView = GetBindingBatchDataView(FEzAbilityIndex16(BatchIndex));
+		if (!TargetView.IsValid())
+		{
+			UE_LOG(LogEzAbility, Error, TEXT("%hs: Invalid target struct when trying to bind to '%s'."), __FUNCTION__, *Batch.TargetStruct.Name.ToString());
+			return false;
+		}
+
+		FString ErrorMsg;
+		for (int32 Index = Batch.BindingsBegin; Index != Batch.BindingsEnd; Index++)
+		{
+			FEzAbilityPropertyPathBinding& Binding = PropertyPathBindings[Index];
+			FEzAbilityDataView SourceView = GetDataSourceView(Binding.GetSourceDataHandle());
+			
+			if (!Binding.GetMutableSourcePath().UpdateSegmentsFromValue(SourceView, &ErrorMsg))
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%hs: Failed to update source instance structs for property binding '%s'. Reason: %s"), __FUNCTION__, *Binding.GetTargetPath().ToString(), *ErrorMsg);
+				return false;
+			}
+
+			if (!Binding.GetMutableTargetPath().UpdateSegmentsFromValue(TargetView, &ErrorMsg))
+			{
+				UE_LOG(LogEzAbility, Error, TEXT("%hs: Failed to update target instance structs for property binding '%s'. Reason: %s"), __FUNCTION__, *Binding.GetTargetPath().ToString(), *ErrorMsg);
+				return false;
+			}
+		}
+	}
+
+	return true;
+}

+ 9 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/Condition/EzAbilityCondition.h

@@ -14,4 +14,13 @@ USTRUCT()
 struct EZABILITY_API FEzAbilityCondition : public FEzAbilityNodeBase
 {
 	GENERATED_BODY()
+
+	UPROPERTY()
+	EEzAbilityConditionOperand Operand = EEzAbilityConditionOperand::And;
+
+	UPROPERTY()
+	int8 DeltaIndent = 0;
+
+	UPROPERTY()
+	EEzAbilityConditionEvaluationMode EvaluationMode = EEzAbilityConditionEvaluationMode::Evaluated;
 };

+ 53 - 1
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbility.h

@@ -36,15 +36,34 @@ public:
 	const FInstancedStructContainer& GetNodes() const { return Nodes; }
 	FEzAbilityIndex16 GetNodeIndexFromId(const FGuid Id) const;
 	FGuid GetNodeIdFromIndex(const FEzAbilityIndex16 NodeIndex) const;
+
+	/**
+	 * Resolves references between data in the EzAbility.
+	 * @return true if all references to internal and external data are resolved properly, false otherwise.
+	 */
+	[[nodiscard]] bool Link();
 protected:
 	UFUNCTION(BlueprintNativeEvent)
 	bool K2_ActivateAbility(FEzAbilityContext& Context) const;
 	
 	UFUNCTION(BlueprintNativeEvent)
 	bool K2_CanActivateAbility(FEzAbilityContext& Context, FText& OutText) const;
+
+private:
+	/**
+	 * Reset the data generated by Link(), this in turn will cause IsReadyToRun() to return false.
+	 * Used during linking, or to invalidate the linked data when data version is old (requires recompile). 
+	 */
+	void ResetLinked();
+
+	bool PatchBindings();
 	
 public:
 
+	/** Schema used to compile the EzAbility. */
+	UPROPERTY(Instanced)
+	TObjectPtr<class UEzAbilitySchema> Schema = nullptr;
+	
 	//States
 	UPROPERTY()
 	TArray<FCompactEzAbilityState>		States;
@@ -81,6 +100,14 @@ public:
 	UPROPERTY()
 	uint16 GlobalTasksNum = 0;
 
+	/** Number of context data, include parameters and all context data. */
+	UPROPERTY()
+	uint16 NumContextData = 0;
+
+	/** True if any global task is a transition task. */
+	UPROPERTY()
+	bool bHasGlobalTransitionTasks = false;
+	
 	UPROPERTY()
 	FEzAbilityIndex16		ParameterTemplateIndex	= FEzAbilityIndex16::Invalid;
 	
@@ -95,18 +122,43 @@ public:
 
 	UPROPERTY()
 	uint32 LastCompiledEditorDataHash = 0;
+	
+	/** Mapping of state guid for the Editor and state handles, created at compilation. */
+	UPROPERTY()
+	TArray<FEzAbilityStateIdToHandle> IDToStateMappings;
 
+	/** Mapping of node guid for the Editor and node index, created at compilation. */
 	UPROPERTY()
 	TArray<FEzAbilityNodeIdToIndex> IDToNodeMappings;
+	
+	/** Mapping of state transition identifiers and runtime compact transition index, created at compilation. */
+	UPROPERTY()
+	TArray<FEzAbilityTransitionIdToIndex> IDToTransitionMappings;
 
+	/** List of external data required by the state tree, created during linking. */
+	UPROPERTY(Transient)
+	TArray<FEzAbilityExternalDataDesc> ExternalDataDescs;
+
+	/** List of names external data enforced by the schema, created at compilation. */
+	UPROPERTY()
+	TArray<FEzAbilityExternalDataDesc> ContextDataDescs;
+	
+	/** True if the EzAbility was linked successfully. */
+	bool bIsLinked = false;
+	
 	//////////////////////////////////////////////////////////////////////////////////////////////////////////////
 	///Editor
 #if WITH_EDITORONLY_DATA
-	/** Edit time data for the StateTree, instance of UEzAbilityEditorData */
+	/** Edit time data for the EzAbility, instance of UEzAbilityEditorData */
 	UPROPERTY()
 	TObjectPtr<UObject> EditorData;
 
 	FDelegateHandle OnObjectsReinstancedHandle;
 	FDelegateHandle OnUserDefinedStructReinstancedHandle;
 #endif
+
+#if WITH_EDITOR
+	/** Resets the compiled data to empty. */
+	void ResetCompiled() {}
+#endif
 };

+ 39 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityAnyEnum.h

@@ -0,0 +1,39 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "EzAbilityAnyEnum.generated.h"
+
+/**
+ * Enum that can be any type in the UI. Helper class to deal with any enum in property binding.
+ */
+USTRUCT()
+struct EZABILITY_API FEzAbilityAnyEnum
+{
+	GENERATED_BODY()
+
+	bool operator==(const FEzAbilityAnyEnum& RHS) const
+	{
+		return Value == RHS.Value && Enum == RHS.Enum;
+	}
+
+	bool operator!=(const FEzAbilityAnyEnum& RHS) const
+	{
+		return Value != RHS.Value || Enum != RHS.Enum;
+	}
+
+	/** Initializes the class and value to specific enum. The value is set to the first value of the enum or 0 if class is null */
+	void Initialize(UEnum* NewEnum)
+	{
+		Enum = NewEnum;
+		Value = Enum == nullptr ? 0 : int32(Enum->GetValueByIndex(0));
+	}
+
+	/** The enum integer value. */
+	UPROPERTY(EditAnywhere, Category = Enum)
+	uint32 Value = 0;
+
+	/** The enum class associated with this enum. */
+	UPROPERTY(EditAnywhere, Category = Enum)
+	TObjectPtr<UEnum> Enum = nullptr;
+};

+ 14 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityExecutionTypes.h

@@ -8,6 +8,20 @@
 #include "UObject/Object.h"
 #include "EzAbilityExecutionTypes.generated.h"
 
+/** Defines how to assign the result of a condition to evaluate.  */
+UENUM()
+enum class EEzAbilityConditionEvaluationMode : uint8
+{
+	/** Condition is evaluated to set the result. This is the normal behavior. */
+	Evaluated,
+	
+	/** Do not evaluate the condition and force result to 'true'. */
+	ForcedTrue,
+	
+	/** Do not evaluate the condition and force result to 'false'. */
+	ForcedFalse,
+};
+
 UENUM()
 enum class EEzAbilityUpdatePhase : uint8
 {

+ 104 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityLinker.h

@@ -0,0 +1,104 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "EzAbilityExecutionTypes.h"
+#include "EzAbilityLog.h"
+#include "EzAbilitySchema.h"
+#include "UObject/Object.h"
+#include "EzAbilityLinker.generated.h"
+
+UENUM()
+enum class EEzAbilityLinkerStatus : uint8
+{
+	Succeeded,
+	Failed,
+};
+
+/**
+ * The EzAbility linker is used to resolved references to various EzAbility data at load time.
+ * @see TEzAbilityExternalDataHandle<> for example usage.
+ */
+struct FEzAbilityLinker
+{
+	explicit FEzAbilityLinker(const UEzAbilitySchema* InSchema) : Schema(InSchema) {}
+	
+	/** @returns the linking status. */
+	EEzAbilityLinkerStatus GetStatus() const { return Status; }
+	
+	/**
+	 * Links reference to an external UObject.
+	 * @param Handle Reference to TEzAbilityExternalDataHandle<> with UOBJECT type to link to.
+	 */
+	template <typename T>
+	typename TEnableIf<TIsDerivedFrom<typename T::DataType, UObject>::IsDerived, void>::Type LinkExternalData(T& Handle)
+	{
+		LinkExternalData(Handle, T::DataType::StaticClass(), T::DataRequirement);
+	}
+
+	/**
+	 * Links reference to an external UStruct.
+	 * @param Handle Reference to TEzAbilityExternalDataHandle<> with USTRUCT type to link to.
+	 */
+	template <typename T>
+	typename TEnableIf<!TIsDerivedFrom<typename T::DataType, UObject>::IsDerived && !TIsIInterface<typename T::DataType>::Value, void>::Type LinkExternalData(T& Handle)
+	{
+		LinkExternalData(Handle, T::DataType::StaticStruct(), T::DataRequirement);
+	}
+
+	/**
+	 * Links reference to an external IInterface.
+	 * @param Handle Reference to TEzAbilityExternalDataHandle<> with IINTERFACE type to link to.
+	 */
+	template <typename T>
+	typename TEnableIf<TIsIInterface<typename T::DataType>::Value, void>::Type LinkExternalData(T& Handle)
+	{
+		LinkExternalData(Handle, T::DataType::UClassType::StaticClass(), T::DataRequirement);
+	}
+
+	/**
+	 * Links reference to an external Object or Struct.
+	 * This function should only be used when TEzAbilityExternalDataHandle<> cannot be used, i.e. the Struct is based on some data.
+	 * @param Handle Reference to link to.
+	 * @param Struct Expected type of the Object or Struct to link to.
+	 * @param Requirement Describes if the external data is expected to be required or optional.
+	 */
+	void LinkExternalData(FEzAbilityExternalDataHandle& Handle, const UStruct* Struct, const EEzAbilityExternalDataRequirement Requirement)
+	{
+		if (Schema != nullptr && !Schema->IsExternalItemAllowed(*Struct))
+		{
+			UE_LOG(LogEzAbility, Error,
+				TEXT("External data of type '%s' used by current node is not allowed by schema '%s' (i.e. rejected by IsExternalItemAllowed)"),
+				*Struct->GetName(),
+				*Schema->GetClass()->GetName());
+
+			Handle = FEzAbilityExternalDataHandle();
+			Status = EEzAbilityLinkerStatus::Failed;
+			return;
+		}
+		
+		const FEzAbilityExternalDataDesc Desc(Struct, Requirement);
+		int32 Index = ExternalDataDescs.Find(Desc);
+		
+		if (Index == INDEX_NONE)
+		{
+			Index = ExternalDataDescs.Add(Desc);
+			ExternalDataDescs[Index].Handle.DataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::ExternalData, Index);
+		}
+		
+		Handle.DataHandle = ExternalDataDescs[Index].Handle.DataHandle;
+	}
+
+	/** @return linked external data descriptors. */
+	TConstArrayView<FEzAbilityExternalDataDesc> GetExternalDataDescs() const { return ExternalDataDescs; }
+
+	UE_DEPRECATED(5.4, "Not used anymore.")
+	void SetExternalDataBaseIndex(const int32 InExternalDataBaseIndex) {}
+
+protected:
+
+	const UEzAbilitySchema* Schema = nullptr;
+	EEzAbilityLinkerStatus Status = EEzAbilityLinkerStatus::Succeeded;
+	TArray<FEzAbilityExternalDataDesc> ExternalDataDescs;
+};

+ 11 - 2
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityNodeBase.h

@@ -4,6 +4,7 @@
 
 #include "CoreMinimal.h"
 #include "EzAbilityIndexTypes.h"
+#include "EzAbilityLinker.h"
 #include "EzAbilityPropertyBindings.h"
 #include "EzAbilityTypes.h"
 #include "UObject/Object.h"
@@ -28,8 +29,16 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS
 	virtual ~FEzAbilityNodeBase() = default;
 
 	virtual UStruct* GetInstanceDataType() const { return nullptr; }
-	virtual bool Link() { return false; }
-
+	virtual bool Link(FEzAbilityLinker& Linker) { return true; }
+	/**
+	 * Called during State Tree compilation, allows to modify and validate the node and instance data.
+	 * The method is called with node and instance that is duplicated during compilation and used at runtime (it's different than the data used in editor).  
+	 * @param InstanceDataView Pointer to the instance data.
+	 * @param ValidationMessages Any messages to report during validation. Displayed as errors if the validation result is Invalid, else as warnings.
+	 * @return Validation result based on if the validation succeeded or not. Returning Invalid will fail compilation and messages will be displayed as errors.
+	*/
+	virtual EDataValidationResult Compile(FEzAbilityDataView InstanceDataView, TArray<FText>& ValidationMessages) { return EDataValidationResult::NotValidated; }
+	
 #if WITH_EDITOR
 	PRAGMA_DISABLE_DEPRECATION_WARNINGS
 	UE_DEPRECATED(5.3, "Use version with FEzAbilityPropertyPath instead.")

+ 67 - 3
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityTypes.h

@@ -11,6 +11,13 @@
 
 class UEzAbility;
 
+namespace UE::EzAbility
+{
+	inline constexpr int32 MaxConditionIndent = 4;
+
+	inline const FName SchemaTag(TEXT("Schema"));
+}
+
 UENUM()
 enum class EEzAbilityExternalDataRequirement : uint8
 {
@@ -427,8 +434,7 @@ struct EZABILITY_API FCompactEzAbilityState
 	UPROPERTY()
 	uint8 TransitionsNum = 0;
 
-	UPROPERTY()
-	uint16 TasksBegin = 0;
+	
 	
 	UPROPERTY()
 	uint8 TasksNum = 0;
@@ -436,12 +442,26 @@ struct EZABILITY_API FCompactEzAbilityState
 	UPROPERTY()
 	uint8 InstanceDataNum = 0;
 
+	/** Index to first state enter condition */
+	UPROPERTY()
+	uint16 EnterConditionsBegin = 0;
+
+	UPROPERTY()
+	uint16 TasksBegin = 0;
+	
+	/** Index to first transition */
+	UPROPERTY()
+	uint16 TransitionsBegin = 0;
+	
 	UPROPERTY()
 	FEzAbilityDataHandle ParameterDataHandle = FEzAbilityDataHandle::Invalid;
 
 	UPROPERTY()
 	FEzAbilityIndex16 ParameterTemplateIndex = FEzAbilityIndex16::Invalid;
 
+	UPROPERTY()
+	FEzAbilityIndex16 ParameterBindingsBatch = FEzAbilityIndex16::Invalid;
+	
 	UPROPERTY()
 	EEzAbilityStateSelectionBehavior SelectionBehavior = EEzAbilityStateSelectionBehavior::TrySelectChildrenInOrder;
 };
@@ -824,6 +844,50 @@ struct EZABILITY_API FEzAbilityNodeIdToIndex
 	FEzAbilityIndex16 Index;
 };
 
+/**
+ * Pair of state guid and its associated state handle created at compilation.
+ */
+USTRUCT()
+struct EZABILITY_API FEzAbilityStateIdToHandle
+{
+	GENERATED_BODY()
+
+	FEzAbilityStateIdToHandle() = default;
+	explicit FEzAbilityStateIdToHandle(const FGuid& Id, const FEzAbilityStateHandle Handle)
+		: Id(Id)
+		, Handle(Handle)
+	{
+	}
+
+	UPROPERTY();
+	FGuid Id;
+
+	UPROPERTY();
+	FEzAbilityStateHandle Handle;
+};
+
+/**
+ * Pair of transition id and its associated compact transition index created at compilation.
+ */
+USTRUCT()
+struct EZABILITY_API FEzAbilityTransitionIdToIndex
+{
+	GENERATED_BODY()
+
+	FEzAbilityTransitionIdToIndex() = default;
+	explicit FEzAbilityTransitionIdToIndex(const FGuid& Id, const FEzAbilityIndex16 Index)
+		: Id(Id)
+		, Index(Index)
+	{
+	}
+
+	UPROPERTY();
+	FGuid Id;
+	
+	UPROPERTY();
+	FEzAbilityIndex16 Index;
+};
+
 /**
  * Link to another state in EzAbility
  */
@@ -840,7 +904,7 @@ struct EZABILITY_API FEzAbilityStateLink
 	UE_DEPRECATED(5.2, "Use UEzAbilityState::GetLinkToState() instead.")
 	void Set(const EEzAbilityTransitionType InType, const class UEzAbilityState* InState = nullptr) {}
 	UE_DEPRECATED(5.2, "Use UEzAbilityState::GetLinkToState() instead.")
-	void Set(const class UStateTreeState* InState) {}
+	void Set(const class UEzAbilityState* InState) {}
 #endif // WITH_EDITORONLY_DATA
 	
 PRAGMA_DISABLE_DEPRECATION_WARNINGS

+ 8 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/Task/EzAbilityTask.h

@@ -18,4 +18,12 @@ struct EZABILITY_API FEzAbilityTask : public FEzAbilityNodeBase
 	GENERATED_BODY()
 
 	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const {}
+
+
+	/** If set to true, TriggerTransitions() is called during transition handling. Default false. */
+	uint8 bShouldAffectTransitions : 1;
+
+	/** True if the node is Enabled (i.e. not explicitly disabled in the asset). */
+	UPROPERTY()
+	uint8 bTaskEnabled : 1;
 };

+ 1740 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EzAbilityCompiler.cpp

@@ -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);
+			}
+		}
+	}
+}

+ 83 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EzAbilityCompilerLog.cpp

@@ -0,0 +1,83 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "EzAbilityCompilerLog.h"
+
+#include "EzAbilityState.h"
+#include "IMessageLogListing.h"
+#include "Misc/UObjectToken.h"
+
+#include UE_INLINE_GENERATED_CPP_BY_NAME(EzAbilityCompilerLog)
+
+#define LOCTEXT_NAMESPACE "EzAbilityEditor"
+
+void FEzAbilityCompilerLog::AppendToLog(IMessageLogListing* LogListing) const
+{
+	for (const FEzAbilityCompilerLogMessage& EzAbilityMessage : Messages)
+	{
+		TSharedRef<FTokenizedMessage> Message = FTokenizedMessage::Create((EMessageSeverity::Type)EzAbilityMessage.Severity);
+
+		if (EzAbilityMessage.State != nullptr)
+		{
+			TSharedRef<FUObjectToken> ObjectToken = FUObjectToken::Create(EzAbilityMessage.State, FText::FromName(EzAbilityMessage.State->Name));
+
+			// Add a dummy activation since default behavior opens content browser.
+			static auto DummyActivation = [](const TSharedRef<class IMessageToken>&) {};
+			ObjectToken->OnMessageTokenActivated(FOnMessageTokenActivated::CreateLambda(DummyActivation));
+			
+			Message->AddToken(ObjectToken);
+		}
+
+		if (EzAbilityMessage.Item.ID.IsValid())
+		{
+			Message->AddToken(FTextToken::Create(FText::Format(LOCTEXT("LogMessageItem", " {0} '{1}': "),
+				UEnum::GetDisplayValueAsText(EzAbilityMessage.Item.DataSource), FText::FromName(EzAbilityMessage.Item.Name))));
+		}
+
+		if (!EzAbilityMessage.Message.IsEmpty())
+		{
+			Message->AddToken(FTextToken::Create(FText::FromString(EzAbilityMessage.Message)));
+		}
+
+		LogListing->AddMessage(Message);
+	}
+}
+
+void FEzAbilityCompilerLog::DumpToLog(const FLogCategoryBase& Category) const
+{
+	for (const FEzAbilityCompilerLogMessage& EzAbilityMessage : Messages)
+	{
+		FString Message;
+		
+		if (EzAbilityMessage.State != nullptr)
+		{
+			Message += FString::Printf(TEXT("State '%s': "), *EzAbilityMessage.State->Name.ToString());
+		}
+
+		if (EzAbilityMessage.Item.ID.IsValid())
+		{
+			Message += FString::Printf(TEXT("%s '%s': "), *UEnum::GetDisplayValueAsText(EzAbilityMessage.Item.DataSource).ToString(), *EzAbilityMessage.Item.Name.ToString());
+		}
+
+		Message += EzAbilityMessage.Message;
+
+		switch (EzAbilityMessage.Severity)
+		{
+		case EMessageSeverity::Error:
+			UE_LOG_REF(Category, Error, TEXT("%s"), *Message);
+			break;
+		case EMessageSeverity::PerformanceWarning:
+		case EMessageSeverity::Warning:
+			UE_LOG_REF(Category, Warning, TEXT("%s"), *Message);
+			break;
+		case EMessageSeverity::Info:
+		default:
+			UE_LOG_REF(Category, Log, TEXT("%s"), *Message);
+			break;
+		};
+	}
+}
+
+
+#undef LOCTEXT_NAMESPACE
+

+ 493 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EzAbilityPropertyBindingCompiler.cpp

@@ -0,0 +1,493 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "EzAbilityPropertyBindingCompiler.h"
+
+#include "EzAbilityCompiler.h"
+#include "EzAbilityCompilerLog.h"
+#include "EzAbilityLog.h"
+#include "EzAbilityPropertyHelpers.h"
+#include "EzAbilityPropertyRef.h"
+#include "IPropertyAccessEditor.h"
+#include "PropertyPathHelpers.h"
+
+#include UE_INLINE_GENERATED_CPP_BY_NAME(EzAbilityPropertyBindingCompiler)
+
+bool FEzAbilityPropertyBindingCompiler::Init(FEzAbilityPropertyBindings& InPropertyBindings, FEzAbilityCompilerLog& InLog)
+{
+	Log = &InLog;
+	PropertyBindings = &InPropertyBindings;
+	PropertyBindings->Reset();
+
+	SourceStructs.Reset();
+	
+	return true;
+}
+
+bool FEzAbilityPropertyBindingCompiler::CompileBatch(const FEzAbilityBindableStructDesc& TargetStruct, TConstArrayView<FEzAbilityPropertyPathBinding> BatchPropertyBindings, int32& OutBatchIndex)
+{
+	check(Log);
+	check(PropertyBindings);
+	OutBatchIndex = INDEX_NONE;
+
+	StoreSourceStructs();
+
+	struct FSortedBinding
+	{
+		FEzAbilityPropertyPathBinding Binding;
+		TArray<FEzAbilityPropertyPathIndirection> TargetIndirections;
+	};
+	TArray<FSortedBinding> NewBindings;
+
+	for (const FEzAbilityPropertyPathBinding& Binding : BatchPropertyBindings)
+	{
+		if (Binding.GetTargetPath().GetStructID() != TargetStruct.ID)
+		{
+			continue;
+		}
+		// Source must be in the source array
+		const FEzAbilityBindableStructDesc* SourceStruct = GetSourceStructDescByID(Binding.GetSourcePath().GetStructID());
+		if (!SourceStruct)
+		{
+			Log->Reportf(EMessageSeverity::Error, TargetStruct,
+				TEXT("Could not find a binding source."));
+			return false;
+		}
+
+		FString Error;
+		TArray<FEzAbilityPropertyPathIndirection> SourceIndirections;
+		TArray<FEzAbilityPropertyPathIndirection> 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;
+		}
+
+		FEzAbilityPropertyCopy DummyCopy;
+		FEzAbilityPropertyPathIndirection LastSourceIndirection = !SourceIndirections.IsEmpty() ? SourceIndirections.Last() : FEzAbilityPropertyPathIndirection(SourceStruct->Struct);
+		FEzAbilityPropertyPathIndirection LastTargetIndirection = !TargetIndirections.IsEmpty() ? TargetIndirections.Last() : FEzAbilityPropertyPathIndirection(TargetStruct.Struct);
+		if (!PropertyBindings->ResolveCopyType(LastSourceIndirection, LastTargetIndirection, DummyCopy))
+		{
+			Log->Reportf(EMessageSeverity::Error, TargetStruct,
+			TEXT("Cannot copy properties between %s and %s, properties are incompatible."),
+				*UE::EzAbility::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()),
+				*UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()));
+			return false;
+		}
+
+		FSortedBinding& NewBinding = NewBindings.AddDefaulted_GetRef();
+		NewBinding.Binding = FEzAbilityPropertyPathBinding(SourceStruct->DataHandle, Binding.GetSourcePath(), Binding.GetTargetPath());
+		NewBinding.TargetIndirections = 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();
+
+		FEzAbilityPropertyCopyBatch& Batch = PropertyBindings->CopyBatches.AddDefaulted_GetRef();
+		Batch.TargetStruct = TargetStruct;
+		Batch.BindingsBegin = IntCastChecked<uint16>(BindingsBegin);
+		Batch.BindingsEnd = IntCastChecked<uint16>(BindingsEnd);
+		OutBatchIndex = PropertyBindings->CopyBatches.Num() - 1;
+	}
+
+	return true;
+}
+
+bool FEzAbilityPropertyBindingCompiler::CompileReferences(const FEzAbilityBindableStructDesc& TargetStruct, TConstArrayView<FEzAbilityPropertyPathBinding> PropertyReferenceBindings, FEzAbilityDataView InstanceDataView)
+{
+	for (const FEzAbilityPropertyPathBinding& Binding : PropertyReferenceBindings)
+	{
+		if (Binding.GetTargetPath().GetStructID() != TargetStruct.ID)
+		{
+			continue;
+		}
+
+		// Source must be in the source array/
+		const FEzAbilityBindableStructDesc* SourceStruct = GetSourceStructDescByID(Binding.GetSourcePath().GetStructID());
+		if (!SourceStruct)
+		{
+			Log->Reportf(EMessageSeverity::Error, TargetStruct,
+				TEXT("Could not find a binding source."));
+			return false;
+		}
+
+		FString Error;
+		TArray<FEzAbilityPropertyPathIndirection> SourceIndirections;
+		
+		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 (!UE::EzAbility::PropertyRefHelpers::IsPropertyAccessibleForPropertyRef(SourceIndirections, *SourceStruct))
+		{
+			Log->Reportf(EMessageSeverity::Error, TargetStruct,
+					TEXT("%s cannot reference non-output %s "),
+					*UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()),
+					*UE::EzAbility::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()));
+			return false;
+		}
+
+		TArray<FEzAbilityPropertyIndirection> TargetIndirections;
+		FEzAbilityPropertyIndirection TargetFirstIndirection;
+		FEzAbilityPropertyPathIndirection TargetLeafIndirection;
+		if (!FEzAbilityPropertyBindings::ResolvePath(InstanceDataView.GetStruct(), Binding.GetTargetPath(), TargetIndirections, TargetFirstIndirection, TargetLeafIndirection))
+		{
+			Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Resolving path in %s: %s"), *TargetStruct.ToString(), *Error);
+			return false;
+		}
+
+		if (!UE::EzAbility::PropertyRefHelpers::IsPropertyRefCompatibleWithProperty(*TargetLeafIndirection.GetProperty(), *SourceIndirections.Last().GetProperty()))
+		{
+			Log->Reportf(EMessageSeverity::Error, TargetStruct,
+				TEXT("%s cannot reference %s, types are incompatible."),		
+				*UE::EzAbility::GetDescAndPathAsString(TargetStruct, Binding.GetTargetPath()),
+				*UE::EzAbility::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()));
+			return false;
+		}
+
+		FEzAbilityIndex16 ReferenceIndex;
+
+		// Reuse the index if another PropertyRef already references the same property.
+		{
+			int32 IndexOfAlreadyExisting = PropertyBindings->PropertyReferencePaths.IndexOfByPredicate([&Binding](const FEzAbilityPropertyRefPath& RefPath)
+			{
+				return RefPath.GetSourcePath() == Binding.GetSourcePath();
+			});
+
+			if (IndexOfAlreadyExisting != INDEX_NONE)
+			{
+				ReferenceIndex = FEzAbilityIndex16(IndexOfAlreadyExisting);
+			}
+		}
+
+		if (!ReferenceIndex.IsValid())
+		{
+			// If referencing another PropertyRef, reuse it's index.
+			if (UE::EzAbility::PropertyRefHelpers::IsPropertyRef(*SourceIndirections.Last().GetProperty()))
+			{
+				const FCompiledReference* ReferencedReference = CompiledReferences.FindByPredicate([&Binding](const FCompiledReference& CompiledReference)
+				{
+					return CompiledReference.Path == Binding.GetSourcePath();
+				});
+
+				if (ReferencedReference)
+				{
+					ReferenceIndex = ReferencedReference->Index;
+				}
+				else
+				{
+					if(!UE::EzAbility::PropertyHelpers::HasOptionalMetadata(*TargetLeafIndirection.GetProperty()))
+					{
+						Log->Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Referenced %s is not bound"), *UE::EzAbility::GetDescAndPathAsString(*SourceStruct, Binding.GetSourcePath()));
+						return false;
+					}
+							
+					return true;
+				}
+			}
+		}
+
+		if (!ReferenceIndex.IsValid())
+		{
+			ReferenceIndex = FEzAbilityIndex16(PropertyBindings->PropertyReferencePaths.Num());
+			PropertyBindings->PropertyReferencePaths.Emplace(SourceStruct->DataHandle, Binding.GetSourcePath());
+		}
+
+		// Store index in instance data.
+		uint8* RawData = FEzAbilityPropertyBindings::GetAddress(InstanceDataView, TargetIndirections, TargetFirstIndirection, TargetLeafIndirection.GetProperty());
+		check(RawData);
+		reinterpret_cast<FEzAbilityPropertyRef*>(RawData)->RefAccessIndex = ReferenceIndex;
+
+		FCompiledReference& CompiledReference = CompiledReferences.AddDefaulted_GetRef();
+		CompiledReference.Path = Binding.GetTargetPath();
+		CompiledReference.Index = ReferenceIndex;
+	}
+
+	return true;
+}
+
+void FEzAbilityPropertyBindingCompiler::Finalize()
+{
+	StoreSourceStructs();
+}
+
+int32 FEzAbilityPropertyBindingCompiler::AddSourceStruct(const FEzAbilityBindableStructDesc& SourceStruct)
+{
+	const FEzAbilityBindableStructDesc* ExistingStruct = SourceStructs.FindByPredicate([&SourceStruct](const FEzAbilityBindableStructDesc& Struct) { return (Struct.ID == SourceStruct.ID); });
+	if (ExistingStruct)
+	{
+		UE_LOG(LogEzAbility, Error, TEXT("%s already exists as %s using ID '%s'"),
+			*SourceStruct.ToString(), *ExistingStruct->ToString(), *ExistingStruct->ID.ToString());
+	}
+	
+	UE_CLOG(!SourceStruct.DataHandle.IsValid(), LogEzAbility, Error, TEXT("%s does not have a valid data handle."), *SourceStruct.ToString());
+	
+	SourceStructs.Add(SourceStruct);
+	return SourceStructs.Num() - 1;
+}
+
+int32 FEzAbilityPropertyBindingCompiler::GetSourceStructIndexByID(const FGuid& ID) const
+{
+	return SourceStructs.IndexOfByPredicate([ID](const FEzAbilityBindableStructDesc& Structs) { return (Structs.ID == ID); });
+}
+
+PRAGMA_DISABLE_DEPRECATION_WARNINGS
+bool FEzAbilityPropertyBindingCompiler::ResolvePropertyPath(const FEzAbilityBindableStructDesc& InStructDesc, const FEzAbilityEditorPropertyPath& InPath,
+															TArray<FEzAbilityPropertySegment>& OutSegments, const FProperty*& OutLeafProperty, int32& OutLeafArrayIndex,
+															FEzAbilityCompilerLog* InLog, const FEzAbilityBindableStructDesc* InLogContextStruct)
+{
+	if (!InPath.IsValid())
+	{
+		if (InLog != nullptr && InLogContextStruct != nullptr)
+		{
+			InLog->Reportf(EMessageSeverity::Error, *InLogContextStruct,
+					TEXT("Invalid path '%s:%s'."),
+					*InStructDesc.Name.ToString(), *InPath.ToString());
+		}
+		return false;
+	}
+
+	// If the path is empty, we're pointing directly at the source struct.
+	if (InPath.Path.Num() == 0)
+	{
+		OutLeafProperty = nullptr;
+		OutLeafArrayIndex = INDEX_NONE;
+		return true;
+	}
+
+	const UStruct* CurrentStruct = InStructDesc.Struct;
+	const FProperty* LeafProperty = nullptr;
+	int32 LeafArrayIndex = INDEX_NONE;
+	bool bResult = true;
+
+	for (int32 SegmentIndex = 0; SegmentIndex < InPath.Path.Num(); SegmentIndex++)
+	{
+		const FString& SegmentString = InPath.Path[SegmentIndex];
+		const TCHAR* PropertyNamePtr = nullptr;
+		int32 PropertyNameLength = 0;
+		int32 ArrayIndex = INDEX_NONE;
+		PropertyPathHelpers::FindFieldNameAndArrayIndex(SegmentString.Len(), *SegmentString, PropertyNameLength, &PropertyNamePtr, ArrayIndex);
+		ensure(PropertyNamePtr != nullptr);
+		FString PropertyNameString(PropertyNameLength, PropertyNamePtr);
+		const FName PropertyName = FName(*PropertyNameString, FNAME_Find);
+
+		const bool bFinalSegment = SegmentIndex == (InPath.Path.Num() - 1);
+
+		if (CurrentStruct == nullptr)
+		{
+			if (InLog != nullptr && InLogContextStruct != nullptr)
+			{
+				InLog->Reportf(EMessageSeverity::Error, *InLogContextStruct,
+						TEXT("Malformed path '%s:%s'."),
+						*InStructDesc.Name.ToString(), *InPath.ToString(SegmentIndex, TEXT("<"), TEXT(">")));
+			}
+			bResult = false;
+			break;
+		}
+
+		const FProperty* Property = CurrentStruct->FindPropertyByName(PropertyName);
+		if (Property == nullptr)
+		{
+			// TODO: use core redirects to fix up the name.
+			if (InLog != nullptr && InLogContextStruct != nullptr)
+			{
+				InLog->Reportf(EMessageSeverity::Error, *InLogContextStruct,
+						TEXT("Malformed path '%s:%s', could not find property '%s%s.%s'."),
+						*InStructDesc.Name.ToString(), *InPath.ToString(SegmentIndex, TEXT("<"), TEXT(">")),
+						CurrentStruct->GetPrefixCPP(), *CurrentStruct->GetName(), *PropertyName.ToString());
+			}
+			bResult = false;
+			break;
+		}
+
+		if (const auto Validation = UE::EzAbility::Compiler::IsValidIndex16(ArrayIndex); Validation.DidFail())
+		{
+			if (InLog != nullptr)
+			{
+				Validation.Log(*InLog, TEXT("ArrayIndex"), InStructDesc);
+			}
+			return false;
+		}
+
+		FEzAbilityPropertySegment& Segment = OutSegments.AddDefaulted_GetRef();
+		Segment.Name = PropertyName;
+		Segment.ArrayIndex = FEzAbilityIndex16(ArrayIndex);
+
+		// Check to see if it is an array access first
+		const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property);
+		if (ArrayProperty != nullptr && ArrayIndex != INDEX_NONE)
+		{
+			// It is an array, now check to see if this is an array of structures
+			if (const FStructProperty* ArrayOfStructsProperty = CastField<FStructProperty>(ArrayProperty->Inner))
+			{
+				Segment.Type = EEzAbilityPropertyAccessType::IndexArray;
+				CurrentStruct = ArrayOfStructsProperty->Struct;
+			}
+			// if it's not an array of structs, maybe it's an array of objects
+			else if (const FObjectPropertyBase* ArrayOfObjectsProperty = CastField<FObjectPropertyBase>(ArrayProperty->Inner))
+			{
+				Segment.Type = EEzAbilityPropertyAccessType::IndexArray;
+				CurrentStruct = ArrayOfObjectsProperty->PropertyClass;
+
+				if (!bFinalSegment)
+				{
+					// Object arrays need an object dereference adding if non-leaf
+					FEzAbilityPropertySegment& ExtraSegment = OutSegments.AddDefaulted_GetRef();
+					ExtraSegment.ArrayIndex = FEzAbilityIndex16(0);
+					ExtraSegment.Type = EEzAbilityPropertyAccessType::Object;
+					const FProperty* InnerProperty = ArrayProperty->Inner;
+					if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(InnerProperty))
+					{
+						ExtraSegment.Type = EEzAbilityPropertyAccessType::Object;
+					}
+					else if (const FWeakObjectProperty* WeakObjectProperty = CastField<FWeakObjectProperty>(InnerProperty))
+					{
+						ExtraSegment.Type = EEzAbilityPropertyAccessType::WeakObject;
+					}
+					else if (const FSoftObjectProperty* SoftObjectProperty = CastField<FSoftObjectProperty>(InnerProperty))
+					{
+						ExtraSegment.Type = EEzAbilityPropertyAccessType::SoftObject;
+					}
+				}
+			}
+			else
+			{
+				Segment.Type = EEzAbilityPropertyAccessType::IndexArray;
+				Segment.ArrayIndex = FEzAbilityIndex16(ArrayIndex);
+				CurrentStruct = nullptr;
+			}
+		}
+		// Leaf segments all get treated the same, plain, array, struct or object. Copy type is figured out separately.
+		else if (bFinalSegment)
+		{
+			Segment.Type = EEzAbilityPropertyAccessType::Offset;
+			CurrentStruct = nullptr;
+		}
+		// Check to see if this is a simple structure (eg. not an array of structures)
+		else if (const FStructProperty* StructProperty = CastField<FStructProperty>(Property))
+		{
+			Segment.Type = EEzAbilityPropertyAccessType::Offset;
+			CurrentStruct = StructProperty->Struct;
+		}
+		// Check to see if this is a simple object (eg. not an array of objects)
+		else if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
+		{
+			Segment.Type = EEzAbilityPropertyAccessType::Object;
+			CurrentStruct = ObjectProperty->PropertyClass;
+		}
+		// Check to see if this is a simple weak object property (eg. not an array of weak objects).
+		else if (const FWeakObjectProperty* WeakObjectProperty = CastField<FWeakObjectProperty>(Property))
+		{
+			Segment.Type = EEzAbilityPropertyAccessType::WeakObject;
+			CurrentStruct = WeakObjectProperty->PropertyClass;
+		}
+		// Check to see if this is a simple soft object property (eg. not an array of soft objects).
+		else if (const FSoftObjectProperty* SoftObjectProperty = CastField<FSoftObjectProperty>(Property))
+		{
+			Segment.Type = EEzAbilityPropertyAccessType::SoftObject;
+			CurrentStruct = SoftObjectProperty->PropertyClass;
+		}
+		else
+		{
+			if (InLog != nullptr && InLogContextStruct != nullptr)
+			{
+				InLog->Reportf(EMessageSeverity::Error, *InLogContextStruct,
+						TEXT("Unsupported segment %s in path '%s:%s'."),
+						*InStructDesc.Name.ToString(), *InPath.ToString(SegmentIndex, TEXT("<"), TEXT(">")),
+						*Property->GetCPPType(), *InStructDesc.Name.ToString(), *InPath.ToString(SegmentIndex, TEXT("<"), TEXT(">")));
+			}
+			bResult = false;
+			break;
+		}
+
+		if (bFinalSegment)
+		{
+			LeafProperty = Property;
+			LeafArrayIndex = ArrayIndex;
+		}
+	}
+
+	if (!bResult)
+	{
+		return false;
+	}
+
+	OutLeafProperty = LeafProperty;
+	OutLeafArrayIndex = LeafArrayIndex;
+
+	return true;
+}
+
+EPropertyAccessCompatibility FEzAbilityPropertyBindingCompiler::GetPropertyCompatibility(const FProperty* FromProperty, const FProperty* ToProperty)
+{
+	const EEzAbilityPropertyAccessCompatibility Result = FEzAbilityPropertyBindings::GetPropertyCompatibility(FromProperty, ToProperty);
+	if (Result == EEzAbilityPropertyAccessCompatibility::Compatible)
+	{
+		return EPropertyAccessCompatibility::Compatible;
+	}
+	if (Result == EEzAbilityPropertyAccessCompatibility::Promotable)
+	{
+		return EPropertyAccessCompatibility::Promotable;
+	}
+	return EPropertyAccessCompatibility::Incompatible;
+}
+PRAGMA_ENABLE_DEPRECATION_WARNINGS
+
+void FEzAbilityPropertyBindingCompiler::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]);
+		}
+	}
+}

+ 155 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/EzAbilityCompiler.h

@@ -0,0 +1,155 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "EzAbilityCompilerLog.h"
+#include "EzAbilityPropertyBindingCompiler.h"
+
+struct FStructView;
+
+enum class EEzAbilityConditionOperand : uint8;
+enum class EEzAbilityPropertyUsage : uint8;
+struct FEzAbilityDataView;
+struct FEzAbilityStateHandle;
+
+class UEzAbility;
+class UEzAbilityState;
+class UEzAbilityEditorData;
+struct FEzAbilityEditorNode;
+struct FEzAbilityStateLink;
+struct FEzAbilityNodeBase;
+
+/**
+ * Helper class to convert EzAbility editor representation into a compact data.
+ * Holds data needed during compiling.
+ */
+struct EZABILITYEDITOR_API FEzAbilityCompiler
+{
+public:
+
+	explicit FEzAbilityCompiler(FEzAbilityCompilerLog& InLog)
+		: Log(InLog)
+	{
+	}
+	
+	bool Compile(UEzAbility& InEzAbility);
+	
+private:
+
+	/** Resolves the state a transition points to. SourceState is nullptr for global tasks. */
+	bool ResolveTransitionState(const UEzAbilityState* SourceState, const FEzAbilityStateLink& Link, FEzAbilityStateHandle& OutTransitionHandle) const;
+	FEzAbilityStateHandle GetStateHandle(const FGuid& StateID) const;
+	UEzAbilityState* GetState(const FGuid& StateID) const;
+
+	bool CreateStates();
+	bool CreateStateRecursive(UEzAbilityState& State, const FEzAbilityStateHandle Parent);
+	
+	bool CreateEvaluators();
+	bool CreateGlobalTasks();
+	bool CreateStateTasksAndParameters();
+	bool CreateStateTransitions();
+	
+	bool CreateConditions(UEzAbilityState& State, TConstArrayView<FEzAbilityEditorNode> Conditions);
+	bool CreateCondition(UEzAbilityState& State, const FEzAbilityEditorNode& CondNode, const EEzAbilityConditionOperand Operand, const int8 DeltaIndent);
+	bool CreateTask(UEzAbilityState* State, const FEzAbilityEditorNode& TaskNode, const FEzAbilityDataHandle TaskDataHandle);
+	bool CreateEvaluator(const FEzAbilityEditorNode& EvalNode, const FEzAbilityDataHandle EvalDataHandle);
+	bool GetAndValidateBindings(const FEzAbilityBindableStructDesc& TargetStruct, FEzAbilityDataView TargetValue, TArray<FEzAbilityPropertyPathBinding>& OutCopyBindings, TArray<FEzAbilityPropertyPathBinding>& OutReferenceBindings) const;
+	bool IsPropertyOfType(UScriptStruct& Type, const FEzAbilityBindableStructDesc& Struct, FEzAbilityPropertyPath Path) const;
+	bool ValidateStructRef(const FEzAbilityBindableStructDesc& SourceStruct, FEzAbilityPropertyPath SourcePath,
+							const FEzAbilityBindableStructDesc& TargetStruct, FEzAbilityPropertyPath TargetPath) const;
+	bool CompileAndValidateNode(const UEzAbilityState* SourceState, const FEzAbilityBindableStructDesc& NodeDesc, FStructView NodeView, const FEzAbilityDataView InstanceData);
+
+	void InstantiateStructSubobjects(FStructView Struct);
+	
+	FEzAbilityCompilerLog& Log;
+	UEzAbility* EzAbility = nullptr;
+	UEzAbilityEditorData* EditorData = nullptr;
+	TMap<FGuid, int32> IDToNode;
+	TMap<FGuid, int32> IDToState;
+	TMap<FGuid, int32> IDToTransition;
+	TMap<FGuid, const FEzAbilityDataView > IDToStructValue;
+	TArray<UEzAbilityState*> SourceStates;
+
+	TArray<FInstancedStruct> Nodes;
+	TArray<FInstancedStruct> InstanceStructs;
+	TArray<FInstancedStruct> SharedInstanceStructs;
+	
+	FEzAbilityPropertyBindingCompiler BindingsCompiler;
+};
+
+
+namespace UE::EzAbility::Compiler
+{
+	struct FValidationResult
+	{
+		FValidationResult() = default;
+		FValidationResult(const bool bInResult, const int32 InValue, const int32 InMaxValue) : bResult(bInResult), Value(InValue), MaxValue(InMaxValue) {}
+
+		/** Validation succeeded */
+		bool DidSucceed() const { return bResult == true; }
+
+		/** Validation failed */
+		bool DidFail() const { return bResult == false; }
+
+		/**
+		 * Logs common validation for IsValidIndex16(), IsValidIndex8(), IsValidCount16(), IsValidCount8().
+		 * @param Log reference to the compiler log.
+		 * @param ContextText Text identifier for the context where the test is done.
+		 * @param ContextStruct Struct identifier for the context where the test is done.
+		 */
+		void Log(FEzAbilityCompilerLog& Log, const TCHAR* ContextText, const FEzAbilityBindableStructDesc& ContextStruct = FEzAbilityBindableStructDesc()) const;
+		
+		bool bResult = true;
+		int32 Value = 0;
+		int32 MaxValue = 0;
+	};
+
+	/**
+	 * Checks if given index can be represented as uint16, including MAX_uint16 as INDEX_NONE.
+	 * @param Index Index to test
+	 * @return validation result.
+	 */
+	inline FValidationResult IsValidIndex16(const int32 Index)
+	{
+		return FValidationResult(Index == INDEX_NONE || (Index >= 0 && Index < MAX_uint16), Index, MAX_uint16 - 1);
+	}
+
+	/**
+	 * Checks if given index can be represented as uint8, including MAX_uint8 as INDEX_NONE. 
+	 * @param Index Index to test
+	 * @return true if the index is valid.
+	 */
+	inline FValidationResult IsValidIndex8(const int32 Index)
+	{
+		return FValidationResult(Index == INDEX_NONE || (Index >= 0 && Index < MAX_uint8), Index, MAX_uint8 - 1);
+	}
+
+	/**
+	 * Checks if given count can be represented as uint16. 
+	 * @param Count Count to test
+	 * @return true if the count is valid.
+	 */
+	inline FValidationResult IsValidCount16(const int32 Count)
+	{
+		return FValidationResult(Count >= 0 && Count <= MAX_uint16, Count, MAX_uint16);
+	}
+
+	/**
+	 * Checks if given count can be represented as uint8. 
+	 * @param Count Count to test
+	 * @return true if the count is valid.
+	 */
+	inline FValidationResult IsValidCount8(const int32 Count)
+	{
+		return FValidationResult(Count >= 0 && Count <= MAX_uint8, Count, MAX_uint8);
+	}
+
+	/**
+	 * Returns UScriptStruct defined in "BaseStruct" metadata of given property.
+	 * @param Property Handle to property where value is got from.
+	 * @param OutBaseStructName Handle to property where value is got from.
+	 * @return Script struct defined by the BaseStruct or nullptr if not found.
+	 */
+	const UScriptStruct* GetBaseStructFromMetaData(const FProperty* Property, FString& OutBaseStructName);
+
+}; // UE::EzAbility::Compiler

+ 122 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/EzAbilityCompilerLog.h

@@ -0,0 +1,122 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "Logging/TokenizedMessage.h"
+#include "EzAbilityPropertyBindings.h"
+#include "EzAbilityCompilerLog.generated.h"
+
+class UEzAbilityState;
+
+/** EzAbility compiler log message */
+USTRUCT()
+struct EZABILITYEDITOR_API FEzAbilityCompilerLogMessage
+{
+	GENERATED_BODY()
+
+	FEzAbilityCompilerLogMessage() = default;
+	FEzAbilityCompilerLogMessage(const EMessageSeverity::Type InSeverity, const UEzAbilityState* InState, const FEzAbilityBindableStructDesc& InItem, const FString& InMessage)
+		: Severity(InSeverity)
+		, State(InState)
+		, Item(InItem)
+		, Message(InMessage)
+	{
+	}
+
+	/** Severity of the message. */
+	UPROPERTY()
+	int32 Severity = EMessageSeverity::Error;
+
+	/** (optional) The EzAbility state the message refers to. */
+	UPROPERTY()
+	TObjectPtr<const UEzAbilityState> State = nullptr;
+
+	/** (optional) The State tee item (condition/evaluator/task) the message refers to. */
+	UPROPERTY()
+	FEzAbilityBindableStructDesc Item;
+
+	/** The message */
+	UPROPERTY()
+	FString Message;
+};
+
+/** Message log for EzAbility compilation */
+USTRUCT()
+struct EZABILITYEDITOR_API FEzAbilityCompilerLog
+{
+	GENERATED_BODY()
+
+	/** Pushes State to be reported along with the message. */
+	void PushState(const UEzAbilityState* InState)
+	{
+		StateStack.Add(InState);
+	}
+	
+	/** Pops State to be reported along with the message. @see FEzAbilityCompilerLogStateScope */
+	void PopState(const UEzAbilityState* InState)
+	{
+		// Check for potentially miss matching push/pop
+		check(StateStack.Num() > 0 && StateStack.Last() == InState);
+		StateStack.Pop();
+	}
+
+	/** Returns current state context. */
+	const UEzAbilityState* CurrentState() const { return StateStack.Num() > 0 ? StateStack.Last() : nullptr;  }
+
+	/**
+	 * Reports a message.
+	 * @param InSeverity Severity of the message.
+	 * @param InItem EzAbility item (condition/evaluator/task) the message affects.
+	 * @param InMessage Message to display.
+	 */
+	void Report(EMessageSeverity::Type InSeverity, const FEzAbilityBindableStructDesc& InItem, const FString& InMessage)
+	{
+		Messages.Emplace(InSeverity, CurrentState(), InItem, InMessage);
+	}
+
+	/** Formatted version of the Report(). */
+	template <typename FmtType, typename... Types>
+	void Reportf(EMessageSeverity::Type InSeverity, const FEzAbilityBindableStructDesc& InItem, const FmtType& Fmt, Types... Args)
+	{
+		Report(InSeverity, InItem, FString::Printf(Fmt, Args...));
+	}
+
+	/** Formatted version of the Report(), omits Item for convenience. */
+	template <typename FmtType, typename... Types>
+	void Reportf(EMessageSeverity::Type InSeverity, const FmtType& Fmt, Types... Args)
+	{
+		Report(InSeverity, FEzAbilityBindableStructDesc(), FString::Printf(Fmt, Args...));
+	}
+
+	/** Appends EzAbility log to log listing. */
+	void AppendToLog(class IMessageLogListing* LogListing) const;
+
+	/** Dumps EzAbility log to log */
+	void DumpToLog(const FLogCategoryBase& Category) const;
+	
+protected:
+	UPROPERTY()
+	TArray<TObjectPtr<const UEzAbilityState>> StateStack;
+	
+	UPROPERTY()
+	TArray<FEzAbilityCompilerLogMessage> Messages;
+};
+
+/** Helper struct to manage reported state within a scope. */
+struct FEzAbilityCompilerLogStateScope
+{
+	FEzAbilityCompilerLogStateScope(const UEzAbilityState* InState, FEzAbilityCompilerLog& InLog)
+		: Log(InLog)
+		, State(InState)
+	{
+		Log.PushState(State);
+	}
+
+	~FEzAbilityCompilerLogStateScope()
+	{
+		Log.PopState(State);
+	}
+
+	FEzAbilityCompilerLog& Log; 
+	const UEzAbilityState* State = nullptr;
+};

+ 122 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/EzAbilityPropertyBindingCompiler.h

@@ -0,0 +1,122 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "EzAbilityPropertyBindings.h"
+#include "EzAbilityPropertyBindingCompiler.generated.h"
+
+enum class EPropertyAccessCompatibility;
+struct FEzAbilityCompilerLog;
+struct FEzAbilityPropertyPathBinding;
+
+/**
+ * Helper class to compile editor representation of property bindings into runtime representation.
+ * TODO: Better error reporting, something that can be shown in the UI.
+ */
+USTRUCT()
+struct EZABILITYEDITOR_API FEzAbilityPropertyBindingCompiler
+{
+	GENERATED_BODY()
+
+	/**
+	  * Initializes the compiler to compile copies to specified Property Bindings.
+	  * @param PropertyBindings - Reference to the Property Bindings where all the batches will be stored.
+	  * @return true on success.
+	  */
+	[[nodiscard]] bool Init(FEzAbilityPropertyBindings& InPropertyBindings, FEzAbilityCompilerLog& InLog);
+
+	/**
+	  * Compiles a batch of property copies.
+	  * @param TargetStruct - Description of the structs which contains the target properties.
+	  * @param PropertyBindings - Array of bindings to compile, all bindings that point to TargetStructs will be added to the batch.
+	  * @param OutBatchIndex - Resulting batch index, if index is INDEX_NONE, no bindings were found and no batch was generated.
+	  * @return True on success, false on failure.
+	 */
+	[[nodiscard]] bool CompileBatch(const FEzAbilityBindableStructDesc& TargetStruct, TConstArrayView<FEzAbilityPropertyPathBinding> PropertyBindings, int32& OutBatchIndex);
+
+	/**
+	  * Compiles references for selected struct
+	  * @param TargetStruct - Description of the structs which contains the target properties.
+	  * @param PropertyReferenceBindings - Array of bindings to compile, all bindings that point to TargetStructs will be added.
+	  * @param InstanceDataView - view to the instance data
+	  * @return True on success, false on failure.
+	 */
+	[[nodiscard]] bool CompileReferences(const FEzAbilityBindableStructDesc& TargetStruct, TConstArrayView<FEzAbilityPropertyPathBinding> PropertyReferenceBindings, FEzAbilityDataView InstanceDataView);
+
+	/** Finalizes compilation, should be called once all batches are compiled. */
+	void Finalize();
+
+	/**
+	  * Adds source struct. When compiling a batch, the bindings can be between any of the defined source structs, and the target struct.
+	  * Source structs can be added between calls to Compilebatch().
+	  * @param SourceStruct - Description of the source struct to add.
+	  * @return Source struct index.
+	  */
+	int32 AddSourceStruct(const FEzAbilityBindableStructDesc& SourceStruct);
+
+	/** @return Index of a source struct by specified ID, or INDEX_NONE if not found. */
+	UE_DEPRECATED(5.4, "Use GetSourceStructDescByID() instead.")
+	int32 GetSourceStructIndexByID(const FGuid& ID) const;
+
+	/** @return Reference to a source struct based on ID. */
+	UE_DEPRECATED(5.4, "Use GetSourceStructDescByID() instead.")
+	const FEzAbilityBindableStructDesc& GetSourceStructDesc(const int32 Index) const
+	{
+		return SourceStructs[Index];
+	}
+
+	const FEzAbilityBindableStructDesc* GetSourceStructDescByID(const FGuid& ID) const
+	{
+		return SourceStructs.FindByPredicate([ID](const FEzAbilityBindableStructDesc& Structs) { return (Structs.ID == ID); });
+	}
+
+PRAGMA_DISABLE_DEPRECATION_WARNINGS
+	/**
+	 * Resolves a string based property path in specified struct into segments of property names and access types.
+	 * If logging is required both, Log and LogContextStruct needs to be non-null.
+	 * @param InStructDesc Description of the struct in which the property path is valid.
+	 * @param InPath The property path in string format.
+	 * @param OutSegments The resolved property access path as segments.
+	 * @param OutLeafProperty The leaf property of the resolved path.
+	 * @param OutLeafArrayIndex The left array index (or INDEX_NONE if not applicable) of the resolved path.
+	 * @param Log Pointer to compiler log, or null if no logging needed.
+	 * @param LogContextStruct Pointer to bindable struct desc where the property path belongs to.
+	 * @return True of the property was solved successfully.
+	 */
+	UE_DEPRECATED(5.3, "Use FEzAbilityPropertyPath resolve indirection methods instead.")
+	[[nodiscard]] static bool ResolvePropertyPath(const FEzAbilityBindableStructDesc& InStructDesc, const FEzAbilityEditorPropertyPath& InPath,
+												  TArray<FEzAbilityPropertySegment>& OutSegments, const FProperty*& OutLeafProperty, int32& OutLeafArrayIndex,
+												  FEzAbilityCompilerLog* Log = nullptr, const FEzAbilityBindableStructDesc* LogContextStruct = nullptr);
+
+	/**
+	 * Checks if two property types can are compatible for copying.
+	 * @param FromProperty Property to copy from.
+	 * @param ToProperty Property to copy to.
+	 * @return Incompatible if the properties cannot be copied, Compatible if they are trivially copyable, or Promotable if numeric values can be promoted to another numeric type.
+	 */
+	UE_DEPRECATED(5.3, "Use FEzAbilityPropertyBindings::GetPropertyCompatibility instead.")
+	static EPropertyAccessCompatibility GetPropertyCompatibility(const FProperty* FromProperty, const FProperty* ToProperty);
+PRAGMA_ENABLE_DEPRECATION_WARNINGS
+
+protected:
+
+	void StoreSourceStructs();
+
+	UPROPERTY()
+	TArray<FEzAbilityBindableStructDesc> SourceStructs;
+
+	/**
+	 * Representation of compiled reference.
+	 */
+	struct FCompiledReference
+	{
+		FEzAbilityPropertyPath Path;
+		FEzAbilityIndex16 Index;
+	};
+
+	TArray<FCompiledReference> CompiledReferences;
+
+	FEzAbilityPropertyBindings* PropertyBindings = nullptr;
+
+	FEzAbilityCompilerLog* Log = nullptr;
+};