diff --git a/Source/Flow/Flow.Build.cs b/Source/Flow/Flow.Build.cs index 74df20a4c..4d8b12253 100644 --- a/Source/Flow/Flow.Build.cs +++ b/Source/Flow/Flow.Build.cs @@ -32,7 +32,8 @@ public Flow(ReadOnlyTargetRules target) : base(target) PublicDependencyModuleNames.AddRange(new[] { "MessageLog", - "UnrealEd" + "UnrealEd", + "SourceControl", }); } } diff --git a/Source/Flow/Private/Asset/FlowAssetParams.cpp b/Source/Flow/Private/Asset/FlowAssetParams.cpp new file mode 100644 index 000000000..a30daeede --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParams.cpp @@ -0,0 +1,570 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParams.h" +#include "FlowAsset.h" +#include "FlowLogChannels.h" +#include "Asset/FlowAssetParamsUtils.h" +#if WITH_EDITOR +#include "SourceControlHelpers.h" +#include "Misc/DataValidation.h" +#endif + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParams) + +void UFlowAssetParams::PostLoad() +{ + Super::PostLoad(); + +#if WITH_EDITOR + const EFlowReconcilePropertiesResult ReconcileResult = ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile ParentParams for %s: %s"), + *GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } +#endif +} + +void UFlowAssetParams::Serialize(FArchive& Ar) +{ +#if WITH_EDITOR + if (Ar.IsCooking()) + { + const EFlowReconcilePropertiesResult ReconcileResult = ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile ParentParams during cooking for %s: %s"), + *GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } + } +#endif + + Super::Serialize(Ar); +} + +UFlowAsset* UFlowAssetParams::ProvideFlowAsset() const +{ +#if WITH_EDITOR + return OwnerFlowAsset.LoadSynchronous(); +#else + // We don't have knowledge of the OwnerFlowAsset in non-editor builds + checkNoEntry(); + return nullptr; +#endif +} + +#if WITH_EDITOR +EDataValidationResult UFlowAssetParams::IsDataValid(FDataValidationContext& Context) const +{ + EDataValidationResult Result = Super::IsDataValid(Context); + + if (OwnerFlowAsset.IsNull()) + { + Context.AddError(FText::FromString(TEXT("OwnerFlowAsset is null"))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + else if (!OwnerFlowAsset.IsValid() && !OwnerFlowAsset.LoadSynchronous()) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Failed to load OwnerFlowAsset: %s"), *OwnerFlowAsset.ToString()))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + const EFlowReconcilePropertiesResult CycleResult = CheckForParentCycle(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(CycleResult)) + { + Context.AddError(FText::FromString(TEXT("Cyclic inheritance detected"))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + TSet SeenGuids; + for (int32 Index = 0; Index < Properties.Num(); ++Index) + { + const FFlowNamedDataPinProperty& Property = Properties[Index]; + if (Property.Name == NAME_None) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid name"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + if (!Property.DataPinProperty.IsValid()) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid DataPinProperty"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + if (!Property.Guid.IsValid()) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid Guid"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + else if (SeenGuids.Contains(Property.Guid)) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Duplicate Guid found for property at index %d"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + else + { + SeenGuids.Add(Property.Guid); + } + + if (Property.bMayChangeNameAndType) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has bMayChangeNameAndType = true in UFlowAssetParams"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + } + + return Result; +} + +EFlowReconcilePropertiesResult UFlowAssetParams::ReconcilePropertiesWithStartNode( + const FDateTime& FlowAssetLastSaveTimeStamp, + const TSoftObjectPtr& InOwnerFlowAsset, + TArray& MutablePropertiesFromStartNode) +{ + OwnerFlowAsset = InOwnerFlowAsset; + + if (OwnerFlowAsset.IsNull()) + { + return EFlowReconcilePropertiesResult::Error_InvalidAsset; + } + + const EFlowReconcilePropertiesResult PropertiesMatchResult = FFlowAssetParamsUtils::CheckPropertiesMatch(Properties, MutablePropertiesFromStartNode); + const FDateTime ParamsTimestamp = FFlowAssetParamsUtils::GetLastSavedTimestampForObject(this); + + if (FlowAssetLastSaveTimeStamp >= ParamsTimestamp || + EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(PropertiesMatchResult)) + { + ConfigureFlowAssetParams(InOwnerFlowAsset, nullptr, MutablePropertiesFromStartNode); + + return EFlowReconcilePropertiesResult::ParamsPropertiesUpdated; + } + + MutablePropertiesFromStartNode = Properties; + + FFlowNamedDataPinProperty::ConfigurePropertiesForFlowAssetParams(MutablePropertiesFromStartNode); + + return EFlowReconcilePropertiesResult::AssetPropertyValuesUpdated; +} + +EFlowReconcilePropertiesResult UFlowAssetParams::ReconcilePropertiesWithParentParams() +{ + const EFlowReconcilePropertiesResult CycleResult = CheckForParentCycle(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(CycleResult)) + { + return CycleResult; + } + + if (ParentParams.AssetPtr.IsNull()) + { + return EFlowReconcilePropertiesResult::NoChanges; + } + + UFlowAssetParams* Parent = ParentParams.AssetPtr.LoadSynchronous(); + if (!Parent) + { + UE_LOG(LogFlow, Warning, TEXT("Failed to load ParentParams: %s"), *ParentParams.AssetPtr.ToString()); + + return EFlowReconcilePropertiesResult::Error_UnloadableParent; + } + + const EFlowReconcilePropertiesResult ParentResult = Parent->ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ParentResult)) + { + return ParentResult; + } + + const TArray& ParentProps = Parent->Properties; + TArray NewProperties; + + for (const FFlowNamedDataPinProperty& ParentProp : ParentProps) + { + FFlowNamedDataPinProperty* LocalProp = FFlowAssetParamsUtils::FindPropertyByGuid(Properties, ParentProp.Guid); + if (LocalProp && LocalProp->bIsOverride) + { + FFlowNamedDataPinProperty UpdatedProp = *LocalProp; + + // Enforce Parent's name + UpdatedProp.Name = ParentProp.Name; + + NewProperties.Add(UpdatedProp); + + continue; + } + + if (LocalProp && FFlowAssetParamsUtils::ArePropertiesEqual(*LocalProp, ParentProp)) + { + LocalProp->bIsOverride = false; + + // Enforce Parent's name + LocalProp->Name = ParentProp.Name; + + NewProperties.Add(*LocalProp); + + continue; + } + + NewProperties.Add(ParentProp); + } + + for (FFlowNamedDataPinProperty& LocalProp : Properties) + { + if (!FFlowAssetParamsUtils::FindPropertyByGuid(ParentProps, LocalProp.Guid)) + { + LocalProp.bIsOverride = true; + + NewProperties.Add(LocalProp); + } + } + + if (FFlowAssetParamsUtils::ArePropertyArraysEqual(NewProperties, Properties)) + { + return EFlowReconcilePropertiesResult::NoChanges; + } + + Properties = NewProperties; + + (void) TryCheckOutFromSourceControl(); + + ModifyAndRebuildPropertiesMap(); + + return EFlowReconcilePropertiesResult::ParamsPropertiesUpdated; +} + +void UFlowAssetParams::ConfigureFlowAssetParams(TSoftObjectPtr OwnerAsset, TSoftObjectPtr InParentParams, const TArray& InProperties) +{ + ParentParams.AssetPtr = InParentParams; + OwnerFlowAsset = OwnerAsset; + Properties = InProperties; + FFlowNamedDataPinProperty::ConfigurePropertiesForFlowAssetParams(Properties); + + ModifyAndRebuildPropertiesMap(); +} + +bool UFlowAssetParams::TryCheckOutFromSourceControl() const +{ + if (!USourceControlHelpers::IsAvailable()) + { + return true; + } + + const FString FileName = USourceControlHelpers::PackageFilename(GetPathName()); + if (!USourceControlHelpers::CheckOutOrAddFile(FileName)) + { + UE_LOG(LogFlow, Warning, TEXT("%s is not checked out; properties updated in-memory only"), *GetPathName()); + return false; + } + + return true; +} + +EFlowReconcilePropertiesResult UFlowAssetParams::CheckForParentCycle() const +{ + TSet> Visited; + TSoftObjectPtr Current = ParentParams.AssetPtr; + + while (!Current.IsNull()) + { + if (Visited.Contains(Current)) + { + UE_LOG(LogFlow, Error, TEXT("Cyclic inheritance detected at: %s"), *Current.ToString()); + return EFlowReconcilePropertiesResult::Error_CyclicInheritance; + } + + Visited.Add(Current); + const UFlowAssetParams* CurrentParams = Current.LoadSynchronous(); + if (!CurrentParams) + { + UE_LOG(LogFlow, Warning, TEXT("Failed to load ParentParams: %s"), *Current.ToString()); + return EFlowReconcilePropertiesResult::Error_UnloadableParent; + } + + Current = CurrentParams->ParentParams.AssetPtr; + } + + return EFlowReconcilePropertiesResult::NoChanges; +} + +void UFlowAssetParams::ModifyAndRebuildPropertiesMap() +{ + Modify(); + + RebuildPropertiesMap(); + + MarkPackageDirty(); +} + +void UFlowAssetParams::RebuildPropertiesMap() +{ + PropertyMap.Reset(); + + for (const FFlowNamedDataPinProperty& Prop : Properties) + { + if (Prop.IsValid()) + { + PropertyMap.Add(Prop.Name, Prop.DataPinProperty); + } + else + { + UE_LOG(LogFlow, Warning, TEXT("Skipping invalid property %s during rebuild for %s"), *Prop.Name.ToString(), *GetPathName()); + } + } +} +#endif + +bool UFlowAssetParams::CanSupplyDataPinValues_Implementation() const +{ + return !PropertyMap.IsEmpty(); +} + +FFlowDataPinResult_Bool UFlowAssetParams::TrySupplyDataPinAsBool_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Bool::StaticStruct())) + { + const FFlowDataPinOutputProperty_Bool& BoolProp = Found->Get(); + return FFlowDataPinResult_Bool(BoolProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Bool pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Bool(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Int UFlowAssetParams::TrySupplyDataPinAsInt_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + const UScriptStruct* Struct = Found->GetScriptStruct(); + if (Struct->IsChildOf(FFlowDataPinOutputProperty_Int64::StaticStruct())) + { + const FFlowDataPinOutputProperty_Int64& IntProp = Found->Get(); + return FFlowDataPinResult_Int(IntProp.Value); + } + else if (Struct->IsChildOf(FFlowDataPinOutputProperty_Int32::StaticStruct())) + { + const FFlowDataPinOutputProperty_Int32& IntProp = Found->Get(); + return FFlowDataPinResult_Int(static_cast(IntProp.Value)); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Int pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Int(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Float UFlowAssetParams::TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + const UScriptStruct* Struct = Found->GetScriptStruct(); + if (Struct->IsChildOf(FFlowDataPinOutputProperty_Double::StaticStruct())) + { + const FFlowDataPinOutputProperty_Double& FloatProp = Found->Get(); + return FFlowDataPinResult_Float(FloatProp.Value); + } + else if (Struct->IsChildOf(FFlowDataPinOutputProperty_Float::StaticStruct())) + { + const FFlowDataPinOutputProperty_Float& FloatProp = Found->Get(); + return FFlowDataPinResult_Float(static_cast(FloatProp.Value)); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Float pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Float(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Name UFlowAssetParams::TrySupplyDataPinAsName_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Name::StaticStruct())) + { + const FFlowDataPinOutputProperty_Name& NameProp = Found->Get(); + return FFlowDataPinResult_Name(NameProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Name pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Name(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_String UFlowAssetParams::TrySupplyDataPinAsString_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_String::StaticStruct())) + { + const FFlowDataPinOutputProperty_String& StringProp = Found->Get(); + return FFlowDataPinResult_String(StringProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for String pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_String(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Text UFlowAssetParams::TrySupplyDataPinAsText_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Text::StaticStruct())) + { + const FFlowDataPinOutputProperty_Text& TextProp = Found->Get(); + return FFlowDataPinResult_Text(TextProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Text pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Text(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Enum UFlowAssetParams::TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Enum::StaticStruct())) + { + const FFlowDataPinOutputProperty_Enum& EnumProp = Found->Get(); + return FFlowDataPinResult_Enum(EnumProp.Value, EnumProp.EnumClass); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Enum pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Enum(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Vector UFlowAssetParams::TrySupplyDataPinAsVector_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Vector::StaticStruct())) + { + const FFlowDataPinOutputProperty_Vector& VectorProp = Found->Get(); + return FFlowDataPinResult_Vector(VectorProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Vector pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Vector(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Rotator UFlowAssetParams::TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Rotator::StaticStruct())) + { + const FFlowDataPinOutputProperty_Rotator& RotatorProp = Found->Get(); + return FFlowDataPinResult_Rotator(RotatorProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Rotator pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Rotator(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Transform UFlowAssetParams::TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Transform::StaticStruct())) + { + const FFlowDataPinOutputProperty_Transform& TransformProp = Found->Get(); + return FFlowDataPinResult_Transform(TransformProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Transform pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Transform(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_GameplayTag UFlowAssetParams::TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_GameplayTag::StaticStruct())) + { + const FFlowDataPinOutputProperty_GameplayTag& TagProp = Found->Get(); + return FFlowDataPinResult_GameplayTag(TagProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for GameplayTag pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_GameplayTag(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_GameplayTagContainer UFlowAssetParams::TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_GameplayTagContainer::StaticStruct())) + { + const FFlowDataPinOutputProperty_GameplayTagContainer& ContainerProp = Found->Get(); + return FFlowDataPinResult_GameplayTagContainer(ContainerProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for GameplayTagContainer pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_GameplayTagContainer(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_InstancedStruct UFlowAssetParams::TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_InstancedStruct::StaticStruct())) + { + const FFlowDataPinOutputProperty_InstancedStruct& StructProp = Found->Get(); + return FFlowDataPinResult_InstancedStruct(StructProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for InstancedStruct pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_InstancedStruct(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Object UFlowAssetParams::TrySupplyDataPinAsObject_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Object::StaticStruct())) + { + const FFlowDataPinOutputProperty_Object& ObjectProp = Found->Get(); + return FFlowDataPinResult_Object(ObjectProp.GetObjectValue()); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Object pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Object(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Class UFlowAssetParams::TrySupplyDataPinAsClass_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Class::StaticStruct())) + { + const FFlowDataPinOutputProperty_Class& ClassProp = Found->Get(); + return FFlowDataPinResult_Class(ClassProp.GetResolvedClass()); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Class pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Class(EFlowDataPinResolveResult::FailedUnknownPin); +} \ No newline at end of file diff --git a/Source/Flow/Private/Asset/FlowAssetParamsTypes.cpp b/Source/Flow/Private/Asset/FlowAssetParamsTypes.cpp new file mode 100644 index 000000000..48043832b --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParamsTypes.cpp @@ -0,0 +1,11 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParamsTypes.h" +#include "Asset/FlowAssetParams.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParamsTypes) + +UFlowAssetParams* FFlowAssetParamsPtr::ResolveFlowAssetParams() const +{ + return AssetPtr.LoadSynchronous(); +} diff --git a/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp b/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp new file mode 100644 index 000000000..d2ad66b99 --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp @@ -0,0 +1,122 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParamsUtils.h" +#include "Asset/FlowAssetParamsTypes.h" +#include "Types/FlowDataPinProperties.h" +#include "FlowLogChannels.h" +#include "Misc/DateTime.h" +#include "HAL/FileManager.h" +#include "UObject/Class.h" +#include "UObject/UObjectGlobals.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParamsUtils) + +#if WITH_EDITOR +FDateTime FFlowAssetParamsUtils::GetLastSavedTimestampForObject(const UObject* Object) +{ + if (!Object) + { + return FDateTime::MinValue(); + } + + const FString PackagePath = Object->GetPathName(); + return IFileManager::Get().GetTimeStamp(*PackagePath); +} + +EFlowReconcilePropertiesResult FFlowAssetParamsUtils::CheckPropertiesMatch( + const TArray& PropertiesA, + const TArray& PropertiesB) +{ + if (PropertiesA.Num() != PropertiesB.Num()) + { + return EFlowReconcilePropertiesResult::Error_PropertyCountMismatch; + } + + for (int32 Index = 0; Index < PropertiesA.Num(); ++Index) + { + const FFlowNamedDataPinProperty& PropA = PropertiesA[Index]; + const FFlowNamedDataPinProperty& PropB = PropertiesB[Index]; + const UScriptStruct* ScriptStructA = PropA.DataPinProperty.GetScriptStruct(); + const UScriptStruct* ScriptStructB = PropB.DataPinProperty.GetScriptStruct(); + + if (PropA.Name != PropB.Name || + ScriptStructA != ScriptStructB || + !IsValid(ScriptStructA)) + { + return EFlowReconcilePropertiesResult::Error_PropertyTypeMismatch; + } + } + + return EFlowReconcilePropertiesResult::NoChanges; +} + +const FFlowNamedDataPinProperty* FFlowAssetParamsUtils::FindPropertyByGuid( + const TArray& Props, + const FGuid& Guid) +{ + for (const FFlowNamedDataPinProperty& Prop : Props) + { + if (Prop.Guid == Guid) + { + return &Prop; + } + } + + return nullptr; +} + +FFlowNamedDataPinProperty* FFlowAssetParamsUtils::FindPropertyByGuid( + TArray& Props, + const FGuid& Guid) +{ + for (FFlowNamedDataPinProperty& Prop : Props) + { + if (Prop.Guid == Guid) + { + return &Prop; + } + } + + return nullptr; +} + +bool FFlowAssetParamsUtils::ArePropertyArraysEqual( + const TArray& A, + const TArray& B) +{ + if (A.Num() != B.Num()) + { + return false; + } + + for (int32 Index = 0; Index < A.Num(); ++Index) + { + if (!ArePropertiesEqual(A[Index], B[Index])) + { + return false; + } + } + + return true; +} + +bool FFlowAssetParamsUtils::ArePropertiesEqual( + const FFlowNamedDataPinProperty& A, + const FFlowNamedDataPinProperty& B) +{ + if (A.Name != B.Name || A.Guid != B.Guid) + { + return false; + } + + const UScriptStruct* ScriptStructA = A.DataPinProperty.GetScriptStruct(); + const UScriptStruct* ScriptStructB = B.DataPinProperty.GetScriptStruct(); + if (ScriptStructA != ScriptStructB) + { + return false; + } + + return A.DataPinProperty == B.DataPinProperty; +} + +#endif diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 521ed3e36..9f52f9edb 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -5,8 +5,9 @@ #include "FlowLogChannels.h" #include "FlowSettings.h" #include "FlowSubsystem.h" - #include "AddOns/FlowNodeAddOn.h" +#include "Asset/FlowAssetParams.h" +#include "Asset/FlowAssetParamsUtils.h" #include "Interfaces/FlowDataPinGeneratorNodeInterface.h" #include "Nodes/FlowNodeBase.h" #include "Nodes/Graph/FlowNode_CustomInput.h" @@ -19,8 +20,18 @@ #include "Serialization/MemoryWriter.h" #if WITH_EDITOR +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" #include "Editor.h" #include "Editor/EditorEngine.h" +#include "Modules/ModuleManager.h" +#include "ObjectTools.h" +#include "SourceControlHelpers.h" +#include "UObject/ObjectSaveContext.h" +#include "UObject/Package.h" +#include "UObject/SavePackage.h" FString UFlowAsset::ValidationError_NodeClassNotAllowed = TEXT("Node class {0} is not allowed in this asset."); FString UFlowAsset::ValidationError_NullNodeInstance = TEXT("Node with GUID {0} is NULL"); @@ -101,6 +112,127 @@ void UFlowAsset::PostLoad() { UnregisterNode(Guid); } + + ReconcileBaseAssetParams(FFlowAssetParamsUtils::GetLastSavedTimestampForObject(this)); +} + +void UFlowAsset::PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext) +{ + ReconcileBaseAssetParams(FDateTime::Now()); +} + +void UFlowAsset::ReconcileBaseAssetParams(const FDateTime& AssetLastSavedTimestamp) +{ + if (BaseAssetParams.AssetPtr.IsNull()) + { + return; + } + + UFlowAssetParams* BaseAssetParamsPtr = BaseAssetParams.AssetPtr.LoadSynchronous(); + if (!IsValid(BaseAssetParamsPtr)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to load BaseAssetParams: %s"), *BaseAssetParams.AssetPtr.ToString()); + return; + } + + IFlowNamedPropertiesSupplierInterface* NamedPropertiesSupplier = Cast(GetDefaultEntryNode()); + if (!NamedPropertiesSupplier) + { + UE_LOG(LogFlow, Error, TEXT("No NamedPropertiesSupplier (e.g., Start node) found in FlowAsset: %s"), *GetPathName()); + return; + } + + TArray& MutableStartNodeProperties = NamedPropertiesSupplier->GetMutableNamedProperties(); + const EFlowReconcilePropertiesResult ReconcileResult = + BaseAssetParamsPtr->ReconcilePropertiesWithStartNode(AssetLastSavedTimestamp, this, MutableStartNodeProperties); + + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile BaseAssetParams for %s: %s"), + *BaseAssetParamsPtr->GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } +} + +UFlowAssetParams* UFlowAsset::GenerateParamsFromStartNode() +{ + if (BaseAssetParams.AssetPtr.IsValid()) + { + UE_LOG(LogFlow, Warning, TEXT("BaseAssetParams already exists for %s: %s"), *GetPathName(), *BaseAssetParams.AssetPtr.ToString()); + return BaseAssetParams.AssetPtr.LoadSynchronous(); + } + + // Get the Start node + IFlowNamedPropertiesSupplierInterface* NamedPropertiesSupplier = Cast(GetDefaultEntryNode()); + if (!NamedPropertiesSupplier) + { + UE_LOG(LogFlow, Error, TEXT("No valid Start node found for generating params in %s"), *GetPathName()); + return nullptr; + } + + // Determine the params asset name + const FString ParamsAssetName = GenerateParamsAssetName(); + if (ParamsAssetName.IsEmpty()) + { + UE_LOG(LogFlow, Error, TEXT("Generated empty params asset name for %s"), *GetPathName()); + return nullptr; + } + + // Create the params asset + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + const FString PackagePath = FPackageName::GetLongPackagePath(GetPackage()->GetPathName()); + FString UniquePackageName, UniqueAssetName; + AssetToolsModule.Get().CreateUniqueAssetName(PackagePath + TEXT("/") + ParamsAssetName, TEXT(""), UniquePackageName, UniqueAssetName); + + UFlowAssetParams* NewParams = Cast( + AssetToolsModule.Get().CreateAsset(UniqueAssetName, PackagePath, UFlowAssetParams::StaticClass(), nullptr)); + if (!IsValid(NewParams)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to create Flow Asset Params: %s"), *UniqueAssetName); + return nullptr; + } + + // Reconfigure with the new properties + NewParams->ConfigureFlowAssetParams(this, nullptr, NamedPropertiesSupplier->GetMutableNamedProperties()); + + // Source control integration + if (USourceControlHelpers::IsAvailable()) + { + const FString FileName = USourceControlHelpers::PackageFilename(NewParams->GetPathName()); + if (!USourceControlHelpers::CheckOutOrAddFile(FileName)) + { + UE_LOG(LogFlow, Warning, TEXT("Failed to check out/add %s; saved in-memory only"), *NewParams->GetPathName()); + } + } + + // Assign to BaseAssetParams and sync Content Browser + BaseAssetParams.AssetPtr = NewParams; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistryModule.Get().AssetCreated(NewParams); + + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + TArray AssetsToSync = { NewParams }; + ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true); + + return NewParams; +} + +FString UFlowAsset::GenerateParamsAssetName() const +{ + const FString FlowAssetName = GetName(); + + const int32 UnderscoreIndex = FlowAssetName.Find(TEXT("_"), ESearchCase::CaseSensitive); + + if (UnderscoreIndex != INDEX_NONE) + { + const FString Prefix = FlowAssetName.Left(UnderscoreIndex); + const FString Suffix = FlowAssetName.Mid(UnderscoreIndex + 1); + return FString::Printf(TEXT("%sParams_%s"), *Prefix, *Suffix); + } + else + { + return FlowAssetName + TEXT("Params"); + } } EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog) diff --git a/Source/Flow/Private/FlowComponent.cpp b/Source/Flow/Private/FlowComponent.cpp index fc1dc359e..0f4a66d01 100644 --- a/Source/Flow/Private/FlowComponent.cpp +++ b/Source/Flow/Private/FlowComponent.cpp @@ -2,6 +2,7 @@ #include "FlowComponent.h" +#include "Asset/FlowAssetParams.h" #include "FlowAsset.h" #include "FlowLogChannels.h" #include "FlowSettings.h" @@ -450,7 +451,8 @@ void UFlowComponent::StartRootFlow() { VerifyIdentityTags(); - FlowSubsystem->StartRootFlow(this, RootFlow, bAllowMultipleInstances); + TScriptInterface RootFlowParamsAsInterface = RootFlowParams.ResolveFlowAssetParams(); + FlowSubsystem->StartRootFlow(this, RootFlow, RootFlowParamsAsInterface, bAllowMultipleInstances); } } } diff --git a/Source/Flow/Private/FlowSubsystem.cpp b/Source/Flow/Private/FlowSubsystem.cpp index 946610632..d8f934a03 100644 --- a/Source/Flow/Private/FlowSubsystem.cpp +++ b/Source/Flow/Private/FlowSubsystem.cpp @@ -76,15 +76,13 @@ void UFlowSubsystem::AbortActiveFlows() RootInstances.Empty(); } -void UFlowSubsystem::StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const bool bAllowMultipleInstances /* = true */) +void UFlowSubsystem::StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, TScriptInterface DataPinValueSupplier, const bool bAllowMultipleInstances) { if (FlowAsset) { if (UFlowAsset* NewFlow = CreateRootFlow(Owner, FlowAsset, bAllowMultipleInstances)) { - // todo: (gtaylor) In the future, we may want to provide a way to set a data pin value supplier - // for the root flow graph. - NewFlow->StartFlow(); + NewFlow->StartFlow(DataPinValueSupplier.GetInterface()); } } #if WITH_EDITOR @@ -664,4 +662,4 @@ void UFlowSubsystem::FindComponents(const FGameplayTagContainer& Tags, const EGa } } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/Flow/Private/Interfaces/FlowAssetProviderInterface.cpp b/Source/Flow/Private/Interfaces/FlowAssetProviderInterface.cpp new file mode 100644 index 000000000..bbf8e4583 --- /dev/null +++ b/Source/Flow/Private/Interfaces/FlowAssetProviderInterface.cpp @@ -0,0 +1,9 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Interfaces/FlowAssetProviderInterface.h" +#include "FlowAsset.h" + +UFlowAsset* IFlowAssetProviderInterface::ProvideFlowAsset() const +{ + return Execute_K2_ProvideFlowAsset(Cast(this)); +} diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 7c2bdcbdb..2f70c55b0 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -319,39 +319,6 @@ UObject* UFlowNodeBase::TryGetRootFlowObjectOwner() const return nullptr; } -IFlowOwnerInterface* UFlowNodeBase::GetFlowOwnerInterface() const -{ - const UFlowAsset* FlowAsset = GetFlowAsset(); - if (!IsValid(FlowAsset)) - { - return nullptr; - } - - const UClass* ExpectedOwnerClass = FlowAsset->GetExpectedOwnerClass(); - if (!IsValid(ExpectedOwnerClass)) - { - return nullptr; - } - - UObject* RootFlowOwner = FlowAsset->GetOwner(); - if (!IsValid(RootFlowOwner)) - { - return nullptr; - } - - if (IFlowOwnerInterface* FlowOwnerInterface = TryGetFlowOwnerInterfaceFromRootFlowOwner(*RootFlowOwner, *ExpectedOwnerClass)) - { - return FlowOwnerInterface; - } - - if (IFlowOwnerInterface* FlowOwnerInterface = TryGetFlowOwnerInterfaceActor(*RootFlowOwner, *ExpectedOwnerClass)) - { - return FlowOwnerInterface; - } - - return nullptr; -} - TArray UFlowNodeBase::BuildFlowNodeBaseAncestorChain(UFlowNodeBase& FromFlowNodeBase, bool bIncludeFromFlowNodeBase) { TArray AncestorChain; @@ -374,47 +341,6 @@ TArray UFlowNodeBase::BuildFlowNodeBaseAncestorChain(UFlowNodeBa return AncestorChain; } -IFlowOwnerInterface* UFlowNodeBase::TryGetFlowOwnerInterfaceFromRootFlowOwner(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass) -{ - const UClass* RootFlowOwnerClass = RootFlowOwner.GetClass(); - if (!IsValid(RootFlowOwnerClass)) - { - return nullptr; - } - - if (!RootFlowOwnerClass->IsChildOf(&ExpectedOwnerClass)) - { - return nullptr; - } - - // If the immediate owner is the expected class type, return its FlowOwnerInterface - return CastChecked(&RootFlowOwner); -} - -IFlowOwnerInterface* UFlowNodeBase::TryGetFlowOwnerInterfaceActor(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass) -{ - // Special case if the immediate owner is a component, also consider the component's owning actor - const UActorComponent* FlowComponent = Cast(&RootFlowOwner); - if (!IsValid(FlowComponent)) - { - return nullptr; - } - - AActor* ActorOwner = FlowComponent->GetOwner(); - if (!IsValid(ActorOwner)) - { - return nullptr; - } - - const UClass* ActorOwnerClass = ActorOwner->GetClass(); - if (!ActorOwnerClass->IsChildOf(&ExpectedOwnerClass)) - { - return nullptr; - } - - return CastChecked(ActorOwner); -} - EFlowAddOnAcceptResult UFlowNodeBase::AcceptFlowNodeAddOnChild_Implementation( const UFlowNodeAddOn* AddOnTemplate, const TArray& AdditionalAddOnsToAssumeAreChildren) const diff --git a/Source/Flow/Public/Asset/FlowAssetParams.h b/Source/Flow/Public/Asset/FlowAssetParams.h new file mode 100644 index 000000000..d8053d108 --- /dev/null +++ b/Source/Flow/Public/Asset/FlowAssetParams.h @@ -0,0 +1,96 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Engine/DataAsset.h" +#include "Types/FlowDataPinProperties.h" +#include "Interfaces/FlowDataPinValueSupplierInterface.h" +#include "Interfaces/FlowAssetProviderInterface.h" +#include "Asset/FlowAssetParamsTypes.h" + +#include "FlowAssetParams.generated.h" + +class UFlowAsset; + +/** +* Data asset for storing Flow Graph Start node parameters, supporting external configuration. +*/ +UCLASS(BlueprintType) +class FLOW_API UFlowAssetParams : public UDataAsset, public IFlowDataPinValueSupplierInterface, public IFlowAssetProviderInterface +{ + GENERATED_BODY() + +public: +#if WITH_EDITORONLY_DATA + // Reference to the associated Flow Asset. + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = FlowAssetParams) + TSoftObjectPtr OwnerFlowAsset; + + // Reference to the "Parent" params object to inherit from (if any). + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = FlowAssetParams) + FFlowAssetParamsPtr ParentParams; + + // Array of properties synchronized with the Start node (local adds/overrides; effective flattened via ReconcilePropertiesWithParentParams). + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = FlowAssetParams, meta = (EditFixedSize)) + TArray Properties; +#endif + + UPROPERTY() + TMap> PropertyMap; + +public: + // UObject interface + virtual void PostLoad() override; + virtual void Serialize(FArchive& Ar) override; + // -- + + // IFlowDataPinValueSupplierInterface + virtual bool CanSupplyDataPinValues_Implementation() const override; + virtual FFlowDataPinResult_Bool TrySupplyDataPinAsBool_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Int TrySupplyDataPinAsInt_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Float TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Name TrySupplyDataPinAsName_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_String TrySupplyDataPinAsString_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Text TrySupplyDataPinAsText_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Enum TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Vector TrySupplyDataPinAsVector_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Rotator TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Transform TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_GameplayTag TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_GameplayTagContainer TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_InstancedStruct TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Object TrySupplyDataPinAsObject_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Class TrySupplyDataPinAsClass_Implementation(const FName& PinName) const override; + // -- + + // IFlowAssetProviderInterface + virtual UFlowAsset* ProvideFlowAsset() const override; + // -- + +#if WITH_EDITOR + // UObject interface + virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override; + // -- + + // Generates properties from the associated Start node or updates Start node from params. + EFlowReconcilePropertiesResult ReconcilePropertiesWithStartNode( + const FDateTime& FlowAssetLastSaveTimeStamp, + const TSoftObjectPtr& InOwnerFlowAsset, + TArray& MutablePropertiesFromStartNode); + + void ConfigureFlowAssetParams(TSoftObjectPtr OwnerAsset, TSoftObjectPtr InParentParams, const TArray& InProperties); + +protected: + + // Updates properties from ParentParams, handling inheritance and name enforcement. + EFlowReconcilePropertiesResult ReconcilePropertiesWithParentParams(); + + bool TryCheckOutFromSourceControl() const; + + EFlowReconcilePropertiesResult CheckForParentCycle() const; + + void ModifyAndRebuildPropertiesMap(); + + void RebuildPropertiesMap(); +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/Asset/FlowAssetParamsTypes.h b/Source/Flow/Public/Asset/FlowAssetParamsTypes.h new file mode 100644 index 000000000..6d8ecf13d --- /dev/null +++ b/Source/Flow/Public/Asset/FlowAssetParamsTypes.h @@ -0,0 +1,67 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowEnumUtils.h" +#include "UObject/SoftObjectPtr.h" + +#include "FlowAssetParamsTypes.generated.h" + +class UFlowAssetParams; + +// Result of reconciling Flow Asset Params with Start node or SuperParams. +UENUM(BlueprintType) +enum class EFlowReconcilePropertiesResult : uint8 +{ + NoChanges, + + ParamsPropertiesUpdated, + AssetPropertyValuesUpdated, + + Error_InvalidAsset, + Error_PropertyCountMismatch, + Error_PropertyTypeMismatch, + Error_CyclicInheritance, + Error_UnloadableParent, + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), + + SuccessFirst = NoChanges UMETA(Hidden), + SuccessLast = AssetPropertyValuesUpdated UMETA(Hidden), + + ModifiedFirst = ParamsPropertiesUpdated UMETA(Hidden), + ModifiedLast = AssetPropertyValuesUpdated UMETA(Hidden), + + ErrorFirst = Error_InvalidAsset UMETA(Hidden), + ErrorLast = Error_UnloadableParent UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowReconcilePropertiesResult) + +namespace EFlowReconcilePropertiesResult_Classifiers +{ + FORCEINLINE bool IsSuccessResult(EFlowReconcilePropertiesResult Result) { return FLOW_IS_ENUM_IN_SUBRANGE(Result, EFlowReconcilePropertiesResult::Success); } + FORCEINLINE bool IsModifiedResult(EFlowReconcilePropertiesResult Result) { return FLOW_IS_ENUM_IN_SUBRANGE(Result, EFlowReconcilePropertiesResult::Modified); } + FORCEINLINE bool IsErrorResult(EFlowReconcilePropertiesResult Result) { return FLOW_IS_ENUM_IN_SUBRANGE(Result, EFlowReconcilePropertiesResult::Error); } +} + +// Wrapper for TSoftObjectPtr to enable editor customization. +// +// Supported metadata tags: +// - ShowCreateNew - Should we show the "Create New" button? +// - HideChildParams - When showing a chooser, should we hide "Child" params or not? (Child params have a non-null ParentParams) +USTRUCT(BlueprintType) +struct FFlowAssetParamsPtr +{ + GENERATED_BODY() + + FFlowAssetParamsPtr() = default; + FFlowAssetParamsPtr(TSoftObjectPtr InAssetParamsPtr) : AssetPtr(InAssetParamsPtr) { } + + UFlowAssetParams* ResolveFlowAssetParams() const; + + // Reference to the Flow Asset Params. + UPROPERTY(EditAnywhere, Category = FlowAssetParams, meta = (EditAssetInline)) + TSoftObjectPtr AssetPtr; +}; diff --git a/Source/Flow/Public/Asset/FlowAssetParamsUtils.h b/Source/Flow/Public/Asset/FlowAssetParamsUtils.h new file mode 100644 index 000000000..9c1f966a1 --- /dev/null +++ b/Source/Flow/Public/Asset/FlowAssetParamsUtils.h @@ -0,0 +1,44 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Misc/DateTime.h" +#include "Asset/FlowAssetParamsTypes.h" + +#include "FlowAssetParamsUtils.generated.h" + +class UObject; +struct FFlowNamedDataPinProperty; + +/** +* Utility functions for Flow Asset Params reconciliation and validation. +*/ +USTRUCT() +struct FLOW_API FFlowAssetParamsUtils +{ + GENERATED_BODY() + +#if WITH_EDITOR + static FDateTime GetLastSavedTimestampForObject(const UObject* Object); + + static EFlowReconcilePropertiesResult CheckPropertiesMatch( + const TArray& PropertiesA, + const TArray& PropertiesB); + + static const FFlowNamedDataPinProperty* FindPropertyByGuid( + const TArray& Props, + const FGuid& Guid); + + static FFlowNamedDataPinProperty* FindPropertyByGuid( + TArray& Props, + const FGuid& Guid); + + static bool ArePropertyArraysEqual( + const TArray& A, + const TArray& B); + + static bool ArePropertiesEqual( + const FFlowNamedDataPinProperty& A, + const FFlowNamedDataPinProperty& B); +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index 38b9e206d..e001873c9 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -4,6 +4,7 @@ #include "FlowSave.h" #include "FlowTypes.h" +#include "Asset/FlowAssetParamsTypes.h" #include "Nodes/FlowNode.h" #if WITH_EDITOR @@ -21,6 +22,7 @@ class UFlowSubsystem; class UEdGraph; class UEdGraphNode; class UFlowAsset; +class UFlowAssetParams; #if !UE_BUILD_SHIPPING DECLARE_DELEGATE(FFlowGraphEvent); @@ -457,6 +459,28 @@ class FLOW_API UFlowAsset : public UObject UFUNCTION(BlueprintNativeEvent, Category = "SaveGame") bool IsBoundToWorld(); +////////////////////////////////////////////////////////////////////////// +// FlowAssetParams support (Start node params for a flow graph) + + // Default parameters asset for this Flow Asset (optional) + UPROPERTY(EditAnywhere, Category = FlowAssetParams, meta = (ShowCreateNew, HideChildParams)) + FFlowAssetParamsPtr BaseAssetParams; + +#if WITH_EDITOR + // Called before saving the asset. + virtual void PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext) override; + + // Generates a new params asset from the Start node. + UFlowAssetParams* GenerateParamsFromStartNode(); + + // Generates the FlowAssetParams name for the 'base' (root) asset, used when creating the params asset + virtual FString GenerateParamsAssetName() const; + +protected: + + void ReconcileBaseAssetParams(const FDateTime& AssetLastSavedTimestamp); +#endif + ////////////////////////////////////////////////////////////////////////// // Utils diff --git a/Source/Flow/Public/FlowComponent.h b/Source/Flow/Public/FlowComponent.h index d579e2568..333779911 100644 --- a/Source/Flow/Public/FlowComponent.h +++ b/Source/Flow/Public/FlowComponent.h @@ -7,7 +7,8 @@ #include "FlowSave.h" #include "FlowTypes.h" -#include "Interfaces/FlowOwnerInterface.h" +#include "Interfaces/FlowAssetProviderInterface.h" +#include "Asset/FlowAssetParamsTypes.h" #include "FlowComponent.generated.h" class UFlowAsset; @@ -42,7 +43,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FFlowComponentDynamicNotify, class * Base component of Flow System - makes possible to communicate between Actor, Flow Subsystem and Flow Graphs */ UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) -class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterface +class FLOW_API UFlowComponent : public UActorComponent, public IFlowAssetProviderInterface { GENERATED_UCLASS_BODY() @@ -167,6 +168,10 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RootFlow") TObjectPtr RootFlow; + // Flow Asset Params to use as the data pin value supplier for the Root Flow + UPROPERTY(EditAnywhere, Category = "RootFlow") + FFlowAssetParamsPtr RootFlowParams; + // If true, component will start Root Flow on Begin Play UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "RootFlow") bool bAutoStartRootFlow; @@ -197,6 +202,10 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa UFUNCTION(BlueprintPure, Category = "RootFlow", meta = (DeprecatedFunction, DeprecationMessage="Use GetRootInstances() instead.")) UFlowAsset* GetRootFlowInstance() const; + // IFlowAssetProviderInterface + virtual UFlowAsset* ProvideFlowAsset() const override { return RootFlow; } + // -- + ////////////////////////////////////////////////////////////////////////// // Custom Input and Output events @@ -253,4 +262,4 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa public: UFlowSubsystem* GetFlowSubsystem() const; bool IsFlowNetMode(const EFlowNetMode NetMode) const; -}; +}; \ No newline at end of file diff --git a/Source/Flow/Public/FlowSubsystem.h b/Source/Flow/Public/FlowSubsystem.h index 4852cb519..01790fcd6 100644 --- a/Source/Flow/Public/FlowSubsystem.h +++ b/Source/Flow/Public/FlowSubsystem.h @@ -11,6 +11,7 @@ class UFlowAsset; class UFlowNode_SubGraph; +class IFlowDataPinValueSupplierInterface; DECLARE_DYNAMIC_MULTICAST_DELEGATE(FSimpleFlowEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSimpleFlowComponentEvent, UFlowComponent*, Component); @@ -73,7 +74,7 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem /* Start the root Flow, graph that will eventually instantiate next Flow Graphs through the SubGraph node */ UFUNCTION(BlueprintCallable, Category = "FlowSubsystem", meta = (DefaultToSelf = "Owner")) - virtual void StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const bool bAllowMultipleInstances = true); + virtual void StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, TScriptInterface DataPinValueSupplier, const bool bAllowMultipleInstances = true); virtual UFlowAsset* CreateRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const bool bAllowMultipleInstances = true, const FString& NewInstanceName = FString()); @@ -431,4 +432,4 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem private: void FindComponents(const FGameplayTag& Tag, const bool bExactMatch, TArray>& OutComponents) const; void FindComponents(const FGameplayTagContainer& Tags, const EGameplayContainerMatchType MatchType, const bool bExactMatch, TSet>& OutComponents) const; -}; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Interfaces/FlowAssetProviderInterface.h b/Source/Flow/Public/Interfaces/FlowAssetProviderInterface.h new file mode 100644 index 000000000..29d9bc95a --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowAssetProviderInterface.h @@ -0,0 +1,29 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" + +#include "FlowAssetProviderInterface.generated.h" + +class UFlowAsset; + +// Interface to define a UFlowAsset provider. +// This is used for filtering in FFlowAssetParamsPtrCustomization +UINTERFACE(MinimalAPI, Blueprintable, DisplayName = "Flow Asset Provider Interface") +class UFlowAssetProviderInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowAssetProviderInterface +{ + GENERATED_BODY() + +public: + + // Provide a FlowAsset for use in FFlowAssetParamsPtr resolution + UFUNCTION(BlueprintImplementableEvent, Category = FlowAssetParams, DisplayName = "ProvideFlowAsset") + UFlowAsset* K2_ProvideFlowAsset() const; + virtual UFlowAsset* ProvideFlowAsset() const; +}; diff --git a/Source/Flow/Public/Interfaces/FlowNamedPropertiesSupplierInterface.h b/Source/Flow/Public/Interfaces/FlowNamedPropertiesSupplierInterface.h new file mode 100644 index 000000000..b41bd87ac --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowNamedPropertiesSupplierInterface.h @@ -0,0 +1,30 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" + +#include "FlowNamedPropertiesSupplierInterface.generated.h" + +struct FFlowNamedDataPinProperty; + +UINTERFACE(Blueprintable) +class FLOW_API UFlowNamedPropertiesSupplierInterface : public UInterface +{ + GENERATED_BODY() +}; + +/** + * Interface for Flow nodes that supply named properties, such as Start or DefineProperties nodes. + */ +class FLOW_API IFlowNamedPropertiesSupplierInterface +{ + GENERATED_BODY() + +public: + +#if WITH_EDITOR + // Returns the array of named properties defined by this node. + virtual TArray& GetMutableNamedProperties() = 0; +#endif +}; diff --git a/Source/Flow/Public/Interfaces/FlowOwnerInterface.h b/Source/Flow/Public/Interfaces/FlowOwnerInterface.h index 28a00f5e0..5c17b1a62 100644 --- a/Source/Flow/Public/Interfaces/FlowOwnerInterface.h +++ b/Source/Flow/Public/Interfaces/FlowOwnerInterface.h @@ -6,8 +6,8 @@ #include "FlowOwnerInterface.generated.h" -// (optional) interface to enable a Flow owner object to execute CallOwnerFunction nodes -UINTERFACE(MinimalAPI, Blueprintable, BlueprintType) +// (deprecated) interface to enable a Flow owner object to execute CallOwnerFunction nodes +UINTERFACE(MinimalAPI) class UFlowOwnerInterface : public UInterface { GENERATED_BODY() diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h index c4d8a4c58..a4d0f33b5 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h @@ -11,7 +11,6 @@ #include "FlowNode_ExecuteComponent.generated.h" // Forward Declarations -class IFlowOwnerInterface; class UFlowInjectComponentsManager; UENUM() diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index b9f9d5169..6c54a0a8d 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -18,7 +18,6 @@ class UFlowNode; class UFlowNodeAddOn; class UFlowSubsystem; class UEdGraphNode; -class IFlowOwnerInterface; class IFlowDataPinValueSupplierInterface; struct FFlowPin; struct FFlowNamedDataPinProperty; @@ -157,17 +156,8 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintCallable, Category = "FlowNode") UObject* TryGetRootFlowObjectOwner() const; - // Returns the IFlowOwnerInterface for the owner object (if implemented) - // NOTE - will consider a UActorComponent owner's owning actor if appropriate - IFlowOwnerInterface* GetFlowOwnerInterface() const; - static TArray BuildFlowNodeBaseAncestorChain(UFlowNodeBase& FromFlowNodeBase, bool bIncludeFromFlowNodeBase); -protected: - // Helper functions for GetFlowOwnerInterface() - static IFlowOwnerInterface* TryGetFlowOwnerInterfaceFromRootFlowOwner(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass); - static IFlowOwnerInterface* TryGetFlowOwnerInterfaceActor(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass); - ////////////////////////////////////////////////////////////////////////// // AddOn support diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h index 0573362bc..c40ed2c69 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h @@ -3,6 +3,7 @@ #pragma once #include "Interfaces/FlowDataPinGeneratorNodeInterface.h" +#include "Interfaces/FlowNamedPropertiesSupplierInterface.h" #include "Nodes/FlowNode.h" #include "Types/FlowDataPinProperties.h" @@ -12,7 +13,10 @@ * FlowNode to define data pin property literals for use connecting to data pin inputs in a flow graph */ UCLASS(Blueprintable, meta = (DisplayName = "Define Properties")) -class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPinGeneratorNodeInterface +class FLOW_API UFlowNode_DefineProperties + : public UFlowNode + , public IFlowDataPinGeneratorNodeInterface + , public IFlowNamedPropertiesSupplierInterface { GENERATED_UCLASS_BODY() @@ -35,6 +39,10 @@ class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPi // IFlowDataPinGeneratorNodeInterface virtual void AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const override; // -- + + // IFlowNamedPropertiesSupplierInterface + virtual TArray& GetMutableNamedProperties() override { return NamedProperties; } + // -- #endif bool TryFormatTextWithNamedPropertiesAsParameters(const FText& FormatText, FText& OutFormattedText) const; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h index 5f7f89f0d..24cbed357 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h @@ -15,7 +15,7 @@ class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGenerat { GENERATED_UCLASS_BODY() -public: +public: friend class UFlowAsset; friend class FFlowNode_SubGraphDetails; friend class UFlowSubsystem; @@ -27,6 +27,8 @@ class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGenerat UPROPERTY(EditAnywhere, Category = "Graph") TSoftObjectPtr Asset; + // TODO (gtaylor) Create FlowAssetParams option for the Subgraph & reconcile with connected input pins' values + /* * Allow to create instance of the same Flow Asset as the asset containing this node * Enabling it may cause an infinite loop, if graph would keep creating copies of itself diff --git a/Source/Flow/Public/Types/FlowDataPinProperties.h b/Source/Flow/Public/Types/FlowDataPinProperties.h index a40a0e24f..75407e7a9 100644 --- a/Source/Flow/Public/Types/FlowDataPinProperties.h +++ b/Source/Flow/Public/Types/FlowDataPinProperties.h @@ -3,6 +3,7 @@ #pragma once #include "GameplayTagContainer.h" +#include "Kismet/BlueprintFunctionLibrary.h" #include "StructUtils/InstancedStruct.h" #include "UObject/Class.h" @@ -17,7 +18,7 @@ struct FFlowDataPinProperty { GENERATED_BODY() - FFlowDataPinProperty() { } + FFlowDataPinProperty() = default; virtual ~FFlowDataPinProperty() { } @@ -44,11 +45,86 @@ struct FFlowDataPinProperty return UnrealType; } - else + + return StructProperty->Struct; + } +#endif +}; + +// Wrapper for FFlowDataPinProperty that is used for flow nodes that add +// dynamic properties, with associated data pins, on the flow node instance +// (as opposed to C++ or blueprint compile-time). +USTRUCT(BlueprintType, DisplayName = "Flow Named DataPin Property") +struct FFlowNamedDataPinProperty +{ + GENERATED_BODY() + +public: + // Name of this instanced property + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins, meta = (EditCondition = "bMayChangeNameAndType", HideEditConditionToggle)) + FName Name = NAME_None; + + // DataPinProperty payload + UPROPERTY(EditAnywhere, Category = DataPins, meta = (ExcludeBaseStruct, NoClear)) + TInstancedStruct DataPinProperty; + +#if WITH_EDITORONLY_DATA + // Unique identifier for property tracking + UPROPERTY() + FGuid Guid = FGuid::NewGuid(); + + // Tracks if this property overrides its super (auto-clears if matches super) + UPROPERTY() + bool bIsOverride = false; + + // TODO (gtaylor) Does not currently police the type, + // because that prevents the instanced struct contents being edited as well, + // which is not what we want from this feature. + // Will try to fix next pass on the details customization. + UPROPERTY() + bool bMayChangeNameAndType = true; +#endif + +public: + FFlowNamedDataPinProperty() = default; + + bool IsValid() const { return Name != NAME_None && DataPinProperty.GetPtr() != nullptr; } + + bool IsInputProperty() const; + bool IsOutputProperty() const; + +#if WITH_EDITOR + FFlowPin CreateFlowPin() const { return FFlowDataPinProperty::CreateFlowPin(Name, DataPinProperty); } + + FLOW_API FText BuildHeaderText() const; + + void ConfigureForFlowAssetParams() + { + bIsOverride = false; + bMayChangeNameAndType = false; + } + + void ConfigureForFlowAssetStartNode() + { + bIsOverride = false; + bMayChangeNameAndType = true; + } + + static void ConfigurePropertiesForFlowAssetParams(TArray& MutableProperties) + { + for (FFlowNamedDataPinProperty& Property : MutableProperties) + { + Property.ConfigureForFlowAssetParams(); + } + } + static void ConfigurePropertiesForFlowAssetStartNode(TArray& MutableProperties) + { + for (FFlowNamedDataPinProperty& Property : MutableProperties) { - return StructProperty->Struct; + Property.ConfigureForFlowAssetStartNode(); } } + #endif }; @@ -446,40 +522,6 @@ struct FFlowDataPinOutputProperty_Class : public FFlowDataPinProperty UClass* GetResolvedClass() const { return Value.ResolveClass(); } }; -// Wrapper for FFlowDataPinProperty that is used for flow nodes that add -// dynamic properties, with associated data pins, on the flow node instance -// (as opposed to C++ or blueprint compile-time). -USTRUCT(BlueprintType, DisplayName = "Flow Named DataPin Property") -struct FFlowNamedDataPinProperty -{ - GENERATED_BODY() - -public: - - // Name of this instanced property - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - FName Name; - - // DataPinProperty payload - UPROPERTY(EditAnywhere, Category = DataPins, meta = (ExcludeBaseStruct, NoClear)) - TInstancedStruct DataPinProperty; - -public: - - FFlowNamedDataPinProperty() { } - - bool IsValid() const { return Name != NAME_None && DataPinProperty.GetPtr() != nullptr; } - - bool IsInputProperty() const; - bool IsOutputProperty() const; - -#if WITH_EDITOR - FFlowPin CreateFlowPin() const { return FFlowDataPinProperty::CreateFlowPin(Name, DataPinProperty); } - - FLOW_API FText BuildHeaderText() const; -#endif // WITH_EDITOR -}; - // Wrapper-structs for a blueprint defaulted input pin types // "Hidden" to keep them out of the TInstancedStruct selection list (but they can still be authored as properties in blueprint) // "DefaultForInputFlowPin" to change them to an Defaulted-Input property (rather than an output property) diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp index 03a513c6d..df88d22cb 100644 --- a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp +++ b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp @@ -30,7 +30,7 @@ TConstArrayView UAssetDefinition_FlowAsset::GetAssetCategori { if (UFlowGraphSettings::Get()->bExposeFlowAssetCreation) { - static const auto Categories = {FFLowAssetCategoryPaths::Flow}; + static const auto Categories = {FFlowAssetCategoryPaths::Flow}; return Categories; } diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp new file mode 100644 index 000000000..c42d98ea7 --- /dev/null +++ b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp @@ -0,0 +1,152 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/AssetDefinition_FlowAssetParams.h" +#include "Asset/FlowAssetParams.h" +#include "FlowAsset.h" +#include "FlowEditorLogChannels.h" +#include "FlowEditorModule.h" +#include "Types/FlowDataPinProperties.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "ContentBrowserMenuContexts.h" +#include "ContentBrowserModule.h" +#include "FileHelpers.h" +#include "IContentBrowserSingleton.h" +#include "SourceControlHelpers.h" +#include "ToolMenus.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(AssetDefinition_FlowAssetParams) + +#define LOCTEXT_NAMESPACE "AssetDefinition_FlowAssetParams" + +FText UAssetDefinition_FlowAssetParams::GetAssetDisplayName() const +{ + return LOCTEXT("GetAssetDisplayName", "Flow Asset Params"); +} + +FLinearColor UAssetDefinition_FlowAssetParams::GetAssetColor() const +{ + return FLinearColor(255, 196, 128); +} + +TSoftClassPtr UAssetDefinition_FlowAssetParams::GetAssetClass() const +{ + return UFlowAssetParams::StaticClass(); +} + +TConstArrayView UAssetDefinition_FlowAssetParams::GetAssetCategories() const +{ + static const auto Categories = { FFlowAssetCategoryPaths::Flow }; + return Categories; +} + +FAssetSupportResponse UAssetDefinition_FlowAssetParams::CanLocalize(const FAssetData& InAsset) const +{ + return FAssetSupportResponse::Supported(); +} + +namespace MenuExtension_FlowAssetParams +{ + static void ExecuteCreateChildParams(const FToolMenuContext& InContext) + { + const UContentBrowserAssetContextMenuContext* Context = UContentBrowserAssetContextMenuContext::FindContextWithAssets(InContext); + if (!Context) + { + UE_LOG(LogFlowEditor, Warning, TEXT("No valid context for Create Child Params action")); + return; + } + + const TArray& SelectedParams = Context->LoadSelectedObjects(); + if (SelectedParams.Num() != 1) + { + UE_LOG(LogFlowEditor, Warning, TEXT("Create Child Params requires exactly one selected Flow Asset Params")); + return; + } + + UFlowAssetParams* ParentParams = SelectedParams[0]; + if (!IsValid(ParentParams)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Invalid Flow Asset Params selected for Create Child Params")); + return; + } + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + const FString PackagePath = FPackageName::GetLongPackagePath(ParentParams->GetPackage()->GetPathName()); + const FString BaseAssetName = ParentParams->GetName(); + + FString UniquePackageName, UniqueAssetName; + AssetToolsModule.Get().CreateUniqueAssetName(PackagePath + TEXT("/") + BaseAssetName, TEXT(""), UniquePackageName, UniqueAssetName); + if (UniqueAssetName.IsEmpty()) + { + UE_LOG(LogFlowEditor, Error, TEXT("Failed to generate unique asset name for child params of %s"), *BaseAssetName); + return; + } + + UFlowAssetParams* NewParams = Cast( + AssetToolsModule.Get().CreateAsset(UniqueAssetName, PackagePath, ParentParams->GetClass(), nullptr)); + if (!IsValid(NewParams)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Failed to create child Flow Asset Params: %s"), *UniqueAssetName); + return; + } + + if (USourceControlHelpers::IsAvailable()) + { + const FString FileName = USourceControlHelpers::PackageFilename(NewParams->GetPathName()); + if (!USourceControlHelpers::CheckOutOrAddFile(FileName)) + { + UE_LOG(LogFlowEditor, Warning, TEXT("Failed to check out/add %s; saved in-memory only"), *NewParams->GetPathName()); + } + } + + NewParams->ConfigureFlowAssetParams(ParentParams->OwnerFlowAsset, ParentParams, ParentParams->Properties); + + // Save the package (force save even if not prompted) + UPackage* Package = NewParams->GetPackage(); + TArray PackagesToSave = { Package }; + + // Saves without dialog/prompt + const bool bForceSave = true; + if (!UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, bForceSave)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Failed to save child Flow Asset Params: %s"), *NewParams->GetPathName()); + return; + } + + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistryModule.Get().AssetCreated(NewParams); + TArray AssetsToSync = { NewParams }; + ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true); + } + + static void RegisterContextMenu() + { + UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateLambda([]() + { + FToolMenuOwnerScoped OwnerScoped(UE_MODULE_NAME); + UToolMenu* Menu = UE::ContentBrowser::ExtendToolMenu_AssetContextMenu(UFlowAssetParams::StaticClass()); + + FToolMenuSection& Section = Menu->FindOrAddSection("GetAssetActions"); + Section.AddDynamicEntry("Flow Asset Params Commands", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) + { + const TAttribute Label = LOCTEXT("FlowAssetParams_CreateChildParams", "Create Child Params"); + const TAttribute ToolTip = LOCTEXT("FlowAssetParams_CreateChildParamsTooltip", "Creates a new Flow Asset Params inheriting from the selected params."); + const FSlateIcon Icon = FSlateIcon(); + + FToolUIAction UIAction; + UIAction.ExecuteAction = FToolMenuExecuteAction::CreateStatic(&ExecuteCreateChildParams); + UIAction.CanExecuteAction = FToolMenuCanExecuteAction::CreateLambda([](const FToolMenuContext& InContext) + { + const UContentBrowserAssetContextMenuContext* Context = UContentBrowserAssetContextMenuContext::FindContextWithAssets(InContext); + return Context && Context->SelectedAssets.Num() == 1; + }); + InSection.AddMenuEntry("FlowAssetParams_CreateChildParams", Label, ToolTip, Icon, UIAction); + })); + })); + } + + static FDelayedAutoRegisterHelper DelayedAutoRegister(EDelayedRegisterRunPhase::EndOfEngineInit, &RegisterContextMenu); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp index 370ff0bd9..7871b3514 100644 --- a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp +++ b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp @@ -3,8 +3,8 @@ #include "Asset/FlowObjectDiff.h" #include "Asset/FlowDiffControl.h" -#include "Graph/Nodes/FlowGraphNode.h" #include "Nodes/FlowNodeBase.h" +#include "Graph/Nodes/FlowGraphNode.h" #include "DiffResults.h" #include "EdGraph/EdGraph.h" diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowAssetParamsPtrCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowAssetParamsPtrCustomization.cpp new file mode 100644 index 000000000..dc3406b04 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowAssetParamsPtrCustomization.cpp @@ -0,0 +1,192 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowAssetParamsPtrCustomization.h" +#include "Asset/FlowAssetParams.h" +#include "FlowAsset.h" +#include "FlowComponent.h" +#include "FlowEditorLogChannels.h" +#include "Interfaces/FlowAssetProviderInterface.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "ContentBrowserModule.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IContentBrowserSingleton.h" +#include "PropertyCustomizationHelpers.h" +#include "Widgets/Input/SButton.h" + +#define LOCTEXT_NAMESPACE "FlowAssetParamsPtrCustomization" + +TSharedRef FFlowAssetParamsPtrCustomization::MakeInstance() +{ + return MakeShared(); +} + +void FFlowAssetParamsPtrCustomization::CustomizeHeader( + TSharedRef PropertyHandle, + FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + StructPropertyHandle = PropertyHandle; + + const TSharedRef ObjectPicker = SNew(SObjectPropertyEntryBox) + .PropertyHandle(PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowAssetParamsPtr, AssetPtr))) + .AllowedClass(UFlowAssetParams::StaticClass()) + .AllowClear(true) + .DisplayUseSelected(false) + .DisplayBrowse(false) + .DisplayCompactSize(true) + .OnShouldFilterAsset(this, &FFlowAssetParamsPtrCustomization::ShouldFilterAsset); + + // Show create button if ShowCreateNew metadata is specified + const bool bShowCreateButton = PropertyHandle->HasMetaData(TEXT("ShowCreateNew")); + + HeaderRow + .NameContent()[PropertyHandle->CreatePropertyNameWidget()] + .ValueContent() + .MinDesiredWidth(200.f) + .MaxDesiredWidth(800.f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + ObjectPicker + ] + + SHorizontalBox::Slot().AutoWidth().Padding(2, 0) + .VAlign(VAlign_Center) + [ + bShowCreateButton ? + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateSP(this, &FFlowAssetParamsPtrCustomization::HandleCreateNew), + LOCTEXT("CreateNewAsset", "Create New") + ) : + SNullWidget::NullWidget + ] + ]; +} + +void FFlowAssetParamsPtrCustomization::CustomizeChildren( + TSharedRef PropertyHandle, + IDetailChildrenBuilder& ChildBuilder, + IPropertyTypeCustomizationUtils& CustomizationUtils) +{ +} + +void FFlowAssetParamsPtrCustomization::HandleCreateNew() +{ + if (!StructPropertyHandle.IsValid()) + { + UE_LOG(LogFlowEditor, Error, TEXT("Invalid property handle for FFlowAssetParamsPtr customization")); + + return; + } + + TArray OuterObjects; + StructPropertyHandle->GetOuterObjects(OuterObjects); + if (OuterObjects.Num() == 0) + { + UE_LOG(LogFlowEditor, Error, TEXT("No outer objects found for BaseAssetParams")); + + return; + } + + const FName PropertyName = StructPropertyHandle->GetProperty()->GetFName(); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + TArray AssetsToSync; + + for (UObject* OuterObject : OuterObjects) + { + UFlowAsset* FlowAsset = CastChecked(OuterObject, ECastCheckedType::NullAllowed); + if (!IsValid(FlowAsset)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Outer object is not a valid UFlowAsset: %s"), *OuterObject->GetPathName()); + + continue; + } + + if (PropertyName != GET_MEMBER_NAME_CHECKED(UFlowAsset, BaseAssetParams)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Property %s is not BaseAssetParams for %s"), *PropertyName.ToString(), *FlowAsset->GetPathName()); + + continue; + } + + UFlowAssetParams* NewParams = FlowAsset->GenerateParamsFromStartNode(); + if (IsValid(NewParams)) + { + StructPropertyHandle->NotifyPreChange(); + StructPropertyHandle->SetValueFromFormattedString(NewParams->GetPathName()); + StructPropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + + AssetRegistryModule.Get().AssetCreated(NewParams); + AssetsToSync.Add(NewParams); + } + } + + if (!AssetsToSync.IsEmpty()) + { + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true); + } +} + +bool FFlowAssetParamsPtrCustomization::ShouldFilterAsset(const FAssetData& AssetData) const +{ + UFlowAssetParams* Params = Cast(AssetData.GetAsset()); + if (!Params) + { + // Filter out invalid assets + return true; + } + + // Ensure Params->OwnerFlowAsset is valid + if (Params->OwnerFlowAsset.IsNull()) + { + UE_LOG(LogFlowEditor, Warning, TEXT("OwnerFlowAsset is null for %s"), *AssetData.GetFullName()); + + // Filter out if OwnerFlowAsset is invalid + return true; + } + + // Check if child params are allowed + const bool bHideChildParams = StructPropertyHandle->HasMetaData(TEXT("HideChildParams")); + if (bHideChildParams && !Params->ParentParams.AssetPtr.IsNull()) + { + // Filter out params with non-null ParentParams unless allowed + return true; + } + + TArray OuterObjects; + StructPropertyHandle->GetOuterObjects(OuterObjects); + if (OuterObjects.IsEmpty()) + { + UE_LOG(LogFlowEditor, Warning, TEXT("No outer objects found for FFlowAssetParamsPtr customization")); + + // Filter out if no outer objects + return true; + } + + // All OwnerAssets must match Params->OwnerFlowAsset + for (UObject* OuterObject : OuterObjects) + { + UFlowAsset* OwnerAssetCur = Cast(OuterObject); + if (!OwnerAssetCur) + { + if (IFlowAssetProviderInterface* AssetProvider = Cast(OuterObject)) + { + OwnerAssetCur = AssetProvider->ProvideFlowAsset(); + } + } + + if (!IsValid(OwnerAssetCur) || Params->OwnerFlowAsset != OwnerAssetCur) + { + // Filter out if any OwnerAsset doesn't match + return true; + } + } + + // Allow the asset + return false; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index 94582f2f7..c036a4651 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -32,9 +32,11 @@ #include "DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h" #include "DetailCustomizations/FlowPinCustomization.h" #include "DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h" +#include "DetailCustomizations/FlowAssetParamsPtrCustomization.h" #include "FlowAsset.h" #include "AddOns/FlowNodeAddOn.h" +#include "Asset/FlowAssetParamsTypes.h" #include "Nodes/Actor/FlowNode_ComponentObserver.h" #include "Nodes/Actor/FlowNode_PlayLevelSequence.h" #include "Nodes/Graph/FlowNode_CustomInput.h" @@ -55,7 +57,7 @@ static FName AssetSearchModuleName = TEXT("AssetSearch"); #define LOCTEXT_NAMESPACE "FlowEditorModule" EAssetTypeCategories::Type FFlowEditorModule::FlowAssetCategory = static_cast(0); -FAssetCategoryPath FFLowAssetCategoryPaths::Flow(LOCTEXT("Flow", "Flow")); +FAssetCategoryPath FFlowAssetCategoryPaths::Flow(LOCTEXT("Flow", "Flow")); void FFlowEditorModule::StartupModule() { @@ -259,6 +261,8 @@ void FFlowEditorModule::RegisterDetailCustomizations() RegisterCustomStructLayout(*FFlowDataPinInputProperty_Class::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_ClassCustomization::MakeInstance)); RegisterCustomStructLayout(*FFlowDataPinInputProperty_Object::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_ObjectCustomization::MakeInstance)); + RegisterCustomStructLayout(*FFlowAssetParamsPtr::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowAssetParamsPtrCustomization::MakeInstance)); + // Consider implementing details customizations... for every EFlowPinType FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index 50056584d..99a8203dc 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -28,6 +28,7 @@ #include "Kismet2/KismetEditorUtilities.h" #include "ScopedTransaction.h" #include "SourceCodeNavigation.h" +#include "Subsystems/AssetEditorSubsystem.h" #include "Textures/SlateIcon.h" #include "ToolMenuSection.h" #include "Editor/Transactor.h" diff --git a/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp b/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp index 5ef3afb86..77e64d428 100644 --- a/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp +++ b/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp @@ -59,22 +59,22 @@ void IFlowCuratedNamePropertyCustomization::CreateHeaderRowWidget(FDetailWidgetR .NameContent() [ SAssignNew(HeaderTextBlock, STextBlock) - .Text(BuildHeaderText()) + .Text(BuildHeaderText()) ] .ValueContent() .MaxDesiredWidth(250.0f) [ SAssignNew(TextListWidget, SComboBox>) - .OptionsSource(&CachedTextList) - .OnGenerateWidget_Static(&IFlowCuratedNamePropertyCustomization::GenerateTextListWidget) - .OnComboBoxOpening(this, &IFlowCuratedNamePropertyCustomization::OnTextListComboBoxOpening) - .OnSelectionChanged(this, &IFlowCuratedNamePropertyCustomization::OnTextSelected) - [ - SNew(STextBlock) - .Text(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) - ] + .OptionsSource(&CachedTextList) + .OnGenerateWidget_Static(&IFlowCuratedNamePropertyCustomization::GenerateTextListWidget) + .OnComboBoxOpening(this, &IFlowCuratedNamePropertyCustomization::OnTextListComboBoxOpening) + .OnSelectionChanged(this, &IFlowCuratedNamePropertyCustomization::OnTextSelected) + [ + SNew(STextBlock) + .Text(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) + ] ]; // Hook-up the ResetToDefault overrides @@ -82,7 +82,7 @@ void IFlowCuratedNamePropertyCustomization::CreateHeaderRowWidget(FDetailWidgetR FIsResetToDefaultVisible::CreateSP( this, &IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisible); - FResetToDefaultHandler ResetHandler = + FResetToDefaultHandler ResetHandler = FResetToDefaultHandler::CreateSP( this, &IFlowCuratedNamePropertyCustomization::CustomResetToDefault); @@ -95,7 +95,7 @@ void IFlowCuratedNamePropertyCustomization::CreateHeaderRowWidget(FDetailWidgetR HeaderRow.IsEnabled(IsEnabledAttribute); } -bool IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisible(TSharedPtr Property) const +bool IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisibleImpl(TSharedPtr Property) const { FName CuratedName; if (!TryGetCuratedName(CuratedName)) @@ -106,7 +106,7 @@ bool IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisible(TShare return !CuratedName.IsNone(); } -void IFlowCuratedNamePropertyCustomization::CustomResetToDefault(TSharedPtr Property) +void IFlowCuratedNamePropertyCustomization::CustomResetToDefaultImpl(TSharedPtr Property) { if (TrySetCuratedNameWithSideEffects(NAME_None)) { @@ -114,7 +114,7 @@ void IFlowCuratedNamePropertyCustomization::CustomResetToDefault(TSharedPtrIsEditConst()) { @@ -132,7 +132,7 @@ bool IFlowCuratedNamePropertyCustomization::CustomIsEnabled() const bool IFlowCuratedNamePropertyCustomization::TrySetCuratedNameWithSideEffects(const FName& NewName) { FName ExistingName; - (void) TryGetCuratedName(ExistingName); + (void)TryGetCuratedName(ExistingName); if (ExistingName != NewName) { @@ -196,7 +196,7 @@ void IFlowCuratedNamePropertyCustomization::OnTextListComboBoxOpening() for (TSharedPtr& Text : CachedTextList) { - (void) MapNameToText.FindOrAdd(FName(Text.Get()->ToString()), Text); + (void)MapNameToText.FindOrAdd(FName(Text.Get()->ToString()), Text); } TArray CuratedNameOptions = GetCuratedNameOptions(); diff --git a/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp b/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp index d54ab8610..96d42a729 100644 --- a/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp +++ b/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp @@ -10,9 +10,6 @@ #include "IDetailPropertyRow.h" #include "Widgets/Text/STextBlock.h" - -// IFlowExtendedPropertyTypeCustomization Implementation - void IFlowExtendedPropertyTypeCustomization::CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { StructPropertyHandle = InStructPropertyHandle; diff --git a/Source/FlowEditor/Public/Asset/AssetDefinition_FlowAssetParams.h b/Source/FlowEditor/Public/Asset/AssetDefinition_FlowAssetParams.h new file mode 100644 index 000000000..99c8aa74c --- /dev/null +++ b/Source/FlowEditor/Public/Asset/AssetDefinition_FlowAssetParams.h @@ -0,0 +1,24 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "AssetDefinitionDefault.h" +#include "AssetDefinition_FlowAssetParams.generated.h" + +/** +* Asset Definition for Flow Asset Params, providing Content Browser integration. +*/ +UCLASS() +class FLOWEDITOR_API UAssetDefinition_FlowAssetParams : public UAssetDefinitionDefault +{ + GENERATED_BODY() + +public: + // UAssetDefinition interface + virtual FText GetAssetDisplayName() const override; + virtual FLinearColor GetAssetColor() const override; + virtual TSoftClassPtr GetAssetClass() const override; + virtual TConstArrayView GetAssetCategories() const override; + virtual FAssetSupportResponse CanLocalize(const FAssetData& InAsset) const override; + // -- +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/Asset/FlowDiffControl.h b/Source/FlowEditor/Public/Asset/FlowDiffControl.h index 5240636ff..770f3a996 100644 --- a/Source/FlowEditor/Public/Asset/FlowDiffControl.h +++ b/Source/FlowEditor/Public/Asset/FlowDiffControl.h @@ -5,6 +5,7 @@ #include "Asset/FlowObjectDiff.h" #include "DiffResults.h" +#include "Runtime/Launch/Resources/Version.h" #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 7 #include "Editor/Kismet/Private/DiffControl.h" #else diff --git a/Source/FlowEditor/Public/Asset/FlowObjectDiff.h b/Source/FlowEditor/Public/Asset/FlowObjectDiff.h index d1896fabe..0bb17be07 100644 --- a/Source/FlowEditor/Public/Asset/FlowObjectDiff.h +++ b/Source/FlowEditor/Public/Asset/FlowObjectDiff.h @@ -2,6 +2,7 @@ #pragma once +#include "Runtime/Launch/Resources/Version.h" #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 7 #include "Editor/Kismet/Private/DiffControl.h" #else diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowAssetParamsPtrCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowAssetParamsPtrCustomization.h new file mode 100644 index 000000000..2c979633f --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowAssetParamsPtrCustomization.h @@ -0,0 +1,24 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "IPropertyTypeCustomization.h" +#include "PropertyHandle.h" + +class IPropertyHandle; + +// Customizes the FFlowAssetParamsPtr property in the Details panel. +class FFlowAssetParamsPtrCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) override; + +private: + TSharedPtr StructPropertyHandle; + + void HandleCreateNew(); + bool ShouldFilterAsset(const FAssetData& AssetData) const; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 018bfd314..8dec688cf 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -16,7 +16,7 @@ struct FGraphPanelPinConnectionFactory; class FFlowAssetEditor; class UFlowAsset; -struct FLOWEDITOR_API FFLowAssetCategoryPaths : EAssetCategoryPaths +struct FLOWEDITOR_API FFlowAssetCategoryPaths : EAssetCategoryPaths { static FAssetCategoryPath Flow; }; diff --git a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h index a9d715862..6c5e3d9ca 100644 --- a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h +++ b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h @@ -157,7 +157,7 @@ class FLOWEDITOR_API UFlowGraphNode : public UEdGraphNode // -- virtual void OnNodeDoubleClicked() const; - virtual void OnNodeDoubleClickedInPIE() const {}; + virtual void OnNodeDoubleClickedInPIE() const {} /** check if node has any errors, used for assigning colors on graph */ virtual bool HasErrors() const; diff --git a/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h b/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h index a7977813b..2428d41b9 100644 --- a/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h +++ b/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h @@ -4,6 +4,7 @@ #include "SGraphNode.h" #include "KismetPins/SGraphPinExec.h" +#include "Runtime/Launch/Resources/Version.h" #include "Graph/Nodes/FlowGraphNode.h" @@ -35,10 +36,10 @@ class FLOWEDITOR_API SFlowGraphNode : public SGraphNode virtual void GetNodeInfoPopups(FNodeInfoContext* Context, TArray& Popups) const override; virtual const FSlateBrush* GetShadowBrush(bool bSelected) const override; - #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6 -virtual void GetOverlayBrushes(bool bSelected, const FVector2D WidgetSize, TArray& Brushes) const override; +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6 + virtual void GetOverlayBrushes(bool bSelected, const FVector2D WidgetSize, TArray& Brushes) const override; #else -virtual void GetOverlayBrushes(bool bSelected, const FVector2f& WidgetSize, TArray& Brushes) const override; + virtual void GetOverlayBrushes(bool bSelected, const FVector2f& WidgetSize, TArray& Brushes) const override; #endif // -- diff --git a/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h b/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h index cabc2636a..6bd95250d 100644 --- a/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h +++ b/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h @@ -12,7 +12,7 @@ class FLOWEDITOR_API IFlowCuratedNamePropertyCustomization : public IFlowExtendedPropertyTypeCustomization { protected: - // IExtendedPropertyTypeCustomization + // IFlowExtendedPropertyTypeCustomization virtual void CreateHeaderRowWidget(FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; // --- @@ -34,9 +34,9 @@ class FLOWEDITOR_API IFlowCuratedNamePropertyCustomization : public IFlowExtende void AddToCachedTextList(const TSharedPtr Text); void InsertAtHeadOfCachedTextList(const TSharedPtr Text); - bool CustomIsResetToDefaultVisible(TSharedPtr Property) const; - void CustomResetToDefault(TSharedPtr Property); - bool CustomIsEnabled() const; + bool CustomIsResetToDefaultVisible(TSharedPtr Property) const { return CustomIsResetToDefaultVisibleImpl(Property); } + void CustomResetToDefault(TSharedPtr Property) { CustomResetToDefaultImpl(Property); } + bool CustomIsEnabled() const { return CustomIsEnabledImpl(); } // IFlowCuratedNamePropertyCustomization virtual TSharedPtr GetCuratedNamePropertyHandle() const = 0; @@ -44,6 +44,9 @@ class FLOWEDITOR_API IFlowCuratedNamePropertyCustomization : public IFlowExtende virtual bool TryGetCuratedName(FName& OutName) const = 0; virtual TArray GetCuratedNameOptions() const = 0; virtual bool AllowNameNoneIfOtherOptionsExist() const { return true; } + virtual bool CustomIsResetToDefaultVisibleImpl(TSharedPtr Property) const; + virtual void CustomResetToDefaultImpl(TSharedPtr Property); + virtual bool CustomIsEnabledImpl() const; // --- public: