diff --git a/Flow.uplugin b/Flow.uplugin index d0e0e654b..f4eb2d4c3 100644 --- a/Flow.uplugin +++ b/Flow.uplugin @@ -1,4 +1,4 @@ -{ +{ "FileVersion" : 3, "Version" : 2.2, "FriendlyName" : "Flow", @@ -8,7 +8,7 @@ "DocsURL" : "https://github.com/MothCocoon/FlowGraph/wiki", "MarketplaceURL" : "", "SupportURL": "https://discord.gg/Xmtr6GhbmW", - "EnabledByDefault" : true, + "EnabledByDefault" : false, "CanContainContent" : false, "IsBetaVersion" : false, "Installed" : false, diff --git a/Source/Flow/Flow.Build.cs b/Source/Flow/Flow.Build.cs index 74df20a4c..9964b1443 100644 --- a/Source/Flow/Flow.Build.cs +++ b/Source/Flow/Flow.Build.cs @@ -10,7 +10,7 @@ public Flow(ReadOnlyTargetRules target) : base(target) PublicDependencyModuleNames.AddRange(new[] { - "LevelSequence" + "LevelSequence", }); PrivateDependencyModuleNames.AddRange(new[] @@ -31,8 +31,11 @@ public Flow(ReadOnlyTargetRules target) : base(target) { PublicDependencyModuleNames.AddRange(new[] { + "GraphEditor", "MessageLog", - "UnrealEd" + "PropertyEditor", + "UnrealEd", + "SourceControl", }); } } diff --git a/Source/Flow/Private/Asset/FlowAssetParams.cpp b/Source/Flow/Private/Asset/FlowAssetParams.cpp new file mode 100644 index 000000000..fd0c89984 --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParams.cpp @@ -0,0 +1,387 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParams.h" +#include "FlowAsset.h" +#include "FlowLogChannels.h" +#include "Asset/FlowAssetParamsUtils.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "UObject/ObjectSaveContext.h" + +#if WITH_EDITOR +#include "SourceControlHelpers.h" +#include "Misc/DataValidation.h" +#endif + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParams) + +#if WITH_EDITOR +void UFlowAssetParams::PostLoad() +{ + Super::PostLoad(); + + if (!HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject)) + { + // Migrate the named properties over to the new structs + + bool bMadeAnyChanges = false; + for (FFlowNamedDataPinProperty& NamedProperty : Properties) + { + bMadeAnyChanges |= NamedProperty.FixupDataPinProperty(); + } + + if (bMadeAnyChanges) + { + ModifyAndRebuildPropertiesMap(); + } + } + + const EFlowReconcilePropertiesResult ReconcileResult = ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile ParentParams during PostLoad() for %s: %s"), + *GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } +} + +void UFlowAssetParams::PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext) +{ + Super::PreSaveRoot(ObjectSaveContext); + + const EFlowReconcilePropertiesResult ReconcileResult = ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile ParentParams during PreSaveRoot() 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.DataPinValue.IsValid()) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid DataPinValue"), 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 != nullptr) + { + // We have a version of ParentProp locally. + // Determine if our local property has been modified since our last reconcile. + // A local property is considered modified if we've never added it to PropertyMap or is different from what currently exists in PropertyMap. + bool bLocalPropHasChanged = true; + if (PropertyMap.Contains(LocalProp->Name)) + { + FFlowNamedDataPinProperty PreviousLocalProp = *LocalProp; + PreviousLocalProp.DataPinValue = PropertyMap[LocalProp->Name]; + bLocalPropHasChanged = !FFlowAssetParamsUtils::ArePropertiesEqual(*LocalProp, PreviousLocalProp); + } + + if (bLocalPropHasChanged) + { + // If the local property has been changed then compare it to the parent value to determine if it is an override or not. + if (FFlowAssetParamsUtils::ArePropertiesEqual(*LocalProp, ParentProp)) + { + FFlowNamedDataPinProperty& NewProp = NewProperties.Add_GetRef(ParentProp); + NewProp.bIsOverride = false; + } + else + { + FFlowNamedDataPinProperty& NewProp = NewProperties.Add_GetRef(*LocalProp); + NewProp.Name = ParentProp.Name; + NewProp.bIsOverride = true; + } + } + else + { + // If the local property has not been changed then check whether it is an override. + // Overrides will get copied over while non-overrides will be updated to match the parent. + if (LocalProp->bIsOverride) + { + FFlowNamedDataPinProperty& NewProp = NewProperties.Add_GetRef(*LocalProp); + NewProp.Name = ParentProp.Name; + NewProp.bIsOverride = true; + } + else + { + FFlowNamedDataPinProperty& NewProp = NewProperties.Add_GetRef(ParentProp); + NewProp.bIsOverride = false; + } + } + } + else + { + // We do not have a version of ParentProp. Just make a non-override copy. + FFlowNamedDataPinProperty& NewProp = NewProperties.Add_GetRef(ParentProp); + NewProp.bIsOverride = false; + } + } + + for (FFlowNamedDataPinProperty& LocalProp : Properties) + { + if (!FFlowAssetParamsUtils::FindPropertyByGuid(ParentProps, LocalProp.Guid)) + { + LocalProp.bIsOverride = true; + + NewProperties.Add(LocalProp); + } + } + + Properties = NewProperties; + + 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::CanModifyFlowDataPinType() const +{ + // These are set by the Flow asset, which is authoritative + return false; +} + +bool UFlowAssetParams::ShowFlowDataPinValueInputPinCheckbox() const +{ + // These are set by the Flow asset, which is authoritative + return false; +} + +bool UFlowAssetParams::ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const +{ + return true; +} + +bool UFlowAssetParams::CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const +{ + // These are set by the Flow asset, which is authoritative + return false; +} + +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.DataPinValue); + } + 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 UFlowAssetParams::TrySupplyDataPin_Implementation(FName PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + FFlowDataPinResult DataPinResult(EFlowDataPinResolveResult::Success); + DataPinResult.ResultValue = (*Found); + + return DataPinResult; + } + + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); +} 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..8b1ae828d --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp @@ -0,0 +1,118 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParamsUtils.h" +#include "Types/FlowNamedDataPinProperty.h" +#include "Misc/DateTime.h" +#include "HAL/FileManager.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.DataPinValue.GetScriptStruct(); + const UScriptStruct* ScriptStructB = PropB.DataPinValue.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.DataPinValue.GetScriptStruct(); + const UScriptStruct* ScriptStructB = B.DataPinValue.GetScriptStruct(); + if (ScriptStructA != ScriptStructB) + { + return false; + } + + return A.DataPinValue == B.DataPinValue; +} + +#endif diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 521ed3e36..a26b48efe 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -5,22 +5,36 @@ #include "FlowLogChannels.h" #include "FlowSettings.h" #include "FlowSubsystem.h" - #include "AddOns/FlowNodeAddOn.h" -#include "Interfaces/FlowDataPinGeneratorNodeInterface.h" +#include "Asset/FlowAssetParams.h" +#include "Asset/FlowAssetParamsUtils.h" #include "Nodes/FlowNodeBase.h" #include "Nodes/Graph/FlowNode_CustomInput.h" #include "Nodes/Graph/FlowNode_CustomOutput.h" #include "Nodes/Graph/FlowNode_Start.h" #include "Nodes/Graph/FlowNode_SubGraph.h" +#include "Types/FlowAutoDataPinsWorkingData.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowPinType.h" +#include "Types/FlowStructUtils.h" #include "Engine/World.h" #include "Serialization/MemoryReader.h" #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"); @@ -83,23 +97,148 @@ void UFlowAsset::PostLoad() { Super::PostLoad(); - // If we removed or moved a flow node blueprint (and there is no redirector) we might loose the reference to it resulting - // in null pointers in the Nodes FGUID->UFlowNode* Map. So here we iterate over all the Nodes and remove all pairs that - // are nulled out. + const UPackage* Package = GetPackage(); + if (IsValid(Package) && !FPackageName::IsTempPackage(Package->GetPathName())) + { + // If we removed or moved a flow node blueprint (and there is no redirector) we might loose the reference to it resulting + // in null pointers in the Nodes FGUID->UFlowNode* Map. So here we iterate over all the Nodes and remove all pairs that + // are nulled out. + + TSet NodesToRemoveGUID; + + for (auto& [Guid, Node] : GetNodes()) + { + if (!IsValid(Node)) + { + NodesToRemoveGUID.Emplace(Guid); + } + } + + for (const FGuid& Guid : NodesToRemoveGUID) + { + 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); - TSet NodesToRemoveGUID; + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile BaseAssetParams for %s: %s"), + *BaseAssetParamsPtr->GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } +} - for (auto& [Guid, Node] : GetNodes()) +UFlowAssetParams* UFlowAsset::GenerateParamsFromStartNode() +{ + if (BaseAssetParams.AssetPtr.IsValid()) { - if (!IsValid(Node)) + 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)) { - NodesToRemoveGUID.Emplace(Guid); + UE_LOG(LogFlow, Warning, TEXT("Failed to check out/add %s; saved in-memory only"), *NewParams->GetPathName()); } } - for (const FGuid& Guid : NodesToRemoveGUID) + // 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) { - UnregisterNode(Guid); + 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"); } } @@ -358,7 +497,7 @@ void UFlowAsset::HarvestNodeConnections(UFlowNode* TargetNode) for (const UEdGraphPin* ThisPin : GraphNodePins) { const bool bIsExecPin = FFlowPin::IsExecPinCategory(ThisPin->PinType.PinCategory); - const bool bIsDataPin = FFlowPin::IsDataPinCategory(ThisPin->PinType.PinCategory); + const bool bIsDataPin = !bIsExecPin; const bool bIsOutputPin = (ThisPin->Direction == EGPD_Output); const bool bIsInputPin = (ThisPin->Direction == EGPD_Input); const bool bHasAtLeastOneConnection = ThisPin->LinkedTo.Num() > 0; @@ -421,57 +560,53 @@ void UFlowAsset::HarvestNodeConnections(UFlowNode* TargetNode) } } } - -bool UFlowAsset::TryUpdateManagedFlowPinsForNode(UFlowNode& FlowNode) + +bool UFlowAsset::TryGetDefaultForInputPinName(const FStructProperty& StructProperty, const void* Container, FString& OutString) { - const UClass* FlowNodeClass = FlowNode.GetClass(); - if (!IsValid(FlowNodeClass)) + // We also look in the USTRUCT for DefaultForInputFlowPin + const FString* DefaultForInputFlowPinName = StructProperty.Struct->FindMetaData(FFlowPin::MetadataKey_DefaultForInputFlowPin); + + if (DefaultForInputFlowPinName) { - return false; + OutString = *DefaultForInputFlowPinName; + + return true; } - // Setup the working data struct - FFlowHarvestDataPinsWorkingData WorkingData = - FFlowHarvestDataPinsWorkingData( - FlowNode, - FlowNode.GetPinNameToBoundPropertyNameMap(), - FlowNode.GetAutoInputDataPins(), - FlowNode.GetAutoOutputDataPins()); + // For blueprint use, we allow the Value structs to set input pins via editor-only data - // Some nodes can auto-generate some pins directly, - // so let them append their pins into our arrays first. - if (IFlowDataPinGeneratorNodeInterface* AutoGeneratorNode = Cast(&FlowNode)) + const FFlowDataPinValue* DataPinValue = FlowStructUtils::CastStructValue(StructProperty, Container); + if (DataPinValue && DataPinValue->IsInputPin()) { - AutoGeneratorNode->AutoGenerateDataPins( - WorkingData.PinNameToBoundPropertyNameMapNext, - WorkingData.AutoInputDataPinsNext, - WorkingData.AutoOutputDataPinsNext); - } + OutString.Empty(); - // Try to harvest pins to auto-generate and/or bind to for each property in the flow node - for (TFieldIterator PropertyIt(FlowNodeClass); PropertyIt; ++PropertyIt) - { - HarvestFlowPinMetadataForProperty(*PropertyIt, WorkingData); + return true; } - // Check if the pin name to bound property map changed - WorkingData.bPinNameMapChanged |= WorkingData.DidPinNameToBoundPropertyNameMapChange(); + return false; +} + +bool UFlowAsset::TryUpdateManagedFlowPinsForNode(UFlowNode& FlowNode) +{ + // Setup the working data struct + FFlowAutoDataPinsWorkingData WorkingData = + FFlowAutoDataPinsWorkingData( + FlowNode.GetAutoInputDataPins(), + FlowNode.GetAutoOutputDataPins()); + + // Allow the node to auto-generate data pins + FlowNode.AutoGenerateDataPins(WorkingData); // If the auto-generated data pins array changed, it counts as dirty as well const bool bAutoInputDataPinsChanged = WorkingData.DidAutoInputDataPinsChange(); const bool bAutoOutputDataPinsChanged = WorkingData.DidAutoOutputDataPinsChange(); - if (WorkingData.bPinNameMapChanged || bAutoInputDataPinsChanged || bAutoOutputDataPinsChanged) + if (bAutoInputDataPinsChanged || bAutoOutputDataPinsChanged) { FlowNode.SetFlags(RF_Transactional); FlowNode.Modify(); // Lock-in the data that changed. - if (WorkingData.bPinNameMapChanged) - { - FlowNode.SetPinNameToBoundPropertyNameMap(WorkingData.PinNameToBoundPropertyNameMapNext); - } - if (bAutoInputDataPinsChanged || bAutoOutputDataPinsChanged) { if (bAutoInputDataPinsChanged) @@ -493,394 +628,6 @@ bool UFlowAsset::TryUpdateManagedFlowPinsForNode(UFlowNode& FlowNode) return false; } -void UFlowAsset::HarvestFlowPinMetadataForProperty(const FProperty* Property, FFlowHarvestDataPinsWorkingData& InOutData) -{ - FText PinDisplayName = Property->GetDisplayNameText(); - const FName& PinAuthoredName = Property->GetFName(); - - // Default assumption is the pin is will be a output pin, if no metadata is specified (ie, bIsSourceForOutputPin == false), - // because this is the most common case (the auto-generated input-pin-from-property case is only for defaulting) - TArray* FlowPinArray = &InOutData.AutoOutputDataPinsNext; - - const FString* SourceForOutputFlowPinName = Property->FindMetaData(FFlowPin::MetadataKey_SourceForOutputFlowPin); - const FString* DefaultForInputFlowPinName = Property->FindMetaData(FFlowPin::MetadataKey_DefaultForInputFlowPin); - - if (SourceForOutputFlowPinName && DefaultForInputFlowPinName) - { - LogError( - FString::Printf(TEXT("Error. A property cannot be both a %s and %s"), - *FFlowPin::MetadataKey_SourceForOutputFlowPin.ToString(), - *FFlowPin::MetadataKey_DefaultForInputFlowPin.ToString()), - InOutData.FlowNode); - - return; - } - - if (SourceForOutputFlowPinName) - { - const FString SpecifyOutputPinNameString = *SourceForOutputFlowPinName; - - if (SpecifyOutputPinNameString.Len() > 0) - { - // Replace the default PinDisplayName with the name specified in the Metadata value - PinDisplayName = FText::FromString(SpecifyOutputPinNameString); - } - } - else if (DefaultForInputFlowPinName) - { - const FString SpecifyInputPinNameString = *DefaultForInputFlowPinName; - - if (SpecifyInputPinNameString.Len() > 0) - { - // Replace the default PinDisplayName with the name specified in the Metadata value - PinDisplayName = FText::FromString(SpecifyInputPinNameString); - } - - // If the property is a Default Input for a data pin, then we need to generate the pin in the - // Input Pins array. - FlowPinArray = &InOutData.AutoInputDataPinsNext; - } - - // Check for relevant metadata keys on the property's USTRUCT() - const FStructProperty* StructProperty = CastField(Property); - if (StructProperty && StructProperty->Struct) - { - const UScriptStruct* ScriptStruct = StructProperty->Struct; - - // We also look in the USTRUCT for DefaultForInputFlowPin - DefaultForInputFlowPinName = ScriptStruct->FindMetaData(FFlowPin::MetadataKey_DefaultForInputFlowPin); - if (DefaultForInputFlowPinName) - { - // If the property is a Default Input for a data pin, then we need to generate the pin in the - // Input Pins array. - FlowPinArray = &InOutData.AutoInputDataPinsNext; - } - - if (const FString* AutoPinType = ScriptStruct->FindMetaData(FFlowPin::MetadataKey_FlowPinType)) - { - const bool bIsInputPin = DefaultForInputFlowPinName != nullptr; - - // Auto-generate the pin for this property - if (!TryCreateFlowDataPinFromMetadataValue(*AutoPinType, *InOutData.FlowNode, *Property, PinDisplayName, bIsInputPin, FlowPinArray)) - { - LogError(FString::Printf(TEXT("Error. Unknown value %s for metadata %s"), **AutoPinType, *FFlowPin::MetadataKey_FlowPinType.ToString()), InOutData.FlowNode); - - return; - } - - // Add a binding for the new pin to its property - AddDataPinPropertyBindingToMap( - PinAuthoredName, - Property->GetFName(), - InOutData); - - return; - } - } - - const FString* AutoPinType = Property->FindMetaData(FFlowPin::MetadataKey_FlowPinType); - - if (!SourceForOutputFlowPinName && !DefaultForInputFlowPinName && !AutoPinType) - { - // If we didn't detect any the relevent metadata keys, we can exit early - - return; - } - - if (AutoPinType) - { - // Auto-generate the desired pin for this property - const bool bIsInputPin = DefaultForInputFlowPinName != nullptr; - - if (!TryCreateFlowDataPinFromMetadataValue(*AutoPinType, *InOutData.FlowNode, *Property, PinDisplayName, bIsInputPin, FlowPinArray)) - { - LogError(FString::Printf(TEXT("Unknown value %s for metadata %s"), **AutoPinType, *FFlowPin::MetadataKey_FlowPinType.ToString()), InOutData.FlowNode); - - return; - } - } - else if (SourceForOutputFlowPinName) - { - // Bind to the output data pin to source from the property (but do not auto-generate the pin) - - FFlowPin* FoundFlowPin = InOutData.FlowNode->FindOutputPinByName(PinAuthoredName); - if (!FoundFlowPin) - { - LogError(FString::Printf(TEXT("Could not find bound data pin named %s for property %s"), *PinAuthoredName.ToString(), *Property->GetName()), InOutData.FlowNode); - - return; - } - } - else if (DefaultForInputFlowPinName) - { - // Bind to the input data pin to default its value from the property (but do not auto-generate the pin) - - FFlowPin* FoundFlowPin = InOutData.FlowNode->FindInputPinByName(PinAuthoredName); - if (!FoundFlowPin) - { - LogError(FString::Printf(TEXT("Could not find bound data pin named %s for property %s"), *PinAuthoredName.ToString(), *Property->GetName()), InOutData.FlowNode); - - return; - } - } - - // Add a binding for the data pin to its property - AddDataPinPropertyBindingToMap( - PinAuthoredName, - Property->GetFName(), - InOutData); -} - -void UFlowAsset::AddDataPinPropertyBindingToMap( - const FName& PinAuthoredName, - const FName& PropertyAuthoredName, - FFlowHarvestDataPinsWorkingData& InOutData) -{ - // Add a new entry in the map for this DataPin name to the property it sources from - InOutData.PinNameToBoundPropertyNameMapNext.Add(PinAuthoredName, PropertyAuthoredName); -} - -template -void AddPinForPinType(EFlowPinType PinType, UFlowNode& FlowNode, const FProperty& Property, const FText& PinDisplayName, TArray* InOutDataPinsNext) -{ - const FName& PinAuthoredName = Property.GetFName(); - - // Some of the FlowPinTypes require a SubCategoryObject to fully define the type, so - // we need to find that for the cases that it applies to. - - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - - FFlowPin& NewFlowPin = InOutDataPinsNext->Add_GetRef(FFlowPin(PinAuthoredName, PinDisplayName)); - switch (PinType) - { - case EFlowPinType::Enum: - { - UEnum* EnumClass = nullptr; - - if (const FStructProperty* StructProperty = CastField(&Property)) - { - // Check for a wrapper struct to get the enum data from - const UStruct* ScriptStruct = TEnumProperty::StaticStruct(); - if (StructProperty->Struct == ScriptStruct) - { - TEnumProperty ValueStruct; - StructProperty->GetValue_InContainer(&FlowNode, &ValueStruct); - - EnumClass = ValueStruct.EnumClass; - } - } - else if (const FEnumProperty* EnumProperty = CastField(&Property)) - { - // Get the enum data from the FEnumProperty - EnumClass = EnumProperty->GetEnum(); - } - - NewFlowPin.SetPinType(PinType, EnumClass); - } - break; - - case EFlowPinType::Vector: - { - UScriptStruct* ValueStructType = FFlowDataPinProperty::FindScriptStructForFlowDataPinProperty(Property); - NewFlowPin.SetPinType(PinType, ValueStructType); - } - break; - - case EFlowPinType::Rotator: - { - UScriptStruct* ValueStructType = FFlowDataPinProperty::FindScriptStructForFlowDataPinProperty(Property); - NewFlowPin.SetPinType(PinType, ValueStructType); - } - break; - - case EFlowPinType::Transform: - { - UScriptStruct* ValueStructType = FFlowDataPinProperty::FindScriptStructForFlowDataPinProperty(Property); - NewFlowPin.SetPinType(PinType, ValueStructType); - } - break; - - case EFlowPinType::GameplayTag: - { - UScriptStruct* ValueStructType = FFlowDataPinProperty::FindScriptStructForFlowDataPinProperty(Property); - NewFlowPin.SetPinType(PinType, ValueStructType); - } - break; - - case EFlowPinType::GameplayTagContainer: - { - UScriptStruct* ValueStructType = FFlowDataPinProperty::FindScriptStructForFlowDataPinProperty(Property); - NewFlowPin.SetPinType(PinType, ValueStructType); - } - break; - - case EFlowPinType::InstancedStruct: - { - UScriptStruct* ValueStructType = FFlowDataPinProperty::FindScriptStructForFlowDataPinProperty(Property); - NewFlowPin.SetPinType(PinType, ValueStructType); - } - break; - - case EFlowPinType::Object: - { - UClass* Class = nullptr; - if (const FStructProperty* StructProperty = CastField(&Property)) - { - const UStruct* ScriptStruct = TObjectProperty::StaticStruct(); - static const UStruct* SoftObjectPathStruct = TBaseStructure::Get(); - - if (StructProperty->Struct == ScriptStruct) - { - TObjectProperty ValueStruct; - StructProperty->GetValue_InContainer(&FlowNode, &ValueStruct); - - // Get the Object property's base UClass from the FFlowDataPinProperty - Class = ValueStruct.DeriveObjectClass(*StructProperty); - } - else if (StructProperty->Struct == SoftObjectPathStruct) - { - // Get the Object property's base UClass from the struct property's MetaData - Class = FFlowDataPinOutputProperty_Object::TryGetObjectClassFromProperty(*StructProperty); - } - } - else if (const FObjectProperty* ObjectProperty = CastField(&Property)) - { - // Get the Object property's base UClass from the property's MetaData - Class = ObjectProperty->PropertyClass; - } - else if (const FSoftObjectProperty* SoftObjectProperty = CastField(&Property)) - { - // Get the Object property's base UClass from the property's MetaData - Class = SoftObjectProperty->PropertyClass; - } - else if (const FWeakObjectProperty* WeakObjectProperty = CastField(&Property)) - { - // Get the Object property's base UClass from the property's MetaData - Class = WeakObjectProperty->PropertyClass; - } - else if (const FLazyObjectProperty* LazyObjectProperty = CastField(&Property)) - { - // Get the Object property's base UClass from the property's MetaData - Class = LazyObjectProperty->PropertyClass; - } - - NewFlowPin.SetPinType(PinType, Class); - } - break; - - case EFlowPinType::Class: - { - UClass* Class = nullptr; - if (const FStructProperty* StructProperty = CastField(&Property)) - { - const UStruct* ScriptStruct = TClassProperty::StaticStruct(); - static const UStruct* SoftClassPathStruct = TBaseStructure::Get(); - - if (StructProperty->Struct == ScriptStruct) - { - TClassProperty ValueStruct; - StructProperty->GetValue_InContainer(&FlowNode, &ValueStruct); - - // Get the Class property's base UClass from the FFlowDataPinProperty - Class = ValueStruct.DeriveMetaClass(*StructProperty); - } - else if (StructProperty->Struct == SoftClassPathStruct) - { - // Get the Class property's base UClass from the struct property's MetaData - Class = FFlowDataPinOutputProperty_Class::TryGetMetaClassFromProperty(*StructProperty); - } - } - else if (const FClassProperty* ClassProperty = CastField(&Property)) - { - // Get the Class property's base UClass from the property's MetaData - Class = ClassProperty->MetaClass; - } - else if (const FSoftClassProperty* SoftClassProperty = CastField(&Property)) - { - // Get the Class property's base UClass from the property's MetaData - Class = SoftClassProperty->MetaClass; - } - - NewFlowPin.SetPinType(PinType, Class); - } - break; - - default: - { - NewFlowPin.SetPinType(PinType); - } - break; - } -} - -bool UFlowAsset::TryCreateFlowDataPinFromMetadataValue( - const FString& MetadataValue, - UFlowNode& FlowNode, - const FProperty& Property, - const FText& PinDisplayName, - const bool bIsInputPin, - TArray* InOutDataPinsNext) const -{ - check(InOutDataPinsNext); - - const TArray& CachedEnumValueNames = FFlowPin::GetFlowPinTypeEnumValuesWithoutSpaces(); - - const FName MetadataValueAsName = FName(MetadataValue); - - for (EFlowPinType PinType : TEnumRange()) - { - const int32 PinTypeAsInt = FlowEnum::ToInt(PinType); - check(CachedEnumValueNames.IsValidIndex(PinTypeAsInt)); - const FName& EnumValueAsName = CachedEnumValueNames[PinTypeAsInt]; - - if (MetadataValueAsName == EnumValueAsName) - { - if (bIsInputPin) - { - AddPinForPinType< - FFlowDataPinInputProperty_Enum, - FFlowDataPinInputProperty_Vector, - FFlowDataPinInputProperty_Rotator, - FFlowDataPinInputProperty_Transform, - FFlowDataPinInputProperty_GameplayTag, - FFlowDataPinInputProperty_GameplayTagContainer, - FFlowDataPinInputProperty_InstancedStruct, - FFlowDataPinInputProperty_Object, - FFlowDataPinInputProperty_Class>( - PinType, - FlowNode, - Property, - PinDisplayName, - InOutDataPinsNext); - } - else - { - AddPinForPinType< - FFlowDataPinOutputProperty_Enum, - FFlowDataPinOutputProperty_Vector, - FFlowDataPinOutputProperty_Rotator, - FFlowDataPinOutputProperty_Transform, - FFlowDataPinOutputProperty_GameplayTag, - FFlowDataPinOutputProperty_GameplayTagContainer, - FFlowDataPinOutputProperty_InstancedStruct, - FFlowDataPinOutputProperty_Object, - FFlowDataPinOutputProperty_Class>( - PinType, - FlowNode, - Property, - PinDisplayName, - InOutDataPinsNext); - } - - return true; - } - } - - // Subclasses of UFlowAsset can extend the supported MetadataValues -> FFlowPin mappings - return false; -} - #endif UFlowNode* UFlowAsset::GetDefaultEntryNode() const @@ -1045,6 +792,32 @@ TArray UFlowAsset::GatherNodesConnectedToAllInputs() const return ConnectedNodes; } +TArray UFlowAsset::GatherPinsConnectedToPin(const FConnectedPin& Pin) const +{ + TArray ConnectedPins; + + // Connections are only stored on one of the Nodes they connect depending on pin type. + // As such, we need to iterate all Nodes to find all possible Connections for the Pin. + for (const auto& GuidNodePair : Nodes) + { + if (IsValid(GuidNodePair.Value)) + { + ConnectedPins.Append(GuidNodePair.Value->GetKnownConnectionsToPin(Pin)); + } + } + + return ConnectedPins; +} + +TArray UFlowAsset::GetAllNodes() const +{ + TArray> AllNodes; + AllNodes.Reserve(Nodes.Num()); + Nodes.GenerateValueArray(AllNodes); + + return ObjectPtrDecay(AllNodes); +} + void UFlowAsset::AddInstance(UFlowAsset* Instance) { ActiveInstances.Add(Instance); @@ -1308,7 +1081,7 @@ void UFlowAsset::TriggerCustomOutput(const FName& EventName) } } -void UFlowAsset::TriggerInput(const FGuid& NodeGuid, const FName& PinName) +void UFlowAsset::TriggerInput(const FGuid& NodeGuid, const FName& PinName, const FConnectedPin& FromPin) { if (UFlowNode* Node = Nodes.FindRef(NodeGuid)) { @@ -1523,43 +1296,3 @@ void UFlowAsset::LogNote(const FString& MessageToLog, const UFlowNodeBase* Node) } } #endif - -#if WITH_EDITOR -bool FFlowHarvestDataPinsWorkingData::DidPinNameToBoundPropertyNameMapChange() const -{ - if (PinNameToBoundPropertyNameMapPrev.Num() != PinNameToBoundPropertyNameMapNext.Num()) - { - return true; - } - - for (const auto& KV : PinNameToBoundPropertyNameMapPrev) - { - const FName& PinNameFromPrev = KV.Key; - const FName& PropertyNameFromPrev = KV.Value; - - const FName* FoundPropertyNameInNext = PinNameToBoundPropertyNameMapNext.Find(PinNameFromPrev); - if (!FoundPropertyNameInNext) - { - return true; - } - - if (*FoundPropertyNameInNext != PropertyNameFromPrev) - { - return true; - } - } - - return false; -} - -bool FFlowHarvestDataPinsWorkingData::DidAutoInputDataPinsChange() const -{ - return !FFlowPin::ArePinArraysMatchingNamesAndTypes(AutoInputDataPinsPrev, AutoInputDataPinsNext); -} - -bool FFlowHarvestDataPinsWorkingData::DidAutoOutputDataPinsChange() const -{ - return !FFlowPin::ArePinArraysMatchingNamesAndTypes(AutoOutputDataPinsPrev, AutoOutputDataPinsNext); -} - -#endif 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/FlowExecutableActorComponent.cpp b/Source/Flow/Private/FlowExecutableActorComponent.cpp new file mode 100644 index 000000000..9858ca376 --- /dev/null +++ b/Source/Flow/Private/FlowExecutableActorComponent.cpp @@ -0,0 +1,50 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "FlowExecutableActorComponent.h" +#include "Nodes/FlowNode.h" +#include "Types/FlowAutoDataPinsWorkingData.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowExecutableActorComponent) + +#if WITH_EDITOR +bool UFlowExecutableActorComponent::CanModifyFlowDataPinType() const +{ + return IsDefaultObject(); +} + +bool UFlowExecutableActorComponent::ShowFlowDataPinValueInputPinCheckbox() const +{ + return IsDefaultObject(); +} + +bool UFlowExecutableActorComponent::ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const +{ + return IsDefaultObject(); +} + +bool UFlowExecutableActorComponent::CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const +{ + return IsDefaultObject(); +} + +void UFlowExecutableActorComponent::SetFlowDataPinValuesRebuildDelegate(FSimpleDelegate InDelegate) +{ + FlowDataPinValuesRebuildDelegate = InDelegate; +} + +void UFlowExecutableActorComponent::RequestFlowDataPinValuesDetailsRebuild() +{ + if (FlowDataPinValuesRebuildDelegate.IsBound()) + { + FlowDataPinValuesRebuildDelegate.Execute(); + } +} + +#endif + +void UFlowExecutableActorComponent::PreActivateExternalFlowExecutable(UFlowNodeBase& FlowNodeBase) +{ + IFlowExternalExecutableInterface::PreActivateExternalFlowExecutable(FlowNodeBase); + + FlowNodeProxy = &FlowNodeBase; +} diff --git a/Source/Flow/Private/FlowPinSubsystem.cpp b/Source/Flow/Private/FlowPinSubsystem.cpp new file mode 100644 index 000000000..f8174db5f --- /dev/null +++ b/Source/Flow/Private/FlowPinSubsystem.cpp @@ -0,0 +1,76 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "FlowPinSubsystem.h" +#include "FlowLogChannels.h" +#include "Types/FlowPinTypesStandard.h" +#include "Engine/Engine.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowPinSubsystem) + +UFlowPinSubsystem* UFlowPinSubsystem::Get() +{ + return GEngine->GetEngineSubsystem(); +} + +void UFlowPinSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + check(PinTypes.IsEmpty()); + + // Register standard types + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); + RegisterPinType(); +} + +void UFlowPinSubsystem::Deinitialize() +{ + UnregisterAllPinTypes(); + + Super::Deinitialize(); +} + +void UFlowPinSubsystem::UnregisterAllPinTypes() +{ + const TArray PinTypeNames = GetPinTypeNames(); + for (const FFlowPinTypeName& PinTypeName : PinTypeNames) + { + UnregisterPinType(PinTypeName); + } + + check(PinTypes.IsEmpty()); +} + +void UFlowPinSubsystem::RegisterPinType(const FFlowPinTypeName& TypeName, const TInstancedStruct& PinType) +{ + PinTypes.Add(TypeName, PinType); +} + +void UFlowPinSubsystem::UnregisterPinType(const FFlowPinTypeName& TypeName) +{ + PinTypes.Remove(TypeName); +} + +TArray UFlowPinSubsystem::GetPinTypeNames() const +{ + TArray TypeNames; + PinTypes.GetKeys(TypeNames); + + return TypeNames; +} + 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/Actor/FlowNode_ExecuteComponent.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_ExecuteComponent.cpp index 5714c31e9..291d86a4f 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_ExecuteComponent.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_ExecuteComponent.cpp @@ -20,7 +20,7 @@ UFlowNode_ExecuteComponent::UFlowNode_ExecuteComponent() : Super() { #if WITH_EDITOR - Category = TEXT("Actor"); + Category = TEXT("Gameplay|Actor"); #endif InputPins.Reset(); @@ -125,7 +125,7 @@ void UFlowNode_ExecuteComponent::OnActivate() } else { - UE_LOG(LogFlow, Error, TEXT("Expected a valid UActorComponent that implemented the IFlowExternalExecutableInterface")); + UE_LOG(LogFlow, Error, TEXT("Expected a valid UActorComponent that implemented the IFlowExternalExecutableInterface (%s)"), *ResolvedComp->GetClass()->GetName()); } if (IFlowCoreExecutableInterface* ComponentAsCoreExecutable = Cast(ResolvedComp)) @@ -138,7 +138,7 @@ void UFlowNode_ExecuteComponent::OnActivate() } else { - UE_LOG(LogFlow, Error, TEXT("Expected a valid UActorComponent that implemented the IFlowCoreExecutableInterface")); + UE_LOG(LogFlow, Error, TEXT("Expected a valid UActorComponent that implemented the IFlowCoreExecutableInterface (%s)"), *ResolvedComp->GetClass()->GetName()); } } } @@ -196,32 +196,35 @@ void UFlowNode_ExecuteComponent::ExecuteInput(const FName& PinName) { LogError(FString::Printf(TEXT("Could not ExecuteInput %s, because the component was missing or could not be resolved."), *PinName.ToString())); } - - // Trigger the default output (if the output pins weren't replaced, - // and the node hasn't already Finished) - if (OutputPins.Contains(UFlowNode::DefaultOutputPin.PinName) && !HasFinished()) - { - constexpr bool bFinish = false; - TriggerOutput(UFlowNode::DefaultOutputPin.PinName, bFinish); - } } #if WITH_EDITOR TArray UFlowNode_ExecuteComponent::GetContextInputs() const { TArray ContextInputs; - if (UActorComponent* ResolvedComp = GetResolvedComponent()) + const UActorComponent* ResolvedComp = GetResolvedComponent(); + + if (!IsValid(ResolvedComp)) { - if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - ContextInputs = PinSupplierInterface->GetContextInputs(); - } + // If we don't have a Resolved Component object yet, try to find the expected component object. For + // injected components this will return the CDO + ResolvedComp = TryGetExpectedComponent(); } - else if (const UActorComponent* ExpectedComponent = TryGetExpectedComponent()) + + // NOTE: we have to call GetClass on the Resolved Component; not StaticClass(). This makes it so we can handle classes that only implement the + // interface in Blueprints. + if (ResolvedComp && ResolvedComp->GetClass()->ImplementsInterface(UFlowContextPinSupplierInterface::StaticClass())) { - if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ExpectedComponent)) + if (const IFlowContextPinSupplierInterface* CompAsStaticInterface = Cast(ResolvedComp)) + { + // The native (static) class implements the interface, so we call it directly (default implementation provided by the interface will call the K2 BP version of it). + // Ee assume that the implementor of the interface is responsible for invoking Execute_K2_GetContextInputs in their overrides of GetContextInputs() + ContextInputs = CompAsStaticInterface->GetContextInputs(); + } + else { - ContextInputs = PinSupplierInterface->GetContextInputs(); + // Only the BP class implements the interface, so we call it here. + ContextInputs = IFlowContextPinSupplierInterface::Execute_K2_GetContextInputs(ResolvedComp); } } @@ -239,18 +242,29 @@ TArray UFlowNode_ExecuteComponent::GetContextInputs() const TArray UFlowNode_ExecuteComponent::GetContextOutputs() const { TArray ContextOutputs; - if (UActorComponent* ResolvedComp = GetResolvedComponent()) + const UActorComponent* ResolvedComp = GetResolvedComponent(); + + if (!IsValid(ResolvedComp)) { - if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - ContextOutputs = PinSupplierInterface->GetContextOutputs(); - } + // If we don't have a Resolved Component object yet, try to find the expected component object. For + // injected components this will return the CDO + ResolvedComp = TryGetExpectedComponent(); } - else if (const UActorComponent* ExpectedComponent = TryGetExpectedComponent()) + + // NOTE: we have to call GetClass on the Resolved Component; not StaticClass(). This makes it so we can handle classes that only implement the + // interface in Blueprints. + if (ResolvedComp && ResolvedComp->GetClass()->ImplementsInterface(UFlowContextPinSupplierInterface::StaticClass())) { - if (const IFlowContextPinSupplierInterface* PinSupplierInterface = Cast(ExpectedComponent)) + if (const IFlowContextPinSupplierInterface* CompAsStaticInterface = Cast(ResolvedComp)) + { + // The native (static) class implements the interface, so we call it directly (default implementation provided by the interface will call the K2 BP version of it. + // we assume that the implementor of the interface is responsible for invoking K2_GetContextOutputs in their overrides of GetContextOutputs() + ContextOutputs = CompAsStaticInterface->GetContextOutputs(); + } + else { - ContextOutputs = PinSupplierInterface->GetContextOutputs(); + // Only the BP class implements the interface, so we call it here. + ContextOutputs = IFlowContextPinSupplierInterface::Execute_K2_GetContextOutputs(ResolvedComp); } } @@ -264,277 +278,19 @@ TArray UFlowNode_ExecuteComponent::GetContextOutputs() const return ContextOutputs; } -#endif // WITH_EDITOR - -bool UFlowNode_ExecuteComponent::CanSupplyDataPinValues_Implementation() const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - if (IFlowDataPinValueSupplierInterface::Execute_CanSupplyDataPinValues(ResolvedComp)) - { - return true; - } - } - } - - return Super::CanSupplyDataPinValues_Implementation(); -} - -FFlowDataPinResult_Bool UFlowNode_ExecuteComponent::TrySupplyDataPinAsBool_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Bool PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsBool(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsBool_Implementation(PinName); -} - -FFlowDataPinResult_Int UFlowNode_ExecuteComponent::TrySupplyDataPinAsInt_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Int PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInt(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsInt_Implementation(PinName); -} - -FFlowDataPinResult_Float UFlowNode_ExecuteComponent::TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Float PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsFloat(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsFloat_Implementation(PinName); -} - -FFlowDataPinResult_Name UFlowNode_ExecuteComponent::TrySupplyDataPinAsName_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Name PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsName(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsName_Implementation(PinName); -} -FFlowDataPinResult_String UFlowNode_ExecuteComponent::TrySupplyDataPinAsString_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_String PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsString(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsString_Implementation(PinName); -} - -FFlowDataPinResult_Text UFlowNode_ExecuteComponent::TrySupplyDataPinAsText_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Text PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsText(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsText_Implementation(PinName); -} - -FFlowDataPinResult_Enum UFlowNode_ExecuteComponent::TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Enum PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsEnum(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsEnum_Implementation(PinName); -} - -FFlowDataPinResult_Vector UFlowNode_ExecuteComponent::TrySupplyDataPinAsVector_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Vector PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsVector(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsVector_Implementation(PinName); -} - -FFlowDataPinResult_Rotator UFlowNode_ExecuteComponent::TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Rotator PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsRotator(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsRotator_Implementation(PinName); -} - -FFlowDataPinResult_Transform UFlowNode_ExecuteComponent::TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Transform PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsTransform(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsTransform_Implementation(PinName); -} - -FFlowDataPinResult_GameplayTag UFlowNode_ExecuteComponent::TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_GameplayTag PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTag(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsGameplayTag_Implementation(PinName); -} - -FFlowDataPinResult_GameplayTagContainer UFlowNode_ExecuteComponent::TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_GameplayTagContainer PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTagContainer(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsGameplayTagContainer_Implementation(PinName); -} - -FFlowDataPinResult_InstancedStruct UFlowNode_ExecuteComponent::TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_InstancedStruct PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInstancedStruct(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } - - return Super::TrySupplyDataPinAsInstancedStruct_Implementation(PinName); -} +#endif // WITH_EDITOR -FFlowDataPinResult_Object UFlowNode_ExecuteComponent::TrySupplyDataPinAsObject_Implementation(const FName& PinName) const +void UFlowNode_ExecuteComponent::GatherPotentialPropertyOwnersForDataPins(TArray& InOutOwners) const { - if (UActorComponent* ResolvedComp = GetResolvedComponent()) - { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Object PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsObject(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } - } + Super::GatherPotentialPropertyOwnersForDataPins(InOutOwners); - return Super::TrySupplyDataPinAsObject_Implementation(PinName); -} - -FFlowDataPinResult_Class UFlowNode_ExecuteComponent::TrySupplyDataPinAsClass_Implementation(const FName& PinName) const -{ - if (UActorComponent* ResolvedComp = GetResolvedComponent()) + // Can also source properties from the resolved component (runtime) or expected component (in-editor) + const UActorComponent* ResolvedComp = GetResolvedOrExpectedComponent(); + if (IsValid(ResolvedComp)) { - if (IFlowDataPinValueSupplierInterface* PinSupplierInterface = Cast(ResolvedComp)) - { - const FFlowDataPinResult_Class PinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsClass(ResolvedComp, PinName); - if (PinResult.Result == EFlowDataPinResolveResult::Success) - { - return PinResult; - } - } + InOutOwners.AddUnique(ResolvedComp); } - - return Super::TrySupplyDataPinAsClass_Implementation(PinName); } bool UFlowNode_ExecuteComponent::TryInjectComponent() @@ -624,6 +380,25 @@ bool UFlowNode_ExecuteComponent::TryInjectComponent() return true; } +const UActorComponent* UFlowNode_ExecuteComponent::GetResolvedOrExpectedComponent() const +{ + const UActorComponent* ResolvedComp = ComponentRef.GetResolvedComponent(); + if (IsValid(ResolvedComp)) + { + return ResolvedComp; + } + +#if WITH_EDITOR + const UActorComponent* ExpectedComp = TryGetExpectedComponent(); + if (IsValid(ExpectedComp)) + { + return ExpectedComp; + } +#endif + + return nullptr; +} + UActorComponent* UFlowNode_ExecuteComponent::TryResolveComponent() { UActorComponent* ResolvedComp = ComponentRef.GetResolvedComponent(); @@ -753,7 +528,7 @@ EDataValidationResult UFlowNode_ExecuteComponent::ValidateNode() const bool bHasComponent = ComponentRef.IsConfigured(); if (!bHasComponent) { - ValidationLog.Error(TEXT("ExectuteComponent requires a valid Compoennt reference"), this); + ValidationLog.Error(TEXT("ExecuteComponent requires a valid Compoennt reference"), this); return EDataValidationResult::Invalid; } @@ -816,7 +591,7 @@ TSubclassOf UFlowNode_ExecuteComponent::TryGetExpectedActorOwnerClass() return nullptr; } -FText UFlowNode_ExecuteComponent::GetNodeTitle() const +FText UFlowNode_ExecuteComponent::K2_GetNodeTitle_Implementation() const { if (UFlowSettings::Get()->bUseAdaptiveNodeTitles) { @@ -868,7 +643,7 @@ FText UFlowNode_ExecuteComponent::GetNodeTitle() const } } - return Super::GetNodeTitle(); + return Super::K2_GetNodeTitle_Implementation(); } #endif // WITH_EDITOR diff --git a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp index 9cfbd4f2b..fa1b636a2 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp @@ -75,9 +75,10 @@ TArray UFlowNode_PlayLevelSequence::GetContextOutputs() const { for (const FString& EventName : FlowSection->GetAllEntryPoints()) { - if (!EventName.IsEmpty() && !Pins.Contains(EventName)) + FFlowPin NewEventPin(EventName); + if (!EventName.IsEmpty() && !Pins.Contains(NewEventPin)) { - Pins.Emplace(EventName); + Pins.Emplace(NewEventPin); } } } diff --git a/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp b/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp index 5a6f66d0a..0f1d20f47 100644 --- a/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp +++ b/Source/Flow/Private/Nodes/Developer/FlowNode_Log.cpp @@ -29,62 +29,81 @@ UFlowNode_Log::UFlowNode_Log(const FObjectInitializer& ObjectInitializer) void UFlowNode_Log::ExecuteInput(const FName& PinName) { // Get the Message from either the default (Message property) or the data pin (if connected) - FFlowDataPinResult_String MessageResult = TryResolveDataPinAsString(GET_MEMBER_NAME_CHECKED(UFlowNode_Log, Message)); + FString ResolvedMessage; + const EFlowDataPinResolveResult MessageResult = TryResolveDataPinValue(GET_MEMBER_NAME_CHECKED(ThisClass, Message), ResolvedMessage); - if (MessageResult.Result == EFlowDataPinResolveResult::FailedMissingPin) + // #FlowDataPinLegacy - retire this backward compatibility when we remove legacy data pin support? + FLOW_ASSERT_ENUM_MAX(EFlowDataPinResolveResult, 8); + if (MessageResult == EFlowDataPinResolveResult::FailedUnknownPin) { // Handle lookup of a FlowNode_Log that predated DataPins - MessageResult.Result = EFlowDataPinResolveResult::Success; - MessageResult.SetValue(Message); + ResolvedMessage = Message; } + // -- // Format Message with named properties - FText FormattedText; - if (TryFormatTextWithNamedPropertiesAsParameters(FText::FromString(MessageResult.Value), FormattedText)) - { - MessageResult.Value = FormattedText.ToString(); - } + FText FormattedMessage = FText::FromString(ResolvedMessage); + (void) TryFormatTextWithNamedPropertiesAsParameters(FormattedMessage, FormattedMessage); // Display the message - check(MessageResult.Result == EFlowDataPinResolveResult::Success); switch (Verbosity) { case EFlowLogVerbosity::Error: - UE_LOG(LogFlow, Error, TEXT("%s"), *MessageResult.Value); + UE_LOG(LogFlow, Error, TEXT("%s"), *FormattedMessage.ToString()); break; case EFlowLogVerbosity::Warning: - UE_LOG(LogFlow, Warning, TEXT("%s"), *MessageResult.Value); + UE_LOG(LogFlow, Warning, TEXT("%s"), *FormattedMessage.ToString()); break; case EFlowLogVerbosity::Display: - UE_LOG(LogFlow, Display, TEXT("%s"), *MessageResult.Value); + UE_LOG(LogFlow, Display, TEXT("%s"), *FormattedMessage.ToString()); break; case EFlowLogVerbosity::Log: - UE_LOG(LogFlow, Log, TEXT("%s"), *MessageResult.Value); + UE_LOG(LogFlow, Log, TEXT("%s"), *FormattedMessage.ToString()); break; case EFlowLogVerbosity::Verbose: - UE_LOG(LogFlow, Verbose, TEXT("%s"), *MessageResult.Value); + UE_LOG(LogFlow, Verbose, TEXT("%s"), *FormattedMessage.ToString()); break; case EFlowLogVerbosity::VeryVerbose: - UE_LOG(LogFlow, VeryVerbose, TEXT("%s"), *MessageResult.Value); + UE_LOG(LogFlow, VeryVerbose, TEXT("%s"), *FormattedMessage.ToString()); break; default: ; } if (bPrintToScreen) { - GEngine->AddOnScreenDebugMessage(-1, Duration, TextColor, MessageResult.Value); + GEngine->AddOnScreenDebugMessage(-1, Duration, TextColor, FormattedMessage.ToString()); } TriggerFirstOutput(true); } #if WITH_EDITOR +void UFlowNode_Log::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChainEvent) +{ + auto& Property = PropertyChainEvent.PropertyChain.GetActiveMemberNode()->GetValue(); + + const bool bChangedOutputProperties = Property->GetFName() == GET_MEMBER_NAME_CHECKED(ThisClass, NamedProperties); + if (bChangedOutputProperties) + { + for (FFlowNamedDataPinProperty& NamedProperty : NamedProperties) + { + const UScriptStruct* ScriptStruct = NamedProperty.DataPinValue.GetScriptStruct(); + if (IsValid(ScriptStruct) && ScriptStruct->IsChildOf()) + { + FFlowDataPinValue& Value = NamedProperty.DataPinValue.GetMutable(); + Value.bIsInputPin = true; + } + } + } + + Super::PostEditChangeChainProperty(PropertyChainEvent); +} void UFlowNode_Log::UpdateNodeConfigText_Implementation() { constexpr bool bErrorIfInputPinNotFound = false; - const bool bIsInputConnected = IsInputConnected(GET_MEMBER_NAME_CHECKED(UFlowNode_Log, Message), bErrorIfInputPinNotFound); + const bool bIsInputConnected = IsInputConnected(GET_MEMBER_NAME_CHECKED(ThisClass, Message), bErrorIfInputPinNotFound); if (bIsInputConnected) { diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index 048339277..76280720c 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -6,7 +6,9 @@ #include "FlowAsset.h" #include "FlowSettings.h" #include "Interfaces/FlowNodeWithExternalDataPinSupplierInterface.h" -#include "Types/FlowDataPinProperties.h" +#include "Types/FlowPinType.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowAutoDataPinsWorkingData.h" #include "Components/ActorComponent.h" #if WITH_EDITOR @@ -64,16 +66,57 @@ void UFlowNode::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEve } } +EDataValidationResult UFlowNode::ValidateNode() +{ + EDataValidationResult ValidationResult = EDataValidationResult::Valid; + + // Validate that output and input pins have unique names + TSet UniquePinNames; + ValidateFlowPinArrayIsUnique(InputPins, UniquePinNames, ValidationResult); + ValidateFlowPinArrayIsUnique(OutputPins, UniquePinNames, ValidationResult); + + return ValidationResult; +} + +void UFlowNode::ValidateFlowPinArrayIsUnique(const TArray& FlowPins, TSet& InOutUniquePinNames, EDataValidationResult& InOutResult) +{ + for (const FFlowPin& FlowPin : FlowPins) + { + const FName& ThisPinName = FlowPin.PinName; + if (InOutUniquePinNames.Contains(ThisPinName)) + { + ValidationLog.Warning( + *FString::Printf( + TEXT("All pin names on a flow node must be unique, pin name %s is duplicated"), + *ThisPinName.ToString()), + this); + + InOutResult = EDataValidationResult::Invalid; + } + else + { + InOutUniquePinNames.Add(FlowPin.PinName); + } + } +} + +#endif + void UFlowNode::PostLoad() { Super::PostLoad(); +#if WITH_EDITOR // fix Class Default Object FixNode(nullptr); -} - #endif + if (!HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject)) + { + FixupDataPinTypes(); + } +} + bool UFlowNode::IsSupportedInputPinName(const FName& PinName) const { const FFlowPin* InputPin = FindFlowPinByName(PinName, InputPins); @@ -364,11 +407,6 @@ void UFlowNode::RemoveUserOutput(const FName& PinName) } } -void UFlowNode::SetPinNameToBoundPropertyNameMap(const TMap& Map) -{ - PinNameToBoundPropertyNameMap = Map; -} - void UFlowNode::SetAutoInputDataPins(const TArray& AutoInputPins) { AutoInputDataPins = AutoInputPins; @@ -379,30 +417,163 @@ void UFlowNode::SetAutoOutputDataPins(const TArray& AutoOutputPins) AutoOutputDataPins = AutoOutputPins; } +void UFlowNode::SetConnections(const TMap& InConnections) +{ + TMap OldConnections = Connections; + Connections = InConnections; + OnConnectionsChanged(OldConnections); +} + #endif // WITH_EDITOR -bool UFlowNode::CanSupplyDataPinValues_Implementation() const +// #FlowDataPinLegacy +void UFlowNode::FixupDataPinTypes() +{ + FixupDataPinTypesForArray(InputPins); + FixupDataPinTypesForArray(OutputPins); +#if WITH_EDITOR + FixupDataPinTypesForArray(AutoInputDataPins); + FixupDataPinTypesForArray(AutoOutputDataPins); +#endif +} + +void UFlowNode::FixupDataPinTypesForArray(TArray& MutableDataPinArray) +{ + for (FFlowPin& MutableFlowPin : MutableDataPinArray) + { + FixupDataPinTypesForPin(MutableFlowPin); + } +} + +void UFlowNode::FixupDataPinTypesForPin(FFlowPin& MutableDataPin) +{ + const FFlowPinTypeName NewPinTypeName = FFlowPin::GetPinTypeNameForLegacyPinType(MutableDataPin.PinType); + + if (!NewPinTypeName.IsNone()) + { + MutableDataPin.SetPinTypeName(NewPinTypeName); + } + + if (MutableDataPin.GetPinTypeName().IsNone()) + { + // Ensure we have a pin type even if the enum was invalid before + MutableDataPin.SetPinTypeName(FFlowPinType_Exec::GetPinTypeNameStatic()); + } + + MutableDataPin.PinType = EFlowPinType::Invalid; +} + +// -- + +bool UFlowNode::TryFindPropertyByPinName( + const UObject& PropertyOwnerObject, + const FName& PinName, + const FProperty*& OutFoundProperty, + TInstancedStruct& OutFoundInstancedStruct) const +{ + return UFlowNode::TryFindPropertyByPinName_Static(PropertyOwnerObject, PinName, OutFoundProperty, OutFoundInstancedStruct); +} + +bool UFlowNode::TryFindPropertyByPinName_Static( + const UObject& PropertyOwnerObject, + const FName& PinName, + const FProperty*& OutFoundProperty, + TInstancedStruct& OutFoundInstancedStruct) { - if (!PinNameToBoundPropertyNameMap.IsEmpty()) + // Try direct property match + OutFoundProperty = PropertyOwnerObject.GetClass()->FindPropertyByName(PinName); + if (OutFoundProperty) { + const FStructProperty* StructProperty = CastField(OutFoundProperty); + if (StructProperty && StructProperty->Struct->IsChildOf(FFlowDataPinValue::StaticStruct())) + { + // Initialize to match property's struct + OutFoundInstancedStruct.InitializeAsScriptStruct(StructProperty->Struct); + + StructProperty->GetValue_InContainer(&PropertyOwnerObject, OutFoundInstancedStruct.GetMutableMemory()); + return true; + } + + // Raw property (e.g., bool, TArray) is valid return true; } return false; } +void UFlowNode::GatherPotentialPropertyOwnersForDataPins(TArray& InOutOwners) const +{ + // TODO (gtaylor) Also add any AddOns that can supply data pins, when/if we want to add AddOn data pin supply support + + InOutOwners.AddUnique(this); +} + +FFlowDataPinResult UFlowNode::TrySupplyDataPin_Implementation(FName PinName) const +{ + const FFlowPin* FlowPin = FindOutputPinByName(PinName); + if (!FlowPin) + { + // Also look in the Input Pins (for supplying default values for unconnected pins) + FlowPin = FindInputPinByName(PinName); + if (!FlowPin) + { + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); + } + } + + const FFlowPinType* DataPinType = FlowPin->ResolveFlowPinType(); + if (!DataPinType) + { + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedMismatchedType); + } + + FFlowDataPinResult SuppliedResult; + if (TryGatherPropertyOwnersAndPopulateResult(PinName, *DataPinType, *FlowPin, SuppliedResult)) + { + return SuppliedResult; + } + + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); +} + +bool UFlowNode::TryGatherPropertyOwnersAndPopulateResult( + const FName& PinName, + const FFlowPinType& DataPinType, + const FFlowPin& FlowPin, + FFlowDataPinResult& OutSuppliedResult) const +{ + // Gather all of the potential providers for this DataPin + TArray PropertyOwnerObjects; + GatherPotentialPropertyOwnersForDataPins(PropertyOwnerObjects); + + // Look through all of the potential providers + for (const UObject* PropertyOwnerObject : PropertyOwnerObjects) + { + const UFlowNode& FlowNodeThis = *this; + + checkf(IsValid(PropertyOwnerObject), TEXT("Every UObject provided by GatherPotentialPropertyOwnersForDataPins must be valid")); + + if (DataPinType.PopulateResult(*PropertyOwnerObject, FlowNodeThis, FlowPin, OutSuppliedResult)) + { + return true; + } + } + + return false; +} + bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName( const FName& PinName, - TArray& InOutPinValueSupplierDatas) const + TFlowPinValueSupplierDataArray& InOutPinValueSupplierDatas) const { const IFlowDataPinValueSupplierInterface* ThisAsPinValueSupplier = Cast(this); - // This function will build the priority-ordered array of data suppliers for a given PinName. + // This function will build the inverse-priority-ordered array of data suppliers for a given PinName. // It works in two modes: // - Standard case - Add a connected node as the priority supplier, and this node as the default value supplier // - Exception case - for External data supplied nodes, we recurse (below) to crawl further and add the supplier // for the external supplier's node. In practice, this is a node (A) connected to a Start node, which is - // supplied by its outer SubGraph node, which sources its values from the nodes tha are connected to the external inputs + // supplied by its outer SubGraph node, which sources its values from the nodes that are connected to the external inputs // that the subgraph node added as inputs for its instanced subgraph). The external supplier's value has top priority, // then it falls to the standard case sources (as above). @@ -415,7 +586,7 @@ bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName( NewPinValueSupplier.SupplierPinName = PinName; // Put this node as the backup supplier - InOutPinValueSupplierDatas.Insert(NewPinValueSupplier, 0); + InOutPinValueSupplierDatas.Add(NewPinValueSupplier); } // If the pin is connected, try to add the connected node as the priority supplier @@ -434,7 +605,7 @@ bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName( { ConnectedPinValueSupplier.PinValueSupplier = SupplierFlowNodeAsInterface; - InOutPinValueSupplierDatas.Insert(ConnectedPinValueSupplier, 0); + InOutPinValueSupplierDatas.Add(ConnectedPinValueSupplier); } // Exception case for nodes with external suppliers, recurse here to crawl further @@ -452,49 +623,6 @@ bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName( return !InOutPinValueSupplierDatas.IsEmpty(); } -bool UFlowNode::TryFindPropertyByPinName( - const FName& PinName, - const FProperty*& OutFoundProperty, - TInstancedStruct& OutFoundInstancedStruct, - EFlowDataPinResolveResult& InOutResult) const -{ - const FName* RemappedPinName = PinNameToBoundPropertyNameMap.Find(PinName); - if (!RemappedPinName) - { - InOutResult = EFlowDataPinResolveResult::FailedUnknownPin; - - return false; - } - - if (!TryFindPropertyByRemappedPinName(*RemappedPinName, OutFoundProperty, OutFoundInstancedStruct, InOutResult)) - { - return false; - } - - return true; -} - -bool UFlowNode::TryFindPropertyByRemappedPinName( - const FName& RemappedPinName, - const FProperty*& OutFoundProperty, - TInstancedStruct& OutFoundInstancedStruct, - EFlowDataPinResolveResult& InOutResult) const -{ - const UClass* ThisClass = GetClass(); - OutFoundProperty = ThisClass->FindPropertyByName(RemappedPinName); - - if (!OutFoundProperty) - { - LogError(FString::Printf(TEXT("Could not find property %s, but expected to"), *RemappedPinName.ToString()), EFlowOnScreenMessageType::Temporary); - - InOutResult = EFlowDataPinResolveResult::FailedWithError; - - return false; - } - - return true; -} - TSet UFlowNode::GatherConnectedNodes() const { TSet Result; @@ -576,16 +704,16 @@ bool UFlowNode::IsInputConnected(const FFlowPin& FlowPin) const return false; } - if (FlowPin.IsDataPin()) - { - return FindConnectedNodeForPinFast(FlowPin.PinName); - } - else + if (FlowPin.IsExecPin()) { // We don't cache the input exec pins for fast lookup in Connections, so use the slow path for them: return FindConnectedNodeForPinSlow(FlowPin.PinName); } + else + { + return FindConnectedNodeForPinFast(FlowPin.PinName); + } } bool UFlowNode::IsOutputConnected(const FFlowPin& FlowPin) const @@ -669,82 +797,30 @@ bool UFlowNode::FindConnectedNodeForPinSlow(const FName& PinName, FGuid* OutGuid return false; } -// Must implement TrySupplyDataPinAs... for every EFlowPinType -FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - -FFlowDataPinResult_Bool UFlowNode::TrySupplyDataPinAsBool_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsType(PinName); -} - -FFlowDataPinResult_Int UFlowNode::TrySupplyDataPinAsInt_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsNumericType(PinName); -} - -FFlowDataPinResult_Float UFlowNode::TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsNumericType(PinName); -} - -FFlowDataPinResult_Name UFlowNode::TrySupplyDataPinAsName_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsAnyTextType(PinName); -} - -FFlowDataPinResult_String UFlowNode::TrySupplyDataPinAsString_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsAnyTextType(PinName); -} - -FFlowDataPinResult_Text UFlowNode::TrySupplyDataPinAsText_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsAnyTextType(PinName); -} - -FFlowDataPinResult_Enum UFlowNode::TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsEnumType(PinName); -} - -FFlowDataPinResult_Vector UFlowNode::TrySupplyDataPinAsVector_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsStructType(PinName); -} - -FFlowDataPinResult_Rotator UFlowNode::TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsStructType(PinName); -} - -FFlowDataPinResult_Transform UFlowNode::TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsStructType(PinName); -} - -FFlowDataPinResult_GameplayTag UFlowNode::TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsStructType(PinName); -} - -FFlowDataPinResult_GameplayTagContainer UFlowNode::TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsStructType(PinName); -} - -FFlowDataPinResult_InstancedStruct UFlowNode::TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsStructType(PinName); -} - -FFlowDataPinResult_Object UFlowNode::TrySupplyDataPinAsObject_Implementation(const FName& PinName) const +TArray UFlowNode::GetKnownConnectionsToPin(const FConnectedPin& Pin) const { - return TrySupplyDataPinAsUObjectType(PinName); -} + TArray ConnectedPins; -FFlowDataPinResult_Class UFlowNode::TrySupplyDataPinAsClass_Implementation(const FName& PinName) const -{ - return TrySupplyDataPinAsUClassType(PinName); + if (Pin.NodeGuid == NodeGuid) + { + const FConnectedPin& Connection = Connections.FindRef(Pin.PinName); + if (Connection.NodeGuid.IsValid()) + { + ConnectedPins.Add(Connection); + } + } + else + { + for (const TPair& Connection : Connections) + { + if (Connection.Value.NodeGuid == Pin.NodeGuid && Connection.Value.PinName == Pin.PinName) + { + ConnectedPins.Emplace(NodeGuid, Connection.Key); + } + } + } + + return ConnectedPins; } void UFlowNode::RecursiveFindNodesByClass(UFlowNode* Node, const TSubclassOf Class, uint8 Depth, TArray& OutNodes) @@ -888,7 +964,7 @@ void UFlowNode::TriggerOutput(const FName PinName, const bool bFinish /*= false* if (OutputPins.Contains(PinName) && Connections.Contains(PinName)) { const FConnectedPin FlowPin = GetConnection(PinName); - GetFlowAsset()->TriggerInput(FlowPin.NodeGuid, FlowPin.PinName); + GetFlowAsset()->TriggerInput(FlowPin.NodeGuid, FlowPin.PinName, FConnectedPin(GetGuid(), PinName)); } } @@ -1015,6 +1091,21 @@ TArray UFlowNode::GetPinRecords(const FName& PinName, const EEdGraph } } +void UFlowNode::AutoGenerateDataPins(FFlowAutoDataPinsWorkingData& InOutWorkingData) const +{ + // Gather all of the potential providers for this DataPin + TArray PropertyOwnerObjects; + GatherPotentialPropertyOwnersForDataPins(PropertyOwnerObjects); + + // GenerateDataPins for all of the potential providers + for (const UObject* PropertyOwnerObject : PropertyOwnerObjects) + { + checkf(IsValid(PropertyOwnerObject), TEXT("Every UObject provided by GatherPotentialPropertyOwnersForDataPins must be valid")); + + InOutWorkingData.AddFlowDataPinsForClassProperties(*PropertyOwnerObject); + } +} + #endif FString UFlowNode::GetIdentityTagDescription(const FGameplayTag& Tag) diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 85420b9ba..8b243fe70 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -2,13 +2,17 @@ #include "Nodes/FlowNodeBase.h" -#include "AddOns/FlowNodeAddOn.h" #include "FlowAsset.h" #include "FlowLogChannels.h" #include "FlowSubsystem.h" #include "FlowTypes.h" +#include "AddOns/FlowNodeAddOn.h" #include "Interfaces/FlowDataPinValueSupplierInterface.h" +#include "Interfaces/FlowNamedPropertiesSupplierInterface.h" #include "Types/FlowArray.h" +#include "Types/FlowDataPinResults.h" +#include "Types/FlowPinTypesStandard.h" +#include "Types/FlowNamedDataPinProperty.h" #include "Components/ActorComponent.h" #if WITH_EDITOR @@ -319,39 +323,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 +345,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 @@ -697,61 +627,6 @@ bool UFlowNodeBase::GetDynamicTitleColor(FLinearColor& OutColor) const return false; } -FText UFlowNodeBase::GetNodeTitle() const -{ - if (GetClass()->ClassGeneratedBy) - { - const FString& BlueprintTitle = Cast(GetClass()->ClassGeneratedBy)->BlueprintDisplayName; - if (!BlueprintTitle.IsEmpty()) - { - return FText::FromString(BlueprintTitle); - } - } - - static const FName NAME_DisplayName(TEXT("DisplayName")); - if (bDisplayNodeTitleWithoutPrefix && !GetClass()->HasMetaData(NAME_DisplayName)) - { - return GetGeneratedDisplayName(); - } - - return GetClass()->GetDisplayNameText(); -} - -FText UFlowNodeBase::GetNodeToolTip() const -{ - if (GetClass()->ClassGeneratedBy) - { - const FString& BlueprintToolTip = Cast(GetClass()->ClassGeneratedBy)->BlueprintDescription; - if (!BlueprintToolTip.IsEmpty()) - { - return FText::FromString(BlueprintToolTip); - } - } - - static const FName NAME_Tooltip(TEXT("Tooltip")); - if (bDisplayNodeTitleWithoutPrefix && !GetClass()->HasMetaData(NAME_Tooltip)) - { - return GetGeneratedDisplayName(); - } - - // GetClass()->GetToolTipText() can return meta = (DisplayName = ... ), but ignore BlueprintDisplayName even if it is BP Node - if (GetClass()->ClassGeneratedBy) - { - const FString& BlueprintTitle = Cast(GetClass()->ClassGeneratedBy)->BlueprintDisplayName; - if (!BlueprintTitle.IsEmpty()) - { - return FText::FromString(BlueprintTitle); - } - } - - return GetClass()->GetToolTipText(); -} - -FText UFlowNodeBase::GetNodeConfigText() const -{ - return DevNodeConfigText; -} - FText UFlowNodeBase::GetGeneratedDisplayName() const { static const FName NAME_GeneratedDisplayName(TEXT("GeneratedDisplayName")); @@ -825,15 +700,116 @@ FString UFlowNodeBase::GetNodeDescription() const { return K2_GetNodeDescription(); } + +bool UFlowNodeBase::CanModifyFlowDataPinType() const +{ + return !IsPlacedInFlowAsset() || IsFlowNamedPropertiesSupplier(); +} + +bool UFlowNodeBase::ShowFlowDataPinValueInputPinCheckbox() const +{ + const bool bIsPlacedInFlowAsset = IsPlacedInFlowAsset(); + return !bIsPlacedInFlowAsset; +} + +bool UFlowNodeBase::ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const +{ + const bool bIsPlacedInFlowAsset = IsPlacedInFlowAsset(); + const bool bIsFlowNamedPropertiesSupplier = IsFlowNamedPropertiesSupplier(); + return !bIsPlacedInFlowAsset || bIsFlowNamedPropertiesSupplier; +} + +bool UFlowNodeBase::CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const +{ + const bool bIsPlacedInFlowAsset = IsPlacedInFlowAsset(); + const bool bIsFlowNamedPropertiesSupplier = IsFlowNamedPropertiesSupplier(); + return !bIsPlacedInFlowAsset || bIsFlowNamedPropertiesSupplier; +} + +bool UFlowNodeBase::IsPlacedInFlowAsset() const +{ + return GetFlowAsset() != nullptr; +} + +bool UFlowNodeBase::IsFlowNamedPropertiesSupplier() const +{ + return Implements(); +} + +#endif + +FText UFlowNodeBase::K2_GetNodeTitle_Implementation() const +{ +#if WITH_EDITOR + if (GetClass()->ClassGeneratedBy) + { + const FString& BlueprintTitle = Cast(GetClass()->ClassGeneratedBy)->BlueprintDisplayName; + if (!BlueprintTitle.IsEmpty()) + { + return FText::FromString(BlueprintTitle); + } + } + + static const FName NAME_DisplayName(TEXT("DisplayName")); + if (bDisplayNodeTitleWithoutPrefix && !GetClass()->HasMetaData(NAME_DisplayName)) + { + return GetGeneratedDisplayName(); + } + + return GetClass()->GetDisplayNameText(); +#else + return FText::GetEmpty(); +#endif +} + +FText UFlowNodeBase::K2_GetNodeToolTip_Implementation() const +{ +#if WITH_EDITOR + if (GetClass()->ClassGeneratedBy) + { + const FString& BlueprintToolTip = Cast(GetClass()->ClassGeneratedBy)->BlueprintDescription; + if (!BlueprintToolTip.IsEmpty()) + { + return FText::FromString(BlueprintToolTip); + } + } + + static const FName NAME_Tooltip(TEXT("Tooltip")); + if (bDisplayNodeTitleWithoutPrefix && !GetClass()->HasMetaData(NAME_Tooltip)) + { + return GetGeneratedDisplayName(); + } + + // GetClass()->GetToolTipText() can return meta = (DisplayName = ... ), but ignore BlueprintDisplayName even if it is BP Node + if (GetClass()->ClassGeneratedBy) + { + const FString& BlueprintTitle = Cast(GetClass()->ClassGeneratedBy)->BlueprintDisplayName; + if (!BlueprintTitle.IsEmpty()) + { + return FText::FromString(BlueprintTitle); + } + } + + return GetClass()->GetToolTipText(); +#else + return FText::GetEmpty(); #endif +} + +FText UFlowNodeBase::GetNodeConfigText() const +{ +#if WITH_EDITORONLY_DATA + return DevNodeConfigText; +#else + return FText::GetEmpty(); +#endif // WITH_EDITORONLY_DATA +} void UFlowNodeBase::SetNodeConfigText(const FText& NodeConfigText) { #if WITH_EDITOR if (!NodeConfigText.EqualTo(DevNodeConfigText)) { - Modify(); - DevNodeConfigText = NodeConfigText; } #endif // WITH_EDITOR @@ -956,569 +932,186 @@ bool UFlowNodeBase::BuildMessage(FString& Message) const bool UFlowNodeBase::TryAddValueToFormatNamedArguments(const FFlowNamedDataPinProperty& NamedDataPinProperty, FFormatNamedArguments& InOutArguments) const { - const FFlowDataPinProperty* FlowDataPinProperty = NamedDataPinProperty.DataPinProperty.GetPtr(); - if (!FlowDataPinProperty) - { - return false; - } - - const EFlowPinType FlowPinType = FlowDataPinProperty->GetFlowPinType(); + const FFlowDataPinValue& DataPinValue = NamedDataPinProperty.DataPinValue.Get(); - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - switch (FlowPinType) + const FFlowPinTypeName PinTypeName = DataPinValue.GetPinTypeName(); + if (PinTypeName.IsNone()) { - case EFlowPinType::Exec: - { - LogError(TEXT("Cannot add Exec pin value to FFormatNamedArguments")); - } - break; - - case EFlowPinType::InstancedStruct: - { - LogError(TEXT("Cannot add InstancedStruct pin value to FFormatNamedArguments")); - } - break; - - case EFlowPinType::Bool: - { - const FFlowDataPinResult_Bool ResolvedResult = TryResolveDataPinAsBool(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); - - return true; - } - } - break; - - case EFlowPinType::Int: - { - const FFlowDataPinResult_Int ResolvedResult = TryResolveDataPinAsInt(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); - - return true; - } - } - break; - - case EFlowPinType::Float: - { - const FFlowDataPinResult_Float ResolvedResult = TryResolveDataPinAsFloat(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); - - return true; - } - } - break; - - case EFlowPinType::Name: - { - const FFlowDataPinResult_Name ResolvedResult = TryResolveDataPinAsName(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); - - return true; - } - } - break; - - case EFlowPinType::String: - { - const FFlowDataPinResult_String ResolvedResult = TryResolveDataPinAsString(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value))); - - return true; - } - } - break; - - case EFlowPinType::Text: - { - const FFlowDataPinResult_Text ResolvedResult = TryResolveDataPinAsText(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(ResolvedResult.Value)); - - return true; - } - } - break; - - case EFlowPinType::Enum: - { - const FFlowDataPinResult_Enum ResolvedResult = TryResolveDataPinAsEnum(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); - - return true; - } - } - break; - - case EFlowPinType::Vector: - { - const FFlowDataPinResult_Vector ResolvedResult = TryResolveDataPinAsVector(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); - - return true; - } - } - break; - - case EFlowPinType::Rotator: - { - const FFlowDataPinResult_Rotator ResolvedResult = TryResolveDataPinAsRotator(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); - - return true; - } - } - break; - - case EFlowPinType::Transform: - { - const FFlowDataPinResult_Transform ResolvedResult = TryResolveDataPinAsTransform(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); - - return true; - } - } - break; - - case EFlowPinType::GameplayTag: - { - const FFlowDataPinResult_GameplayTag ResolvedResult = TryResolveDataPinAsGameplayTag(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); - - return true; - } - } - break; - - case EFlowPinType::GameplayTagContainer: - { - const FFlowDataPinResult_GameplayTagContainer ResolvedResult = TryResolveDataPinAsGameplayTagContainer(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value.ToString()))); - - return true; - } - } - break; - - case EFlowPinType::Object: - { - const FFlowDataPinResult_Object ResolvedResult = TryResolveDataPinAsObject(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - if (IsValid(ResolvedResult.Value)) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.Value->GetName()))); - } - else - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(TEXT("null")))); - } - - return true; - } - } - break; - - case EFlowPinType::Class: - { - const FFlowDataPinResult_Class ResolvedResult = TryResolveDataPinAsClass(NamedDataPinProperty.Name); - if (ResolvedResult.Result == EFlowDataPinResolveResult::Success) - { - InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FFormatArgumentValue(FText::FromString(ResolvedResult.GetAsSoftClass().ToString()))); - - return true; - } - } - break; - - default: break; - } - - return false; -} - -EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinPrerequisites(const FName& PinName, const UFlowNode*& FlowNode, const FFlowPin*& FlowPin, EFlowPinType PinType) const -{ - FlowNode = GetFlowNodeSelfOrOwner(); - - if (!IsValid(FlowNode)) - { - LogError(FString::Printf(TEXT("Unexpected for %s to not have an associated FlowNode"), *GetName()), EFlowOnScreenMessageType::Temporary); - - return EFlowDataPinResolveResult::FailedWithError; + return false; } - FlowPin = FindFlowPinByName(PinName, FlowNode->GetInputPins()); - if (!FlowPin) + const FFlowPinType* PinType = FFlowPinType::LookupPinType(PinTypeName); + if (!PinType) { - return EFlowDataPinResolveResult::FailedMissingPin; + return false; } - if (FlowPin->GetPinType() != PinType) + FFormatArgumentValue FormatValue; + if (PinType->ResolveAndFormatPinValue(*this, NamedDataPinProperty.Name, FormatValue)) { - return EFlowDataPinResolveResult::FailedMismatchedType; + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FormatValue); + return true; } - return EFlowDataPinResolveResult::Success; + return false; } -// Must implement TryResolveDataPinAs...() for every EFlowPinType -FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - -template -bool TResolveDataPinWorkingData::TrySetupWorkingData(const FName& PinName, const UFlowNodeBase& FlowNodeBase) +FFlowDataPinResult UFlowNodeBase::TryResolveDataPin(FName PinName) const { - DataPinResult.Result = FlowNodeBase.TryResolveDataPinPrerequisites(PinName, FlowNode, FlowPin, PinType); - if (DataPinResult.Result != EFlowDataPinResolveResult::Success) - { - return false; - } + FFlowDataPinResult DataPinResult(EFlowDataPinResolveResult::Success); - if (!FlowNode->TryGetFlowDataPinSupplierDatasForPinName(FlowPin->PinName, PinValueSupplierDatas)) + const UFlowNode* FlowNode = GetFlowNodeSelfOrOwner(); + UFlowNode::TFlowPinValueSupplierDataArray PinValueSupplierDatas; + if (!FlowNode->TryGetFlowDataPinSupplierDatasForPinName(PinName, PinValueSupplierDatas)) { // If we could not build the PinValueDataSuppliers array, // then the pin must be disconnected and have no default value available. - DataPinResult.Result = EFlowDataPinResolveResult::FailedUnconnected; - return false; - } + DataPinResult.Result = EFlowDataPinResolveResult::FailedWithError; - return true; -} + LogError(FString::Printf(TEXT("DataPin named '%s' could not be supplied with a value."), *PinName.ToString()), EFlowOnScreenMessageType::Temporary); -FFlowDataPinResult_Bool UFlowNodeBase::TryResolveDataPinAsBool(const FName& PinName) const -{ - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; + return DataPinResult; } - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) + // Iterate over the suppliers in inverse order + for (int32 Index = PinValueSupplierDatas.Num() - 1; Index >= 0; --Index) { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsBool(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); + const FFlowPinValueSupplierData& SupplierData = PinValueSupplierDatas[Index]; + + DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPin(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) + if (FlowPinType::IsSuccess(DataPinResult.Result)) { - return WorkData.DataPinResult; + return DataPinResult; } } - return WorkData.DataPinResult; + return DataPinResult; } -FFlowDataPinResult_Int UFlowNodeBase::TryResolveDataPinAsInt(const FName& PinName) const +// #FlowDataPinLegacy +FFlowDataPinResult_Bool UFlowNodeBase::TryResolveDataPinAsBool(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInt(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } + FFlowDataPinResult_Bool BoolResolveResult; + BoolResolveResult.Result = TryResolveDataPinValue(PinName, BoolResolveResult.Value); + return BoolResolveResult; +} - return WorkData.DataPinResult; +FFlowDataPinResult_Int UFlowNodeBase::TryResolveDataPinAsInt(const FName& PinName) const +{ + FFlowDataPinResult_Int ResolveResult; + int32 Value = 0; + ResolveResult.Result = TryResolveDataPinValue(PinName, Value); + ResolveResult.Value = Value; + return ResolveResult; } FFlowDataPinResult_Float UFlowNodeBase::TryResolveDataPinAsFloat(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsFloat(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Float ResolveResult; + float Value = 0.0f; + ResolveResult.Result = TryResolveDataPinValue(PinName, Value); + ResolveResult.Value = Value; + return ResolveResult; } FFlowDataPinResult_Name UFlowNodeBase::TryResolveDataPinAsName(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsName(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Name ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_String UFlowNodeBase::TryResolveDataPinAsString(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsString(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_String ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_Text UFlowNodeBase::TryResolveDataPinAsText(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsText(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Text ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_Enum UFlowNodeBase::TryResolveDataPinAsEnum(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) + const FFlowDataPinResult DataPinResult = TryResolveDataPin(PinName); + if (!FlowPinType::IsSuccess(DataPinResult.Result)) { - return WorkData.DataPinResult; + return FFlowDataPinResult_Enum(DataPinResult.Result); } - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsEnum(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); + const FFlowDataPinValue_Enum& Wrapper = DataPinResult.ResultValue.Get(); - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } + if (Wrapper.Values.IsEmpty()) + { + return FFlowDataPinResult_Enum(EFlowDataPinResolveResult::FailedInsufficientValues); } - return WorkData.DataPinResult; + const FFlowDataPinResult_Enum ResolveResult(Wrapper.Values[0], Wrapper.EnumClass.LoadSynchronous()); + return ResolveResult; } FFlowDataPinResult_Vector UFlowNodeBase::TryResolveDataPinAsVector(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsVector(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Vector ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_Rotator UFlowNodeBase::TryResolveDataPinAsRotator(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsRotator(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Rotator ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_Transform UFlowNodeBase::TryResolveDataPinAsTransform(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsTransform(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Transform ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_GameplayTag UFlowNodeBase::TryResolveDataPinAsGameplayTag(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTag(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_GameplayTag ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_GameplayTagContainer UFlowNodeBase::TryResolveDataPinAsGameplayTagContainer(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTagContainer(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_GameplayTagContainer ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_InstancedStruct UFlowNodeBase::TryResolveDataPinAsInstancedStruct(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInstancedStruct(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_InstancedStruct ResolveResult; + ResolveResult.Result = TryResolveDataPinValue(PinName, ResolveResult.Value); + return ResolveResult; } FFlowDataPinResult_Object UFlowNodeBase::TryResolveDataPinAsObject(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsObject(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Object ResolveResult; + TObjectPtr Value = nullptr; + ResolveResult.Result = TryResolveDataPinValue(PinName, Value); + ResolveResult.Value = Value; + return ResolveResult; } FFlowDataPinResult_Class UFlowNodeBase::TryResolveDataPinAsClass(const FName& PinName) const { - TResolveDataPinWorkingData WorkData; - if (!WorkData.TrySetupWorkingData(PinName, *this)) - { - return WorkData.DataPinResult; - } - - for (const FFlowPinValueSupplierData& SupplierData : WorkData.PinValueSupplierDatas) - { - WorkData.DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsClass(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Class ResolveResult; + TObjectPtr Value = nullptr; + ResolveResult.Result = TryResolveDataPinValue(PinName, Value); + ResolveResult.SetValueFromObjectPtr(Value); + return ResolveResult; } +// -- \ No newline at end of file diff --git a/Source/Flow/Private/Nodes/FlowPin.cpp b/Source/Flow/Private/Nodes/FlowPin.cpp index 69de7d244..8e9f8ff20 100644 --- a/Source/Flow/Private/Nodes/FlowPin.cpp +++ b/Source/Flow/Private/Nodes/FlowPin.cpp @@ -7,6 +7,8 @@ #include "Misc/DateTime.h" #include "Misc/MessageDialog.h" #include "StructUtils/InstancedStruct.h" +#include "Types/FlowPinType.h" +#include "Types/FlowPinTypesStandard.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowPin) @@ -48,202 +50,155 @@ FORCEINLINE FString FPinRecord::DoubleDigit(const int32 Number) ////////////////////////////////////////////////////////////////////////// // Flow Pin -TArray FFlowPin::FlowPinTypeEnumValuesWithoutSpaces; +bool FFlowPin::IsExecPin() const +{ + return PinTypeName == FFlowPinType_Exec::GetPinTypeNameStatic(); +} + +bool FFlowPin::IsExecPinCategory(const FName& PC) +{ + return PC == FFlowPinType_Exec::GetPinTypeNameStatic().Name; +} const FName FFlowPin::MetadataKey_SourceForOutputFlowPin = "SourceForOutputFlowPin"; const FName FFlowPin::MetadataKey_DefaultForInputFlowPin = "DefaultForInputFlowPin"; const FName FFlowPin::MetadataKey_FlowPinType = "FlowPinType"; -const TArray& FFlowPin::GetFlowPinTypeEnumValuesWithoutSpaces() +void FFlowPin::SetPinTypeName(const FFlowPinTypeName& InTypeName) { - if (FlowPinTypeEnumValuesWithoutSpaces.IsEmpty()) + if (PinTypeName == InTypeName) { - FlowPinTypeEnumValuesWithoutSpaces.Reserve(static_cast(EFlowPinType::Max)); - - // Do a one-time caching of the string-names for this enum, - // since we need to de-spacify it and everything.... - - for (const EFlowPinType PinType : TEnumRange()) - { - FString StringValue = UEnum::GetDisplayValueAsText(PinType).ToString(); - StringValue.RemoveSpacesInline(); - - FlowPinTypeEnumValuesWithoutSpaces.Add(FName(StringValue)); - } + return; } - return FlowPinTypeEnumValuesWithoutSpaces; + PinTypeName = InTypeName; } -bool FFlowPin::ArePinArraysMatchingNamesAndTypes(const TArray& Left, const TArray& Right) +void FFlowPin::TrySetStructSubCategoryObjectFromPinType() { - if (Left.Num() != Right.Num()) + if (PinTypeName == FFlowPinType_Vector::GetPinTypeNameStatic()) { - return false; + PinSubCategoryObject = TBaseStructure::Get(); } - - for (int32 Index = 0; Index < Left.Num(); ++Index) + else if (PinTypeName == FFlowPinType_Rotator::GetPinTypeNameStatic()) { - const FFlowPin& LeftPin = Left[Index]; - const FFlowPin& RightPin = Right[Index]; - - if (!DoPinsMatchNamesAndTypes(LeftPin, RightPin)) + PinSubCategoryObject = TBaseStructure::Get(); + } + else if (PinTypeName == FFlowPinType_Transform::GetPinTypeNameStatic()) + { + PinSubCategoryObject = TBaseStructure::Get(); + } + else if (PinTypeName == FFlowPinType_GameplayTag::GetPinTypeNameStatic()) + { + PinSubCategoryObject = TBaseStructure::Get(); + } + else if (PinTypeName == FFlowPinType_GameplayTagContainer::GetPinTypeNameStatic()) + { + PinSubCategoryObject = TBaseStructure::Get(); + } + else if (PinTypeName == FFlowPinType_InstancedStruct::GetPinTypeNameStatic()) + { + PinSubCategoryObject = TBaseStructure::Get(); + } + else if (PinTypeName == FFlowPinType_Enum::GetPinTypeNameStatic()) + { + // Clear the PinSubCategoryObject if it is not an Enum + const UObject* PinSubCategoryObjectPtr = PinSubCategoryObject.Get(); + if (PinSubCategoryObjectPtr && !PinSubCategoryObjectPtr->IsA()) { - return false; + PinSubCategoryObject = nullptr; } } - - return true; -} - -void FFlowPin::SetPinType(const EFlowPinType InFlowPinType, UObject* SubCategoryObject) -{ - if (PinType == InFlowPinType) + else if (PinTypeName == FFlowPinType_Object::GetPinTypeNameStatic()) { - return; + // Clear the PinSubCategoryObject if it is not an Object + const UObject* PinSubCategoryObjectPtr = PinSubCategoryObject.Get(); + if (PinSubCategoryObjectPtr && !PinSubCategoryObjectPtr->IsA()) + { + PinSubCategoryObject = nullptr; + } + } + else if (PinTypeName == FFlowPinType_Class::GetPinTypeNameStatic()) + { + // Clear the PinSubCategoryObject if it is not a Class + const UObject* PinSubCategoryObjectPtr = PinSubCategoryObject.Get(); + if (PinSubCategoryObjectPtr && !PinSubCategoryObjectPtr->IsA()) + { + PinSubCategoryObject = nullptr; + } + } + else + { + // Clear the PinSubCategoryObject for all PinTypes that do not use it. + PinSubCategoryObject = nullptr; } - - PinType = InFlowPinType; - - PinSubCategoryObject = SubCategoryObject; - - TrySetStructSubCategoryObjectFromPinType(); } -void FFlowPin::TrySetStructSubCategoryObjectFromPinType() +#if WITH_EDITOR +FEdGraphPinType FFlowPin::BuildEdGraphPinType() const { - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + FEdGraphPinType EdGraphPinType; - // Set the PinSubCategoryObject based on the PinType (if appropriate) - switch (PinType) - { - case EFlowPinType::Vector: - { - PinSubCategoryObject = TBaseStructure::Get(); - } - break; + check(!PinTypeName.Name.IsNone()); - case EFlowPinType::Rotator: - { - PinSubCategoryObject = TBaseStructure::Get(); - } - break; + EdGraphPinType.PinCategory = PinTypeName.Name; - case EFlowPinType::Transform: - { - PinSubCategoryObject = TBaseStructure::Get(); - } - break; + // TODO (gtaylor) possible future extension for types, to allow sub categories + EdGraphPinType.PinSubCategory = NAME_None; - case EFlowPinType::GameplayTag: - { - PinSubCategoryObject = TBaseStructure::Get(); - } - break; - - case EFlowPinType::GameplayTagContainer: - { - PinSubCategoryObject = TBaseStructure::Get(); - } - break; - - case EFlowPinType::InstancedStruct: - { - PinSubCategoryObject = TBaseStructure::Get(); - } - break; - - case EFlowPinType::Enum: - { - // Clear the PinSubCategoryObject if it is not an Enum - const UObject* PinSubCategoryObjectPtr = PinSubCategoryObject.Get(); - if (PinSubCategoryObjectPtr && !PinSubCategoryObjectPtr->IsA()) - { - PinSubCategoryObject = nullptr; - } - } - break; - - case EFlowPinType::Object: - { - // Clear the PinSubCategoryObject if it is not an Object - const UObject* PinSubCategoryObjectPtr = PinSubCategoryObject.Get(); - if (PinSubCategoryObjectPtr && !PinSubCategoryObjectPtr->IsA()) - { - PinSubCategoryObject = nullptr; - } - } - break; + EdGraphPinType.PinSubCategoryObject = PinSubCategoryObject; + EdGraphPinType.ContainerType = ContainerType; - case EFlowPinType::Class: - { - // Clear the PinSubCategoryObject if it is not a Class - const UObject* PinSubCategoryObjectPtr = PinSubCategoryObject.Get(); - if (PinSubCategoryObjectPtr && !PinSubCategoryObjectPtr->IsA()) - { - PinSubCategoryObject = nullptr; - } - } - break; + return EdGraphPinType; +} +#endif - default: - { - // Clear the PinSubCategoryObject for all PinTypes that do not use it. - PinSubCategoryObject = nullptr; - } - break; - } +const FFlowPinType* FFlowPin::ResolveFlowPinType() const +{ + // TODO (gtaylor) consider caching this in a mutable? + return FFlowPinType::LookupPinType(PinTypeName); } -const FName& FFlowPin::GetPinCategoryFromPinType(EFlowPinType FlowPinType) +// #FlowDataPinLegacy +FFlowPinTypeName FFlowPin::GetPinTypeNameForLegacyPinType(EFlowPinType PinType) { FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - - switch (FlowPinType) + switch (PinType) { - case EFlowPinType::Exec: - return FFlowPin::PC_Exec; - - case EFlowPinType::Bool: - return FFlowPin::PC_Boolean; - - case EFlowPinType::Int: - return FFlowPin::PC_Int; - - case EFlowPinType::Float: - return FFlowPin::PC_Float; - - case EFlowPinType::Name: - return FFlowPin::PC_Name; - - case EFlowPinType::String: - return FFlowPin::PC_String; - - case EFlowPinType::Text: - return FFlowPin::PC_Text; - - case EFlowPinType::Enum: - return FFlowPin::PC_Enum; - - case EFlowPinType::Vector: - case EFlowPinType::Rotator: - case EFlowPinType::Transform: - case EFlowPinType::GameplayTag: - case EFlowPinType::GameplayTagContainer: - case EFlowPinType::InstancedStruct: - return FFlowPin::PC_Struct; - - case EFlowPinType::Object: - return FFlowPin::PC_Object; - - case EFlowPinType::Class: - return FFlowPin::PC_Class; - - default: - { - static const FName NameNone = NAME_None; - return NameNone; - } + case EFlowPinType::Exec: + return FFlowPinType_Exec::GetPinTypeNameStatic(); + case EFlowPinType::Bool: + return FFlowPinType_Bool::GetPinTypeNameStatic(); + case EFlowPinType::Int: + return FFlowPinType_Int::GetPinTypeNameStatic(); + case EFlowPinType::Float: + return FFlowPinType_Float::GetPinTypeNameStatic(); + case EFlowPinType::Name: + return FFlowPinType_Name::GetPinTypeNameStatic(); + case EFlowPinType::String: + return FFlowPinType_String::GetPinTypeNameStatic(); + case EFlowPinType::Text: + return FFlowPinType_Text::GetPinTypeNameStatic(); + case EFlowPinType::Enum: + return FFlowPinType_Enum::GetPinTypeNameStatic(); + case EFlowPinType::Vector: + return FFlowPinType_Vector::GetPinTypeNameStatic(); + case EFlowPinType::Rotator: + return FFlowPinType_Rotator::GetPinTypeNameStatic(); + case EFlowPinType::Transform: + return FFlowPinType_Transform::GetPinTypeNameStatic(); + case EFlowPinType::GameplayTag: + return FFlowPinType_GameplayTag::GetPinTypeNameStatic(); + case EFlowPinType::GameplayTagContainer: + return FFlowPinType_GameplayTagContainer::GetPinTypeNameStatic(); + case EFlowPinType::InstancedStruct: + return FFlowPinType_InstancedStruct::GetPinTypeNameStatic(); + case EFlowPinType::Object: + return FFlowPinType_Object::GetPinTypeNameStatic(); + case EFlowPinType::Class: + return FFlowPinType_Class::GetPinTypeNameStatic(); + default: + return FFlowPinTypeName(); } } @@ -251,52 +206,49 @@ const FName& FFlowPin::GetPinCategoryFromPinType(EFlowPinType FlowPinType) void FFlowPin::PostEditChangedPinTypeOrSubCategorySource() { // PinTypes with PinSubCategoryObjects will need to update this function - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); // Must be called from PostEditChangeProperty() by an owning UObject - switch (PinType) + if (PinTypeName == FFlowPinType_Class::GetPinTypeNameStatic()) { - case EFlowPinType::Class: - PinSubCategoryObject = SubCategoryClassFilter; - break; - - case EFlowPinType::Object: - PinSubCategoryObject = SubCategoryObjectFilter; - break; - - case EFlowPinType::Enum: + PinSubCategoryObject = SubCategoryClassFilter; + } + else if (PinTypeName == FFlowPinType_Object::GetPinTypeNameStatic()) + { + PinSubCategoryObject = SubCategoryObjectFilter; + } + else if (PinTypeName == FFlowPinType_Enum::GetPinTypeNameStatic()) + { + if (!SubCategoryEnumName.IsEmpty()) + { + SubCategoryEnumClass = UClass::TryFindTypeSlow(SubCategoryEnumName, EFindFirstObjectOptions::ExactClass); + if (SubCategoryEnumClass != nullptr && !FFlowPin::ValidateEnum(*SubCategoryEnumClass)) { - if (!SubCategoryEnumName.IsEmpty()) - { - SubCategoryEnumClass = UClass::TryFindTypeSlow(SubCategoryEnumName, EFindFirstObjectOptions::ExactClass); - if (SubCategoryEnumClass != nullptr && !FFlowPin::ValidateEnum(*SubCategoryEnumClass)) - { - SubCategoryEnumClass = nullptr; - } - } - - PinSubCategoryObject = SubCategoryEnumClass; + SubCategoryEnumClass = nullptr; } - break; + } - default: - TrySetStructSubCategoryObjectFromPinType(); - break; + PinSubCategoryObject = SubCategoryEnumClass; + } + else + { + TrySetStructSubCategoryObjectFromPinType(); } } +// -- + FText FFlowPin::BuildHeaderText() const { const FText PinNameToUse = !PinFriendlyName.IsEmpty() ? PinFriendlyName : FText::FromName(PinName); - if (PinType == EFlowPinType::Exec) + if (IsExecPin()) { return PinNameToUse; } else { - return FText::Format(LOCTEXT("FlowPinNameAndType", "{0} ({1})"), {PinNameToUse, UEnum::GetDisplayValueAsText(PinType)}); + return FText::Format(LOCTEXT("FlowPinNameAndType", "{0} ({1})"), {PinNameToUse, FText::FromString(PinTypeName.ToString()) }); } } diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_CustomInput.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_CustomInput.cpp index f189ce4d4..94e4410f8 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_CustomInput.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_CustomInput.cpp @@ -25,14 +25,14 @@ void UFlowNode_CustomInput::PostEditImport() } #if WITH_EDITOR -FText UFlowNode_CustomInput::GetNodeTitle() const +FText UFlowNode_CustomInput::K2_GetNodeTitle_Implementation() const { if (!EventName.IsNone() && UFlowSettings::Get()->bUseAdaptiveNodeTitles) { return FText::Format(LOCTEXT("CustomInputTitle", "{0} Input"), {FText::FromString(EventName.ToString())}); } - return Super::GetNodeTitle(); + return Super::K2_GetNodeTitle_Implementation(); } #endif diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_CustomOutput.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_CustomOutput.cpp index b19912f47..ee5263ee0 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_CustomOutput.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_CustomOutput.cpp @@ -53,14 +53,14 @@ void UFlowNode_CustomOutput::ExecuteInput(const FName& PinName) } #if WITH_EDITOR -FText UFlowNode_CustomOutput::GetNodeTitle() const +FText UFlowNode_CustomOutput::K2_GetNodeTitle_Implementation() const { if (!EventName.IsNone() && UFlowSettings::Get()->bUseAdaptiveNodeTitles) { return FText::Format(LOCTEXT("CustomOutputTitle", "{0} Output"), {FText::FromString(EventName.ToString())}); } - return Super::GetNodeTitle(); + return Super::K2_GetNodeTitle_Implementation(); } #endif diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp index fd835b546..6d453eaf0 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_DefineProperties.cpp @@ -1,6 +1,9 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Nodes/Graph/FlowNode_DefineProperties.h" +#include "Types/FlowAutoDataPinsWorkingData.h" +#include "Types/FlowPinTypesStandard.h" +#include "Types/FlowDataPinValuesStandard.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_DefineProperties) @@ -18,47 +21,60 @@ UFlowNode_DefineProperties::UFlowNode_DefineProperties(const FObjectInitializer& AllowedSignalModes = {EFlowSignalMode::Enabled, EFlowSignalMode::Disabled}; } -bool UFlowNode_DefineProperties::TryFindPropertyByRemappedPinName( - const FName& RemappedPinName, +void UFlowNode_DefineProperties::PostLoad() +{ + Super::PostLoad(); + + if (!HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject)) + { + // Migrate the named properties over to the new structs + + for (FFlowNamedDataPinProperty& NamedProperty : NamedProperties) + { + NamedProperty.FixupDataPinProperty(); + } + } +} + +bool UFlowNode_DefineProperties::TryFindPropertyByPinName( + const UObject& PropertyOwnerObject, + const FName& PinName, const FProperty*& OutFoundProperty, - TInstancedStruct& OutFoundInstancedStruct, - EFlowDataPinResolveResult& InOutResult) const + TInstancedStruct& OutFoundInstancedStruct) const { // The start node stores its properties in instanced structs in an array, so look there first for (const FFlowNamedDataPinProperty& NamedProperty : NamedProperties) { - if (NamedProperty.Name == RemappedPinName && NamedProperty.IsValid()) + if (NamedProperty.Name == PinName && NamedProperty.IsValid()) { - OutFoundInstancedStruct = NamedProperty.DataPinProperty; + OutFoundInstancedStruct = NamedProperty.DataPinValue; return true; } } - return Super::TryFindPropertyByRemappedPinName(RemappedPinName, OutFoundProperty, OutFoundInstancedStruct, InOutResult); + return Super::TryFindPropertyByPinName(PropertyOwnerObject, PinName, OutFoundProperty, OutFoundInstancedStruct); } #if WITH_EDITOR -void UFlowNode_DefineProperties::AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const +void UFlowNode_DefineProperties::AutoGenerateDataPins(FFlowAutoDataPinsWorkingData& InOutWorkingData) const { + Super::AutoGenerateDataPins(InOutWorkingData); + for (const FFlowNamedDataPinProperty& DataPinProperty : NamedProperties) { if (DataPinProperty.IsValid()) { - PinNameToBoundPropertyMap.Add(DataPinProperty.Name, DataPinProperty.Name); + const FFlowDataPinValue& DataPinValue = DataPinProperty.DataPinValue.Get(); - if (DataPinProperty.IsInputProperty()) + if (DataPinValue.IsInputPin()) { - InputDataPins.AddUnique(DataPinProperty.CreateFlowPin()); - } - else if (DataPinProperty.IsOutputProperty()) - { - OutputDataPins.AddUnique(DataPinProperty.CreateFlowPin()); + InOutWorkingData.AutoInputDataPinsNext.AddUnique(DataPinProperty.CreateFlowPin()); } else { - LogError(TEXT("DataPin must be either an Input or Output property!")); + InOutWorkingData.AutoOutputDataPinsNext.AddUnique(DataPinProperty.CreateFlowPin()); } } } @@ -75,8 +91,8 @@ void UFlowNode_DefineProperties::PostEditChangeChainProperty(FPropertyChangedCha auto& Property = PropertyChainEvent.PropertyChain.GetActiveMemberNode()->GetValue(); - // The DetailsCustomization for FFlowDataPinProperties isn't being called when using an InstancedStruct - // so we need to call this refresh by hand... + // The DetailsCustomization for FFlowDataPinValue_Enum isn't being called when using an InstancedStruct + // so we need to call OnEnumNameChanged refresh by hand... if (PropertyChainEvent.ChangeType == EPropertyChangeType::ValueSet && Property->GetFName() == GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Enum, EnumName)) { @@ -87,16 +103,13 @@ void UFlowNode_DefineProperties::PostEditChangeChainProperty(FPropertyChangedCha continue; } - const FFlowDataPinProperty& FlowDataPinProperty = NamedProperty.DataPinProperty.Get(); + const FFlowDataPinValue& FlowDataPinProperty = NamedProperty.DataPinValue.Get(); - if (FlowDataPinProperty.GetFlowPinType() == EFlowPinType::Enum) + if (FlowDataPinProperty.GetPinTypeName() == FFlowPinType_Enum::GetPinTypeNameStatic()) { - FFlowDataPinOutputProperty_Enum& EnumProperty = NamedProperty.DataPinProperty.GetMutable(); + FFlowDataPinValue_Enum& EnumProperty = NamedProperty.DataPinValue.GetMutable(); EnumProperty.OnEnumNameChanged(); } - - // We may need to manually call any PostEdit linked property updates here for future EFlowPinType values - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); } } diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp index 83adf38c0..3468c4302 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp @@ -1,6 +1,7 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Nodes/Graph/FlowNode_FormatText.h" +#include "Types/FlowPinTypesStandard.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_FormatText) @@ -16,81 +17,41 @@ UFlowNode_FormatText::UFlowNode_FormatText(const FObjectInitializer& ObjectIniti NodeDisplayStyle = FlowNodeStyle::Terminal; #endif - OutputPins.Add(FFlowPin(OUTPIN_TextOutput, EFlowPinType::Text)); + OutputPins.Add(FFlowPin(OUTPIN_TextOutput, FFlowPinType_Text::GetPinTypeNameStatic())); } -FFlowDataPinResult_Name UFlowNode_FormatText::TrySupplyDataPinAsName_Implementation(const FName& PinName) const +FFlowDataPinResult UFlowNode_FormatText::TrySupplyDataPin_Implementation(FName PinName) const { - FText FormattedText; - const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); - if (FormatResult != EFlowDataPinResolveResult::Invalid) + if (PinName == OUTPIN_TextOutput) { - if (FormatResult == EFlowDataPinResolveResult::Success) + FText FormattedText; + const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); + + if (FlowPinType::IsSuccess(FormatResult)) { - return FFlowDataPinResult_Name(FName(FormattedText.ToString())); + return FFlowDataPinResult(FFlowDataPinValue_Text(FormattedText)); } else { - return FFlowDataPinResult_Name(FormatResult); + return FFlowDataPinResult(FormatResult); } } - return Super::TrySupplyDataPinAsName_Implementation(PinName); + return Super::TrySupplyDataPin_Implementation(PinName); } -FFlowDataPinResult_String UFlowNode_FormatText::TrySupplyDataPinAsString_Implementation(const FName& PinName) const +EFlowDataPinResolveResult UFlowNode_FormatText::TryResolveFormatText(const FName& PinName, FText& OutFormattedText) const { - FText FormattedText; - const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); - if (FormatResult != EFlowDataPinResolveResult::Invalid) + if (TryFormatTextWithNamedPropertiesAsParameters(FormatText, OutFormattedText)) { - if (FormatResult == EFlowDataPinResolveResult::Success) - { - return FFlowDataPinResult_String(FormattedText.ToString()); - } - else - { - return FFlowDataPinResult_String(FormatResult); - } + return EFlowDataPinResolveResult::Success; } - - return Super::TrySupplyDataPinAsString_Implementation(PinName); -} - -FFlowDataPinResult_Text UFlowNode_FormatText::TrySupplyDataPinAsText_Implementation(const FName& PinName) const -{ - FText FormattedText; - const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); - if (FormatResult != EFlowDataPinResolveResult::Invalid) + else { - if (FormatResult == EFlowDataPinResolveResult::Success) - { - return FFlowDataPinResult_Text(FormattedText); - } - else - { - return FFlowDataPinResult_Text(FormatResult); - } - } - - return Super::TrySupplyDataPinAsText_Implementation(PinName); -} + LogError(FString::Printf(TEXT("Could not format text '%s' with properties as parameters"), *FormatText.ToString()), EFlowOnScreenMessageType::Temporary); -EFlowDataPinResolveResult UFlowNode_FormatText::TryResolveFormatText(const FName& PinName, FText& OutFormattedText) const -{ - if (PinName == OUTPIN_TextOutput) - { - if (TryFormatTextWithNamedPropertiesAsParameters(FormatText, OutFormattedText)) - { - return EFlowDataPinResolveResult::Success; - } - else - { - return EFlowDataPinResolveResult::FailedWithError; - } + return EFlowDataPinResolveResult::FailedWithError; } - - return EFlowDataPinResolveResult::Invalid; } #if WITH_EDITOR diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp index 3e30befa8..b8f399b78 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_Start.cpp @@ -44,230 +44,18 @@ bool UFlowNode_Start::TryAppendExternalInputPins(TArray& InOutPins) co #endif // WITH_EDITOR -// Must implement TrySupplyDataPinAs... for every EFlowPinType -FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - -FFlowDataPinResult_Bool UFlowNode_Start::TrySupplyDataPinAsBool_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Bool SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsBool(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsBool_Implementation(PinName); -} - -FFlowDataPinResult_Int UFlowNode_Start::TrySupplyDataPinAsInt_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Int SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInt(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsInt_Implementation(PinName); -} - -FFlowDataPinResult_Float UFlowNode_Start::TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Float SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsFloat(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsFloat_Implementation(PinName); -} - -FFlowDataPinResult_Name UFlowNode_Start::TrySupplyDataPinAsName_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Name SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsName(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsName_Implementation(PinName); -} - -FFlowDataPinResult_String UFlowNode_Start::TrySupplyDataPinAsString_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_String SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsString(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsString_Implementation(PinName); -} - -FFlowDataPinResult_Text UFlowNode_Start::TrySupplyDataPinAsText_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Text SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsText(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsText_Implementation(PinName); -} - -FFlowDataPinResult_Enum UFlowNode_Start::TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Enum SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsEnum(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsEnum_Implementation(PinName); -} - -FFlowDataPinResult_Vector UFlowNode_Start::TrySupplyDataPinAsVector_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Vector SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsVector(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsVector_Implementation(PinName); -} - -FFlowDataPinResult_Rotator UFlowNode_Start::TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const +FFlowDataPinResult UFlowNode_Start::TrySupplyDataPin_Implementation(FName PinName) const { if (FlowDataPinValueSupplierInterface) { - FFlowDataPinResult_Rotator SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsRotator(FlowDataPinValueSupplierInterface.GetObject(), PinName); + FFlowDataPinResult SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPin(FlowDataPinValueSupplierInterface.GetObject(), PinName); - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) + if (FlowPinType::IsSuccess(SuppliedResult.Result)) { return SuppliedResult; } } - return Super::TrySupplyDataPinAsRotator_Implementation(PinName); + return Super::TrySupplyDataPin_Implementation(PinName); } -FFlowDataPinResult_Transform UFlowNode_Start::TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Transform SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsTransform(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsTransform_Implementation(PinName); -} - -FFlowDataPinResult_GameplayTag UFlowNode_Start::TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_GameplayTag SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTag(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsGameplayTag_Implementation(PinName); -} - -FFlowDataPinResult_GameplayTagContainer UFlowNode_Start::TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_GameplayTagContainer SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsGameplayTagContainer(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsGameplayTagContainer_Implementation(PinName); -} - -FFlowDataPinResult_InstancedStruct UFlowNode_Start::TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_InstancedStruct SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsInstancedStruct(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsInstancedStruct_Implementation(PinName); -} - -FFlowDataPinResult_Object UFlowNode_Start::TrySupplyDataPinAsObject_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Object SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsObject(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsObject_Implementation(PinName); -} - -FFlowDataPinResult_Class UFlowNode_Start::TrySupplyDataPinAsClass_Implementation(const FName& PinName) const -{ - if (FlowDataPinValueSupplierInterface) - { - FFlowDataPinResult_Class SuppliedResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPinAsClass(FlowDataPinValueSupplierInterface.GetObject(), PinName); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::Success) - { - return SuppliedResult; - } - } - - return Super::TrySupplyDataPinAsClass_Implementation(PinName); -} diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp index 98d42b3e2..9e74acc08 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_SubGraph.cpp @@ -6,6 +6,7 @@ #include "FlowSettings.h" #include "FlowSubsystem.h" #include "Interfaces/FlowNodeWithExternalDataPinSupplierInterface.h" +#include "Types/FlowAutoDataPinsWorkingData.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_SubGraph) @@ -106,14 +107,14 @@ void UFlowNode_SubGraph::OnLoad_Implementation() #if WITH_EDITOR -FText UFlowNode_SubGraph::GetNodeTitle() const +FText UFlowNode_SubGraph::K2_GetNodeTitle_Implementation() const { if (UFlowSettings::Get()->bUseAdaptiveNodeTitles && !Asset.IsNull()) { - return FText::Format(LOCTEXT("SubGraphTitle", "{0}\n{1}"), {Super::GetNodeTitle(), FText::FromString(Asset.ToSoftObjectPath().GetAssetName())}); + return FText::Format(LOCTEXT("SubGraphTitle", "{0}\n{1}"), {Super::K2_GetNodeTitle_Implementation(), FText::FromString(Asset.ToSoftObjectPath().GetAssetName())}); } - return Super::GetNodeTitle(); + return Super::K2_GetNodeTitle_Implementation(); } FString UFlowNode_SubGraph::GetNodeDescription() const @@ -186,8 +187,10 @@ TArray UFlowNode_SubGraph::GetContextOutputs() const return ContextOutputPins; } -void UFlowNode_SubGraph::AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const +void UFlowNode_SubGraph::AutoGenerateDataPins(FFlowAutoDataPinsWorkingData& InOutWorkingData) const { + Super::AutoGenerateDataPins(InOutWorkingData); + if (Asset.IsNull()) { return; @@ -209,12 +212,7 @@ void UFlowNode_SubGraph::AutoGenerateDataPins(TMap& PinNameToBound TArray ExternalInputPins; if (ExternalPinSuppliedNode->TryAppendExternalInputPins(ExternalInputPins)) { - for (const FFlowPin& FlowPin : ExternalInputPins) - { - PinNameToBoundPropertyMap.Add(FlowPin.PinName, FlowPin.PinName); - } - - InputDataPins.Append(ExternalInputPins); + InOutWorkingData.AutoInputDataPinsNext.Append(ExternalInputPins); } } } diff --git a/Source/Flow/Private/Nodes/Route/FlowNode_Timer.cpp b/Source/Flow/Private/Nodes/Route/FlowNode_Timer.cpp index 2e842dd9c..92a742f73 100644 --- a/Source/Flow/Private/Nodes/Route/FlowNode_Timer.cpp +++ b/Source/Flow/Private/Nodes/Route/FlowNode_Timer.cpp @@ -34,7 +34,7 @@ UFlowNode_Timer::UFlowNode_Timer(const FObjectInitializer& ObjectInitializer) OutputPins.Add(FFlowPin(TEXT("Step"))); OutputPins.Add(FFlowPin(TEXT("Skipped"))); - INPIN_CompletionTime = GET_MEMBER_NAME_CHECKED(UFlowNode_Timer, CompletionTime); + INPIN_CompletionTime = GET_MEMBER_NAME_CHECKED(ThisClass, CompletionTime); } void UFlowNode_Timer::InitializeInstance() @@ -107,18 +107,10 @@ void UFlowNode_Timer::Restart() float UFlowNode_Timer::ResolveCompletionTime() const { // Get the CompletionTime from either the default (property) or the data pin (if connected) - FFlowDataPinResult_Float CompletionTimeResult = TryResolveDataPinAsFloat(INPIN_CompletionTime); + float ResolvedTime = CompletionTime; + const EFlowDataPinResolveResult TimeResult = TryResolveDataPinValue(INPIN_CompletionTime, ResolvedTime); - if (CompletionTimeResult.Result == EFlowDataPinResolveResult::FailedMissingPin) - { - // Handle lookup of a UFlowNode_Timer that predated DataPins - CompletionTimeResult.Result = EFlowDataPinResolveResult::Success; - CompletionTimeResult.Value = CompletionTime; - } - - check(CompletionTimeResult.Result == EFlowDataPinResolveResult::Success); - - return static_cast(CompletionTimeResult.Value); + return ResolvedTime; } void UFlowNode_Timer::OnStep() diff --git a/Source/Flow/Private/Types/FlowAutoDataPinsWorkingData.cpp b/Source/Flow/Private/Types/FlowAutoDataPinsWorkingData.cpp new file mode 100644 index 000000000..ce72c4117 --- /dev/null +++ b/Source/Flow/Private/Types/FlowAutoDataPinsWorkingData.cpp @@ -0,0 +1,137 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowAutoDataPinsWorkingData.h" +#include "FlowLogChannels.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowStructUtils.h" + +#if WITH_EDITOR +bool FFlowAutoDataPinsWorkingData::DidAutoInputDataPinsChange() const +{ + return !FFlowPin::DeepArePinArraysMatching(AutoInputDataPinsPrev, AutoInputDataPinsNext); +} + +bool FFlowAutoDataPinsWorkingData::DidAutoOutputDataPinsChange() const +{ + return !FFlowPin::DeepArePinArraysMatching(AutoOutputDataPinsPrev, AutoOutputDataPinsNext); +} + +void FFlowAutoDataPinsWorkingData::AddFlowDataPinsForClassProperties(const UObject& ObjectContainer) +{ + // Try to harvest pins to auto-generate and/or bind to for each property in the flow node + UClass* Class = ObjectContainer.GetClass(); + for (TFieldIterator PropertyIt(Class); PropertyIt; ++PropertyIt) + { + AddFlowDataPinForProperty(*PropertyIt, ObjectContainer); + } +} + +void FFlowAutoDataPinsWorkingData::AddFlowDataPinForProperty(const FProperty* Property, const UObject& ObjectContainer) +{ + bool bIsInputPin = false; + + const FString* AutoPinType = nullptr; + const FString* SourceForOutputFlowPinName = nullptr; + const FString* DefaultForInputFlowPinName = nullptr; + + const void* Container = &ObjectContainer; + + const FStructProperty* StructProperty = CastField(Property); + const FFlowDataPinValue* DataPinValue = nullptr; + if (StructProperty && StructProperty->Struct) + { + const UScriptStruct* ScriptStruct = StructProperty->Struct; + + AutoPinType = ScriptStruct->FindMetaData(FFlowPin::MetadataKey_FlowPinType); + SourceForOutputFlowPinName = ScriptStruct->FindMetaData(FFlowPin::MetadataKey_SourceForOutputFlowPin); + DefaultForInputFlowPinName = ScriptStruct->FindMetaData(FFlowPin::MetadataKey_DefaultForInputFlowPin); + + // For blueprint use, we allow the Value structs to set input pins via editor-only data + DataPinValue = FlowStructUtils::CastStructValue(StructProperty, Container); + if (DataPinValue) + { + bIsInputPin = DataPinValue->IsInputPin(); + } + } + + if (!AutoPinType) + { + AutoPinType = Property->FindMetaData(FFlowPin::MetadataKey_FlowPinType); + + if (!AutoPinType) + { + return; + } + } + + const FFlowPinType* FlowPinType = FFlowPinType::LookupPinType(FFlowPinTypeName(*AutoPinType)); + if (!FlowPinType) + { + UE_LOG(LogFlow, Error, TEXT("Unknown pin type %s for property %s"), **AutoPinType, *Property->GetName()); + + return; + } + + if (!SourceForOutputFlowPinName) + { + SourceForOutputFlowPinName = Property->FindMetaData(FFlowPin::MetadataKey_SourceForOutputFlowPin); + } + + if (!DefaultForInputFlowPinName) + { + DefaultForInputFlowPinName = Property->FindMetaData(FFlowPin::MetadataKey_DefaultForInputFlowPin); + } + + if (SourceForOutputFlowPinName && DefaultForInputFlowPinName) + { + UE_LOG(LogFlow, Error, TEXT("Error. A property cannot be both a %s and %s"), + *FFlowPin::MetadataKey_SourceForOutputFlowPin.ToString(), + *FFlowPin::MetadataKey_DefaultForInputFlowPin.ToString()); + + return; + } + + bIsInputPin = bIsInputPin || DefaultForInputFlowPinName != nullptr; + + // Default assumption is the pin will be an output pin, unless metadata specifies otherwise + TArray* FlowPinArray = nullptr; + if (bIsInputPin) + { + FlowPinArray = &AutoInputDataPinsNext; + } + else + { + FlowPinArray = &AutoOutputDataPinsNext; + } + + // Create the new FlowPin + FFlowPin NewFlowPin = FlowPinType->CreateFlowPinFromProperty(*Property, Container); + + // Potentially override the PinFriendlyName if the metadata specified an alternative + if (DefaultForInputFlowPinName) + { + const FString SpecifyInputPinNameString = *DefaultForInputFlowPinName; + if (SpecifyInputPinNameString.Len() > 0) + { + NewFlowPin.PinFriendlyName = FText::FromString(SpecifyInputPinNameString); + } + } + else if (SourceForOutputFlowPinName) + { + const FString SpecifyOutputPinNameString = *SourceForOutputFlowPinName; + if (SpecifyOutputPinNameString.Len() > 0) + { + NewFlowPin.PinFriendlyName = FText::FromString(SpecifyOutputPinNameString); + } + } + + FlowPinArray->Add(NewFlowPin); + + if (DataPinValue) + { + // Store the PropertyPinName in the property, for blueprint lookup functions. + DataPinValue->PropertyPinName = NewFlowPin.PinName; + } +} + +#endif diff --git a/Source/Flow/Private/Types/FlowDataPinBlueprintLibrary.cpp b/Source/Flow/Private/Types/FlowDataPinBlueprintLibrary.cpp index 13b17024a..b3aab541e 100644 --- a/Source/Flow/Private/Types/FlowDataPinBlueprintLibrary.cpp +++ b/Source/Flow/Private/Types/FlowDataPinBlueprintLibrary.cpp @@ -1,36 +1,2364 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Types/FlowDataPinBlueprintLibrary.h" -#include "FlowLogChannels.h" +#include "Types/FlowDataPinValue.h" +#include "Nodes/FlowNodeBase.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinBlueprintLibrary) -uint8 UFlowDataPinBlueprintLibrary::AutoConvert_FlowDataPinPropertyEnumToEnum(const FFlowDataPinOutputProperty_Enum& EnumProperty) +void UFlowDataPinBlueprintLibrary::ResolveAndExtract_Impl( + UFlowNodeBase* Target, + FName PinName, + EFlowDataPinResolveSimpleResult& SimpleResult, + EFlowDataPinResolveResult& ResultEnum, + auto&& ExtractLambda) { - if (IsValid(EnumProperty.EnumClass)) + using namespace FlowPinType; + + if (!IsValid(Target)) + { + ResultEnum = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + SimpleResult = ConvertToSimpleResult(ResultEnum); + return; + } + + ResultEnum = ExtractLambda(); + SimpleResult = ConvertToSimpleResult(ResultEnum); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsBool(UFlowNodeBase* Target, const FFlowDataPinValue_Bool& BoolValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, bool& Value, EFlowSingleFromArray SingleMode) +{ + Value = false; + ResolveAndExtract_Impl(Target, BoolValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(BoolValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsBoolArray(UFlowNodeBase* Target, const FFlowDataPinValue_Bool& BoolValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, BoolValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(BoolValue.PropertyPinName, Values); + }); +} + +// Int +void UFlowDataPinBlueprintLibrary::ResolveAsInt(UFlowNodeBase* Target, const FFlowDataPinValue_Int& IntValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, int32& Value, EFlowSingleFromArray SingleMode) +{ + Value = 0; + ResolveAndExtract_Impl(Target, IntValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(IntValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsIntArray(UFlowNodeBase* Target, const FFlowDataPinValue_Int& IntValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, IntValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(IntValue.PropertyPinName, Values); + }); +} + +// Int64 +void UFlowDataPinBlueprintLibrary::ResolveAsInt64(UFlowNodeBase* Target, const FFlowDataPinValue_Int64& Int64Value, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, int64& Value, EFlowSingleFromArray SingleMode) +{ + Value = 0; + ResolveAndExtract_Impl(Target, Int64Value.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(Int64Value.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsInt64Array(UFlowNodeBase* Target, const FFlowDataPinValue_Int64& Int64Value, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, Int64Value.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(Int64Value.PropertyPinName, Values); + }); +} + +// Float +void UFlowDataPinBlueprintLibrary::ResolveAsFloat(UFlowNodeBase* Target, const FFlowDataPinValue_Float& FloatValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, float& Value, EFlowSingleFromArray SingleMode) +{ + Value = 0.0f; + ResolveAndExtract_Impl(Target, FloatValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(FloatValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsFloatArray(UFlowNodeBase* Target, const FFlowDataPinValue_Float& FloatValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, FloatValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(FloatValue.PropertyPinName, Values); + }); +} + +// Double +void UFlowDataPinBlueprintLibrary::ResolveAsDouble(UFlowNodeBase* Target, const FFlowDataPinValue_Double& DoubleValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, double& Value, EFlowSingleFromArray SingleMode) +{ + Value = 0.0; + ResolveAndExtract_Impl(Target, DoubleValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(DoubleValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsDoubleArray(UFlowNodeBase* Target, const FFlowDataPinValue_Double& DoubleValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, DoubleValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(DoubleValue.PropertyPinName, Values); + }); +} + +// Name +void UFlowDataPinBlueprintLibrary::ResolveAsName(UFlowNodeBase* Target, const FFlowDataPinValue_Name& NameValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FName& Value, EFlowSingleFromArray SingleMode) +{ + Value = NAME_None; + ResolveAndExtract_Impl(Target, NameValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(NameValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsNameArray(UFlowNodeBase* Target, const FFlowDataPinValue_Name& NameValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, NameValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(NameValue.PropertyPinName, Values); + }); +} + +// String +void UFlowDataPinBlueprintLibrary::ResolveAsString(UFlowNodeBase* Target, const FFlowDataPinValue_String& StringValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FString& Value, EFlowSingleFromArray SingleMode) +{ + Value = FString(); + ResolveAndExtract_Impl(Target, StringValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(StringValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsStringArray(UFlowNodeBase* Target, const FFlowDataPinValue_String& StringValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, StringValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(StringValue.PropertyPinName, Values); + }); +} + +// Text +void UFlowDataPinBlueprintLibrary::ResolveAsText(UFlowNodeBase* Target, const FFlowDataPinValue_Text& TextValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FText& Value, EFlowSingleFromArray SingleMode) +{ + Value = FText::GetEmpty(); + ResolveAndExtract_Impl(Target, TextValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(TextValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsTextArray(UFlowNodeBase* Target, const FFlowDataPinValue_Text& TextValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, TextValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(TextValue.PropertyPinName, Values); + }); +} + +// Enum +void UFlowDataPinBlueprintLibrary::ResolveAsEnum(UFlowNodeBase* Target, const FFlowDataPinValue_Enum& EnumValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, uint8& Value, EFlowSingleFromArray SingleMode) +{ + Value = 0; + ResolveAndExtract_Impl(Target, EnumValue.PropertyPinName, Result, ResultEnum, [&]() { + FName ExtractedName; + UEnum* EnumClass = nullptr; + const EFlowDataPinResolveResult ResolveResult = Target->TryResolveDataPinValue(EnumValue.PropertyPinName, ExtractedName, EnumClass, SingleMode); + if (FlowPinType::IsSuccess(ResolveResult) && ensure(IsValid(EnumClass))) + { + const int64 IntValue = EnumClass->GetValueByName(ExtractedName); + Value = static_cast(IntValue); + } + return ResolveResult; + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsEnumArray(UFlowNodeBase* Target, const FFlowDataPinValue_Enum& EnumValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, EnumValue.PropertyPinName, Result, ResultEnum, [&]() { + TArray Names; + UEnum* EnumClass = nullptr; + const EFlowDataPinResolveResult ResolveResult = Target->TryResolveDataPinValues(EnumValue.PropertyPinName, Names, EnumClass); + if (FlowPinType::IsSuccess(ResolveResult) && ensure(IsValid(EnumClass))) + { + Values.Reserve(Names.Num()); + for (const FName& Name : Names) + { + const int64 IntValue = EnumClass->GetValueByName(Name); + Values.Add(static_cast(IntValue)); + } + } + return ResolveResult; + }); +} + +// Vector +void UFlowDataPinBlueprintLibrary::ResolveAsVector(UFlowNodeBase* Target, const FFlowDataPinValue_Vector& VectorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FVector& Value, EFlowSingleFromArray SingleMode) +{ + Value = FVector::ZeroVector; + ResolveAndExtract_Impl(Target, VectorValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(VectorValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsVectorArray(UFlowNodeBase* Target, const FFlowDataPinValue_Vector& VectorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, VectorValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(VectorValue.PropertyPinName, Values); + }); +} + +// Rotator +void UFlowDataPinBlueprintLibrary::ResolveAsRotator(UFlowNodeBase* Target, const FFlowDataPinValue_Rotator& RotatorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FRotator& Value, EFlowSingleFromArray SingleMode) +{ + Value = FRotator::ZeroRotator; + ResolveAndExtract_Impl(Target, RotatorValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(RotatorValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsRotatorArray(UFlowNodeBase* Target, const FFlowDataPinValue_Rotator& RotatorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, RotatorValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(RotatorValue.PropertyPinName, Values); + }); +} + +// Transform +void UFlowDataPinBlueprintLibrary::ResolveAsTransform(UFlowNodeBase* Target, const FFlowDataPinValue_Transform& TransformValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FTransform& Value, EFlowSingleFromArray SingleMode) +{ + Value = FTransform::Identity; + ResolveAndExtract_Impl(Target, TransformValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(TransformValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsTransformArray(UFlowNodeBase* Target, const FFlowDataPinValue_Transform& TransformValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, TransformValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(TransformValue.PropertyPinName, Values); + }); +} + +// GameplayTag +void UFlowDataPinBlueprintLibrary::ResolveAsGameplayTag(UFlowNodeBase* Target, const FFlowDataPinValue_GameplayTag& GameplayTagValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FGameplayTag& Value, EFlowSingleFromArray SingleMode) +{ + Value = FGameplayTag(); + ResolveAndExtract_Impl(Target, GameplayTagValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(GameplayTagValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsGameplayTagArray(UFlowNodeBase* Target, const FFlowDataPinValue_GameplayTag& GameplayTagValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, GameplayTagValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(GameplayTagValue.PropertyPinName, Values); + }); +} + +// GameplayTagContainer +void UFlowDataPinBlueprintLibrary::ResolveAsGameplayTagContainer(UFlowNodeBase* Target, const FFlowDataPinValue_GameplayTagContainer& GameplayTagContainerValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FGameplayTagContainer& Value) +{ + Value = FGameplayTagContainer(); + ResolveAndExtract_Impl(Target, GameplayTagContainerValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(GameplayTagContainerValue.PropertyPinName, Value, EFlowSingleFromArray::FirstValue); + }); +} + +// InstancedStruct +void UFlowDataPinBlueprintLibrary::ResolveAsInstancedStruct(UFlowNodeBase* Target, const FFlowDataPinValue_InstancedStruct& InstancedStructValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FInstancedStruct& Value, EFlowSingleFromArray SingleMode) +{ + Value = FInstancedStruct(); + ResolveAndExtract_Impl(Target, InstancedStructValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValue(InstancedStructValue.PropertyPinName, Value, SingleMode); + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsInstancedStructArray(UFlowNodeBase* Target, const FFlowDataPinValue_InstancedStruct& InstancedStructValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, InstancedStructValue.PropertyPinName, Result, ResultEnum, [&]() { + return Target->TryResolveDataPinValues(InstancedStructValue.PropertyPinName, Values); + }); +} + +// Object +void UFlowDataPinBlueprintLibrary::ResolveAsObject(UFlowNodeBase* Target, const FFlowDataPinValue_Object& ObjectValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, UObject*& Value, EFlowSingleFromArray SingleMode) +{ + Value = nullptr; + ResolveAndExtract_Impl(Target, ObjectValue.PropertyPinName, Result, ResultEnum, [&]() { + TObjectPtr Obj; + const EFlowDataPinResolveResult ResolveResult = Target->TryResolveDataPinValue(ObjectValue.PropertyPinName, Obj, SingleMode); + Value = Obj; + return ResolveResult; + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsObjectArray(UFlowNodeBase* Target, const FFlowDataPinValue_Object& ObjectValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, ObjectValue.PropertyPinName, Result, ResultEnum, [&]() { + TArray> ObjArray; + const EFlowDataPinResolveResult ResolveResult = Target->TryResolveDataPinValues(ObjectValue.PropertyPinName, ObjArray); + Values = ObjArray; + return ResolveResult; + }); +} + +// Class +void UFlowDataPinBlueprintLibrary::ResolveAsClass(UFlowNodeBase* Target, const FFlowDataPinValue_Class& ClassValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, UClass*& Value, EFlowSingleFromArray SingleMode) +{ + Value = nullptr; + ResolveAndExtract_Impl(Target, ClassValue.PropertyPinName, Result, ResultEnum, [&]() { + TObjectPtr ClassObj; + const EFlowDataPinResolveResult ResolveResult = Target->TryResolveDataPinValue(ClassValue.PropertyPinName, ClassObj, SingleMode); + Value = ClassObj; + return ResolveResult; + }); +} + +void UFlowDataPinBlueprintLibrary::ResolveAsClassArray(UFlowNodeBase* Target, const FFlowDataPinValue_Class& ClassValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values) +{ + Values.Reset(); + ResolveAndExtract_Impl(Target, ClassValue.PropertyPinName, Result, ResultEnum, [&]() { + TArray> ClassArray; + const EFlowDataPinResolveResult ResolveResult = Target->TryResolveDataPinValues(ClassValue.PropertyPinName, ClassArray); + Values = ClassArray; + return ResolveResult; + }); +} + +// Bool +bool UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsBool(const FFlowDataPinValue_Bool& BoolValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + bool Extracted = false; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(BoolValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Bool failed on pin '%s': %s"), + *BoolValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsBoolArray(const FFlowDataPinValue_Bool& BoolValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(BoolValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Bool Array failed on pin '%s': %s"), + *BoolValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Int +int32 UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsInt(const FFlowDataPinValue_Int& IntValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + int32 Extracted = 0; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(IntValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Int failed on pin '%s': %s"), + *IntValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsIntArray(const FFlowDataPinValue_Int& IntValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(IntValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Int Array failed on pin '%s': %s"), + *IntValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Int64 +int64 UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsInt64(const FFlowDataPinValue_Int64& Int64Value, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + int64 Extracted = 0; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(Int64Value.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Int64 failed on pin '%s': %s"), + *Int64Value.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsInt64Array(const FFlowDataPinValue_Int64& Int64Value, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(Int64Value.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Int64 Array failed on pin '%s': %s"), + *Int64Value.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Float +float UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsFloat(const FFlowDataPinValue_Float& FloatValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + float Extracted = 0.0f; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(FloatValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Float failed on pin '%s': %s"), + *FloatValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsFloatArray(const FFlowDataPinValue_Float& FloatValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(FloatValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Float Array failed on pin '%s': %s"), + *FloatValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Double +double UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsDouble(const FFlowDataPinValue_Double& DoubleValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + double Extracted = 0.0; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(DoubleValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Double failed on pin '%s': %s"), + *DoubleValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsDoubleArray(const FFlowDataPinValue_Double& DoubleValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(DoubleValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Double Array failed on pin '%s': %s"), + *DoubleValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Name +FName UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsName(const FFlowDataPinValue_Name& NameValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FName Extracted = NAME_None; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(NameValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Name failed on pin '%s': %s"), + *NameValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsNameArray(const FFlowDataPinValue_Name& NameValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(NameValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Name Array failed on pin '%s': %s"), + *NameValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// String +FString UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsString(const FFlowDataPinValue_String& StringValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FString Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(StringValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to String failed on pin '%s': %s"), + *StringValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsStringArray(const FFlowDataPinValue_String& StringValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(StringValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to String Array failed on pin '%s': %s"), + *StringValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Text +FText UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsText(const FFlowDataPinValue_Text& TextValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FText Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(TextValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Text failed on pin '%s': %s"), + *TextValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsTextArray(const FFlowDataPinValue_Text& TextValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(TextValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Text Array failed on pin '%s': %s"), + *TextValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Enum +uint8 UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsEnum(const FFlowDataPinValue_Enum& EnumValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FName ExtractedName; + UEnum* EnumClass = nullptr; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(EnumValue.PropertyPinName, ExtractedName, EnumClass, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Enum failed on pin '%s': %s"), + *EnumValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return static_cast(INDEX_NONE); + } + + if (ensure(IsValid(EnumClass))) + { + const uint64 ValueInt = EnumClass->GetValueByName(ExtractedName); + return static_cast(ValueInt); + } + + return static_cast(INDEX_NONE); +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsEnumArray(const FFlowDataPinValue_Enum& EnumValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray ExtractedNames; + UEnum* EnumClass = nullptr; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(EnumValue.PropertyPinName, ExtractedNames, EnumClass); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Enum Array failed on pin '%s': %s"), + *EnumValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + if (!ensure(IsValid(EnumClass))) + { + return TArray(); + } + + TArray Result; + Result.Reserve(ExtractedNames.Num()); + for (const FName& Name : ExtractedNames) + { + const uint64 ValueInt = EnumClass->GetValueByName(Name); + Result.Add(static_cast(ValueInt)); + } + + return Result; +} + +// Vector +FVector UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsVector(const FFlowDataPinValue_Vector& VectorValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FVector Extracted = FVector::ZeroVector; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(VectorValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Vector failed on pin '%s': %s"), + *VectorValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsVectorArray(const FFlowDataPinValue_Vector& VectorValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(VectorValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Vector Array failed on pin '%s': %s"), + *VectorValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Rotator +FRotator UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsRotator(const FFlowDataPinValue_Rotator& RotatorValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FRotator Extracted = FRotator::ZeroRotator; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(RotatorValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Rotator failed on pin '%s': %s"), + *RotatorValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsRotatorArray(const FFlowDataPinValue_Rotator& RotatorValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(RotatorValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Rotator Array failed on pin '%s': %s"), + *RotatorValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Transform +FTransform UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsTransform(const FFlowDataPinValue_Transform& TransformValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FTransform Extracted = FTransform::Identity; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(TransformValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Transform failed on pin '%s': %s"), + *TransformValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsTransformArray(const FFlowDataPinValue_Transform& TransformValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(TransformValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Transform Array failed on pin '%s': %s"), + *TransformValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// GameplayTag +FGameplayTag UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsGameplayTag(const FFlowDataPinValue_GameplayTag& GameplayTagValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FGameplayTag Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(GameplayTagValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to GameplayTag failed on pin '%s': %s"), + *GameplayTagValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsGameplayTagArray(const FFlowDataPinValue_GameplayTag& GameplayTagValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(GameplayTagValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to GameplayTag Array failed on pin '%s': %s"), + *GameplayTagValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// GameplayTagContainer +FGameplayTagContainer UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsGameplayTagContainer(const FFlowDataPinValue_GameplayTagContainer& GameplayTagContainerValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + FGameplayTagContainer Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(GameplayTagContainerValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to GameplayTagContainer failed on pin '%s': %s"), + *GameplayTagContainerValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +// InstancedStruct +FInstancedStruct UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsInstancedStruct(const FFlowDataPinValue_InstancedStruct& InstancedStructValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + FInstancedStruct Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(InstancedStructValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to InstancedStruct failed on pin '%s': %s"), + *InstancedStructValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsInstancedStructArray(const FFlowDataPinValue_InstancedStruct& InstancedStructValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray Extracted; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(InstancedStructValue.PropertyPinName, Extracted); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to InstancedStruct Array failed on pin '%s': %s"), + *InstancedStructValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + return Extracted; +} + +// Object +UObject* UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsObject(const FFlowDataPinValue_Object& ObjectValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + TObjectPtr Extracted = nullptr; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(ObjectValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Object failed on pin '%s': %s"), + *ObjectValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsObjectArray(const FFlowDataPinValue_Object& ObjectValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray> ExtractedTemp; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(ObjectValue.PropertyPinName, ExtractedTemp); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Object Array failed on pin '%s': %s"), + *ObjectValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + TArray Result; + Result.Reserve(ExtractedTemp.Num()); + for (const TObjectPtr& Obj : ExtractedTemp) + { + Result.Add(Obj.Get()); + } + return Result; +} + +// Class +UClass* UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsClass(const FFlowDataPinValue_Class& ClassValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + TObjectPtr Extracted = nullptr; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValue(ClassValue.PropertyPinName, Extracted, SingleMode); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Class failed on pin '%s': %s"), + *ClassValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + return Extracted; +} + +// Class +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryResolveAsClassArray(const FFlowDataPinValue_Class& ClassValue, UFlowNodeBase* Target) +{ + using namespace FlowPinType; + + TArray> ExtractedTemp; + EFlowDataPinResolveResult ResolveResult = EFlowDataPinResolveResult::FailedNullFlowNodeBase; + + if (IsValid(Target)) + { + ResolveResult = Target->TryResolveDataPinValues(ClassValue.PropertyPinName, ExtractedTemp); + } + + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("Auto-Resolve to Class Array failed on pin '%s': %s"), + *ClassValue.PropertyPinName.ToString(), + *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + TArray Result; + Result.Reserve(ExtractedTemp.Num()); + for (const TObjectPtr& ClassPtr : ExtractedTemp) + { + Result.Add(ClassPtr.Get()); + } + + return Result; +} + +bool UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractBool(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + bool ExtractedValue = false; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractBool Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractBoolArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractBoolArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +int32 UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractInt(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + int32 ExtractedValue = 0; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractInt Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractIntArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractIntArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +int64 UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractInt64(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + int64 ExtractedValue = 0; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractInt64 Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractInt64Array(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractInt64Array Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +float UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractFloat(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + float ExtractedValue = 0.0f; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractFloat Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractFloatArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractFloatArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +double UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractDouble(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + double ExtractedValue = 0.0; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractDouble Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractDoubleArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractDoubleArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +FName UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractName(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FName ExtractedValue = NAME_None; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractName Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractNameArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractNameArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +FString UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractString(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FString ExtractedValue; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractString Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractStringArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractStringArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +FText UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractText(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FText ExtractedValue; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractText Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractTextArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractTextArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +uint8 UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractEnum(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FName ExtractedValueAsName; + UEnum* EnumClass = nullptr; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValueAsName, EnumClass, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractEnum Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + + if (ensure(IsValid(EnumClass))) + { + const uint64 EnumValueAsInt = EnumClass->GetValueByName(ExtractedValueAsName); + return static_cast(EnumValueAsInt); + } + + return static_cast(INDEX_NONE); +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractEnumArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + + TArray ExtractedNames; + UEnum* EnumClass = nullptr; + + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedNames, EnumClass); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractEnumArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + return TArray(); + } + + if (!ensure(IsValid(EnumClass))) + { + return TArray(); + } + + TArray ExtractedValues; + ExtractedValues.Reserve(ExtractedNames.Num()); + + for (const FName& Name : ExtractedNames) + { + const uint64 EnumValueAsInt = EnumClass->GetValueByName(Name); + ExtractedValues.Add(static_cast(EnumValueAsInt)); + } + + return ExtractedValues; +} + +FVector UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractVector(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FVector ExtractedValue = FVector::ZeroVector; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractVector Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractVectorArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractVectorArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +FRotator UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractRotator(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FRotator ExtractedValue = FRotator::ZeroRotator; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractRotator Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractRotatorArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractRotatorArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +FTransform UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractTransform(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FTransform ExtractedValue = FTransform::Identity; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractTransform Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractTransformArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractTransformArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +FGameplayTag UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractGameplayTag(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FGameplayTag ExtractedValue; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractGameplayTag Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractGameplayTagArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractGameplayTagArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +FGameplayTagContainer UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractGameplayTagContainer(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FGameplayTagContainer ExtractedValue; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractGameplayTagContainer Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +FInstancedStruct UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractInstancedStruct(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + FInstancedStruct ExtractedValue; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractInstancedStruct Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractInstancedStructArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractInstancedStructArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +UObject* UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractObject(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TObjectPtr ExtractedValue = nullptr; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractObject Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractObjectArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray> ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractObjectArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +UClass* UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractClass(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TObjectPtr ExtractedValue = nullptr; + constexpr EFlowSingleFromArray SingleMode = EFlowSingleFromArray::LastValue; + const EFlowDataPinResolveResult ResolveResult = TryExtractValue(DataPinResult, ExtractedValue, SingleMode); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractClass Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValue; +} + +TArray UFlowDataPinBlueprintLibrary::AutoConvert_TryExtractClassArray(const FFlowDataPinResult& DataPinResult) +{ + using namespace FlowPinType; + TArray> ExtractedValues; + const EFlowDataPinResolveResult ResolveResult = TryExtractValues(DataPinResult, ExtractedValues); + if (!IsSuccess(ResolveResult)) + { + UE_LOG(LogFlow, Error, TEXT("TryExtractClassArray Error: %s"), *UEnum::GetDisplayValueAsText(ResolveResult).ToString()); + } + return ExtractedValues; +} + +bool UFlowDataPinBlueprintLibrary::GetBoolValue(const FFlowDataPinValue_Bool& BoolDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (BoolDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetBoolValue on an input pin, use ResolveAsBool instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, BoolDataPinValue.Values.Num()); + if (BoolDataPinValue.Values.IsValidIndex(Index)) + { + return BoolDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Bool Data Pin Value.")); + return false; +} + +TArray UFlowDataPinBlueprintLibrary::GetBoolValues(FFlowDataPinValue_Bool& BoolDataPinValue) +{ +#if WITH_EDITOR + if (BoolDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetBoolValues on an input pin, use ResolveAsBoolArray instead.")); + } +#endif + return BoolDataPinValue.Values; +} + +// Int +int32 UFlowDataPinBlueprintLibrary::GetIntValue(const FFlowDataPinValue_Int& IntDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (IntDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetIntValue on an input pin, use ResolveAsInt instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, IntDataPinValue.Values.Num()); + if (IntDataPinValue.Values.IsValidIndex(Index)) + { + return IntDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Int Data Pin Value.")); + return 0; +} + +TArray UFlowDataPinBlueprintLibrary::GetIntValues(FFlowDataPinValue_Int& IntDataPinValue) +{ +#if WITH_EDITOR + if (IntDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetIntValues on an input pin, use ResolveAsIntArray instead.")); + } +#endif + return IntDataPinValue.Values; +} + +// Int64 +int64 UFlowDataPinBlueprintLibrary::GetInt64Value(const FFlowDataPinValue_Int64& Int64DataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (Int64DataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetInt64Value on an input pin, use ResolveAsInt64 instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, Int64DataPinValue.Values.Num()); + if (Int64DataPinValue.Values.IsValidIndex(Index)) + { + return Int64DataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Int64 Data Pin Value.")); + return 0; +} + +TArray UFlowDataPinBlueprintLibrary::GetInt64Values(FFlowDataPinValue_Int64& Int64DataPinValue) +{ +#if WITH_EDITOR + if (Int64DataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetInt64Values on an input pin, use ResolveAsInt64Array instead.")); + } +#endif + return Int64DataPinValue.Values; +} + +// Float +float UFlowDataPinBlueprintLibrary::GetFloatValue(const FFlowDataPinValue_Float& FloatDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (FloatDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetFloatValue on an input pin, use ResolveAsFloat instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, FloatDataPinValue.Values.Num()); + if (FloatDataPinValue.Values.IsValidIndex(Index)) + { + return FloatDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Float Data Pin Value.")); + return 0.f; +} + +TArray UFlowDataPinBlueprintLibrary::GetFloatValues(FFlowDataPinValue_Float& FloatDataPinValue) +{ +#if WITH_EDITOR + if (FloatDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetFloatValues on an input pin, use ResolveAsFloatArray instead.")); + } +#endif + return FloatDataPinValue.Values; +} + +// Double +double UFlowDataPinBlueprintLibrary::GetDoubleValue(const FFlowDataPinValue_Double& DoubleDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (DoubleDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetDoubleValue on an input pin, use ResolveAsDouble instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, DoubleDataPinValue.Values.Num()); + if (DoubleDataPinValue.Values.IsValidIndex(Index)) + { + return DoubleDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Double Data Pin Value.")); + return 0.0; +} + +TArray UFlowDataPinBlueprintLibrary::GetDoubleValues(FFlowDataPinValue_Double& DoubleDataPinValue) +{ +#if WITH_EDITOR + if (DoubleDataPinValue.bIsInputPin) { - const uint64 EnumValueAsInt = EnumProperty.EnumClass->GetValueByName(EnumProperty.Value); + UE_LOG(LogFlow, Error, TEXT("Should not call GetDoubleValues on an input pin, use ResolveAsDoubleArray instead.")); + } +#endif + return DoubleDataPinValue.Values; +} - // At least For Now(tm) Blueprint Enums want to be uint8's - return static_cast(EnumValueAsInt); +// Name +FName UFlowDataPinBlueprintLibrary::GetNameValue(const FFlowDataPinValue_Name& NameDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (NameDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetNameValue on an input pin, use ResolveAsName instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, NameDataPinValue.Values.Num()); + if (NameDataPinValue.Values.IsValidIndex(Index)) + { + return NameDataPinValue.Values[Index]; } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Name Data Pin Value.")); + return FName(); +} - UE_LOG(LogFlow, Error, TEXT("Could not cast enum value %s, because missing enum class"), *EnumProperty.Value.ToString()); +TArray UFlowDataPinBlueprintLibrary::GetNameValues(FFlowDataPinValue_Name& NameDataPinValue) +{ +#if WITH_EDITOR + if (NameDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetNameValues on an input pin, use ResolveAsNameArray instead.")); + } +#endif + return NameDataPinValue.Values; +} - return static_cast(INDEX_NONE); +// String +FString UFlowDataPinBlueprintLibrary::GetStringValue(const FFlowDataPinValue_String& StringDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (StringDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetStringValue on an input pin, use ResolveAsString instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, StringDataPinValue.Values.Num()); + if (StringDataPinValue.Values.IsValidIndex(Index)) + { + return StringDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in String Data Pin Value.")); + return FString(); } -uint8 UFlowDataPinBlueprintLibrary::AutoConvert_FlowDataPinResultEnumToEnum(const FFlowDataPinResult_Enum& EnumProperty) +TArray UFlowDataPinBlueprintLibrary::GetStringValues(FFlowDataPinValue_String& StringDataPinValue) { - if (IsValid(EnumProperty.EnumClass)) +#if WITH_EDITOR + if (StringDataPinValue.bIsInputPin) { - const uint64 EnumValueAsInt = EnumProperty.EnumClass->GetValueByName(EnumProperty.Value); + UE_LOG(LogFlow, Error, TEXT("Should not call GetStringValues on an input pin, use ResolveAsStringArray instead.")); + } +#endif + return StringDataPinValue.Values; +} - // At least For Now(tm) Blueprint Enums want to be uint8's - return static_cast(EnumValueAsInt); +// Text +FText UFlowDataPinBlueprintLibrary::GetTextValue(const FFlowDataPinValue_Text& TextDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (TextDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetTextValue on an input pin, use ResolveAsText instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, TextDataPinValue.Values.Num()); + if (TextDataPinValue.Values.IsValidIndex(Index)) + { + return TextDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Text Data Pin Value.")); + return FText::GetEmpty(); +} + +TArray UFlowDataPinBlueprintLibrary::GetTextValues(FFlowDataPinValue_Text& TextDataPinValue) +{ +#if WITH_EDITOR + if (TextDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetTextValues on an input pin, use ResolveAsTextArray instead.")); } +#endif + return TextDataPinValue.Values; +} - UE_LOG(LogFlow, Error, TEXT("Could not cast enum value %s, because missing enum class"), *EnumProperty.Value.ToString()); +// ----- Enum Setters ----- - return static_cast(INDEX_NONE); +void UFlowDataPinBlueprintLibrary::SetEnumValue(uint8 InValue, FFlowDataPinValue_Enum& EnumDataPinValue) +{ + UEnum* EnumClass = EnumDataPinValue.EnumClass.LoadSynchronous(); + if (!IsValid(EnumClass)) + { + UE_LOG(LogFlow, Error, TEXT("SetEnumValue: Null EnumClass")); + return; + } + + const int64 ValueInt64 = static_cast(InValue); + const FName EnumValueName = EnumClass->GetNameByValue(ValueInt64); + if (EnumValueName.IsNone()) + { + UE_LOG(LogFlow, Error, TEXT("SetEnumValue: Could not find enum name for value %d in %s"), InValue, *EnumClass->GetPathName()); + return; + } + + EnumDataPinValue.EnumClass = EnumClass; + EnumDataPinValue.Values = { EnumValueName }; +#if WITH_EDITOR + EnumDataPinValue.MultiType = EFlowDataMultiType::Single; +#endif +} + +void UFlowDataPinBlueprintLibrary::SetEnumValues(const TArray& InValues, FFlowDataPinValue_Enum& EnumDataPinValue) +{ + UEnum* EnumClass = EnumDataPinValue.EnumClass.LoadSynchronous(); + if (!IsValid(EnumClass)) + { + UE_LOG(LogFlow, Error, TEXT("SetEnumValues: Null EnumClass")); + return; + } + + TArray Names; + Names.Reserve(InValues.Num()); + + for (uint8 RawValue : InValues) + { + const int64 ValueInt64 = static_cast(RawValue); + const FName EnumValueName = EnumClass->GetNameByValue(ValueInt64); + if (EnumValueName.IsNone()) + { + UE_LOG(LogFlow, Error, TEXT("SetEnumValues: Could not find enum name for value %d in %s"), RawValue, *EnumClass->GetPathName()); + + // Abort entire set to avoid partial data + return; + } + + Names.Add(EnumValueName); + } + + EnumDataPinValue.EnumClass = EnumClass; + EnumDataPinValue.Values = MoveTemp(Names); +#if WITH_EDITOR + EnumDataPinValue.MultiType = EFlowDataMultiType::Array; +#endif +} + +uint8 UFlowDataPinBlueprintLibrary::GetEnumValue(const FFlowDataPinValue_Enum& EnumDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (EnumDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetEnumValue on an input pin, use ResolveAsEnum instead.")); + } +#endif + + UEnum* EnumClass = EnumDataPinValue.EnumClass.LoadSynchronous(); + if (!IsValid(EnumClass)) + { + UE_LOG(LogFlow, Error, TEXT("GetEnumValue: Null EnumClass")); + return 0; + } + + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, EnumDataPinValue.Values.Num()); + if (!EnumDataPinValue.Values.IsValidIndex(Index)) + { + UE_LOG(LogFlow, Error, TEXT("GetEnumValue: Insufficient values.")); + return 0; + } + + const FName& EnumName = EnumDataPinValue.Values[Index]; + const int32 EnumIndex = EnumClass->GetIndexByName(EnumName); + if (EnumIndex == INDEX_NONE) + { + UE_LOG(LogFlow, Error, TEXT("GetEnumValue: Name '%s' not found in enum %s"), *EnumName.ToString(), *EnumClass->GetPathName()); + return 0; + } + + const int64 RawValue = EnumClass->GetValueByIndex(EnumIndex); + return static_cast(RawValue); +} + +TArray UFlowDataPinBlueprintLibrary::GetEnumValues(const FFlowDataPinValue_Enum& EnumDataPinValue) +{ +#if WITH_EDITOR + if (EnumDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetEnumValues on an input pin, use ResolveAsEnumArray instead.")); + } +#endif + TArray Values; + + UEnum* EnumClass = EnumDataPinValue.EnumClass.LoadSynchronous(); + if (!IsValid(EnumClass)) + { + UE_LOG(LogFlow, Error, TEXT("GetEnumValues: Null EnumClass")); + return Values; + } + + Values.Reserve(EnumDataPinValue.Values.Num()); + for (const FName& EnumName : EnumDataPinValue.Values) + { + const int32 EnumIndex = EnumClass->GetIndexByName(EnumName); + if (EnumIndex == INDEX_NONE) + { + UE_LOG(LogFlow, Error, TEXT("GetEnumValues: Name '%s' not found in enum %s"), *EnumName.ToString(), *EnumClass->GetPathName()); + + // Abort entire set to avoid partial data + return TArray(); + } + const int64 RawValue = EnumClass->GetValueByIndex(EnumIndex); + Values.Add(static_cast(RawValue)); + } + + return Values; +} + +// Vector +FVector UFlowDataPinBlueprintLibrary::GetVectorValue(const FFlowDataPinValue_Vector& VectorDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (VectorDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetVectorValue on an input pin, use ResolveAsVector instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, VectorDataPinValue.Values.Num()); + if (VectorDataPinValue.Values.IsValidIndex(Index)) + { + return VectorDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Vector Data Pin Value.")); + return FVector::ZeroVector; +} + +TArray UFlowDataPinBlueprintLibrary::GetVectorValues(FFlowDataPinValue_Vector& VectorDataPinValue) +{ +#if WITH_EDITOR + if (VectorDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetVectorValues on an input pin, use ResolveAsVectorArray instead.")); + } +#endif + return VectorDataPinValue.Values; +} + +// Rotator +FRotator UFlowDataPinBlueprintLibrary::GetRotatorValue(const FFlowDataPinValue_Rotator& RotatorDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (RotatorDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetRotatorValue on an input pin, use ResolveAsRotator instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, RotatorDataPinValue.Values.Num()); + if (RotatorDataPinValue.Values.IsValidIndex(Index)) + { + return RotatorDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Rotator Data Pin Value.")); + return FRotator::ZeroRotator; +} + +TArray UFlowDataPinBlueprintLibrary::GetRotatorValues(FFlowDataPinValue_Rotator& RotatorDataPinValue) +{ +#if WITH_EDITOR + if (RotatorDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetRotatorValues on an input pin, use ResolveAsRotatorArray instead.")); + } +#endif + return RotatorDataPinValue.Values; +} + +// Transform +FTransform UFlowDataPinBlueprintLibrary::GetTransformValue(const FFlowDataPinValue_Transform& TransformDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (TransformDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetTransformValue on an input pin, use ResolveAsTransform instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, TransformDataPinValue.Values.Num()); + if (TransformDataPinValue.Values.IsValidIndex(Index)) + { + return TransformDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Transform Data Pin Value.")); + return FTransform::Identity; +} + +TArray UFlowDataPinBlueprintLibrary::GetTransformValues(FFlowDataPinValue_Transform& TransformDataPinValue) +{ +#if WITH_EDITOR + if (TransformDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetTransformValues on an input pin, use ResolveAsTransformArray instead.")); + } +#endif + return TransformDataPinValue.Values; +} + +// GameplayTag +FGameplayTag UFlowDataPinBlueprintLibrary::GetGameplayTagValue(const FFlowDataPinValue_GameplayTag& GameplayTagDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (GameplayTagDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetGameplayTagValue on an input pin, use ResolveAsGameplayTag instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, GameplayTagDataPinValue.Values.Num()); + if (GameplayTagDataPinValue.Values.IsValidIndex(Index)) + { + return GameplayTagDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in GameplayTag Data Pin Value.")); + return FGameplayTag(); +} + +TArray UFlowDataPinBlueprintLibrary::GetGameplayTagValues(FFlowDataPinValue_GameplayTag& GameplayTagDataPinValue) +{ +#if WITH_EDITOR + if (GameplayTagDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetGameplayTagValues on an input pin, use ResolveAsGameplayTagArray instead.")); + } +#endif + return GameplayTagDataPinValue.Values; +} + +// GameplayTagContainer (scalar only) +FGameplayTagContainer UFlowDataPinBlueprintLibrary::GetGameplayTagContainerValue(const FFlowDataPinValue_GameplayTagContainer& GameplayTagContainerDataPinValue) +{ +#if WITH_EDITOR + if (GameplayTagContainerDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetGameplayTagContainerValue on an input pin, use ResolveAsGameplayTagContainer instead.")); + } +#endif + return GameplayTagContainerDataPinValue.Values; +} + +// InstancedStruct +FInstancedStruct UFlowDataPinBlueprintLibrary::GetInstancedStructValue(const FFlowDataPinValue_InstancedStruct& InstancedStructDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (InstancedStructDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetInstancedStructValue on an input pin, use ResolveAsInstancedStruct instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, InstancedStructDataPinValue.Values.Num()); + if (InstancedStructDataPinValue.Values.IsValidIndex(Index)) + { + return InstancedStructDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in InstancedStruct Data Pin Value.")); + return FInstancedStruct(); +} + +TArray UFlowDataPinBlueprintLibrary::GetInstancedStructValues(FFlowDataPinValue_InstancedStruct& InstancedStructDataPinValue) +{ +#if WITH_EDITOR + if (InstancedStructDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetInstancedStructValues on an input pin, use ResolveAsInstancedStructArray instead.")); + } +#endif + return InstancedStructDataPinValue.Values; +} + +// Object +UObject* UFlowDataPinBlueprintLibrary::GetObjectValue(const FFlowDataPinValue_Object& ObjectDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (ObjectDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetObjectValue on an input pin, use ResolveAsObject instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, ObjectDataPinValue.Values.Num()); + if (ObjectDataPinValue.Values.IsValidIndex(Index)) + { + return ObjectDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Object Data Pin Value.")); + return nullptr; +} + +TArray UFlowDataPinBlueprintLibrary::GetObjectValues(FFlowDataPinValue_Object& ObjectDataPinValue) +{ +#if WITH_EDITOR + if (ObjectDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetObjectValues on an input pin, use ResolveAsObjectArray instead.")); + } +#endif + return ObjectDataPinValue.Values; +} + +// Class +FSoftClassPath UFlowDataPinBlueprintLibrary::GetClassValue(const FFlowDataPinValue_Class& ClassDataPinValue, EFlowSingleFromArray SingleFromArray) +{ +#if WITH_EDITOR + if (ClassDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetClassValue on an input pin, use ResolveAsClass instead.")); + } +#endif + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, ClassDataPinValue.Values.Num()); + if (ClassDataPinValue.Values.IsValidIndex(Index)) + { + return ClassDataPinValue.Values[Index]; + } + UE_LOG(LogFlow, Error, TEXT("Insufficient values in Class Data Pin Value.")); + return nullptr; +} + +TArray UFlowDataPinBlueprintLibrary::GetClassValues(FFlowDataPinValue_Class& ClassDataPinValue) +{ +#if WITH_EDITOR + if (ClassDataPinValue.bIsInputPin) + { + UE_LOG(LogFlow, Error, TEXT("Should not call GetClassValues on an input pin, use ResolveAsClassArray instead.")); + } +#endif + return ClassDataPinValue.Values; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Bool(const TArray& BoolValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Bool ValueStruct; + ValueStruct.Values = BoolValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Int(const TArray& IntValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Int ValueStruct; + ValueStruct.Values = IntValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Int64(const TArray& Int64Values) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Int64 ValueStruct; + ValueStruct.Values = Int64Values; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Float(const TArray& FloatValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Float ValueStruct; + ValueStruct.Values = FloatValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Double(const TArray& DoubleValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Double ValueStruct; + ValueStruct.Values = DoubleValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Name(const TArray& NameValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Name ValueStruct; + ValueStruct.Values = NameValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_String(const TArray& StringValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_String ValueStruct; + ValueStruct.Values = StringValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Text(const TArray& TextValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Text ValueStruct; + ValueStruct.Values = TextValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Enum(const TArray& EnumValues, UEnum* EnumClass) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + if (EnumClass) + { + FFlowDataPinValue_Enum ValueStruct; + ValueStruct.EnumClass = EnumClass; + ValueStruct.Values.Reserve(EnumValues.Num()); + + for (uint8 RawValue : EnumValues) + { + const FName EnumName = EnumClass->GetNameByValue(RawValue); + ValueStruct.Values.Add(EnumName); + } + + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + } + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Vector(const TArray& VectorValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Vector ValueStruct; + ValueStruct.Values = VectorValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Rotator(const TArray& RotatorValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Rotator ValueStruct; + ValueStruct.Values = RotatorValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Transform(const TArray& TransformValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Transform ValueStruct; + ValueStruct.Values = TransformValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_GameplayTag(const TArray& GameplayTagValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_GameplayTag ValueStruct; + ValueStruct.Values = GameplayTagValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_GameplayTagContainer(FGameplayTagContainer GameplayTagContainerValue) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_GameplayTagContainer ValueStruct; + ValueStruct.Values = GameplayTagContainerValue; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_InstancedStruct(const TArray& InstancedStructValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_InstancedStruct ValueStruct; + ValueStruct.Values = InstancedStructValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; +} + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Object(const TArray& ObjectValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Object ValueStruct; + ValueStruct.Values = ObjectValues; + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + + return Out; } + +FFlowDataPinResult UFlowDataPinBlueprintLibrary::MakeFlowDataPinResult_Class(const TArray& ClassValues) +{ + FFlowDataPinResult Out(EFlowDataPinResolveResult::Success); + + FFlowDataPinValue_Class ValueStruct; + ValueStruct.Values = ClassValues; + + Out.ResultValue = TInstancedStruct::Make(ValueStruct); + return Out; +} \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowDataPinProperties.cpp b/Source/Flow/Private/Types/FlowDataPinProperties.cpp index 1f11e190a..42a63ccb5 100644 --- a/Source/Flow/Private/Types/FlowDataPinProperties.cpp +++ b/Source/Flow/Private/Types/FlowDataPinProperties.cpp @@ -1,202 +1,21 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +// #FlowDataPinLegacy #include "Types/FlowDataPinProperties.h" -#include "Types/FlowClassUtils.h" #include "FlowLogChannels.h" #include "UObject/Class.h" #include "UObject/UObjectIterator.h" -#if WITH_EDITOR -#include "EditorClassUtils.h" -#endif - -#define LOCTEXT_NAMESPACE "FlowDataPinProperties" - -#if WITH_EDITOR - -FFlowPin FFlowDataPinProperty::CreateFlowPin(const FName& PinName, const TInstancedStruct& DataPinProperty) -{ - FFlowPin FlowPin; - - const FFlowDataPinProperty* Property = DataPinProperty.GetPtr(); - if (!Property) - { - return FlowPin; - } - - FlowPin.PinName = PinName; - - const EFlowPinType FlowPinType = Property->GetFlowPinType(); - - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - - switch (FlowPinType) - { - case EFlowPinType::Enum: - { - const FFlowDataPinOutputProperty_Enum& EnumDataPinProperty = DataPinProperty.Get(); - UEnum* EnumClass = EnumDataPinProperty.EnumClass; - - FlowPin.SetPinType(FlowPinType, EnumClass); - } - break; - - case EFlowPinType::Class: - { - const FFlowDataPinOutputProperty_Class& ClassDataPinProperty = DataPinProperty.Get(); - - FlowPin.SetPinType(FlowPinType, ClassDataPinProperty.ClassFilter); - } - break; - - case EFlowPinType::Object: - { - const FFlowDataPinOutputProperty_Object& ObjectDataPinProperty = DataPinProperty.Get(); - - FlowPin.SetPinType(FlowPinType, ObjectDataPinProperty.ClassFilter); - } - break; +#include "Types/FlowPinTypesStandard.h" - default: - { - FlowPin.SetPinType(FlowPinType); - } - break; - } - - return FlowPin; -} - -void FFlowDataPinOutputProperty_Enum::OnEnumNameChanged() -{ - if (!EnumName.IsEmpty()) - { - EnumClass = UClass::TryFindTypeSlow(EnumName, EFindFirstObjectOptions::ExactClass); +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinProperties) - if (EnumClass != nullptr && !FFlowPin::ValidateEnum(*EnumClass)) - { - EnumClass = nullptr; - } - } -} - -FText FFlowNamedDataPinProperty::BuildHeaderText() const -{ - EFlowPinType PinType = EFlowPinType::Invalid; - - if (const FFlowDataPinProperty* DataPinPropertyPtr = DataPinProperty.GetPtr()) - { - PinType = DataPinPropertyPtr->GetFlowPinType(); - } - - return FText::Format(LOCTEXT("FlowNamedDataPinPropertyHeader", "{0} ({1})"), { FText::FromName(Name), UEnum::GetDisplayValueAsText(PinType) }); -} - -UClass* FFlowDataPinOutputProperty_Class::DeriveMetaClass(const FProperty& MetaDataProperty) const -{ - if (UClass* MetaClass = TryGetMetaClassFromProperty(MetaDataProperty)) - { - return MetaClass; - } - - return ClassFilter; -} - -UClass* FFlowDataPinOutputProperty_Class::TryGetMetaClassFromProperty(const FProperty& MetaDataProperty) -{ - const FString& MetaClassName = MetaDataProperty.GetMetaData("MetaClass"); - - if (!MetaClassName.IsEmpty()) - { - if (UClass* FoundClass = FEditorClassUtils::GetClassFromString(MetaClassName)) - { - return FoundClass; - } - else - { - UE_LOG(LogFlow, Error, TEXT("Could not resolve MetaClass named %s for property %s"), *MetaClassName, *MetaDataProperty.GetName()); - } - } - - return nullptr; -} - -UClass* FFlowDataPinOutputProperty_Object::DeriveObjectClass(const FProperty& MetaDataProperty) const -{ - if (UClass* MetaClass = FFlowDataPinOutputProperty_Object::TryGetObjectClassFromProperty(MetaDataProperty)) - { - return MetaClass; - } - - return ClassFilter; -} - -UClass* FFlowDataPinOutputProperty_Object::TryGetObjectClassFromProperty(const FProperty& MetaDataProperty) -{ - if (UClass* MetaClass = FFlowDataPinOutputProperty_Class::TryGetMetaClassFromProperty(MetaDataProperty)) - { - return MetaClass; - } - - // FSoftObjectPath can use the "AllowedClasses" to define what classes are allowed for the object. - // Using the "AllowedClasses" metadata tag, but we only support a single class, due to singular return value for this function. - const FString AllowedClassesString = MetaDataProperty.GetMetaData("AllowedClasses"); - const TArray AllowedClasses = FlowClassUtils::GetClassesFromMetadataString(AllowedClassesString); - - if (AllowedClasses.Num() > 1) - { - UE_LOG(LogFlow, Error, TEXT("Only a single AllowedClasses entry is allowed for flow data pin properties (multiple found: %s) for property %s"), *AllowedClassesString, *MetaDataProperty.GetName()); - - return nullptr; - } - - if (AllowedClasses.IsEmpty()) - { - return nullptr; - } - - if (UClass* AllowedClass = AllowedClasses[0]) - { - return AllowedClass; - } - else - { - UE_LOG(LogFlow, Error, TEXT("Could not resolve AllowedClasses '%s' for property %s"), *AllowedClassesString, *MetaDataProperty.GetName()); - } - - return nullptr; -} -#endif - -bool FFlowNamedDataPinProperty::IsInputProperty() const -{ - if (const FFlowDataPinProperty* DataPinPropertyPtr = DataPinProperty.GetPtr()) - { - return DataPinPropertyPtr->IsInputProperty(); - } - - return false; -} - -bool FFlowNamedDataPinProperty::IsOutputProperty() const -{ - if (const FFlowDataPinProperty* DataPinPropertyPtr = DataPinProperty.GetPtr()) - { - return !DataPinPropertyPtr->IsInputProperty(); - } - - return false; -} +#define LOCTEXT_NAMESPACE "FlowDataPinProperties" FFlowDataPinOutputProperty_Object::FFlowDataPinOutputProperty_Object(UObject* InValue, UClass* InClassFilter) : Super() #if WITH_EDITOR , ClassFilter(InClassFilter) #endif -{ - SetObjectValue(InValue); -} - -void FFlowDataPinOutputProperty_Object::SetObjectValue(UObject* InValue) { UClass* ObjectClass = IsValid(InValue) ? InValue->GetClass() : nullptr; if (IsValid(ObjectClass)) @@ -222,3 +41,4 @@ void FFlowDataPinOutputProperty_Object::SetObjectValue(UObject* InValue) } #undef LOCTEXT_NAMESPACE +// -- \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowDataPinResults.cpp b/Source/Flow/Private/Types/FlowDataPinResults.cpp index f8e5f440c..7f92c5df6 100644 --- a/Source/Flow/Private/Types/FlowDataPinResults.cpp +++ b/Source/Flow/Private/Types/FlowDataPinResults.cpp @@ -1,9 +1,9 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Types/FlowDataPinResults.h" -#include "Types/FlowDataPinProperties.h" +#include "Types/FlowDataPinValuesStandard.h" -// FFlowDataPinResult_Object +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinResults) FFlowDataPinResult_Object::FFlowDataPinResult_Object(UObject* InValue) : Super(EFlowDataPinResolveResult::Success) @@ -11,11 +11,6 @@ FFlowDataPinResult_Object::FFlowDataPinResult_Object(UObject* InValue) SetValueFromObjectPtr(InValue); } -void FFlowDataPinResult_Object::SetValueFromPropertyWrapper(const FFlowDataPinOutputProperty_Object& InPropertyWrapper) -{ - Value = InPropertyWrapper.GetObjectValue(); -} - // FFlowDataPinResult_Class FFlowDataPinResult_Class::FFlowDataPinResult_Class(const FSoftClassPath& InValuePath) @@ -30,11 +25,6 @@ FFlowDataPinResult_Class::FFlowDataPinResult_Class(UClass* InValueClass) SetValueFromObjectPtr(InValueClass); } -void FFlowDataPinResult_Class::SetValueFromPropertyWrapper(const FFlowDataPinOutputProperty_Class& PropertyWrapper) -{ - SetValueFromSoftPath(PropertyWrapper.GetAsSoftClass()); -} - void FFlowDataPinResult_Class::SetValueFromSoftPath(const FSoftObjectPath& SoftObjectPath) { const FSoftClassPath SoftClassPath(SoftObjectPath.ToString()); diff --git a/Source/Flow/Private/Types/FlowDataPinValue.cpp b/Source/Flow/Private/Types/FlowDataPinValue.cpp new file mode 100644 index 000000000..b10c3e206 --- /dev/null +++ b/Source/Flow/Private/Types/FlowDataPinValue.cpp @@ -0,0 +1,16 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowDataPinValue.h" +#include "Types/FlowDataPinResults.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "Types/FlowPinType.h" +#include "Nodes/FlowPin.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinValue) + +const FString FFlowDataPinValue::StringArraySeparator = TEXT(","); + +const FFlowPinType* FFlowDataPinValue::LookupPinType() const +{ + return FFlowPinType::LookupPinType(GetPinTypeName()); +} diff --git a/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp new file mode 100644 index 000000000..f074f85e3 --- /dev/null +++ b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp @@ -0,0 +1,606 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowDataPinValuesStandard.h" +#include "Nodes/FlowPin.h" +#include "GameFramework/Actor.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinValuesStandard) + +//====================================================================== +// Bool +//====================================================================== +FFlowDataPinValue_Bool::FFlowDataPinValue_Bool(ValueType InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Bool::FFlowDataPinValue_Bool(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Bool::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](ValueType b) + { + return b ? TEXT("true") : TEXT("false"); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Int (int32) +//====================================================================== +FFlowDataPinValue_Int::FFlowDataPinValue_Int(ValueType InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Int::FFlowDataPinValue_Int(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Int::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](ValueType Val) + { + return FString::FromInt(Val); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Int64 +//====================================================================== +FFlowDataPinValue_Int64::FFlowDataPinValue_Int64(ValueType InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Int64::FFlowDataPinValue_Int64(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Int64::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](ValueType Val) + { + return FString::Printf(TEXT("%lld"), Val); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Float +//====================================================================== +FFlowDataPinValue_Float::FFlowDataPinValue_Float(ValueType InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Float::FFlowDataPinValue_Float(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Float::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](ValueType Val) + { + return FString::SanitizeFloat(Val); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Double +//====================================================================== +FFlowDataPinValue_Double::FFlowDataPinValue_Double(ValueType InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Double::FFlowDataPinValue_Double(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Double::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](ValueType Val) + { + return FString::SanitizeFloat(Val); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Name +//====================================================================== +FFlowDataPinValue_Name::FFlowDataPinValue_Name(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Name::FFlowDataPinValue_Name(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Name::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& Val) + { + return Val.ToString(); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// String +//====================================================================== +FFlowDataPinValue_String::FFlowDataPinValue_String(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_String::FFlowDataPinValue_String(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_String::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& Val) + { + return Val; + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Text +//====================================================================== +FFlowDataPinValue_Text::FFlowDataPinValue_Text(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Text::FFlowDataPinValue_Text(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Text::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& Val) + { + return Val.ToString(); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Enum +//====================================================================== +FFlowDataPinValue_Enum::FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, const ValueType& InValue) + : Values({ InValue }), EnumClass(InEnumClass) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Enum::FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, const TArray& InValues) + : Values(InValues), EnumClass(InEnumClass) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +#if WITH_EDITOR +void FFlowDataPinValue_Enum::OnEnumNameChanged() +{ + if (!EnumName.IsEmpty()) + { + EnumClass = UClass::TryFindTypeSlow(EnumName, EFindFirstObjectOptions::ExactClass); + + if (EnumClass != nullptr && !FFlowPin::ValidateEnum(*EnumClass)) + { + EnumClass = nullptr; + } + } +} +#endif + +UField* FFlowDataPinValue_Enum::GetFieldType() const +{ + return EnumClass.LoadSynchronous(); +} + +bool FFlowDataPinValue_Enum::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& Val) { return Val.ToString(); }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Vector +//====================================================================== +FFlowDataPinValue_Vector::FFlowDataPinValue_Vector(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Vector::FFlowDataPinValue_Vector(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Vector::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& V) + { + return V.ToCompactString(); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Rotator +//====================================================================== +FFlowDataPinValue_Rotator::FFlowDataPinValue_Rotator(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Rotator::FFlowDataPinValue_Rotator(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Rotator::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& R) + { + return R.ToCompactString(); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Transform +//====================================================================== +FFlowDataPinValue_Transform::FFlowDataPinValue_Transform(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Transform::FFlowDataPinValue_Transform(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_Transform::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& T) + { + return T.ToString(); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// GameplayTag +//====================================================================== +FFlowDataPinValue_GameplayTag::FFlowDataPinValue_GameplayTag(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_GameplayTag::FFlowDataPinValue_GameplayTag(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +bool FFlowDataPinValue_GameplayTag::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const ValueType& Tag) + { + return Tag.ToString(); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// GameplayTagContainer +//====================================================================== +FFlowDataPinValue_GameplayTagContainer::FFlowDataPinValue_GameplayTagContainer(const FGameplayTag& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_GameplayTagContainer::FFlowDataPinValue_GameplayTagContainer(const FGameplayTagContainer& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_GameplayTagContainer::FFlowDataPinValue_GameplayTagContainer(const TArray& InValues) + : Values(FGameplayTagContainer::CreateFromArray(InValues)) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +FFlowDataPinValue_GameplayTagContainer::FFlowDataPinValue_GameplayTagContainer(const TArray& InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif + + for (const FGameplayTagContainer& Container : InValues) + { + Values.AppendTags(Container); + } +} + +bool FFlowDataPinValue_GameplayTagContainer::TryConvertValuesToString(FString& OutString) const +{ + OutString = Values.ToStringSimple(); + return true; +} + +//====================================================================== +// InstancedStruct +//====================================================================== +FFlowDataPinValue_InstancedStruct::FFlowDataPinValue_InstancedStruct(const ValueType& InValue) + : Values({ InValue }) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_InstancedStruct::FFlowDataPinValue_InstancedStruct(const TArray& InValues) + : Values(InValues) +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +//====================================================================== +// Object +//====================================================================== +FFlowDataPinValue_Object::FFlowDataPinValue_Object(TObjectPtr InObject, UClass* InClassFilter) +#if WITH_EDITOR + : ClassFilter(InClassFilter) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif + Values = { InObject }; +} + +FFlowDataPinValue_Object::FFlowDataPinValue_Object(const TArray>& InObjects, UClass* InClassFilter) +#if WITH_EDITOR + : ClassFilter(InClassFilter) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif + Values = InObjects; +} + +FFlowDataPinValue_Object::FFlowDataPinValue_Object(AActor* InActor, UClass* InClassFilter) +#if WITH_EDITOR + : ClassFilter(InClassFilter ? InClassFilter : AActor::StaticClass()) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif + Values = { InActor }; +} + +FFlowDataPinValue_Object::FFlowDataPinValue_Object(const TArray& InActors, UClass* InClassFilter) +#if WITH_EDITOR + : ClassFilter(InClassFilter ? InClassFilter : AActor::StaticClass()) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif + + Values.Reset(); + Values.Reserve(InActors.Num()); + for (AActor* Actor : InActors) + { + Values.Add(Cast(Actor)); + } +} + +bool FFlowDataPinValue_Object::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](UObject* Obj) + { + return Obj ? Obj->GetName() : TEXT("None"); + }, + StringArraySeparator); + return true; +} + +//====================================================================== +// Class +//====================================================================== +FFlowDataPinValue_Class::FFlowDataPinValue_Class(const FSoftClassPath& InPath, UClass* InClassFilter) + : Values({ InPath }) +#if WITH_EDITOR + , ClassFilter(InClassFilter) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Class::FFlowDataPinValue_Class(const TArray& InPaths, UClass* InClassFilter) + : Values(InPaths) +#if WITH_EDITOR + , ClassFilter(InClassFilter) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif +} + +FFlowDataPinValue_Class::FFlowDataPinValue_Class(const UClass* InClass, UClass* InClassFilter) + : Values({ FSoftClassPath(InClass) }) +#if WITH_EDITOR + , ClassFilter(InClassFilter) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Single; +#endif +} + +FFlowDataPinValue_Class::FFlowDataPinValue_Class(const TArray& InClasses, UClass* InClassFilter) +#if WITH_EDITOR + : ClassFilter(InClassFilter) +#endif +{ +#if WITH_EDITOR + MultiType = EFlowDataMultiType::Array; +#endif + + Values.Reset(); + Values.Reserve(InClasses.Num()); + for (UClass* Class : InClasses) + { + Values.Add(FSoftClassPath(Class)); + } +} + +bool FFlowDataPinValue_Class::TryConvertValuesToString(FString& OutString) const +{ + OutString = FlowArray::FormatArrayString(Values, + [](const FSoftClassPath& Path) + { + return Path.IsValid() ? Path.GetAssetName() : TEXT("None"); + }, + StringArraySeparator); + return true; +} \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowNamedDataPinProperty.cpp b/Source/Flow/Private/Types/FlowNamedDataPinProperty.cpp new file mode 100644 index 000000000..6069c6ec3 --- /dev/null +++ b/Source/Flow/Private/Types/FlowNamedDataPinProperty.cpp @@ -0,0 +1,38 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowNamedDataPinProperty.h" +#include "Types/FlowDataPinPropertyToValueMigration.h" +#include "Types/FlowPinTypeNamesStandard.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNamedDataPinProperty) + +#define LOCTEXT_NAMESPACE "FlowNamedDataPinProperty" + +#if WITH_EDITOR +FFlowPin FFlowNamedDataPinProperty::CreateFlowPin() const +{ + if (const FFlowDataPinValue* FlowDataPinValuePtr = DataPinValue.GetPtr()) + { + if (const FFlowPinType* FlowPinType = FFlowPinType::LookupPinType(FlowDataPinValuePtr->GetPinTypeName())) + { + return FlowPinType->CreateFlowPinFromValueWrapper(Name, *FlowDataPinValuePtr); + } + } + + return FFlowPin(); +} + +FText FFlowNamedDataPinProperty::BuildHeaderText() const +{ + FFlowPinTypeName PinTypeName; + + if (const FFlowDataPinValue* FlowDataPinValuePtr = DataPinValue.GetPtr()) + { + PinTypeName = FlowDataPinValuePtr->GetPinTypeName(); + } + + return FText::Format(LOCTEXT("FlowNamedFFlowDataPinValueHeader", "{0} ({1})"), { FText::FromName(Name), FText::FromString(PinTypeName.ToString()) }); +} +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Flow/Private/Types/FlowPinType.cpp b/Source/Flow/Private/Types/FlowPinType.cpp new file mode 100644 index 000000000..637522dd3 --- /dev/null +++ b/Source/Flow/Private/Types/FlowPinType.cpp @@ -0,0 +1,99 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowPinType.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowStructUtils.h" +#include "Nodes/FlowNode.h" +#include "FlowPinSubsystem.h" +#include "FlowLogChannels.h" +#if WITH_EDITOR +#include "PropertyHandle.h" +#endif + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowPinType) + +const FFlowPinTypeName FFlowPinType::PinTypeNameUnknown = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameUnknown); + +const FFlowPinType* FFlowPinType::LookupPinType(const FFlowPinTypeName& PinTypeName) +{ + const FFlowPinType* PinType = UFlowPinSubsystem::Get()->FindPinType(PinTypeName); + + if (!PinType) + { + UE_LOG(LogFlow, Error, TEXT("Could not find pin type %s in FlowPinSubsystem"), *PinTypeName.ToString()); + return nullptr; + } + + return PinType; +} + +bool FFlowPinType::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return false; +} + +bool FFlowPinType::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + OutResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; + return false; +} + +#if WITH_EDITOR +FFlowPin FFlowPinType::CreateFlowPinFromProperty(const FProperty& Property, void const* InContainer) const +{ + FFlowPin NewFlowPin; + + if (const FFlowDataPinValue* DataPinValue = FlowStructUtils::CastStructValue(&Property, InContainer)) + { + // Create the pin from a FFlowDataPinValue property wrapper + NewFlowPin = CreateFlowPinFromValueWrapper(Property.GetFName(), *DataPinValue); + } + else + { + // Create the pin from a native property + NewFlowPin.PinName = Property.GetFName(); + NewFlowPin.SetPinTypeName(GetPinTypeName()); + + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + if (const FArrayProperty* ArrayProperty = CastField(&Property)) + { + NewFlowPin.ContainerType = EPinContainerType::Array; + } + + UObject* SubCategoryObject = GetPinSubCatetoryObjectFromProperty(&Property, InContainer, DataPinValue); + NewFlowPin.SetPinSubCategoryObject(SubCategoryObject); + } + + // Common property settings for both versions + NewFlowPin.PinFriendlyName = Property.GetDisplayNameText(); + NewFlowPin.PinToolTip = Property.GetToolTipText().ToString(); + + return NewFlowPin; +} + +FFlowPin FFlowPinType::CreateFlowPinFromValueWrapper(const FName& PinName, const FFlowDataPinValue& Wrapper) const +{ + FFlowPin NewFlowPin(PinName); + + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + if (Wrapper.IsArray()) + { + NewFlowPin.ContainerType = EPinContainerType::Array; + } + + constexpr const FProperty* Property = nullptr; + constexpr void const* InContainer = nullptr; + UObject* SubCategoryObject = GetPinSubCatetoryObjectFromProperty(Property, InContainer, &Wrapper); + NewFlowPin.SetPinSubCategoryObject(SubCategoryObject); + + // Common property settings for both versions + NewFlowPin.SetPinTypeName(GetPinTypeName()); + + return NewFlowPin; +} + +TSharedPtr FFlowPinType::GetValuesHandle(const TSharedRef& FlowDataPinValuePropertyHandle) const +{ + return FlowDataPinValuePropertyHandle.Get().GetChildHandle(TEXT("Values")); +} +#endif \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp b/Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp new file mode 100644 index 000000000..28d1b0c94 --- /dev/null +++ b/Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp @@ -0,0 +1,162 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowPinTypeNamesStandard.h" + +#if WITH_EDITOR + +// Cross-conversion rules: +// - Most* types → String (one-way) (*except InstancedStruct) +// - Numeric: full bidirectional conversion +// - Name/String/Text: full bidirectional +// - GameplayTag ↔ Container: bidirectional + +const TMap FFlowPinTypeNamesStandard::PinTypeMatchPolicies = +{ + { FFlowPinTypeNamesStandard::PinTypeNameBool, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameInt, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameInt64, + FFlowPinTypeNamesStandard::PinTypeNameFloat, + FFlowPinTypeNamesStandard::PinTypeNameDouble + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameInt64, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameInt, + FFlowPinTypeNamesStandard::PinTypeNameFloat, + FFlowPinTypeNamesStandard::PinTypeNameDouble + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameFloat, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameInt, + FFlowPinTypeNamesStandard::PinTypeNameInt64, + FFlowPinTypeNamesStandard::PinTypeNameDouble + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameDouble, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameInt, + FFlowPinTypeNamesStandard::PinTypeNameInt64, + FFlowPinTypeNamesStandard::PinTypeNameFloat + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameEnum, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameName, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameString, + FFlowPinTypeNamesStandard::PinTypeNameText + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameString, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameName, + FFlowPinTypeNamesStandard::PinTypeNameText, + + // All other types (except InstancedStruct) can cross-convert to string + FFlowPinTypeNamesStandard::PinTypeNameBool, + FFlowPinTypeNamesStandard::PinTypeNameInt, + FFlowPinTypeNamesStandard::PinTypeNameInt64, + FFlowPinTypeNamesStandard::PinTypeNameFloat, + FFlowPinTypeNamesStandard::PinTypeNameDouble, + FFlowPinTypeNamesStandard::PinTypeNameEnum, + FFlowPinTypeNamesStandard::PinTypeNameVector, + FFlowPinTypeNamesStandard::PinTypeNameRotator, + FFlowPinTypeNamesStandard::PinTypeNameTransform, + FFlowPinTypeNamesStandard::PinTypeNameGameplayTag, + FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer, + FFlowPinTypeNamesStandard::PinTypeNameObject, + FFlowPinTypeNamesStandard::PinTypeNameClass + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameText, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameString, + FFlowPinTypeNamesStandard::PinTypeNameName + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameVector, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameRotator, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameTransform, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameGameplayTag, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer, + { + EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask, + { + FFlowPinTypeNamesStandard::PinTypeNameGameplayTag + }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameInstancedStruct, + { + EFlowPinTypeMatchRules::SubCategoryObjectPinTypeMatchRulesMask, + { }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameObject, + { + EFlowPinTypeMatchRules::SubCategoryObjectPinTypeMatchRulesMask, + { }, + } + }, + { FFlowPinTypeNamesStandard::PinTypeNameClass, + { + EFlowPinTypeMatchRules::SubCategoryObjectPinTypeMatchRulesMask, + { }, + } + }, +}; +#endif \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowPinTypesStandard.cpp b/Source/Flow/Private/Types/FlowPinTypesStandard.cpp new file mode 100644 index 000000000..89c502d95 --- /dev/null +++ b/Source/Flow/Private/Types/FlowPinTypesStandard.cpp @@ -0,0 +1,503 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowPinTypesStandard.h" +#include "Types/FlowClassUtils.h" +#include "Nodes/FlowNode.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "Types/FlowDataPinResults.h" +#include "Types/FlowPinTypeTemplates.h" +#include "Types/FlowPinTypeNodeTemplates.h" +#include "Types/FlowArray.h" +#include "FlowLogChannels.h" +#if WITH_EDITOR +#include "EditorClassUtils.h" +#endif + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowPinTypesStandard) + +// NOTE (gtaylor) Beware static initialization order if attempting to use these in static initialization. +// Instead, consider sourcing directly from the FFlowPinTypeNamesStandard's TCHAR form in those cases. +const FFlowPinTypeName FFlowPinType_Exec::PinTypeNameExec = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameExec); +const FFlowPinTypeName FFlowPinType_Bool::PinTypeNameBool = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameBool); +const FFlowPinTypeName FFlowPinType_Int::PinTypeNameInt = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameInt); +const FFlowPinTypeName FFlowPinType_Int64::PinTypeNameInt64 = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameInt64); +const FFlowPinTypeName FFlowPinType_Float::PinTypeNameFloat = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameFloat); +const FFlowPinTypeName FFlowPinType_Double::PinTypeNameDouble = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameDouble); +const FFlowPinTypeName FFlowPinType_Enum::PinTypeNameEnum = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameEnum); +const FFlowPinTypeName FFlowPinType_Name::PinTypeNameName = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameName); +const FFlowPinTypeName FFlowPinType_String::PinTypeNameString = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameString); +const FFlowPinTypeName FFlowPinType_Text::PinTypeNameText = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameText); +const FFlowPinTypeName FFlowPinType_Vector::PinTypeNameVector = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameVector); +const FFlowPinTypeName FFlowPinType_Rotator::PinTypeNameRotator = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameRotator); +const FFlowPinTypeName FFlowPinType_Transform::PinTypeNameTransform = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameTransform); +const FFlowPinTypeName FFlowPinType_GameplayTag::PinTypeNameGameplayTag = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameGameplayTag); +const FFlowPinTypeName FFlowPinType_GameplayTagContainer::PinTypeNameGameplayTagContainer = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameGameplayTagContainer); +const FFlowPinTypeName FFlowPinType_InstancedStruct::PinTypeNameInstancedStruct = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameInstancedStruct); +const FFlowPinTypeName FFlowPinType_Object::PinTypeNameObject = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameObject); +const FFlowPinTypeName FFlowPinType_Class::PinTypeNameClass = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameClass); + +bool FFlowPinType_Bool::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Int::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Int64::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Float::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Double::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Enum::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + using TFlowPinType = FFlowPinType_Enum; + using TValue = typename TFlowPinType::ValueType; + using TWrapper = typename TFlowPinType::WrapperType; + using Traits = FlowPinType::FFlowDataPinValueTraits; + + TInstancedStruct ValueStruct; + const FProperty* FoundProperty = nullptr; + + if (!Node.TryFindPropertyByPinName(PropertyOwnerObject, Pin.PinName, FoundProperty, ValueStruct)) + { + OutResult.Result = EFlowDataPinResolveResult::FailedUnknownPin; + return false; + } + + if (ValueStruct.IsValid() && ValueStruct.Get().GetPinTypeName() == TFlowPinType::GetPinTypeNameStatic()) + { + OutResult.ResultValue = ValueStruct; + OutResult.Result = EFlowDataPinResolveResult::Success; + return true; + } + + TArray Values; + TSoftObjectPtr EnumClass; + if (FlowPinType::IsSuccess(Traits::ExtractFromProperty(FoundProperty, &PropertyOwnerObject, Values, EnumClass))) + { + OutResult.ResultValue = TInstancedStruct::Make(EnumClass, Values); + OutResult.Result = EFlowDataPinResolveResult::Success; + return true; + } + + OutResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; + return false; +} + +bool FFlowPinType_Name::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_String::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Text::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Vector::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Rotator::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Transform::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_GameplayTag::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_GameplayTagContainer::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_InstancedStruct::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Object::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +bool FFlowPinType_Class::PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const +{ + return FlowPinType::PopulateResultTemplate(PropertyOwnerObject, Node, Pin, OutResult); +} + +#if WITH_EDITOR + +UObject* FFlowPinType_Vector::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + static UObject* PinSubCategoryObject = TBaseStructure::Get(); + return PinSubCategoryObject; +} + +UObject* FFlowPinType_Rotator::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + static UObject* PinSubCategoryObject = TBaseStructure::Get(); + return PinSubCategoryObject; +} + +UObject* FFlowPinType_Transform::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + static UObject* PinSubCategoryObject = TBaseStructure::Get(); + return PinSubCategoryObject; +} + +UObject* FFlowPinType_GameplayTag::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + static UObject* PinSubCategoryObject = TBaseStructure::Get(); + return PinSubCategoryObject; +} + +UObject* FFlowPinType_GameplayTagContainer::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + static UObject* PinSubCategoryObject = TBaseStructure::Get(); + return PinSubCategoryObject; +} + +UObject* FFlowPinType_InstancedStruct::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + static UObject* PinSubCategoryObject = TBaseStructure::Get(); + return PinSubCategoryObject; +} + +UObject* FFlowPinType_Enum::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + UEnum* EnumClass = nullptr; + if (Wrapper && Wrapper->GetPinTypeName() == FFlowPinType_Enum::GetPinTypeNameStatic()) + { + const FFlowDataPinValue_Enum* EnumWrapper = static_cast(Wrapper); + TSoftObjectPtr EnumClassPtr = EnumWrapper->EnumClass; + EnumClass = EnumClassPtr.LoadSynchronous(); + } + else if (Property) + { + if (const FStructProperty* StructProperty = CastField(Property)) + { + const UStruct* ScriptStruct = FFlowDataPinValue_Enum::StaticStruct(); + if (StructProperty->Struct == ScriptStruct) + { + FFlowDataPinValue_Enum ValueStruct; + StructProperty->GetValue_InContainer(InContainer, &ValueStruct); + EnumClass = ValueStruct.EnumClass.LoadSynchronous(); + } + } + else if (const FEnumProperty* EnumProperty = CastField(Property)) + { + EnumClass = EnumProperty->GetEnum(); + } + else if (const FByteProperty* ByteProperty = CastField(Property)) + { + if (ByteProperty->Enum) + { + EnumClass = ByteProperty->Enum; + } + } + else if (const FArrayProperty* ArrayProperty = CastField(Property)) + { + if (const FEnumProperty* InnerEnumProperty = CastField(ArrayProperty->Inner)) + { + EnumClass = InnerEnumProperty->GetEnum(); + } + else if (const FByteProperty* InnerByteProperty = CastField(ArrayProperty->Inner)) + { + if (InnerByteProperty->Enum) + { + EnumClass = InnerByteProperty->Enum; + } + } + } + } + + return EnumClass; +} + +UClass* FFlowPinType_Object::TryGetObjectClassFromProperty(const FProperty& MetaDataProperty) +{ + if (UClass* MetaClass = FFlowPinType_Object::TryGetMetaClassFromProperty(MetaDataProperty)) + { + return MetaClass; + } + + // FSoftObjectPath can use the "AllowedClasses" to define what classes are allowed for the object. + // Using the "AllowedClasses" metadata tag, but we only support a single class, due to singular return value for this function. + const FString AllowedClassesString = MetaDataProperty.GetMetaData("AllowedClasses"); + const TArray AllowedClasses = FlowClassUtils::GetClassesFromMetadataString(AllowedClassesString); + + if (AllowedClasses.Num() > 1) + { + UE_LOG(LogFlow, Error, TEXT("Only a single AllowedClasses entry is allowed for flow data pin properties (multiple found: %s) for property %s"), *AllowedClassesString, *MetaDataProperty.GetName()); + + return nullptr; + } + + if (AllowedClasses.IsEmpty()) + { + return nullptr; + } + + if (UClass* AllowedClass = AllowedClasses[0]) + { + return AllowedClass; + } + else + { + UE_LOG(LogFlow, Error, TEXT("Could not resolve AllowedClasses '%s' for property %s"), *AllowedClassesString, *MetaDataProperty.GetName()); + } + + return nullptr; +} + +UClass* FFlowPinType_Object::TryGetMetaClassFromProperty(const FProperty& MetaDataProperty) +{ + const FString& MetaClassName = MetaDataProperty.GetMetaData("MetaClass"); + + if (!MetaClassName.IsEmpty()) + { + if (UClass* FoundClass = FEditorClassUtils::GetClassFromString(MetaClassName)) + { + return FoundClass; + } + else + { + UE_LOG(LogFlow, Error, TEXT("Could not resolve MetaClass named %s for property %s"), *MetaClassName, *MetaDataProperty.GetName()); + } + } + + return nullptr; +} + +UObject* FFlowPinType_Object::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + UClass* Class = nullptr; + if (Wrapper && Wrapper->GetPinTypeName() == FFlowPinType_Object::GetPinTypeNameStatic()) + { + const FFlowDataPinValue_Object* ObjectWrapper = static_cast(Wrapper); + Class = ObjectWrapper->ClassFilter; + } + else if (Property) + { + if (const FStructProperty* StructProperty = CastField(Property)) + { + const UStruct* ScriptStruct = FFlowDataPinValue_Object::StaticStruct(); + static const UStruct* SoftObjectPathStruct = TBaseStructure::Get(); + if (StructProperty->Struct == ScriptStruct) + { + FFlowDataPinValue_Object ValueStruct; + StructProperty->GetValue_InContainer(InContainer, &ValueStruct); + Class = ValueStruct.ClassFilter; + } + else if (StructProperty->Struct == SoftObjectPathStruct) + { + Class = FFlowPinType_Object::TryGetObjectClassFromProperty(*StructProperty); + } + } + else if (const FObjectProperty* ObjectProperty = CastField(Property)) + { + Class = ObjectProperty->PropertyClass; + } + else if (const FSoftObjectProperty* SoftObjectProperty = CastField(Property)) + { + Class = SoftObjectProperty->PropertyClass; + } + else if (const FWeakObjectProperty* WeakObjectProperty = CastField(Property)) + { + Class = WeakObjectProperty->PropertyClass; + } + else if (const FLazyObjectProperty* LazyObjectProperty = CastField(Property)) + { + Class = LazyObjectProperty->PropertyClass; + } + else if (const FArrayProperty* ArrayProperty = CastField(Property)) + { + if (const FObjectProperty* InnerObjectProperty = CastField(ArrayProperty->Inner)) + { + Class = InnerObjectProperty->PropertyClass; + } + else if (const FSoftObjectProperty* InnerSoftObjectProperty = CastField(ArrayProperty->Inner)) + { + Class = InnerSoftObjectProperty->PropertyClass; + } + else if (const FWeakObjectProperty* InnerWeakObjectProperty = CastField(ArrayProperty->Inner)) + { + Class = InnerWeakObjectProperty->PropertyClass; + } + else if (const FLazyObjectProperty* InnerLazyObjectProperty = CastField(ArrayProperty->Inner)) + { + Class = InnerLazyObjectProperty->PropertyClass; + } + } + } + + return Class; +} + +UObject* FFlowPinType_Class::GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const +{ + UClass* Class = nullptr; + if (Wrapper && Wrapper->GetPinTypeName() == FFlowPinType_Class::GetPinTypeNameStatic()) + { + const FFlowDataPinValue_Class* ClassWrapper = static_cast(Wrapper); + Class = ClassWrapper->ClassFilter; + } + else if (Property) + { + if (const FStructProperty* StructProperty = CastField(Property)) + { + const UStruct* ScriptStruct = FFlowDataPinValue_Class::StaticStruct(); + static const UStruct* SoftClassPathStruct = TBaseStructure::Get(); + if (StructProperty->Struct == ScriptStruct) + { + FFlowDataPinValue_Class ValueStruct; + StructProperty->GetValue_InContainer(InContainer, &ValueStruct); + Class = ValueStruct.ClassFilter; + } + else if (StructProperty->Struct == SoftClassPathStruct) + { + Class = FFlowPinType_Object::TryGetMetaClassFromProperty(*StructProperty); + } + } + else if (const FClassProperty* ClassProperty = CastField(Property)) + { + Class = ClassProperty->MetaClass; + } + else if (const FSoftClassProperty* SoftClassProperty = CastField(Property)) + { + Class = SoftClassProperty->MetaClass; + } + else if (const FArrayProperty* ArrayProperty = CastField(Property)) + { + if (const FClassProperty* InnerClassProperty = CastField(ArrayProperty->Inner)) + { + Class = InnerClassProperty->MetaClass; + } + else if (const FSoftClassProperty* InnerSoftClassProperty = CastField(ArrayProperty->Inner)) + { + Class = InnerSoftClassProperty->MetaClass; + } + } + } + + return Class; +} + +bool FFlowPinType_Exec::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + OutValue = FFormatArgumentValue(FText::FromString(TEXT("Exec"))); + return true; +} + +bool FFlowPinType_Bool::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const bool& Value) { return Value ? TEXT("true") : TEXT("false"); }); +} + +bool FFlowPinType_Int::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const int32& Value) { return FString::FromInt(Value); }); +} + +bool FFlowPinType_Int64::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const int64& Value) { return FString::Printf(TEXT("%lld"), Value); }); +} + +bool FFlowPinType_Float::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const float& Value) { return FString::SanitizeFloat(Value); }); +} + +bool FFlowPinType_Double::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const double& Value) { return FString::SanitizeFloat(Value); }); +} + +bool FFlowPinType_Enum::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FName& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_Name::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FName& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_String::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FString& Value) { return Value; }); +} + +bool FFlowPinType_Text::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FText& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_Vector::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FVector& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_Rotator::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FRotator& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_Transform::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FTransform& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_GameplayTag::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FGameplayTag& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_GameplayTagContainer::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FGameplayTagContainer& Value) { return Value.ToString(); }); +} + +bool FFlowPinType_InstancedStruct::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const FInstancedStruct& Value) { return Value.GetScriptStruct() ? Value.GetScriptStruct()->GetName() : TEXT("None"); }); +} + +bool FFlowPinType_Object::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const UObject* Value) { return Value ? Value->GetName() : TEXT("None"); }); +} + +bool FFlowPinType_Class::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + return FlowPinType::ResolveAndFormatArray(Node, PinName, OutValue, [](const UClass* Value) { return Value ? Value->GetName() : TEXT("None"); }); +} +#endif \ No newline at end of file diff --git a/Source/Flow/Public/Asset/FlowAssetParams.h b/Source/Flow/Public/Asset/FlowAssetParams.h new file mode 100644 index 000000000..be14fb535 --- /dev/null +++ b/Source/Flow/Public/Asset/FlowAssetParams.h @@ -0,0 +1,110 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Engine/DataAsset.h" +#include "Types/FlowNamedDataPinProperty.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.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 IFlowAssetProviderInterface + , public IFlowDataPinValueOwnerInterface + , public IFlowDataPinValueSupplierInterface +{ + 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 +#if WITH_EDITOR + virtual void PostLoad() override; + virtual void PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext) override; +#endif + virtual void Serialize(FArchive& Ar) override; + // -- + + // IFlowDataPinValueSupplierInterface + virtual bool CanSupplyDataPinValues_Implementation() const override; + virtual FFlowDataPinResult TrySupplyDataPin_Implementation(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); + + // IFlowDataPinValueOwnerInterface + virtual bool CanModifyFlowDataPinType() const override; + virtual bool ShowFlowDataPinValueInputPinCheckbox() const override; + virtual bool ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const override; + virtual bool CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const override; + virtual void SetFlowDataPinValuesRebuildDelegate(FSimpleDelegate InDelegate) override + { + FlowDataPinValuesRebuildDelegate = InDelegate; + } + + virtual void RequestFlowDataPinValuesDetailsRebuild() override + { + if (FlowDataPinValuesRebuildDelegate.IsBound()) + { + FlowDataPinValuesRebuildDelegate.Execute(); + } + } + +private: + FSimpleDelegate FlowDataPinValuesRebuildDelegate; + // -- + +protected: + + // Updates properties from ParentParams, handling inheritance and name enforcement. + EFlowReconcilePropertiesResult ReconcilePropertiesWithParentParams(); + + 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..95e50c7e0 --- /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 FLOW_API 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/Asset/FlowPinTypeMatchPolicy.h b/Source/Flow/Public/Asset/FlowPinTypeMatchPolicy.h new file mode 100644 index 000000000..ee2d8a32c --- /dev/null +++ b/Source/Flow/Public/Asset/FlowPinTypeMatchPolicy.h @@ -0,0 +1,43 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowPinTypeMatchPolicy.generated.h" + +UENUM(meta = (BitFlags)) +enum class EFlowPinTypeMatchRules : uint32 +{ + None = 0, + + RequirePinCategoryMatch = 1 << 0, + RequirePinCategoryMemberReferenceMatch = 1 << 1, + RequireContainerTypeMatch = 1 << 2, + RequirePinSubCategoryObjectMatch = 1 << 3, + AllowSubCategoryObjectSubclasses = 1 << 4, + AllowSubCategoryObjectSameLayout = 1 << 5, + SameLayoutMustMatchPropertyNames = 1 << 6, + + // Masks for convenience + StandardPinTypeMatchRulesMask = + RequirePinCategoryMatch | + RequirePinCategoryMemberReferenceMatch | + AllowSubCategoryObjectSubclasses | + AllowSubCategoryObjectSameLayout UMETA(Hidden), + + SubCategoryObjectPinTypeMatchRulesMask = + StandardPinTypeMatchRulesMask | + RequirePinSubCategoryObjectMatch UMETA(Hidden), +}; + +USTRUCT() +struct FFlowPinTypeMatchPolicy +{ + GENERATED_BODY() + + UPROPERTY() + EFlowPinTypeMatchRules PinTypeMatchRules = EFlowPinTypeMatchRules::StandardPinTypeMatchRulesMask; + + // Pin categories to allow beyond an exact match + UPROPERTY() + TSet PinCategories; +}; diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index 38b9e206d..2b5ec6f2e 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,42 +22,13 @@ class UFlowSubsystem; class UEdGraph; class UEdGraphNode; class UFlowAsset; +class UFlowAssetParams; #if !UE_BUILD_SHIPPING DECLARE_DELEGATE(FFlowGraphEvent); DECLARE_DELEGATE_TwoParams(FFlowSignalEvent, const FGuid& /*NodeGuid*/, const FName& /*PinName*/); #endif -// Working Data struct for the Harvest Data Pins operation -// (passed between functions involved in the harvesting operation to simplify the function signatures) -struct FFlowHarvestDataPinsWorkingData -{ - FFlowHarvestDataPinsWorkingData(UFlowNode& InFlowNode, const TMap& PinNameMapPrev, const TArray& InputPinsPrev, const TArray& OutputPinsPrev) - : FlowNode(&InFlowNode) - , PinNameToBoundPropertyNameMapPrev(PinNameMapPrev) - , AutoInputDataPinsPrev(InputPinsPrev) - , AutoOutputDataPinsPrev(OutputPinsPrev) - { } - -#if WITH_EDITOR - bool DidPinNameToBoundPropertyNameMapChange() const; - bool DidAutoInputDataPinsChange() const; - bool DidAutoOutputDataPinsChange() const; -#endif - - UFlowNode* FlowNode = nullptr; - - const TMap& PinNameToBoundPropertyNameMapPrev; - const TArray& AutoInputDataPinsPrev; - const TArray& AutoOutputDataPinsPrev; - - TMap PinNameToBoundPropertyNameMapNext; - TArray AutoInputDataPinsNext; - TArray AutoOutputDataPinsNext; - - bool bPinNameMapChanged = false; -}; - /** * Single asset containing flow nodes. */ @@ -119,6 +91,8 @@ class FLOW_API UFlowAsset : public UObject // Returns whether the node class is allowed in this flow asset bool IsNodeOrAddOnClassAllowed(const UClass* FlowNodeClass, FText* OutOptionalFailureReason = nullptr) const; + virtual TSubclassOf GetDefaultFlowAssetForSubgraphs() const { return GetClass(); } + protected: bool CanFlowNodeClassBeUsedByFlowAsset(const UClass& FlowNodeClass) const; bool CanFlowAssetUseFlowNodeClass(const UClass& FlowNodeClass) const; @@ -173,24 +147,11 @@ class FLOW_API UFlowAsset : public UObject // Processes nodes and updates pin connections from the graph to the UFlowNode (processes all nodes in the graph if passed nullptr) void HarvestNodeConnections(UFlowNode* TargetNode = nullptr); + static bool TryGetDefaultForInputPinName(const FStructProperty& StructProperty, const void* Container, FString& OutString); + // Updates the auto-generated pins and bindings for a given FlowNode, // returns true if any changes were made. bool TryUpdateManagedFlowPinsForNode(UFlowNode& FlowNode); - -protected: - void AddDataPinPropertyBindingToMap( - const FName& PinAuthoredName, - const FName& PropertyAuthoredName, - FFlowHarvestDataPinsWorkingData& InOutData); - virtual bool TryCreateFlowDataPinFromMetadataValue( - const FString& MetadataValue, - UFlowNode& FlowNode, - const FProperty& Property, - const FText& PinDisplayName, - const bool bIsInputPin, - TArray* InOutDataPinsNext) const; - - void HarvestFlowPinMetadataForProperty(const FProperty* Property, FFlowHarvestDataPinsWorkingData& InOutData); #endif public: @@ -209,6 +170,8 @@ class FLOW_API UFlowAsset : public UObject return nullptr; } + + TArray GetAllNodes() const; UFUNCTION(BlueprintPure, Category = "FlowAsset") virtual UFlowNode* GetDefaultEntryNode() const; @@ -216,6 +179,9 @@ class FLOW_API UFlowAsset : public UObject // Gathers all of the nodes that are connected to the Start & Custom Inputs of the flow graph TArray GatherNodesConnectedToAllInputs() const; + // Return all other Pins connected to the passed Pin. + TArray GatherPinsConnectedToPin(const FConnectedPin& Pin) const; + UFUNCTION(BlueprintPure, Category = "FlowAsset", meta = (DeterminesOutputType = "FlowNodeClass")) TArray GetNodesInExecutionOrder(UFlowNode* FirstIteratedNode, const TSubclassOf FlowNodeClass); @@ -392,9 +358,10 @@ class FLOW_API UFlowAsset : public UObject void TriggerCustomInput_FromSubGraph(UFlowNode_SubGraph* Node, const FName& EventName) const; void TriggerCustomOutput(const FName& EventName); - void TriggerInput(const FGuid& NodeGuid, const FName& PinName); + // TODO: Extend FromPin through to Node level Trigger functions + virtual void TriggerInput(const FGuid& NodeGuid, const FName& PinName, const FConnectedPin& FromPin); - void FinishNode(UFlowNode* Node); + virtual void FinishNode(UFlowNode* Node); void ResetNodes(); #if !UE_BUILD_SHIPPING @@ -431,7 +398,7 @@ class FLOW_API UFlowAsset : public UObject // Expects to be owned (at runtime) by an object with this class (or one of its subclasses) // NOTE - If the class is an AActor, and the flow asset is owned by a component, // it will consider the component's owner for the AActor - UPROPERTY(EditAnywhere, Category = "Flow", meta = (MustImplement = "/Script/Flow.FlowOwnerInterface")) + UPROPERTY(EditAnywhere, Category = "Flow") TSubclassOf ExpectedOwnerClass; ////////////////////////////////////////////////////////////////////////// @@ -457,6 +424,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/FlowExecutableActorComponent.h b/Source/Flow/Public/FlowExecutableActorComponent.h new file mode 100644 index 000000000..e4ecd6f5d --- /dev/null +++ b/Source/Flow/Public/FlowExecutableActorComponent.h @@ -0,0 +1,61 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Components/ActorComponent.h" +#include "Interfaces/FlowContextPinSupplierInterface.h" +#include "Interfaces/FlowCoreExecutableInterface.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "Interfaces/FlowExternalExecutableInterface.h" + +#include "FlowExecutableActorComponent.generated.h" + +/** + * A base class for blueprint components that are expected to be executed from an ExecuteComponent flow node. + * Provides the support for FFlowDataPinValue subclasses, so that blueprint components (that derive from this) + * can have their pins be automatically discovered and supplied. + */ +UCLASS(Abstract, Blueprintable, EditInlineNew, DisplayName = "Flow Executable Actor Component", hidecategories = (Tags, Activation, Cooking, AssetUserData, Navigation)) +class FLOW_API UFlowExecutableActorComponent + : public UActorComponent + , public IFlowContextPinSupplierInterface + , public IFlowCoreExecutableInterface + , public IFlowDataPinValueOwnerInterface + , public IFlowExternalExecutableInterface +{ + GENERATED_BODY() + +private: + FSimpleDelegate FlowDataPinValuesRebuildDelegate; + +protected: + // FlowNodeBase that will execute this component in the FlowGraph on our behalf + UPROPERTY(Transient, BlueprintReadOnly, Category = DataPins) + TObjectPtr FlowNodeProxy; + +public: + + // IFlowContextPinSupplierInterface + virtual bool K2_SupportsContextPins_Implementation() const { return true; } + // -- + +#if WITH_EDITOR + + // IFlowDataPinValueOwnerInterface + virtual bool CanModifyFlowDataPinType() const override; + virtual bool ShowFlowDataPinValueInputPinCheckbox() const override; + virtual bool ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const override; + virtual bool CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const override; + virtual void SetFlowDataPinValuesRebuildDelegate(FSimpleDelegate InDelegate) override; + virtual void RequestFlowDataPinValuesDetailsRebuild() override; + // -- +#endif //WITH_EDITOR + + // IFlowExternalExecutableInterface + virtual void PreActivateExternalFlowExecutable(UFlowNodeBase& FlowNodeBase) override; + // -- + +protected: + + FORCEINLINE bool IsDefaultObject() const { return HasAnyFlags(RF_ClassDefaultObject); } +}; \ No newline at end of file diff --git a/Source/Flow/Public/FlowPinSubsystem.h b/Source/Flow/Public/FlowPinSubsystem.h new file mode 100644 index 000000000..12a66516f --- /dev/null +++ b/Source/Flow/Public/FlowPinSubsystem.h @@ -0,0 +1,60 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Subsystems/EngineSubsystem.h" +#include "Types/FlowPinType.h" +#include "StructUtils/InstancedStruct.h" +#include "Templates/UnrealTypeTraits.h" + +#include "FlowPinSubsystem.generated.h" + +UCLASS(MinimalApi) +class UFlowPinSubsystem : public UEngineSubsystem +{ + GENERATED_BODY() + +protected: + UPROPERTY(Transient) + TMap> PinTypes; + +public: + FLOW_API static UFlowPinSubsystem* Get(); + + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + template + void RegisterPinType() + { + TInstancedStruct PinType; + PinType.InitializeAs(); + RegisterPinType(TPinType::GetPinTypeNameStatic(), PinType); + } + FLOW_API void RegisterPinType(const FFlowPinTypeName& TypeName, const TInstancedStruct& PinType); + + template + void UnregisterPinType() + { + UnregisterPinType(TPinType::GetPinTypeNameStatic()); + } + FLOW_API void UnregisterPinType(const FFlowPinTypeName& TypeName); + + template + const TPinType* FindPinType(const FFlowPinTypeName& TypeName) const + { + static_assert(TIsDerivedFrom::IsDerived, "TPinType must be derived from FFlowPinType"); + + if (const TInstancedStruct* Found = PinTypes.Find(TypeName)) + { + return Found->GetPtr(); + } + + return nullptr; + } + + FLOW_API TArray GetPinTypeNames() const; + +protected: + void UnregisterAllPinTypes(); +}; \ 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/FlowContextPinSupplierInterface.h b/Source/Flow/Public/Interfaces/FlowContextPinSupplierInterface.h index 6e14ed37d..ad2f59a29 100644 --- a/Source/Flow/Public/Interfaces/FlowContextPinSupplierInterface.h +++ b/Source/Flow/Public/Interfaces/FlowContextPinSupplierInterface.h @@ -30,7 +30,10 @@ class FLOW_API IFlowContextPinSupplierInterface UFUNCTION(BlueprintNativeEvent, Category = "FlowNode In-Editor Functions", DisplayName = "SupportsContextPins", meta = (DevelopmentOnly)) bool K2_SupportsContextPins() const; virtual bool K2_SupportsContextPins_Implementation() const; + #if WITH_EDITOR + // Note: This method can only be called by native implementors of the interface, so we still have to manually handle and check + // classes that only implement the interface in Blueprint. virtual bool SupportsContextPins() const { return Execute_K2_SupportsContextPins(Cast(this)); } #endif // WITH_EDITOR diff --git a/Source/Flow/Public/Interfaces/FlowDataPinGeneratorInterface.h b/Source/Flow/Public/Interfaces/FlowDataPinGeneratorInterface.h new file mode 100644 index 000000000..b23748592 --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowDataPinGeneratorInterface.h @@ -0,0 +1,27 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" +#include "Nodes/FlowPin.h" + +#include "FlowDataPinGeneratorInterface.generated.h" + +struct FFlowAutoDataPinsWorkingData; + +// Interface for Classes that can auto-generate DataPins +UINTERFACE(MinimalAPI, NotBlueprintable, DisplayName = "Flow Data Pin Generator Interface", meta = (CannotImplementInterfaceInBlueprint)) +class UFlowDataPinGeneratorInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowDataPinGeneratorInterface +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + virtual void AutoGenerateDataPins(FFlowAutoDataPinsWorkingData& InOutWorkingData) const = 0; +#endif +}; diff --git a/Source/Flow/Public/Interfaces/FlowDataPinGeneratorNodeInterface.h b/Source/Flow/Public/Interfaces/FlowDataPinGeneratorNodeInterface.h deleted file mode 100644 index 2ebe5f3a5..000000000 --- a/Source/Flow/Public/Interfaces/FlowDataPinGeneratorNodeInterface.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "UObject/Interface.h" -#include "Nodes/FlowPin.h" - -#include "FlowDataPinGeneratorNodeInterface.generated.h" - -// Interface for special UFlowNodes that can auto-generate pins directly -UINTERFACE(MinimalAPI, NotBlueprintable, DisplayName = "Flow Data Pin Generator Node Interface") -class UFlowDataPinGeneratorNodeInterface : public UInterface -{ - GENERATED_BODY() -}; - -class FLOW_API IFlowDataPinGeneratorNodeInterface -{ - GENERATED_BODY() - -public: -#if WITH_EDITOR - virtual void AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const = 0; -#endif -}; diff --git a/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h b/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h index e91924248..21b964bcf 100644 --- a/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h +++ b/Source/Flow/Public/Interfaces/FlowDataPinPropertyProviderInterface.h @@ -6,9 +6,9 @@ #include "UObject/Interface.h" #include "FlowDataPinPropertyProviderInterface.generated.h" -struct FFlowDataPinProperty; +struct FFlowDataPinValue; -// Interface to define a FFlowDataPinProperty provider. +// Interface to define a FFlowDataPinValue provider. // This is used in plumbing data in the AI Flow extension plugin into the Flow Data Pins framework. UINTERFACE(MinimalAPI, NotBlueprintable) class UFlowDataPinPropertyProviderInterface : public UInterface @@ -22,6 +22,6 @@ class FLOW_API IFlowDataPinPropertyProviderInterface public: - // Provide a FFlowDataPinProperty (instancedStruct) for the creation of data pins and supplying their values. - virtual bool TryProvideFlowDataPinProperty(const bool bIsInputPin, TInstancedStruct& OutFlowDataPinProperty) const = 0; + // Provide a FFlowDataPinValue (instancedStruct) for the creation of data pins and supplying their values. + virtual bool TryProvideFlowDataPinProperty(TInstancedStruct& OutFlowDataPinProperty) const = 0; }; diff --git a/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h b/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h new file mode 100644 index 000000000..3ae3136cc --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h @@ -0,0 +1,43 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" +#include "Delegates/Delegate.h" + +#include "FlowDataPinValueOwnerInterface.generated.h" + +struct FFlowDataPinValue; + +UINTERFACE(NotBlueprintable) +class FLOW_API UFlowDataPinValueOwnerInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowDataPinValueOwnerInterface +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + + // Determines if the pin's type properties (bIsInputPin, MultiType) can be modified + virtual bool CanModifyFlowDataPinType() const { return true; } + + // Determines if the bIsInputPin checkbox should be visible in the Details panel + virtual bool ShowFlowDataPinValueInputPinCheckbox() const { return true; } + + // Should the ClassFilter or EnumClass row be visible? + virtual bool ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const { return true; } + + // Base policy for whether the ClassFilter / Enum source can be edited + virtual bool CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const { return true; } + + // Set the delegate that forces a layout rebuild (provided by owner detail customization). + virtual void SetFlowDataPinValuesRebuildDelegate(FSimpleDelegate InDelegate) {} + + // Request a details rebuild (executes delegate if bound). + virtual void RequestFlowDataPinValuesDetailsRebuild() {} +#endif +}; diff --git a/Source/Flow/Public/Interfaces/FlowDataPinValueSupplierInterface.h b/Source/Flow/Public/Interfaces/FlowDataPinValueSupplierInterface.h index 950bd1c5c..efbcf17c5 100644 --- a/Source/Flow/Public/Interfaces/FlowDataPinValueSupplierInterface.h +++ b/Source/Flow/Public/Interfaces/FlowDataPinValueSupplierInterface.h @@ -22,87 +22,14 @@ class FLOW_API IFlowDataPinValueSupplierInterface GENERATED_BODY() public: + // Can this node actually supply Data Pin values? // Implementers of this interface will need to use their own logic to answer this question. UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Can Supply DataPin Values") bool CanSupplyDataPinValues() const; virtual bool CanSupplyDataPinValues_Implementation() const { return true; } - // Must implement TrySupplyDataAs... for every EFlowPinType - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - - // Try to supply the value for a data Bool pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Bool") - FFlowDataPinResult_Bool TrySupplyDataPinAsBool(const FName& PinName) const; - virtual FFlowDataPinResult_Bool TrySupplyDataPinAsBool_Implementation(const FName& PinName) const { return FFlowDataPinResult_Bool(); } - - // Try to supply the value for a data Int pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Int") - FFlowDataPinResult_Int TrySupplyDataPinAsInt(const FName& PinName) const; - virtual FFlowDataPinResult_Int TrySupplyDataPinAsInt_Implementation(const FName& PinName) const { return FFlowDataPinResult_Int(); } - - // Try to supply the value for a data Float pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Float") - FFlowDataPinResult_Float TrySupplyDataPinAsFloat(const FName& PinName) const; - virtual FFlowDataPinResult_Float TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const { return FFlowDataPinResult_Float(); } - - // Try to supply the value for a data Name pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Name") - FFlowDataPinResult_Name TrySupplyDataPinAsName(const FName& PinName) const; - virtual FFlowDataPinResult_Name TrySupplyDataPinAsName_Implementation(const FName& PinName) const { return FFlowDataPinResult_Name(); } - - // Try to supply the value for a data String pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As String") - FFlowDataPinResult_String TrySupplyDataPinAsString(const FName& PinName) const; - virtual FFlowDataPinResult_String TrySupplyDataPinAsString_Implementation(const FName& PinName) const { return FFlowDataPinResult_String(); } - - // Try to supply the value for a data Text pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Text") - FFlowDataPinResult_Text TrySupplyDataPinAsText(const FName& PinName) const; - virtual FFlowDataPinResult_Text TrySupplyDataPinAsText_Implementation(const FName& PinName) const { return FFlowDataPinResult_Text(); } - - // Try to supply the value for a data Enum pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Enum") - FFlowDataPinResult_Enum TrySupplyDataPinAsEnum(const FName& PinName) const; - virtual FFlowDataPinResult_Enum TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const { return FFlowDataPinResult_Enum(); } - - // Try to supply the value for a data Vector pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Vector") - FFlowDataPinResult_Vector TrySupplyDataPinAsVector(const FName& PinName) const; - virtual FFlowDataPinResult_Vector TrySupplyDataPinAsVector_Implementation(const FName& PinName) const { return FFlowDataPinResult_Vector(); } - - // Try to supply the value for a data Rotator pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Rotator") - FFlowDataPinResult_Rotator TrySupplyDataPinAsRotator(const FName& PinName) const; - virtual FFlowDataPinResult_Rotator TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const { return FFlowDataPinResult_Rotator(); } - - // Try to supply the value for a data Transform pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Transform") - FFlowDataPinResult_Transform TrySupplyDataPinAsTransform(const FName& PinName) const; - virtual FFlowDataPinResult_Transform TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const { return FFlowDataPinResult_Transform(); } - - // Try to supply the value for a data GameplayTag pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As GameplayTag") - FFlowDataPinResult_GameplayTag TrySupplyDataPinAsGameplayTag(const FName& PinName) const; - virtual FFlowDataPinResult_GameplayTag TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const { return FFlowDataPinResult_GameplayTag(); } - - // Try to supply the value for a data GameplayTagContainer pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As GameplayTagContainer") - FFlowDataPinResult_GameplayTagContainer TrySupplyDataPinAsGameplayTagContainer(const FName& PinName) const; - virtual FFlowDataPinResult_GameplayTagContainer TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const { return FFlowDataPinResult_GameplayTagContainer(); } - - // Try to supply the value for a data InstancedStruct pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As InstancedStruct") - FFlowDataPinResult_InstancedStruct TrySupplyDataPinAsInstancedStruct(const FName& PinName) const; - virtual FFlowDataPinResult_InstancedStruct TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const { return FFlowDataPinResult_InstancedStruct(); } - - // Try to supply the value for a data Object pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Object") - FFlowDataPinResult_Object TrySupplyDataPinAsObject(const FName& PinName) const; - virtual FFlowDataPinResult_Object TrySupplyDataPinAsObject_Implementation(const FName& PinName) const { return FFlowDataPinResult_Object(); } - - // Try to supply the value for a data Class pin - UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin As Class") - FFlowDataPinResult_Class TrySupplyDataPinAsClass(const FName& PinName) const; - virtual FFlowDataPinResult_Class TrySupplyDataPinAsClass_Implementation(const FName& PinName) const { return FFlowDataPinResult_Class(); } + UFUNCTION(BlueprintNativeEvent, Category = DataPins, DisplayName = "Try Supply DataPin") + FFlowDataPinResult TrySupplyDataPin(FName PinName) const; + virtual FFlowDataPinResult TrySupplyDataPin_Implementation(FName PinName) const { return FFlowDataPinResult(); } }; 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..6d19c1099 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() @@ -64,23 +63,8 @@ class FLOW_API UFlowNode_ExecuteComponent : public UFlowNode virtual void UpdateNodeConfigText_Implementation() 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; + // UFlowNode + virtual void GatherPotentialPropertyOwnersForDataPins(TArray& InOutOwners) const override; // -- #if WITH_EDITOR @@ -96,7 +80,7 @@ class FLOW_API UFlowNode_ExecuteComponent : public UFlowNode // -- // UFlowNode - virtual FText GetNodeTitle() const override; + virtual FText K2_GetNodeTitle_Implementation() const override; virtual EDataValidationResult ValidateNode() override; virtual FString GetStatusString() const override; @@ -114,6 +98,8 @@ class FLOW_API UFlowNode_ExecuteComponent : public UFlowNode bool TryInjectComponent(); + const UActorComponent* GetResolvedOrExpectedComponent() const; + UActorComponent* TryResolveComponent(); UActorComponent* GetResolvedComponent() const; TSubclassOf TryGetExpectedActorOwnerClass() const; diff --git a/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h b/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h index 0a88727ed..0fdf3fa77 100644 --- a/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h +++ b/Source/Flow/Public/Nodes/Developer/FlowNode_Log.h @@ -51,6 +51,10 @@ class FLOW_API UFlowNode_Log : public UFlowNode_DefineProperties #if WITH_EDITOR public: + // UObject + virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; + // -- + virtual void UpdateNodeConfigText_Implementation() override; #endif }; diff --git a/Source/Flow/Public/Nodes/FlowNode.h b/Source/Flow/Public/Nodes/FlowNode.h index b9da34695..8942f1d86 100644 --- a/Source/Flow/Public/Nodes/FlowNode.h +++ b/Source/Flow/Public/Nodes/FlowNode.h @@ -9,9 +9,10 @@ #include "FlowNodeBase.h" #include "FlowTypes.h" +#include "Interfaces/FlowDataPinGeneratorInterface.h" #include "Interfaces/FlowDataPinValueSupplierInterface.h" #include "Nodes/FlowPin.h" -#include "Types/FlowDataPinProperties.h" +#include "Types/FlowArray.h" #include "FlowNode.generated.h" @@ -21,6 +22,7 @@ UCLASS(Abstract, Blueprintable, HideCategories = Object) class FLOW_API UFlowNode : public UFlowNodeBase + , public IFlowDataPinGeneratorInterface , public IFlowDataPinValueSupplierInterface , public IVisualLoggerDebugSnapshotInterface { @@ -53,13 +55,18 @@ class FLOW_API UFlowNode // -- public: + // UObject + virtual void PostLoad() override; + // -- + #if WITH_EDITOR // UObject virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; - virtual void PostLoad() override; // -- - virtual EDataValidationResult ValidateNode() { return EDataValidationResult::NotValidated; } + virtual EDataValidationResult ValidateNode(); + + void ValidateFlowPinArrayIsUnique(const TArray& FlowPins, TSet& InOutUniquePinNames, EDataValidationResult& InOutResult); #endif @@ -146,11 +153,6 @@ class FLOW_API UFlowNode void RemoveUserInput(const FName& PinName); void RemoveUserOutput(const FName& PinName); - - // Functions to determine acceptance for 'wildcard' data pin types (eg., singular, array, set, map) - // TODO (gtaylor) The data pins feature is under construction - bool DoesInputWildcardPinAcceptArray(const UEdGraphPin* Pin) const { return true; } - bool DoesOutputWildcardPinAcceptContainer(const UEdGraphPin* Pin) const { return true; } #endif protected: @@ -169,7 +171,6 @@ class FLOW_API UFlowNode TMap Connections; public: - void SetConnections(const TMap& InConnections) { Connections = InConnections; } FConnectedPin GetConnection(const FName OutputName) const { return Connections.FindRef(OutputName); } UE_DEPRECATED(5.5, "Please use GatherConnectedNodes instead.") @@ -191,6 +192,8 @@ class FLOW_API UFlowNode FFlowPin* FindInputPinByName(const FName& PinName); FFlowPin* FindOutputPinByName(const FName& PinName); + const FFlowPin* FindInputPinByName(const FName& PinName) const { return const_cast(this)->FindInputPinByName(PinName); } + const FFlowPin* FindOutputPinByName(const FName& PinName) const { return const_cast(this)->FindOutputPinByName(PinName); } static void RecursiveFindNodesByClass(UFlowNode* Node, const TSubclassOf Class, uint8 Depth, TArray& OutNodes); @@ -201,18 +204,17 @@ class FLOW_API UFlowNode bool FindConnectedNodeForPinFast(const FName& FlowPinName, FGuid* FoundGuid = nullptr, FName* OutConnectedPinName = nullptr) const; bool FindConnectedNodeForPinSlow(const FName& FlowPinName, FGuid* FoundGuid = nullptr, FName* OutConnectedPinName = nullptr) const; + // Return all connections to a Pin this Node knows about. + // Connections are only stored on one of the Nodes they connect depending on pin type. + // As such, this function may not return anything even if the Node is connected to the Pin. + // Use UFlowAsset::GetAllPinsConnectedToPin() to do a guaranteed find of all Connections. + TArray GetKnownConnectionsToPin(const FConnectedPin& Pin) const; + ////////////////////////////////////////////////////////////////////////// // Data Pins public: - // Map of DataPin Name to its Bound Property, - // when using metadata tag 'BindOutputFlowDataPin' to bind properties to data pins for automatic supplier support - UPROPERTY(VisibleDefaultsOnly, AdvancedDisplay, Category = "FlowNode", meta = (GetByRef)) - TMap PinNameToBoundPropertyNameMap; - - const TMap& GetPinNameToBoundPropertyNameMap() const { return PinNameToBoundPropertyNameMap; } - #if WITH_EDITORONLY_DATA UPROPERTY(VisibleDefaultsOnly, AdvancedDisplay, Category = "FlowNode", meta = (GetByRef)) TArray AutoInputDataPins; @@ -222,9 +224,6 @@ class FLOW_API UFlowNode #endif // WITH_EDITORONLY_DATA #if WITH_EDITOR - void SetPinNameToBoundPropertyNameMap(const TMap& Map); - TMap& GetMutablePinNameToBoundPropertyNameMap() { return PinNameToBoundPropertyNameMap; } - void SetAutoInputDataPins(const TArray& AutoInputPins); void SetAutoOutputDataPins(const TArray& AutoOutputPins); const TArray& GetAutoInputDataPins() const { return AutoInputDataPins; } @@ -232,72 +231,64 @@ class FLOW_API UFlowNode TArray& GetMutableAutoInputDataPins() { return AutoInputDataPins; } TArray& GetMutableAutoOutputDataPins() { return AutoOutputDataPins; } + + void SetConnections(const TMap& InConnections); + virtual void OnConnectionsChanged(const TMap& OldConnections) { } #endif // WITH_EDITOR // 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; - - bool TryGetFlowDataPinSupplierDatasForPinName( - const FName& PinName, - TArray& InOutPinValueSupplierDatas) const; - // -- - -protected: +public: + virtual FFlowDataPinResult TrySupplyDataPin_Implementation(FName PinName) const override; - // Helper functions for the TrySupplyDataPin...() functions - bool TryFindPropertyByPinName( + // Advanced helper for TrySupplyDataPin, which can be overridden in subclasses to provide alternate sourcing for properties. + // If returns true, either OutFoundProperty or OutFoundInstancedStruct is expected to carry the property value. + // (this function is used for cases like DefineProperties, Start, and blackboard lookup nodes) + virtual bool TryFindPropertyByPinName( + const UObject& PropertyOwnerObject, const FName& PinName, const FProperty*& OutFoundProperty, - TInstancedStruct& OutFoundInstancedStruct, - EFlowDataPinResolveResult& InOutResult) const; - virtual bool TryFindPropertyByRemappedPinName( - const FName& RemappedPinName, - const FProperty*& OutFoundProperty, - TInstancedStruct& OutFoundInstancedStruct, - EFlowDataPinResolveResult& InOutResult) const; + TInstancedStruct& OutFoundInstancedStruct) const; - // Functions to supply the pin data value from a variety of supported property types - template - TFlowDataPinResultType TrySupplyDataPinAsType(const FName& PinName) const; + // Advanced helper for TrySupplyDataPin, which can be overridden in subclasses to provide additional or replacement object(s) + // for sourcing the properties for the given pin name. These objects will have PopulateResult called on them. + // (this function is used for cases like ExecuteComponent) + virtual void GatherPotentialPropertyOwnersForDataPins(TArray& InOutOwners) const; - template - TFlowDataPinResultType TrySupplyDataPinAsNumericType(const FName& PinName) const; + bool TryGatherPropertyOwnersAndPopulateResult( + const FName& PinName, + const FFlowPinType& DataPinType, + const FFlowPin& FlowPin, + FFlowDataPinResult& OutSuppliedResult) const; - template - TFlowDataPinResultType TrySupplyDataPinAsAnyTextType(const FName& PinName) const; +protected: + // Static implementation of the default TryFindPropertyByPinName (which subclasses can incorperate into overrides) + static bool TryFindPropertyByPinName_Static( + const UObject& PropertyOwnerObject, + const FName& PinName, + const FProperty*& OutFoundProperty, + TInstancedStruct& OutFoundInstancedStruct); - FORCEINLINE_DEBUGGABLE FFlowDataPinResult_Enum TrySupplyDataPinAsEnumType(const FName& PinName) const; + // #FlowDataPinLegacy +public: + void FixupDataPinTypes(); - template - TFlowDataPinResultType TrySupplyDataPinAsStructType(const FName& PinName) const; +protected: + void FixupDataPinTypesForArray(TArray& MutableDataPinArray); + void FixupDataPinTypesForPin(FFlowPin& MutableDataPin); + // -- - template - TFlowDataPinResultType TrySupplyDataPinAsUObjectTypeCommon(const FName& PinName, const FProperty*& OutFoundProperty) const; +public: - template - TFlowDataPinResultType TrySupplyDataPinAsUObjectType(const FName& PinName) const; + using TFlowPinValueSupplierDataArray = FlowArray::TInlineArray; + bool TryGetFlowDataPinSupplierDatasForPinName( + const FName& PinName, + TFlowPinValueSupplierDataArray& InOutPinValueSupplierDatas) const; - template - TFlowDataPinResultType TrySupplyDataPinAsUClassType(const FName& PinName) const; + // IFlowDataPinGeneratorInterface +#if WITH_EDITOR + virtual void AutoGenerateDataPins(FFlowAutoDataPinsWorkingData& InOutWorkingData) const override; +#endif + // -- ////////////////////////////////////////////////////////////////////////// // Debugger @@ -324,7 +315,7 @@ class FLOW_API UFlowNode #if !UE_BUILD_SHIPPING -private: +protected: TMap> InputRecords; TMap> OutputRecords; #endif @@ -418,492 +409,3 @@ class FLOW_API UFlowNode static FString GetProgressAsString(float Value); }; -// Templates & inline implementations: - -template -TFlowDataPinResultType UFlowNode::TrySupplyDataPinAsType(const FName& PinName) const -{ - TFlowDataPinResultType SuppliedResult; - - const FProperty* FoundProperty = nullptr; - TInstancedStruct InstancedStruct; - if (!TryFindPropertyByPinName(PinName, FoundProperty, InstancedStruct, SuppliedResult.Result)) - { - return SuppliedResult; - } - - if (const TFlowDataPinProperty* FlowDataPinProp = InstancedStruct.GetPtr()) - { - // In some cases, TryFindPropertyByPinName can find an instanced struct for the wrapper, - // so get the value from it and return straight away - - SuppliedResult.Value = FlowDataPinProp->Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - // Check for struct-based wrapper for the property and get the value out of it - if (const FStructProperty* StructProperty = CastField(FoundProperty)) - { - const UScriptStruct* FlowDataPinPropertyStruct = TFlowDataPinProperty::StaticStruct(); - - if (StructProperty->Struct == FlowDataPinPropertyStruct) - { - TFlowDataPinProperty ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.Value = ValueStruct.Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - - return SuppliedResult; - } - - // Get the value from a UE simple property type - if (const TFieldPropertyType* UnrealProperty = CastField(FoundProperty)) - { - SuppliedResult.Value = UnrealProperty->GetPropertyValue_InContainer(this); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - SuppliedResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; - - return SuppliedResult; -} - -template -TFlowDataPinResultType UFlowNode::TrySupplyDataPinAsNumericType(const FName& PinName) const -{ - TFlowDataPinResultType SuppliedResult; - - const FProperty* FoundProperty = nullptr; - TInstancedStruct InstancedStruct; - if (!TryFindPropertyByPinName(PinName, FoundProperty, InstancedStruct, SuppliedResult.Result)) - { - return SuppliedResult; - } - - if (const FFlowDataPinProperty* FlowDataPinProp = InstancedStruct.GetPtr()) - { - // In some cases, TryFindPropertyByPinName can find an instanced struct for the wrapper, - // so get the value from it and return straight away - - if (const TFlowLargeDataPinProperty* FlowDataPinPropLarge = InstancedStruct.GetPtr()) - { - SuppliedResult.Value = FlowDataPinPropLarge->Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else if (const TFlowMediumDataPinProperty* FlowDataPinPropMedium = InstancedStruct.GetPtr()) - { - SuppliedResult.Value = FlowDataPinPropMedium->Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - } - - // Check for struct-based wrapper for the property and get the value out of it - if (const FStructProperty* StructProperty = CastField(FoundProperty)) - { - const UScriptStruct* FlowLargeDataPinPropertyStruct = TFlowLargeDataPinProperty::StaticStruct(); - const UScriptStruct* FlowMediumDataPinPropertyStruct = TFlowMediumDataPinProperty::StaticStruct(); - - // Supporting both a 64 and 32 bit wrapper for ints/floats, given the ubiquity of int32/float. - if (StructProperty->Struct == FlowLargeDataPinPropertyStruct) - { - TFlowLargeDataPinProperty ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.Value = ValueStruct.Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - else if (StructProperty->Struct == FlowMediumDataPinPropertyStruct) - { - TFlowMediumDataPinProperty ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.Value = ValueStruct.Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - - return SuppliedResult; - } - - // Get the value from a UE simple property type - if (const FNumericProperty* NumericProperty = CastField(FoundProperty)) - { - if (const FFloatProperty* FloatProperty = CastField(NumericProperty)) - { - float FloatValue; - FloatProperty->GetValue_InContainer(this, &FloatValue); - SuppliedResult.Value = FloatValue; - } - else if (const FDoubleProperty* DoubleProperty = CastField(NumericProperty)) - { - double DoubleValue; - DoubleProperty->GetValue_InContainer(this, &DoubleValue); - SuppliedResult.Value = DoubleValue; - } - else - { - SuppliedResult.Value = NumericProperty->GetSignedIntPropertyValue_InContainer(this); - } - - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - SuppliedResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; - - return SuppliedResult; -} - -template -TFlowDataPinResultType UFlowNode::TrySupplyDataPinAsAnyTextType(const FName& PinName) const -{ - TFlowDataPinResultType SuppliedResult; - - const FProperty* FoundProperty = nullptr; - TInstancedStruct InstancedStruct; - if (!TryFindPropertyByPinName(PinName, FoundProperty, InstancedStruct, SuppliedResult.Result)) - { - return SuppliedResult; - } - - if (const FFlowDataPinProperty* FlowDataPinProp = InstancedStruct.GetPtr()) - { - // In some cases, TryFindPropertyByPinName can find an instanced struct for the wrapper, - // so get the value from it and return straight away - - if (const FFlowDataPinOutputProperty_Name* FlowDataPinPropName = InstancedStruct.GetPtr()) - { - SuppliedResult.SetValue(FlowDataPinPropName->Value); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else if (const FFlowDataPinOutputProperty_String* FlowDataPinPropString = InstancedStruct.GetPtr()) - { - SuppliedResult.SetValue(FlowDataPinPropString->Value); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else if (const FFlowDataPinOutputProperty_Text* FlowDataPinPropText = InstancedStruct.GetPtr()) - { - SuppliedResult.SetValue(FlowDataPinPropText->Value); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - } - - // Check for struct-based wrapper for the property and get the value out of it - if (const FStructProperty* StructProperty = CastField(FoundProperty)) - { - const UScriptStruct* FlowDataPinPropertyStruct_Name = FFlowDataPinOutputProperty_Name::StaticStruct(); - const UScriptStruct* FlowDataPinPropertyStruct_String = FFlowDataPinOutputProperty_String::StaticStruct(); - const UScriptStruct* FlowDataPinPropertyStruct_Text = FFlowDataPinOutputProperty_Text::StaticStruct(); - - if (StructProperty->Struct == FlowDataPinPropertyStruct_Name) - { - FFlowDataPinOutputProperty_Name ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.SetValue(ValueStruct.Value); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - else if (StructProperty->Struct == FlowDataPinPropertyStruct_String) - { - FFlowDataPinOutputProperty_String ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.SetValue(ValueStruct.Value); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - else if (StructProperty->Struct == FlowDataPinPropertyStruct_Text) - { - FFlowDataPinOutputProperty_Text ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.SetValue(ValueStruct.Value); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - - return SuppliedResult; - } - - // Get the value from a UE simple property type - if (const FNameProperty* NameProperty = CastField(FoundProperty)) - { - SuppliedResult.SetValue(NameProperty->GetPropertyValue_InContainer(this)); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else if (const FStrProperty* StrProperty = CastField(FoundProperty)) - { - SuppliedResult.SetValue(StrProperty->GetPropertyValue_InContainer(this)); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else if (const FTextProperty* TextProperty = CastField(FoundProperty)) - { - SuppliedResult.SetValue(TextProperty->GetPropertyValue_InContainer(this)); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else - { - SuppliedResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; - - return SuppliedResult; - } -} - -FFlowDataPinResult_Enum UFlowNode::TrySupplyDataPinAsEnumType(const FName& PinName) const -{ - FFlowDataPinResult_Enum SuppliedResult; - - const FProperty* FoundProperty = nullptr; - TInstancedStruct InstancedStruct; - if (!TryFindPropertyByPinName(PinName, FoundProperty, InstancedStruct, SuppliedResult.Result)) - { - return SuppliedResult; - } - - if (const FFlowDataPinOutputProperty_Enum* FlowDataPinProp = InstancedStruct.GetPtr()) - { - // In some cases, TryFindPropertyByPinName can find an instanced struct for the wrapper, - // so get the value from it and return straight away - - SuppliedResult.Value = FlowDataPinProp->Value; - SuppliedResult.EnumClass = FlowDataPinProp->EnumClass; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - // Check for struct-based wrapper for the property and get the value out of it - if (const FStructProperty* StructProperty = CastField(FoundProperty)) - { - const UScriptStruct* FlowDataPinPropertyStruct_Enum = FFlowDataPinOutputProperty_Enum::StaticStruct(); - - if (StructProperty->Struct == FlowDataPinPropertyStruct_Enum) - { - FFlowDataPinOutputProperty_Enum ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.Value = ValueStruct.Value; - SuppliedResult.EnumClass = ValueStruct.EnumClass; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - - return SuppliedResult; - } - - // Get the value from a UE enum property type - if (const FEnumProperty* EnumProperty = CastField(FoundProperty)) - { - UEnum* EnumClass = EnumProperty->GetEnum(); - - const FNumericProperty* UnderlyingProperty = EnumProperty->GetUnderlyingProperty(); - const int64 SignedIntValue = UnderlyingProperty->GetSignedIntPropertyValue_InContainer(this); - const FString StringValue = EnumClass->GetAuthoredNameStringByValue(SignedIntValue); - - SuppliedResult.Value = FName(StringValue); - SuppliedResult.EnumClass = EnumClass; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else - { - SuppliedResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; - - return SuppliedResult; - } -} - -template -TFlowDataPinResultType UFlowNode::TrySupplyDataPinAsStructType(const FName& PinName) const -{ - TFlowDataPinResultType SuppliedResult; - - const FProperty* FoundProperty = nullptr; - TInstancedStruct InstancedStruct; - if (!TryFindPropertyByPinName(PinName, FoundProperty, InstancedStruct, SuppliedResult.Result)) - { - return SuppliedResult; - } - - if (const TFlowDataPinProperty* FlowDataPinProp = InstancedStruct.GetPtr()) - { - // In some cases, TryFindPropertyByPinName can find an instanced struct for the wrapper, - // so get the value from it and return straight away - - SuppliedResult.Value = FlowDataPinProp->Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - const FStructProperty* StructProperty = CastField(FoundProperty); - if (!StructProperty) - { - SuppliedResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; - - return SuppliedResult; - } - - const UScriptStruct* FlowDataPinPropertyStruct = TFlowDataPinProperty::StaticStruct(); - static const UScriptStruct* TargetPropertyStruct = TBaseStructure::Get(); - - if (StructProperty->Struct == FlowDataPinPropertyStruct) - { - // Check for struct-based wrapper for the property and get the value out of it - - TFlowDataPinProperty ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.Value = ValueStruct.Value; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else if (StructProperty->Struct == TargetPropertyStruct) - { - // Get the value from a UE struct (non-wrapper) property type - - TTargetStruct TargetStruct; - StructProperty->GetValue_InContainer(this, &TargetStruct); - - SuppliedResult.Value = TargetStruct; - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - else - { - SuppliedResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; - - return SuppliedResult; - } -} - -template -TFlowDataPinResultType UFlowNode::TrySupplyDataPinAsUObjectTypeCommon(const FName& PinName, const FProperty*& OutFoundProperty) const -{ - TFlowDataPinResultType SuppliedResult; - - TInstancedStruct InstancedStruct; - if (!TryFindPropertyByPinName(PinName, OutFoundProperty, InstancedStruct, SuppliedResult.Result)) - { - return SuppliedResult; - } - - if (const TFlowDataPinProperty* FlowDataPinProp = InstancedStruct.GetPtr()) - { - // In some cases, TryFindPropertyByPinName can find an instanced struct for the wrapper, - // so get the value from it and return straight away - - SuppliedResult.SetValueFromPropertyWrapper(*FlowDataPinProp); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - // Check for struct-based wrapper for the property and get the value out of it - if (const FStructProperty* StructProperty = CastField(OutFoundProperty)) - { - const UScriptStruct* FlowDataPinPropertyStruct = TFlowDataPinProperty::StaticStruct(); - - if (StructProperty->Struct == FlowDataPinPropertyStruct) - { - TFlowDataPinProperty ValueStruct; - StructProperty->GetValue_InContainer(this, &ValueStruct); - - SuppliedResult.SetValueFromPropertyWrapper(ValueStruct); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - } - - return SuppliedResult; - } - - // Get the value from one of the UE simple property types - if (const TFieldPropertyObjectType0* UnrealProperty0 = CastField(OutFoundProperty)) - { - // TObjectPtr / UObject* - TUObjectType* Object = Cast(UnrealProperty0->GetPropertyValue_InContainer(this)); - SuppliedResult.SetValueFromObjectPtr(Object); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - if (const TFieldPropertySoftObjectType1* UnrealProperty1 = CastField(OutFoundProperty)) - { - // FSoftObjectPath / TSoftObjectPtr (or their Class variants) - const FSoftObjectPath SoftObjectPath = UnrealProperty1->GetPropertyValue_InContainer(this).ToSoftObjectPath(); - SuppliedResult.SetValueFromSoftPath(SoftObjectPath); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - SuppliedResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; - - return SuppliedResult; -} - -template -TFlowDataPinResultType UFlowNode::TrySupplyDataPinAsUObjectType(const FName& PinName) const -{ - // First execute TrySupplyDataPinAsUObjectTypeCommon to handle all of the shared cases between UObject and UClass properties: - const FProperty* FoundProperty = nullptr; - TFlowDataPinResultType SuppliedResult = - TrySupplyDataPinAsUObjectTypeCommon(PinName, FoundProperty); - - if (SuppliedResult.Result == EFlowDataPinResolveResult::FailedMismatchedType) - { - if (const TFieldPropertyWeakType2* UnrealProperty2 = CastField(FoundProperty)) - { - // TWeakObjectPtr - TUObjectType* Object = Cast(UnrealProperty2->GetPropertyValue_InContainer(this).Get()); - SuppliedResult.SetValueFromObjectPtr(Object); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - - if (const TFieldPropertyLazyType3* UnrealProperty3 = CastField(FoundProperty)) - { - // FLazyObjectPtr - TUObjectType* Object = Cast(UnrealProperty3->GetPropertyValue_InContainer(this).Get()); - SuppliedResult.SetValueFromObjectPtr(Object); - SuppliedResult.Result = EFlowDataPinResolveResult::Success; - - return SuppliedResult; - } - } - - return SuppliedResult; -} - -template -TFlowDataPinResultType UFlowNode::TrySupplyDataPinAsUClassType(const FName& PinName) const -{ - const FProperty* FoundProperty = nullptr; - return TrySupplyDataPinAsUObjectTypeCommon(PinName, FoundProperty); -} diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index b9f9d5169..307ba79d5 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -6,10 +6,12 @@ #include "Interfaces/FlowCoreExecutableInterface.h" #include "Interfaces/FlowContextPinSupplierInterface.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" #include "FlowMessageLog.h" #include "FlowTags.h" // used by subclasses #include "FlowTypes.h" #include "Types/FlowDataPinResults.h" +#include "Types/FlowPinTypeTemplates.h" #include "FlowNodeBase.generated.h" @@ -18,10 +20,10 @@ class UFlowNode; class UFlowNodeAddOn; class UFlowSubsystem; class UEdGraphNode; -class IFlowOwnerInterface; class IFlowDataPinValueSupplierInterface; struct FFlowPin; struct FFlowNamedDataPinProperty; +struct FFlowPinType; #if WITH_EDITORONLY_DATA DECLARE_DELEGATE(FFlowNodeEvent); @@ -37,21 +39,6 @@ struct FFlowPinValueSupplierData const IFlowDataPinValueSupplierInterface* PinValueSupplier = nullptr; }; -// Helper template to reduce (some) of the boilerplate in TryResolveDataPinAs...() functions -template -struct TResolveDataPinWorkingData -{ - bool TrySetupWorkingData(const FName& PinName, const UFlowNodeBase& FlowNodeBase); - - TFlowDataPinResultType DataPinResult; - const UFlowNode* FlowNode = nullptr; - const FFlowPin* FlowPin = nullptr; - - TArray PinValueSupplierDatas; - - static constexpr bool bCheckDefaultProperties = true; -}; - /** * The base class for UFlowNode and UFlowNodeAddOn, with their shared functionality */ @@ -60,6 +47,7 @@ class FLOW_API UFlowNodeBase : public UObject , public IFlowCoreExecutableInterface , public IFlowContextPinSupplierInterface + , public IFlowDataPinValueOwnerInterface { GENERATED_UCLASS_BODY() @@ -157,17 +145,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 @@ -233,56 +212,110 @@ class FLOW_API UFlowNodeBase ////////////////////////////////////////////////////////////////////////// // Data Pins - // Must implement TryResolveDataAs... for every EFlowPinType - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + // IFlowDataPinValueOwnerInterface +#if WITH_EDITOR +public: + virtual bool CanModifyFlowDataPinType() const override; + virtual bool ShowFlowDataPinValueInputPinCheckbox() const override; + virtual bool ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const override; + virtual bool CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const override; + virtual void SetFlowDataPinValuesRebuildDelegate(FSimpleDelegate InDelegate) override + { + FlowDataPinValuesRebuildDelegate = InDelegate; + } + virtual void RequestFlowDataPinValuesDetailsRebuild() override + { + if (FlowDataPinValuesRebuildDelegate.IsBound()) + { + FlowDataPinValuesRebuildDelegate.Execute(); + } + } +private: + FSimpleDelegate FlowDataPinValuesRebuildDelegate; +protected: + // Helpers for IFlowDataPinValueOwnerInterface + bool IsPlacedInFlowAsset() const; + bool IsFlowNamedPropertiesSupplier() const; +#endif + // -- + +private: + UFUNCTION(BlueprintPure, Category = DataPins, DisplayName = "Resolve DataPin By Name") + FFlowDataPinResult TryResolveDataPin(FName PinName) const; + +public: + // Generic single-value resolve & extractor + template + EFlowDataPinResolveResult TryResolveDataPinValue(const FName& PinName, typename TFlowPinType::ValueType& OutValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue) const; + + // Generic array-value resolve & extractor + template + EFlowDataPinResolveResult TryResolveDataPinValues(const FName& PinName, TArray& OutValues) const; + + // Special-case single-value resolve & extractor for native enums + template requires std::is_enum_v + EFlowDataPinResolveResult TryResolveDataPinValue(const FName& PinName, TEnumType& OutValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue) const; + + // Special-case array-value resolve & extractor for native enums + template requires std::is_enum_v + EFlowDataPinResolveResult TryResolveDataPinValues(const FName& PinName, TArray& OutValues) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Bool") + // Special-case single-value resolve & extractor for enums (as FName values) + template + EFlowDataPinResolveResult TryResolveDataPinValue(const FName& PinName, FName& OutEnumValue, UEnum*& OutEnumClass, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue) const; + + // Special-case array-value resolve & extractor for enums (as FName values) + template + EFlowDataPinResolveResult TryResolveDataPinValues(const FName& PinName, TArray& OutEnumValues, UEnum*& OutEnumClass) const; + +public: + + // #FlowDataPinLegacy + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Bool", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Bool TryResolveDataPinAsBool(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Int") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Int", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Int TryResolveDataPinAsInt(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Float") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Float", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Float TryResolveDataPinAsFloat(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Name") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Name", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Name TryResolveDataPinAsName(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As String") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As String", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_String TryResolveDataPinAsString(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Text") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Text", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Text TryResolveDataPinAsText(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Enum") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Enum", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Enum TryResolveDataPinAsEnum(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Vector") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Vector", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Vector TryResolveDataPinAsVector(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Rotator") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Rotator", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Rotator TryResolveDataPinAsRotator(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Transform") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Transform", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Transform TryResolveDataPinAsTransform(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As GameplayTag") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As GameplayTag", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_GameplayTag TryResolveDataPinAsGameplayTag(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As GameplayTagContainer") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As GameplayTagContainer", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_GameplayTagContainer TryResolveDataPinAsGameplayTagContainer(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As InstancedStruct") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As InstancedStruct", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_InstancedStruct TryResolveDataPinAsInstancedStruct(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Object") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Object", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Object TryResolveDataPinAsObject(const FName& PinName) const; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Class") + UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Class", meta = (DeprecatedFunction, DeprecationMessage = "Use TryResolveDataPin (in blueprint) or TryResolveDataPinValue(s) (in code) instead")) FFlowDataPinResult_Class TryResolveDataPinAsClass(const FName& PinName) const; - - // Public only for TResolveDataPinWorkingData's use - EFlowDataPinResolveResult TryResolveDataPinPrerequisites(const FName& PinName, const UFlowNode*& FlowNode, const FFlowPin*& FlowPin, EFlowPinType PinType) const; + // -- protected: @@ -342,6 +375,8 @@ class FLOW_API UFlowNodeBase void RequestReconstruction() const { (void) OnReconstructionRequested.ExecuteIfBound(); }; + void SetCanDelete(bool CanDelete) { bCanDelete = CanDelete;} + #endif protected: @@ -373,22 +408,34 @@ class FLOW_API UFlowNodeBase #if WITH_EDITOR public: + // WARNING! Call UFlowGraphSettings::GetNodeCategoryForNode() instead! virtual FString GetNodeCategory() const; const FGameplayTag& GetNodeDisplayStyle() const { return NodeDisplayStyle; } // This method allows to have different for every node instance, i.e. Red if node represents enemy, Green if node represents a friend virtual bool GetDynamicTitleColor(FLinearColor& OutColor) const; - - virtual FText GetNodeTitle() const; - virtual FText GetNodeToolTip() const; - virtual FText GetNodeConfigText() const; + + virtual FText GetNodeTitle() const { return K2_GetNodeTitle(); } + + virtual FText GetNodeToolTip() const { return K2_GetNodeToolTip(); } + FText GetGeneratedDisplayName() const; protected: void EnsureNodeDisplayStyle(); #endif // WITH_EDITOR +public: + UFUNCTION(BlueprintNativeEvent, Category = "FlowNode") + FText K2_GetNodeTitle() const; + + UFUNCTION(BlueprintNativeEvent, Category = "FlowNode") + FText K2_GetNodeToolTip() const; + + UFUNCTION(BlueprintPure, Category = "FlowNode") + virtual FText GetNodeConfigText() const; + protected: // Set the editor-only Config Text // (for displaying config info on the Node in the flow graph, ignored in non-editor builds) @@ -414,6 +461,7 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintImplementableEvent, Category = "FlowNode", meta = (DisplayName = "Get Node Description")) FString K2_GetNodeDescription() const; +public: UFUNCTION(BlueprintCallable, Category = "FlowNode", meta = (DevelopmentOnly)) void LogError(FString Message, const EFlowOnScreenMessageType OnScreenMessageType = EFlowOnScreenMessageType::Permanent) const; @@ -431,3 +479,57 @@ class FLOW_API UFlowNodeBase bool BuildMessage(FString& Message) const; #endif }; + +// Templates & inline implementations: + +template +EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinValue(const FName& PinName, typename TFlowPinType::ValueType& OutValue, EFlowSingleFromArray SingleFromArray /*= EFlowSingleFromArray::LastValue*/) const +{ + const FFlowDataPinResult DataPinResult = TryResolveDataPin(PinName); + return FlowPinType::TryExtractValue(DataPinResult, OutValue, SingleFromArray); +} + +template +EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinValues(const FName& PinName, TArray& OutValues) const +{ + const FFlowDataPinResult DataPinResult = TryResolveDataPin(PinName); + return FlowPinType::TryExtractValues(DataPinResult, OutValues); +} + +template +EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinValue(const FName& PinName, FName& OutEnumValue, UEnum*& OutEnumClass, EFlowSingleFromArray SingleFromArray /*= EFlowSingleFromArray::LastValue*/) const +{ + const FFlowDataPinResult DataPinResult = TryResolveDataPin(PinName); + if (!FlowPinType::IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + return FlowPinType::TryExtractValue(DataPinResult, OutEnumValue, OutEnumClass, SingleFromArray); +} + +template +EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinValues(const FName& PinName, TArray& OutEnumValues, UEnum*& OutEnumClass) const +{ + const FFlowDataPinResult DataPinResult = TryResolveDataPin(PinName); + if (!FlowPinType::IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + return FlowPinType::TryExtractValues(DataPinResult, OutEnumValues, OutEnumClass); +} + +template requires std::is_enum_v +EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinValue(const FName& PinName, TEnumType& OutValue, EFlowSingleFromArray SingleFromArray /*= EFlowSingleFromArray::LastValue*/) const +{ + const FFlowDataPinResult DataPinResult = TryResolveDataPin(PinName); + return FlowPinType::TryExtractValue(DataPinResult, OutValue, SingleFromArray); +} + +template requires std::is_enum_v +EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinValues(const FName& PinName, TArray& OutValues) const +{ + const FFlowDataPinResult DataPinResult = TryResolveDataPin(PinName); + return FlowPinType::TryExtractValues(DataPinResult, OutValues); +} diff --git a/Source/Flow/Public/Nodes/FlowPin.h b/Source/Flow/Public/Nodes/FlowPin.h index 94d733b02..465a2cf14 100644 --- a/Source/Flow/Public/Nodes/FlowPin.h +++ b/Source/Flow/Public/Nodes/FlowPin.h @@ -3,9 +3,12 @@ #pragma once #include "Types/FlowPinEnums.h" +#include "Types/FlowPinTypeName.h" #include "Templates/SubclassOf.h" #include "UObject/ObjectMacros.h" +#include "Types/FlowPinTypeNamesStandard.h" +#include "EdGraph/EdGraphPin.h" #include "FlowPin.generated.h" @@ -13,6 +16,7 @@ class UEnum; class UClass; class UObject; class IPropertyHandle; +struct FFlowPinType; USTRUCT(BlueprintType, meta = (HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStruct", HasNativeBreak = "/Script/Flow.FlowDataPinBlueprintLibrary.BreakStruct")) struct FLOW_API FFlowPin @@ -20,149 +24,140 @@ struct FLOW_API FFlowPin GENERATED_BODY() // A logical name, used during execution of pin - UPROPERTY(EditDefaultsOnly, Category = FlowPin) + UPROPERTY(EditDefaultsOnly, Category = DataPins) FName PinName; // An optional Display Name, you can use it to override PinName without the need to update graph connections - UPROPERTY(EditDefaultsOnly, Category = FlowPin) + UPROPERTY(EditDefaultsOnly, Category = DataPins) FText PinFriendlyName; - UPROPERTY(EditDefaultsOnly, Category = FlowPin) + UPROPERTY(EditDefaultsOnly, Category = DataPins) FString PinToolTip; -protected: // PinType (implies PinCategory) - UPROPERTY(EditAnywhere, Category = FlowPin) - EFlowPinType PinType = EFlowPinType::Exec; + UPROPERTY(Meta = (DeprecatedProperty, DeprecationMessage = "Use PinTypeName instead")) + EFlowPinType PinType = EFlowPinType::Invalid; - // Sub-category object - // (used to identify the struct or class type for some PinCategories, see IsSubtypeSupportedPinCategory) + // Only supporting None (Single) or Array for now(tm) for data pins via EFlowMultiType UPROPERTY() + EPinContainerType ContainerType = EPinContainerType::None; + +protected: + UPROPERTY(EditDefaultsOnly, Category = DataPins) + FFlowPinTypeName PinTypeName = FFlowPinTypeName(FFlowPinTypeNamesStandard::PinTypeNameExec); + + // Sub-category object + // (used to identify the struct or class type for some PinCategories) + UPROPERTY(VisibleAnywhere, Category = DataPins) TWeakObjectPtr PinSubCategoryObject; #if WITH_EDITORONLY_DATA // Filter for limiting the compatible classes for this data pin. - // This property is editor-only, but it is automatically copied into PinSubCategoryObject if the PinType matches (for runtime use). - UPROPERTY(EditAnywhere, Category = FlowPin, meta = (EditCondition = "PinType == EFlowPinType::Class", EditConditionHides)) + // This property is editor-only, but it is automatically copied into PinSubCategoryObject if the PinTypeName matches (for runtime use). + UPROPERTY(EditAnywhere, Category = DataPins, meta = (EditCondition = "PinTypeName == Class", EditConditionHides)) TSubclassOf SubCategoryClassFilter = UClass::StaticClass(); // Filter for limiting the compatible object types for this data pin. - // This property is editor-only, but it is automatically copied into PinSubCategoryObject if the PinType matches (for runtime use). - UPROPERTY(EditAnywhere, Category = FlowPin, meta = (EditCondition = "PinType == EFlowPinType::Object", EditConditionHides)) + // This property is editor-only, but it is automatically copied into PinSubCategoryObject if the PinTypeName matches (for runtime use). + UPROPERTY(EditAnywhere, Category = DataPins, meta = (EditCondition = "PinTypeName == Object", EditConditionHides)) TSubclassOf SubCategoryObjectFilter = UObject::StaticClass(); - // Configuration option for setting the EnumClass to a Blueprint Enum + // Configuration option for setting the EnumClass to a Blueprint Enum // (C++ enums must bind by name using SubCategoryEnumName, due to a limitation with UE's UEnum discovery). // This property is editor-only, but it is automatically copied into PinSubCategoryObject if the PinType matches (for runtime use). - UPROPERTY(EditAnywhere, Category = FlowPin, meta = (EditCondition = "PinType == EFlowPinType::Enum", EditConditionHides)) + UPROPERTY(EditAnywhere, Category = DataPins, meta = (EditCondition = "PinTypeName == Enum", EditConditionHides)) TObjectPtr SubCategoryEnumClass = nullptr; // name of enum defined in c++ code, will take priority over asset from EnumType property - // (this is a work-around because EnumClass cannot find C++ Enums, - // so you need to type the name of the enum in here, manually) + // (this is a work-around because EnumClass cannot find C++ Enums, + // so you need to type the name of the enum in here, manually) // See also: FFlowPin::PostEditChangedEnumName() - UPROPERTY(EditAnywhere, Category = FlowPin, meta = (EditCondition = "PinType == EFlowPinType::Enum", EditConditionHides)) + UPROPERTY(EditAnywhere, Category = DataPins, meta = (EditCondition = "PinTypeName == Enum", EditConditionHides)) FString SubCategoryEnumName; -#endif // WITH_EDITORONLY_DATA +#endif public: - // PinCategory aliases for (a subset of) those defined in UEdGraphSchema_K2 - static inline FName PC_Exec = TEXT("exec"); - static inline FName PC_Boolean = TEXT("bool"); - static inline FName PC_Byte = TEXT("byte"); - static inline FName PC_Class = TEXT("class"); - static inline FName PC_Int = TEXT("int"); - static inline FName PC_Int64 = TEXT("int64"); - static inline FName PC_Float = TEXT("float"); - static inline FName PC_Double = TEXT("double"); - static inline FName PC_Name = TEXT("name"); - static inline FName PC_Object = TEXT("object"); - static inline FName PC_String = TEXT("string"); - static inline FName PC_Text = TEXT("text"); - static inline FName PC_Struct = TEXT("struct"); - static inline FName PC_Enum = TEXT("enum"); - - static inline FName AnyPinName = TEXT("AnyPinName"); - FFlowPin() : PinName(NAME_None) { } - FFlowPin(const FName& InPinName) + explicit FFlowPin(const FName& InPinName) : PinName(InPinName) { } - FFlowPin(const FString& InPinName) + explicit FFlowPin(const FString& InPinName) : PinName(*InPinName) { } - FFlowPin(const FText& InPinName) + explicit FFlowPin(const FText& InPinName) : PinName(*InPinName.ToString()) { } - FFlowPin(const TCHAR* InPinName) + explicit FFlowPin(const TCHAR* InPinName) : PinName(FName(InPinName)) { } - FFlowPin(const uint8& InPinName) + explicit FFlowPin(const uint8& InPinName) : PinName(FName(*FString::FromInt(InPinName))) { } - FFlowPin(const int32& InPinName) + explicit FFlowPin(const int32& InPinName) : PinName(FName(*FString::FromInt(InPinName))) { } - FFlowPin(const FStringView InPinName, const FText& InPinFriendlyName) + explicit FFlowPin(const FStringView InPinName, const FText& InPinFriendlyName) : PinName(InPinName) , PinFriendlyName(InPinFriendlyName) { } - FFlowPin(const FStringView InPinName, const FString& InPinTooltip) + explicit FFlowPin(const FStringView InPinName, const FString& InPinTooltip) : PinName(InPinName) , PinToolTip(InPinTooltip) { } - FFlowPin(const FStringView InPinName, const FText& InPinFriendlyName, const FString& InPinTooltip) + explicit FFlowPin(const FStringView InPinName, const FText& InPinFriendlyName, const FString& InPinTooltip) : PinName(InPinName) , PinFriendlyName(InPinFriendlyName) , PinToolTip(InPinTooltip) { } - FFlowPin(const FName& InPinName, const FText& InPinFriendlyName) + explicit FFlowPin(const FName& InPinName, const FText& InPinFriendlyName) : PinName(InPinName) , PinFriendlyName(InPinFriendlyName) { } - FFlowPin(const FName& InPinName, const FText& InPinFriendlyName, const FString& InPinTooltip) + explicit FFlowPin(const FName& InPinName, const FText& InPinFriendlyName, const FString& InPinTooltip) : PinName(InPinName) , PinFriendlyName(InPinFriendlyName) , PinToolTip(InPinTooltip) { } - FFlowPin(const FName& InPinName, const FText& InPinFriendlyName, EFlowPinType InFlowPinType, UObject* SubCategoryObject = nullptr) + explicit FFlowPin(const FName& InPinName, const FText& InPinFriendlyName, const FFlowPinTypeName& InTypeName, UObject* OptionalSubCategoryObject = nullptr) : PinName(InPinName) , PinFriendlyName(InPinFriendlyName) { - SetPinType(InFlowPinType, SubCategoryObject); + SetPinTypeName(InTypeName); + SetPinSubCategoryObject(OptionalSubCategoryObject); } - FFlowPin(const FName& InPinName, EFlowPinType InFlowPinType, UObject* SubCategoryObject = nullptr) + explicit FFlowPin(const FName& InPinName, const FFlowPinTypeName& InTypeName, UObject* OptionalSubCategoryObject = nullptr) : PinName(InPinName) { - SetPinType(InFlowPinType, SubCategoryObject); + SetPinTypeName(InTypeName); + SetPinSubCategoryObject(OptionalSubCategoryObject); } FORCEINLINE bool IsValid() const @@ -190,6 +185,18 @@ struct FLOW_API FFlowPin return PinName != Other; } + bool DeepIsEqual(const FFlowPin& Other) const + { + // Do a deep pin match (not a simple name-only match), to check if the pins are exactly equal + return + PinName == Other.PinName && + PinFriendlyName.EqualTo(Other.PinFriendlyName) && + PinToolTip == Other.PinToolTip && + ContainerType == Other.ContainerType && + PinTypeName == Other.PinTypeName && + PinSubCategoryObject == Other.PinSubCategoryObject; + } + friend uint32 GetTypeHash(const FFlowPin& FlowPin) { return GetTypeHash(FlowPin.PinName); @@ -206,46 +213,23 @@ struct FLOW_API FFlowPin static bool ValidateEnum(const UEnum& EnumType); #endif // WITH_EDITOR - void SetPinType(const EFlowPinType InFlowPinType, UObject* SubCategoryObject = nullptr); - EFlowPinType GetPinType() const { return PinType; } - static const FName& GetPinCategoryFromPinType(EFlowPinType FlowPinType); - static const TArray& GetFlowPinTypeEnumValuesWithoutSpaces(); + void SetPinTypeName(const FFlowPinTypeName& InTypeName); + const FFlowPinTypeName& GetPinTypeName() const { return PinTypeName; } + const FFlowPinType* ResolveFlowPinType() const; + void SetPinSubCategoryObject(UObject* Object) { PinSubCategoryObject = Object; } + static FFlowPinTypeName GetPinTypeNameForLegacyPinType(EFlowPinType PinType); + +#if WITH_EDITOR + FEdGraphPinType BuildEdGraphPinType() const; +#endif const TWeakObjectPtr& GetPinSubCategoryObject() const { return PinSubCategoryObject; } - static bool ArePinArraysMatchingNamesAndTypes(const TArray& Left, const TArray& Right); - static bool DoPinsMatchNamesAndTypes(const FFlowPin& LeftPin, const FFlowPin& RightPin) - { - return (LeftPin.PinName == RightPin.PinName && LeftPin.PinType == RightPin.PinType && LeftPin.PinSubCategoryObject == RightPin.PinSubCategoryObject); - } + FORCEINLINE_DEBUGGABLE static bool DeepArePinArraysMatching(const TArray& Left, const TArray& Right); // FFlowPin instance signatures for "trait" functions - FORCEINLINE bool IsExecPin() const { return PinType == EFlowPinType::Exec; } - FORCEINLINE bool IsDataPin() const { return PinType != EFlowPinType::Exec; } - // -- - - // PinCategory "trait" functions: - FORCEINLINE static bool IsExecPinCategory(const FName& PC) { return PC == PC_Exec; } - FORCEINLINE static bool IsDataPinCategory(const FName& PC) { return PC != PC_Exec; } - FORCEINLINE static bool IsBoolPinCategory(const FName& PC) { return PC == PC_Boolean; } - FORCEINLINE static bool IsIntPinCategory(const FName& PC) { return PC == PC_Byte || PC == PC_Int || PC == PC_Int64; } - FORCEINLINE static bool IsFloatPinCategory(const FName& PC) { return PC == PC_Double || PC == PC_Float; } - FORCEINLINE static bool IsEnumPinCategory(const FName& PC) { return PC == PC_Enum; } - FORCEINLINE static bool IsTextPinCategory(const FName& PC) { return PC == PC_Name || PC == PC_String || PC == PC_Text; } - FORCEINLINE static bool IsObjectPinCategory(const FName& PC) { return PC == PC_Object; } - FORCEINLINE static bool IsClassPinCategory(const FName& PC) { return PC == PC_Class; } - FORCEINLINE static bool IsStructPinCategory(const FName& PC) { return PC == PC_Struct; } - // -- - - // IsConvertable trait functions: - FORCEINLINE static bool IsConvertableToBoolPinCategory(const FName& PC) { return IsBoolPinCategory(PC); } - FORCEINLINE static bool IsConvertableToIntPinCategory(const FName& PC) { return IsIntPinCategory(PC); } - FORCEINLINE static bool IsConvertableToFloatPinCategory(const FName& PC) { return IsFloatPinCategory(PC); } - FORCEINLINE static bool IsConvertableToEnumPinCategory(const FName& PC) { return IsEnumPinCategory(PC); } - FORCEINLINE static bool IsConvertableToTextPinCategory(const FName& PC) { return IsTextPinCategory(PC); } - FORCEINLINE static bool IsConvertableToObjectPinCategory(const FName& PC) { return IsObjectPinCategory(PC); } - FORCEINLINE static bool IsConvertableToClassPinCategory(const FName& PC) { return IsClassPinCategory(PC); } - FORCEINLINE static bool IsConvertableToStructPinCategory(const FName& PC) { return IsStructPinCategory(PC); } + bool IsExecPin() const; + static bool IsExecPinCategory(const FName& PC); // -- // Metadata keys for properties that bind and auto-generate Data Pins: @@ -292,12 +276,26 @@ struct FLOW_API FFlowPin protected: void TrySetStructSubCategoryObjectFromPinType(); +}; -private: +// Inline implementations +bool FFlowPin::DeepArePinArraysMatching(const TArray& Left, const TArray& Right) +{ + if (Left.Num() != Right.Num()) + { + return false; + } - // Cached EFlowPinType values as FName, de-spaced, so they can be compared with FlowPinType metadata strings - static TArray FlowPinTypeEnumValuesWithoutSpaces; -}; + for (int32 Index = 0; Index < Left.Num(); ++Index) + { + if (!Left[Index].DeepIsEqual(Right[Index])) + { + return false; + } + } + + return true; +} USTRUCT() struct FLOW_API FFlowPinHandle diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_CustomInput.h b/Source/Flow/Public/Nodes/Graph/FlowNode_CustomInput.h index 549a0a0d8..38bc168e4 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_CustomInput.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_CustomInput.h @@ -23,6 +23,6 @@ class FLOW_API UFlowNode_CustomInput : public UFlowNode_CustomEventBase #if WITH_EDITOR public: - virtual FText GetNodeTitle() const override; + virtual FText K2_GetNodeTitle_Implementation() const override; #endif }; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_CustomOutput.h b/Source/Flow/Public/Nodes/Graph/FlowNode_CustomOutput.h index b0e93d422..4d283fb31 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_CustomOutput.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_CustomOutput.h @@ -18,6 +18,6 @@ class FLOW_API UFlowNode_CustomOutput final : public UFlowNode_CustomEventBase virtual void ExecuteInput(const FName& PinName) override; #if WITH_EDITOR - virtual FText GetNodeTitle() const override; + virtual FText K2_GetNodeTitle_Implementation() const override; #endif }; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h index 0573362bc..2d5d21bb8 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h @@ -2,9 +2,9 @@ #pragma once -#include "Interfaces/FlowDataPinGeneratorNodeInterface.h" +#include "Interfaces/FlowNamedPropertiesSupplierInterface.h" #include "Nodes/FlowNode.h" -#include "Types/FlowDataPinProperties.h" +#include "Types/FlowNamedDataPinProperty.h" #include "FlowNode_DefineProperties.generated.h" @@ -12,7 +12,9 @@ * 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 IFlowNamedPropertiesSupplierInterface { GENERATED_UCLASS_BODY() @@ -23,6 +25,8 @@ class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPi TArray NamedProperties; public: + virtual void PostLoad() override; + #if WITH_EDITOR // IFlowContextPinSupplierInterface virtual bool SupportsContextPins() const override { return Super::SupportsContextPins() || !NamedProperties.IsEmpty(); } @@ -32,17 +36,21 @@ class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPi virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; // -- - // IFlowDataPinGeneratorNodeInterface - virtual void AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const override; + // IFlowDataPinGeneratorInterface + virtual void AutoGenerateDataPins(FFlowAutoDataPinsWorkingData& InOutWorkingData) const override; + // -- + + // IFlowNamedPropertiesSupplierInterface + virtual TArray& GetMutableNamedProperties() override { return NamedProperties; } // -- #endif bool TryFormatTextWithNamedPropertiesAsParameters(const FText& FormatText, FText& OutFormattedText) const; protected: - virtual bool TryFindPropertyByRemappedPinName( - const FName& RemappedPinName, + virtual bool TryFindPropertyByPinName( + const UObject& PropertyOwnerObject, + const FName& PinName, const FProperty*& OutFoundProperty, - TInstancedStruct& OutFoundInstancedStruct, - EFlowDataPinResolveResult& InOutResult) const override; + TInstancedStruct& OutFoundInstancedStruct) const override; }; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h b/Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h index 02d5addea..bc699ddc6 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_FormatText.h @@ -33,9 +33,7 @@ class FLOW_API UFlowNode_FormatText : public UFlowNode_DefineProperties public: // IFlowDataPinValueSupplierInterface - 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 TrySupplyDataPin_Implementation(FName PinName) const override; // -- static const FName OUTPIN_TextOutput; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_Start.h b/Source/Flow/Public/Nodes/Graph/FlowNode_Start.h index d47fd6343..8b31a392b 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_Start.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_Start.h @@ -39,24 +39,7 @@ class FLOW_API UFlowNode_Start #endif // -- - // Must implement TrySupplyDataAs... for every EFlowPinType - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - // IFlowDataPinValueSupplierInterface - 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; + virtual FFlowDataPinResult TrySupplyDataPin_Implementation(FName PinName) const override; // -- }; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h index 5f7f89f0d..572a42aa3 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h @@ -3,7 +3,6 @@ #pragma once #include "Nodes/FlowNode.h" -#include "Interfaces/FlowDataPinGeneratorNodeInterface.h" #include "FlowNode_SubGraph.generated.h" @@ -11,11 +10,11 @@ * Creates instance of provided Flow Asset and starts its execution */ UCLASS(NotBlueprintable, meta = (DisplayName = "Sub Graph")) -class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGeneratorNodeInterface +class FLOW_API UFlowNode_SubGraph : public UFlowNode { GENERATED_UCLASS_BODY() -public: +public: friend class UFlowAsset; friend class FFlowNode_SubGraphDetails; friend class UFlowSubsystem; @@ -27,6 +26,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 @@ -74,7 +75,7 @@ class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGenerat virtual TArray GetContextOutputs() const override; // -- - virtual FText GetNodeTitle() const override; + virtual FText K2_GetNodeTitle_Implementation() const override; virtual FString GetNodeDescription() const override; virtual UObject* GetAssetToEdit() override; virtual EDataValidationResult ValidateNode() override; @@ -89,8 +90,8 @@ class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGenerat virtual bool CanSupplyDataPinValues_Implementation() const override; // -- - // IFlowDataPinGeneratorNodeInterface - virtual void AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const override; + // IFlowDataPinGeneratorInterface + virtual void AutoGenerateDataPins(FFlowAutoDataPinsWorkingData& InOutWorkingData) const override; // -- private: diff --git a/Source/Flow/Public/Types/FlowArray.h b/Source/Flow/Public/Types/FlowArray.h index 7940a6db8..b5817ca53 100644 --- a/Source/Flow/Public/Types/FlowArray.h +++ b/Source/Flow/Public/Types/FlowArray.h @@ -70,4 +70,21 @@ namespace FlowArray return false; } + + template + FString FormatArrayString(const TArray& Values, TFunctionRef Formatter, const FString& Separator = TEXT(",")) + { + FString ValueString; + for (const T& Value : Values) + { + if (!ValueString.IsEmpty()) + { + ValueString += Separator; + } + + ValueString += Formatter(Value); + } + + return ValueString; + } } \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowAutoDataPinsWorkingData.h b/Source/Flow/Public/Types/FlowAutoDataPinsWorkingData.h new file mode 100644 index 000000000..1a5f41bd7 --- /dev/null +++ b/Source/Flow/Public/Types/FlowAutoDataPinsWorkingData.h @@ -0,0 +1,28 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Nodes/FlowPin.h" + +// Working Data struct for the UFlowDataPinGeneratorNodeInterface::AutoGenerateDataPins function +struct FFlowAutoDataPinsWorkingData +{ + FFlowAutoDataPinsWorkingData(const TArray& InputPinsPrev, const TArray& OutputPinsPrev) + : AutoInputDataPinsPrev(InputPinsPrev) + , AutoOutputDataPinsPrev(OutputPinsPrev) + { } + +#if WITH_EDITOR + FLOW_API void AddFlowDataPinsForClassProperties(const UObject& ObjectContainer); + FLOW_API void AddFlowDataPinForProperty(const FProperty* Property, const UObject& ObjectContainer); + + FLOW_API bool DidAutoInputDataPinsChange() const; + FLOW_API bool DidAutoOutputDataPinsChange() const; +#endif + + const TArray& AutoInputDataPinsPrev; + const TArray& AutoOutputDataPinsPrev; + + TArray AutoInputDataPinsNext; + TArray AutoOutputDataPinsNext; +}; diff --git a/Source/Flow/Public/Types/FlowDataPinBlueprintLibrary.h b/Source/Flow/Public/Types/FlowDataPinBlueprintLibrary.h index 7183d9146..c5ec0f9f5 100644 --- a/Source/Flow/Public/Types/FlowDataPinBlueprintLibrary.h +++ b/Source/Flow/Public/Types/FlowDataPinBlueprintLibrary.h @@ -4,246 +4,819 @@ #include "Kismet/BlueprintFunctionLibrary.h" -#include "FlowDataPinProperties.h" +#include "FlowDataPinValuesStandard.h" #include "FlowDataPinResults.h" +#include "FlowPinTypeTemplates.h" #include "FlowDataPinBlueprintLibrary.generated.h" -// Auto-cast operators for blueprint to their inner types +struct FFlowDataPinValue; + +// Auto‑cast operators for blueprint to their inner types UCLASS() class UFlowDataPinBlueprintLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() +private: + static void ResolveAndExtract_Impl( + UFlowNodeBase* Target, + FName PinName, + EFlowDataPinResolveSimpleResult& SimpleResult, + EFlowDataPinResolveResult& ResultEnum, + auto&& ExtractLambda); + public: + // ---------- Pin construction helpers ---------- + UFUNCTION(BlueprintPure, Category = FlowPin, Meta = (BlueprintThreadSafe, DisplayName = "Make Flow Pin")) - static - UPARAM(DisplayName = "Flow Pin") FFlowPin - MakeStruct( - FName PinName, - FText PinFriendlyName, - FString PinToolTip) + static UPARAM(DisplayName = "Flow Pin") FFlowPin MakeStruct(FName PinName, FText PinFriendlyName, FString PinToolTip) { return FFlowPin(PinName, PinFriendlyName, PinToolTip); } UFUNCTION(BlueprintPure, Category = FlowPin, Meta = (BlueprintThreadSafe, DisplayName = "Break Flow Pin")) - static void - BreakStruct( - UPARAM(DisplayName = "Flow Pin") FFlowPin Ref, - FName& OutPinName, - FText& OutPinFriendlyName, - FString& OutPinToolTip) + static void BreakStruct(UPARAM(DisplayName = "Flow Pin") FFlowPin Ref, FName& OutPinName, FText& OutPinFriendlyName, FString& OutPinToolTip) { OutPinName = Ref.PinName; OutPinFriendlyName = Ref.PinFriendlyName; OutPinToolTip = Ref.PinToolTip; } - // Recommend implementing AutoConvert_FlowDataPinProperty... for every EFlowPinType - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + // ---------- Resolve As ... functions ---------- + // Full-featured resolve nodes with execution pins for detailed error handling. + // Use these when you need to branch on Success/Failure/Coercion or inspect the exact resolve result. + + // Resolve a Bool DataPin Value to a single bool. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Bool", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsBool(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Bool& BoolValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, bool& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Bool DataPin Value to a bool array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Bool Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsBoolArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Bool& BoolValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve an Int DataPin Value to a single int32. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Int", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsInt(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Int& IntValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, int32& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve an Int DataPin Value to an int32 array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Int Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsIntArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Int& IntValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve an Int64 DataPin Value to a single int64. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Int64", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsInt64(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Int64& Int64Value, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, int64& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve an Int64 DataPin Value to an int64 array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Int64 Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsInt64Array(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Int64& Int64Value, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Float DataPin Value to a single float. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Float", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsFloat(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Float& FloatValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, float& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Float DataPin Value to a float array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Float Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsFloatArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Float& FloatValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Double DataPin Value to a single double. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Double", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsDouble(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Double& DoubleValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, double& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Double DataPin Value to a double array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Double Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsDoubleArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Double& DoubleValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Name DataPin Value to a single FName. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Name", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsName(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Name& NameValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FName& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Name DataPin Value to an FName array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Name Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsNameArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Name& NameValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a String DataPin Value to a single FString. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As String", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsString(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_String& StringValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FString& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a String DataPin Value to an FString array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As String Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsStringArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_String& StringValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Text DataPin Value to a single FText. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Text", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsText(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Text& TextValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FText& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Text DataPin Value to an FText array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Text Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsTextArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Text& TextValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve an Enum DataPin Value to a single uint8. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Enum", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsEnum(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Enum& EnumValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, uint8& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve an Enum DataPin Value to a uint8 array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Enum Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsEnumArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Enum& EnumValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Vector DataPin Value to a single FVector. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Vector", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsVector(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Vector& VectorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FVector& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Vector DataPin Value to an FVector array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Vector Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsVectorArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Vector& VectorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Rotator DataPin Value to a single FRotator. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Rotator", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsRotator(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Rotator& RotatorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FRotator& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Rotator DataPin Value to an FRotator array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Rotator Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsRotatorArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Rotator& RotatorValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Transform DataPin Value to a single FTransform. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Transform", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsTransform(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Transform& TransformValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FTransform& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Transform DataPin Value to an FTransform array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Transform Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsTransformArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Transform& TransformValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a GameplayTag DataPin Value to a single FGameplayTag. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As GameplayTag", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsGameplayTag(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_GameplayTag& GameplayTagValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FGameplayTag& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a GameplayTag DataPin Value to an FGameplayTag array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As GameplayTag Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsGameplayTagArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_GameplayTag& GameplayTagValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a GameplayTagContainer DataPin Value (scalar only). + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As GameplayTagContainer", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsGameplayTagContainer(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_GameplayTagContainer& GameplayTagContainerValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FGameplayTagContainer& Value); + + // Resolve an InstancedStruct DataPin Value to a single FInstancedStruct. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As InstancedStruct", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsInstancedStruct(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_InstancedStruct& InstancedStructValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, FInstancedStruct& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve an InstancedStruct DataPin Value to an FInstancedStruct array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As InstancedStruct Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsInstancedStructArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_InstancedStruct& InstancedStructValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve an Object DataPin Value to a single UObject*. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Object", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsObject(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Object& ObjectValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, UObject*& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve an Object DataPin Value to a UObject* array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Object Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsObjectArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Object& ObjectValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // Resolve a Class DataPin Value to a single UClass*. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Class", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsClass(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Class& ClassValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, UClass*& Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Resolve a Class DataPin Value to a UClass* array. + UFUNCTION(BlueprintCallable, Category = DataPins, meta = (DisplayName = "Resolve As Class Array", DefaultToSelf = "Target", ExpandEnumAsExecs = "Result")) + static void ResolveAsClassArray(UFlowNodeBase* Target, UPARAM(Ref) const FFlowDataPinValue_Class& ClassValue, EFlowDataPinResolveSimpleResult& Result, EFlowDataPinResolveResult& ResultEnum, TArray& Values); + + // ---------- Auto-Resolve As ... functions ---------- + // Easy-resolve convenience nodes. On failure, logs an error and returns a safe default (false/empty/null). + // Use these for fast, fire-and-forget resolving when you don't expect failures. + + // Easy Resolve a Bool DataPin Value to a single bool (last value if array). Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Bool", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static bool AutoConvert_TryResolveAsBool(UPARAM(Ref) const FFlowDataPinValue_Bool& BoolValue, UFlowNodeBase* Target); + + // Easy Resolve a Bool DataPin Value to a bool array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Bool Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsBoolArray(UPARAM(Ref) const FFlowDataPinValue_Bool& BoolValue, UFlowNodeBase* Target); + + // Easy Resolve an Int DataPin Value to a single int32. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Int", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static int32 AutoConvert_TryResolveAsInt(UPARAM(Ref) const FFlowDataPinValue_Int& IntValue, UFlowNodeBase* Target); + + // Easy Resolve an Int DataPin Value to an int32 array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Int Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsIntArray(UPARAM(Ref) const FFlowDataPinValue_Int& IntValue, UFlowNodeBase* Target); + + // Easy Resolve an Int64 DataPin Value to a single int64. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Int64", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static int64 AutoConvert_TryResolveAsInt64(UPARAM(Ref) const FFlowDataPinValue_Int64& Int64Value, UFlowNodeBase* Target); + + // Easy Resolve an Int64 DataPin Value to an int64 array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Int64 Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsInt64Array(UPARAM(Ref) const FFlowDataPinValue_Int64& Int64Value, UFlowNodeBase* Target); + + // Easy Resolve a Float DataPin Value to a single float. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Float", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static float AutoConvert_TryResolveAsFloat(UPARAM(Ref) const FFlowDataPinValue_Float& FloatValue, UFlowNodeBase* Target); + + // Easy Resolve a Float DataPin Value to a float array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Float Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsFloatArray(UPARAM(Ref) const FFlowDataPinValue_Float& FloatValue, UFlowNodeBase* Target); + + // Easy Resolve a Double DataPin Value to a single double. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Double", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static double AutoConvert_TryResolveAsDouble(UPARAM(Ref) const FFlowDataPinValue_Double& DoubleValue, UFlowNodeBase* Target); + + // Easy Resolve a Double DataPin Value to a double array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Double Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsDoubleArray(UPARAM(Ref) const FFlowDataPinValue_Double& DoubleValue, UFlowNodeBase* Target); + + // Easy Resolve a Name DataPin Value to a single FName. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Name", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FName AutoConvert_TryResolveAsName(UPARAM(Ref) const FFlowDataPinValue_Name& NameValue, UFlowNodeBase* Target); + + // Easy Resolve a Name DataPin Value to an FName array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Name Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsNameArray(UPARAM(Ref) const FFlowDataPinValue_Name& NameValue, UFlowNodeBase* Target); + + // Easy Resolve a String DataPin Value to a single FString. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to String", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FString AutoConvert_TryResolveAsString(UPARAM(Ref) const FFlowDataPinValue_String& StringValue, UFlowNodeBase* Target); + + // Easy Resolve a String DataPin Value to an FString array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to String Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsStringArray(UPARAM(Ref) const FFlowDataPinValue_String& StringValue, UFlowNodeBase* Target); + + // Easy Resolve a Text DataPin Value to a single FText. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Text", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FText AutoConvert_TryResolveAsText(UPARAM(Ref) const FFlowDataPinValue_Text& TextValue, UFlowNodeBase* Target); + + // Easy Resolve a Text DataPin Value to an FText array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Text Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsTextArray(UPARAM(Ref) const FFlowDataPinValue_Text& TextValue, UFlowNodeBase* Target); + + // Easy Resolve an Enum DataPin Value to a single uint8. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Enum", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static uint8 AutoConvert_TryResolveAsEnum(UPARAM(Ref) const FFlowDataPinValue_Enum& EnumValue, UFlowNodeBase* Target); + + // Easy Resolve an Enum DataPin Value to a uint8 array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Enum Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsEnumArray(UPARAM(Ref) const FFlowDataPinValue_Enum& EnumValue, UFlowNodeBase* Target); + + // Easy Resolve a Vector DataPin Value to a single FVector. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Vector", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FVector AutoConvert_TryResolveAsVector(UPARAM(Ref) const FFlowDataPinValue_Vector& VectorValue, UFlowNodeBase* Target); + + // Easy Resolve a Vector DataPin Value to an FVector array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Vector Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsVectorArray(UPARAM(Ref) const FFlowDataPinValue_Vector& VectorValue, UFlowNodeBase* Target); + + // Easy Resolve a Rotator DataPin Value to a single FRotator. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Rotator", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FRotator AutoConvert_TryResolveAsRotator(UPARAM(Ref) const FFlowDataPinValue_Rotator& RotatorValue, UFlowNodeBase* Target); + + // Easy Resolve a Rotator DataPin Value to an FRotator array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Rotator Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsRotatorArray(UPARAM(Ref) const FFlowDataPinValue_Rotator& RotatorValue, UFlowNodeBase* Target); + + // Easy Resolve a Transform DataPin Value to a single FTransform. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Transform", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FTransform AutoConvert_TryResolveAsTransform(UPARAM(Ref) const FFlowDataPinValue_Transform& TransformValue, UFlowNodeBase* Target); + + // Easy Resolve a Transform DataPin Value to an FTransform array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Transform Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsTransformArray(UPARAM(Ref) const FFlowDataPinValue_Transform& TransformValue, UFlowNodeBase* Target); + + // Easy Resolve a GameplayTag DataPin Value to a single FGameplayTag. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to GameplayTag", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FGameplayTag AutoConvert_TryResolveAsGameplayTag(UPARAM(Ref) const FFlowDataPinValue_GameplayTag& GameplayTagValue, UFlowNodeBase* Target); + + // Easy Resolve a GameplayTag DataPin Value to an FGameplayTag array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to GameplayTag Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsGameplayTagArray(UPARAM(Ref) const FFlowDataPinValue_GameplayTag& GameplayTagValue, UFlowNodeBase* Target); + + // Easy Resolve a GameplayTagContainer DataPin Value (scalar only). Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to GameplayTagContainer", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FGameplayTagContainer AutoConvert_TryResolveAsGameplayTagContainer(UPARAM(Ref) const FFlowDataPinValue_GameplayTagContainer& GameplayTagContainerValue, UFlowNodeBase* Target); + + // Easy Resolve an InstancedStruct DataPin Value to a single FInstancedStruct. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to InstancedStruct", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static FInstancedStruct AutoConvert_TryResolveAsInstancedStruct(UPARAM(Ref) const FFlowDataPinValue_InstancedStruct& InstancedStructValue, UFlowNodeBase* Target); + + // Easy Resolve an InstancedStruct DataPin Value to an FInstancedStruct array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to InstancedStruct Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsInstancedStructArray(UPARAM(Ref) const FFlowDataPinValue_InstancedStruct& InstancedStructValue, UFlowNodeBase* Target); + + // Easy Resolve an Object DataPin Value to a single UObject*. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Object", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static UObject* AutoConvert_TryResolveAsObject(UPARAM(Ref) const FFlowDataPinValue_Object& ObjectValue, UFlowNodeBase* Target); + + // Easy Resolve an Object DataPin Value to a UObject* array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Object Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsObjectArray(UPARAM(Ref) const FFlowDataPinValue_Object& ObjectValue, UFlowNodeBase* Target); + + // Easy Resolve a Class DataPin Value to a single UClass*. Logs error on failure. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Class", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static UClass* AutoConvert_TryResolveAsClass(UPARAM(Ref) const FFlowDataPinValue_Class& ClassValue, UFlowNodeBase* Target); + + // Easy Resolve a Class DataPin Value to a UClass* array. Logs error on failure and returns empty array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Auto-Resolve to Class Array", CompactNodeTitle = "->", BlueprintAutocast, DefaultToSelf = "Target"), Category = DataPins) + static TArray AutoConvert_TryResolveAsClassArray(UPARAM(Ref) const FFlowDataPinValue_Class& ClassValue, UFlowNodeBase* Target); + + // ---------- Result → result enum converter ---------- + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Result", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static EFlowDataPinResolveResult AutoConvert_TryExtractResultEnum(const FFlowDataPinResult& DataPinResult) + { + return DataPinResult.Result; + } + + // ---------- Result → native value extractors ---------- + + // Bool + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Bool", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static bool AutoConvert_TryExtractBool(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Bool Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractBoolArray(const FFlowDataPinResult& DataPinResult); + + // Int (int32) + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Int", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static int32 AutoConvert_TryExtractInt(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Int Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractIntArray(const FFlowDataPinResult& DataPinResult); + + // Int64 + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Int64", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static int64 AutoConvert_TryExtractInt64(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Int64 Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractInt64Array(const FFlowDataPinResult& DataPinResult); + + // Float + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Float", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static float AutoConvert_TryExtractFloat(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Float Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractFloatArray(const FFlowDataPinResult& DataPinResult); + + // Double + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Double", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static double AutoConvert_TryExtractDouble(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Double Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractDoubleArray(const FFlowDataPinResult& DataPinResult); + + // Name + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Name", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FName AutoConvert_TryExtractName(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Name Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractNameArray(const FFlowDataPinResult& DataPinResult); + + // String + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to String", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FString AutoConvert_TryExtractString(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to String Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractStringArray(const FFlowDataPinResult& DataPinResult); + + // Text + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Text", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FText AutoConvert_TryExtractText(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Text Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractTextArray(const FFlowDataPinResult& DataPinResult); + + // Enum + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Enum", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static uint8 AutoConvert_TryExtractEnum(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Enum Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractEnumArray(const FFlowDataPinResult& DataPinResult); + + // Vector + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Vector", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FVector AutoConvert_TryExtractVector(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Vector Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractVectorArray(const FFlowDataPinResult& DataPinResult); + + // Rotator + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Rotator", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FRotator AutoConvert_TryExtractRotator(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Rotator Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractRotatorArray(const FFlowDataPinResult& DataPinResult); + + // Transform + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Transform", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FTransform AutoConvert_TryExtractTransform(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Transform Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractTransformArray(const FFlowDataPinResult& DataPinResult); + + // GameplayTag + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to GameplayTag", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FGameplayTag AutoConvert_TryExtractGameplayTag(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to GameplayTag Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractGameplayTagArray(const FFlowDataPinResult& DataPinResult); + + // GameplayTagContainer + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to GameplayTagContainer", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FGameplayTagContainer AutoConvert_TryExtractGameplayTagContainer(const FFlowDataPinResult& DataPinResult); + + // InstancedStruct + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to InstancedStruct", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static FInstancedStruct AutoConvert_TryExtractInstancedStruct(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to InstancedStruct Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractInstancedStructArray(const FFlowDataPinResult& DataPinResult); + + // Object + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Object", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static UObject* AutoConvert_TryExtractObject(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Object Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractObjectArray(const FFlowDataPinResult& DataPinResult); + + // Class + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Class", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static UClass* AutoConvert_TryExtractClass(const FFlowDataPinResult& DataPinResult); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Extract to Class Array", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) + static TArray AutoConvert_TryExtractClassArray(const FFlowDataPinResult& DataPinResult); + + // ---------- Get & Set Value functions ---------- + // Direct access to the stored payload on a DataPin Value struct. + // Set functions: Safe for both input and output pins. + // Get functions: ONLY safe on output pins. Using on an input pin triggers a runtime error in editor builds. + + // Set a single bool on a Bool DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Bool Value")) + static void SetBoolValue(bool bInValue, UPARAM(Ref) FFlowDataPinValue_Bool& BoolValue) { BoolValue.Values = { bInValue }; } + + // Set a bool array on a Bool DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Bool Values")) + static void SetBoolValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Bool& BoolValue) { BoolValue.Values = InValues; } + + // Get a single bool from an output Bool DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Bool Value")) + static bool GetBoolValue(UPARAM(Ref) const FFlowDataPinValue_Bool& BoolValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full bool array from an output Bool DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Bool Values")) + static TArray GetBoolValues(UPARAM(Ref) FFlowDataPinValue_Bool& BoolValue); + + // Set a single int32 on an Int DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Int Value")) + static void SetIntValue(int32 InValue, UPARAM(Ref) FFlowDataPinValue_Int& IntValue) { IntValue.Values = { InValue }; } + + // Set an int32 array on an Int DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Int Values")) + static void SetIntValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Int& IntValue) { IntValue.Values = InValues; } + + // Get a single int32 from an output Int DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Int Value")) + static int32 GetIntValue(UPARAM(Ref) const FFlowDataPinValue_Int& IntValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full int32 array from an output Int DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Int Values")) + static TArray GetIntValues(UPARAM(Ref) FFlowDataPinValue_Int& IntValue); + + // Set a single int64 on an Int64 DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Int64 Value")) + static void SetInt64Value(int64 InValue, UPARAM(Ref) FFlowDataPinValue_Int64& Int64Value) { Int64Value.Values = { InValue }; } + + // Set an int64 array on an Int64 DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Int64 Values")) + static void SetInt64Values(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Int64& Int64Value) { Int64Value.Values = InValues; } + + // Get a single int64 from an output Int64 DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Int64 Value")) + static int64 GetInt64Value(UPARAM(Ref) const FFlowDataPinValue_Int64& Int64Value, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full int64 array from an output Int64 DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Int64 Values")) + static TArray GetInt64Values(UPARAM(Ref) FFlowDataPinValue_Int64& Int64Value); + + // Set a single float on a Float DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Float Value")) + static void SetFloatValue(float InValue, UPARAM(Ref) FFlowDataPinValue_Float& FloatValue) { FloatValue.Values = { InValue }; } + + // Set a float array on a Float DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Float Values")) + static void SetFloatValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Float& FloatValue) { FloatValue.Values = InValues; } + + // Get a single float from an output Float DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Float Value")) + static float GetFloatValue(UPARAM(Ref) const FFlowDataPinValue_Float& FloatValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full float array from an output Float DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Float Values")) + static TArray GetFloatValues(UPARAM(Ref) FFlowDataPinValue_Float& FloatValue); + + // Set a single double on a Double DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Double Value")) + static void SetDoubleValue(double InValue, UPARAM(Ref) FFlowDataPinValue_Double& DoubleValue) { DoubleValue.Values = { InValue }; } + + // Set a double array on a Double DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Double Values")) + static void SetDoubleValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Double& DoubleValue) { DoubleValue.Values = InValues; } + + // Get a single double from an output Double DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Double Value")) + static double GetDoubleValue(UPARAM(Ref) const FFlowDataPinValue_Double& DoubleValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full double array from an output Double DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Double Values")) + static TArray GetDoubleValues(UPARAM(Ref) FFlowDataPinValue_Double& DoubleValue); + + // Set a single FName on a Name DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Name Value")) + static void SetNameValue(FName InValue, UPARAM(Ref) FFlowDataPinValue_Name& NameValue) { NameValue.Values = { InValue }; } + + // Set an FName array on a Name DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Name Values")) + static void SetNameValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Name& NameValue) { NameValue.Values = InValues; } + + // Get a single FName from an output Name DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Name Value")) + static FName GetNameValue(UPARAM(Ref) const FFlowDataPinValue_Name& NameValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full FName array from an output Name DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Name Values")) + static TArray GetNameValues(UPARAM(Ref) FFlowDataPinValue_Name& NameValue); + + // Set a single FString on a String DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set String Value")) + static void SetStringValue(const FString& InValue, UPARAM(Ref) FFlowDataPinValue_String& StringValue) { StringValue.Values = { InValue }; } + + // Set an FString array on a String DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set String Values")) + static void SetStringValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_String& StringValue) { StringValue.Values = InValues; } + + // Get a single FString from an output String DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get String Value")) + static FString GetStringValue(UPARAM(Ref) const FFlowDataPinValue_String& StringValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full FString array from an output String DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get String Values")) + static TArray GetStringValues(UPARAM(Ref) FFlowDataPinValue_String& StringValue); + + // Set a single FText on a Text DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Text Value")) + static void SetTextValue(const FText& InValue, UPARAM(Ref) FFlowDataPinValue_Text& TextValue) { TextValue.Values = { InValue }; } + + // Set an FText array on a Text DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Text Values")) + static void SetTextValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Text& TextValue) { TextValue.Values = InValues; } + + // Get a single FText from an output Text DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Text Value")) + static FText GetTextValue(UPARAM(Ref) const FFlowDataPinValue_Text& TextValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full FText array from an output Text DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Text Values")) + static TArray GetTextValues(UPARAM(Ref) FFlowDataPinValue_Text& TextValue); + + // Set a single enum value (as uint8) on an Enum DataPin Value (input or output pin). Requires EnumClass to be set on the struct. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Enum Value")) + static void SetEnumValue(uint8 InValue, UPARAM(Ref) FFlowDataPinValue_Enum& EnumValue); + + // Set an enum value array (as uint8) on an Enum DataPin Value (input or output pin). Requires EnumClass to be set on the struct. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Enum Values")) + static void SetEnumValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Enum& EnumValue); + + // Get a single enum value (as uint8) from an output Enum DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Enum Value")) + static uint8 GetEnumValue(UPARAM(Ref) const FFlowDataPinValue_Enum& EnumValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full enum value array (as uint8) from an output Enum DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Enum Values")) + static TArray GetEnumValues(UPARAM(Ref) const FFlowDataPinValue_Enum& EnumValue); + + // Set a single FVector on a Vector DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Vector Value")) + static void SetVectorValue(const FVector& InValue, UPARAM(Ref) FFlowDataPinValue_Vector& VectorValue) { VectorValue.Values = { InValue }; } + + // Set an FVector array on a Vector DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Vector Values")) + static void SetVectorValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Vector& VectorValue) { VectorValue.Values = InValues; } + + // Get a single FVector from an output Vector DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Vector Value")) + static FVector GetVectorValue(UPARAM(Ref) const FFlowDataPinValue_Vector& VectorValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full FVector array from an output Vector DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Vector Values")) + static TArray GetVectorValues(UPARAM(Ref) FFlowDataPinValue_Vector& VectorValue); + + // Set a single FRotator on a Rotator DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Rotator Value")) + static void SetRotatorValue(const FRotator& InValue, UPARAM(Ref) FFlowDataPinValue_Rotator& RotatorValue) { RotatorValue.Values = { InValue }; } + + // Set an FRotator array on a Rotator DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Rotator Values")) + static void SetRotatorValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Rotator& RotatorValue) { RotatorValue.Values = InValues; } + + // Get a single FRotator from an output Rotator DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Rotator Value")) + static FRotator GetRotatorValue(UPARAM(Ref) const FFlowDataPinValue_Rotator& RotatorValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); + + // Get the full FRotator array from an output Rotator DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Rotator Values")) + static TArray GetRotatorValues(UPARAM(Ref) FFlowDataPinValue_Rotator& RotatorValue); - // FFlowDataPinProperty auto-cast functions + // Set a single FTransform on a Transform DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Transform Value")) + static void SetTransformValue(const FTransform& InValue, UPARAM(Ref) FFlowDataPinValue_Transform& TransformValue) { TransformValue.Values = { InValue }; } - // Convert bool property values to their inner blue values - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Bool", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static bool AutoConvert_FlowDataPinPropertyBoolToBool(const FFlowDataPinOutputProperty_Bool& BoolProperty) { return BoolProperty.Value; } + // Set an FTransform array on a Transform DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Transform Values")) + static void SetTransformValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Transform& TransformValue) { TransformValue.Values = InValues; } - // to Int variants for all int types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Int", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static int32 AutoConvert_FlowDataPinPropertyInt32ToInt32(const FFlowDataPinOutputProperty_Int32& IntProperty) { return IntProperty.Value; } + // Get a single FTransform from an output Transform DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Transform Value")) + static FTransform GetTransformValue(UPARAM(Ref) const FFlowDataPinValue_Transform& TransformValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Int64", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static int64 AutoConvert_FlowDataPinPropertyInt32ToInt64(const FFlowDataPinOutputProperty_Int32& IntProperty) { return static_cast(IntProperty.Value); } + // Get the full FTransform array from an output Transform DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Transform Values")) + static TArray GetTransformValues(UPARAM(Ref) FFlowDataPinValue_Transform& TransformValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Int", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static int32 AutoConvert_FlowDataPinPropertyInt64ToInt32(const FFlowDataPinOutputProperty_Int64& IntProperty) { /* possible loss of precision */ return static_cast(IntProperty.Value); } + // Set a single FGameplayTag on a GameplayTag DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set GameplayTag Value")) + static void SetGameplayTagValue(FGameplayTag InValue, UPARAM(Ref) FFlowDataPinValue_GameplayTag& GameplayTagValue) { GameplayTagValue.Values = { InValue }; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Int64", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static int64 AutoConvert_FlowDataPinPropertyInt64ToInt64(const FFlowDataPinOutputProperty_Int64& IntProperty) { return IntProperty.Value; } + // Set an FGameplayTag array on a GameplayTag DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set GameplayTag Values")) + static void SetGameplayTagValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_GameplayTag& GameplayTagValue) { GameplayTagValue.Values = InValues; } - // to Float variants for all float types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Float", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static float AutoConvert_FlowDataPinPropertyFloat32ToFloat32(const FFlowDataPinOutputProperty_Float& FloatProperty) { return FloatProperty.Value; } + // Get a single FGameplayTag from an output GameplayTag DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get GameplayTag Value")) + static FGameplayTag GetGameplayTagValue(UPARAM(Ref) const FFlowDataPinValue_GameplayTag& GameplayTagValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Double", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static double AutoConvert_FlowDataPinPropertyFloat32ToFloat64(const FFlowDataPinOutputProperty_Float& FloatProperty) { return static_cast(FloatProperty.Value); } + // Get the full FGameplayTag array from an output GameplayTag DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get GameplayTag Values")) + static TArray GetGameplayTagValues(UPARAM(Ref) FFlowDataPinValue_GameplayTag& GameplayTagValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Float", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static float AutoConvert_FlowDataPinPropertyFloat64ToFloat32(const FFlowDataPinOutputProperty_Double& FloatProperty) { /* possible loss of precision */ return static_cast(FloatProperty.Value); } + // Set a GameplayTagContainer on a GameplayTagContainer DataPin Value (input or output pin, scalar only). + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set GameplayTagContainer Value")) + static void SetGameplayTagContainerValue(const FGameplayTagContainer& InValue, UPARAM(Ref) FFlowDataPinValue_GameplayTagContainer& GameplayTagContainerValue) { GameplayTagContainerValue.Values = InValue; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Double", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static double AutoConvert_FlowDataPinPropertyFloat64ToFloat64(const FFlowDataPinOutputProperty_Double& FloatProperty) { return FloatProperty.Value; } + // Get the GameplayTagContainer from an output GameplayTagContainer DataPin Value (scalar only). DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get GameplayTagContainer Value")) + static FGameplayTagContainer GetGameplayTagContainerValue(UPARAM(Ref) const FFlowDataPinValue_GameplayTagContainer& GameplayTagContainerValue); - // to Name variants for all text-based types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Name", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FName AutoConvert_FlowDataPinPropertyNameToName(const FFlowDataPinOutputProperty_Name& NameProperty) { return NameProperty.Value; } + // Set a single FInstancedStruct on an InstancedStruct DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set InstancedStruct Value")) + static void SetInstancedStructValue(const FInstancedStruct& InValue, UPARAM(Ref) FFlowDataPinValue_InstancedStruct& InstancedStructValue) { InstancedStructValue.Values = { InValue }; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Name", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FName AutoConvert_FlowDataPinPropertyStringToName(const FFlowDataPinOutputProperty_String& StringProperty) { return FName(StringProperty.Value); } + // Set an FInstancedStruct array on an InstancedStruct DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set InstancedStruct Values")) + static void SetInstancedStructValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_InstancedStruct& InstancedStructValue) { InstancedStructValue.Values = InValues; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Name", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FName AutoConvert_FlowDataPinPropertyTextToName(const FFlowDataPinOutputProperty_Text& TextProperty) { return FName(TextProperty.Value.ToString()); } + // Get a single FInstancedStruct from an output InstancedStruct DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get InstancedStruct Value")) + static FInstancedStruct GetInstancedStructValue(UPARAM(Ref) const FFlowDataPinValue_InstancedStruct& InstancedStructValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); - // to String variants for all text-based types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to String", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FString AutoConvert_FlowDataPinPropertyNameToString(const FFlowDataPinOutputProperty_Name& NameProperty) { return NameProperty.Value.ToString(); } + // Get the full FInstancedStruct array from an output InstancedStruct DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get InstancedStruct Values")) + static TArray GetInstancedStructValues(UPARAM(Ref) FFlowDataPinValue_InstancedStruct& InstancedStructValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to String", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FString AutoConvert_FlowDataPinPropertyStringToString(const FFlowDataPinOutputProperty_String& StringProperty) { return StringProperty.Value; } + // Set a single UObject* on an Object DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Object Value")) + static void SetObjectValue(UObject* InValue, UPARAM(Ref) FFlowDataPinValue_Object& ObjectValue) { ObjectValue.Values = { InValue }; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to String", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FString AutoConvert_FlowDataPinPropertyTextToString(const FFlowDataPinOutputProperty_Text& TextProperty) { return TextProperty.Value.ToString(); } + // Set a UObject* array on an Object DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Object Values")) + static void SetObjectValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Object& ObjectValue) { ObjectValue.Values = InValues; } - // to Text variants for all text-based types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Text", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FText AutoConvert_FlowDataPinPropertyNameToText(const FFlowDataPinOutputProperty_Name& NameProperty) { return FText::FromName(NameProperty.Value); } + // Get a single UObject* from an output Object DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Object Value")) + static UObject* GetObjectValue(UPARAM(Ref) const FFlowDataPinValue_Object& ObjectValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Text", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FText AutoConvert_FlowDataPinPropertyStringToText(const FFlowDataPinOutputProperty_String& StringProperty) { return FText::FromString(StringProperty.Value); } + // Get the full UObject* array from an output Object DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Object Values")) + static TArray GetObjectValues(UPARAM(Ref) FFlowDataPinValue_Object& ObjectValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Text", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FText AutoConvert_FlowDataPinPropertyTextToText(const FFlowDataPinOutputProperty_Text& TextProperty) { return TextProperty.Value; } + // Set a single FSoftClassPath on a Class DataPin Value (input or output pin). Replaces any existing values. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Class Value")) + static void SetClassValue(const FSoftClassPath& InValue, UPARAM(Ref) FFlowDataPinValue_Class& ClassValue) { ClassValue.Values = { InValue }; } - // Convert enum property values to their inner enum values - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Enum", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static uint8 AutoConvert_FlowDataPinPropertyEnumToEnum(const FFlowDataPinOutputProperty_Enum& EnumProperty); + // Set an FSoftClassPath array on a Class DataPin Value (input or output pin). Replaces the entire array. + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Set Class Values")) + static void SetClassValues(const TArray& InValues, UPARAM(Ref) FFlowDataPinValue_Class& ClassValue) { ClassValue.Values = InValues; } - // Convert vector property values to their inner Vector - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Vector", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FVector AutoConvert_FlowDataPinPropertyVectorToVector(const FFlowDataPinOutputProperty_Vector& VectorProperty) { return VectorProperty.Value; } + // Get a single FSoftClassPath from an output Class DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Class Value")) + static FSoftClassPath GetClassValue(UPARAM(Ref) const FFlowDataPinValue_Class& ClassValue, EFlowSingleFromArray SingleFromArray = EFlowSingleFromArray::LastValue); - // Convert Rotator property values to their inner Rotator - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Rotator", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FRotator AutoConvert_FlowDataPinPropertyRotatorToRotator(const FFlowDataPinOutputProperty_Rotator& RotatorProperty) { return RotatorProperty.Value; } + // Get the full FSoftClassPath array from an output Class DataPin Value. DO NOT use on input pins — will error in editor, use Resolve As... functions instead! + UFUNCTION(BlueprintCallable, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Get Class Values")) + static TArray GetClassValues(UPARAM(Ref) FFlowDataPinValue_Class& ClassValue); - // Convert Transform property values to their inner Transform - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Transform", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FTransform AutoConvert_FlowDataPinPropertyTransformToTransform(const FFlowDataPinOutputProperty_Transform& TransformProperty) { return TransformProperty.Value; } + // --------- Make Flow DataPin Result - for use in blueprint TrySupplyDataPin implementations ---------- + + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Empty(EFlowDataPinResolveResult Result) { return FFlowDataPinResult(Result); } - // Convert GameplayTag property values to their inner GameplayTag - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to GameplayTag", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FGameplayTag AutoConvert_FlowDataPinPropertyGameplayTagToGameplayTag(const FFlowDataPinOutputProperty_GameplayTag& GameplayTagProperty) { return GameplayTagProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Bool Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Bool(const TArray& BoolValues); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to GameplayTagContainer", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FGameplayTagContainer AutoConvert_FlowDataPinPropertyGameplayTagToGameplayTagContainer(const FFlowDataPinOutputProperty_GameplayTag& GameplayTagProperty) { return FGameplayTagContainer(GameplayTagProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Int Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Int(const TArray& IntValues); - // Convert GameplayTagContainer property values to their inner GameplayTagContainer - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to GameplayTagContainer", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FGameplayTagContainer AutoConvert_FlowDataPinPropertyGameplayTagContainerToGameplayTagContainer(const FFlowDataPinOutputProperty_GameplayTagContainer& GameplayTagContainerProperty) { return GameplayTagContainerProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Int64 Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Int64(const TArray& Int64Values); - // Convert InstancedStruct property values to their inner InstancedStruct - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to InstancedStruct", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FInstancedStruct AutoConvert_FlowDataPinPropertyInstancedStructToInstancedStruct(const FFlowDataPinOutputProperty_InstancedStruct& InstancedStructProperty) { return InstancedStructProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Float Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Float(const TArray& FloatValues); - // Convert Object property values to their inner Object - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Object", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static UObject* AutoConvert_FlowDataPinPropertyObjectToObject(const FFlowDataPinOutputProperty_Object& ObjectProperty) { return ObjectProperty.GetObjectValue(); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Double Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Double(const TArray& DoubleValues); - // Convert Class property values to their inner Class - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Class", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static UClass* AutoConvert_FlowDataPinPropertyClassToClass(const FFlowDataPinOutputProperty_Class& ClassProperty) { return ClassProperty.GetResolvedClass(); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Name Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Name(const TArray& NameValues); - // Convert Class property values to their FSoftClassPath - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to SoftClass", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FSoftClassPath AutoConvert_FlowDataPinPropertyClassToSoftClass(const FFlowDataPinOutputProperty_Class& ClassProperty) { return ClassProperty.GetAsSoftClass(); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make String Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_String(const TArray& StringValues); - // Recommend implementing AutoConvert_FlowDataPinResult... for every EFlowPinType - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Text Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Text(const TArray& TextValues); - // FFlowDataPinResults auto-cast functions + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Enum Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Enum(const TArray& EnumValues, UEnum* EnumClass); - // Convert bool property values to their inner blueprint values - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Bool", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static bool AutoConvert_FlowDataPinResultBoolToBool(const FFlowDataPinResult_Bool& BoolProperty) { return BoolProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Vector Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Vector(const TArray& VectorValues); - // to Int variants for all int types (that blueprint supports - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Int", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static int32 AutoConvert_FlowDataPinResultInt64ToInt32(const FFlowDataPinResult_Int& IntProperty) { /* possible loss of precision */ return static_cast(IntProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Rotator Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Rotator(const TArray& RotatorValues); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Int64", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static int64 AutoConvert_FlowDataPinResultInt64ToInt64(const FFlowDataPinResult_Int& IntProperty) { return IntProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Transform Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Transform(const TArray& TransformValues); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Int", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static uint8 AutoConvert_FlowDataPinResultInt64ToUint8(const FFlowDataPinResult_Int& IntProperty) { /* possible loss of precision */ return static_cast(IntProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make GameplayTag Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_GameplayTag(const TArray& GameplayTagValues); - // to Float variants for all float types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Float", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static float AutoConvert_FlowDataPinResultFloat64ToFloat32(const FFlowDataPinResult_Float& FloatProperty) { /* possible loss of precision */ return static_cast(FloatProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make GameplayTagContainer Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_GameplayTagContainer(FGameplayTagContainer GameplayTagContainerValue); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Double", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static double AutoConvert_FlowDataPinResultFloat64ToFloat64(const FFlowDataPinResult_Float& FloatProperty) { return FloatProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make InstancedStruct Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_InstancedStruct(const TArray& InstancedStructValues); - // to Name variants for all text-based types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Name", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FName AutoConvert_FlowDataPinResultNameToName(const FFlowDataPinResult_Name& NameProperty) { return NameProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Object Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Object(const TArray& ObjectValues); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Name", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FName AutoConvert_FlowDataPinResultStringToName(const FFlowDataPinResult_String& StringProperty) { return FName(StringProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, meta = (DisplayName = "Make Class Flow DataPin Result")) + static FFlowDataPinResult MakeFlowDataPinResult_Class(const TArray& ClassValues); - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Name", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FName AutoConvert_FlowDataPinResultTextToName(const FFlowDataPinResult_Text& TextProperty) { return FName(TextProperty.Value.ToString()); } + // ---------- Override the Make functions to discourage use ---------- + // Ideally, we would forbid Make altogether, but this is the best work-around I have found. - // to String variants for all text-based types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to String", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FString AutoConvert_FlowDataPinResultNameToString(const FFlowDataPinResult_Name& NameProperty) { return NameProperty.Value.ToString(); } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Bool Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetBoolValue(s) instead")) + static FFlowDataPinValue_Bool MakeStructBool(const FFlowDataPinValue_Bool& OtherValueStruct) { return OtherValueStruct; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to String", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FString AutoConvert_FlowDataPinResultStringToString(const FFlowDataPinResult_String& StringProperty) { return StringProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Int Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetIntValue(s) instead")) + static FFlowDataPinValue_Int MakeStructInt(const FFlowDataPinValue_Int& OtherValueStruct) { return OtherValueStruct; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to String", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FString AutoConvert_FlowDataPinResultTextToString(const FFlowDataPinResult_Text& TextProperty) { return TextProperty.Value.ToString(); } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Int64 Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetInt64Value(s) instead")) + static FFlowDataPinValue_Int64 MakeStructInt64(const FFlowDataPinValue_Int64& OtherValueStruct) { return OtherValueStruct; } - // to Text variants for all text-based types - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Text", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FText AutoConvert_FlowDataPinResultNameToText(const FFlowDataPinResult_Name& NameProperty) { return FText::FromName(NameProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Float Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetFloatValue(s) instead")) + static FFlowDataPinValue_Float MakeStructFloat(const FFlowDataPinValue_Float& OtherValueStruct) { return OtherValueStruct; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Text", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FText AutoConvert_FlowDataPinResultStringToText(const FFlowDataPinResult_String& StringProperty) { return FText::FromString(StringProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Double Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetDoubleValue(s) instead")) + static FFlowDataPinValue_Double MakeStructDouble(const FFlowDataPinValue_Double& OtherValueStruct) { return OtherValueStruct; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Text", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FText AutoConvert_FlowDataPinResultTextToText(const FFlowDataPinResult_Text& TextProperty) { return TextProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Name Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetNameValue(s) instead")) + static FFlowDataPinValue_Name MakeStructName(const FFlowDataPinValue_Name& OtherValueStruct) { return OtherValueStruct; } - // Convert enum property values to their inner enum values - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Enum", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static uint8 AutoConvert_FlowDataPinResultEnumToEnum(const FFlowDataPinResult_Enum& EnumProperty); + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make String Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetStringValue(s) instead")) + static FFlowDataPinValue_String MakeStructString(const FFlowDataPinValue_String& OtherValueStruct) { return OtherValueStruct; } - // Convert vector property values to their inner Vector - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Vector", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FVector AutoConvert_FlowDataPinResultVectorToVector(const FFlowDataPinResult_Vector& VectorProperty) { return VectorProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Text Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetTextValue(s) instead")) + static FFlowDataPinValue_Text MakeStructText(const FFlowDataPinValue_Text& OtherValueStruct) { return OtherValueStruct; } - // Convert Rotator property values to their inner Rotator - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Rotator", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FRotator AutoConvert_FlowDataPinResultRotatorToRotator(const FFlowDataPinResult_Rotator& RotatorProperty) { return RotatorProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Enum Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetEnumValue(s) instead")) + static FFlowDataPinValue_Enum MakeStructEnum(const FFlowDataPinValue_Enum& OtherValueStruct) { return OtherValueStruct; } - // Convert Transform property values to their inner Transform - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Transform", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FTransform AutoConvert_FlowDataPinResultTransformToTransform(const FFlowDataPinResult_Transform& TransformProperty) { return TransformProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Vector Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetVectorValue(s) instead")) + static FFlowDataPinValue_Vector MakeStructVector(const FFlowDataPinValue_Vector& OtherValueStruct) { return OtherValueStruct; } - // Convert GameplayTag property values to their inner GameplayTag - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to GameplayTag", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FGameplayTag AutoConvert_FlowDataPinResultGameplayTagToGameplayTag(const FFlowDataPinResult_GameplayTag& GameplayTagProperty) { return GameplayTagProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Rotator Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetRotatorValue(s) instead")) + static FFlowDataPinValue_Rotator MakeStructRotator(const FFlowDataPinValue_Rotator& OtherValueStruct) { return OtherValueStruct; } - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to GameplayTagContainer", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FGameplayTagContainer AutoConvert_FlowDataPinResultGameplayTagToGameplayTagContainer(const FFlowDataPinResult_GameplayTag& GameplayTagProperty) { return FGameplayTagContainer(GameplayTagProperty.Value); } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Transform Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetTransformValue(s) instead")) + static FFlowDataPinValue_Transform MakeStructTransform(const FFlowDataPinValue_Transform& OtherValueStruct) { return OtherValueStruct; } - // Convert GameplayTagContainer property values to their inner GameplayTagContainer - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to GameplayTagContainer", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FGameplayTagContainer AutoConvert_FlowDataPinResultGameplayTagContainerToGameplayTagContainer(const FFlowDataPinResult_GameplayTagContainer& GameplayTagContainerProperty) { return GameplayTagContainerProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make GameplayTag Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetGameplayTagValue(s) instead")) + static FFlowDataPinValue_GameplayTag MakeStructGameplayTag(const FFlowDataPinValue_GameplayTag& OtherValueStruct) { return OtherValueStruct; } - // Convert InstancedStruct property values to their inner InstancedStruct - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to InstancedStruct", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FInstancedStruct AutoConvert_FlowDataPinResultInstancedStructToInstancedStruct(const FFlowDataPinResult_InstancedStruct& InstancedStructProperty) { return InstancedStructProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make GameplayTagContainer Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetGameplayTagContainerValue(s) instead")) + static FFlowDataPinValue_GameplayTagContainer MakeStructGameplayTagContainer(const FFlowDataPinValue_GameplayTagContainer& OtherValueStruct) { return OtherValueStruct; } - // Convert Object property values to their inner Object - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Object", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static UObject* AutoConvert_FlowDataPinResultObjectToObject(const FFlowDataPinResult_Object& ObjectProperty) { return ObjectProperty.Value; } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make InstancedStruct Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetInstancedStructValue(s) instead")) + static FFlowDataPinValue_InstancedStruct MakeStructInstancedStruct(const FFlowDataPinValue_InstancedStruct& OtherValueStruct) { return OtherValueStruct; } - // Convert Class property values to their inner Class - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to Class", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static UClass* AutoConvert_FlowDataPinResultClassToClass(const FFlowDataPinResult_Class& ClassProperty) { return ClassProperty.GetOrResolveClass(); } + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Object Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetObjectValue(s) instead")) + static FFlowDataPinValue_Object MakeStructObject(const FFlowDataPinValue_Object& OtherValueStruct) { return OtherValueStruct; } - // Convert Class property values to their the FSoftClassPath - UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert to SoftClass", CompactNodeTitle = "->", BlueprintAutocast), Category = DataPins) - static FSoftClassPath AutoConvert_FlowDataPinResultClassToSoftClass(const FFlowDataPinResult_Class& ClassProperty) { return ClassProperty.GetAsSoftClass(); } -}; \ No newline at end of file + UFUNCTION(BlueprintPure, Category = DataPins, Meta = (BlueprintThreadSafe, DisplayName = "Make Class Flow DataPin Value", DeprecatedFunction, DeprecationMessage = "use SetClassValue(s) instead")) + static FFlowDataPinValue_Class MakeStructClass(const FFlowDataPinValue_Class& OtherValueStruct) { return OtherValueStruct; } +}; diff --git a/Source/Flow/Public/Types/FlowDataPinProperties.h b/Source/Flow/Public/Types/FlowDataPinProperties.h index a40a0e24f..a2780a353 100644 --- a/Source/Flow/Public/Types/FlowDataPinProperties.h +++ b/Source/Flow/Public/Types/FlowDataPinProperties.h @@ -3,60 +3,32 @@ #pragma once #include "GameplayTagContainer.h" +#include "Kismet/BlueprintFunctionLibrary.h" #include "StructUtils/InstancedStruct.h" #include "UObject/Class.h" #include "Nodes/FlowPin.h" +#include "Types/FlowPinTypeName.h" +#include "Types/FlowPinTypesStandard.h" + #include "FlowDataPinProperties.generated.h" class FStructProperty; class UScriptStruct; -USTRUCT(BlueprintType, DisplayName = "Base - Flow DataPin Property") +// #FlowDataPinLegacy +USTRUCT(DisplayName = "Base - Flow DataPin Property", meta = (Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinProperty { GENERATED_BODY() - FFlowDataPinProperty() { } + FFlowDataPinProperty() = default; virtual ~FFlowDataPinProperty() { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const { return EFlowPinType::Invalid; } - FLOW_API virtual bool IsInputProperty() const { return false; } - -#if WITH_EDITOR - FLOW_API static FFlowPin CreateFlowPin(const FName& PinName, const TInstancedStruct& DataPinProperty); - - template - static UScriptStruct* FindScriptStructForFlowDataPinProperty(const FProperty& Property) - { - // Find the ScriptStruct of the wrapped struct in a wrapper (eg, FFlowDataPinOutputProperty_Vector) or the struct itself (eg, FVector) - const FStructProperty* StructProperty = CastField(&Property); - if (!StructProperty) - { - return nullptr; - } - - UScriptStruct* ScriptStruct = TFlowDataPinPropertyType::StaticStruct(); - if (StructProperty->Struct == ScriptStruct) - { - static UScriptStruct* UnrealType = TBaseStructure::Get(); - - return UnrealType; - } - else - { - return StructProperty->Struct; - } - } -#endif }; -// Recommend implementing FFlowDataPinProperty... for every EFlowPinType -FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - // Wrapper struct for a bool that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Bool - Output Flow Data Pin Property", meta = (FlowPinType = "Bool")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Bool - Output Flow Data Pin Property", meta = (FlowPinType = "Bool", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Bool : public FFlowDataPinProperty { GENERATED_BODY() @@ -70,12 +42,10 @@ struct FFlowDataPinOutputProperty_Bool : public FFlowDataPinProperty FFlowDataPinOutputProperty_Bool() { } FFlowDataPinOutputProperty_Bool(bool InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Bool; } }; -// Wrapper struct for a int64 that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Int64 - Output Flow Data Pin Property", meta = (FlowPinType = "Int")) +// Wrapper struct for an int64 that will generate and link to a Data Pin with its same name +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Int64 - Output Flow Data Pin Property", meta = (FlowPinType = "Int64", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Int64 : public FFlowDataPinProperty { GENERATED_BODY() @@ -89,12 +59,10 @@ struct FFlowDataPinOutputProperty_Int64 : public FFlowDataPinProperty FFlowDataPinOutputProperty_Int64() { } FFlowDataPinOutputProperty_Int64(int64 InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Int; } }; -// Wrapper struct for a int32 that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Int - Output Flow Data Pin Property", meta = (FlowPinType = "Int")) +// Wrapper struct for an int32 that will generate and link to a Data Pin with its same name +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Int - Output Flow Data Pin Property", meta = (FlowPinType = "Int", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Int32 : public FFlowDataPinProperty { GENERATED_BODY() @@ -108,12 +76,10 @@ struct FFlowDataPinOutputProperty_Int32 : public FFlowDataPinProperty FFlowDataPinOutputProperty_Int32() { } FFlowDataPinOutputProperty_Int32(int32 InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Int; } }; // Wrapper struct for a Double (64bit float) that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Double (float64) - Output Flow Data Pin Property", meta = (FlowPinType = "Float")) +USTRUCT(BlueprintType, DisplayName = "Double (float64) - Output Flow Data Pin Property", meta = (FlowPinType = "Double", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Double : public FFlowDataPinProperty { GENERATED_BODY() @@ -127,12 +93,10 @@ struct FFlowDataPinOutputProperty_Double : public FFlowDataPinProperty FFlowDataPinOutputProperty_Double() { } FFlowDataPinOutputProperty_Double(double InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Float; } }; // Wrapper struct for a Float (32bit) that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Float - Output Flow Data Pin Property", meta = (FlowPinType = "Float")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Float - Output Flow Data Pin Property", meta = (FlowPinType = "Float", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Float : public FFlowDataPinProperty { GENERATED_BODY() @@ -146,12 +110,10 @@ struct FFlowDataPinOutputProperty_Float : public FFlowDataPinProperty FFlowDataPinOutputProperty_Float() { } FFlowDataPinOutputProperty_Float(float InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Float; } }; // Wrapper struct for a FName that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Name - Output Flow Data Pin Property", meta = (FlowPinType = "Name")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Name - Output Flow Data Pin Property", meta = (FlowPinType = "Name", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Name : public FFlowDataPinProperty { GENERATED_BODY() @@ -165,12 +127,10 @@ struct FFlowDataPinOutputProperty_Name : public FFlowDataPinProperty FFlowDataPinOutputProperty_Name() { } FFlowDataPinOutputProperty_Name(const FName& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Name; } }; // Wrapper struct for a FString that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "String - Output Flow Data Pin Property", meta = (FlowPinType = "String")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] String - Output Flow Data Pin Property", meta = (FlowPinType = "String", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_String : public FFlowDataPinProperty { GENERATED_BODY() @@ -184,12 +144,10 @@ struct FFlowDataPinOutputProperty_String : public FFlowDataPinProperty FFlowDataPinOutputProperty_String() { } FFlowDataPinOutputProperty_String(const FString& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::String; } }; // Wrapper struct for a FText that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Text - Output Flow Data Pin Property", meta = (FlowPinType = "Text")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Text - Output Flow Data Pin Property", meta = (FlowPinType = "Text", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Text : public FFlowDataPinProperty { GENERATED_BODY() @@ -203,12 +161,10 @@ struct FFlowDataPinOutputProperty_Text : public FFlowDataPinProperty FFlowDataPinOutputProperty_Text() { } FFlowDataPinOutputProperty_Text(const FText& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Text; } }; // Wrapper struct for an enum that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Enum - Output Flow Data Pin Property", meta = (FlowPinType = "Enum")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Enum - Output Flow Data Pin Property", meta = (FlowPinType = "Enum", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Enum : public FFlowDataPinProperty { GENERATED_BODY() @@ -238,17 +194,12 @@ struct FFlowDataPinOutputProperty_Enum : public FFlowDataPinProperty FFlowDataPinOutputProperty_Enum(const FName& InValue, UEnum* InEnumClass) : Value(InValue) , EnumClass(InEnumClass) - { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Enum; } - -#if WITH_EDITOR - FLOW_API void OnEnumNameChanged(); -#endif // WITH_EDITOR + { + } }; // Wrapper struct for a FVector that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Vector - Output Flow Data Pin Property", meta = (FlowPinType = "Vector")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Vector - Output Flow Data Pin Property", meta = (FlowPinType = "Vector", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Vector : public FFlowDataPinProperty { GENERATED_BODY() @@ -262,12 +213,10 @@ struct FFlowDataPinOutputProperty_Vector : public FFlowDataPinProperty FFlowDataPinOutputProperty_Vector() {} FFlowDataPinOutputProperty_Vector(const FVector& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Vector; } }; // Wrapper struct for a FRotator that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Rotator - Output Flow Data Pin Property", meta = (FlowPinType = "Rotator")) +USTRUCT(BlueprintType, DisplayName = "Rotator - Output Flow Data Pin Property", meta = (FlowPinType = "Rotator", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Rotator : public FFlowDataPinProperty { GENERATED_BODY() @@ -281,12 +230,10 @@ struct FFlowDataPinOutputProperty_Rotator : public FFlowDataPinProperty FFlowDataPinOutputProperty_Rotator() {} FFlowDataPinOutputProperty_Rotator(const FRotator& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Rotator; } }; // Wrapper struct for a FTransform that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Transform - Output Flow Data Pin Property", meta = (FlowPinType = "Transform")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Transform - Output Flow Data Pin Property", meta = (FlowPinType = "Transform", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Transform : public FFlowDataPinProperty { GENERATED_BODY() @@ -300,12 +247,10 @@ struct FFlowDataPinOutputProperty_Transform : public FFlowDataPinProperty FFlowDataPinOutputProperty_Transform() {} FFlowDataPinOutputProperty_Transform(const FTransform& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Transform; } }; // Wrapper struct for a FGameplayTag that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "GameplayTag - Output Flow Data Pin Property", meta = (FlowPinType = "GameplayTag")) +USTRUCT(BlueprintType, DisplayName = "GameplayTag - Output Flow Data Pin Property", meta = (FlowPinType = "GameplayTag", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_GameplayTag : public FFlowDataPinProperty { GENERATED_BODY() @@ -319,12 +264,10 @@ struct FFlowDataPinOutputProperty_GameplayTag : public FFlowDataPinProperty FFlowDataPinOutputProperty_GameplayTag() {} FFlowDataPinOutputProperty_GameplayTag(const FGameplayTag& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::GameplayTag; } }; // Wrapper struct for a FGameplayTagContainer that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "GameplayTagContainer - Output Flow DataPin Property", meta = (FlowPinType = "GameplayTagContainer")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] GameplayTagContainer - Output Flow DataPin Property", meta = (FlowPinType = "GameplayTagContainer", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_GameplayTagContainer : public FFlowDataPinProperty { GENERATED_BODY() @@ -338,12 +281,10 @@ struct FFlowDataPinOutputProperty_GameplayTagContainer : public FFlowDataPinProp FFlowDataPinOutputProperty_GameplayTagContainer() {} FFlowDataPinOutputProperty_GameplayTagContainer(const FGameplayTagContainer& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::GameplayTagContainer; } }; // Wrapper struct for a FInstancedStruct that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "InstancedStruct - Output Flow DataPin Property", meta = (FlowPinType = "InstancedStruct")) +USTRUCT(BlueprintType, DisplayName = "InstancedStruct - Output Flow DataPin Property", meta = (FlowPinType = "InstancedStruct", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_InstancedStruct : public FFlowDataPinProperty { GENERATED_BODY() @@ -357,19 +298,17 @@ struct FFlowDataPinOutputProperty_InstancedStruct : public FFlowDataPinProperty FFlowDataPinOutputProperty_InstancedStruct() {} FFlowDataPinOutputProperty_InstancedStruct(const FInstancedStruct& InValue) : Value(InValue) { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::InstancedStruct; } }; // Wrapper struct for a UObject that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Object - Output Flow DataPin Property", meta = (FlowPinType = "Object")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Object - Output Flow DataPin Property", meta = (FlowPinType = "Object", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Object : public FFlowDataPinProperty { GENERATED_BODY() friend class FFlowDataPinProperty_ObjectCustomizationBase; -protected: +public: // These pointers are separate so that the default value for the object can be configured // in the editor according to the type of object that it is (instanced or not). @@ -382,8 +321,6 @@ struct FFlowDataPinOutputProperty_Object : public FFlowDataPinProperty UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = DataPins, DisplayName = "Value (inline)", meta = (EditCondition = "ReferenceValue == nullptr")) TObjectPtr InlineValue = nullptr; -public: - #if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract)) TObjectPtr ClassFilter = UObject::StaticClass(); @@ -394,32 +331,22 @@ struct FFlowDataPinOutputProperty_Object : public FFlowDataPinProperty FFlowDataPinOutputProperty_Object() {} FLOW_API FFlowDataPinOutputProperty_Object(UObject* InValue, UClass* InClassFilter = nullptr); - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Object; } - UObject* GetObjectValue() const { return ReferenceValue ? ReferenceValue : InlineValue; } - void SetObjectValue(UObject* InValue); - -#if WITH_EDITOR - UClass* DeriveObjectClass(const FProperty& MetaDataProperty) const; - FLOW_API static UClass* TryGetObjectClassFromProperty(const FProperty& MetaDataProperty); -#endif }; // Wrapper struct for a UClass that will generate and link to a Data Pin with its same name -USTRUCT(BlueprintType, DisplayName = "Class - Output Flow DataPin Property", meta = (FlowPinType = "Class")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Class - Output Flow DataPin Property", meta = (FlowPinType = "Class", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinOutputProperty_Class : public FFlowDataPinProperty { GENERATED_BODY() friend class FFlowDataPinProperty_ClassCustomizationBase; -protected: +public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) FSoftClassPath Value; -public: - #if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract)) TObjectPtr ClassFilter = UObject::StaticClass(); @@ -433,236 +360,160 @@ struct FFlowDataPinOutputProperty_Class : public FFlowDataPinProperty #if WITH_EDITOR , ClassFilter(InClassFilter) #endif - { } - - FLOW_API virtual EFlowPinType GetFlowPinType() const override { return EFlowPinType::Class; } - -#if WITH_EDITOR - UClass* DeriveMetaClass(const FProperty& MetaDataProperty) const; - FLOW_API static UClass* TryGetMetaClassFromProperty(const FProperty& MetaDataProperty); -#endif - - const FSoftClassPath& GetAsSoftClass() const { return Value; } - 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 + UClass* GetObjectValue() const { return Value.ResolveClass(); } }; // 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) +// "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) -USTRUCT(BlueprintType, DisplayName = "Bool - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Bool")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Bool - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Bool", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Bool : public FFlowDataPinOutputProperty_Bool { GENERATED_BODY() FFlowDataPinInputProperty_Bool(bool InValue = false) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Int64 - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Int")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Int64 - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Int64", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Int64 : public FFlowDataPinOutputProperty_Int64 { GENERATED_BODY() FFlowDataPinInputProperty_Int64(int64 InValue = 0) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Int - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Int")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Int - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Int", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Int32 : public FFlowDataPinOutputProperty_Int32 { GENERATED_BODY() FFlowDataPinInputProperty_Int32(int32 InValue = 0) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Double (float64) - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Float")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Double (float64) - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Double", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Double : public FFlowDataPinOutputProperty_Double { GENERATED_BODY() FFlowDataPinInputProperty_Double(double InValue = 0.0) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Float - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Float")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Float - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Float", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Float : public FFlowDataPinOutputProperty_Float { GENERATED_BODY() FFlowDataPinInputProperty_Float(float InValue = 0.0f) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Name - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Name")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Name - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Name", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Name : public FFlowDataPinOutputProperty_Name { GENERATED_BODY() FFlowDataPinInputProperty_Name() : Super() { } FFlowDataPinInputProperty_Name(const FName& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "String - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "String")) +USTRUCT(BlueprintType, DisplayName = "String - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "String", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_String : public FFlowDataPinOutputProperty_String { GENERATED_BODY() FFlowDataPinInputProperty_String() : Super() { } FFlowDataPinInputProperty_String(const FString& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Text - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Text")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Text - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Text", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Text : public FFlowDataPinOutputProperty_Text { GENERATED_BODY() FFlowDataPinInputProperty_Text() : Super() { } FFlowDataPinInputProperty_Text(const FText& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Enum - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Enum")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Enum - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Enum", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Enum : public FFlowDataPinOutputProperty_Enum { GENERATED_BODY() FFlowDataPinInputProperty_Enum() : Super() { } FFlowDataPinInputProperty_Enum(const FName& InValue, UEnum* InEnumClass) : Super(InValue, InEnumClass) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Vector - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Vector")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Vector - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Vector", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Vector : public FFlowDataPinOutputProperty_Vector { GENERATED_BODY() FFlowDataPinInputProperty_Vector() : Super() { } FFlowDataPinInputProperty_Vector(const FVector& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Rotator - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Rotator")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Rotator - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Rotator", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Rotator : public FFlowDataPinOutputProperty_Rotator { GENERATED_BODY() FFlowDataPinInputProperty_Rotator() : Super() { } FFlowDataPinInputProperty_Rotator(const FRotator& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Transform - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Transform")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Transform - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Transform", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Transform : public FFlowDataPinOutputProperty_Transform { GENERATED_BODY() FFlowDataPinInputProperty_Transform() : Super() { } FFlowDataPinInputProperty_Transform(const FTransform& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "GameplayTag - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "GameplayTag")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] GameplayTag - Input Flow Data Pin Property", meta = (DefaultForInputFlowPin, FlowPinType = "GameplayTag", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_GameplayTag : public FFlowDataPinOutputProperty_GameplayTag { GENERATED_BODY() FFlowDataPinInputProperty_GameplayTag() : Super() { } FFlowDataPinInputProperty_GameplayTag(const FGameplayTag& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "GameplayTagContainer - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "GameplayTagContainer")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] GameplayTagContainer - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "GameplayTagContainer", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_GameplayTagContainer : public FFlowDataPinOutputProperty_GameplayTagContainer { GENERATED_BODY() FFlowDataPinInputProperty_GameplayTagContainer() : Super() { } FFlowDataPinInputProperty_GameplayTagContainer(const FGameplayTagContainer& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "InstancedStruct - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "InstancedStruct")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] InstancedStruct - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "InstancedStruct", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_InstancedStruct : public FFlowDataPinOutputProperty_InstancedStruct { GENERATED_BODY() FFlowDataPinInputProperty_InstancedStruct() : Super() { } FFlowDataPinInputProperty_InstancedStruct(const FInstancedStruct& InValue) : Super(InValue) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Object - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Object")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Object - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Object", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Object : public FFlowDataPinOutputProperty_Object { GENERATED_BODY() FFlowDataPinInputProperty_Object() : Super() { } FFlowDataPinInputProperty_Object(UObject* InValue, UClass* InClassFilter) : Super(InValue, InClassFilter) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; -USTRUCT(BlueprintType, DisplayName = "Class - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Class")) +USTRUCT(BlueprintType, DisplayName = "[DEPRECATED] Class - Input Flow DataPin Property", meta = (DefaultForInputFlowPin, FlowPinType = "Class", Deprecated, DeprecationMessage = "Use FFlowDataPinValue* instead")) struct FFlowDataPinInputProperty_Class : public FFlowDataPinOutputProperty_Class { GENERATED_BODY() FFlowDataPinInputProperty_Class() : Super() { } FFlowDataPinInputProperty_Class(const FSoftClassPath& InValue, UClass* InClassFilter) : Super(InValue, InClassFilter) { } - - FLOW_API virtual bool IsInputProperty() const override { return true; } }; - diff --git a/Source/Flow/Public/Types/FlowDataPinPropertyToValueMigration.h b/Source/Flow/Public/Types/FlowDataPinPropertyToValueMigration.h new file mode 100644 index 000000000..4233d2d83 --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinPropertyToValueMigration.h @@ -0,0 +1,409 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowDataPinValuesStandard.h" + +// #FlowDataPinLegacy +#include "Types/FlowDataPinProperties.h" +// -- + +// #FlowDataPinLegacy + +// Templated helper to migrate simple types (scalar Value to TArray Values) +template +static bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const SourceType* SourceData = Source.GetPtr(); + Target.InitializeAsScriptStruct(TargetType::StaticStruct()); + TargetType* TargetData = Target.GetMutablePtr(); + if (TargetData) + { + TargetData->Values.Add(SourceData->Value); + return true; + } + return false; +} + +// Specialization for FGameplayTagContainer +template <> +bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinOutputProperty_GameplayTagContainer* SourceData = Source.GetPtr(); + Target.InitializeAsScriptStruct(FFlowDataPinValue_GameplayTagContainer::StaticStruct()); + FFlowDataPinValue_GameplayTagContainer* TargetData = Target.GetMutablePtr(); + if (TargetData) + { + TargetData->Values.AppendTags(SourceData->Value); + return true; + } + return false; +} + +// Specialization for FGameplayTagContainer (Input) +template <> +bool MigrateSimpleType< + FFlowDataPinInputProperty_GameplayTagContainer, + FFlowDataPinValue_GameplayTagContainer, + FGameplayTagContainer>( + const TInstancedStruct& Source, + TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinInputProperty_GameplayTagContainer* SourceData = + Source.GetPtr(); + + Target.InitializeAsScriptStruct(FFlowDataPinValue_GameplayTagContainer::StaticStruct()); + FFlowDataPinValue_GameplayTagContainer* TargetData = + Target.GetMutablePtr(); + + if (TargetData) + { + TargetData->Values.AppendTags(SourceData->Value); + return true; + } + return false; +} + +// Specialization for Enum (handles Value and EnumClass) +template <> +bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinOutputProperty_Enum* SourceData = Source.GetPtr(); + Target.InitializeAsScriptStruct(FFlowDataPinValue_Enum::StaticStruct()); + FFlowDataPinValue_Enum* TargetData = Target.GetMutablePtr(); + if (TargetData) + { + TargetData->Values.Add(SourceData->Value); + TargetData->EnumClass = SourceData->EnumClass; +#if WITH_EDITORONLY_DATA + TargetData->EnumName = SourceData->EnumName; +#endif + return true; + } + return false; +} + +// Specialization for Enum (Input) +template <> +bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinInputProperty_Enum* SourceData = Source.GetPtr(); + Target.InitializeAsScriptStruct(FFlowDataPinValue_Enum::StaticStruct()); + FFlowDataPinValue_Enum* TargetData = Target.GetMutablePtr(); + if (TargetData) + { + TargetData->Values.Add(SourceData->Value); + TargetData->EnumClass = SourceData->EnumClass; +#if WITH_EDITORONLY_DATA + TargetData->EnumName = SourceData->EnumName; +#endif + return true; + } + return false; +} + +// Specialization for Object (handles ReferenceValue/InlineValue and ClassFilter) +template <> +bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinOutputProperty_Object* SourceData = Source.GetPtr(); + UScriptStruct* TargetStruct = FFlowDataPinValue_Object::StaticStruct(); + Target.InitializeAsScriptStruct(TargetStruct); + + { + FFlowDataPinValue_Object* TargetData = Target.GetMutablePtr(); + if (TargetData && SourceData->ReferenceValue) + { + TargetData->Values.Add(SourceData->ReferenceValue); +#if WITH_EDITORONLY_DATA + TargetData->ClassFilter = SourceData->ClassFilter; +#endif + return true; + } + } + return false; +} + +// Specialization for Object (Input) +template <> +bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinInputProperty_Object* SourceData = Source.GetPtr(); + UScriptStruct* TargetStruct = FFlowDataPinValue_Object::StaticStruct(); + Target.InitializeAsScriptStruct(TargetStruct); + + { + FFlowDataPinValue_Object* TargetData = Target.GetMutablePtr(); + if (TargetData && SourceData->ReferenceValue) + { + TargetData->Values.Add(SourceData->ReferenceValue); +#if WITH_EDITORONLY_DATA + TargetData->ClassFilter = SourceData->ClassFilter; +#endif + return true; + } + } + return false; +} + +// Specialization for Class (handles Value and ClassFilter) +template <> +bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinOutputProperty_Class* SourceData = Source.GetPtr(); + Target.InitializeAsScriptStruct(FFlowDataPinValue_Class::StaticStruct()); + FFlowDataPinValue_Class* TargetData = Target.GetMutablePtr(); + if (TargetData) + { + TargetData->Values.Add(SourceData->Value); +#if WITH_EDITORONLY_DATA + TargetData->ClassFilter = SourceData->ClassFilter; +#endif + return true; + } + return false; +} + +// Specialization for Class (Input) +template <> +bool MigrateSimpleType(const TInstancedStruct& Source, TInstancedStruct& Target) +{ + if (!Source.IsValid() || !Source.GetPtr()) + { + return false; + } + + const FFlowDataPinInputProperty_Class* SourceData = Source.GetPtr(); + Target.InitializeAsScriptStruct(FFlowDataPinValue_Class::StaticStruct()); + FFlowDataPinValue_Class* TargetData = Target.GetMutablePtr(); + if (TargetData) + { + TargetData->Values.Add(SourceData->Value); +#if WITH_EDITORONLY_DATA + TargetData->ClassFilter = SourceData->ClassFilter; +#endif + return true; + } + return false; +} + +bool FFlowNamedDataPinProperty::FixupDataPinProperty() +{ + // Skip if no data to migrate or target already has data + if (!DataPinProperty.IsValid() || DataPinValue.IsValid()) + { + DataPinProperty.Reset(); + return false; + } + + // Get source struct type + const UScriptStruct* SourceStruct = DataPinProperty.GetScriptStruct(); + if (!SourceStruct || !SourceStruct->IsChildOf(FFlowDataPinProperty::StaticStruct())) + { + DataPinProperty.Reset(); + return false; + } + + // Map source struct to target struct and migrate data + bool bSuccess = false; + + // Bool (Output and Input) + if (SourceStruct == FFlowDataPinOutputProperty_Bool::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Bool::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Int32 (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Int32::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Int32::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Int64 (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Int64::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Int64::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Float (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Float::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Float::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Double (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Double::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Double::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Name (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Name::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Name::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // String (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_String::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_String::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Text (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Text::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Text::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Enum (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Enum::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Enum::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Vector (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Vector::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Vector::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Rotator (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Rotator::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Rotator::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Transform (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Transform::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Transform::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // GameplayTag (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_GameplayTag::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_GameplayTag::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // GameplayTagContainer (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_GameplayTagContainer::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_GameplayTagContainer::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // InstancedStruct (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_InstancedStruct::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_InstancedStruct::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Object (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Object::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Object::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + // Class (Output and Input) + else if (SourceStruct == FFlowDataPinOutputProperty_Class::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + else if (SourceStruct == FFlowDataPinInputProperty_Class::StaticStruct()) + { + bSuccess = MigrateSimpleType(DataPinProperty, DataPinValue); + } + + // Clear the deprecated property + DataPinProperty.Reset(); + + return bSuccess; +} +// -- diff --git a/Source/Flow/Public/Types/FlowDataPinResults.h b/Source/Flow/Public/Types/FlowDataPinResults.h index d186d6ea9..9b442604d 100644 --- a/Source/Flow/Public/Types/FlowDataPinResults.h +++ b/Source/Flow/Public/Types/FlowDataPinResults.h @@ -9,8 +9,12 @@ #include "FlowDataPinResults.generated.h" struct FInstancedStruct; +struct FFlowDataPinValue; + +// #FlowDataPinLegacy struct FFlowDataPinOutputProperty_Object; struct FFlowDataPinOutputProperty_Class; +// -- USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result") struct FFlowDataPinResult @@ -20,25 +24,31 @@ struct FFlowDataPinResult public: // Result for the DataPin resolve attempt - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) EFlowDataPinResolveResult Result = EFlowDataPinResolveResult::FailedUnimplemented; public: - FLOW_API FFlowDataPinResult() { } + FLOW_API explicit FFlowDataPinResult() = default; FLOW_API explicit FFlowDataPinResult(EFlowDataPinResolveResult InResult) : Result(InResult) { } + + template + explicit FFlowDataPinResult(const TFlowDataPinValueSubclass& InValue) : Result(EFlowDataPinResolveResult::Success), ResultValue(TInstancedStruct::Make(InValue)) {} + +public: + UPROPERTY() + TInstancedStruct ResultValue; }; -// Recommend implementing FFlowDataPinResult... for every EFlowPinType -FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); +// #FlowDataPinLegacy -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Bool)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Bool)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Bool : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) bool Value = false; public: @@ -51,14 +61,14 @@ struct FFlowDataPinResult_Bool : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Int)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Int)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Int : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) int64 Value = 0; public: @@ -71,14 +81,14 @@ struct FFlowDataPinResult_Int : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Float)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Float)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Float : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) double Value = 0; public: @@ -91,14 +101,14 @@ struct FFlowDataPinResult_Float : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Name)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Name)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Name : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FName Value = NAME_None; public: @@ -115,14 +125,14 @@ struct FFlowDataPinResult_Name : public FFlowDataPinResult FLOW_API void SetValue(const FText& FromText) { Value = FName(FromText.ToString()); } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (String)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (String)", meta = (DeprecatedClass)) struct FFlowDataPinResult_String : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FString Value; public: @@ -139,14 +149,14 @@ struct FFlowDataPinResult_String : public FFlowDataPinResult FLOW_API void SetValue(const FText& FromText) { Value = FromText.ToString(); } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Text)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Text)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Text : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FText Value; public: @@ -163,7 +173,7 @@ struct FFlowDataPinResult_Text : public FFlowDataPinResult FLOW_API void SetValue(const FText& FromText) { Value = FromText; } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Enum)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Enum)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Enum : public FFlowDataPinResult { GENERATED_BODY() @@ -171,11 +181,11 @@ struct FFlowDataPinResult_Enum : public FFlowDataPinResult public: // The selected enum Value - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FName Value = NAME_None; // Class for this enum - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) TObjectPtr EnumClass = nullptr; public: @@ -238,14 +248,14 @@ struct FFlowDataPinResult_Enum : public FFlowDataPinResult } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Vector)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Vector)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Vector : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FVector Value = FVector::ZeroVector; public: @@ -258,14 +268,14 @@ struct FFlowDataPinResult_Vector : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Rotator)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Rotator)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Rotator : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FRotator Value = FRotator::ZeroRotator; public: @@ -278,14 +288,14 @@ struct FFlowDataPinResult_Rotator : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Transform)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Transform)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Transform : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FTransform Value; public: @@ -298,14 +308,14 @@ struct FFlowDataPinResult_Transform : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (GameplayTag)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (GameplayTag)", meta = (DeprecatedClass)) struct FFlowDataPinResult_GameplayTag : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FGameplayTag Value; public: @@ -318,14 +328,14 @@ struct FFlowDataPinResult_GameplayTag : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (GameplayTagContainer)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (GameplayTagContainer)", meta = (DeprecatedClass)) struct FFlowDataPinResult_GameplayTagContainer : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FGameplayTagContainer Value; public: @@ -338,14 +348,14 @@ struct FFlowDataPinResult_GameplayTagContainer : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (InstancedStruct)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (InstancedStruct)", meta = (DeprecatedClass)) struct FFlowDataPinResult_InstancedStruct : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FInstancedStruct Value; public: @@ -358,14 +368,14 @@ struct FFlowDataPinResult_InstancedStruct : public FFlowDataPinResult { } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Object)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Object)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Object : public FFlowDataPinResult { GENERATED_BODY() public: - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) TObjectPtr Value; public: @@ -374,12 +384,11 @@ struct FFlowDataPinResult_Object : public FFlowDataPinResult FLOW_API FFlowDataPinResult_Object(EFlowDataPinResolveResult InResult) : Super(InResult) { } FLOW_API FFlowDataPinResult_Object(UObject* InValue); - FLOW_API void SetValueFromPropertyWrapper(const FFlowDataPinOutputProperty_Object& InPropertyWrapper); FLOW_API FORCEINLINE void SetValueFromSoftPath(const FSoftObjectPath& SoftPath) { Value = SoftPath.ResolveObject(); } FLOW_API FORCEINLINE void SetValueFromObjectPtr(UObject* ObjectPtr) { Value = ObjectPtr; } }; -USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Class)") +USTRUCT(BlueprintType, DisplayName = "Flow DataPin Result (Class)", meta = (DeprecatedClass)) struct FFlowDataPinResult_Class : public FFlowDataPinResult { GENERATED_BODY() @@ -388,12 +397,12 @@ struct FFlowDataPinResult_Class : public FFlowDataPinResult // SoftClassPath version of the result // (both the SoftClassPath and the UClass (if available) will be set for the result) - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) FSoftClassPath ValuePath; // UClass version of the result // (both the SoftClassPath and the UClass (if available) will be set for the result) - UPROPERTY(Transient, BlueprintReadWrite, Category = DataPins) + UPROPERTY(BlueprintReadWrite, Category = DataPins) TObjectPtr ValueClass = nullptr; public: @@ -403,7 +412,6 @@ struct FFlowDataPinResult_Class : public FFlowDataPinResult FLOW_API FFlowDataPinResult_Class(const FSoftClassPath& InValuePath); FLOW_API FFlowDataPinResult_Class(UClass* InValueClass); - FLOW_API void SetValueFromPropertyWrapper(const FFlowDataPinOutputProperty_Class& PropertyWrapper); FLOW_API void SetValueSoftClassAndClassPtr(const FSoftClassPath& SoftPath, UClass* ObjectPtr); FLOW_API void SetValueFromSoftPath(const FSoftObjectPath& SoftObjectPath); FLOW_API FORCEINLINE void SetValueFromObjectPtr(UClass* ClassPtr) { SetValueSoftClassAndClassPtr(FSoftClassPath(ClassPtr), ClassPtr); } @@ -411,3 +419,4 @@ struct FFlowDataPinResult_Class : public FFlowDataPinResult FLOW_API UClass* GetOrResolveClass() const { return IsValid(ValueClass) ? ValueClass.Get() : ValuePath.ResolveClass(); } FLOW_API FSoftClassPath GetAsSoftClass() const; }; +// -- \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinValue.h b/Source/Flow/Public/Types/FlowDataPinValue.h new file mode 100644 index 000000000..ba53deb9f --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinValue.h @@ -0,0 +1,64 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowPinEnums.h" +#include "FlowPinType.h" +#include "UObject/NameTypes.h" +#include "UObject/ObjectPtr.h" +#include "StructUtils/InstancedStruct.h" + +#include "FlowDataPinValue.generated.h" + +struct FFlowDataPinResult; +class FProperty; +class UObject; +class IPropertyHandle; +class UScriptStruct; + +USTRUCT() +struct FFlowDataPinValue +{ + GENERATED_BODY() + + friend class FFlowDataPinValueCustomization; + +public: + // IF a pin was created from this property, this is the cached pin name that was used + // (which can be used in UFlowDataPinBlueprintLibrary::ResolveAs... functions to lookup the correct pin by name) + UPROPERTY(VisibleAnywhere, Category = DataPins) + mutable FName PropertyPinName; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = DataPins) + bool bIsInputPin = false; + + UPROPERTY(EditAnywhere, Category = DataPins) + EFlowDataMultiType MultiType = EFlowDataMultiType::Single; +#endif + + FFlowDataPinValue() {} + virtual ~FFlowDataPinValue() {} + +#if WITH_EDITOR + FLOW_API bool IsInputPin() const { return bIsInputPin; } + FLOW_API bool IsArray() const { FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); return MultiType == EFlowDataMultiType::Array; } + + // Helper to get the Values property handle (implemented by subclasses or via type system) + FLOW_API virtual TSharedPtr GetValuesPropertyHandle() const PURE_VIRTUAL(GetValuesPropertyHandle, return nullptr;); +#endif + + // Pin Type Name (identity) + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const PURE_VIRTUAL(GetPinTypeName, return FFlowPinType::PinTypeNameUnknown;); + + // (optional) Get the field type if one exists (only used for UEnum For Now) + FLOW_API virtual UField* GetFieldType() const { return nullptr; } + + // (optional) + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const { return false; } + + // Resolve the registered data pin type + FLOW_API const FFlowPinType* LookupPinType() const; + + FLOW_API static const FString StringArraySeparator; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinValuesStandard.h b/Source/Flow/Public/Types/FlowDataPinValuesStandard.h new file mode 100644 index 000000000..7a788f09b --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinValuesStandard.h @@ -0,0 +1,497 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowDataPinValue.h" +#include "Types/FlowPinTypesStandard.h" +#include "Types/FlowArray.h" + +#include "StructUtils/InstancedStruct.h" +#include "GameplayTagContainer.h" +#include "UObject/SoftObjectPtr.h" +#include "UObject/SoftObjectPath.h" +#include "UObject/Class.h" +#include "Math/Vector.h" +#include "Math/Rotator.h" +#include "Math/Transform.h" + +#include "FlowDataPinValuesStandard.generated.h" + +//====================================================================== +// Bool +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Bool - Flow DataPin Value", meta = (FlowPinType = "Bool", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructBool")) +struct FFlowDataPinValue_Bool : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Bool; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ false }; + + FLOW_API FFlowDataPinValue_Bool() = default; + FLOW_API FFlowDataPinValue_Bool(ValueType InValue); + FLOW_API FFlowDataPinValue_Bool(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Int (int32) +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Int - Flow DataPin Value", meta = (FlowPinType = "Int", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructInt")) +struct FFlowDataPinValue_Int : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Int; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0 }; + + FLOW_API FFlowDataPinValue_Int() = default; + FLOW_API FFlowDataPinValue_Int(ValueType InValue); + FLOW_API FFlowDataPinValue_Int(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Int64 +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Int64 - Flow DataPin Value", meta = (FlowPinType = "Int64", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructInt64")) +struct FFlowDataPinValue_Int64 : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Int64; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0 }; + + FLOW_API FFlowDataPinValue_Int64() = default; + FLOW_API FFlowDataPinValue_Int64(ValueType InValue); + FLOW_API FFlowDataPinValue_Int64(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Float +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Float - Flow DataPin Value", meta = (FlowPinType = "Float", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructFloat")) +struct FFlowDataPinValue_Float : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Float; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0.0f }; + + FLOW_API FFlowDataPinValue_Float() = default; + FLOW_API FFlowDataPinValue_Float(ValueType InValue); + FLOW_API FFlowDataPinValue_Float(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Double +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Double - Flow DataPin Value", meta = (FlowPinType = "Double", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructDouble")) +struct FFlowDataPinValue_Double : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Double; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0.0 }; + + FLOW_API FFlowDataPinValue_Double() = default; + FLOW_API FFlowDataPinValue_Double(ValueType InValue); + FLOW_API FFlowDataPinValue_Double(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Name +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Name - Flow DataPin Value", meta = (FlowPinType = "Name", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructName")) +struct FFlowDataPinValue_Name : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Name; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ NAME_None }; + + FLOW_API FFlowDataPinValue_Name() = default; + FLOW_API FFlowDataPinValue_Name(const ValueType& InValue); + FLOW_API FFlowDataPinValue_Name(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// String +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "String - Flow DataPin Value", meta = (FlowPinType = "String", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructString")) +struct FFlowDataPinValue_String : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_String; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FLOW_API FFlowDataPinValue_String() = default; + FLOW_API FFlowDataPinValue_String(const ValueType& InValue); + FLOW_API FFlowDataPinValue_String(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Text +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Text - Flow DataPin Value", meta = (FlowPinType = "Text", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructText")) +struct FFlowDataPinValue_Text : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Text; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FLOW_API FFlowDataPinValue_Text() = default; + FLOW_API FFlowDataPinValue_Text(const ValueType& InValue); + FLOW_API FFlowDataPinValue_Text(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Enum +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Enum - Flow DataPin Value", meta = (FlowPinType = "Enum", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructEnum")) +struct FFlowDataPinValue_Enum : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Enum; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + // Enum asset reference (advanced) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins, meta = (NoClear, AdvancedDisplay)) + TSoftObjectPtr EnumClass; + +#if WITH_EDITORONLY_DATA + // Native C++ enum name (advanced) + UPROPERTY(EditAnywhere, Category = DataPins, meta = (AdvancedDisplay)) + FString EnumName; +#endif + + FLOW_API FFlowDataPinValue_Enum() = default; + FLOW_API FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, const ValueType& InValue); + FLOW_API FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, const TArray& InValues); + +#if WITH_EDITOR + FLOW_API void OnEnumNameChanged(); +#endif + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual UField* GetFieldType() const override; + virtual bool TryConvertValuesToString(FString& OutString) const override; + + // Helper templates + template + static bool TryGetEnumValueByName(const UEnum* EnumClass, const FName& EnumValueName, TUnrealNativeEnumType& OutValue, EGetByNameFlags GetByNameFlags = EGetByNameFlags::ErrorIfNotFound) + { + if (!IsValid(EnumClass)) + { + return false; + } + + const int32 EnumIndex = EnumClass->GetIndexByName(EnumValueName, GetByNameFlags); + if (EnumIndex != INDEX_NONE) + { + OutValue = static_cast(EnumClass->GetValueByIndex(EnumIndex)); + return true; + } + return false; + } + + template + EFlowDataPinResolveResult TryGetSingleEnumValue(TUnrealNativeEnumType& OutEnumValue, EFlowSingleFromArray SingleFromArray, EGetByNameFlags GetByNameFlags = EGetByNameFlags::ErrorIfNotFound) const + { + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, Values.Num()); + if (!Values.IsValidIndex(Index)) + { + return EFlowDataPinResolveResult::FailedInsufficientValues; + } + + UEnum* EnumClassPtr = EnumClass.LoadSynchronous(); + if (!TryGetEnumValueByName(EnumClassPtr, Values[Index], OutEnumValue, GetByNameFlags)) + { + return EFlowDataPinResolveResult::FailedUnknownEnumValue; + } + return EFlowDataPinResolveResult::Success; + } + + template + EFlowDataPinResolveResult TryGetAllNativeEnumValues(TArray& OutEnumValues, EGetByNameFlags GetByNameFlags = EGetByNameFlags::ErrorIfNotFound) const + { + if (Values.IsEmpty()) + { + return EFlowDataPinResolveResult::FailedInsufficientValues; + } + + UEnum* EnumClassPtr = EnumClass.LoadSynchronous(); + OutEnumValues.Reserve(Values.Num()); + + for (const ValueType& ValueName : Values) + { + TUnrealNativeEnumType EnumValue; + if (!TryGetEnumValueByName(EnumClassPtr, ValueName, EnumValue, GetByNameFlags)) + { + return EFlowDataPinResolveResult::FailedUnknownEnumValue; + } + OutEnumValues.Add(EnumValue); + } + return EFlowDataPinResolveResult::Success; + } +}; + +//====================================================================== +// Vector +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Vector - Flow DataPin Value", meta = (FlowPinType = "Vector", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructVector")) +struct FFlowDataPinValue_Vector : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Vector; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ FVector::ZeroVector }; + + FLOW_API FFlowDataPinValue_Vector() = default; + FLOW_API FFlowDataPinValue_Vector(const ValueType& InValue); + FLOW_API FFlowDataPinValue_Vector(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Rotator +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Rotator - Flow DataPin Value", meta = (FlowPinType = "Rotator", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructRotator")) +struct FFlowDataPinValue_Rotator : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Rotator; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ FRotator::ZeroRotator }; + + FLOW_API FFlowDataPinValue_Rotator() = default; + FLOW_API FFlowDataPinValue_Rotator(const ValueType& InValue); + FLOW_API FFlowDataPinValue_Rotator(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Transform +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Transform - Flow DataPin Value", meta = (FlowPinType = "Transform", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructTransform")) +struct FFlowDataPinValue_Transform : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Transform; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ FTransform::Identity }; + + FLOW_API FFlowDataPinValue_Transform() = default; + FLOW_API FFlowDataPinValue_Transform(const ValueType& InValue); + FLOW_API FFlowDataPinValue_Transform(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// GameplayTag +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "GameplayTag - Flow DataPin Value", meta = (FlowPinType = "GameplayTag", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructGameplayTag")) +struct FFlowDataPinValue_GameplayTag : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_GameplayTag; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FLOW_API FFlowDataPinValue_GameplayTag() = default; + FLOW_API FFlowDataPinValue_GameplayTag(const ValueType& InValue); + FLOW_API FFlowDataPinValue_GameplayTag(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// GameplayTagContainer +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "GameplayTagContainer - Flow DataPin Value", meta = (FlowPinType = "GameplayTagContainer", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructGameplayTagContainer")) +struct FFlowDataPinValue_GameplayTagContainer : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_GameplayTagContainer; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + FGameplayTagContainer Values; + + FLOW_API FFlowDataPinValue_GameplayTagContainer() = default; + FLOW_API FFlowDataPinValue_GameplayTagContainer(const FGameplayTag& InValue); + FLOW_API FFlowDataPinValue_GameplayTagContainer(const FGameplayTagContainer& InValues); + FLOW_API FFlowDataPinValue_GameplayTagContainer(const TArray& InValues); + FLOW_API FFlowDataPinValue_GameplayTagContainer(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// InstancedStruct +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "InstancedStruct - Flow DataPin Value", meta = (FlowPinType = "InstancedStruct", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructInstancedStruct")) +struct FFlowDataPinValue_InstancedStruct : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_InstancedStruct; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FLOW_API FFlowDataPinValue_InstancedStruct() = default; + FLOW_API FFlowDataPinValue_InstancedStruct(const ValueType& InValue); + FLOW_API FFlowDataPinValue_InstancedStruct(const TArray& InValues); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } +}; + +//====================================================================== +// Object +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Object - Flow DataPin Value", meta = (FlowPinType = "Object", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructObject")) +struct FFlowDataPinValue_Object : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Object; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray> Values; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) + TObjectPtr ClassFilter = UObject::StaticClass(); +#endif + + FLOW_API FFlowDataPinValue_Object() = default; + FLOW_API FFlowDataPinValue_Object(TObjectPtr InObject, UClass* InClassFilter = UObject::StaticClass()); + FLOW_API FFlowDataPinValue_Object(const TArray>& InObjects, UClass* InClassFilter = UObject::StaticClass()); + FLOW_API FFlowDataPinValue_Object(AActor* InActor, UClass* InClassFilter = nullptr /* nullptr here defaults to AActor::StaticClass() */ ); + FLOW_API FFlowDataPinValue_Object(const TArray& InActors, UClass* InClassFilter = nullptr /* nullptr here defaults to AActor::StaticClass() */); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; + +//====================================================================== +// Class +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Class - Flow DataPin Value", meta = (FlowPinType = "Class", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructClass")) +struct FFlowDataPinValue_Class : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + using PinType = FFlowPinType_Class; + using ValueType = PinType::ValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) + TObjectPtr ClassFilter = UObject::StaticClass(); +#endif + + FLOW_API FFlowDataPinValue_Class() = default; + FLOW_API FFlowDataPinValue_Class(const FSoftClassPath& InPath, UClass* InClassFilter = UObject::StaticClass()); + FLOW_API FFlowDataPinValue_Class(const TArray& InPaths, UClass* InClassFilter = UObject::StaticClass()); + FLOW_API FFlowDataPinValue_Class(const UClass* InClass, UClass* InClassFilter = UObject::StaticClass()); + FLOW_API FFlowDataPinValue_Class(const TArray& InClasses, UClass* InClassFilter = UObject::StaticClass()); + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowNamedDataPinProperty.h b/Source/Flow/Public/Types/FlowNamedDataPinProperty.h new file mode 100644 index 000000000..939b67fa1 --- /dev/null +++ b/Source/Flow/Public/Types/FlowNamedDataPinProperty.h @@ -0,0 +1,98 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "StructUtils/InstancedStruct.h" +#include "UObject/Class.h" +#include "Nodes/FlowPin.h" +#include "Types/FlowDataPinValue.h" + +#include "FlowNamedDataPinProperty.generated.h" + +struct FFlowDataPinProperty; +struct FFlowDataPinValue; + +// 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; + +private: + // DataPinProperty payload + UPROPERTY(VisibleAnywhere, Category = DataPins, meta = (DeprecatedProperty)) + TInstancedStruct DataPinProperty; + +public: + // DataPinProperty payload + UPROPERTY(EditAnywhere, Category = DataPins, meta = (ExcludeBaseStruct, NoClear)) + TInstancedStruct DataPinValue; + +#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 && DataPinValue.GetPtr() != nullptr; } + + // #FlowDataPinLegacy + bool FixupDataPinProperty(); + // -- + +#if WITH_EDITOR + FLOW_API FFlowPin CreateFlowPin() const; + + 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) + { + Property.ConfigureForFlowAssetStartNode(); + } + } + +#endif +}; + diff --git a/Source/Flow/Public/Types/FlowPinEnums.h b/Source/Flow/Public/Types/FlowPinEnums.h index 5035013cd..53bbecfb4 100644 --- a/Source/Flow/Public/Types/FlowPinEnums.h +++ b/Source/Flow/Public/Types/FlowPinEnums.h @@ -6,7 +6,7 @@ #include "FlowPinEnums.generated.h" -UENUM(BlueprintType) +UENUM(BlueprintType, meta = (ScriptName = "LegacyFlowPinNameEnum")) enum class EFlowPinType : uint8 { // Execution pin @@ -63,16 +63,13 @@ enum class EFlowPinType : uint8 }; FLOW_ENUM_RANGE_VALUES(EFlowPinType) -// Result enum for TryResolveDataPinAs...() functions +// Result enum for TryResolveDataPin() UENUM(BlueprintType) enum class EFlowDataPinResolveResult : uint8 { // Pin resolved successfully Success, - // The pin is not connected to another pin - FailedUnconnected, - // The pin name is unknown FailedUnknownPin, @@ -82,8 +79,14 @@ enum class EFlowDataPinResolveResult : uint8 // The Flow Node or AddOn did not implement the necessary function to provide this value FailedUnimplemented, - // Failed due to missing pin (may just need re-save for the asset) - FailedMissingPin, + // Failed due to insufficient values (eg. resolving a single value with an empty array) + FailedInsufficientValues, + + // Could not resolve an enum value + FailedUnknownEnumValue, + + // Tried to extract with a null FlowNodeBase + FailedNullFlowNodeBase, // Failed with an error message (see the error log) FailedWithError, @@ -93,3 +96,108 @@ enum class EFlowDataPinResolveResult : uint8 Min = 0 UMETA(Hidden), }; FLOW_ENUM_RANGE_VALUES(EFlowDataPinResolveResult) + +UENUM(BlueprintType) +enum class EFlowDataPinResolveSimpleResult : uint8 +{ + Succeeded = 1, + Failed = 0, + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowDataPinResolveSimpleResult) + +namespace EFlowDataPinResolveResult_Classifiers +{ + FORCEINLINE bool IsSuccess(EFlowDataPinResolveResult Result) { return Result == EFlowDataPinResolveResult::Success; } + FORCEINLINE EFlowDataPinResolveSimpleResult ConvertToSimpleResult(EFlowDataPinResolveResult ResultEnum) + { return IsSuccess(ResultEnum) ? EFlowDataPinResolveSimpleResult::Succeeded : EFlowDataPinResolveSimpleResult::Failed; } +}; + +UENUM(BlueprintType) +enum class EFlowDataMultiType : uint8 +{ + Single, + Array, + + // TODO (gtaylor) Consider future types like Set, Map + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowDataMultiType) + +UENUM(BlueprintType) +enum class EFlowSingleFromArray : uint8 +{ + // For the Single value, use the [0]th value (First) + FirstValue, + + // For the Single value, use the [N-1]th value (Last) + LastValue, + + // Expect a single value only, log an error if not (and return [0]th) + ExpectSingleValueOnly, + + // Used in the FlowPinType templates for entire array extraction + EntireArray UMETA(Hidden), + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowSingleFromArray) + +namespace EFlowSingleFromArray_Classifiers +{ + FORCEINLINE int32 ConvertToIndex(EFlowSingleFromArray SingleFromArray, int32 ArrayMax) + { + FLOW_ASSERT_ENUM_MAX(EFlowSingleFromArray, 4); + switch (SingleFromArray) + { + case EFlowSingleFromArray::FirstValue: + { + if (ArrayMax > 0) + { + return 0; + } + else + { + return INDEX_NONE; + } + } + + case EFlowSingleFromArray::LastValue: + { + if (ArrayMax > 0) + { + return ArrayMax - 1; + } + else + { + return INDEX_NONE; + } + } + + case EFlowSingleFromArray::EntireArray: + check(SingleFromArray != EFlowSingleFromArray::EntireArray); + return INDEX_NONE; + + default: + case EFlowSingleFromArray::ExpectSingleValueOnly: + { + if (ArrayMax == 1) + { + return 0; + } + else + { + return INDEX_NONE; + } + } + } + } +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowPinType.h b/Source/Flow/Public/Types/FlowPinType.h new file mode 100644 index 000000000..9367f47b0 --- /dev/null +++ b/Source/Flow/Public/Types/FlowPinType.h @@ -0,0 +1,55 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowPinEnums.h" +#include "FlowPinTypeNamesStandard.h" + +#include "UObject/NameTypes.h" +#include "Math/Color.h" +#if WITH_EDITOR +#include "GraphEditorSettings.h" +#endif + +#include "FlowPinType.generated.h" + +class FFormatArgumentValue; +class IPropertyHandle; +class UFlowNodeBase; +class UFlowNode; +struct FFlowDataPinResult; +struct FFlowPin; +struct FFlowDataPinValue; + +USTRUCT(BlueprintType) +struct FFlowPinType +{ + GENERATED_BODY() + +public: + virtual ~FFlowPinType() {} + + // Lookup a registered type by name + FLOW_API static const FFlowPinType* LookupPinType(const FFlowPinTypeName& FlowPinTypeName); + + // Identity + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const PURE_VIRTUAL(GetPinTypeName, return PinTypeNameUnknown;); + + // Value resolution + FLOW_API virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const; + FLOW_API virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const; + +#if WITH_EDITOR + // Editor visualization + FLOW_API virtual FLinearColor GetPinColor() const { return GetDefault()->DefaultPinTypeColor; } + FLOW_API virtual TSharedPtr GetValuesHandle(const TSharedRef& FlowDataPinValuePropertyHandle) const; + FLOW_API virtual bool SupportsMultiType(EFlowDataMultiType Mode) const { return true; } + FLOW_API virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const { return nullptr; } + + // Pin creation + FLOW_API FFlowPin CreateFlowPinFromProperty(const FProperty& Property, void const* InContainer) const; + FLOW_API FFlowPin CreateFlowPinFromValueWrapper(const FName& PinName, const FFlowDataPinValue& Wrapper) const; +#endif + + static const FFlowPinTypeName PinTypeNameUnknown; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowPinTypeName.h b/Source/Flow/Public/Types/FlowPinTypeName.h new file mode 100644 index 000000000..1531c289c --- /dev/null +++ b/Source/Flow/Public/Types/FlowPinTypeName.h @@ -0,0 +1,33 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/NameTypes.h" + +#include "FlowPinTypeName.generated.h" + +USTRUCT(BlueprintType) +struct FFlowPinTypeName +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, Category = FlowPin) + FName Name = NAME_None; + + FFlowPinTypeName() = default; + explicit FFlowPinTypeName(const TCHAR* InPinName) : Name(FName(InPinName)) {} + explicit FFlowPinTypeName(const FName& InName) : Name(InName) {} + explicit FFlowPinTypeName(const FString& InString) : Name(FName(InString)) {} + + friend inline uint32 GetTypeHash(const FFlowPinTypeName& PinTypeName) + { + return GetTypeHash(PinTypeName.Name); + } + + FORCEINLINE bool operator==(const FFlowPinTypeName& Other) const { return Name == Other.Name; } + FORCEINLINE bool operator==(const FName& OtherName) const { return Name == OtherName; } + + FString ToString() const { return Name.ToString(); } + bool IsNone() const { return Name.IsNone(); } +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowPinTypeNamesStandard.h b/Source/Flow/Public/Types/FlowPinTypeNamesStandard.h new file mode 100644 index 000000000..35a273099 --- /dev/null +++ b/Source/Flow/Public/Types/FlowPinTypeNamesStandard.h @@ -0,0 +1,39 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/NameTypes.h" +#include "FlowPinTypeName.h" +#include "Asset/FlowPinTypeMatchPolicy.h" + +struct FFlowPinTypeNamesStandard +{ + // Other Standard Pin Types + FLOW_API static constexpr const TCHAR* PinTypeNameUnknown = TEXT("Unknown"); + FLOW_API static constexpr const TCHAR* PinTypeNameExec = TEXT("Exec"); + + // "Standard" Data Pin Types + FLOW_API static constexpr const TCHAR* PinTypeNameBool = TEXT("Bool"); + FLOW_API static constexpr const TCHAR* PinTypeNameInt = TEXT("Int"); + FLOW_API static constexpr const TCHAR* PinTypeNameInt64 = TEXT("Int64"); + FLOW_API static constexpr const TCHAR* PinTypeNameFloat = TEXT("Float"); + FLOW_API static constexpr const TCHAR* PinTypeNameDouble = TEXT("Double"); + FLOW_API static constexpr const TCHAR* PinTypeNameEnum = TEXT("Enum"); + FLOW_API static constexpr const TCHAR* PinTypeNameName = TEXT("Name"); + FLOW_API static constexpr const TCHAR* PinTypeNameString = TEXT("String"); + FLOW_API static constexpr const TCHAR* PinTypeNameText = TEXT("Text"); + FLOW_API static constexpr const TCHAR* PinTypeNameVector = TEXT("Vector"); + FLOW_API static constexpr const TCHAR* PinTypeNameRotator = TEXT("Rotator"); + FLOW_API static constexpr const TCHAR* PinTypeNameTransform = TEXT("Transform"); + FLOW_API static constexpr const TCHAR* PinTypeNameGameplayTag = TEXT("GameplayTag"); + FLOW_API static constexpr const TCHAR* PinTypeNameGameplayTagContainer = TEXT("GameplayTagContainer"); + FLOW_API static constexpr const TCHAR* PinTypeNameInstancedStruct = TEXT("InstancedStruct"); + FLOW_API static constexpr const TCHAR* PinTypeNameObject = TEXT("Object"); + FLOW_API static constexpr const TCHAR* PinTypeNameClass = TEXT("Class"); + +#if WITH_EDITOR + // These are the default pin match policies for input pin connections + // in the UFlowGraphSchema. Schema subclasses can modify this map + FLOW_API static const TMap PinTypeMatchPolicies; +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowPinTypeNodeTemplates.h b/Source/Flow/Public/Types/FlowPinTypeNodeTemplates.h new file mode 100644 index 000000000..a59cc3c24 --- /dev/null +++ b/Source/Flow/Public/Types/FlowPinTypeNodeTemplates.h @@ -0,0 +1,70 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowDataPinResults.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "Types/FlowPinTypeTemplates.h" +#include "Types/FlowArray.h" +#include "Nodes/FlowNode.h" + +// Additional FlowPinType templates that require FlowNode.h include +namespace FlowPinType +{ + template + static bool PopulateResultTemplate(const UObject& PropertyOwnerObject, const UFlowNode& FlowNode, const FFlowPin& Pin, FFlowDataPinResult& OutResult) + { + using TValue = typename TPinType::ValueType; + using TWrapper = typename TPinType::WrapperType; + using Traits = FlowPinType::FFlowDataPinValueTraits; + + TInstancedStruct ValueStruct; + const FProperty* FoundProperty = nullptr; + + if (!FlowNode.TryFindPropertyByPinName(PropertyOwnerObject, Pin.PinName, FoundProperty, ValueStruct)) + { + OutResult.Result = EFlowDataPinResolveResult::FailedUnknownPin; + return false; + } + + if (ValueStruct.IsValid() && ValueStruct.Get().GetPinTypeName() == TPinType::GetPinTypeNameStatic()) + { + OutResult.ResultValue = ValueStruct; + OutResult.Result = EFlowDataPinResolveResult::Success; + return true; + } + + TArray Values; + if (FlowPinType::IsSuccess(Traits::ExtractFromProperty(FoundProperty, &PropertyOwnerObject, Values))) + { + OutResult.ResultValue = TInstancedStruct::Make(Values); + OutResult.Result = EFlowDataPinResolveResult::Success; + return true; + } + + OutResult.Result = EFlowDataPinResolveResult::FailedMismatchedType; + return false; + } + + // ResolveAndFormatArray + template + bool ResolveAndFormatArray( + const UFlowNodeBase& Node, + const FName& PinName, + FFormatArgumentValue& OutValue, + TFunctionRef Formatter) + { + using TValue = typename TPinType::ValueType; + + TArray Values; + const EFlowDataPinResolveResult ResolveResult = Node.TryResolveDataPinValues(PinName, Values); + if (FlowPinType::IsSuccess(ResolveResult)) + { + const FString ValueString = FlowArray::FormatArrayString(Values, Formatter); + OutValue = FFormatArgumentValue(FText::FromString(ValueString)); + return true; + } + + return false; + } +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowPinTypeTemplates.h b/Source/Flow/Public/Types/FlowPinTypeTemplates.h new file mode 100644 index 000000000..3ad34cde0 --- /dev/null +++ b/Source/Flow/Public/Types/FlowPinTypeTemplates.h @@ -0,0 +1,1025 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/NameTypes.h" +#include "UObject/TextProperty.h" +#include "Math/Vector.h" +#include "Math/Rotator.h" +#include "Math/Transform.h" +#include "GameplayTagContainer.h" +#include "StructUtils/InstancedStruct.h" +#include "UObject/Class.h" +#include "UObject/UnrealType.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "Types/FlowDataPinResults.h" +#include "FlowLogChannels.h" +#include +#include + +// #FlowDataPinLegacy +#include "Types/FlowDataPinProperties.h" +// -- + +namespace FlowPinType +{ + // Success check helper + FORCEINLINE bool IsSuccess(EFlowDataPinResolveResult ResultEnum) + { + return EFlowDataPinResolveResult_Classifiers::IsSuccess(ResultEnum); + } + + FORCEINLINE EFlowDataPinResolveSimpleResult ConvertToSimpleResult(EFlowDataPinResolveResult ResultEnum) + { + return EFlowDataPinResolveResult_Classifiers::ConvertToSimpleResult(ResultEnum); + } + + // ----------------------------------------------------------------------- + // Value Conversion System + // ----------------------------------------------------------------------- + + // Numeric conversion dispatcher + template + struct TValueConverter; + + // int32 + template <> struct TValueConverter { static int32 Convert(int64 Val); }; + template <> struct TValueConverter { static int32 Convert(float Val); }; + template <> struct TValueConverter { static int32 Convert(double Val); }; + + // int64 + template <> struct TValueConverter { static int64 Convert(int32 Val); }; + template <> struct TValueConverter { static int64 Convert(float Val); }; + template <> struct TValueConverter { static int64 Convert(double Val); }; + + // float + template <> struct TValueConverter { static float Convert(int32 Val); }; + template <> struct TValueConverter { static float Convert(int64 Val); }; + template <> struct TValueConverter { static float Convert(double Val); }; + + // double + template <> struct TValueConverter { static double Convert(int32 Val); }; + template <> struct TValueConverter { static double Convert(int64 Val); }; + template <> struct TValueConverter { static double Convert(float Val); }; + + // String types + template <> struct TValueConverter { static FName Convert(const FString& Val); }; + template <> struct TValueConverter { static FName Convert(const FText& Val); }; + template <> struct TValueConverter { static FString Convert(const FName& Val); }; + template <> struct TValueConverter { static FString Convert(const FText& Val); }; + template <> struct TValueConverter { static FText Convert(const FName& Val); }; + template <> struct TValueConverter { static FText Convert(const FString& Val); }; + + // To string for logging + template <> struct TValueConverter { static FString Convert(int32 Val); }; + template <> struct TValueConverter { static FString Convert(int64 Val); }; + template <> struct TValueConverter { static FString Convert(float Val); }; + template <> struct TValueConverter { static FString Convert(double Val); }; + template <> struct TValueConverter { static FString Convert(bool Val); }; + + // GameplayTag + template <> struct TValueConverter + { + static FGameplayTag Convert(const FGameplayTagContainer& Container); + }; + + template <> struct TValueConverter + { + static FGameplayTagContainer Convert(const FGameplayTag& Tag); + }; + + // ----------------------------------------------------------------------- + // Array Conversion Helper + // ----------------------------------------------------------------------- + + // Converts array with logging and clamping + template + void ConvertArray(const TArray& Source, TArray& OutValues, TConverter Converter) + { + OutValues.Reserve(Source.Num()); + for (const TSource& Val : Source) + { +#if !UE_BUILD_SHIPPING + // Lossy conversion warnings + if constexpr (std::is_integral_v && std::is_floating_point_v) + { + int64 iv = FMath::FloorToInt64(Val); + if (iv < std::numeric_limits::min() || iv > std::numeric_limits::max()) + { + UE_LOG(LogFlow, Warning, TEXT("Converting %s to %s (out of range, clamping)"), + *TValueConverter::Convert(Val), TEXT("int")); + } + } + else if constexpr (std::is_same_v && std::is_same_v) + { + if (Val < std::numeric_limits::lowest() || Val > std::numeric_limits::max()) + { + UE_LOG(LogFlow, Warning, TEXT("Converting %s to float (out of range, clamping)"), + *TValueConverter::Convert(Val)); + } + } + else if constexpr (std::is_same_v && (std::is_same_v || std::is_same_v)) + { + FString SourceStr; + if constexpr (std::is_same_v) + { + SourceStr = Val; + } + else + { + SourceStr = TValueConverter::Convert(Val); + } + if (SourceStr.Len() > NAME_SIZE) + { + UE_LOG(LogFlow, Warning, TEXT("Converting '%s' to FName (possible truncation)"), *SourceStr); + } + } +#endif + OutValues.Add(Converter(Val)); + } + } + + // ----------------------------------------------------------------------- + // Internal helper – applies the single-from-array policy after extraction + // ----------------------------------------------------------------------- + template + FORCEINLINE EFlowDataPinResolveResult ApplySinglePolicy( + const TArray& Source, + TArray& OutValues, + EFlowSingleFromArray Policy) + { + if (Policy == EFlowSingleFromArray::EntireArray) + { + OutValues = Source; + return EFlowDataPinResolveResult::Success; + } + + const int32 Num = Source.Num(); + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(Policy, Num); + + if (!Source.IsValidIndex(Index)) + { + return EFlowDataPinResolveResult::FailedInsufficientValues; + } + + OutValues.Add(Source[Index]); + return EFlowDataPinResolveResult::Success; + } + + template + FORCEINLINE EFlowDataPinResolveResult ConvertWithPolicy( + const TArray& Source, + TArray& OutValues, + TConverter Converter, + EFlowSingleFromArray Policy) + { + if (Policy == EFlowSingleFromArray::EntireArray) + { + ConvertArray(Source, OutValues, Converter); + return EFlowDataPinResolveResult::Success; + } + + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(Policy, Source.Num()); + if (!Source.IsValidIndex(Index)) + { + return EFlowDataPinResolveResult::FailedInsufficientValues; + } + + OutValues.Add(Converter(Source[Index])); + return EFlowDataPinResolveResult::Success; + } + + // ----------------------------------------------------------------------- + // Numeric Validation & Clamping + // ----------------------------------------------------------------------- + + template + TValue ValidateAndClampNumericValue(TInput Val, TValue MinValue, TValue MaxValue) + { + if constexpr (std::is_floating_point::value) + { +#if !UE_BUILD_SHIPPING + if (!FMath::IsFinite(Val)) + { + UE_LOG(LogFlow, Warning, TEXT("Non-finite value %s encountered during conversion to %s"), + *TValueConverter::Convert(Val), TEXT("numeric")); + return TValue(0); + } +#endif + } + + if constexpr (std::is_floating_point::value) + { + if constexpr (std::is_same::value && std::is_same::value) + { +#if !UE_BUILD_SHIPPING + if (Val < MinValue || Val > MaxValue) + { + UE_LOG(LogFlow, Warning, TEXT("Double value %s out of range for float, clamping"), + *TValueConverter::Convert(Val)); + } +#endif + } + return FMath::Clamp(static_cast(Val), MinValue, MaxValue); + } + else + { + int64 iv = std::is_floating_point::value ? FMath::FloorToInt64(Val) : static_cast(Val); +#if !UE_BUILD_SHIPPING + if (iv < MinValue || iv > MaxValue) + { + UE_LOG(LogFlow, Warning, TEXT("Value %lld out of range for %s, clamping"), iv, TEXT("int")); + } +#endif + return static_cast(FMath::Clamp(iv, static_cast(MinValue), static_cast(MaxValue))); + } + } + + // ----------------------------------------------------------------------- + // ValueConverter Implementations + // ----------------------------------------------------------------------- + + // int32 + inline int32 TValueConverter::Convert(int64 Val) { return ValidateAndClampNumericValue(Val, std::numeric_limits::min(), std::numeric_limits::max()); } + inline int32 TValueConverter::Convert(float Val) { return ValidateAndClampNumericValue(Val, std::numeric_limits::min(), std::numeric_limits::max()); } + inline int32 TValueConverter::Convert(double Val) { return ValidateAndClampNumericValue(Val, std::numeric_limits::min(), std::numeric_limits::max()); } + + // int64 + inline int64 TValueConverter::Convert(int32 Val) { return static_cast(Val); } + inline int64 TValueConverter::Convert(float Val) { return ValidateAndClampNumericValue(Val, MIN_int64, MAX_int64); } + inline int64 TValueConverter::Convert(double Val) { return ValidateAndClampNumericValue(Val, MIN_int64, MAX_int64); } + + // float + inline float TValueConverter::Convert(int32 Val) { return static_cast(Val); } + inline float TValueConverter::Convert(int64 Val) { return ValidateAndClampNumericValue(Val, std::numeric_limits::lowest(), std::numeric_limits::max()); } + inline float TValueConverter::Convert(double Val) { return ValidateAndClampNumericValue(Val, std::numeric_limits::lowest(), std::numeric_limits::max()); } + + // double + inline double TValueConverter::Convert(int32 Val) { return static_cast(Val); } + inline double TValueConverter::Convert(int64 Val) { return static_cast(Val); } + inline double TValueConverter::Convert(float Val) { return static_cast(Val); } + + // String types + inline FName TValueConverter::Convert(const FString& Val) { return FName(*Val); } + inline FName TValueConverter::Convert(const FText& Val) { return FName(*Val.ToString()); } + inline FString TValueConverter::Convert(const FName& Val) { return Val.ToString(); } + inline FString TValueConverter::Convert(const FText& Val) { return Val.ToString(); } + inline FText TValueConverter::Convert(const FName& Val) { return FText::FromName(Val); } + inline FText TValueConverter::Convert(const FString& Val) { return FText::FromString(Val); } + + // String converters for other types + inline FString TValueConverter::Convert(int32 Val) { return FString::Printf(TEXT("%d"), Val); } + inline FString TValueConverter::Convert(int64 Val) { return FString::Printf(TEXT("%lld"), Val); } + inline FString TValueConverter::Convert(float Val) { return FString::Printf(TEXT("%f"), Val); } + inline FString TValueConverter::Convert(double Val) { return FString::Printf(TEXT("%f"), Val); } + inline FString TValueConverter::Convert(bool Val) { return Val ? TEXT("true") : TEXT("false"); } + + // GameplayTag + inline FGameplayTag TValueConverter::Convert(const FGameplayTagContainer& Container) + { +#if !UE_BUILD_SHIPPING + if (Container.Num() > 1) + { + UE_LOG(LogFlow, Warning, TEXT("Multiple tags in container; using first: %s"), *Container.ToStringSimple()); + } +#endif + return Container.Num() > 0 ? Container.GetByIndex(0) : FGameplayTag(); + } + + inline FGameplayTagContainer TValueConverter::Convert(const FGameplayTag& Tag) + { + return FGameplayTagContainer(Tag); + } + + // ----------------------------------------------------------------------- + // Property Traits + // ----------------------------------------------------------------------- + + // Base for simple scalar types + template + struct FFlowSimplePropertyTraitsBase + { + using ValueType = typename TPinType::ValueType; + using WrapperType = typename TPinType::WrapperType; + using PropertyType = typename TPinType::MainPropertyType; + using LegacyWrapperType = typename TPinType::LegacyWrapperType; + + static EFlowDataPinResolveResult ExtractFromProperty(const FProperty* Property, const void* Container, TArray& OutValues) + { + // 1. Wrapper struct + if (const FStructProperty* StructProp = CastField(Property)) + { + if (StructProp->Struct == WrapperType::StaticStruct()) + { + const WrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = Wrapper->Values; + return EFlowDataPinResolveResult::Success; + } + + // #FlowDataPinLegacy - support sourcing from old property wrappers For Now(tm) + static const UScriptStruct* OldPropStruct = LegacyWrapperType::StaticStruct(); + if (StructProp->Struct->IsChildOf(OldPropStruct)) + { + const LegacyWrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = { Wrapper->Value }; + return EFlowDataPinResolveResult::Success; + } + // -- + } + + // 2. Direct property + if (const PropertyType* Prop = CastField(Property)) + { + OutValues = { *Prop->template ContainerPtrToValuePtr(Container) }; + return EFlowDataPinResolveResult::Success; + } + + // 3. Array of property + if (const FArrayProperty* ArrProp = CastField(Property)) + { + if (const PropertyType* Inner = CastField(ArrProp->Inner)) + { + FScriptArrayHelper ArrHelper(ArrProp, ArrProp->ContainerPtrToValuePtr(Container)); + OutValues.Reserve(ArrHelper.Num()); + for (int32 i = 0; i < ArrHelper.Num(); ++i) + { + OutValues.Add(*Inner->template ContainerPtrToValuePtr(ArrHelper.GetRawPtr(i))); + } + return EFlowDataPinResolveResult::Success; + } + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + + static EFlowDataPinResolveResult ExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + if (DataPinResult.ResultValue.GetScriptStruct() == WrapperType::StaticStruct()) + { + const WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + return ApplySinglePolicy(Wrapper.Values, OutValues, SingleFromArray); + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + // Numeric cross-conversion + template + struct FFlowNumericTraitsBase : public FFlowSimplePropertyTraitsBase + { + using Super = FFlowSimplePropertyTraitsBase; + using ValueType = typename TPinType::ValueType; + using WrapperType = typename TPinType::WrapperType; + + static EFlowDataPinResolveResult ExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const UScriptStruct* ScriptStruct = DataPinResult.ResultValue.GetScriptStruct(); + + if (ScriptStruct == WrapperType::StaticStruct()) + { + const WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + return ApplySinglePolicy(Wrapper.Values, OutValues, SingleFromArray); + } + + // Cross-convert from other numeric types + if constexpr (!std::is_same_v) + { + if (ScriptStruct == FFlowDataPinValue_Int::StaticStruct()) + { + const FFlowDataPinValue_Int& Wrapper = DataPinResult.ResultValue.Get(); + return ConvertWithPolicy(Wrapper.Values, OutValues, TValueConverter::Convert, SingleFromArray); + } + } + + if constexpr (!std::is_same_v) + { + if (ScriptStruct == FFlowDataPinValue_Int64::StaticStruct()) + { + const FFlowDataPinValue_Int64& Wrapper = DataPinResult.ResultValue.Get(); + return ConvertWithPolicy(Wrapper.Values, OutValues, TValueConverter::Convert, SingleFromArray); + } + } + + if constexpr (!std::is_same_v) + { + if (ScriptStruct == FFlowDataPinValue_Float::StaticStruct()) + { + const FFlowDataPinValue_Float& Wrapper = DataPinResult.ResultValue.Get(); + return ConvertWithPolicy(Wrapper.Values, OutValues, TValueConverter::Convert, SingleFromArray); + } + } + + if constexpr (!std::is_same_v) + { + if (ScriptStruct == FFlowDataPinValue_Double::StaticStruct()) + { + const FFlowDataPinValue_Double& Wrapper = DataPinResult.ResultValue.Get(); + return ConvertWithPolicy(Wrapper.Values, OutValues, TValueConverter::Convert, SingleFromArray); + } + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + // String cross-conversion + template + struct FFlowStringTraitsBase : public FFlowSimplePropertyTraitsBase + { + using Super = FFlowSimplePropertyTraitsBase; + using ValueType = typename TPinType::ValueType; + using WrapperType = typename TPinType::WrapperType; + + static EFlowDataPinResolveResult ExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const UScriptStruct* ScriptStruct = DataPinResult.ResultValue.GetScriptStruct(); + + if (ScriptStruct == WrapperType::StaticStruct()) + { + const WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + return ApplySinglePolicy(Wrapper.Values, OutValues, SingleFromArray); + } + + // Cross-convert from other string types + if constexpr (!std::is_same_v) + { + if (ScriptStruct == FFlowDataPinValue_Name::StaticStruct()) + { + const FFlowDataPinValue_Name& Wrapper = DataPinResult.ResultValue.Get(); + return ConvertWithPolicy(Wrapper.Values, OutValues, TValueConverter::Convert, SingleFromArray); + } + } + + if constexpr (!std::is_same_v) + { + if (ScriptStruct == FFlowDataPinValue_String::StaticStruct()) + { + const FFlowDataPinValue_String& Wrapper = DataPinResult.ResultValue.Get(); + return ConvertWithPolicy(Wrapper.Values, OutValues, TValueConverter::Convert, SingleFromArray); + } + } + + if constexpr (!std::is_same_v) + { + if (ScriptStruct == FFlowDataPinValue_Text::StaticStruct()) + { + const FFlowDataPinValue_Text& Wrapper = DataPinResult.ResultValue.Get(); + return ConvertWithPolicy(Wrapper.Values, OutValues, TValueConverter::Convert, SingleFromArray); + } + } + + // Fallback to string conversion from any pin value + if (ScriptStruct->IsChildOf(FFlowDataPinValue::StaticStruct())) + { + const FFlowDataPinValue& BaseWrapper = DataPinResult.ResultValue.Get(); + FString StrValue; + if (BaseWrapper.TryConvertValuesToString(StrValue)) + { + if constexpr (std::is_same_v) + { + OutValues = { StrValue }; + } + else + { + OutValues = { TValueConverter::Convert(StrValue) }; + } + return EFlowDataPinResolveResult::Success; + } + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + // Struct types (Vector, Rotator, etc.) + template + struct FFlowStructTraitsBase : public FFlowSimplePropertyTraitsBase + { + using ValueType = typename TPinType::ValueType; + using WrapperType = typename TPinType::WrapperType; + using LegacyWrapperType = typename TPinType::LegacyWrapperType; + + static EFlowDataPinResolveResult ExtractFromProperty(const FProperty* Property, const void* Container, TArray& OutValues) + { + static const UScriptStruct* ValueStruct = TBaseStructure::Get(); + + if (const FStructProperty* StructProp = CastField(Property)) + { + static const UScriptStruct* WrapperStruct = TBaseStructure::Get(); + if (StructProp->Struct == WrapperStruct) + { + const WrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = Wrapper->Values; + return EFlowDataPinResolveResult::Success; + } + + if (StructProp->Struct == ValueStruct) + { + OutValues = { *StructProp->ContainerPtrToValuePtr(Container) }; + return EFlowDataPinResolveResult::Success; + } + + // #FlowDataPinLegacy - support sourcing from old property wrappers For Now(tm) + static const UScriptStruct* OldPropStruct = LegacyWrapperType::StaticStruct(); + if (StructProp->Struct->IsChildOf(OldPropStruct)) + { + const LegacyWrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = { Wrapper->Value }; + return EFlowDataPinResolveResult::Success; + } + // -- + } + else if (const FArrayProperty* ArrayProp = CastField(Property)) + { + const FStructProperty* InnerStruct = CastField(ArrayProp->Inner); + if (InnerStruct && InnerStruct->Struct == ValueStruct) + { + FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr(Container)); + OutValues.Reserve(Helper.Num()); + for (int32 i = 0; i < Helper.Num(); ++i) + { + OutValues.Add(*reinterpret_cast(Helper.GetRawPtr(i))); + } + return EFlowDataPinResolveResult::Success; + } + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + // ----------------------------------------------------------------------- + // Pin Type Traits + // ----------------------------------------------------------------------- + + template struct FFlowDataPinValueTraits; + + // Scalars + template <> struct FFlowDataPinValueTraits : public FFlowSimplePropertyTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowNumericTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowNumericTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowNumericTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowNumericTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowStringTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowStringTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowStringTraitsBase {}; + + // Structs + template <> struct FFlowDataPinValueTraits : public FFlowStructTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowStructTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowStructTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowStructTraitsBase {}; + + // Enum + template <> + struct FFlowDataPinValueTraits : public FFlowSimplePropertyTraitsBase + { + using TPinType = FFlowPinType_Enum; + using WrapperType = typename TPinType::WrapperType; + using ValueType = typename TPinType::ValueType; + using LegacyWrapperType = typename TPinType::LegacyWrapperType; + + static EFlowDataPinResolveResult ExtractFromProperty(const FProperty* Property, const void* Container, TArray& OutValues, TSoftObjectPtr& OutEnumClass) + { + const FStructProperty* StructProp = CastField(Property); + if (StructProp && StructProp->Struct == WrapperType::StaticStruct()) + { + const WrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = Wrapper->Values; + OutEnumClass = Wrapper->EnumClass; + return EFlowDataPinResolveResult::Success; + } + + // #FlowDataPinLegacy - support sourcing from old property wrappers For Now(tm) + static const UScriptStruct* OldPropStruct = LegacyWrapperType::StaticStruct(); + if (StructProp && StructProp->Struct->IsChildOf(OldPropStruct)) + { + const LegacyWrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = { Wrapper->Value }; + OutEnumClass = Wrapper->EnumClass; + return EFlowDataPinResolveResult::Success; + } + // -- + + if (const FEnumProperty* EnumProp = CastField(Property)) + { + const void* ContainerPtr = EnumProp->ContainerPtrToValuePtr(Container); + UEnum* EnumClass = EnumProp->GetEnum(); + const FNumericProperty* Underlying = EnumProp->GetUnderlyingProperty(); + int64 RawValue = Underlying->GetSignedIntPropertyValue_InContainer(ContainerPtr); + FString AuthoredName = EnumClass->GetAuthoredNameStringByValue(RawValue); + + OutValues = { FName(AuthoredName) }; + OutEnumClass = EnumClass; + return EFlowDataPinResolveResult::Success; + } + + if (const FArrayProperty* ArrayProp = CastField(Property)) + { + if (const FEnumProperty* Inner = CastField(ArrayProp->Inner)) + { + FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr(Container)); + UEnum* EnumClass = Inner->GetEnum(); + const FNumericProperty* Underlying = Inner->GetUnderlyingProperty(); + OutValues.Reserve(Helper.Num()); + for (int32 i = 0; i < Helper.Num(); ++i) + { + int64 RawValue = Underlying->GetSignedIntPropertyValue(Helper.GetRawPtr(i)); + FString Name = EnumClass->GetAuthoredNameStringByValue(RawValue); + OutValues.Add(FName(Name)); + } + OutEnumClass = EnumClass; + return EFlowDataPinResolveResult::Success; + } + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + // GameplayTag + template <> + struct FFlowDataPinValueTraits : public FFlowStructTraitsBase + { + using PinType = FFlowPinType_GameplayTag; + using ValueType = PinType::ValueType; + using WrapperType = FFlowDataPinValue_GameplayTag; + using ContainerWrapper = FFlowDataPinValue_GameplayTagContainer; + + static EFlowDataPinResolveResult ExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const UScriptStruct* ScriptStruct = DataPinResult.ResultValue.GetScriptStruct(); + + if (ScriptStruct == WrapperType::StaticStruct()) + { + const WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + return ApplySinglePolicy(Wrapper.Values, OutValues, SingleFromArray); + } + + if (ScriptStruct == ContainerWrapper::StaticStruct()) + { + const ContainerWrapper& Wrapper = DataPinResult.ResultValue.Get(); + TArray Temp = Wrapper.Values.GetGameplayTagArray(); + return ApplySinglePolicy(Temp, OutValues, SingleFromArray); + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + // GameplayTagContainer + template <> + struct FFlowDataPinValueTraits : public FFlowStructTraitsBase + { + using PinType = FFlowPinType_GameplayTagContainer; + using ValueType = PinType::ValueType; + using WrapperType = FFlowDataPinValue_GameplayTagContainer; + + static EFlowDataPinResolveResult ExtractFromProperty(const FProperty* Property, const void* Container, TArray& OutValues) + { + static const UScriptStruct* ValueStruct = TBaseStructure::Get(); + + if (const FStructProperty* StructProp = CastField(Property)) + { + static const UScriptStruct* WrapperStruct = TBaseStructure::Get(); + if (StructProp->Struct == WrapperStruct) + { + const WrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = { Wrapper->Values }; + return EFlowDataPinResolveResult::Success; + } + + if (StructProp->Struct == ValueStruct) + { + OutValues = { *StructProp->ContainerPtrToValuePtr(Container) }; + return EFlowDataPinResolveResult::Success; + } + + // #FlowDataPinLegacy - support sourcing from old property wrappers For Now(tm) + static const UScriptStruct* OldPropStruct = LegacyWrapperType::StaticStruct(); + if (StructProp->Struct->IsChildOf(OldPropStruct)) + { + const LegacyWrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = { Wrapper->Value }; + return EFlowDataPinResolveResult::Success; + } + // -- + } + else if (const FArrayProperty* ArrayProp = CastField(Property)) + { + const FStructProperty* Inner = CastField(ArrayProp->Inner); + if (Inner && Inner->Struct == ValueStruct) + { + FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr(Container)); + ValueType Consolidated; + for (int32 i = 0; i < Helper.Num(); ++i) + { + Consolidated.AppendTags(*reinterpret_cast(Helper.GetRawPtr(i))); + } + OutValues = { Consolidated }; + return EFlowDataPinResolveResult::Success; + } + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + + static EFlowDataPinResolveResult ExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const UScriptStruct* ScriptStruct = DataPinResult.ResultValue.GetScriptStruct(); + + if (ScriptStruct == WrapperType::StaticStruct()) + { + const WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + OutValues = { Wrapper.Values }; + return EFlowDataPinResolveResult::Success; + } + + if (ScriptStruct == FFlowDataPinValue_GameplayTag::StaticStruct()) + { + const FFlowDataPinValue_GameplayTag& Wrapper = DataPinResult.ResultValue.Get(); + OutValues = { FGameplayTagContainer::CreateFromArray(Wrapper.Values) }; + return EFlowDataPinResolveResult::Success; + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + // Base for Object, Class + template + struct FFlowObjectTraitsBase + { + using ValueType = typename TPinType::ValueType; + using WrapperType = typename TPinType::WrapperType; + using LegacyWrapperType = typename TPinType::LegacyWrapperType; + + static EFlowDataPinResolveResult ExtractFromProperty(const FProperty* Property, const void* Container, TArray& OutValues) + { + if (const FStructProperty* StructProp = CastField(Property)) + { + if (StructProp->Struct == WrapperType::StaticStruct()) + { + const WrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + for (const auto& Path : Wrapper->Values) + { + if constexpr (std::is_same_v, FSoftObjectPath> || + std::is_same_v, FSoftClassPath>) + { + OutValues.Add(Cast(Path.ResolveObject())); + } + else + { + OutValues.Add(Cast(Path)); + } + } + return EFlowDataPinResolveResult::Success; + } + + // #FlowDataPinLegacy - support sourcing from old property wrappers For Now(tm) + static const UScriptStruct* OldPropStruct = LegacyWrapperType::StaticStruct(); + if (StructProp->Struct->IsChildOf(OldPropStruct)) + { + const LegacyWrapperType* Wrapper = StructProp->ContainerPtrToValuePtr(Container); + OutValues = { Cast(Wrapper->GetObjectValue()) }; + return EFlowDataPinResolveResult::Success; + } + // -- + } + + if (const FArrayProperty* ArrProp = CastField(Property)) + { + if (const TProperty* InnerObjProp = CastField(ArrProp->Inner)) + { + FScriptArrayHelper ArrHelper(ArrProp, ArrProp->ContainerPtrToValuePtr(Container)); + const int32 Num = ArrHelper.Num(); + OutValues.Reserve(Num); + for (int32 i = 0; i < Num; ++i) + { + OutValues.Add(Cast(InnerObjProp->GetObjectPropertyValue(ArrHelper.GetRawPtr(i)))); + } + return EFlowDataPinResolveResult::Success; + } + else if (const TSoftProperty* InnerSoftProp = CastField(ArrProp->Inner)) + { + FScriptArrayHelper ArrHelper(ArrProp, ArrProp->ContainerPtrToValuePtr(Container)); + const int32 Num = ArrHelper.Num(); + OutValues.Reserve(Num); + for (int32 i = 0; i < Num; ++i) + { + const FSoftObjectPath Path = InnerSoftProp->GetPropertyValue(ArrHelper.GetRawPtr(i)).ToSoftObjectPath(); + OutValues.Add(Cast(Path.ResolveObject())); + } + return EFlowDataPinResolveResult::Success; + } + else if (const FWeakObjectProperty* InnerWeakProp = CastField(ArrProp->Inner)) + { + FScriptArrayHelper ArrHelper(ArrProp, ArrProp->ContainerPtrToValuePtr(Container)); + const int32 Num = ArrHelper.Num(); + OutValues.Reserve(Num); + for (int32 i = 0; i < Num; ++i) + { + OutValues.Add(Cast(InnerWeakProp->GetPropertyValue_InContainer(Container).Get())); + } + return EFlowDataPinResolveResult::Success; + } + } + + if (const TProperty* ObjProp = CastField(Property)) + { + OutValues = { Cast(ObjProp->GetObjectPropertyValue_InContainer(Container)) }; + return EFlowDataPinResolveResult::Success; + } + else if (const TSoftProperty* SoftObjProp = CastField(Property)) + { + const FSoftObjectPath Path = SoftObjProp->GetPropertyValue_InContainer(Container).ToSoftObjectPath(); + OutValues = { Cast(Path.ResolveObject()) }; + return EFlowDataPinResolveResult::Success; + } + else if (const FWeakObjectProperty* WeakProp = CastField(Property)) + { + OutValues = { Cast(WeakProp->GetPropertyValue_InContainer(Container).Get()) }; + return EFlowDataPinResolveResult::Success; + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + + static EFlowDataPinResolveResult ExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + if (DataPinResult.ResultValue.GetScriptStruct() == WrapperType::StaticStruct()) + { + const WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + const auto& Source = Wrapper.Values; // this is TArray or TArray + + if (SingleFromArray == EFlowSingleFromArray::EntireArray) + { + OutValues.Reserve(Source.Num()); + for (const auto& Path : Source) + { + if constexpr (std::is_same_v, FSoftObjectPath> || + std::is_same_v, FSoftClassPath>) + { + OutValues.Add(Cast(Path.ResolveObject())); + } + else + { + OutValues.Add(Cast(Path)); + } + } + } + else + { + const int32 Index = EFlowSingleFromArray_Classifiers::ConvertToIndex(SingleFromArray, Source.Num()); + if (!Source.IsValidIndex(Index)) + { + return EFlowDataPinResolveResult::FailedInsufficientValues; + } + + const auto& Path = Source[Index]; + if constexpr (std::is_same_v, FSoftObjectPath> || + std::is_same_v, FSoftClassPath>) + { + OutValues.Add(Cast(Path.ResolveObject())); + } + else + { + OutValues.Add(Cast(Path)); + } + } + + return EFlowDataPinResolveResult::Success; + } + + return EFlowDataPinResolveResult::FailedMismatchedType; + } + }; + + template <> struct FFlowDataPinValueTraits : public FFlowObjectTraitsBase {}; + template <> struct FFlowDataPinValueTraits : public FFlowObjectTraitsBase {}; + + // ----------------------------------------------------------------------- + // Value Extractors + // ----------------------------------------------------------------------- + + template + static EFlowDataPinResolveResult TryExtractValue(const FFlowDataPinResult& DataPinResult, typename TPinType::ValueType& OutValue, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + TArray Values; + const EFlowDataPinResolveResult Result = FFlowDataPinValueTraits::ExtractValues(DataPinResult, Values, SingleFromArray); + + if (!IsSuccess(Result)) + { + return Result; + } + + if (Values.IsEmpty()) + { + return EFlowDataPinResolveResult::FailedInsufficientValues; + } + + OutValue = Values[0]; + return EFlowDataPinResolveResult::Success; + } + + template + static EFlowDataPinResolveResult TryExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + return FFlowDataPinValueTraits::ExtractValues(DataPinResult, OutValues, EFlowSingleFromArray::EntireArray); + } + + // Special-case single-value extractor for enums (FName + EnumClass) + template + static EFlowDataPinResolveResult TryExtractValue(const FFlowDataPinResult& DataPinResult, typename TPinType::ValueType& OutValue, typename TPinType::FieldType*& OutField, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const typename TPinType::WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + OutField = Cast(Wrapper.GetFieldType()); + return TryExtractValue(DataPinResult, OutValue, SingleFromArray); + } + + // Special-case array-value extractor for enums (TArray + EnumClass) + template + static EFlowDataPinResolveResult TryExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues, typename TPinType::FieldType*& OutField) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const typename TPinType::WrapperType& Wrapper = DataPinResult.ResultValue.Get(); + OutField = Cast(Wrapper.GetFieldType()); + return TryExtractValues(DataPinResult, OutValues); + } + + // Special-case single-value extractor for enums (Native enum value) + template requires std::is_enum_v + static EFlowDataPinResolveResult TryExtractValue(const FFlowDataPinResult& DataPinResult, TEnumType& OutValue, EFlowSingleFromArray SingleFromArray) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const FFlowDataPinValue_Enum& Wrapper = DataPinResult.ResultValue.Get(); + return Wrapper.TryGetSingleEnumValue(OutValue, SingleFromArray); + } + + // Special-case array-value extractor for enums (Native enum values) + template requires std::is_enum_v + static EFlowDataPinResolveResult TryExtractValues(const FFlowDataPinResult& DataPinResult, TArray& OutValues) + { + if (!IsSuccess(DataPinResult.Result)) + { + return DataPinResult.Result; + } + + const FFlowDataPinValue_Enum& Wrapper = DataPinResult.ResultValue.Get(); + return Wrapper.TryGetAllNativeEnumValues(OutValues); + } +} \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowPinTypesStandard.h b/Source/Flow/Public/Types/FlowPinTypesStandard.h new file mode 100644 index 000000000..b0f87b508 --- /dev/null +++ b/Source/Flow/Public/Types/FlowPinTypesStandard.h @@ -0,0 +1,520 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowPinType.h" +#include "Types/FlowPinTypeNamesStandard.h" +#include "Nodes/FlowPin.h" +#include "Math/Vector.h" +#include "Math/Rotator.h" +#include "Math/Transform.h" +#include "GameplayTagContainer.h" +#include "StructUtils/InstancedStruct.h" +#include "UObject/Class.h" + +#if WITH_EDITOR +#include "GraphEditorSettings.h" +#endif + +#include "FlowPinTypesStandard.generated.h" + +// Forward declarations +struct FFlowDataPinValue_Bool; +struct FFlowDataPinValue_Int; +struct FFlowDataPinValue_Int64; +struct FFlowDataPinValue_Float; +struct FFlowDataPinValue_Double; +struct FFlowDataPinValue_Name; +struct FFlowDataPinValue_String; +struct FFlowDataPinValue_Text; +struct FFlowDataPinValue_Enum; +struct FFlowDataPinValue_Vector; +struct FFlowDataPinValue_Rotator; +struct FFlowDataPinValue_Transform; +struct FFlowDataPinValue_GameplayTag; +struct FFlowDataPinValue_GameplayTagContainer; +struct FFlowDataPinValue_InstancedStruct; +struct FFlowDataPinValue_Object; +struct FFlowDataPinValue_Class; + +// #FlowDataPinLegacy +struct FFlowDataPinOutputProperty_Bool; +struct FFlowDataPinOutputProperty_Int32; +struct FFlowDataPinOutputProperty_Int64; +struct FFlowDataPinOutputProperty_Float; +struct FFlowDataPinOutputProperty_Double; +struct FFlowDataPinOutputProperty_Name; +struct FFlowDataPinOutputProperty_String; +struct FFlowDataPinOutputProperty_Text; +struct FFlowDataPinOutputProperty_Enum; +struct FFlowDataPinOutputProperty_Vector; +struct FFlowDataPinOutputProperty_Rotator; +struct FFlowDataPinOutputProperty_Transform; +struct FFlowDataPinOutputProperty_GameplayTag; +struct FFlowDataPinOutputProperty_GameplayTagContainer; +struct FFlowDataPinOutputProperty_InstancedStruct; +struct FFlowDataPinOutputProperty_Object; +struct FFlowDataPinOutputProperty_Class; +// -- + +// Exec +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Exec : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = void; + using WrapperType = void; + using MainPropertyType = void; + using LegacyWrapperType = void; + +private: + static const FFlowPinTypeName PinTypeNameExec; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameExec; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameExec; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->ExecutionPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif +}; + +// Bool +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Bool : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = bool; + using WrapperType = FFlowDataPinValue_Bool; + using MainPropertyType = FBoolProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Bool; + +private: + static const FFlowPinTypeName PinTypeNameBool; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameBool; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameBool; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->BooleanPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Int +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Int : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = int32; + using WrapperType = FFlowDataPinValue_Int; + using MainPropertyType = FIntProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Int32; + +private: + static const FFlowPinTypeName PinTypeNameInt; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameInt; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameInt; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->IntPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Int64 +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Int64 : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = int64; + using WrapperType = FFlowDataPinValue_Int64; + using MainPropertyType = FInt64Property; + using LegacyWrapperType = FFlowDataPinOutputProperty_Int64; + +private: + static const FFlowPinTypeName PinTypeNameInt64; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameInt64; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameInt64; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->IntPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Float +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Float : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = float; + using WrapperType = FFlowDataPinValue_Float; + using MainPropertyType = FFloatProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Float; + +private: + static const FFlowPinTypeName PinTypeNameFloat; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameFloat; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameFloat; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->FloatPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Double +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Double : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = double; + using WrapperType = FFlowDataPinValue_Double; + using MainPropertyType = FDoubleProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Double; + +private: + static const FFlowPinTypeName PinTypeNameDouble; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameDouble; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameDouble; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->FloatPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Name +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Name : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FName; + using WrapperType = FFlowDataPinValue_Name; + using MainPropertyType = FNameProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Name; + +private: + static const FFlowPinTypeName PinTypeNameName; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameName; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameName; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->NamePinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// String +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_String : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FString; + using WrapperType = FFlowDataPinValue_String; + using MainPropertyType = FStrProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_String; + +private: + static const FFlowPinTypeName PinTypeNameString; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameString; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameString; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StringPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Text +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Text : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FText; + using WrapperType = FFlowDataPinValue_Text; + using MainPropertyType = FTextProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Text; + +private: + static const FFlowPinTypeName PinTypeNameText; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameText; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameText; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->TextPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Enum +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Enum : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FName; + using WrapperType = FFlowDataPinValue_Enum; + using MainPropertyType = FEnumProperty; + using FieldType = UEnum; + using LegacyWrapperType = FFlowDataPinOutputProperty_Enum; + +private: + static const FFlowPinTypeName PinTypeNameEnum; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameEnum; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameEnum; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Vector +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Vector : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FVector; + using WrapperType = FFlowDataPinValue_Vector; + using MainPropertyType = FStructProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Vector; + +private: + static const FFlowPinTypeName PinTypeNameVector; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameVector; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameVector; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Rotator +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Rotator : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FRotator; + using WrapperType = FFlowDataPinValue_Rotator; + using MainPropertyType = FStructProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Rotator; + +private: + static const FFlowPinTypeName PinTypeNameRotator; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameRotator; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameRotator; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Transform +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Transform : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FTransform; + using WrapperType = FFlowDataPinValue_Transform; + using MainPropertyType = FStructProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Transform; + +private: + static const FFlowPinTypeName PinTypeNameTransform; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameTransform; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameTransform; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// GameplayTag +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_GameplayTag : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FGameplayTag; + using WrapperType = FFlowDataPinValue_GameplayTag; + using MainPropertyType = FStructProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_GameplayTag; + +private: + static const FFlowPinTypeName PinTypeNameGameplayTag; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameGameplayTag; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameGameplayTag; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// GameplayTagContainer +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_GameplayTagContainer : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FGameplayTagContainer; + using WrapperType = FFlowDataPinValue_GameplayTagContainer; + using MainPropertyType = FStructProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_GameplayTagContainer; + +private: + static const FFlowPinTypeName PinTypeNameGameplayTagContainer; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameGameplayTagContainer; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameGameplayTagContainer; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } + virtual bool SupportsMultiType(EFlowDataMultiType Mode) const { FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); return (Mode == EFlowDataMultiType::Single); } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// InstancedStruct +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_InstancedStruct : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = FInstancedStruct; + using WrapperType = FFlowDataPinValue_InstancedStruct; + using MainPropertyType = FStructProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_InstancedStruct; + +private: + static const FFlowPinTypeName PinTypeNameInstancedStruct; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameInstancedStruct; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameInstancedStruct; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Object +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Object : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = TObjectPtr; + using WrapperType = FFlowDataPinValue_Object; + using MainPropertyType = FObjectProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Object; + +private: + static const FFlowPinTypeName PinTypeNameObject; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameObject; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameObject; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->ObjectPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; + + static UClass* TryGetObjectClassFromProperty(const FProperty& MetaDataProperty); + static UClass* TryGetMetaClassFromProperty(const FProperty& MetaDataProperty); +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; + +// Class +USTRUCT(BlueprintType) +struct FLOW_API FFlowPinType_Class : public FFlowPinType +{ + GENERATED_BODY() + + using ValueType = TObjectPtr; + using WrapperType = FFlowDataPinValue_Class; + using MainPropertyType = FClassProperty; + using LegacyWrapperType = FFlowDataPinOutputProperty_Class; + +private: + static const FFlowPinTypeName PinTypeNameClass; +public: + static const FFlowPinTypeName& GetPinTypeNameStatic() { return PinTypeNameClass; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinTypeNameClass; } + +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->ClassPinTypeColor; } + virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const override; + virtual UObject* GetPinSubCatetoryObjectFromProperty(const FProperty* Property, void const* InContainer, const FFlowDataPinValue* Wrapper) const override; +#endif + + virtual bool PopulateResult(const UObject& PropertyOwnerObject, const UFlowNode& Node, const FFlowPin& Pin, FFlowDataPinResult& OutResult) const override; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowStructUtils.h b/Source/Flow/Public/Types/FlowStructUtils.h new file mode 100644 index 000000000..c6054fe73 --- /dev/null +++ b/Source/Flow/Public/Types/FlowStructUtils.h @@ -0,0 +1,102 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Field.h" + +#if WITH_EDITOR +namespace FlowStructUtils +{ + template + static UScriptStruct* FindScriptStructForProperty(const FProperty& Property) + { + const FStructProperty* StructProperty = CastField(&Property); + if (!StructProperty) + { + return nullptr; + } + + UScriptStruct* ScriptStruct = TPropertyType::StaticStruct(); + + if (StructProperty->Struct == ScriptStruct) + { + static UScriptStruct* UnrealType = TBaseStructure::Get(); + return UnrealType; + } + + return StructProperty->Struct; + } + + template + TStruct* GetTypedStructValue(FProperty& Prop, void* Container) + { + static_assert(TIsDerivedFrom::IsDerived, "Must be a USTRUCT type"); + if (auto* StructProp = CastField(&Prop)) + { + if (StructProp->Struct->IsChildOf(TStruct::StaticStruct())) + { + return reinterpret_cast(StructProp + ->ContainerPtrToValuePtr(Container)); + } + } + return nullptr; + } + + // Internal SFINAE probe: will fail to compile if TStruct has no StaticStruct(). + template + struct THasStaticStruct + { + private: + template + static auto Test(int) -> decltype(U::StaticStruct(), std::true_type{}); + template + static std::false_type Test(...); + public: + static constexpr bool Value = decltype(Test(0))::value; + }; + + template + FORCEINLINE TStruct* CastStructValue(FProperty* Prop, void* Container) + { + static_assert(THasStaticStruct::Value, + "TStruct must be a USTRUCT type providing StaticStruct()."); + + if (!Prop || !Container) + return nullptr; + + FStructProperty* StructProp = CastField(Prop); + if (!StructProp) + return nullptr; + + // Check exact or derived type. + if (!StructProp->Struct->IsChildOf(TStruct::StaticStruct())) + return nullptr; + + // Retrieve the memory for this property within the container and cast. + void* ValueMem = StructProp->ContainerPtrToValuePtr(Container); + return static_cast(ValueMem); + } + + // Pointer overload (const) + template + FORCEINLINE const TStruct* CastStructValue(const FProperty* Prop, const void* Container) + { + return CastStructValue( + const_cast(Prop), + const_cast(Container)); + } + + // Reference overloads for convenience + template + FORCEINLINE TStruct* CastStructValue(FProperty& Prop, void* Container) + { + return CastStructValue(&Prop, Container); + } + + template + FORCEINLINE const TStruct* CastStructValue(const FProperty& Prop, const void* Container) + { + return CastStructValue(&Prop, Container); + } +} +#endif \ No newline at end of file diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp index 03a513c6d..ce1ef302e 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; } @@ -60,15 +60,15 @@ EAssetCommandResult UAssetDefinition_FlowAsset::PerformAssetDiff(const FAssetDif return EAssetCommandResult::Unhandled; } - const UFlowAsset* OldFlow = CastChecked(DiffArgs.OldAsset); - const UFlowAsset* NewFlow = CastChecked(DiffArgs.NewAsset); + const UFlowAsset* OldFlow = Cast(DiffArgs.OldAsset); + const UFlowAsset* NewFlow = Cast(DiffArgs.NewAsset); // sometimes we're comparing different revisions of one single asset (other // times we're comparing two completely separate assets altogether) - const bool bIsSingleAsset = (OldFlow->GetName() == NewFlow->GetName()); - + const bool bIsSingleAsset = !IsValid(OldFlow) || !IsValid(NewFlow) || (OldFlow->GetName() == NewFlow->GetName()); + static const FText BasicWindowTitle = LOCTEXT("FlowAssetDiff", "FlowAsset Diff"); - const FText WindowTitle = !bIsSingleAsset ? BasicWindowTitle : FText::Format(LOCTEXT("FlowAsset Diff", "{0} - FlowAsset Diff"), FText::FromString(NewFlow->GetName())); + const FText WindowTitle = !bIsSingleAsset ? BasicWindowTitle : FText::Format(LOCTEXT("FlowAsset Diff", "{0} - FlowAsset Diff"), FText::FromString(IsValid(NewFlow) ? NewFlow->GetName() : OldFlow->GetName())); SFlowDiff::CreateDiffWindow(WindowTitle, OldFlow, NewFlow, DiffArgs.OldRevision, DiffArgs.NewRevision); return EAssetCommandResult::Handled; diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp new file mode 100644 index 000000000..1c9748aaa --- /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/FlowDataPinValuesStandard.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..270eab9ac 100644 --- a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp +++ b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp @@ -1,10 +1,10 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #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" @@ -72,7 +72,7 @@ void FFlowObjectDiff::DiffProperties(TArray& OutProperty if (OldDetailsView.IsValid() && NewDetailsView.IsValid()) { static constexpr bool bSortByDisplayOrder = true; - OldDetailsView->DiffAgainst(*NewDetailsView.Get(), OutPropertyDiffsArray, bSortByDisplayOrder); + //OldDetailsView->DiffAgainst(*NewDetailsView.Get(), OutPropertyDiffsArray, bSortByDisplayOrder); } } diff --git a/Source/FlowEditor/Private/Asset/SFlowDiff.cpp b/Source/FlowEditor/Private/Asset/SFlowDiff.cpp index 3ec288cb0..96540b6dc 100644 --- a/Source/FlowEditor/Private/Asset/SFlowDiff.cpp +++ b/Source/FlowEditor/Private/Asset/SFlowDiff.cpp @@ -11,6 +11,7 @@ #include "Framework/Commands/GenericCommands.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/MultiBox/MultiBoxDefs.h" +#include "Graph/Nodes/FlowGraphNode.h" #include "GraphDiffControl.h" #include "HAL/PlatformApplicationMisc.h" #include "Internationalization/Text.h" @@ -86,11 +87,13 @@ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SFlowDiff::Construct(const FArguments& InArgs) { - check(InArgs._OldFlow && InArgs._NewFlow); + check(InArgs._OldFlow || InArgs._NewFlow); PanelOld.FlowAsset = InArgs._OldFlow; PanelNew.FlowAsset = InArgs._NewFlow; PanelOld.RevisionInfo = InArgs._OldRevision; PanelNew.RevisionInfo = InArgs._NewRevision; + PanelOld.bIsOldPanel = true; + PanelNew.bIsOldPanel = false; // sometimes we want to clearly identify the assets being diffed (when it's // not the same asset in each panel) @@ -330,7 +333,7 @@ TSharedPtr SFlowDiff::CreateDiffWindow(const FText WindowTitle, const U { // sometimes we're comparing different revisions of one single asset (other // times we're comparing two completely separate assets altogether) - const bool bIsSingleAsset = (NewFlow->GetName() == OldFlow->GetName()); + const bool bIsSingleAsset = !IsValid(OldFlow) || !IsValid(NewFlow) || (OldFlow->GetName() == NewFlow->GetName()); TSharedPtr Window = SNew(SWindow) .Title(WindowTitle) @@ -425,12 +428,16 @@ void SFlowDiff::OnDiffListSelectionChanged(TSharedPtr FlowO SafeClearSelection(PanelNew.GraphEditor); SafeClearSelection(PanelOld.GraphEditor); + // PanelDefaultDetailsView can be used for displaying nodes on click. Clear out it's content before potentially trying to show an empty panel. + PanelOld.PanelDefaultDetailsView->SetObject(nullptr); + PanelNew.PanelDefaultDetailsView->SetObject(nullptr); + //Select the details panel to display below the graphs. //Show an empty details panel if there is no generated details panel. const TSharedPtr OldDetailsPanel = FlowObjectDiff->OldDetailsView.IsValid() ? - FlowObjectDiff->OldDetailsView->DetailsWidget() : PanelOld.EmptyDetailsView.ToSharedRef(); + FlowObjectDiff->OldDetailsView->DetailsWidget() : PanelOld.PanelDefaultDetailsView.ToSharedRef(); const TSharedPtr NewDetailsPanel = FlowObjectDiff->NewDetailsView.IsValid() ? - FlowObjectDiff->NewDetailsView->DetailsWidget() : PanelNew.EmptyDetailsView.ToSharedRef(); + FlowObjectDiff->NewDetailsView->DetailsWidget() : PanelNew.PanelDefaultDetailsView.ToSharedRef(); GraphDiffSplitter->SetBottomLeftContent(OldDetailsPanel.ToSharedRef()); GraphDiffSplitter->SetBottomRightContent(NewDetailsPanel.ToSharedRef()); @@ -531,6 +538,8 @@ void FFlowDiffPanel::GeneratePanel(UEdGraph* Graph, TSharedPtrSetContent(Widget.ToSharedRef()); } +void FFlowDiffPanel::OnNodeClicked(UObject* ClickedNode) +{ + UFlowGraphNode* ClickedFlowGraphNode = Cast(ClickedNode); + if (IsValid(ClickedFlowGraphNode)) + { + PanelDefaultDetailsView->SetObject(ClickedFlowGraphNode->GetFlowNodeBase()); + } + else + { + PanelDefaultDetailsView->SetObject(nullptr); + } + + if (GraphDiffSplitter.IsValid()) + { + if (bIsOldPanel) + { + GraphDiffSplitter.Pin()->SetBottomLeftContent(PanelDefaultDetailsView.ToSharedRef()); + } + else + { + GraphDiffSplitter.Pin()->SetBottomRightContent(PanelDefaultDetailsView.ToSharedRef()); + } + } +} + FGraphPanelSelectionSet FFlowDiffPanel::GetSelectedNodes() const { FGraphPanelSelectionSet CurrentSelection; @@ -660,13 +694,13 @@ void SFlowDiff::HandleGraphChanged(const FString& GraphPath) }); // only regenerate PanelOld if the old graph has changed - if (!PanelOld.GraphEditor.IsValid() || GraphOld != PanelOld.GraphEditor.Pin()->GetCurrentGraph()) + if (PanelOld.FlowAsset && (!PanelOld.GraphEditor.IsValid() || GraphOld != PanelOld.GraphEditor.Pin()->GetCurrentGraph())) { PanelOld.GeneratePanel(GraphOld, DiffResults, FocusedDiffResult); } // only regenerate PanelNew if the old graph has changed - if (!PanelNew.GraphEditor.IsValid() || GraphNew != PanelNew.GraphEditor.Pin()->GetCurrentGraph()) + if (PanelNew.FlowAsset && (!PanelNew.GraphEditor.IsValid() || GraphNew != PanelNew.GraphEditor.Pin()->GetCurrentGraph())) { PanelNew.GeneratePanel(GraphNew, DiffResults, FocusedDiffResult); } @@ -695,8 +729,8 @@ void SFlowDiff::GenerateDifferencesList() return DetailsView; }; - PanelOld.EmptyDetailsView = CreateInspector(nullptr); - PanelNew.EmptyDetailsView = CreateInspector(nullptr); + PanelOld.PanelDefaultDetailsView = CreateInspector(nullptr); + PanelNew.PanelDefaultDetailsView = CreateInspector(nullptr); // Now that we have done the diffs, create the panel widgets ModePanels.Add(DetailsMode, GenerateDetailsPanel()); @@ -715,23 +749,63 @@ SFlowDiff::FDiffControl SFlowDiff::GenerateDetailsPanel() const TSharedRef Splitter = SNew(SDetailsSplitter); if (PanelOld.FlowAsset) + { + if (PanelNew.FlowAsset) + { + Splitter->AddSlot( + SDetailsSplitter::Slot() + .Value(0.5f) + .DetailsView(NewDiffControl->GetDetailsWidget(PanelOld.FlowAsset)) + .DifferencesWithRightPanel(NewDiffControl.ToSharedRef(), &FFlowAssetDiffControl::GetDifferencesWithRight, Cast(PanelOld.FlowAsset)) + ); + } + else + { + Splitter->AddSlot( + SDetailsSplitter::Slot() + .Value(0.5f) + .DetailsView(NewDiffControl->GetDetailsWidget(PanelOld.FlowAsset)) + ); + } + } + else { Splitter->AddSlot( SDetailsSplitter::Slot() .Value(0.5f) - .DetailsView(NewDiffControl->GetDetailsWidget(PanelOld.FlowAsset)) - .DifferencesWithRightPanel(NewDiffControl.ToSharedRef(), &FFlowAssetDiffControl::GetDifferencesWithRight, Cast(PanelOld.FlowAsset)) + .DetailsView(PanelOld.PanelDefaultDetailsView) ); } - if (PanelNew.FlowAsset) + + if ( PanelNew.FlowAsset) + { + if (PanelOld.FlowAsset) + { + Splitter->AddSlot( + SDetailsSplitter::Slot() + .Value(0.5f) + .DetailsView(NewDiffControl->GetDetailsWidget(PanelNew.FlowAsset)) + .DifferencesWithRightPanel(NewDiffControl.ToSharedRef(), &FFlowAssetDiffControl::GetDifferencesWithRight, Cast(PanelOld.FlowAsset)) + ); + } + else + { + Splitter->AddSlot( + SDetailsSplitter::Slot() + .Value(0.5f) + .DetailsView(NewDiffControl->GetDetailsWidget(PanelNew.FlowAsset)) + ); + } + } + else { Splitter->AddSlot( SDetailsSplitter::Slot() .Value(0.5f) - .DetailsView(NewDiffControl->GetDetailsWidget(PanelNew.FlowAsset)) - .DifferencesWithRightPanel(NewDiffControl.ToSharedRef(), &FFlowAssetDiffControl::GetDifferencesWithRight, Cast(PanelOld.FlowAsset)) + .DetailsView(PanelNew.PanelDefaultDetailsView) ); } + Ret.Widget = Splitter; return Ret; @@ -740,15 +814,24 @@ SFlowDiff::FDiffControl SFlowDiff::GenerateDetailsPanel() SFlowDiff::FDiffControl SFlowDiff::GenerateGraphPanel() { // We only have a single permanent graph in Flow Asset - GraphToDiff = MakeShared(this, PanelOld.FlowAsset->GetGraph(), PanelNew.FlowAsset->GetGraph(), PanelOld.RevisionInfo, PanelNew.RevisionInfo); + GraphToDiff = MakeShared( + this, + IsValid(PanelOld.FlowAsset) ? PanelOld.FlowAsset->GetGraph() : nullptr, + IsValid(PanelNew.FlowAsset) ? PanelNew.FlowAsset->GetGraph() : nullptr, + PanelOld.RevisionInfo, + PanelNew.RevisionInfo); GraphToDiff->GenerateTreeEntries(PrimaryDifferencesList, RealDifferences); SAssignNew(GraphDiffSplitter,SSplitter2x2) .TopLeft()[ GenerateGraphWidgetForPanel(PanelOld) ] .TopRight()[ GenerateGraphWidgetForPanel(PanelNew) ] - .BottomLeft()[ PanelOld.EmptyDetailsView.ToSharedRef() ] - .BottomRight()[ PanelNew.EmptyDetailsView.ToSharedRef() ]; + .BottomLeft()[ PanelOld.PanelDefaultDetailsView.ToSharedRef() ] + .BottomRight()[ PanelNew.PanelDefaultDetailsView.ToSharedRef() ]; + + //the panels need a pointer to GraphDiffSplitter to update DetailsViews on click of a node. + PanelOld.GraphDiffSplitter = GraphDiffSplitter; + PanelNew.GraphDiffSplitter = GraphDiffSplitter; static const FVector2D GraphPercentage = {.5f, .7f}; static const FVector2D DetailsViewPercentage = {.5f, .3f}; @@ -761,8 +844,13 @@ SFlowDiff::FDiffControl SFlowDiff::GenerateGraphPanel() return Ret; } -TSharedRef SFlowDiff::GenerateGraphWidgetForPanel(FFlowDiffPanel& OutDiffPanel) const +TSharedRef SFlowDiff::GenerateGraphWidgetForPanel(FFlowDiffPanel& OutDiffPanel) const { + if (!IsValid(OutDiffPanel.FlowAsset)) + { + return SNullWidget::NullWidget; + } + return SNew(SOverlay) + SOverlay::Slot() // Graph slot [ 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/DetailCustomizations/FlowDataPinPropertyCustomizationBase.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinPropertyCustomizationBase.cpp deleted file mode 100644 index e9e998a66..000000000 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinPropertyCustomizationBase.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "DetailCustomizations/FlowDataPinPropertyCustomizationBase.h" -#include "DetailWidgetRow.h" - -void FFlowDataPinPropertyCustomizationBase::CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - uint32 NumChildren = 0; - InStructPropertyHandle->GetNumChildren(NumChildren); - - for (uint32 ChildNum = 0; ChildNum < NumChildren; ++ChildNum) - { - TSharedPtr ChildPtr = InStructPropertyHandle->GetChildHandle(ChildNum); - - HeaderRow.NameContent() - [ - InStructPropertyHandle->CreatePropertyNameWidget() - ]; - HeaderRow.ValueContent() - [ - ChildPtr->CreatePropertyValueWidget() - ]; - - // Use the 0th child's Value Widget to replace the Header row's Value Widget - break; - } -} - -void FFlowDataPinPropertyCustomizationBase::CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - // Do not display any children -} diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ClassCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ClassCustomization.cpp deleted file mode 100644 index 30f1f7756..000000000 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ClassCustomization.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "DetailCustomizations/FlowDataPinProperty_ClassCustomization.h" -#include "DetailWidgetRow.h" -#include "Types/FlowDataPinProperties.h" -#include "EditorClassUtils.h" -#include "PropertyCustomizationHelpers.h" -#include "IDetailChildrenBuilder.h" - -void FFlowDataPinProperty_ClassCustomizationBase::CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - StructPropertyHandle = InStructPropertyHandle; - - // Based on SoftClassPtr Customization - - const FString& MustImplementName = StructPropertyHandle->GetMetaData("MustImplement"); - const bool bAllowAbstract = StructPropertyHandle->HasMetaData("AllowAbstract"); - const bool bIsBlueprintBaseOnly = StructPropertyHandle->HasMetaData("IsBlueprintBaseOnly") || StructPropertyHandle->HasMetaData("BlueprintBaseOnly"); - const bool bAllowNone = !(StructPropertyHandle->GetMetaDataProperty()->PropertyFlags & CPF_NoClear); - const bool bShowTreeView = StructPropertyHandle->HasMetaData("ShowTreeView"); - const bool bHideViewOptions = StructPropertyHandle->HasMetaData("HideViewOptions"); - const bool bShowDisplayNames = StructPropertyHandle->HasMetaData("ShowDisplayNames"); - - CachedMetaClassPtr = DeriveBestClassFilter(); - - TrySetClassFilterFromMetaData(); - - const UClass* const RequiredInterface = FEditorClassUtils::GetClassFromString(MustImplementName); - - HeaderRow - .NameContent() - [ - InStructPropertyHandle->CreatePropertyNameWidget() - ] - .ValueContent() - .MinDesiredWidth(250.0f) - .MaxDesiredWidth(0.0f) - [ - // Add a class entry box. Even though this isn't an class entry, we will simulate one - SNew(SClassPropertyEntryBox) - .MetaClass(BuildMetaClass()) - .RequiredInterface(RequiredInterface) - .AllowAbstract(bAllowAbstract) - .IsBlueprintBaseOnly(bIsBlueprintBaseOnly) - .AllowNone(bAllowNone) - .ShowTreeView(bShowTreeView) - .HideViewOptions(bHideViewOptions) - .ShowDisplayNames(bShowDisplayNames) - .SelectedClass(this, &FFlowDataPinProperty_ClassCustomizationBase::OnGetClass) - .OnSetClass(this, &FFlowDataPinProperty_ClassCustomizationBase::OnSetClass) - ]; -} - -void FFlowDataPinProperty_ClassCustomizationBase::CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - if (TSharedPtr ClassFilterHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Class, ClassFilter))) - { - StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); - - ClassFilterHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FFlowDataPinProperty_ClassCustomizationBase::OnClassFilterChanged)); - } -} - -void FFlowDataPinProperty_ClassCustomizationBase::OnClassFilterChanged() -{ - // We don't allow changing away from the class filter specified in property metadata. - // So potentially undo the change (would be better to make it non-editable if the metadata was set, but I'm not sure how to do that) - TrySetClassFilterFromMetaData(); - - UClass* MetaClass = DeriveBestClassFilter(); - - TSharedPtr ClassValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Class, Value)); - UObject* ClassValueAsObject = nullptr; - ClassValueHandle->GetValue(ClassValueAsObject); - UClass* ClassValue = Cast(ClassValueAsObject); - - if (MetaClass && ClassValue && !ClassValue->IsChildOf(MetaClass)) - { - // Clear the class value if it is not compatible with the new ClassFilter value - const UClass* NullClassPtr = nullptr; - ClassValueHandle->SetValue(NullClassPtr, EPropertyValueSetFlags::DefaultFlags); - } - - CachedMetaClassPtr = MetaClass; - - IFlowExtendedPropertyTypeCustomization::OnAnyChildPropertyChanged(); -} - -UClass* FFlowDataPinProperty_ClassCustomizationBase::DeriveBestClassFilter() const -{ - const FProperty* StructProperty = StructPropertyHandle->GetProperty(); - - if (!StructProperty) - { - return nullptr; - } - - if (UClass* MetaClass = FFlowDataPinOutputProperty_Class::TryGetMetaClassFromProperty(*StructProperty)) - { - return MetaClass; - } - - // Allow the Instance to edit the ClassFilter to override the MetaClass - if (TSharedPtr ClassFilterHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Class, ClassFilter))) - { - UObject* ExistingMetaClass = nullptr; - ClassFilterHandle->GetValue(ExistingMetaClass); - - if (ExistingMetaClass) - { - return Cast(ExistingMetaClass); - } - } - - return nullptr; -} - -void FFlowDataPinProperty_ClassCustomizationBase::TrySetClassFilterFromMetaData() -{ - const FString& MetaClassName = StructPropertyHandle->GetMetaData("MetaClass"); - - if (MetaClassName.IsEmpty()) - { - return; - } - - UClass* MetaClass = FEditorClassUtils::GetClassFromString(MetaClassName); - if (!MetaClass) - { - return; - } - - // If the class filter was set in meta data, force that value to the ClassFilter property - if (TSharedPtr ClassFilterHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Class, ClassFilter))) - { - UObject* ExistingMetaClass = nullptr; - ClassFilterHandle->GetValue(ExistingMetaClass); - - if (ExistingMetaClass != MetaClass) - { - ClassFilterHandle->SetValue(MetaClass, EPropertyValueSetFlags::DefaultFlags); - } - } -} - -const UClass* FFlowDataPinProperty_ClassCustomizationBase::OnGetClass() const -{ - FString ClassName; - - if (TSharedPtr ClassValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Class, Value))) - { - ClassValueHandle->GetValueAsFormattedString(ClassName); - } - - // Do we have a valid cached class pointer? - const UClass* Class = CachedClassPtr.Get(); - if (!Class || Class->GetPathName() != ClassName) - { - Class = FEditorClassUtils::GetClassFromString(ClassName); - CachedClassPtr = MakeWeakObjectPtr(const_cast(Class)); - } - return Class; -} - -UClass* FFlowDataPinProperty_ClassCustomizationBase::BuildMetaClass() const -{ - UClass* MetaClass = CachedMetaClassPtr.Get(); - return MetaClass ? MetaClass : UObject::StaticClass(); -} - -void FFlowDataPinProperty_ClassCustomizationBase::OnSetClass(const UClass* NewClass) -{ - if (TSharedPtr ClassValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Class, Value))) - { - if (ClassValueHandle->SetValueFromFormattedString((NewClass) ? NewClass->GetPathName() : "None") == FPropertyAccess::Result::Success) - { - CachedClassPtr = MakeWeakObjectPtr(const_cast(NewClass)); - } - } -} diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_EnumCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_EnumCustomization.cpp deleted file mode 100644 index b1b5027c5..000000000 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_EnumCustomization.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "DetailCustomizations/FlowDataPinProperty_EnumCustomization.h" -#include "Types/FlowDataPinProperties.h" -#include "Nodes/FlowPin.h" - -#include "IDetailChildrenBuilder.h" -#include "UObject/UnrealType.h" - -void FFlowDataPinProperty_EnumCustomizationBase::CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - if (TSharedPtr EnumClassHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Enum, EnumClass))) - { - StructBuilder.AddProperty(EnumClassHandle.ToSharedRef()); - } - - if (TSharedPtr EnumNameHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Enum, EnumName))) - { - StructBuilder.AddProperty(EnumNameHandle.ToSharedRef()); - - EnumNameHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FFlowDataPinProperty_EnumCustomizationBase::OnEnumNameChanged)); - } -} - -TSharedPtr FFlowDataPinProperty_EnumCustomizationBase::GetCuratedNamePropertyHandle() const -{ - check(StructPropertyHandle->IsValidHandle()); - - TSharedPtr FoundHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Enum, Value)); - check(FoundHandle); - - return FoundHandle; -} - -TArray FFlowDataPinProperty_EnumCustomizationBase::GetCuratedNameOptions() const -{ - TArray Results; - - const UEnum* Enum = GetEnumClass(); - - if (IsValid(Enum)) - { - Results = GetEnumValues(*Enum); - } - - return Results; -} - -TArray FFlowDataPinProperty_EnumCustomizationBase::GetEnumValues(const UEnum& Enum) -{ - TArray EnumValues; - - for (int Index = 0; Index < Enum.GetMaxEnumValue(); Index++) - { - if (!Enum.IsValidEnumValue(Index)) - { - continue; - } - - static const TCHAR* MetaDataKey_Hidden = TEXT("Hidden"); - if (!Enum.HasMetaData(MetaDataKey_Hidden, Index)) - { - EnumValues.Add(*Enum.GetDisplayNameTextByIndex(Index).ToString()); - } - } - - return EnumValues; -} - -void FFlowDataPinProperty_EnumCustomizationBase::SetCuratedName(const FName& NewValue) -{ - TSharedPtr ValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Enum, Value)); - - check(ValueHandle); - - ValueHandle->SetPerObjectValue(0, NewValue.ToString()); -} - -bool FFlowDataPinProperty_EnumCustomizationBase::TryGetCuratedName(FName& OutName) const -{ - if (const FFlowDataPinOutputProperty_Enum* ConfigurableEnumProperty = GetFlowDataPinEnumProperty()) - { - OutName = ConfigurableEnumProperty->Value; - - return true; - } - else - { - return false; - } -} - -void FFlowDataPinProperty_EnumCustomizationBase::OnEnumNameChanged() -{ - if (FFlowDataPinOutputProperty_Enum* FlowDataPinEnumProperty = GetFlowDataPinEnumProperty()) - { - FlowDataPinEnumProperty->OnEnumNameChanged(); - } -} - -const UEnum* FFlowDataPinProperty_EnumCustomizationBase::GetEnumClass() const -{ - if (const FFlowDataPinOutputProperty_Enum* FlowDataPinEnumProperty = GetFlowDataPinEnumProperty()) - { - return FlowDataPinEnumProperty->EnumClass; - } - - return nullptr; -} diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.cpp deleted file mode 100644 index a5f6a236a..000000000 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h" -#include "DetailWidgetRow.h" -#include "Types/FlowDataPinProperties.h" -#include "EditorClassUtils.h" -#include "IDetailChildrenBuilder.h" - -void FFlowDataPinProperty_ObjectCustomizationBase::CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - StructPropertyHandle = InStructPropertyHandle; - - CachedMetaClassPtr = DeriveBestClassFilter(); - - TrySetClassFilterFromMetaData(); - - // NOTE (gtaylor) Unfortunately, I wasn't able to get the customization filtering the object options using the ClassFilter - // (like FFlowDataPinProperty_ClassCustomizationBase does), because the object selection widget is less customizable (compared to the Class selection widget). - // Longer-term, this property customization could be improved to do this object filtering using the ClassFilter, - // but I don't have time to do that right now. - - HeaderRow - .NameContent() - [ - InStructPropertyHandle->CreatePropertyNameWidget() - ]; - - // This avoids making duplicate reset boxes - StructPropertyHandle->MarkResetToDefaultCustomized(); -} - -void FFlowDataPinProperty_ObjectCustomizationBase::CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - if (TSharedPtr ClassFilterHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Object, ClassFilter))) - { - StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); - - ClassFilterHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FFlowDataPinProperty_ObjectCustomizationBase::OnClassFilterChanged)); - } - - if (TSharedPtr ReferenceValueHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Object, ReferenceValue))) - { - StructBuilder.AddProperty(ReferenceValueHandle.ToSharedRef()); - - ReferenceValueHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FFlowDataPinProperty_ObjectCustomizationBase::OnObjectValueChanged)); - } - - if (TSharedPtr InlineValueHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Object, InlineValue))) - { - StructBuilder.AddProperty(InlineValueHandle.ToSharedRef()); - - InlineValueHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FFlowDataPinProperty_ObjectCustomizationBase::OnObjectValueChanged)); - } -} - -void FFlowDataPinProperty_ObjectCustomizationBase::OnClassFilterChanged() -{ - // We don't allow changing away from the Object filter specified in property metadata. - // So potentially undo the change (would be better to make it non-editable if the metadata was set, but I'm not sure how to do that) - TrySetClassFilterFromMetaData(); - - UClass* MetaClass = DeriveBestClassFilter(); - void* ObjectValuePropertyAsVoid = nullptr; - StructPropertyHandle->GetValueData(ObjectValuePropertyAsVoid); - FFlowDataPinOutputProperty_Object* ObjectValueProperty = static_cast(ObjectValuePropertyAsVoid); - - UObject* ObjectValue = ObjectValueProperty ? ObjectValueProperty->GetObjectValue() : nullptr; - - if (MetaClass && ObjectValue && !ObjectValue->IsA(MetaClass)) - { - TSharedPtr ReferenceObjectValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Object, ReferenceValue)); - TSharedPtr InlineObjectValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Object, InlineValue)); - - // Clear the Object value if it is not compatible with the new ClassFilter value - const UObject* NullObjectPtr = nullptr; - ReferenceObjectValueHandle->SetValue(NullObjectPtr, EPropertyValueSetFlags::DefaultFlags); - InlineObjectValueHandle->SetValue(NullObjectPtr, EPropertyValueSetFlags::DefaultFlags); - } - - CachedMetaClassPtr = MetaClass; - - IFlowExtendedPropertyTypeCustomization::OnAnyChildPropertyChanged(); -} - -void FFlowDataPinProperty_ObjectCustomizationBase::OnObjectValueChanged() -{ - OnClassFilterChanged(); -} - -UClass* FFlowDataPinProperty_ObjectCustomizationBase::DeriveBestClassFilter() const -{ - const FProperty* StructProperty = StructPropertyHandle->GetProperty(); - - if (!StructProperty) - { - return nullptr; - } - - if (UClass* MetaClass = FFlowDataPinOutputProperty_Class::TryGetMetaClassFromProperty(*StructProperty)) - { - return MetaClass; - } - - // Allow the Instance to edit the ClassFilter to override the MetaClass - if (TSharedPtr ClassFilterHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Object, ClassFilter))) - { - UObject* ExistingMetaClass = nullptr; - ClassFilterHandle->GetValue(ExistingMetaClass); - - if (ExistingMetaClass) - { - return Cast(ExistingMetaClass); - } - } - - return nullptr; -} - -void FFlowDataPinProperty_ObjectCustomizationBase::TrySetClassFilterFromMetaData() -{ - const FString& MetaClassName = StructPropertyHandle->GetMetaData("MetaClass"); - - if (MetaClassName.IsEmpty()) - { - return; - } - - UClass* MetaClass = FEditorClassUtils::GetClassFromString(MetaClassName); - if (!MetaClass) - { - return; - } - - // If the Object filter was set in meta data, force that value to the ClassFilter property - if (TSharedPtr ClassFilterHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinOutputProperty_Object, ClassFilter))) - { - UObject* ExistingMetaClass = nullptr; - ClassFilterHandle->GetValue(ExistingMetaClass); - - if (ExistingMetaClass != MetaClass) - { - ClassFilterHandle->SetValue(MetaClass, EPropertyValueSetFlags::DefaultFlags); - } - } -} - -UClass* FFlowDataPinProperty_ObjectCustomizationBase::BuildMetaClass() const -{ - return CachedMetaClassPtr.Get(); -} diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp new file mode 100644 index 000000000..db083ef78 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp @@ -0,0 +1,549 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization.h" + +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailChildrenBuilder.h" +#include "IDetailPropertyRow.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "IPropertyUtilities.h" +#include "ScopedTransaction.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Text/STextBlock.h" +#include "FlowEditorLogChannels.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "UnrealExtensions/VisibilityArrayBuilder.h" +#include "UObject/EnumProperty.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization" + +static const TCHAR HiddenMeta[] = TEXT("Hidden"); + +FText FFlowDataPinValueCustomization::GetMultiTypeTooltip() +{ + return LOCTEXT("MultiTypeTooltip", + "Select whether this Data Pin holds a Single value or an Array of values.\n" + "Changing from Array to Single trims the array to the first element."); +} +FText FFlowDataPinValueCustomization::GetInputPinTooltip() +{ + return LOCTEXT("InputPinTooltip", + "Marks this Data Pin as an Input.\nChecked = Input Pin, Unchecked = Output Pin."); +} + +TSharedRef FFlowDataPinValueCustomization::MakeInstance() +{ + return MakeShareable(new FFlowDataPinValueCustomization()); +} + +void FFlowDataPinValueCustomization::CustomizeHeader(TSharedRef InStructPropertyHandle, + FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + Super::CustomizeHeader(InStructPropertyHandle, HeaderRow, StructCustomizationUtils); + + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + CacheOwnerInterface(); + CacheArraySupported(); + + // Populate MultiTypeOptions from enum (respect bArraySupported) + MultiTypeOptions.Reset(); + if (const UEnum* MultiTypeEnum = StaticEnum()) + { + const int32 NumEnums = FMath::Min(static_cast(FlowEnum::MaxOf()), MultiTypeEnum->NumEnums()); + for (int32 i = 0; i < NumEnums; ++i) + { + if (MultiTypeEnum->HasMetaData(HiddenMeta, i)) + { + continue; + } + const int64 Value = MultiTypeEnum->GetValueByIndex(i); + EFlowDataMultiType MT = static_cast(Value); + if (!bArraySupported && MT == EFlowDataMultiType::Array) + { + continue; + } + MultiTypeOptions.Add(MakeShareable(new int32(static_cast(Value)))); + } + } + + // If current mode is Array but unsupported, force Single (non-transactable) + if (!bArraySupported && MultiTypeHandle.IsValid()) + { + uint8 CurrentValue = 0; + if (MultiTypeHandle->GetValue(CurrentValue) == FPropertyAccess::Success && + static_cast(CurrentValue) == EFlowDataMultiType::Array) + { + MultiTypeHandle->SetValue(static_cast(EFlowDataMultiType::Single), + EPropertyValueSetFlags::NotTransactable); + } + + if (MultiTypeComboBox.IsValid()) + { + MultiTypeComboBox->SetEnabled(false); + } + } + + // Select current + const EFlowDataMultiType CurrentType = GetCurrentMultiType(); + for (auto& Opt : MultiTypeOptions) + { + if (Opt.IsValid() && static_cast(*Opt) == CurrentType) + { + SelectedMultiType = Opt; + break; + } + } + + TSharedRef HeaderBox = SNew(SHorizontalBox); + + // MultiType control (combo or static label if array unsupported) + if (bArraySupported) + { + HeaderBox->AddSlot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(MultiTypeComboBox, SComboBox>) + .OptionsSource(&MultiTypeOptions) + .OnGenerateWidget(this, &FFlowDataPinValueCustomization::GenerateMultiTypeWidget) + .OnSelectionChanged(this, &FFlowDataPinValueCustomization::OnMultiTypeChanged) + .IsEnabled(this, &FFlowDataPinValueCustomization::GetInputPinCheckboxEnabled) + .ToolTipText(GetMultiTypeTooltip()) + .Content() + [ + SNew(STextBlock) + .Text(this, &FFlowDataPinValueCustomization::GetSelectedMultiTypeText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ]; + } + else + { + HeaderBox->AddSlot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("MultiTypeForcedSingle", "Single")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("MultiTypeForcedSingleTooltip", "This pin type does not support Array mode.")) + ]; + } + + // Input Pin checkbox + HeaderBox->AddSlot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4.f, 0.f) + [ + SNew(SCheckBox) + .IsChecked(this, &FFlowDataPinValueCustomization::GetCurrentIsInputPin) + .OnCheckStateChanged(this, &FFlowDataPinValueCustomization::OnInputPinChanged) + .IsEnabled(this, &FFlowDataPinValueCustomization::GetInputPinCheckboxEnabled) + .Visibility(this, &FFlowDataPinValueCustomization::GetInputPinCheckboxVisibility) + .ToolTipText(GetInputPinTooltip()) + [ + SNew(STextBlock) + .Text(LOCTEXT("InputPin", "Input Pin")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ]; + + HeaderRow + .NameContent() + [ + SNew(STextBlock) + .Text(StructPropertyHandle->GetPropertyDisplayName()) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + HeaderBox + ]; +} + +void FFlowDataPinValueCustomization::CustomizeChildren(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + BuildValueRows(InStructPropertyHandle, StructBuilder, StructCustomizationUtils); +} + +void FFlowDataPinValueCustomization::BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + CacheArraySupported(); + + if (!ValuesHandle.IsValid()) + { + return; + } + + if (bArraySupported) + { + EnsureSingleElementExists(); + } + + BuildSingleBranch(StructBuilder); + if (bArraySupported) + { + BuildArrayBranch(StructBuilder); + } +} + +void FFlowDataPinValueCustomization::BuildSingleBranch(IDetailChildrenBuilder& StructBuilder) +{ + if (GetSingleModeVisibility() == EVisibility::Collapsed) + { + return; + } + + if (!ValuesHandle.IsValid()) + { + return; + } + + TSharedPtr ValueToShow = bArraySupported + ? ValuesHandle->GetChildHandle(0) + : ValuesHandle; + + if (!ValueToShow.IsValid()) + { + return; + } + + IDetailPropertyRow& Row = StructBuilder.AddProperty(ValueToShow.ToSharedRef()); + Row.ShouldAutoExpand(true); +} + +void FFlowDataPinValueCustomization::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) +{ + if (GetArrayModeVisibility() == EVisibility::Collapsed) + { + return; + } + + if (bArraySupported && ValuesHandle.IsValid() && ValuesHandle->AsArray()) + { + IDetailPropertyRow& Row = StructBuilder.AddProperty(ValuesHandle.ToSharedRef()); + Row.ShouldAutoExpand(true); + } +} + +void FFlowDataPinValueCustomization::RequestRefresh() +{ + if (PropertyUtilities.IsValid()) + { + PropertyUtilities->RequestRefresh(); + } +} + +void FFlowDataPinValueCustomization::EnsureSingleElementExists() +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + if (bArraySupported) + { + if (auto AsArray = ValuesHandle->AsArray()) + { + uint32 Num = 0; + AsArray->GetNumElements(Num); + if (Num == 0) + { + AsArray->AddItem(); + } + } + } +} + +void FFlowDataPinValueCustomization::CacheHandles(const TSharedRef& PropertyHandle, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CustomizationUtils = &StructCustomizationUtils; + MultiTypeHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue, MultiType)); + IsInputPinHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue, bIsInputPin)); + PropertyUtilities = StructCustomizationUtils.GetPropertyUtilities(); + + if (auto* Value = GetFlowDataPinValueBeingEdited()) + { + DataPinType = Value->LookupPinType(); + if (DataPinType) + { + ValuesHandle = DataPinType->GetValuesHandle(PropertyHandle); + } + } +} + +void FFlowDataPinValueCustomization::CacheOwnerInterface() +{ + OwnerInterface = nullptr; + TArray Outers; + StructPropertyHandle->GetOuterObjects(Outers); + + if (Outers.Num() == 1) + { + OwnerInterface = Cast(Outers[0]); + } +} + +void FFlowDataPinValueCustomization::CacheArraySupported() +{ + bArraySupported = DataPinType ? DataPinType->SupportsMultiType(EFlowDataMultiType::Array) : true; +} + +void FFlowDataPinValueCustomization::OnMultiTypeChanged(TSharedPtr NewSelection, ESelectInfo::Type) +{ + if (!NewSelection.IsValid() || !MultiTypeHandle.IsValid()) + { + return; + } + + if (!bArraySupported) + { + return; + } + + const EFlowDataMultiType NewType = static_cast(*NewSelection); + + bool bNeedsTrim = (NewType == EFlowDataMultiType::Single); + if (bNeedsTrim && ValuesHandle.IsValid()) + { + if (auto AsArray = ValuesHandle->AsArray()) + { + uint32 NumElements = 0; + AsArray->GetNumElements(NumElements); + bNeedsTrim = NumElements > 1; + } + } + + FScopedTransaction Transaction(LOCTEXT("ChangePinMultiType", "Change Pin MultiType")); + MultiTypeHandle->NotifyPreChange(); + MultiTypeHandle->SetValue(static_cast(NewType)); + if (bNeedsTrim) + { + TrimArrayToSingle(); + } + MultiTypeHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + + SelectedMultiType = NewSelection; + + // Preferred: trigger owner rebuild +#if WITH_EDITOR + if (OwnerInterface) + { + OwnerInterface->RequestFlowDataPinValuesDetailsRebuild(); + } + else + { + RequestRefresh(); + } +#endif +} + +void FFlowDataPinValueCustomization::OnInputPinChanged(ECheckBoxState NewState) +{ + if (!IsInputPinHandle.IsValid()) + { + return; + } + + bool Existing = false; + IsInputPinHandle->GetValue(Existing); + const bool bNewValue = NewState == ECheckBoxState::Checked; + + if (Existing == bNewValue) + { + return; + } + + FScopedTransaction Transaction(LOCTEXT("ChangeInputPin", "Change Input Pin")); + IsInputPinHandle->NotifyPreChange(); + IsInputPinHandle->SetValue(bNewValue); + IsInputPinHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + + RequestRefresh(); +} + +void FFlowDataPinValueCustomization::TrimArrayToSingle() +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + if (auto AsArray = ValuesHandle->AsArray()) + { + uint32 NumElements = 0; + AsArray->GetNumElements(NumElements); + + if (NumElements == 0) + { + AsArray->AddItem(); + } + else + { + while (NumElements > 1) + { + AsArray->DeleteItem(NumElements - 1); + AsArray->GetNumElements(NumElements); + } + } + + RequestRefresh(); + } +} + +EFlowDataMultiType FFlowDataPinValueCustomization::GetCurrentMultiType() const +{ + if (MultiTypeHandle.IsValid()) + { + uint8 Value = 0; + if (MultiTypeHandle->GetValue(Value) == FPropertyAccess::Success) + { + return static_cast(Value); + } + } + return EFlowDataMultiType::Single; +} + +ECheckBoxState FFlowDataPinValueCustomization::GetCurrentIsInputPin() const +{ + if (IsInputPinHandle.IsValid()) + { + bool Value = false; + IsInputPinHandle->GetValue(Value); + return Value ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + return ECheckBoxState::Unchecked; +} + +EVisibility FFlowDataPinValueCustomization::GetSingleModeVisibility() const +{ + return GetCurrentMultiType() == EFlowDataMultiType::Single ? EVisibility::Visible : EVisibility::Collapsed; +} + +EVisibility FFlowDataPinValueCustomization::GetArrayModeVisibility() const +{ + if (!bArraySupported) + { + return EVisibility::Collapsed; + } + return GetCurrentMultiType() == EFlowDataMultiType::Array ? EVisibility::Visible : EVisibility::Collapsed; +} + +EVisibility FFlowDataPinValueCustomization::GetInputPinCheckboxVisibility() const +{ + return OwnerInterface && OwnerInterface->ShowFlowDataPinValueInputPinCheckbox() + ? EVisibility::Visible + : EVisibility::Collapsed; +} + +bool FFlowDataPinValueCustomization::GetInputPinCheckboxEnabled() const +{ + return OwnerInterface ? OwnerInterface->CanModifyFlowDataPinType() : true; +} + +TSharedRef FFlowDataPinValueCustomization::GenerateMultiTypeWidget(TSharedPtr Item) const +{ + const UEnum* MultiTypeEnum = StaticEnum(); + return SNew(STextBlock) + .Text(Item.IsValid() && MultiTypeEnum + ? MultiTypeEnum->GetDisplayNameTextByValue(*Item) + : FText::GetEmpty()) + .Font(IDetailLayoutBuilder::GetDetailFont()); +} + +FText FFlowDataPinValueCustomization::GetSelectedMultiTypeText() const +{ + const UEnum* MultiTypeEnum = StaticEnum(); + return (SelectedMultiType.IsValid() && MultiTypeEnum) + ? MultiTypeEnum->GetDisplayNameTextByValue(*SelectedMultiType) + : FText::GetEmpty(); +} + +void FFlowDataPinValueCustomization::BuildVisibilityAwareArray( + IDetailChildrenBuilder& StructBuilder, + TSharedPtr ArrayHandle, + TFunction, int32, IDetailChildrenBuilder&, const TAttribute&)> Generator, + TAttribute VisibilityAttribute) +{ + if (!ArrayHandle.IsValid() || !bArraySupported) + { + return; + } + + TSharedRef ArrayBuilder = + MakeShareable(new FVisibilityArrayBuilder(ArrayHandle.ToSharedRef(), true, true, true)); + + ArrayBuilder->SetVisibilityGetter([VisibilityAttribute]() + { + return VisibilityAttribute.Get(); + }); + + ArrayBuilder->OnGenerateArrayElementWidget( + FOnGenerateArrayElementWidgetVisible::CreateLambda( + [Generator](TSharedRef Elem, int32 Index, IDetailChildrenBuilder& Child, const TAttribute& RowVis) + { + Generator(Elem, Index, Child, RowVis); + })); + + StructBuilder.AddCustomBuilder(ArrayBuilder); +} + +void FFlowDataPinValueCustomization::ValidateArrayElements(TSharedPtr ArrayHandle, + TFunction)> IsValidPredicate, + TFunction)> InvalidateAction) +{ + if (!ArrayHandle.IsValid()) + { + return; + } + + auto AsArray = ArrayHandle->AsArray(); + if (!AsArray.IsValid()) + { + return; + } + + uint32 Num = 0; + AsArray->GetNumElements(Num); + + TArray> ToInvalidate; + ToInvalidate.Reserve(Num); + + for (uint32 i = 0; i < Num; ++i) + { + TSharedPtr Elem = ArrayHandle->GetChildHandle(i); + if (!Elem.IsValid()) + { + continue; + } + if (!IsValidPredicate(Elem)) + { + ToInvalidate.Add(Elem); + } + } + + if (ToInvalidate.Num() > 0) + { + const FScopedTransaction Tx(LOCTEXT("InvalidateArrayElements", "Clear Invalid Data Pin Values")); + for (auto& H : ToInvalidate) + { + if (H.IsValid()) + { + InvalidateAction(H); + } + } + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Class.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Class.cpp new file mode 100644 index 000000000..0974a1048 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Class.cpp @@ -0,0 +1,386 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization_Class.h" + +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailChildrenBuilder.h" +#include "PropertyHandle.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "EditorClassUtils.h" +#include "UObject/SoftObjectPath.h" +#include "UnrealExtensions/VisibilityArrayBuilder.h" +#include "IPropertyUtilities.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "ScopedTransaction.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_Class" + +FFlowDataPinValue_Class* FFlowDataPinValueCustomization_Class::GetValueStruct() const +{ + return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); +} + +bool FFlowDataPinValueCustomization_Class::ShouldShowSourceRow() const +{ + return OwnerInterface ? OwnerInterface->ShowFlowDataPinValueClassFilter(GetValueStruct()) : true; +} + +bool FFlowDataPinValueCustomization_Class::IsSourceEditable() const +{ + if (bHasMetaClass) + { + return false; // forced meta class: show disabled + } + return OwnerInterface ? OwnerInterface->CanEditFlowDataPinValueClassFilter(GetValueStruct()) : true; +} + +void FFlowDataPinValueCustomization_Class::BuildValueRows( + TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + CacheArraySupported(); // from base + if (!ValuesHandle.IsValid()) + { + return; + } + + ClassFilterHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Class, ClassFilter)); + + TrySetClassFilterFromMetaData(); + ExtractMetadata(); + RefreshEffectiveFilter(); + + const bool bShowSource = ShouldShowSourceRow(); + if (bShowSource) + { + BuildClassFilterRow(StructBuilder, IsSourceEditable()); + } + + EnsureSingleElementExists(); + BuildSingleBranch(StructBuilder); + if (bArraySupported) + { + BuildArrayBranch(StructBuilder); + } + + BindDelegates(); + ValidateAllElements(); +} + +void FFlowDataPinValueCustomization_Class::ExtractMetadata() +{ + if (!StructPropertyHandle.IsValid()) + { + return; + } + + const FString& MustImplement = StructPropertyHandle->GetMetaData(TEXT("MustImplement")); + RequiredInterface = FEditorClassUtils::GetClassFromString(MustImplement); + + bAllowAbstract = StructPropertyHandle->HasMetaData(TEXT("AllowAbstract")); + bIsBlueprintBaseOnly = StructPropertyHandle->HasMetaData(TEXT("IsBlueprintBaseOnly")) || + StructPropertyHandle->HasMetaData(TEXT("BlueprintBaseOnly")); + bShowTreeView = StructPropertyHandle->HasMetaData(TEXT("ShowTreeView")); + bHideViewOptions = StructPropertyHandle->HasMetaData(TEXT("HideViewOptions")); + bShowDisplayNames = StructPropertyHandle->HasMetaData(TEXT("ShowDisplayNames")); + + bHasMetaClass = !StructPropertyHandle->GetMetaData(TEXT("MetaClass")).IsEmpty(); + + if (const FProperty* MetaProp = StructPropertyHandle->GetMetaDataProperty()) + { + bAllowNone = !(MetaProp->PropertyFlags & CPF_NoClear); + } + else + { + bAllowNone = true; + } +} + +void FFlowDataPinValueCustomization_Class::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder, bool bSourceEditable) +{ + if (!ClassFilterHandle.IsValid()) + { + return; + } + + IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); + Row.DisplayName(LOCTEXT("ClassFilterLabel", "Class Filter")); + Row.IsEnabled(bSourceEditable); + Row.ToolTip(bHasMetaClass + ? LOCTEXT("ClassFilterMetaTooltip", "Class Filter is fixed by MetaClass metadata and cannot be edited.") + : LOCTEXT("ClassFilterTooltip", "Class Filter constrains which classes can be selected.")); +} + +void FFlowDataPinValueCustomization_Class::BuildSingleBranch(IDetailChildrenBuilder& StructBuilder) +{ + auto First = ValuesHandle->GetChildHandle(0); + if (!First.IsValid()) + { + return; + } + + StructBuilder.AddCustomRow(LOCTEXT("ClassSingleSearch", "Class")) + .Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_Class::GetSingleModeVisibility)) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("ClassValueLabel", "Class")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + SNew(SClassPropertyEntryBox) + .MetaClass(CachedEffectiveFilter.Get() ? CachedEffectiveFilter.Get() : UObject::StaticClass()) + .RequiredInterface(RequiredInterface) + .AllowAbstract(bAllowAbstract) + .IsBlueprintBaseOnly(bIsBlueprintBaseOnly) + .AllowNone(bAllowNone) + .ShowTreeView(bShowTreeView) + .HideViewOptions(bHideViewOptions) + .ShowDisplayNames(bShowDisplayNames) + .IsEnabled(AreValuesEditable()) + .SelectedClass_Lambda([this, First]() -> const UClass* + { + return GetSelectedClassForHandle(First); + }) + .OnSetClass_Lambda([this, First](const UClass* NewClass) + { + OnSetClassForHandle(NewClass, First); + }) + ]; +} + +void FFlowDataPinValueCustomization_Class::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) +{ + BuildVisibilityAwareArray(StructBuilder, + ValuesHandle, + [this](TSharedRef ElementHandle, int32 Index, IDetailChildrenBuilder& ChildBuilder, const TAttribute& RowVis) + { + IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); + Row.Visibility(RowVis); + + Row.CustomWidget() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("ClassArrayElemLabelFmt", "Class {0}"), FText::AsNumber(Index))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + SNew(SClassPropertyEntryBox) + .MetaClass(CachedEffectiveFilter.Get() ? CachedEffectiveFilter.Get() : UObject::StaticClass()) + .RequiredInterface(RequiredInterface) + .AllowAbstract(bAllowAbstract) + .IsBlueprintBaseOnly(bIsBlueprintBaseOnly) + .AllowNone(bAllowNone) + .ShowTreeView(bShowTreeView) + .HideViewOptions(bHideViewOptions) + .ShowDisplayNames(bShowDisplayNames) + .IsEnabled(AreValuesEditable()) + .SelectedClass_Lambda([this, ElementHandle]() -> const UClass* + { + return GetSelectedClassForHandle(ElementHandle); + }) + .OnSetClass_Lambda([this, ElementHandle](const UClass* NewClass) + { + OnSetClassForHandle(NewClass, ElementHandle); + }) + ]; + }, + TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_Class::GetArrayModeVisibility)); +} + +void FFlowDataPinValueCustomization_Class::BindDelegates() +{ + if (ClassFilterHandle.IsValid()) + { + ClassFilterHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Class::OnClassFilterChanged)); + } + if (ValuesHandle.IsValid()) + { + ValuesHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Class::OnValuesChanged)); + } +} + +void FFlowDataPinValueCustomization_Class::OnClassFilterChanged() +{ + RefreshEffectiveFilter(); + ValidateAllElements(); + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +void FFlowDataPinValueCustomization_Class::OnValuesChanged() +{ + ValidateAllElements(); +} + +void FFlowDataPinValueCustomization_Class::TrySetClassFilterFromMetaData() +{ + if (!StructPropertyHandle.IsValid() || !ClassFilterHandle.IsValid()) + { + return; + } + + const FString& MetaClassName = StructPropertyHandle->GetMetaData(TEXT("MetaClass")); + if (MetaClassName.IsEmpty()) + { + return; + } + + if (UClass* MetaClass = FEditorClassUtils::GetClassFromString(MetaClassName)) + { + UObject* Existing = nullptr; + ClassFilterHandle->GetValue(Existing); + if (Existing != MetaClass) + { + ClassFilterHandle->SetValue(MetaClass, EPropertyValueSetFlags::DefaultFlags); + } + } +} + +UClass* FFlowDataPinValueCustomization_Class::DeriveBestClassFilter() const +{ + if (!StructPropertyHandle.IsValid()) + { + return nullptr; + } + + const FString& MetaClassName = StructPropertyHandle->GetMetaData(TEXT("MetaClass")); + if (!MetaClassName.IsEmpty()) + { + if (UClass* MetaClass = FEditorClassUtils::GetClassFromString(MetaClassName)) + { + return MetaClass; + } + } + + if (ClassFilterHandle.IsValid()) + { + UObject* Raw = nullptr; + if (ClassFilterHandle->GetValue(Raw) == FPropertyAccess::Success && Raw) + { + return Cast(Raw); + } + } + + return nullptr; +} + +void FFlowDataPinValueCustomization_Class::RefreshEffectiveFilter() +{ + CachedEffectiveFilter = DeriveBestClassFilter(); +} + +void FFlowDataPinValueCustomization_Class::ValidateAllElements() +{ + ValidateArrayElements(ValuesHandle, + [this](TSharedPtr Elem) + { + return IsElementValid(Elem); + }, + [](TSharedPtr Elem) + { + if (Elem.IsValid()) + { + Elem->SetValueFromFormattedString(TEXT("None")); + } + }); +} + +bool FFlowDataPinValueCustomization_Class::IsElementValid(TSharedPtr ElementHandle) const +{ + if (!ElementHandle.IsValid()) + { + return true; + } + + UClass* FilterClass = CachedEffectiveFilter.Get(); + if (!FilterClass) + { + return true; + } + + FString Path; + if (!GetElementPathString(ElementHandle, Path) || IsNoneString(Path)) + { + return true; + } + + FSoftClassPath SCP(Path); + if (UClass* Loaded = SCP.TryLoadClass()) + { + return Loaded->IsChildOf(FilterClass); + } + return false; +} + +const UClass* FFlowDataPinValueCustomization_Class::GetSelectedClassForHandle(TSharedPtr ElementHandle) const +{ + if (!ElementHandle.IsValid()) + { + return nullptr; + } + + FString Path; + if (ElementHandle->GetValueAsFormattedString(Path) != FPropertyAccess::Success || IsNoneString(Path)) + { + return nullptr; + } + return FEditorClassUtils::GetClassFromString(Path); +} + +void FFlowDataPinValueCustomization_Class::OnSetClassForHandle(const UClass* NewClass, TSharedPtr ElementHandle) +{ + if (!ElementHandle.IsValid()) + { + return; + } + + const UClass* Filter = CachedEffectiveFilter.Get(); + if (Filter && NewClass && !NewClass->IsChildOf(Filter)) + { + NewClass = nullptr; + } + + FString Current; + ElementHandle->GetValueAsFormattedString(Current); + const FString NewValue = NewClass ? NewClass->GetPathName() : TEXT("None"); + if (Current == NewValue) + { + return; + } + + FScopedTransaction Tx(LOCTEXT("SetClassArrayElement", "Set Class Value")); + ElementHandle->SetValueFromFormattedString(NewValue); +} + +bool FFlowDataPinValueCustomization_Class::GetElementPathString(const TSharedPtr& ElementHandle, FString& OutPath) const +{ + if (!ElementHandle.IsValid()) + { + return false; + } + return ElementHandle->GetValueAsFormattedString(OutPath) == FPropertyAccess::Success; +} + +bool FFlowDataPinValueCustomization_Class::IsNoneString(const FString& Str) +{ + return Str.IsEmpty() || Str.Equals(TEXT("None"), ESearchCase::IgnoreCase); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp new file mode 100644 index 000000000..49269a4e5 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp @@ -0,0 +1,442 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization_Enum.h" + +#include "Types/FlowDataPinValuesStandard.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailChildrenBuilder.h" +#include "IPropertyUtilities.h" +#include "PropertyHandle.h" +#include "ScopedTransaction.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "UnrealExtensions/VisibilityArrayBuilder.h" + +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_Enum" + +FFlowDataPinValue_Enum* FFlowDataPinValueCustomization_Enum::GetEnumValueStruct() const +{ + return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); +} + +bool FFlowDataPinValueCustomization_Enum::ShouldShowSourceRow() const +{ + return OwnerInterface ? OwnerInterface->ShowFlowDataPinValueClassFilter(GetEnumValueStruct()) : true; +} + +bool FFlowDataPinValueCustomization_Enum::IsSourceEditable() const +{ + return OwnerInterface ? OwnerInterface->CanEditFlowDataPinValueClassFilter(GetEnumValueStruct()) : true; +} + +void FFlowDataPinValueCustomization_Enum::BuildValueRows( + TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + CacheArraySupported(); // base + CacheEnumHandles(InStructPropertyHandle); + + if (!bMultiTypeDelegateBound && MultiTypeHandle.IsValid()) + { + MultiTypeHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Enum::OnMultiTypeChanged)); + bMultiTypeDelegateBound = true; + } + + const bool bShowSource = ShouldShowSourceRow(); + const bool bSourceEditable = IsSourceEditable(); + + if (bShowSource && EnumClassHandle.IsValid()) + { + IDetailPropertyRow& RowEnumClass = StructBuilder.AddProperty(EnumClassHandle.ToSharedRef()); + RowEnumClass.IsEnabled(bSourceEditable); + RowEnumClass.ToolTip(GetEnumSourceTooltip()); + EnumClassHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Enum::OnEnumSourceChanged)); + } + + if (bShowSource && EnumNameHandle.IsValid()) + { + IDetailPropertyRow& RowEnumClassName = StructBuilder.AddProperty(EnumNameHandle.ToSharedRef()); + RowEnumClassName.IsEnabled(bSourceEditable); + RowEnumClassName.ToolTip(LOCTEXT("EnumNameTooltip", "Name of native C++ enum type to derive EnumClass.")); + EnumNameHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Enum::OnEnumSourceChanged)); + } + + RebuildEnumData(); + EnsureSingleElementExists(); + BuildSingle(StructBuilder); + if (bArraySupported) + { + BuildArray(StructBuilder); + } +} + +void FFlowDataPinValueCustomization_Enum::CacheEnumHandles(const TSharedRef& StructHandle) +{ + EnumClassHandle = StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, EnumClass)); + EnumNameHandle = StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, EnumName)); +} + +void FFlowDataPinValueCustomization_Enum::OnEnumSourceChanged() +{ + RebuildEnumData(); + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +void FFlowDataPinValueCustomization_Enum::RebuildEnumData() +{ + EnumeratorOptions.Reset(); + bEnumResolved = false; + + if (FFlowDataPinValue_Enum* EnumStruct = GetEnumValueStruct()) + { + EnumStruct->OnEnumNameChanged(); + } + + if (UEnum* EnumObj = ResolveEnum()) + { + CollectEnumerators(*EnumObj); + bEnumResolved = EnumeratorOptions.Num() > 0; + } + + ValidateStoredValues(); +} + +UEnum* FFlowDataPinValueCustomization_Enum::ResolveEnum() const +{ + const FFlowDataPinValue_Enum* Data = GetEnumValueStruct(); + return Data ? Data->EnumClass.LoadSynchronous() : nullptr; +} + +void FFlowDataPinValueCustomization_Enum::CollectEnumerators(UEnum& EnumObj) +{ + const int32 Max = EnumObj.GetMaxEnumValue(); + static const TCHAR* HiddenKey = TEXT("Hidden"); + + for (int32 Index = 0; Index < Max; ++Index) + { + if (!EnumObj.IsValidEnumValue(Index)) + { + continue; + } + if (EnumObj.HasMetaData(HiddenKey, Index)) + { + continue; + } + + const FText Display = EnumObj.GetDisplayNameTextByIndex(Index); + EnumeratorOptions.Add(MakeShared(*Display.ToString())); + } +} + +void FFlowDataPinValueCustomization_Enum::ValidateStoredValues() +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + TArray ValidNames; + for (auto& Opt : EnumeratorOptions) + { + if (Opt.IsValid()) + { + ValidNames.Add(*Opt); + } + } + + if (auto AsArray = ValuesHandle->AsArray()) + { + uint32 Count = 0; + AsArray->GetNumElements(Count); + + if (GetSingleModeVisibility() == EVisibility::Visible && Count == 0) + { + AsArray->AddItem(); + AsArray->GetNumElements(Count); + } + + for (uint32 i = 0; i < Count; ++i) + { + auto Elem = ValuesHandle->GetChildHandle(i); + if (!Elem.IsValid()) + { + continue; + } + + FName Current; + if (Elem->GetValue(Current) == FPropertyAccess::Success) + { + if (!IsValueValid(Current)) + { + Elem->SetValue(ValidNames.Num() > 0 ? ValidNames[0] : FName(NAME_None)); + } + } + } + } +} + +bool FFlowDataPinValueCustomization_Enum::IsValueValid(const FName& Candidate) const +{ + if (Candidate.IsNone()) + { + return EnumeratorOptions.Num() == 0; + } + for (auto& Opt : EnumeratorOptions) + { + if (Opt.IsValid() && *Opt == Candidate) + { + return true; + } + } + return false; +} + +TSharedPtr FFlowDataPinValueCustomization_Enum::FindEnumeratorMatch(const FName& Current) const +{ + for (auto& Opt : EnumeratorOptions) + { + if (Opt.IsValid() && *Opt == Current) + { + return Opt; + } + } + return nullptr; +} + +void FFlowDataPinValueCustomization_Enum::BuildSingle(IDetailChildrenBuilder& StructBuilder) +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + auto First = ValuesHandle->GetChildHandle(0); + if (!First.IsValid()) + { + if (auto AsArray = ValuesHandle->AsArray()) + { + AsArray->AddItem(); + First = ValuesHandle->GetChildHandle(0); + } + } + + if (!First.IsValid()) + { + return; + } + + StructBuilder.AddCustomRow(LOCTEXT("EnumSingleSearch", "Value")) + .Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_Enum::GetSingleModeVisibility)) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("EnumValueLabel", "Value")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(200.f) + [ + SNew(SComboBox>) + .OptionsSource(&EnumeratorOptions) + .OnGenerateWidget(this, &FFlowDataPinValueCustomization_Enum::GenerateEnumeratorWidget) + .OnSelectionChanged(this, &FFlowDataPinValueCustomization_Enum::OnSingleValueChanged, First) + .IsEnabled(this, &FFlowDataPinValueCustomization_Enum::IsValueEditingEnabled) + .InitiallySelectedItem([this, First]() + { + FName Current; + if (First->GetValue(Current) == FPropertyAccess::Success) + { + return FindEnumeratorMatch(Current); + } + return EnumeratorOptions.Num() > 0 ? EnumeratorOptions[0] : nullptr; + }()) + .Content() + [ + SNew(STextBlock) + .Text_Lambda([this, First]() + { + FName Current; + if (First->GetValue(Current) == FPropertyAccess::Success && !Current.IsNone()) + { + return GetEnumeratorDisplayText(Current); + } + return LOCTEXT("EnumNonePlaceholder", ""); + }) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(GetEnumSourceTooltip()) + ] + ]; +} + +void FFlowDataPinValueCustomization_Enum::BuildArray(IDetailChildrenBuilder& StructBuilder) +{ + BuildVisibilityAwareArray(StructBuilder, + ValuesHandle, + [this](TSharedRef ElementHandle, int32 Index, IDetailChildrenBuilder& ChildBuilder, const TAttribute& RowVis) + { + IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); + Row.Visibility(RowVis); + + Row.CustomWidget() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::AsNumber(Index)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(200.f) + [ + SNew(SComboBox>) + .OptionsSource(&EnumeratorOptions) + .OnGenerateWidget(this, &FFlowDataPinValueCustomization_Enum::GenerateEnumeratorWidget) + .OnSelectionChanged(this, + &FFlowDataPinValueCustomization_Enum::OnArrayElementChanged, + TSharedPtr(ElementHandle)) + .IsEnabled(this, &FFlowDataPinValueCustomization_Enum::IsValueEditingEnabled) + .InitiallySelectedItem([this, ElementHandle]() + { + FName Current; + if (ElementHandle->GetValue(Current) == FPropertyAccess::Success) + { + return FindEnumeratorMatch(Current); + } + return EnumeratorOptions.Num() > 0 ? EnumeratorOptions[0] : nullptr; + }()) + .Content() + [ + SNew(STextBlock) + .Text_Lambda([this, ElementHandle]() + { + FName Current; + if (ElementHandle->GetValue(Current) == FPropertyAccess::Success && !Current.IsNone()) + { + return GetEnumeratorDisplayText(Current); + } + return LOCTEXT("EnumNonePlaceholder", ""); + }) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(GetEnumSourceTooltip()) + ] + ]; + }, + TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_Enum::GetArrayModeVisibility)); +} + +TSharedRef FFlowDataPinValueCustomization_Enum::GenerateEnumeratorWidget(TSharedPtr Item) const +{ + const FName Name = Item.IsValid() ? *Item : NAME_None; + return SNew(STextBlock) + .Text(GetEnumeratorDisplayText(Name)) + .Font(IDetailLayoutBuilder::GetDetailFont()); +} + +FText FFlowDataPinValueCustomization_Enum::GetEnumeratorDisplayText(const FName& Value) const +{ + return Value.IsNone() ? LOCTEXT("EnumNoneDisplay", "") : FText::FromName(Value); +} + +FText FFlowDataPinValueCustomization_Enum::GetEnumSourceTooltip() const +{ + const FFlowDataPinValue_Enum* Data = GetEnumValueStruct(); + if (!Data) + { + return LOCTEXT("EnumTooltipMissing", "Enum value struct not available."); + } + + FString Source; + if (!Data->EnumName.IsEmpty()) + { + Source = FString::Printf(TEXT("Native Enum: %s"), *Data->EnumName); + } + if (Source.IsEmpty() && Data->EnumClass.IsValid()) + { + Source = FString::Printf(TEXT("Enum Asset: %s"), *Data->EnumClass.ToString()); + } + if (Source.IsEmpty()) + { + Source = TEXT("No enum source selected"); + } + return FText::FromString(Source); +} + +void FFlowDataPinValueCustomization_Enum::OnSingleValueChanged( + TSharedPtr NewSelection, + ESelectInfo::Type, + TSharedPtr ElementHandle) +{ + if (!ElementHandle.IsValid() || !NewSelection.IsValid()) + { + return; + } + + FName Current; + ElementHandle->GetValue(Current); + if (Current == *NewSelection) + { + return; + } + + FScopedTransaction Tx(LOCTEXT("SetEnumSingleValue", "Set Enum Value")); + ElementHandle->SetValue(*NewSelection); +} + +void FFlowDataPinValueCustomization_Enum::OnArrayElementChanged( + TSharedPtr NewSelection, + ESelectInfo::Type, + TSharedPtr ElementHandle) +{ + if (!ElementHandle.IsValid() || !NewSelection.IsValid()) + { + return; + } + + FName Current; + ElementHandle->GetValue(Current); + if (Current == *NewSelection) + { + return; + } + + FScopedTransaction Tx(LOCTEXT("SetEnumArrayElement", "Set Enum Array Element")); + ElementHandle->SetValue(*NewSelection); +} + +void FFlowDataPinValueCustomization_Enum::OnMultiTypeChanged() +{ + // If array not supported, ignore switching + if (!bArraySupported) + { + return; + } + + if (GetArrayModeVisibility() == EVisibility::Collapsed) + { + EnsureSingleElementExists(); + } + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Object.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Object.cpp new file mode 100644 index 000000000..806e60532 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Object.cpp @@ -0,0 +1,298 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization_Object.h" + +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailChildrenBuilder.h" +#include "PropertyHandle.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "IPropertyUtilities.h" +#include "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "UnrealExtensions/VisibilityArrayBuilder.h" +#include "EditorClassUtils.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_Object" + +FFlowDataPinValue_Object* FFlowDataPinValueCustomization_Object::GetValueStruct() const +{ + return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); +} + +bool FFlowDataPinValueCustomization_Object::ShouldShowSourceRow() const +{ + return OwnerInterface ? OwnerInterface->ShowFlowDataPinValueClassFilter(GetValueStruct()) : true; +} + +bool FFlowDataPinValueCustomization_Object::IsSourceEditable() const +{ + if (bMetaClassForced) + { + return false; + } + return OwnerInterface ? OwnerInterface->CanEditFlowDataPinValueClassFilter(GetValueStruct()) : true; +} + +void FFlowDataPinValueCustomization_Object::BuildValueRows( + TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + CacheArraySupported(); // base + if (!ValuesHandle.IsValid()) + { + return; + } + + ClassFilterHandle = StructPropertyHandle->GetChildHandle(TEXT("ClassFilter")); + + TryApplyMetaClass(); + ResolveEffectiveFilter(); + + const bool bShowSource = ShouldShowSourceRow(); + if (bShowSource) + { + BuildClassFilterRow(StructBuilder, IsSourceEditable()); + } + + EnsureSingleElementExists(); + BuildSingleBranch(StructBuilder); + if (bArraySupported) + { + BuildArrayBranch(StructBuilder); + } + + BindDelegates(); + ValidateAll(); +} + +void FFlowDataPinValueCustomization_Object::TryApplyMetaClass() +{ + if (!StructPropertyHandle.IsValid() || !ClassFilterHandle.IsValid()) + { + return; + } + + const FString& MetaClassName = StructPropertyHandle->GetMetaData(TEXT("MetaClass")); + if (MetaClassName.IsEmpty()) + { + bMetaClassForced = false; + return; + } + + if (UClass* Meta = FEditorClassUtils::GetClassFromString(MetaClassName)) + { + UObject* Existing = nullptr; + ClassFilterHandle->GetValue(Existing); + if (Existing != Meta) + { + ClassFilterHandle->SetValue(Meta, EPropertyValueSetFlags::DefaultFlags); + } + bMetaClassForced = true; + } + else + { + bMetaClassForced = false; + } +} + +void FFlowDataPinValueCustomization_Object::ResolveEffectiveFilter() +{ + if (bMetaClassForced) + { + const FString& MetaClassName = StructPropertyHandle->GetMetaData(TEXT("MetaClass")); + EffectiveFilterClass = FEditorClassUtils::GetClassFromString(MetaClassName); + return; + } + + if (ClassFilterHandle.IsValid()) + { + UObject* Obj = nullptr; + if (ClassFilterHandle->GetValue(Obj) == FPropertyAccess::Success) + { + EffectiveFilterClass = Cast(Obj); + return; + } + } + + EffectiveFilterClass = nullptr; +} + +void FFlowDataPinValueCustomization_Object::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder, bool bSourceEditable) +{ + if (!ClassFilterHandle.IsValid()) + { + return; + } + + IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); + Row.DisplayName(LOCTEXT("ObjClassFilter", "Class Filter")); + Row.IsEnabled(bSourceEditable); + Row.ToolTip(bMetaClassForced + ? LOCTEXT("ObjClassFilterMetaTooltip", "Class Filter is fixed by MetaClass metadata and cannot be edited.") + : LOCTEXT("ObjClassFilterTooltip", "Class Filter constrains which object classes are selectable.")); +} + +void FFlowDataPinValueCustomization_Object::BuildSingleBranch(IDetailChildrenBuilder& StructBuilder) +{ + auto First = ValuesHandle->GetChildHandle(0); + if (!First.IsValid()) + { + return; + } + + StructBuilder.AddCustomRow(LOCTEXT("ObjectSingleSearch", "Object")) + .Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_Object::GetSingleModeVisibility)) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("ObjectValueLabel", "Object")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + BuildObjectValueWidgetForElement(First) + ]; +} + +void FFlowDataPinValueCustomization_Object::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) +{ + BuildVisibilityAwareArray(StructBuilder, + ValuesHandle, + [this](TSharedRef ElementHandle, int32 Index, IDetailChildrenBuilder& ChildBuilder, const TAttribute& RowVis) + { + IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); + Row.Visibility(RowVis); + + Row.CustomWidget() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("ObjectArrayElemFmt", "Object {0}"), FText::AsNumber(Index))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + BuildObjectValueWidgetForElement(ElementHandle) + ]; + }, + TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_Object::GetArrayModeVisibility)); +} + +TSharedRef FFlowDataPinValueCustomization_Object::BuildObjectValueWidgetForElement(TSharedPtr ElementHandle) +{ + return SNew(SObjectPropertyEntryBox) + .PropertyHandle(ElementHandle) + .AllowedClass(EffectiveFilterClass.Get() ? EffectiveFilterClass.Get() : UObject::StaticClass()) + .AllowClear(AreValuesEditable()) + .IsEnabled(AreValuesEditable()) + .ToolTipText(AreValuesEditable() + ? LOCTEXT("ObjectPickerTooltip", "Select an object reference.") + : LOCTEXT("ObjectPickerLockedTooltip", "Object references are not editable.")); +} + +void FFlowDataPinValueCustomization_Object::BindDelegates() +{ + if (ClassFilterHandle.IsValid()) + { + ClassFilterHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Object::OnClassFilterChanged)); + } + if (ValuesHandle.IsValid()) + { + ValuesHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Object::OnValuesChanged)); + } +} + +void FFlowDataPinValueCustomization_Object::OnClassFilterChanged() +{ + ResolveEffectiveFilter(); + ValidateAll(); + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +void FFlowDataPinValueCustomization_Object::OnValuesChanged() +{ + ValidateAll(); +} + +void FFlowDataPinValueCustomization_Object::ValidateAll() +{ + ValidateArrayElements(ValuesHandle, + [this](TSharedPtr Elem) + { + return IsElementValid(Elem); + }, + [this](TSharedPtr Elem) + { + InvalidateElement(Elem); + }); +} + +bool FFlowDataPinValueCustomization_Object::IsElementValid(TSharedPtr ElementHandle) const +{ + if (!ElementHandle.IsValid()) + { + return true; + } + + UClass* Filter = EffectiveFilterClass.Get(); + if (!Filter) + { + return true; + } + + UObject* Obj = GetObjectValue(ElementHandle); + return !Obj || Obj->IsA(Filter); +} + +void FFlowDataPinValueCustomization_Object::InvalidateElement(TSharedPtr ElementHandle) +{ + if (ElementHandle.IsValid()) + { + SetObjectValue(ElementHandle, nullptr); + } +} + +UObject* FFlowDataPinValueCustomization_Object::GetObjectValue(TSharedPtr ElementHandle) const +{ + UObject* Obj = nullptr; + if (ElementHandle.IsValid()) + { + ElementHandle->GetValue(Obj); + } + return Obj; +} + +void FFlowDataPinValueCustomization_Object::SetObjectValue(TSharedPtr ElementHandle, UObject* NewObj) +{ + if (!ElementHandle.IsValid()) + { + return; + } + + UObject* Current = nullptr; + ElementHandle->GetValue(Current); + if (Current == NewObj) + { + return; + } + + FScopedTransaction Tx(LOCTEXT("SetObjectValue", "Set Object Reference")); + ElementHandle->SetValue(NewObj); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinPropertyCustomization.cpp similarity index 78% rename from Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.cpp rename to Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinPropertyCustomization.cpp index 194def98c..3db153a12 100644 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.cpp +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowNamedDataPinPropertyCustomization.cpp @@ -1,6 +1,7 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors -#include "DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h" +#include "DetailCustomizations/FlowNamedDataPinPropertyCustomization.h" +#include "Types/FlowNamedDataPinProperty.h" FText FFlowNamedDataPinPropertyCustomization::BuildHeaderText() const { diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowNodeAddOn_Details.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowNodeAddOn_Details.cpp index d57a94587..d31e4b0d6 100644 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowNodeAddOn_Details.cpp +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowNodeAddOn_Details.cpp @@ -2,6 +2,7 @@ #include "DetailCustomizations/FlowNodeAddOn_Details.h" #include "DetailLayoutBuilder.h" +#include "AddOns/FlowNodeAddOn.h" void FFlowNodeAddOn_Details::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { @@ -11,4 +12,7 @@ void FFlowNodeAddOn_Details::CustomizeDetails(IDetailLayoutBuilder& DetailLayout DetailLayout.HideCategory(TEXT("FlowNode")); DetailLayout.HideCategory(TEXT("FlowNodeAddOn")); } + + // Call base template to set up rebuild delegate wiring + TFlowDataPinValueOwnerCustomization::CustomizeDetails(DetailLayout); } diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowNode_Details.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowNode_Details.cpp index 8570fc0f1..222034df2 100644 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowNode_Details.cpp +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowNode_Details.cpp @@ -2,12 +2,16 @@ #include "DetailCustomizations/FlowNode_Details.h" #include "DetailLayoutBuilder.h" +#include "Nodes/FlowNode.h" void FFlowNode_Details::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { - // hide class properties while editing node instance placed in the graph - if (DetailLayout.HasClassDefaultObject() == false) + // Hide class-level category when editing an instance (not the CDO) + if (!DetailLayout.HasClassDefaultObject()) { DetailLayout.HideCategory(TEXT("FlowNode")); } + + // Call base template to set up rebuild delegate wiring + TFlowDataPinValueOwnerCustomization::CustomizeDetails(DetailLayout); } diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index 94582f2f7..282fab0d2 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -26,20 +26,21 @@ #include "DetailCustomizations/FlowNode_SubGraphDetails.h" #include "DetailCustomizations/FlowNodeAddOn_Details.h" #include "DetailCustomizations/FlowActorOwnerComponentRefCustomization.h" -#include "DetailCustomizations/FlowDataPinPropertyCustomizations.h" -#include "DetailCustomizations/FlowDataPinProperty_ClassCustomization.h" -#include "DetailCustomizations/FlowDataPinProperty_EnumCustomization.h" -#include "DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h" #include "DetailCustomizations/FlowPinCustomization.h" -#include "DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h" +#include "DetailCustomizations/FlowNamedDataPinPropertyCustomization.h" +#include "DetailCustomizations/FlowAssetParamsPtrCustomization.h" +#include "DetailCustomizations/FlowDataPinValueOwnerCustomizations.h" +#include "DetailCustomizations/FlowDataPinValueStandardCustomizations.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" #include "Nodes/Graph/FlowNode_CustomOutput.h" #include "Nodes/Graph/FlowNode_SubGraph.h" +#include "Types/FlowNamedDataPinProperty.h" #include "AssetToolsModule.h" #include "EdGraphUtilities.h" @@ -55,7 +56,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() { @@ -97,9 +98,6 @@ void FFlowEditorModule::StartupModule() RegisterAssetIndexers(); } ModulesChangedHandle = FModuleManager::Get().OnModulesChanged().AddRaw(this, &FFlowEditorModule::ModulesChangesCallback); - - // run one-time asserts that cannot be asserted statically - UFlowK2SchemaSubclassForAccess::AssertPinCategoryNames(); } void FFlowEditorModule::ShutdownModule() @@ -224,6 +222,9 @@ void FFlowEditorModule::RegisterDetailCustomizations() FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); RegisterCustomClassLayout(UFlowAsset::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowAssetDetails::MakeInstance)); + RegisterCustomClassLayout(UFlowAssetParams::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowAssetParamsCustomization::MakeInstance)); + RegisterCustomClassLayout(UFlowExecutableActorComponent::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowExecutableActorComponentCustomization::MakeInstance)); + RegisterCustomClassLayout(UFlowNodeBase::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowNodeBaseCustomization::MakeInstance)); RegisterCustomClassLayout(UFlowNode::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowNode_Details::MakeInstance)); RegisterCustomClassLayout(UFlowNodeAddOn::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowNodeAddOn_Details::MakeInstance)); RegisterCustomClassLayout(UFlowNode_ComponentObserver::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FFlowNode_ComponentObserverDetails::MakeInstance)); @@ -234,33 +235,24 @@ void FFlowEditorModule::RegisterDetailCustomizations() RegisterCustomStructLayout(*FFlowActorOwnerComponentRef::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowActorOwnerComponentRefCustomization::MakeInstance)); RegisterCustomStructLayout(*FFlowPin::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowPinCustomization::MakeInstance)); RegisterCustomStructLayout(*FFlowNamedDataPinProperty::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowNamedDataPinPropertyCustomization::MakeInstance)); - - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Bool::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_BoolCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Int64::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_Int64Customization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Int32::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_Int32Customization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Double::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_DoubleCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Float::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_FloatCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Name::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_NameCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_String::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_StringCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Text::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_TextCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Enum::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_EnumCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Class::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_ClassCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinOutputProperty_Object::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinOutputProperty_ObjectCustomization::MakeInstance)); - - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Bool::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_BoolCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Int64::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_Int64Customization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Int32::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_Int32Customization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Double::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_DoubleCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Float::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_FloatCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Name::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_NameCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_String::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_StringCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Text::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_TextCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Enum::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_EnumCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Class::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_ClassCustomization::MakeInstance)); - RegisterCustomStructLayout(*FFlowDataPinInputProperty_Object::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_ObjectCustomization::MakeInstance)); - - // Consider implementing details customizations... for every EFlowPinType - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + RegisterCustomStructLayout(*FFlowAssetParamsPtr::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowAssetParamsPtrCustomization::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Bool::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Bool::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Int::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Int::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Int64::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Int64::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Float::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Float::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Double::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Double::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Name::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Name::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_String::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_String::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Text::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Text::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Enum::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Enum::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Vector::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Vector::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Rotator::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Rotator::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Transform::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Transform::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_GameplayTag::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_GameplayTag::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_GameplayTagContainer::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_GameplayTagContainer::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_InstancedStruct::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_InstancedStruct::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Class::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Class::MakeInstance)); + RegisterCustomStructLayout(*FFlowDataPinValue_Object::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_Object::MakeInstance)); PropertyModule.NotifyCustomizationModuleChanged(); } @@ -324,4 +316,4 @@ TSharedRef FFlowEditorModule::CreateFlowAssetEditor(const EToo #undef LOCTEXT_NAMESPACE -IMPLEMENT_MODULE(FFlowEditorModule, FlowEditor) +IMPLEMENT_MODULE(FFlowEditorModule, FlowEditor) \ No newline at end of file diff --git a/Source/FlowEditor/Private/Graph/FlowGraph.cpp b/Source/FlowEditor/Private/Graph/FlowGraph.cpp index 14bfa5881..3c39270c2 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraph.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraph.cpp @@ -176,6 +176,8 @@ void UFlowGraph::OnLoaded() bIsLoadingGraph = true; + UpdateVersion(); + // Setup all the Nodes in the graph for editing for (UEdGraphNode* Node : Nodes) { @@ -214,20 +216,52 @@ void UFlowGraph::Initialize() void UFlowGraph::UpdateVersion() { - if (GraphVersion == 1) + if (GraphVersion == CurrentGraphVersion) { return; } + const int32 PrevGraphVersion = GraphVersion; MarkVersion(); Modify(); // Insert any Version updating code here + + if (PrevGraphVersion < 2) + { + UpgradeAllFlowNodePins(); + } +} + +void UFlowGraph::UpgradeAllFlowNodePins() +{ + if (UFlowAsset* FlowAsset = GetFlowAsset()) + { + for (auto& KV : FlowAsset->Nodes) + { + UFlowNode* FlowNode = KV.Value; + if (IsValid(FlowNode)) + { + FlowNode->FixupDataPinTypes(); + + FlowAsset->TryUpdateManagedFlowPinsForNode(*FlowNode); + } + } + } + + for (UEdGraphNode* Node : Nodes) + { + if (UFlowGraphNode* FlowGraphNode = Cast(Node)) + { + FlowGraphNode->MarkNeedsFullReconstruction(); + FlowGraphNode->ReconstructNode(); + } + } } void UFlowGraph::MarkVersion() { - GraphVersion = 1; + GraphVersion = CurrentGraphVersion; } void UFlowGraph::UpdateClassData() diff --git a/Source/FlowEditor/Private/Graph/FlowGraphNodesPolicy.cpp b/Source/FlowEditor/Private/Graph/FlowGraphNodesPolicy.cpp new file mode 100644 index 000000000..45c038dff --- /dev/null +++ b/Source/FlowEditor/Private/Graph/FlowGraphNodesPolicy.cpp @@ -0,0 +1,54 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Graph/FlowGraphNodesPolicy.h" +#include "Nodes/FlowNodeBase.h" +#include "Graph/FlowGraphSettings.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowGraphNodesPolicy) + +#if WITH_EDITOR +bool FFlowGraphNodesPolicy::IsNodeAllowedByPolicy(const UFlowNodeBase* FlowNodeBase) const +{ + if (!IsValid(FlowNodeBase)) + { + return false; + } + + const FString NodeCategoryString = UFlowGraphSettings::GetNodeCategoryForNode(*FlowNodeBase); + + const bool bIsInAllowedCategory = !AllowedCategories.IsEmpty() && IsAnySubcategory(NodeCategoryString, AllowedCategories); + if (bIsInAllowedCategory) + { + return true; + } + + const bool bIsInDisallowedCategory = !DisallowedCategories.IsEmpty() && IsAnySubcategory(NodeCategoryString, DisallowedCategories); + if (bIsInDisallowedCategory) + { + return false; + } + + if (AllowedCategories.IsEmpty()) + { + // If the AllowedCategories is empty, then we consider any node that isn't disallowed, as allowed + return true; + } + else + { + return false; + } +} + +bool FFlowGraphNodesPolicy::IsAnySubcategory(const FString& CheckCategory, const TArray& Categories) +{ + for (const FString& Category : Categories) + { + if (CheckCategory.StartsWith(Category, ESearchCase::IgnoreCase)) + { + return true; + } + } + + return false; +} +#endif \ No newline at end of file diff --git a/Source/FlowEditor/Private/Graph/FlowGraphPinFactory.cpp b/Source/FlowEditor/Private/Graph/FlowGraphPinFactory.cpp index 83e891c1e..55b874378 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphPinFactory.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphPinFactory.cpp @@ -11,8 +11,6 @@ #include "NodeFactory.h" #include "SGraphPin.h" -#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowGraphPinFactory) - ////////////////////////////////////////////////////////////////////////// // FFlowGraphPinFactory @@ -79,26 +77,3 @@ int32 FFlowGraphPinFactory::GatherValidPinsCount(const TArray& Pins) return Count; } - -////////////////////////////////////////////////////////////////////////// -// UFlowK2SchemaSubclassForAccess - -void UFlowK2SchemaSubclassForAccess::AssertPinCategoryNames() -{ - // Assert that the FFlowPin aliases for the UEdGraphSchema_K2 PC_* FNames match exactly - // (since we will leverage some K2 functions that key off these names) - checkf(FFlowPin::PC_Exec == UEdGraphSchema_K2::PC_Exec, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Boolean == UEdGraphSchema_K2::PC_Boolean, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Byte == UEdGraphSchema_K2::PC_Byte, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Class == UEdGraphSchema_K2::PC_Class, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Int == UEdGraphSchema_K2::PC_Int, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Int64 == UEdGraphSchema_K2::PC_Int64, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Float == UEdGraphSchema_K2::PC_Float, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Double == UEdGraphSchema_K2::PC_Double, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Name == UEdGraphSchema_K2::PC_Name, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Object == UEdGraphSchema_K2::PC_Object, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_String == UEdGraphSchema_K2::PC_String, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Text == UEdGraphSchema_K2::PC_Text, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Struct == UEdGraphSchema_K2::PC_Struct, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); - checkf(FFlowPin::PC_Enum == UEdGraphSchema_K2::PC_Enum, TEXT("Flow PC_* aliases should match Epic's K2 PC_* names exactly")); -} diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp index 21004681a..68b6ce845 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp @@ -12,6 +12,7 @@ #include "FlowAsset.h" #include "FlowEditorLogChannels.h" +#include "FlowPinSubsystem.h" #include "FlowSettings.h" #include "AddOns/FlowNodeAddOn.h" #include "Nodes/FlowNode.h" @@ -20,6 +21,7 @@ #include "Nodes/Graph/FlowNode_CustomInput.h" #include "Nodes/Graph/FlowNode_Start.h" #include "Nodes/Route/FlowNode_Reroute.h" +#include "Types/FlowPinType.h" #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraph/EdGraph.h" @@ -51,10 +53,6 @@ int32 UFlowGraphSchema::CurrentCacheRefreshID = 0; FFlowGraphSchemaRefresh UFlowGraphSchema::OnNodeListChanged; -const UScriptStruct* UFlowGraphSchema::VectorStruct = nullptr; -const UScriptStruct* UFlowGraphSchema::RotatorStruct = nullptr; -const UScriptStruct* UFlowGraphSchema::TransformStruct = nullptr; - namespace FlowGraphSchema::Private { // Adapted from UE::EdGraphSchemaK2::Private, because it's Private @@ -203,16 +201,21 @@ UFlowGraphSchema::UFlowGraphSchema(const FObjectInitializer& ObjectInitializer) GetDefault()->ForceVisualizationCacheClear(); }); } +} - // Initialize cached static references to well-known struct types - if (VectorStruct == nullptr) +void UFlowGraphSchema::EnsurePinTypesInitialized() +{ + if (PinTypeMatchPolicies.IsEmpty()) { - VectorStruct = TBaseStructure::Get(); - RotatorStruct = TBaseStructure::Get(); - TransformStruct = TBaseStructure::Get(); + InitializedPinTypes(); } } +void UFlowGraphSchema::InitializedPinTypes() +{ + PinTypeMatchPolicies = FFlowPinTypeNamesStandard::PinTypeMatchPolicies; +} + void UFlowGraphSchema::SubscribeToAssetChanges() { const FAssetRegistryModule& AssetRegistry = FModuleManager::LoadModuleChecked(AssetRegistryConstants::ModuleName); @@ -287,7 +290,7 @@ UFlowGraphNode* UFlowGraphSchema::CreateDefaultNode(UEdGraph& Graph, const TSubc return NewGraphNode; } -bool UFlowGraphSchema::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UClass* CallingContext, bool bIgnoreArray /*= false*/) const +bool UFlowGraphSchema::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UClass* CallingContext, bool bIgnoreArray) const { // Adapted from UEdGraphSchema_K2 if ((PinA->Direction == EGPD_Input) && (PinB->Direction == EGPD_Output)) @@ -304,126 +307,225 @@ bool UFlowGraphSchema::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraph } } -bool UFlowGraphSchema::ArePinCategoriesEffectivelyMatching(const FName& InputPinCategory, const FName& OutputPinCategory, bool bAllowImplicitCasts) +bool UFlowGraphSchema::ArePinTypesCompatible( + const FEdGraphPinType& OutputPinType, + const FEdGraphPinType& InputPinType, + const UClass* CallingContext, + bool bIgnoreArray) const { - if (InputPinCategory == OutputPinCategory) + FPinConnectionResponse ConnectionResponse; + + const bool bIsInputExecPin = FFlowPin::IsExecPinCategory(InputPinType.PinCategory); + const bool bIsOutputExecPin = FFlowPin::IsExecPinCategory(OutputPinType.PinCategory); + + if (bIsInputExecPin || bIsOutputExecPin) { - return true; + return (bIsInputExecPin && bIsOutputExecPin); } - if (!bAllowImplicitCasts) + UFlowGraphSchema* MutableThis = const_cast(this); + MutableThis->EnsurePinTypesInitialized(); + + const FFlowPinTypeMatchPolicy* FoundPinTypeMatchPolicy = PinTypeMatchPolicies.Find(InputPinType.PinCategory); + + // PinCategories must match exactly or be in the map of compatible PinCategories for the input pin type + if (!FoundPinTypeMatchPolicy) { + ConnectionResponse = + FPinConnectionResponse( + CONNECT_RESPONSE_DISALLOW, + FString::Printf( + TEXT("Could not find PinTypeMatchPolicy for %s"), + *InputPinType.PinCategory.ToString())); + return false; } - // Must handle pin connectivity for all added EFlowPinTypes - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + // PinCategories must match exactly or be in the map of compatible PinCategories for the input pin type + const bool bRequirePinCategoryMatch = EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequirePinCategoryMatch); + if (bRequirePinCategoryMatch && + OutputPinType.PinCategory != InputPinType.PinCategory && + !FoundPinTypeMatchPolicy->PinCategories.Contains(OutputPinType.PinCategory)) + { + ConnectionResponse = + FPinConnectionResponse( + CONNECT_RESPONSE_DISALLOW, + FString::Printf( + TEXT("Pin type mismatch %s != %s"), + *OutputPinType.PinCategory.ToString(), + *InputPinType.PinCategory.ToString())); - // We could extend the compatibility here to accept more implicit conversions (eg, null objects convertible to bools) - // but we'd need to also add support the conversion in the Supply/Resolve side as well. + return false; + } - if (FFlowPin::IsBoolPinCategory(InputPinCategory) && FFlowPin::IsConvertableToBoolPinCategory(OutputPinCategory)) + // RequirePinCategoryMemberReference + const bool bRequirePinCategoryMemberReferenceMatch = EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequirePinCategoryMemberReferenceMatch); + if (bRequirePinCategoryMemberReferenceMatch && + OutputPinType.PinSubCategoryMemberReference != InputPinType.PinSubCategoryMemberReference) { - return true; + ConnectionResponse = + FPinConnectionResponse( + CONNECT_RESPONSE_DISALLOW, + FString::Printf( + TEXT("Pin category member reference mismatch %s != %s"), + *OutputPinType.PinSubCategoryMemberReference.MemberName.ToString(), + *InputPinType.PinSubCategoryMemberReference.MemberName.ToString())); + + return false; } - if (FFlowPin::IsIntPinCategory(InputPinCategory) && FFlowPin::IsConvertableToIntPinCategory(OutputPinCategory)) - { - return true; + // TODO (gtaylor) We will want to revisit how we want to use the "PinSubCategory" field of FEdGraphPinType to best effect. + // So far, we don't have any use for it in the standard flow types, but if we can dream up a good way to use it + // in supporting other projects, we can add support for it here. +#if 0 + // PinSubCategories must match exactly or be in the map of compatible PinSubCategories for the input pin type + const bool bRequirePinSubCategoryMatch = EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequirePinSubCategoryMatch); + if (bRequirePinSubCategoryMatch && + OutputPinType.PinSubCategory != InputPinType.PinSubCategory && + !FoundPinTypeMatchPolicy->PinSubCategories.Contains(OutputPinType.PinSubCategory)) + { + ConnectionResponse = + FPinConnectionResponse( + CONNECT_RESPONSE_DISALLOW, + FString::Printf( + TEXT("Pin sub-category mismatch %s != %s"), + *OutputPinType.PinSubCategory.ToString(), + *InputPinType.PinSubCategory.ToString())); + + return false; } +#endif - if (FFlowPin::IsFloatPinCategory(InputPinCategory) && FFlowPin::IsConvertableToFloatPinCategory(OutputPinCategory)) + // Container type (Single/Array, etc.) + const bool bRequireContainerTypeMatch = EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequireContainerTypeMatch); + if (bRequireContainerTypeMatch && OutputPinType.ContainerType != InputPinType.ContainerType) { - return true; + const bool bIsAnyArray = + OutputPinType.ContainerType == EPinContainerType::Array || + InputPinType.ContainerType == EPinContainerType::Array; + + if (!bIgnoreArray || !bIsAnyArray) + { + ConnectionResponse = + FPinConnectionResponse( + CONNECT_RESPONSE_DISALLOW, + FString::Printf( + TEXT("Mismatched container type %s != %s"), + *UEnum::GetDisplayValueAsText(OutputPinType.ContainerType).ToString(), + *UEnum::GetDisplayValueAsText(InputPinType.ContainerType).ToString())); + + return false; + } } - if (FFlowPin::IsEnumPinCategory(InputPinCategory) && FFlowPin::IsConvertableToEnumPinCategory(OutputPinCategory)) + const bool bRequirePinSubCategoryObjectMatch = EnumHasAnyFlags(FoundPinTypeMatchPolicy->PinTypeMatchRules, EFlowPinTypeMatchRules::RequirePinSubCategoryObjectMatch); + if (bRequirePinSubCategoryObjectMatch) { - return true; + const UStruct* OutputStruct = Cast(OutputPinType.PinSubCategoryObject.Get()); + const UStruct* InputStruct = Cast(InputPinType.PinSubCategoryObject.Get()); + + if (!ArePinSubCategoryObjectsCompatible(OutputStruct, InputStruct, *FoundPinTypeMatchPolicy, ConnectionResponse)) + { + return false; + } } - if (FFlowPin::IsTextPinCategory(InputPinCategory) && FFlowPin::IsConvertableToTextPinCategory(OutputPinCategory)) + return true; +} + +bool UFlowGraphSchema::ArePinSubCategoryObjectsCompatible( + const UStruct* OutputStruct, + const UStruct* InputStruct, + const FFlowPinTypeMatchPolicy& PinTypeMatchPolicy, + FPinConnectionResponse& OutConnectionResponse) const +{ + if (!IsValid(InputStruct)) { + // Assume "InputStruct's SubCategoryObject == null", means any SubCategoryObject is acceptable return true; } - if (FFlowPin::IsObjectPinCategory(InputPinCategory) && FFlowPin::IsConvertableToObjectPinCategory(OutputPinCategory)) + if (!IsValid(OutputStruct)) { + // null objects are the norm for many PinCategories, so long as they match return true; } - if (FFlowPin::IsClassPinCategory(InputPinCategory) && FFlowPin::IsConvertableToClassPinCategory(OutputPinCategory)) + // Exact match + if (OutputStruct == InputStruct) { return true; } - if (FFlowPin::IsStructPinCategory(InputPinCategory) && FFlowPin::IsConvertableToStructPinCategory(OutputPinCategory)) + using namespace FlowGraphSchema::Private; + + // Only allow a match if the input is a superclass of the output + const bool bAllowSubCategoryObjectSubclasses = EnumHasAnyFlags(PinTypeMatchPolicy.PinTypeMatchRules, EFlowPinTypeMatchRules::AllowSubCategoryObjectSubclasses); + if (bAllowSubCategoryObjectSubclasses && IsAuthoritativeChildOf(OutputStruct, InputStruct)) { return true; } - return false; -} + UClass const* OutputClass = Cast(OutputStruct); + UClass const* InputClass = Cast(InputStruct); -bool UFlowGraphSchema::ArePinTypesCompatible(const FEdGraphPinType& Output, const FEdGraphPinType& Input, const UClass* CallingContext, bool bIgnoreArray /*= false*/) const -{ - // NOTE - Adapted from UEdGraphSchema_K2::ArePinTypesCompatible() - - using namespace FlowGraphSchema::Private; - using namespace UE::Kismet::BlueprintTypeConversions; - - if (ArePinCategoriesEffectivelyMatching(Input.PinCategory, Output.PinCategory)) + // Class specifics + if (IsValid(InputClass) && IsValid(OutputClass)) { - const UScriptStruct* OutputStruct = Cast(Output.PinSubCategoryObject.Get()); - const UScriptStruct* InputStruct = Cast(Input.PinSubCategoryObject.Get()); - if (OutputStruct != InputStruct) + // Only allow a match if the input is a superclass of the output + if (bAllowSubCategoryObjectSubclasses && ExtendedIsChildOf(OutputClass, InputClass)) { - const bool bAreConvertibleStructs = - FStructConversionTable::Get().GetConversionFunction(OutputStruct, InputStruct).IsSet(); - - if (bAreConvertibleStructs) - { - return true; - } + return true; } - if ((Output.PinSubCategory == Input.PinSubCategory) - && (Output.PinSubCategoryObject == Input.PinSubCategoryObject) - && (Output.PinSubCategoryMemberReference == Input.PinSubCategoryMemberReference)) + OutConnectionResponse = + FPinConnectionResponse( + CONNECT_RESPONSE_DISALLOW, + FString::Printf( + TEXT("Output %s must be subclass of input %s"), + *OutputClass->GetName(), + *InputClass->GetName())); + + return false; + } + + if (!IsValid(InputClass) && !IsValid(OutputClass)) + { + const bool bAllowSubCategoryObjectSameLayout = EnumHasAnyFlags(PinTypeMatchPolicy.PinTypeMatchRules, EFlowPinTypeMatchRules::AllowSubCategoryObjectSameLayout); + const bool bSameLayoutMustMatchPropertyNames = EnumHasAnyFlags(PinTypeMatchPolicy.PinTypeMatchRules, EFlowPinTypeMatchRules::SameLayoutMustMatchPropertyNames); + + // Allow structs with the same layout + if (bAllowSubCategoryObjectSameLayout + && FStructUtils::TheSameLayout(OutputStruct, InputStruct, bSameLayoutMustMatchPropertyNames)) { - // If the sub-category also matches exactly, then the pins are compatible return true; } - - if ((Output.PinCategory == FFlowPin::PC_Object) || (Output.PinCategory == FFlowPin::PC_Struct) || (Output.PinCategory == FFlowPin::PC_Class)) - { - // Subcategory mismatch, but the two could be castable - // Only allow a match if the input is a superclass of the output - - UStruct const* OutputObject = (Output.PinSubCategory == UEdGraphSchema_K2::PSC_Self) ? CallingContext : Cast(Output.PinSubCategoryObject.Get()); - UStruct const* InputObject = (Input.PinSubCategory == UEdGraphSchema_K2::PSC_Self) ? CallingContext : Cast(Input.PinSubCategoryObject.Get()); - if (OutputObject && InputObject) - { - if (Output.PinCategory == FFlowPin::PC_Struct) - { - return OutputObject->IsChildOf(InputObject) && FStructUtils::TheSameLayout(OutputObject, InputObject); - } + using namespace UE::Kismet::BlueprintTypeConversions; - UClass const* OutputClass = Cast(OutputObject); - UClass const* InputClass = Cast(InputObject); + // Allow convertable ScriptStructs + const UScriptStruct* InputScriptStruct = Cast(InputStruct); + const UScriptStruct* OutputScriptStruct = Cast(OutputStruct); + if (IsValid(InputScriptStruct) && IsValid(OutputScriptStruct)) + { + const bool bAreConvertibleStructs = + FStructConversionTable::Get().GetConversionFunction(OutputScriptStruct, InputScriptStruct).IsSet(); - return - (IsAuthoritativeChildOf(OutputObject, InputObject) || - (OutputClass && InputClass && ExtendedIsChildOf(OutputClass, InputClass))); + if (bAreConvertibleStructs) + { + return true; } - - return false; } - - return false; } + OutConnectionResponse = + FPinConnectionResponse( + CONNECT_RESPONSE_DISALLOW, + FString::Printf( + TEXT("Output %s is not compatible with input %s"), + *OutputStruct->GetName(), + *InputStruct->GetName())); + return false; } @@ -516,14 +618,16 @@ const FPinConnectionResponse UFlowGraphSchema::DetermineConnectionResponseOfComp checkf(!PinB->LinkedTo.Contains(PinA), TEXT("This should be caught with the bIsExistingConnection test above")); // Break existing connections on outputs for Exec Pins - if (FFlowPin::IsExecPinCategory(InputPin->PinType.PinCategory) && OutputPin->LinkedTo.Num() > 0) + const bool bIsExecPin = FFlowPin::IsExecPinCategory(InputPin->PinType.PinCategory); + if (bIsExecPin && OutputPin->LinkedTo.Num() > 0) { const ECanCreateConnectionResponse ReplyBreakInputs = (OutputPin == PinA ? CONNECT_RESPONSE_BREAK_OTHERS_A : CONNECT_RESPONSE_BREAK_OTHERS_B); return FPinConnectionResponse(ReplyBreakInputs, TEXT("Replace existing exec connection")); } // Break existing connections on inputs for Data Pins - if (FFlowPin::IsDataPinCategory(InputPin->PinType.PinCategory) && InputPin->LinkedTo.Num() > 0) + const bool bIsDataPin = !bIsExecPin; + if (bIsDataPin && InputPin->LinkedTo.Num() > 0) { const ECanCreateConnectionResponse ReplyBreakInputs = (InputPin == PinA ? CONNECT_RESPONSE_BREAK_OTHERS_A : CONNECT_RESPONSE_BREAK_OTHERS_B); return FPinConnectionResponse(ReplyBreakInputs, TEXT("Replace existing data connection")); @@ -597,77 +701,12 @@ bool UFlowGraphSchema::ShouldHidePinDefaultValue(UEdGraphPin* Pin) const FLinearColor UFlowGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const { - // NOTE - Adapted from UEdGraphSchema_K2::GetPinTypeColor() - // (because we cannot directly inherit from it, but want the same color language) - - const FName& PinCategory = PinType.PinCategory; - const UGraphEditorSettings* Settings = GetDefault(); - - if (FFlowPin::IsExecPinCategory(PinCategory)) - { - return Settings->ExecutionPinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Object) - { - return Settings->ObjectPinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Boolean) - { - return Settings->BooleanPinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Byte) - { - return Settings->BytePinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Int) - { - return Settings->IntPinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Int64) - { - return Settings->Int64PinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Struct) - { - if (PinType.PinSubCategoryObject == VectorStruct) - { - // vector - return Settings->VectorPinTypeColor; - } - else if (PinType.PinSubCategoryObject == RotatorStruct) - { - // rotator - return Settings->RotatorPinTypeColor; - } - else if (PinType.PinSubCategoryObject == TransformStruct) - { - // transform - return Settings->TransformPinTypeColor; - } - else - { - return Settings->StructPinTypeColor; - } - } - else if (PinCategory == FFlowPin::PC_String) - { - return Settings->StringPinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Text) + if (const FFlowPinType* FlowPinType = LookupDataPinTypeForPinCategory(PinType.PinCategory)) { - return Settings->TextPinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Name) - { - return Settings->NamePinTypeColor; - } - else if (PinCategory == FFlowPin::PC_Class) - { - return Settings->ClassPinTypeColor; + return FlowPinType->GetPinColor(); } - // Type does not have a defined color! - return Settings->DefaultPinTypeColor; + return FLinearColor(1.0f, 1.0f, 1.0f, 1.0f); } FText UFlowGraphSchema::GetPinDisplayName(const UEdGraphPin* Pin) const @@ -753,6 +792,22 @@ bool UFlowGraphSchema::CanShowDataTooltipForPin(const UEdGraphPin& Pin) const return !FFlowPin::IsExecPinCategory(Pin.PinType.PinCategory); } +const FFlowPinType* UFlowGraphSchema::LookupDataPinTypeForPinCategory(const FName& PinCategory) +{ + UFlowPinSubsystem* PinSubsystem = UFlowPinSubsystem::Get(); + if (!PinSubsystem) + { + UE_LOG(LogFlowEditor, Error, TEXT("Could not find the FlowPinSubsystem!")); + + return nullptr; + } + + // Flow uses the PinTypeName as the PinCategory for UEdGraphPin purposes + const FFlowPinTypeName PinTypeName(PinCategory); + const FFlowPinType* PinType = PinSubsystem->FindPinType(PinTypeName); + return PinType; +} + bool UFlowGraphSchema::IsTitleBarPin(const UEdGraphPin& Pin) const { return FFlowPin::IsExecPinCategory(Pin.PinType.PinCategory); @@ -793,15 +848,17 @@ void UFlowGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNoti for (UEdGraphPin* OtherPin : CachedLinkedTo) { UFlowGraphNode* OtherOwningFlowGraphNode = Cast(OtherPin->GetOwningNodeUnchecked()); - - if (OtherPin->bOrphanedPin) - { - // this calls NotifyNodeChanged() - OtherOwningFlowGraphNode->RemoveOrphanedPin(OtherPin); - } - else if (bSendsNodeNotification) + if (IsValid(OtherOwningFlowGraphNode)) { - EdGraph->NotifyNodeChanged(OtherOwningFlowGraphNode); + if (OtherPin->bOrphanedPin) + { + // this calls NotifyNodeChanged() + OtherOwningFlowGraphNode->RemoveOrphanedPin(OtherPin); + } + else if (bSendsNodeNotification) + { + EdGraph->NotifyNodeChanged(OtherOwningFlowGraphNode); + } } } } @@ -816,7 +873,6 @@ TSharedPtr UFlowGraphSchema::GetCreateCommentAction() cons return TSharedPtr(static_cast(new FFlowGraphSchemaAction_NewComment)); } - PRAGMA_DISABLE_DEPRECATION_WARNINGS void UFlowGraphSchema::OnPinConnectionDoubleCicked(UEdGraphPin* PinA, UEdGraphPin* PinB, const FVector2D& GraphPosition) const { @@ -934,7 +990,8 @@ TArray> UFlowGraphSchema::GetFlowNodeCategories() { if (const UFlowNode* DefaultObject = FlowNodeClass->GetDefaultObject()) { - UnsortedCategories.Emplace(DefaultObject->GetNodeCategory()); + const FString NodeCategoryString = UFlowGraphSettings::GetNodeCategoryForNode(*DefaultObject); + UnsortedCategories.Emplace(NodeCategoryString); } } @@ -942,7 +999,8 @@ TArray> UFlowGraphSchema::GetFlowNodeCategories() { if (const UFlowNodeAddOn* DefaultObject = FlowNodeAddOnClass->GetDefaultObject()) { - UnsortedCategories.Emplace(DefaultObject->GetNodeCategory()); + const FString NodeCategoryString = UFlowGraphSettings::GetNodeCategoryForNode(*DefaultObject); + UnsortedCategories.Emplace(NodeCategoryString); } } @@ -1045,23 +1103,46 @@ void UFlowGraphSchema::ApplyNodeOrAddOnFilter(const UFlowAsset* EditedFlowAsset, { return; } - - UFlowNodeBase* NodeDefaults = FlowNodeClass->GetDefaultObject(); - FilteredNodes.Emplace(NodeDefaults); + + const UFlowGraphSettings& FlowGraphSettings = *UFlowGraphSettings::Get(); + const bool bIsHiddenFromPaletteByNodeClass = FlowGraphSettings.NodesHiddenFromPalette.Contains(FlowNodeClass); + if (bIsHiddenFromPaletteByNodeClass) + { + return; + } + + UFlowNodeBase* FlowNodeBaseCDO = FlowNodeClass->GetDefaultObject(); + const FFlowGraphNodesPolicy* FlowAssetPolicy = FlowGraphSettings.PerAssetSubclassFlowNodePolicies.Find(FSoftClassPath(EditedFlowAsset->GetClass())); + if (FlowAssetPolicy) + { + const bool bIsAllowedByPolicy = FlowAssetPolicy->IsNodeAllowedByPolicy(FlowNodeBaseCDO); + if (!bIsAllowedByPolicy) + { + return; + } + } + + FilteredNodes.Emplace(FlowNodeBaseCDO); } void UFlowGraphSchema::GetFlowNodeActions(FGraphActionMenuBuilder& ActionMenuBuilder, const UFlowAsset* EditedFlowAsset, const FString& CategoryName) { - TArray FilteredNodes = GetFilteredPlaceableNodesOrAddOns(EditedFlowAsset, NativeFlowNodes, BlueprintFlowNodes); + const TArray FilteredNodes = GetFilteredPlaceableNodesOrAddOns(EditedFlowAsset, NativeFlowNodes, BlueprintFlowNodes); const UFlowGraphSettings& FlowGraphSettings = *UFlowGraphSettings::Get(); - for (const UFlowNodeBase* FlowNode : FilteredNodes) + for (const UFlowNodeBase* FlowNodeBase : FilteredNodes) { - if ((CategoryName.IsEmpty() || CategoryName.Equals(FlowNode->GetNodeCategory())) && !FlowGraphSettings.NodesHiddenFromPalette.Contains(FlowNode->GetClass())) + // TODO (gtaylor) This should really be integrated into GetFilteredPlaceableNodesOrAddOns, + // but it needs the schema instance, so we need to do a bit more refactoring + const FString NodeCategoryString = UFlowGraphSettings::GetNodeCategoryForNode(*FlowNodeBase); + const bool bAllowedForSchemaCategory = (CategoryName.IsEmpty() || CategoryName.Equals(NodeCategoryString)); + if (!bAllowedForSchemaCategory) { - TSharedPtr NewNodeAction(new FFlowGraphSchemaAction_NewNode(FlowNode, FlowGraphSettings)); - ActionMenuBuilder.AddAction(NewNodeAction); + continue; } + + TSharedPtr NewNodeAction(new FFlowGraphSchemaAction_NewNode(FlowNodeBase, FlowGraphSettings)); + ActionMenuBuilder.AddAction(NewNodeAction); } } @@ -1119,10 +1200,11 @@ void UFlowGraphSchema::GetGraphNodeContextActions(FGraphContextMenuBuilder& Cont UFlowGraphNode* OpNode = NewObject(Graph, GraphNodeClass); OpNode->NodeInstanceClass = FlowNodeAddOnTemplate->GetClass(); + const FString NodeCategoryString = UFlowGraphSettings::GetNodeCategoryForNode(*FlowNodeBase); TSharedPtr AddOpAction = FFlowSchemaAction_NewSubNode::AddNewSubNodeAction( ContextMenuBuilder, - FText::FromString(FlowNodeBase->GetNodeCategory()), + FText::FromString(NodeCategoryString), FlowNodeBase->GetNodeTitle(), FlowNodeBase->GetNodeToolTip()); diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp index abd8db3d2..42b404e38 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp @@ -203,13 +203,8 @@ UFlowGraphNode* FFlowGraphSchemaAction_NewNode::ImportNode(UEdGraph* ParentGraph FText FFlowGraphSchemaAction_NewNode::GetNodeCategory(const UFlowNodeBase* Node, const UFlowGraphSettings& GraphSettings) { - const FString* CategoryOverridenByUser = GraphSettings.OverridenNodeCategories.Find(Node->GetClass()); - if (CategoryOverridenByUser && !CategoryOverridenByUser->IsEmpty()) - { - return FText::FromString(*CategoryOverridenByUser); - } - - return FText::FromString(Node->GetNodeCategory()); + const FString NodeCategoryString = UFlowGraphSettings::GetNodeCategoryForNode(*Node); + return FText::FromString(NodeCategoryString); } ///////////////////////////////////////////////////// diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp index 02d054ac1..595efcdde 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSettings.cpp @@ -98,6 +98,18 @@ void UFlowGraphSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyC } } +FString UFlowGraphSettings::GetNodeCategoryForNode(const UFlowNodeBase& FlowNodeBase) +{ + const UFlowGraphSettings* GraphSettings = UFlowGraphSettings::Get(); + + if (const FString* CategoryOverridenByUser = GraphSettings->OverridenNodeCategories.Find(FlowNodeBase.GetClass())) + { + return *CategoryOverridenByUser; + } + + return FlowNodeBase.GetNodeCategory(); +} + const TMap& UFlowGraphSettings::EnsureNodeDisplayStylesMap() { if (NodeDisplayStylesAuthoredTags.Num() != NodeDisplayStyles.Num()) diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index f041c0280..e06873c90 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" @@ -196,6 +197,14 @@ void UFlowGraphNode::SubscribeToExternalChanges() if (NodeInstance) { NodeInstance->OnReconstructionRequested.BindUObject(this, &UFlowGraphNode::OnExternalChange); + + for (UFlowGraphNode* SubNode : SubNodes) + { + if (SubNode->NodeInstance) + { + SubNode->NodeInstance->OnAddOnRequestedParentReconstruction.BindUObject(this, &UFlowGraphNode::OnExternalChange); + } + } } } @@ -346,6 +355,11 @@ void UFlowGraphNode::ReconstructNode() bNeedsFullReconstruction = false; } + if (UFlowNode* FlowNode = Cast(NodeInstance)) + { + FlowNode->UpdateNodeConfigText(); + } + // This ensures the graph editor 'Refresh' button still rebuilds all the graph widgets even if the FlowGraphNode has nothing to update // Ideally we could get rid of the 'Refresh' button, but I think it will keep being useful, esp. for users making rough custom widgets (void)OnReconstructNodeCompleted.ExecuteIfBound(); @@ -886,13 +900,13 @@ void UFlowGraphNode::CreateInputPin(const FFlowPin& FlowPin, const int32 Index / return; } - const FName PinCategory = GetPinCategoryFromFlowPin(FlowPin); - const FName PinSubCategory = NAME_None; - UObject* PinSubCategoryObject = FlowPin.GetPinSubCategoryObject().Get(); + const FEdGraphPinType EdGraphPinType = FlowPin.BuildEdGraphPinType(); + + check(!EdGraphPinType.PinCategory.IsNone()); + constexpr bool bIsReference = false; - const FEdGraphPinType PinType = FEdGraphPinType(PinCategory, PinSubCategory, PinSubCategoryObject, EPinContainerType::None, bIsReference, FEdGraphTerminalType()); - UEdGraphPin* NewPin = CreatePin(EGPD_Input, PinType, FlowPin.PinName, Index); + UEdGraphPin* NewPin = CreatePin(EGPD_Input, EdGraphPinType, FlowPin.PinName, Index); check(NewPin); if (!FlowPin.PinFriendlyName.IsEmpty()) @@ -913,13 +927,12 @@ void UFlowGraphNode::CreateOutputPin(const FFlowPin& FlowPin, const int32 Index return; } - const FName PinCategory = GetPinCategoryFromFlowPin(FlowPin); - const FName PinSubCategory = NAME_None; - UObject* PinSubCategoryObject = FlowPin.GetPinSubCategoryObject().Get(); + const FEdGraphPinType EdGraphPinType = FlowPin.BuildEdGraphPinType(); constexpr bool bIsReference = false; - const FEdGraphPinType PinType = FEdGraphPinType(PinCategory, PinSubCategory, PinSubCategoryObject, EPinContainerType::None, bIsReference, FEdGraphTerminalType()); - UEdGraphPin* NewPin = CreatePin(EGPD_Output, PinType, FlowPin.PinName, Index); + check(!EdGraphPinType.PinCategory.IsNone()); + + UEdGraphPin* NewPin = CreatePin(EGPD_Output, EdGraphPinType, FlowPin.PinName, Index); check(NewPin); if (!FlowPin.PinFriendlyName.IsEmpty()) @@ -1114,10 +1127,6 @@ void UFlowGraphNode::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextO } } -const FName& UFlowGraphNode::GetPinCategoryFromFlowPin(const FFlowPin& FlowPin) -{ - return FFlowPin::GetPinCategoryFromPinType(FlowPin.GetPinType()); -} void UFlowGraphNode::ForcePinActivation(const FEdGraphPinReference PinReference) const { @@ -1493,6 +1502,10 @@ void UFlowGraphNode::AddSubNode(UFlowGraphNode* SubNode, class UEdGraph* ParentG SubNode->NodePosY = 0; SubNodes.Add(SubNode); + if (SubNode->NodeInstance) + { + SubNode->NodeInstance->OnAddOnRequestedParentReconstruction.BindUObject(this, &UFlowGraphNode::OnExternalChange); + } OnSubNodeAdded(SubNode); ParentGraph->NotifyGraphChanged(); @@ -1509,6 +1522,12 @@ void UFlowGraphNode::OnSubNodeAdded(UFlowGraphNode* SubNode) void UFlowGraphNode::RemoveSubNode(UFlowGraphNode* SubNode) { Modify(); + + if (SubNode->NodeInstance) + { + SubNode->NodeInstance->OnAddOnRequestedParentReconstruction.Unbind(); + } + SubNodes.RemoveSingle(SubNode); RebuildRuntimeAddOnsFromEditorSubNodes(); @@ -1518,6 +1537,14 @@ void UFlowGraphNode::RemoveSubNode(UFlowGraphNode* SubNode) void UFlowGraphNode::RemoveAllSubNodes() { + for (UFlowGraphNode* SubNode : SubNodes) + { + if (SubNode->NodeInstance) + { + SubNode->NodeInstance->OnAddOnRequestedParentReconstruction.Unbind(); + } + } + SubNodes.Reset(); RebuildRuntimeAddOnsFromEditorSubNodes(); @@ -1621,7 +1648,7 @@ void UFlowGraphNode::ValidateGraphNode(FFlowMessageLog& MessageLog) const const UFlowGraphSchema* Schema = CastChecked(GetSchema()); for (const UEdGraphPin* EdGraphPin : InputPins) { - if (!FFlowPin::IsDataPinCategory(EdGraphPin->PinType.PinCategory)) + if (FFlowPin::IsExecPinCategory(EdGraphPin->PinType.PinCategory)) { continue; } @@ -1711,12 +1738,10 @@ bool CheckPinsMatch(const TArray& LeftPins, const TArray& Ri for (const FFlowPin& Left : LeftPins) { + // Do a deep pin match (not a simple name-only match) auto PinsAreEqualPredicate = [&Left](const FFlowPin& Right) { - const bool bNameMatch = Left.PinName == Right.PinName; - const bool bFriendlyNameMatch = Left.PinFriendlyName.EqualTo(Right.PinFriendlyName); - const bool bTypeMatch = Left.GetPinType() == Right.GetPinType(); - return bNameMatch && bFriendlyNameMatch && bTypeMatch; + return Left.DeepIsEqual(Right); }; // For each required pin, make sure the existing pins array contains a pin that matches by name and type @@ -1785,6 +1810,10 @@ bool UFlowGraphNode::TryUpdateNodePins() const const UFlowNode* FlowNodeCDO = FlowNodeInstance->GetClass()->GetDefaultObject(); check(IsValid(FlowNodeCDO)); + // Fix up old pins on the CDO + UFlowNode* MutableCDO = const_cast(FlowNodeCDO); + MutableCDO->FixupDataPinTypes(); + // We grab basic built-in input/output pins from the CDO // We grab extra required pins from the actual node as generated context pins (this includes both data pins and other context exec pins) TArray RequiredNodeInputPins = FlowNodeCDO->GetInputPins(); @@ -1805,10 +1834,12 @@ bool UFlowGraphNode::TryUpdateNodePins() const // ------------ // If required pins don't match existing pins, brute force replace them + // (unless the node allows user added inputs/outputs, in which case we cannot destroy them) bool bPinsChanged = false; - if (!CheckPinsMatch(RequiredNodeInputPins, ExistingNodeInputPins)) + if (!FlowNodeInstance->CanUserAddInput() && + !CheckPinsMatch(RequiredNodeInputPins, ExistingNodeInputPins)) { FlowNodeInstance->Modify(); @@ -1818,7 +1849,8 @@ bool UFlowGraphNode::TryUpdateNodePins() const bPinsChanged = true; } - if (!CheckPinsMatch(RequiredNodeOutputPins, ExistingNodeOutputPins)) + if (!FlowNodeInstance->CanUserAddOutput() && + !CheckPinsMatch(RequiredNodeOutputPins, ExistingNodeOutputPins)) { FlowNodeInstance->Modify(); diff --git a/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp index 25d96645f..07477f3d0 100644 --- a/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp @@ -375,8 +375,12 @@ void SFlowGraphNode::UpdateGraphNode() .IsGraphNodeHovered(this, &SGraphNode::IsHovered); GetOrAddSlot(ENodeZone::TopCenter) - .SlotOffset(TAttribute(CommentBubble.Get(), &SCommentBubble::GetOffset)) - .SlotSize(TAttribute(CommentBubble.Get(), &SCommentBubble::GetSize)) + // #UE56Fix + //.SlotOffset(TAttribute(CommentBubble.Get(), &SCommentBubble::GetOffset)) + //.SlotSize(TAttribute(CommentBubble.Get(), &SCommentBubble::GetSize)) + .SlotOffset2f(TAttribute(CommentBubble.Get(), &SCommentBubble::GetOffset2f)) + .SlotSize2f(TAttribute(CommentBubble.Get(), &SCommentBubble::GetSize2f)) + // .AllowScaling(TAttribute(CommentBubble.Get(), &SCommentBubble::IsScalingAllowed)) .VAlign(VAlign_Top) [ @@ -511,7 +515,7 @@ EVisibility SFlowGraphNode::GetNodeConfigTextVisibility() const { // Hide in lower LODs const TSharedPtr OwnerPanel = GetOwnerPanel(); - if (!OwnerPanel.IsValid() || OwnerPanel->GetCurrentLOD() > EGraphRenderingLOD::MediumDetail) + if (!OwnerPanel.IsValid() || OwnerPanel->GetCurrentLOD() >= EGraphRenderingLOD::MediumDetail) { if (ConfigTextBlock && !ConfigTextBlock->GetText().IsEmptyOrWhitespace()) { 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/Asset/SFlowDiff.h b/Source/FlowEditor/Public/Asset/SFlowDiff.h index 5b4ea888e..c3def66cc 100644 --- a/Source/FlowEditor/Public/Asset/SFlowDiff.h +++ b/Source/FlowEditor/Public/Asset/SFlowDiff.h @@ -48,6 +48,8 @@ struct FLOWEDITOR_API FFlowDiffPanel void FocusDiff(const UEdGraphPin& Pin) const; void FocusDiff(const UEdGraphNode& Node) const; + void OnNodeClicked(UObject* ClickedNode ); + /** The Flow Asset that owns the graph we are showing */ const UFlowAsset* FlowAsset; @@ -55,8 +57,9 @@ struct FLOWEDITOR_API FFlowDiffPanel TSharedPtr GraphEditorBox; /** using SNullWidget::NullNullWidget can only work for a single widget, since widget instances can only be - * used one at a time. EmptyDetailsView is used for displaying an empty details panel instead. */ - TSharedPtr EmptyDetailsView; + * used one at a time. PanelDefaultDetailsView is used for displaying an empty details panel instead, as well + * as if the user selects a node in the graph view. */ + TSharedPtr PanelDefaultDetailsView; /** The graph editor which does the work of displaying the graph */ TWeakPtr GraphEditor; @@ -69,6 +72,10 @@ struct FLOWEDITOR_API FFlowDiffPanel /** The widget that contains the revision info in graph mode */ TSharedPtr OverlayGraphRevisionInfo; + + TWeakPtr GraphDiffSplitter = nullptr; + bool bIsOldPanel = false; + private: /** Command list for this diff panel */ TSharedPtr GraphEditorCommands; @@ -172,7 +179,7 @@ class FLOWEDITOR_API SFlowDiff : public SCompoundWidget FDiffControl GenerateDetailsPanel(); FDiffControl GenerateGraphPanel(); - TSharedRef GenerateGraphWidgetForPanel(FFlowDiffPanel& OutDiffPanel) const; + TSharedRef GenerateGraphWidgetForPanel(FFlowDiffPanel& OutDiffPanel) const; TSharedRef GenerateRevisionInfoWidgetForPanel(TSharedPtr& OutGeneratedWidget, const FText& InRevisionText) const; /** Accessor and event handler for toggling between diff view modes (defaults, components, graph view, interface, macro): */ 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/DetailCustomizations/FlowDataPinPropertyCustomizationBase.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinPropertyCustomizationBase.h deleted file mode 100644 index f29ee10c4..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinPropertyCustomizationBase.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" - -// Base class customization for most FFlow*DataPinProperty structs -class FFlowDataPinPropertyCustomizationBase : public IFlowExtendedPropertyTypeCustomization -{ - typedef IFlowExtendedPropertyTypeCustomization Super; - -protected: - - virtual void CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - virtual void CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; -}; - -// Template to create customization classes for FFlow*DataPinProperty structs -template -class TFlowDataPinPropertyCustomization : public FFlowDataPinPropertyCustomizationBase -{ -public: - static TSharedRef MakeInstance() { return MakeShareable(new TFlowDataPinPropertyCustomization()); } -}; diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinPropertyCustomizations.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinPropertyCustomizations.h deleted file mode 100644 index e696996b5..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinPropertyCustomizations.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "FlowDataPinPropertyCustomizationBase.h" - -#include "Types/FlowDataPinProperties.h" - -// Consider implementing details customization... for every EFlowPinType -FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_BoolCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_Int64Customization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_Int32Customization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_DoubleCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_FloatCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_NameCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_StringCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinOutputProperty_TextCustomization; - -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_BoolCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_Int64Customization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_Int32Customization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_DoubleCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_FloatCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_NameCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_StringCustomization; -typedef TFlowDataPinPropertyCustomization FFlowDataPinInputProperty_TextCustomization; - -// NOTE (gtaylor) Enum is defined separately, because it's quite a bit more complex -// NOTE (gtaylor) Class, Object are also defined separately as they are also more complex -// NOTE (gtaylor) BaseStruct types like FVector don't customize well using this technique, so I am leaving their default details handler in-place \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ClassCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ClassCustomization.h deleted file mode 100644 index eb8a25a90..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ClassCustomization.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" - -#include "Types/FlowDataPinProperties.h" - -class SClassPropertyEntryBox; - -class FFlowDataPinProperty_ClassCustomizationBase : public IFlowExtendedPropertyTypeCustomization -{ -private: - typedef IFlowExtendedPropertyTypeCustomization Super; - -protected: - - //~Begin IPropertyTypeCustomization - virtual void CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - virtual void CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - //~End IPropertyTypeCustomization - - // Accessor to return the actual struct being edited - FORCEINLINE FFlowDataPinOutputProperty_Class* GetFlowDataPinClassProperty() const - { - return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); - } - - UClass* DeriveBestClassFilter() const; - UClass* BuildMetaClass() const; - - void TrySetClassFilterFromMetaData(); - - void OnClassFilterChanged(); - const UClass* OnGetClass() const; - void OnSetClass(const UClass* NewClass); - - mutable TWeakObjectPtr CachedClassPtr; - mutable TWeakObjectPtr CachedMetaClassPtr; -}; - -// Details customization for FFlowDataPinOutputProperty_Class/FFlowDataPinInputProperty_Class -template -class TFlowDataPinProperty_ClassCustomization : public FFlowDataPinProperty_ClassCustomizationBase -{ -public: - static TSharedRef MakeInstance() { return MakeShareable(new TFlowDataPinProperty_ClassCustomization()); } -}; - -// Details customization for Class FFlowDataPinProperties -typedef TFlowDataPinProperty_ClassCustomization FFlowDataPinOutputProperty_ClassCustomization; -typedef TFlowDataPinProperty_ClassCustomization FFlowDataPinInputProperty_ClassCustomization; diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_EnumCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_EnumCustomization.h deleted file mode 100644 index 06ffcaa71..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_EnumCustomization.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "UnrealExtensions/IFlowCuratedNamePropertyCustomization.h" - -#include "Types/FlowDataPinProperties.h" - -// NOTE (gtaylor) this is nearly identical to AI Flow - FConfigurableEnumPropertyCustomization, can we combine them? - -class FFlowDataPinProperty_EnumCustomizationBase : public IFlowCuratedNamePropertyCustomization -{ -private: - typedef IFlowCuratedNamePropertyCustomization Super; - -protected: - - //~Begin IPropertyTypeCustomization - virtual void CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - //~End IPropertyTypeCustomization - - //~Begin ICuratedNamePropertyCustomization - virtual TSharedPtr GetCuratedNamePropertyHandle() const override; - virtual void SetCuratedName(const FName& NewName) override; - virtual bool TryGetCuratedName(FName& OutName) const override; - virtual TArray GetCuratedNameOptions() const override; - virtual bool AllowNameNoneIfOtherOptionsExist() const override { return false; } - //~End ICuratedNamePropertyCustomization - - // Accessor to return the actual struct being edited - FORCEINLINE FFlowDataPinOutputProperty_Enum* GetFlowDataPinEnumProperty() const - { - return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); - } - - void OnEnumNameChanged(); - - const UEnum* GetEnumClass() const; - - static TArray GetEnumValues(const UEnum& Enum); -}; - -// Details customization for FFlowDataPinOutputProperty_Enum -template -class TFlowDataPinProperty_EnumCustomization : public FFlowDataPinProperty_EnumCustomizationBase -{ -public: - static TSharedRef MakeInstance() { return MakeShareable(new TFlowDataPinProperty_EnumCustomization()); } -}; - -// Details customization for enum FFlowDataPinProperties -typedef TFlowDataPinProperty_EnumCustomization FFlowDataPinOutputProperty_EnumCustomization; -typedef TFlowDataPinProperty_EnumCustomization FFlowDataPinInputProperty_EnumCustomization; diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h deleted file mode 100644 index 2a1fffdec..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" - -#include "Types/FlowDataPinProperties.h" - -class FFlowDataPinProperty_ObjectCustomizationBase : public IFlowExtendedPropertyTypeCustomization -{ -private: - typedef IFlowExtendedPropertyTypeCustomization Super; - -protected: - - //~Begin IPropertyTypeCustomization - virtual void CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - virtual void CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - //~End IPropertyTypeCustomization - - // Accessor to return the actual struct being edited - FORCEINLINE FFlowDataPinOutputProperty_Object* GetFlowDataPinObjectProperty() const - { - return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); - } - - UClass* DeriveBestClassFilter() const; - UClass* BuildMetaClass() const; - - void TrySetClassFilterFromMetaData(); - - void OnClassFilterChanged(); - void OnObjectValueChanged(); - - mutable TWeakObjectPtr CachedMetaClassPtr; -}; - -// Details customization for FFlowDataPinOutputProperty_Object/FFlowDataPinInputProperty_Object -template -class TFlowDataPinProperty_ObjectCustomization : public FFlowDataPinProperty_ObjectCustomizationBase -{ -public: - static TSharedRef MakeInstance() { return MakeShareable(new TFlowDataPinProperty_ObjectCustomization()); } -}; - -// Details customization for Object FFlowDataPinProperties -typedef TFlowDataPinProperty_ObjectCustomization FFlowDataPinOutputProperty_ObjectCustomization; -typedef TFlowDataPinProperty_ObjectCustomization FFlowDataPinInputProperty_ObjectCustomization; diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h new file mode 100644 index 000000000..461a4f790 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h @@ -0,0 +1,141 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowPinType.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowPinEnums.h" +#include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" +#include "IPropertyTypeCustomization.h" +#include "Widgets/Input/SComboBox.h" +#include "Templates/Function.h" + +class IFlowDataPinValueOwnerInterface; + +/* +* Flow Data Pin Value Customization +* +* Responsibilities: +* - Header with MultiType selector + Input Pin checkbox. +* - Child rows for single vs array modes (built conditionally now). +* - Base for specialized enum/class/object customizations. +* - Exposes array validation helpers. +* +* Dynamic Mode Switching: +* - After MultiType changes, invokes OwnerInterface->RequestFlowDataPinValuesDetailsRebuild() +* (implemented via owner-level detail customization) to force a full rebuild. +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization : public IFlowExtendedPropertyTypeCustomization +{ + using Super = IFlowExtendedPropertyTypeCustomization; + +protected: + // Property handles + TSharedPtr MultiTypeHandle; + TSharedPtr ValuesHandle; + TSharedPtr IsInputPinHandle; + TSharedPtr PropertyUtilities; + + // Cached context + const FFlowPinType* DataPinType = nullptr; + IPropertyTypeCustomizationUtils* CustomizationUtils = nullptr; + IFlowDataPinValueOwnerInterface* OwnerInterface = nullptr; + + // MultiType UI state (enum values) + TArray> MultiTypeOptions; + TSharedPtr SelectedMultiType; + TSharedPtr>> MultiTypeComboBox; + + // Cached flag whether this pin type supports Array mode + bool bArraySupported = true; + +public: + FFlowDataPinValueCustomization() = default; + static TSharedRef MakeInstance(); + + // Non-copyable / non-movable + FFlowDataPinValueCustomization(const FFlowDataPinValueCustomization&) = delete; + FFlowDataPinValueCustomization& operator=(const FFlowDataPinValueCustomization&) = delete; + FFlowDataPinValueCustomization(FFlowDataPinValueCustomization&&) = delete; + FFlowDataPinValueCustomization& operator=(FFlowDataPinValueCustomization&&) = delete; + + // IPropertyTypeCustomization Interface + virtual void CustomizeHeader(TSharedRef InStructPropertyHandle, + FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + virtual void CustomizeChildren(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +protected: + // Build flow + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils); + + virtual void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); + virtual void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); // Skips if !bArraySupported + + void EnsureSingleElementExists(); + + void OnPropertyValueChanged(); + void OnChildPropertyValueChanged(); + void RequestRefresh(); + + // Mode / State + EFlowDataMultiType GetCurrentMultiType() const; + EVisibility GetSingleModeVisibility() const; + EVisibility GetArrayModeVisibility() const; + void TrimArrayToSingle(); + + // Appearance + FFlowDataPinValue* GetFlowDataPinValueBeingEdited() const + { + return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); + } + + // Input Pin + ECheckBoxState GetCurrentIsInputPin() const; + EVisibility GetInputPinCheckboxVisibility() const; + bool GetInputPinCheckboxEnabled() const; + + // MultiType UI Helpers + TSharedRef GenerateMultiTypeWidget(TSharedPtr Item) const; + FText GetSelectedMultiTypeText() const; + + // Change Handlers + void OnMultiTypeChanged(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + void OnInputPinChanged(ECheckBoxState NewState); + + // Caching + void CacheHandles(const TSharedRef& PropertyHandle, + IPropertyTypeCustomizationUtils& StructCustomizationUtils); + void CacheOwnerInterface(); + void CacheArraySupported(); + + // Shared Helpers ------------------------------------------------------ + void BuildVisibilityAwareArray(IDetailChildrenBuilder& StructBuilder, + TSharedPtr ArrayHandle, + TFunction, int32, IDetailChildrenBuilder&, const TAttribute&)> Generator, + TAttribute VisibilityAttribute); + + void ValidateArrayElements(TSharedPtr ArrayHandle, + TFunction)> IsValidPredicate, + TFunction)> InvalidateAction); + + // Tooltips (centralized) + static FText GetMultiTypeTooltip(); + static FText GetInputPinTooltip(); +}; + +// Template customization for simple scalar value structs +template +class TFlowDataPinValueCustomization : public FFlowDataPinValueCustomization +{ +public: + static TSharedRef MakeInstance() + { + return MakeShareable(new TFlowDataPinValueCustomization()); + } +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h new file mode 100644 index 000000000..a9fc9d82f --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h @@ -0,0 +1,84 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization.h" + +class SClassPropertyEntryBox; + +/* +* Class value customization: +* - Conditionally shows ClassFilter (OwnerInterface->ShowFlowDataPinValueClassFilter). +* - If MetaClass metadata present: show row but disabled. +* - Enabled state otherwise: OwnerInterface->CanEditFlowDataPinValueClassFilter. +* - Validates stored FSoftClassPath values against effective filter. +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_Class : public FFlowDataPinValueCustomization +{ + using Super = FFlowDataPinValueCustomization; + +public: + static TSharedRef MakeInstance() + { + return MakeShareable(new FFlowDataPinValueCustomization_Class()); + } + +protected: + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +private: + // Property handles + TSharedPtr ClassFilterHandle; + + // Metadata-derived flags + const UClass* RequiredInterface = nullptr; + bool bAllowAbstract = true; + bool bIsBlueprintBaseOnly = false; + bool bAllowNone = true; + bool bShowTreeView = false; + bool bHideViewOptions = false; + bool bShowDisplayNames = false; + bool bHasMetaClass = false; + + // Effective filter + TWeakObjectPtr CachedEffectiveFilter; + + // Helpers + void ExtractMetadata(); + void TrySetClassFilterFromMetaData(); + UClass* DeriveBestClassFilter() const; + void RefreshEffectiveFilter(); + + // UI + void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder, bool bSourceEditable); + void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); + void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); + void GenerateArrayElementRow(TSharedRef ElementHandle, int32 Index, + IDetailChildrenBuilder& ChildBuilder, const TAttribute& RowVisibility); + + // Delegates / validation + void BindDelegates(); + void OnClassFilterChanged(); + void OnValuesChanged(); + + void ValidateAllElements(); + bool IsElementValid(TSharedPtr ElementHandle) const; + void InvalidateElement(TSharedPtr ElementHandle); + + // Access / modification + const UClass* GetSelectedClassForHandle(TSharedPtr ElementHandle) const; + void OnSetClassForHandle(const UClass* NewClass, TSharedPtr ElementHandle); + + bool GetElementPathString(const TSharedPtr& ElementHandle, FString& OutPath) const; + static bool IsNoneString(const FString& Str); + + // Permissions (inline owner queries) + bool ShouldShowSourceRow() const; + bool IsSourceEditable() const; + bool AreValuesEditable() const { return true; } + + // Value struct access + struct FFlowDataPinValue_Class* GetValueStruct() const; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Enum.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Enum.h new file mode 100644 index 000000000..2c73e2b0b --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Enum.h @@ -0,0 +1,89 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization.h" + +class UEnum; + +/* +* Enum customization: +* - Conditionally shows EnumClass / EnumName (OwnerInterface->ShowFlowDataPinValueClassFilter). +* - Enabled if OwnerInterface->CanEditFlowDataPinValueClassFilter (MetaClass concept not applied here). +* - Enumerator selection via combo boxes (single / array). +* - Validates stored names. +* - Uses base single/array visibility helpers. +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_Enum : public FFlowDataPinValueCustomization +{ + using Super = FFlowDataPinValueCustomization; + +public: + static TSharedRef MakeInstance() + { + return MakeShareable(new FFlowDataPinValueCustomization_Enum()); + } + +protected: + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +private: + // Source handles + TSharedPtr EnumClassHandle; + TSharedPtr EnumNameHandle; + + // Enumerator state + TArray> EnumeratorOptions; + bool bEnumResolved = false; + bool bMultiTypeDelegateBound = false; + + // Build helpers + void BuildSingle(IDetailChildrenBuilder& StructBuilder); + void BuildArray(IDetailChildrenBuilder& StructBuilder); + + // Enum resolution + void CacheEnumHandles(const TSharedRef& StructHandle); + void OnEnumSourceChanged(); + void RebuildEnumData(); + UEnum* ResolveEnum() const; + void CollectEnumerators(UEnum& EnumObj); + + // Validation + void ValidateStoredValues(); + bool IsValueValid(const FName& Candidate) const; + TSharedPtr FindEnumeratorMatch(const FName& Current) const; + + // Multi-type reaction + void OnMultiTypeChanged(); + + // Array element generation + void GenerateArrayElementVisible(TSharedRef ElementHandle, + int32 Index, + IDetailChildrenBuilder& ChildBuilder, + const TAttribute& RowVisibility); + + // Widgets + TSharedRef GenerateEnumeratorWidget(TSharedPtr Item) const; + FText GetEnumeratorDisplayText(const FName& Value) const; + FText GetEnumSourceTooltip() const; + + // Selection handlers + void OnSingleValueChanged(TSharedPtr NewSelection, + ESelectInfo::Type SelectInfo, + TSharedPtr ElementHandle); + void OnArrayElementChanged(TSharedPtr NewSelection, + ESelectInfo::Type SelectInfo, + TSharedPtr ElementHandle); + + // Convenience + struct FFlowDataPinValue_Enum* GetEnumValueStruct() const; + bool HasEnumeratorOptions() const { return bEnumResolved && EnumeratorOptions.Num() > 0; } + bool IsValueEditingEnabled() const { return HasEnumeratorOptions(); } + + // Permissions (inline owner queries) + bool ShouldShowSourceRow() const; + bool IsSourceEditable() const; + bool AreValuesEditable() const { return true; } +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h new file mode 100644 index 000000000..0c678d48d --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h @@ -0,0 +1,67 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization.h" + +/* +* Object value customization: +* - Conditionally shows ClassFilter (OwnerInterface->ShowFlowDataPinValueClassFilter). +* - MetaClass metadata forces filter (row shown but disabled). +* - Validates object references against effective filter. +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_Object : public FFlowDataPinValueCustomization +{ + using Super = FFlowDataPinValueCustomization; + +public: + static TSharedRef MakeInstance() + { + return MakeShareable(new FFlowDataPinValueCustomization_Object()); + } + +protected: + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +private: + // Property handles + TSharedPtr ClassFilterHandle; + + // MetaClass state + bool bMetaClassForced = false; + TWeakObjectPtr EffectiveFilterClass; + + // UI building + void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder, bool bSourceEditable); + void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); + void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); + + // Metadata / filter + void TryApplyMetaClass(); + void ResolveEffectiveFilter(); + + // Delegates & validation + void BindDelegates(); + void OnClassFilterChanged(); + void OnValuesChanged(); + void ValidateAll(); + bool IsElementValid(TSharedPtr ElementHandle) const; + void InvalidateElement(TSharedPtr ElementHandle); + + // Value access + UObject* GetObjectValue(TSharedPtr ElementHandle) const; + void SetObjectValue(TSharedPtr ElementHandle, UObject* NewObj); + + // Permissions (inline owner queries) + bool ShouldShowSourceRow() const; + bool IsSourceEditable() const; + bool AreValuesEditable() const { return true; } + + // Value struct accessor + struct FFlowDataPinValue_Object* GetValueStruct() const; + + // Widget + TSharedRef BuildObjectValueWidgetForElement(TSharedPtr ElementHandle); +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomization.h new file mode 100644 index 000000000..2531cf5e8 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomization.h @@ -0,0 +1,60 @@ +#pragma once + +#if WITH_EDITOR + +#include "IDetailCustomization.h" +#include "DetailLayoutBuilder.h" +#include "Delegates/Delegate.h" +#include "Templates/SharedPointer.h" + +class IFlowDataPinValueOwnerInterface; + +/* +* Template customization for owner types implementing IFlowDataPinValueOwnerInterface. +* Captures the layout builder pointer and installs a rebuild delegate. +*/ +template +class TFlowDataPinValueOwnerCustomization : public IDetailCustomization +{ +public: + static TSharedRef MakeInstance() + { + return MakeShareable(new TFlowDataPinValueOwnerCustomization()); + } + + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override + { + CachedBuilder = &DetailBuilder; + + TArray> Objects; + DetailBuilder.GetObjectsBeingCustomized(Objects); + + for (TWeakObjectPtr& Obj : Objects) + { + OwnerT* TypedOwner = Cast(Obj.Get()); + if (!TypedOwner) + { + continue; + } + + if (IFlowDataPinValueOwnerInterface* InterfacePtr = Cast(TypedOwner)) + { + InterfacePtr->SetFlowDataPinValuesRebuildDelegate( + FSimpleDelegate::CreateSP(this, &TFlowDataPinValueOwnerCustomization::RequestRebuild)); + } + } + } + +private: + IDetailLayoutBuilder* CachedBuilder = nullptr; + + void RequestRebuild() + { + if (CachedBuilder) + { + CachedBuilder->ForceRefreshDetails(); // Full rebuild; will recreate this customization instance + } + } +}; + +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomizations.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomizations.h new file mode 100644 index 000000000..9cf99f9e8 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomizations.h @@ -0,0 +1,13 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowDataPinValueOwnerCustomization.h" + +#include "Asset/FlowAssetParams.h" +#include "Nodes/FlowNodeBase.h" +#include "FlowExecutableActorComponent.h" + +using FFlowAssetParamsCustomization = TFlowDataPinValueOwnerCustomization; +using FFlowNodeBaseCustomization = TFlowDataPinValueOwnerCustomization; +using FFlowExecutableActorComponentCustomization = TFlowDataPinValueOwnerCustomization; diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h new file mode 100644 index 000000000..0d8767e61 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h @@ -0,0 +1,27 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowDataPinValueCustomization.h" +#include "Types/FlowDataPinValuesStandard.h" + +// Specialized customizations +#include "DetailCustomizations/FlowDataPinValueCustomization_Enum.h" +#include "DetailCustomizations/FlowDataPinValueCustomization_Class.h" +#include "DetailCustomizations/FlowDataPinValueCustomization_Object.h" + +// Scalar / simple using aliases +using FFlowDataPinValueCustomization_Bool = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Int = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Int64 = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Float = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Double = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Name = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_String = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Text = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Vector = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Rotator = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_Transform = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_GameplayTag = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_GameplayTagContainer = TFlowDataPinValueCustomization; +using FFlowDataPinValueCustomization_InstancedStruct = TFlowDataPinValueCustomization; diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinPropertyCustomization.h similarity index 92% rename from Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h rename to Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinPropertyCustomization.h index f63e1e18b..c6a642bd6 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowNamedDataPinPropertyCustomization.h @@ -4,8 +4,6 @@ #include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" -#include "Types/FlowDataPinProperties.h" - // Details customization for FFlowPin class FFlowNamedDataPinPropertyCustomization : public IFlowExtendedPropertyTypeCustomization { diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowNodeAddOn_Details.h b/Source/FlowEditor/Public/DetailCustomizations/FlowNodeAddOn_Details.h index 05544d12b..423820ec3 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowNodeAddOn_Details.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowNodeAddOn_Details.h @@ -2,9 +2,13 @@ #pragma once +#include "FlowDataPinValueOwnerCustomization.h" #include "IDetailCustomization.h" +#include "Templates/SharedPointer.h" -class FFlowNodeAddOn_Details final : public IDetailCustomization +class UFlowNodeAddOn; + +class FFlowNodeAddOn_Details final : public TFlowDataPinValueOwnerCustomization { public: static TSharedRef MakeInstance() diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowNode_Details.h b/Source/FlowEditor/Public/DetailCustomizations/FlowNode_Details.h index ce85a66d6..297d81b5f 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowNode_Details.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowNode_Details.h @@ -2,9 +2,13 @@ #pragma once +#include "FlowDataPinValueOwnerCustomization.h" #include "IDetailCustomization.h" +#include "Templates/SharedPointer.h" -class FFlowNode_Details final : public IDetailCustomization +class UFlowNode; + +class FFlowNode_Details final : public TFlowDataPinValueOwnerCustomization { public: static TSharedRef MakeInstance() diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowPinCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowPinCustomization.h index 81fdde281..d73b8c7c7 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowPinCustomization.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowPinCustomization.h @@ -4,7 +4,7 @@ #include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" -#include "Types/FlowDataPinProperties.h" +struct FFlowPin; // Details customization for FFlowPin class FFlowPinCustomization : public IFlowExtendedPropertyTypeCustomization 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/FlowGraph.h b/Source/FlowEditor/Public/Graph/FlowGraph.h index 4c9decc2c..0fe54ae72 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraph.h +++ b/Source/FlowEditor/Public/Graph/FlowGraph.h @@ -21,6 +21,8 @@ class FLOWEDITOR_API UFlowGraph : public UEdGraph UPROPERTY() int32 GraphVersion; + static constexpr int32 CurrentGraphVersion = 2; + /** if set, graph modifications won't cause updates in internal tree structure * flag allows freezing update during heavy changes like pasting new nodes */ @@ -37,6 +39,8 @@ class FLOWEDITOR_API UFlowGraph : public UEdGraph void RefreshGraph(); protected: + void UpgradeAllFlowNodePins(); + void RecursivelyRefreshAddOns(UFlowGraphNode& FromFlowGraphNode); static void RecursivelySetupAllFlowGraphNodesForEditing(UFlowGraphNode& FromFlowGraphNode); diff --git a/Source/FlowEditor/Public/Graph/FlowGraphNodesPolicy.h b/Source/FlowEditor/Public/Graph/FlowGraphNodesPolicy.h new file mode 100644 index 000000000..1b40db35f --- /dev/null +++ b/Source/FlowEditor/Public/Graph/FlowGraphNodesPolicy.h @@ -0,0 +1,30 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowGraphNodesPolicy.generated.h" + +class UFlowNodeBase; + +USTRUCT() +struct FFlowGraphNodesPolicy +{ + GENERATED_BODY(); + +public: +#if WITH_EDITORONLY_DATA + UPROPERTY(Config, EditAnywhere, Category = "Nodes") + TArray AllowedCategories; + + UPROPERTY(Config, EditAnywhere, Category = "Nodes") + TArray DisallowedCategories; +#endif + +#if WITH_EDITOR +public: + bool IsNodeAllowedByPolicy(const UFlowNodeBase* FlowNodeBase) const; + +protected: + static bool IsAnySubcategory(const FString& CheckCategory, const TArray& Categories); +#endif +}; diff --git a/Source/FlowEditor/Public/Graph/FlowGraphPinFactory.h b/Source/FlowEditor/Public/Graph/FlowGraphPinFactory.h index 23f748d66..67b2f7204 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphPinFactory.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphPinFactory.h @@ -5,8 +5,6 @@ #include "EdGraphSchema_K2.h" #include "EdGraphUtilities.h" -#include "FlowGraphPinFactory.generated.h" - struct FFlowPin; class FFlowGraphPinFactory : public FGraphPanelPinFactory @@ -18,13 +16,3 @@ class FFlowGraphPinFactory : public FGraphPanelPinFactory static int32 GatherValidPinsCount(const TArray& Pins); }; - -// Thin subclass of UEdGraphSchema_K2 to gain access to PC_* defines so we can assert their values -UCLASS() -class UFlowK2SchemaSubclassForAccess : public UEdGraphSchema_K2 -{ - GENERATED_BODY() - -public: - static void AssertPinCategoryNames(); -}; diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h index 070f82151..0d7909562 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h @@ -2,6 +2,9 @@ #pragma once +#include "Asset/FlowPinTypeMatchPolicy.h" +#include "Types/FlowPinTypeNamesStandard.h" + #include "EdGraph/EdGraphSchema.h" #include "Runtime/Launch/Resources/Version.h" #include "Templates/SubclassOf.h" @@ -13,6 +16,7 @@ class UFlowNode; class UFlowNodeAddOn; class UFlowNodeBase; class UFlowGraphNode; +struct FFlowPinType; DECLARE_MULTICAST_DELEGATE(FFlowGraphSchemaRefresh); @@ -31,11 +35,6 @@ class FLOWEDITOR_API UFlowGraphSchema : public UEdGraphSchema static TMap BlueprintFlowNodeAddOns; static TMap, TSubclassOf> GraphNodesByFlowNodes; - // cached pointers to struct types - static const UScriptStruct* VectorStruct; - static const UScriptStruct* RotatorStruct; - static const UScriptStruct* TransformStruct; - static bool bBlueprintCompilationPending; public: @@ -74,6 +73,16 @@ class FLOWEDITOR_API UFlowGraphSchema : public UEdGraphSchema // FlowGraphSchema + static const FFlowPinType* LookupDataPinTypeForPinCategory(const FName& PinCategory); + + void EnsurePinTypesInitialized(); + + bool ArePinSubCategoryObjectsCompatible( + const UStruct* OutputStruct, + const UStruct* InputStruct, + const FFlowPinTypeMatchPolicy& PinTypeMatchPolicy, + FPinConnectionResponse& OutConnectionResponse) const; + /** * Returns true if the two pin types are schema compatible. Handles outputting a more derived * type to an input pin expecting a less derived type. @@ -117,9 +126,16 @@ class FLOWEDITOR_API UFlowGraphSchema : public UEdGraphSchema static bool IsPIESimulating(); protected: - static UFlowGraphNode* CreateDefaultNode(UEdGraph& Graph, const TSubclassOf& NodeClass, const FVector2D& Offset, bool bPlacedAsGhostNode); - static bool ArePinCategoriesEffectivelyMatching(const FName& InputPinCategory, const FName& OutputPinCategory, bool bAllowImplicitCasts = true); + // These are the policies for matching data pin types + UPROPERTY(Transient) + TMap PinTypeMatchPolicies; + + // TODO (gtaylor) The mechanism for customizing PinTypeMatchPolicies will need some revision. + // I am going with a simple virtual method on schema For Now(tm) but expect a revision in how this is done, in the future. + virtual void InitializedPinTypes(); + + static UFlowGraphNode* CreateDefaultNode(UEdGraph& Graph, const TSubclassOf& NodeClass, const FVector2D& Offset, bool bPlacedAsGhostNode); private: static void ApplyNodeOrAddOnFilter(const UFlowAsset* AssetClassDefaults, const UClass* FlowNodeClass, TArray& FilteredNodes); diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSettings.h b/Source/FlowEditor/Public/Graph/FlowGraphSettings.h index 18a91a7a4..007d54c8f 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphSettings.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphSettings.h @@ -7,6 +7,7 @@ #include "GameplayTagContainer.h" #include "FlowTypes.h" +#include "Graph/FlowGraphNodesPolicy.h" #include "FlowGraphSettings.generated.h" class UFlowNodeBase; @@ -98,6 +99,10 @@ class FLOWEDITOR_API UFlowGraphSettings : public UDeveloperSettings UPROPERTY(EditAnywhere, config, Category = "Nodes", meta = (ConfigRestartRequired = true)) TArray> NodesHiddenFromPalette; + /** Configurable map of FlowAsset subclasses to the FlowAssetNodePolicy for that subclass */ + UPROPERTY(EditAnywhere, Config, Category = "Nodes", meta = (ConfigRestartRequired = true, AllowedClasses = "/Script/Flow.FlowAsset")) + TMap PerAssetSubclassFlowNodePolicies; + /** Allows anyone to override Flow Palette category for specific nodes without modifying source code.*/ UPROPERTY(EditAnywhere, config, Category = "Nodes") TMap, FString> OverridenNodeCategories; @@ -184,6 +189,9 @@ class FLOWEDITOR_API UFlowGraphSettings : public UDeveloperSettings virtual FName GetCategoryName() const override { return FName("Flow Graph"); } virtual FText GetSectionText() const override { return INVTEXT("Graph Settings"); } + // Override-safe category query for flow node + static FString GetNodeCategoryForNode(const UFlowNodeBase& FlowNodeBase); + #if WITH_EDITOR const TMap& EnsureNodeDisplayStylesMap(); bool TryAddDefaultNodeDisplayStyle(const FFlowNodeDisplayStyleConfig& StyleConfig); diff --git a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h index a9d715862..c01ccef93 100644 --- a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h +++ b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h @@ -93,6 +93,8 @@ class FLOWEDITOR_API UFlowGraphNode : public UEdGraphNode */ void InsertNewNode(UEdGraphPin* FromPin, UEdGraphPin* NewLinkPin, TSet& OutNodeList); + void MarkNeedsFullReconstruction() { bNeedsFullReconstruction = true; } + // UEdGraphNode virtual void ReconstructNode() override; virtual void AllocateDefaultPins() override; @@ -157,7 +159,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; @@ -208,11 +210,6 @@ class FLOWEDITOR_API UFlowGraphNode : public UEdGraphNode // @return true, if pins cannot be connected due to node's inner logic, put message for user in OutReason virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { return false; } -protected: - // Gets the PinCategory from the FlowPin - // (accounting for FFlowPin structs that predate the PinCategory field) - static const FName& GetPinCategoryFromFlowPin(const FFlowPin& FlowPin); - ////////////////////////////////////////////////////////////////////////// // Execution Override 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: diff --git a/Source/FlowEditor/Public/UnrealExtensions/VisibilityArrayBuilder.h b/Source/FlowEditor/Public/UnrealExtensions/VisibilityArrayBuilder.h new file mode 100644 index 000000000..f73e536b1 --- /dev/null +++ b/Source/FlowEditor/Public/UnrealExtensions/VisibilityArrayBuilder.h @@ -0,0 +1,278 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "IDetailCustomNodeBuilder.h" +#include "IDetailChildrenBuilder.h" +#include "DetailWidgetRow.h" +#include "PropertyHandle.h" + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "PropertyCustomizationHelpers.h" +#include "SResetToDefaultMenu.h" + +DECLARE_DELEGATE_FourParams(FOnGenerateArrayElementWidgetVisible, + TSharedRef, // ElementHandle + int32, // Index + IDetailChildrenBuilder&, // ChildrenBuilder + const TAttribute&); // RowVisibility + +/* +* FVisibilityArrayBuilder +* +* A fork of UE's FDetailArrayBuilder that: +* - Adds a live visibility getter via SetVisibilityGetter(TFunction) +* - Uses a 4‑parameter element generation delegate: +* (ElementHandle, Index, ChildrenBuilder, RowVisibility) +* - Works around engine behavior where setting NodeRow.Visibility() on a custom node +* header does not always live‑update by wrapping the header name & value widgets +* in SBoxes whose Visibility attributes are dynamic. +* +* Usage: +* TSharedRef ArrayBuilder = +* MakeShareable(new FVisibilityArrayBuilder(ValuesHandle.ToSharedRef(), true, true, true)); +* +* ArrayBuilder->SetVisibilityGetter([this]() +* { +* uint8 Raw = 0; +* if (MultiTypeHandle.IsValid() && +* MultiTypeHandle->GetValue(Raw) == FPropertyAccess::Success && +* (EFlowDataMultiType)Raw == EFlowDataMultiType::Array) +* { +* return EVisibility::Visible; +* } +* return EVisibility::Collapsed; +* }); +* +* ArrayBuilder->OnGenerateArrayElementWidget( +* FOnGenerateArrayElementWidgetVisible::CreateSP( +* this, &FFlowDataPinValueCustomization_Enum::GenerateArrayElementVisible)); +* +* StructBuilder.AddCustomBuilder(ArrayBuilder); +* +* Notes: +* - Structural changes (Add / Remove / Reorder) still require RequestRefresh() to rebuild rows. +* - The dynamic visibility lambda MUST read the property handle each time (no cached enum mode). +* - If you want to supply your own header (e.g. to insert custom buttons), construct with +* bInGenerateHeader = false +* and add a separate AddCustomRow() above the builder with its own .Visibility binding. +*/ + +class FVisibilityArrayBuilder : public IDetailCustomNodeBuilder +{ +public: + FVisibilityArrayBuilder(TSharedRef InBaseProperty, + bool bInGenerateHeader = true, + bool bInDisplayResetToDefault = true, + bool bInDisplayElementNum = true) + : ArrayProperty(InBaseProperty->AsArray()) + , BaseProperty(InBaseProperty) + , bGenerateHeader(bInGenerateHeader) + , bDisplayResetToDefault(bInDisplayResetToDefault) + , bDisplayElementNum(bInDisplayElementNum) + { + check(ArrayProperty.IsValid()); + + FSimpleDelegate OnNumChildrenChanged = + FSimpleDelegate::CreateRaw(this, &FVisibilityArrayBuilder::OnNumChildrenChanged); + OnNumElementsChangedHandle = ArrayProperty->SetOnNumElementsChanged(OnNumChildrenChanged); + + // Hide original property presentation so only our custom builder is shown. + BaseProperty->MarkHiddenByCustomization(); + } + + ~FVisibilityArrayBuilder() + { + if (ArrayProperty.IsValid()) + { + ArrayProperty->UnregisterOnNumElementsChanged(OnNumElementsChangedHandle); + } + } + + // Non-copyable / non-movable: avoid duplicate delegate registrations + FVisibilityArrayBuilder(const FVisibilityArrayBuilder&) = delete; + FVisibilityArrayBuilder& operator=(const FVisibilityArrayBuilder&) = delete; + FVisibilityArrayBuilder(FVisibilityArrayBuilder&&) = delete; + FVisibilityArrayBuilder& operator=(FVisibilityArrayBuilder&&) = delete; + + // Assign a visibility callback (evaluated whenever Slate queries the attribute). + FVisibilityArrayBuilder& SetVisibilityGetter(TFunction&& InGetter) + { + VisibilityGetter = MoveTemp(InGetter); + return *this; + } + + void SetDisplayName(const FText& InDisplayName) + { + DisplayName = InDisplayName; + } + + void OnGenerateArrayElementWidget(FOnGenerateArrayElementWidgetVisible InDelegate) + { + OnGenerateArrayElementWidgetDelegate = InDelegate; + } + + // IDetailCustomNodeBuilder interface + virtual bool RequiresTick() const override { return false; } + virtual void Tick(float /*DeltaTime*/) override {} + + virtual FName GetName() const override + { + return BaseProperty->GetProperty()->GetFName(); + } + + virtual bool InitiallyCollapsed() const override { return false; } + + virtual void GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) override + { + if (!bGenerateHeader) + { + return; + } + + // Dynamic visibility attribute for header contents (NOT on NodeRow itself). + TAttribute DynamicVis = MakeDynamicVisibilityAttribute(); + + // Horizontal box for value content (mirrors stock array builder's layout). + TSharedPtr ContentHorizontalBox; + SAssignNew(ContentHorizontalBox, SHorizontalBox); + + if (bDisplayElementNum) + { + ContentHorizontalBox->AddSlot() + [ + BaseProperty->CreatePropertyValueWidget() + ]; + } + + FUIAction CopyAction; + FUIAction PasteAction; + BaseProperty->CreateDefaultPropertyCopyPasteActions(CopyAction, PasteAction); + + NodeRow + // Leave NodeRow itself always present; hide internal widgets via SBox visibility. + .FilterString(!DisplayName.IsEmpty() ? DisplayName : BaseProperty->GetPropertyDisplayName()) + .NameContent() + [ + SNew(SBox) + .Visibility(DynamicVis) + [ + BaseProperty->CreatePropertyNameWidget(DisplayName, FText::GetEmpty()) + ] + ] + .ValueContent() + [ + SNew(SBox) + .Visibility(DynamicVis) + [ + ContentHorizontalBox.ToSharedRef() + ] + ] + .CopyAction(CopyAction) + .PasteAction(PasteAction); + + if (bDisplayResetToDefault) + { + TSharedPtr ResetToDefaultMenu; + ContentHorizontalBox->AddSlot() + .AutoWidth() + .Padding(FMargin(2.f, 0.f, 0.f, 0.f)) + [ + SAssignNew(ResetToDefaultMenu, SResetToDefaultMenu) + ]; + ResetToDefaultMenu->AddProperty(BaseProperty); + } + } + + virtual void GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) override + { + uint32 NumChildren = 0; + ArrayProperty->GetNumElements(NumChildren); + + TAttribute DynamicVis = MakeDynamicVisibilityAttribute(); + + for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) + { + TSharedRef ElementHandle = ArrayProperty->GetElement(ChildIndex); + + if (OnGenerateArrayElementWidgetDelegate.IsBound()) + { + OnGenerateArrayElementWidgetDelegate.Execute( + ElementHandle, + static_cast(ChildIndex), + ChildrenBuilder, + DynamicVis); + } + else + { + IDetailPropertyRow& Row = ChildrenBuilder.AddProperty(ElementHandle); + Row.Visibility(DynamicVis); + } + } + } + + // Manual refresh (not virtual in some engine versions) + void RefreshChildren() + { + OnRebuildChildren.ExecuteIfBound(); + } + + virtual TSharedPtr GetPropertyHandle() const + { + return BaseProperty; + } + +protected: + virtual void SetOnRebuildChildren(FSimpleDelegate InOnRebuildChildren) override + { + OnRebuildChildren = InOnRebuildChildren; + } + + void OnNumChildrenChanged() + { + OnRebuildChildren.ExecuteIfBound(); + } + +private: + TAttribute MakeDynamicVisibilityAttribute() const + { + // Optimization: if no custom getter, return a simple constant attribute (no lambda capture) + if (!VisibilityGetter) + { + return TAttribute(EVisibility::Visible); + } + + // Capture 'this' only when needed + return TAttribute::CreateLambda([this]() + { + return VisibilityGetter(); + }); + } + +private: + // Display name override + FText DisplayName; + + // Element generator + FOnGenerateArrayElementWidgetVisible OnGenerateArrayElementWidgetDelegate; + + // Array + base property handles + TSharedPtr ArrayProperty; + TSharedRef BaseProperty; + + // Rebuild delegate + FSimpleDelegate OnRebuildChildren; + + // Visibility getter (live) + TFunction VisibilityGetter; + + // Config + bool bGenerateHeader; + bool bDisplayResetToDefault; + bool bDisplayElementNum; + + // Delegate handle for array size changes + FDelegateHandle OnNumElementsChangedHandle; +}; \ No newline at end of file