From 93800f0ea1ee9cc70fa39440f8b4b82e6707b6a7 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:26:09 -0700 Subject: [PATCH 1/6] [FlowAssetParams] Initial implementation of FlowAssetParams FlowAssetParams are a new data asset, that is optionally managed by the FlowAsset, sourcing properties from the Start node of the graph. These params can be subclassed to create overrides. At runtime, either the base or a compatible subclass may be used to provide data for start data pins in the flow graph. - Added FlowAssetParams - Added support to FlowAsset to optionally create and sync with FlowAssetParams base - Added AssetDefinition_FlorAssetParams to add 'create child', for creating 'subclassed' params that override some elements - Updated FFlowNamedDataPinProperty to have Guid and other properties used in the FlowAssetParams use-case - Added FlowNamedPropertiesSupplierInterface - Added FlowAssetProviderInterface - FFlowAssetParamsPtr - wrapper class for details customization - Added Params support to FlowComponent & FlowSubsystem Also: - Removed stale references to IFlowOwnerInterface - Merged over some deltas from IFlowExtended/Curated Property Customization --- Source/Flow/Flow.Build.cs | 3 +- Source/Flow/Private/FlowAsset.cpp | 134 +++++++++++++++++- Source/Flow/Private/FlowComponent.cpp | 4 +- Source/Flow/Private/FlowSubsystem.cpp | 8 +- Source/Flow/Private/Nodes/FlowNodeBase.cpp | 74 ---------- Source/Flow/Public/FlowAsset.h | 24 ++++ Source/Flow/Public/FlowComponent.h | 15 +- Source/Flow/Public/FlowSubsystem.h | 5 +- .../Public/Interfaces/FlowOwnerInterface.h | 4 +- .../Nodes/Actor/FlowNode_ExecuteComponent.h | 1 - Source/Flow/Public/Nodes/FlowNodeBase.h | 10 -- .../Nodes/Graph/FlowNode_DefineProperties.h | 10 +- .../Public/Nodes/Graph/FlowNode_SubGraph.h | 4 +- .../Flow/Public/Types/FlowDataPinProperties.h | 116 ++++++++++----- .../Asset/AssetDefinition_FlowAsset.cpp | 2 +- .../Private/Asset/FlowObjectDiff.cpp | 2 +- .../FlowEditor/Private/FlowEditorModule.cpp | 6 +- .../Private/Graph/Nodes/FlowGraphNode.cpp | 1 + .../IFlowCuratedNamePropertyCustomization.cpp | 34 ++--- ...IFlowExtendedPropertyTypeCustomization.cpp | 3 - .../FlowEditor/Public/Asset/FlowDiffControl.h | 1 + .../FlowEditor/Public/Asset/FlowObjectDiff.h | 1 + Source/FlowEditor/Public/FlowEditorModule.h | 2 +- .../Public/Graph/Nodes/FlowGraphNode.h | 2 +- .../Public/Graph/Widgets/SFlowGraphNode.h | 7 +- .../IFlowCuratedNamePropertyCustomization.h | 11 +- 26 files changed, 313 insertions(+), 171 deletions(-) diff --git a/Source/Flow/Flow.Build.cs b/Source/Flow/Flow.Build.cs index 74df20a4c..4d8b12253 100644 --- a/Source/Flow/Flow.Build.cs +++ b/Source/Flow/Flow.Build.cs @@ -32,7 +32,8 @@ public Flow(ReadOnlyTargetRules target) : base(target) PublicDependencyModuleNames.AddRange(new[] { "MessageLog", - "UnrealEd" + "UnrealEd", + "SourceControl", }); } } diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 521ed3e36..9f52f9edb 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -5,8 +5,9 @@ #include "FlowLogChannels.h" #include "FlowSettings.h" #include "FlowSubsystem.h" - #include "AddOns/FlowNodeAddOn.h" +#include "Asset/FlowAssetParams.h" +#include "Asset/FlowAssetParamsUtils.h" #include "Interfaces/FlowDataPinGeneratorNodeInterface.h" #include "Nodes/FlowNodeBase.h" #include "Nodes/Graph/FlowNode_CustomInput.h" @@ -19,8 +20,18 @@ #include "Serialization/MemoryWriter.h" #if WITH_EDITOR +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" #include "Editor.h" #include "Editor/EditorEngine.h" +#include "Modules/ModuleManager.h" +#include "ObjectTools.h" +#include "SourceControlHelpers.h" +#include "UObject/ObjectSaveContext.h" +#include "UObject/Package.h" +#include "UObject/SavePackage.h" FString UFlowAsset::ValidationError_NodeClassNotAllowed = TEXT("Node class {0} is not allowed in this asset."); FString UFlowAsset::ValidationError_NullNodeInstance = TEXT("Node with GUID {0} is NULL"); @@ -101,6 +112,127 @@ void UFlowAsset::PostLoad() { UnregisterNode(Guid); } + + ReconcileBaseAssetParams(FFlowAssetParamsUtils::GetLastSavedTimestampForObject(this)); +} + +void UFlowAsset::PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext) +{ + ReconcileBaseAssetParams(FDateTime::Now()); +} + +void UFlowAsset::ReconcileBaseAssetParams(const FDateTime& AssetLastSavedTimestamp) +{ + if (BaseAssetParams.AssetPtr.IsNull()) + { + return; + } + + UFlowAssetParams* BaseAssetParamsPtr = BaseAssetParams.AssetPtr.LoadSynchronous(); + if (!IsValid(BaseAssetParamsPtr)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to load BaseAssetParams: %s"), *BaseAssetParams.AssetPtr.ToString()); + return; + } + + IFlowNamedPropertiesSupplierInterface* NamedPropertiesSupplier = Cast(GetDefaultEntryNode()); + if (!NamedPropertiesSupplier) + { + UE_LOG(LogFlow, Error, TEXT("No NamedPropertiesSupplier (e.g., Start node) found in FlowAsset: %s"), *GetPathName()); + return; + } + + TArray& MutableStartNodeProperties = NamedPropertiesSupplier->GetMutableNamedProperties(); + const EFlowReconcilePropertiesResult ReconcileResult = + BaseAssetParamsPtr->ReconcilePropertiesWithStartNode(AssetLastSavedTimestamp, this, MutableStartNodeProperties); + + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile BaseAssetParams for %s: %s"), + *BaseAssetParamsPtr->GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } +} + +UFlowAssetParams* UFlowAsset::GenerateParamsFromStartNode() +{ + if (BaseAssetParams.AssetPtr.IsValid()) + { + UE_LOG(LogFlow, Warning, TEXT("BaseAssetParams already exists for %s: %s"), *GetPathName(), *BaseAssetParams.AssetPtr.ToString()); + return BaseAssetParams.AssetPtr.LoadSynchronous(); + } + + // Get the Start node + IFlowNamedPropertiesSupplierInterface* NamedPropertiesSupplier = Cast(GetDefaultEntryNode()); + if (!NamedPropertiesSupplier) + { + UE_LOG(LogFlow, Error, TEXT("No valid Start node found for generating params in %s"), *GetPathName()); + return nullptr; + } + + // Determine the params asset name + const FString ParamsAssetName = GenerateParamsAssetName(); + if (ParamsAssetName.IsEmpty()) + { + UE_LOG(LogFlow, Error, TEXT("Generated empty params asset name for %s"), *GetPathName()); + return nullptr; + } + + // Create the params asset + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + const FString PackagePath = FPackageName::GetLongPackagePath(GetPackage()->GetPathName()); + FString UniquePackageName, UniqueAssetName; + AssetToolsModule.Get().CreateUniqueAssetName(PackagePath + TEXT("/") + ParamsAssetName, TEXT(""), UniquePackageName, UniqueAssetName); + + UFlowAssetParams* NewParams = Cast( + AssetToolsModule.Get().CreateAsset(UniqueAssetName, PackagePath, UFlowAssetParams::StaticClass(), nullptr)); + if (!IsValid(NewParams)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to create Flow Asset Params: %s"), *UniqueAssetName); + return nullptr; + } + + // Reconfigure with the new properties + NewParams->ConfigureFlowAssetParams(this, nullptr, NamedPropertiesSupplier->GetMutableNamedProperties()); + + // Source control integration + if (USourceControlHelpers::IsAvailable()) + { + const FString FileName = USourceControlHelpers::PackageFilename(NewParams->GetPathName()); + if (!USourceControlHelpers::CheckOutOrAddFile(FileName)) + { + UE_LOG(LogFlow, Warning, TEXT("Failed to check out/add %s; saved in-memory only"), *NewParams->GetPathName()); + } + } + + // Assign to BaseAssetParams and sync Content Browser + BaseAssetParams.AssetPtr = NewParams; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistryModule.Get().AssetCreated(NewParams); + + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + TArray AssetsToSync = { NewParams }; + ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true); + + return NewParams; +} + +FString UFlowAsset::GenerateParamsAssetName() const +{ + const FString FlowAssetName = GetName(); + + const int32 UnderscoreIndex = FlowAssetName.Find(TEXT("_"), ESearchCase::CaseSensitive); + + if (UnderscoreIndex != INDEX_NONE) + { + const FString Prefix = FlowAssetName.Left(UnderscoreIndex); + const FString Suffix = FlowAssetName.Mid(UnderscoreIndex + 1); + return FString::Printf(TEXT("%sParams_%s"), *Prefix, *Suffix); + } + else + { + return FlowAssetName + TEXT("Params"); + } } EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog) diff --git a/Source/Flow/Private/FlowComponent.cpp b/Source/Flow/Private/FlowComponent.cpp index fc1dc359e..0f4a66d01 100644 --- a/Source/Flow/Private/FlowComponent.cpp +++ b/Source/Flow/Private/FlowComponent.cpp @@ -2,6 +2,7 @@ #include "FlowComponent.h" +#include "Asset/FlowAssetParams.h" #include "FlowAsset.h" #include "FlowLogChannels.h" #include "FlowSettings.h" @@ -450,7 +451,8 @@ void UFlowComponent::StartRootFlow() { VerifyIdentityTags(); - FlowSubsystem->StartRootFlow(this, RootFlow, bAllowMultipleInstances); + TScriptInterface RootFlowParamsAsInterface = RootFlowParams.ResolveFlowAssetParams(); + FlowSubsystem->StartRootFlow(this, RootFlow, RootFlowParamsAsInterface, bAllowMultipleInstances); } } } diff --git a/Source/Flow/Private/FlowSubsystem.cpp b/Source/Flow/Private/FlowSubsystem.cpp index 946610632..d8f934a03 100644 --- a/Source/Flow/Private/FlowSubsystem.cpp +++ b/Source/Flow/Private/FlowSubsystem.cpp @@ -76,15 +76,13 @@ void UFlowSubsystem::AbortActiveFlows() RootInstances.Empty(); } -void UFlowSubsystem::StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const bool bAllowMultipleInstances /* = true */) +void UFlowSubsystem::StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, TScriptInterface DataPinValueSupplier, const bool bAllowMultipleInstances) { if (FlowAsset) { if (UFlowAsset* NewFlow = CreateRootFlow(Owner, FlowAsset, bAllowMultipleInstances)) { - // todo: (gtaylor) In the future, we may want to provide a way to set a data pin value supplier - // for the root flow graph. - NewFlow->StartFlow(); + NewFlow->StartFlow(DataPinValueSupplier.GetInterface()); } } #if WITH_EDITOR @@ -664,4 +662,4 @@ void UFlowSubsystem::FindComponents(const FGameplayTagContainer& Tags, const EGa } } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 7c2bdcbdb..2f70c55b0 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -319,39 +319,6 @@ UObject* UFlowNodeBase::TryGetRootFlowObjectOwner() const return nullptr; } -IFlowOwnerInterface* UFlowNodeBase::GetFlowOwnerInterface() const -{ - const UFlowAsset* FlowAsset = GetFlowAsset(); - if (!IsValid(FlowAsset)) - { - return nullptr; - } - - const UClass* ExpectedOwnerClass = FlowAsset->GetExpectedOwnerClass(); - if (!IsValid(ExpectedOwnerClass)) - { - return nullptr; - } - - UObject* RootFlowOwner = FlowAsset->GetOwner(); - if (!IsValid(RootFlowOwner)) - { - return nullptr; - } - - if (IFlowOwnerInterface* FlowOwnerInterface = TryGetFlowOwnerInterfaceFromRootFlowOwner(*RootFlowOwner, *ExpectedOwnerClass)) - { - return FlowOwnerInterface; - } - - if (IFlowOwnerInterface* FlowOwnerInterface = TryGetFlowOwnerInterfaceActor(*RootFlowOwner, *ExpectedOwnerClass)) - { - return FlowOwnerInterface; - } - - return nullptr; -} - TArray UFlowNodeBase::BuildFlowNodeBaseAncestorChain(UFlowNodeBase& FromFlowNodeBase, bool bIncludeFromFlowNodeBase) { TArray AncestorChain; @@ -374,47 +341,6 @@ TArray UFlowNodeBase::BuildFlowNodeBaseAncestorChain(UFlowNodeBa return AncestorChain; } -IFlowOwnerInterface* UFlowNodeBase::TryGetFlowOwnerInterfaceFromRootFlowOwner(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass) -{ - const UClass* RootFlowOwnerClass = RootFlowOwner.GetClass(); - if (!IsValid(RootFlowOwnerClass)) - { - return nullptr; - } - - if (!RootFlowOwnerClass->IsChildOf(&ExpectedOwnerClass)) - { - return nullptr; - } - - // If the immediate owner is the expected class type, return its FlowOwnerInterface - return CastChecked(&RootFlowOwner); -} - -IFlowOwnerInterface* UFlowNodeBase::TryGetFlowOwnerInterfaceActor(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass) -{ - // Special case if the immediate owner is a component, also consider the component's owning actor - const UActorComponent* FlowComponent = Cast(&RootFlowOwner); - if (!IsValid(FlowComponent)) - { - return nullptr; - } - - AActor* ActorOwner = FlowComponent->GetOwner(); - if (!IsValid(ActorOwner)) - { - return nullptr; - } - - const UClass* ActorOwnerClass = ActorOwner->GetClass(); - if (!ActorOwnerClass->IsChildOf(&ExpectedOwnerClass)) - { - return nullptr; - } - - return CastChecked(ActorOwner); -} - EFlowAddOnAcceptResult UFlowNodeBase::AcceptFlowNodeAddOnChild_Implementation( const UFlowNodeAddOn* AddOnTemplate, const TArray& AdditionalAddOnsToAssumeAreChildren) const diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index 38b9e206d..e001873c9 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -4,6 +4,7 @@ #include "FlowSave.h" #include "FlowTypes.h" +#include "Asset/FlowAssetParamsTypes.h" #include "Nodes/FlowNode.h" #if WITH_EDITOR @@ -21,6 +22,7 @@ class UFlowSubsystem; class UEdGraph; class UEdGraphNode; class UFlowAsset; +class UFlowAssetParams; #if !UE_BUILD_SHIPPING DECLARE_DELEGATE(FFlowGraphEvent); @@ -457,6 +459,28 @@ class FLOW_API UFlowAsset : public UObject UFUNCTION(BlueprintNativeEvent, Category = "SaveGame") bool IsBoundToWorld(); +////////////////////////////////////////////////////////////////////////// +// FlowAssetParams support (Start node params for a flow graph) + + // Default parameters asset for this Flow Asset (optional) + UPROPERTY(EditAnywhere, Category = FlowAssetParams, meta = (ShowCreateNew, HideChildParams)) + FFlowAssetParamsPtr BaseAssetParams; + +#if WITH_EDITOR + // Called before saving the asset. + virtual void PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext) override; + + // Generates a new params asset from the Start node. + UFlowAssetParams* GenerateParamsFromStartNode(); + + // Generates the FlowAssetParams name for the 'base' (root) asset, used when creating the params asset + virtual FString GenerateParamsAssetName() const; + +protected: + + void ReconcileBaseAssetParams(const FDateTime& AssetLastSavedTimestamp); +#endif + ////////////////////////////////////////////////////////////////////////// // Utils diff --git a/Source/Flow/Public/FlowComponent.h b/Source/Flow/Public/FlowComponent.h index d579e2568..333779911 100644 --- a/Source/Flow/Public/FlowComponent.h +++ b/Source/Flow/Public/FlowComponent.h @@ -7,7 +7,8 @@ #include "FlowSave.h" #include "FlowTypes.h" -#include "Interfaces/FlowOwnerInterface.h" +#include "Interfaces/FlowAssetProviderInterface.h" +#include "Asset/FlowAssetParamsTypes.h" #include "FlowComponent.generated.h" class UFlowAsset; @@ -42,7 +43,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FFlowComponentDynamicNotify, class * Base component of Flow System - makes possible to communicate between Actor, Flow Subsystem and Flow Graphs */ UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) -class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterface +class FLOW_API UFlowComponent : public UActorComponent, public IFlowAssetProviderInterface { GENERATED_UCLASS_BODY() @@ -167,6 +168,10 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RootFlow") TObjectPtr RootFlow; + // Flow Asset Params to use as the data pin value supplier for the Root Flow + UPROPERTY(EditAnywhere, Category = "RootFlow") + FFlowAssetParamsPtr RootFlowParams; + // If true, component will start Root Flow on Begin Play UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "RootFlow") bool bAutoStartRootFlow; @@ -197,6 +202,10 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa UFUNCTION(BlueprintPure, Category = "RootFlow", meta = (DeprecatedFunction, DeprecationMessage="Use GetRootInstances() instead.")) UFlowAsset* GetRootFlowInstance() const; + // IFlowAssetProviderInterface + virtual UFlowAsset* ProvideFlowAsset() const override { return RootFlow; } + // -- + ////////////////////////////////////////////////////////////////////////// // Custom Input and Output events @@ -253,4 +262,4 @@ class FLOW_API UFlowComponent : public UActorComponent, public IFlowOwnerInterfa public: UFlowSubsystem* GetFlowSubsystem() const; bool IsFlowNetMode(const EFlowNetMode NetMode) const; -}; +}; \ No newline at end of file diff --git a/Source/Flow/Public/FlowSubsystem.h b/Source/Flow/Public/FlowSubsystem.h index 4852cb519..01790fcd6 100644 --- a/Source/Flow/Public/FlowSubsystem.h +++ b/Source/Flow/Public/FlowSubsystem.h @@ -11,6 +11,7 @@ class UFlowAsset; class UFlowNode_SubGraph; +class IFlowDataPinValueSupplierInterface; DECLARE_DYNAMIC_MULTICAST_DELEGATE(FSimpleFlowEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSimpleFlowComponentEvent, UFlowComponent*, Component); @@ -73,7 +74,7 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem /* Start the root Flow, graph that will eventually instantiate next Flow Graphs through the SubGraph node */ UFUNCTION(BlueprintCallable, Category = "FlowSubsystem", meta = (DefaultToSelf = "Owner")) - virtual void StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const bool bAllowMultipleInstances = true); + virtual void StartRootFlow(UObject* Owner, UFlowAsset* FlowAsset, TScriptInterface DataPinValueSupplier, const bool bAllowMultipleInstances = true); virtual UFlowAsset* CreateRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const bool bAllowMultipleInstances = true, const FString& NewInstanceName = FString()); @@ -431,4 +432,4 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem private: void FindComponents(const FGameplayTag& Tag, const bool bExactMatch, TArray>& OutComponents) const; void FindComponents(const FGameplayTagContainer& Tags, const EGameplayContainerMatchType MatchType, const bool bExactMatch, TSet>& OutComponents) const; -}; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Interfaces/FlowOwnerInterface.h b/Source/Flow/Public/Interfaces/FlowOwnerInterface.h index 28a00f5e0..5c17b1a62 100644 --- a/Source/Flow/Public/Interfaces/FlowOwnerInterface.h +++ b/Source/Flow/Public/Interfaces/FlowOwnerInterface.h @@ -6,8 +6,8 @@ #include "FlowOwnerInterface.generated.h" -// (optional) interface to enable a Flow owner object to execute CallOwnerFunction nodes -UINTERFACE(MinimalAPI, Blueprintable, BlueprintType) +// (deprecated) interface to enable a Flow owner object to execute CallOwnerFunction nodes +UINTERFACE(MinimalAPI) class UFlowOwnerInterface : public UInterface { GENERATED_BODY() diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h index c4d8a4c58..a4d0f33b5 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h @@ -11,7 +11,6 @@ #include "FlowNode_ExecuteComponent.generated.h" // Forward Declarations -class IFlowOwnerInterface; class UFlowInjectComponentsManager; UENUM() diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index b9f9d5169..6c54a0a8d 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -18,7 +18,6 @@ class UFlowNode; class UFlowNodeAddOn; class UFlowSubsystem; class UEdGraphNode; -class IFlowOwnerInterface; class IFlowDataPinValueSupplierInterface; struct FFlowPin; struct FFlowNamedDataPinProperty; @@ -157,17 +156,8 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintCallable, Category = "FlowNode") UObject* TryGetRootFlowObjectOwner() const; - // Returns the IFlowOwnerInterface for the owner object (if implemented) - // NOTE - will consider a UActorComponent owner's owning actor if appropriate - IFlowOwnerInterface* GetFlowOwnerInterface() const; - static TArray BuildFlowNodeBaseAncestorChain(UFlowNodeBase& FromFlowNodeBase, bool bIncludeFromFlowNodeBase); -protected: - // Helper functions for GetFlowOwnerInterface() - static IFlowOwnerInterface* TryGetFlowOwnerInterfaceFromRootFlowOwner(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass); - static IFlowOwnerInterface* TryGetFlowOwnerInterfaceActor(UObject& RootFlowOwner, const UClass& ExpectedOwnerClass); - ////////////////////////////////////////////////////////////////////////// // AddOn support diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h index 0573362bc..c40ed2c69 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h @@ -3,6 +3,7 @@ #pragma once #include "Interfaces/FlowDataPinGeneratorNodeInterface.h" +#include "Interfaces/FlowNamedPropertiesSupplierInterface.h" #include "Nodes/FlowNode.h" #include "Types/FlowDataPinProperties.h" @@ -12,7 +13,10 @@ * FlowNode to define data pin property literals for use connecting to data pin inputs in a flow graph */ UCLASS(Blueprintable, meta = (DisplayName = "Define Properties")) -class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPinGeneratorNodeInterface +class FLOW_API UFlowNode_DefineProperties + : public UFlowNode + , public IFlowDataPinGeneratorNodeInterface + , public IFlowNamedPropertiesSupplierInterface { GENERATED_UCLASS_BODY() @@ -35,6 +39,10 @@ class FLOW_API UFlowNode_DefineProperties : public UFlowNode, public IFlowDataPi // IFlowDataPinGeneratorNodeInterface virtual void AutoGenerateDataPins(TMap& PinNameToBoundPropertyMap, TArray& InputDataPins, TArray& OutputDataPins) const override; // -- + + // IFlowNamedPropertiesSupplierInterface + virtual TArray& GetMutableNamedProperties() override { return NamedProperties; } + // -- #endif bool TryFormatTextWithNamedPropertiesAsParameters(const FText& FormatText, FText& OutFormattedText) const; diff --git a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h index 5f7f89f0d..24cbed357 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_SubGraph.h @@ -15,7 +15,7 @@ class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGenerat { GENERATED_UCLASS_BODY() -public: +public: friend class UFlowAsset; friend class FFlowNode_SubGraphDetails; friend class UFlowSubsystem; @@ -27,6 +27,8 @@ class FLOW_API UFlowNode_SubGraph : public UFlowNode, public IFlowDataPinGenerat UPROPERTY(EditAnywhere, Category = "Graph") TSoftObjectPtr Asset; + // TODO (gtaylor) Create FlowAssetParams option for the Subgraph & reconcile with connected input pins' values + /* * Allow to create instance of the same Flow Asset as the asset containing this node * Enabling it may cause an infinite loop, if graph would keep creating copies of itself diff --git a/Source/Flow/Public/Types/FlowDataPinProperties.h b/Source/Flow/Public/Types/FlowDataPinProperties.h index a40a0e24f..75407e7a9 100644 --- a/Source/Flow/Public/Types/FlowDataPinProperties.h +++ b/Source/Flow/Public/Types/FlowDataPinProperties.h @@ -3,6 +3,7 @@ #pragma once #include "GameplayTagContainer.h" +#include "Kismet/BlueprintFunctionLibrary.h" #include "StructUtils/InstancedStruct.h" #include "UObject/Class.h" @@ -17,7 +18,7 @@ struct FFlowDataPinProperty { GENERATED_BODY() - FFlowDataPinProperty() { } + FFlowDataPinProperty() = default; virtual ~FFlowDataPinProperty() { } @@ -44,11 +45,86 @@ struct FFlowDataPinProperty return UnrealType; } - else + + return StructProperty->Struct; + } +#endif +}; + +// Wrapper for FFlowDataPinProperty that is used for flow nodes that add +// dynamic properties, with associated data pins, on the flow node instance +// (as opposed to C++ or blueprint compile-time). +USTRUCT(BlueprintType, DisplayName = "Flow Named DataPin Property") +struct FFlowNamedDataPinProperty +{ + GENERATED_BODY() + +public: + // Name of this instanced property + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins, meta = (EditCondition = "bMayChangeNameAndType", HideEditConditionToggle)) + FName Name = NAME_None; + + // DataPinProperty payload + UPROPERTY(EditAnywhere, Category = DataPins, meta = (ExcludeBaseStruct, NoClear)) + TInstancedStruct DataPinProperty; + +#if WITH_EDITORONLY_DATA + // Unique identifier for property tracking + UPROPERTY() + FGuid Guid = FGuid::NewGuid(); + + // Tracks if this property overrides its super (auto-clears if matches super) + UPROPERTY() + bool bIsOverride = false; + + // TODO (gtaylor) Does not currently police the type, + // because that prevents the instanced struct contents being edited as well, + // which is not what we want from this feature. + // Will try to fix next pass on the details customization. + UPROPERTY() + bool bMayChangeNameAndType = true; +#endif + +public: + FFlowNamedDataPinProperty() = default; + + bool IsValid() const { return Name != NAME_None && DataPinProperty.GetPtr() != nullptr; } + + bool IsInputProperty() const; + bool IsOutputProperty() const; + +#if WITH_EDITOR + FFlowPin CreateFlowPin() const { return FFlowDataPinProperty::CreateFlowPin(Name, DataPinProperty); } + + FLOW_API FText BuildHeaderText() const; + + void ConfigureForFlowAssetParams() + { + bIsOverride = false; + bMayChangeNameAndType = false; + } + + void ConfigureForFlowAssetStartNode() + { + bIsOverride = false; + bMayChangeNameAndType = true; + } + + static void ConfigurePropertiesForFlowAssetParams(TArray& MutableProperties) + { + for (FFlowNamedDataPinProperty& Property : MutableProperties) + { + Property.ConfigureForFlowAssetParams(); + } + } + static void ConfigurePropertiesForFlowAssetStartNode(TArray& MutableProperties) + { + for (FFlowNamedDataPinProperty& Property : MutableProperties) { - return StructProperty->Struct; + Property.ConfigureForFlowAssetStartNode(); } } + #endif }; @@ -446,40 +522,6 @@ struct FFlowDataPinOutputProperty_Class : public FFlowDataPinProperty UClass* GetResolvedClass() const { return Value.ResolveClass(); } }; -// Wrapper for FFlowDataPinProperty that is used for flow nodes that add -// dynamic properties, with associated data pins, on the flow node instance -// (as opposed to C++ or blueprint compile-time). -USTRUCT(BlueprintType, DisplayName = "Flow Named DataPin Property") -struct FFlowNamedDataPinProperty -{ - GENERATED_BODY() - -public: - - // Name of this instanced property - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - FName Name; - - // DataPinProperty payload - UPROPERTY(EditAnywhere, Category = DataPins, meta = (ExcludeBaseStruct, NoClear)) - TInstancedStruct DataPinProperty; - -public: - - FFlowNamedDataPinProperty() { } - - bool IsValid() const { return Name != NAME_None && DataPinProperty.GetPtr() != nullptr; } - - bool IsInputProperty() const; - bool IsOutputProperty() const; - -#if WITH_EDITOR - FFlowPin CreateFlowPin() const { return FFlowDataPinProperty::CreateFlowPin(Name, DataPinProperty); } - - FLOW_API FText BuildHeaderText() const; -#endif // WITH_EDITOR -}; - // Wrapper-structs for a blueprint defaulted input pin types // "Hidden" to keep them out of the TInstancedStruct selection list (but they can still be authored as properties in blueprint) // "DefaultForInputFlowPin" to change them to an Defaulted-Input property (rather than an output property) diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp index 03a513c6d..df88d22cb 100644 --- a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp +++ b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp @@ -30,7 +30,7 @@ TConstArrayView UAssetDefinition_FlowAsset::GetAssetCategori { if (UFlowGraphSettings::Get()->bExposeFlowAssetCreation) { - static const auto Categories = {FFLowAssetCategoryPaths::Flow}; + static const auto Categories = {FFlowAssetCategoryPaths::Flow}; return Categories; } diff --git a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp index 370ff0bd9..7871b3514 100644 --- a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp +++ b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp @@ -3,8 +3,8 @@ #include "Asset/FlowObjectDiff.h" #include "Asset/FlowDiffControl.h" -#include "Graph/Nodes/FlowGraphNode.h" #include "Nodes/FlowNodeBase.h" +#include "Graph/Nodes/FlowGraphNode.h" #include "DiffResults.h" #include "EdGraph/EdGraph.h" diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index 94582f2f7..c036a4651 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -32,9 +32,11 @@ #include "DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h" #include "DetailCustomizations/FlowPinCustomization.h" #include "DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h" +#include "DetailCustomizations/FlowAssetParamsPtrCustomization.h" #include "FlowAsset.h" #include "AddOns/FlowNodeAddOn.h" +#include "Asset/FlowAssetParamsTypes.h" #include "Nodes/Actor/FlowNode_ComponentObserver.h" #include "Nodes/Actor/FlowNode_PlayLevelSequence.h" #include "Nodes/Graph/FlowNode_CustomInput.h" @@ -55,7 +57,7 @@ static FName AssetSearchModuleName = TEXT("AssetSearch"); #define LOCTEXT_NAMESPACE "FlowEditorModule" EAssetTypeCategories::Type FFlowEditorModule::FlowAssetCategory = static_cast(0); -FAssetCategoryPath FFLowAssetCategoryPaths::Flow(LOCTEXT("Flow", "Flow")); +FAssetCategoryPath FFlowAssetCategoryPaths::Flow(LOCTEXT("Flow", "Flow")); void FFlowEditorModule::StartupModule() { @@ -259,6 +261,8 @@ void FFlowEditorModule::RegisterDetailCustomizations() RegisterCustomStructLayout(*FFlowDataPinInputProperty_Class::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_ClassCustomization::MakeInstance)); RegisterCustomStructLayout(*FFlowDataPinInputProperty_Object::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinInputProperty_ObjectCustomization::MakeInstance)); + RegisterCustomStructLayout(*FFlowAssetParamsPtr::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowAssetParamsPtrCustomization::MakeInstance)); + // Consider implementing details customizations... for every EFlowPinType FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index 50056584d..99a8203dc 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -28,6 +28,7 @@ #include "Kismet2/KismetEditorUtilities.h" #include "ScopedTransaction.h" #include "SourceCodeNavigation.h" +#include "Subsystems/AssetEditorSubsystem.h" #include "Textures/SlateIcon.h" #include "ToolMenuSection.h" #include "Editor/Transactor.h" diff --git a/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp b/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp index 5ef3afb86..77e64d428 100644 --- a/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp +++ b/Source/FlowEditor/Private/UnrealExtensions/IFlowCuratedNamePropertyCustomization.cpp @@ -59,22 +59,22 @@ void IFlowCuratedNamePropertyCustomization::CreateHeaderRowWidget(FDetailWidgetR .NameContent() [ SAssignNew(HeaderTextBlock, STextBlock) - .Text(BuildHeaderText()) + .Text(BuildHeaderText()) ] .ValueContent() .MaxDesiredWidth(250.0f) [ SAssignNew(TextListWidget, SComboBox>) - .OptionsSource(&CachedTextList) - .OnGenerateWidget_Static(&IFlowCuratedNamePropertyCustomization::GenerateTextListWidget) - .OnComboBoxOpening(this, &IFlowCuratedNamePropertyCustomization::OnTextListComboBoxOpening) - .OnSelectionChanged(this, &IFlowCuratedNamePropertyCustomization::OnTextSelected) - [ - SNew(STextBlock) - .Text(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) - ] + .OptionsSource(&CachedTextList) + .OnGenerateWidget_Static(&IFlowCuratedNamePropertyCustomization::GenerateTextListWidget) + .OnComboBoxOpening(this, &IFlowCuratedNamePropertyCustomization::OnTextListComboBoxOpening) + .OnSelectionChanged(this, &IFlowCuratedNamePropertyCustomization::OnTextSelected) + [ + SNew(STextBlock) + .Text(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(this, &IFlowCuratedNamePropertyCustomization::GetCachedText) + ] ]; // Hook-up the ResetToDefault overrides @@ -82,7 +82,7 @@ void IFlowCuratedNamePropertyCustomization::CreateHeaderRowWidget(FDetailWidgetR FIsResetToDefaultVisible::CreateSP( this, &IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisible); - FResetToDefaultHandler ResetHandler = + FResetToDefaultHandler ResetHandler = FResetToDefaultHandler::CreateSP( this, &IFlowCuratedNamePropertyCustomization::CustomResetToDefault); @@ -95,7 +95,7 @@ void IFlowCuratedNamePropertyCustomization::CreateHeaderRowWidget(FDetailWidgetR HeaderRow.IsEnabled(IsEnabledAttribute); } -bool IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisible(TSharedPtr Property) const +bool IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisibleImpl(TSharedPtr Property) const { FName CuratedName; if (!TryGetCuratedName(CuratedName)) @@ -106,7 +106,7 @@ bool IFlowCuratedNamePropertyCustomization::CustomIsResetToDefaultVisible(TShare return !CuratedName.IsNone(); } -void IFlowCuratedNamePropertyCustomization::CustomResetToDefault(TSharedPtr Property) +void IFlowCuratedNamePropertyCustomization::CustomResetToDefaultImpl(TSharedPtr Property) { if (TrySetCuratedNameWithSideEffects(NAME_None)) { @@ -114,7 +114,7 @@ void IFlowCuratedNamePropertyCustomization::CustomResetToDefault(TSharedPtrIsEditConst()) { @@ -132,7 +132,7 @@ bool IFlowCuratedNamePropertyCustomization::CustomIsEnabled() const bool IFlowCuratedNamePropertyCustomization::TrySetCuratedNameWithSideEffects(const FName& NewName) { FName ExistingName; - (void) TryGetCuratedName(ExistingName); + (void)TryGetCuratedName(ExistingName); if (ExistingName != NewName) { @@ -196,7 +196,7 @@ void IFlowCuratedNamePropertyCustomization::OnTextListComboBoxOpening() for (TSharedPtr& Text : CachedTextList) { - (void) MapNameToText.FindOrAdd(FName(Text.Get()->ToString()), Text); + (void)MapNameToText.FindOrAdd(FName(Text.Get()->ToString()), Text); } TArray CuratedNameOptions = GetCuratedNameOptions(); diff --git a/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp b/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp index d54ab8610..96d42a729 100644 --- a/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp +++ b/Source/FlowEditor/Private/UnrealExtensions/IFlowExtendedPropertyTypeCustomization.cpp @@ -10,9 +10,6 @@ #include "IDetailPropertyRow.h" #include "Widgets/Text/STextBlock.h" - -// IFlowExtendedPropertyTypeCustomization Implementation - void IFlowExtendedPropertyTypeCustomization::CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { StructPropertyHandle = InStructPropertyHandle; diff --git a/Source/FlowEditor/Public/Asset/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/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 018bfd314..8dec688cf 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -16,7 +16,7 @@ struct FGraphPanelPinConnectionFactory; class FFlowAssetEditor; class UFlowAsset; -struct FLOWEDITOR_API FFLowAssetCategoryPaths : EAssetCategoryPaths +struct FLOWEDITOR_API FFlowAssetCategoryPaths : EAssetCategoryPaths { static FAssetCategoryPath Flow; }; diff --git a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h index a9d715862..6c5e3d9ca 100644 --- a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h +++ b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode.h @@ -157,7 +157,7 @@ class FLOWEDITOR_API UFlowGraphNode : public UEdGraphNode // -- virtual void OnNodeDoubleClicked() const; - virtual void OnNodeDoubleClickedInPIE() const {}; + virtual void OnNodeDoubleClickedInPIE() const {} /** check if node has any errors, used for assigning colors on graph */ virtual bool HasErrors() const; diff --git a/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h b/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h index a7977813b..2428d41b9 100644 --- a/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h +++ b/Source/FlowEditor/Public/Graph/Widgets/SFlowGraphNode.h @@ -4,6 +4,7 @@ #include "SGraphNode.h" #include "KismetPins/SGraphPinExec.h" +#include "Runtime/Launch/Resources/Version.h" #include "Graph/Nodes/FlowGraphNode.h" @@ -35,10 +36,10 @@ class FLOWEDITOR_API SFlowGraphNode : public SGraphNode virtual void GetNodeInfoPopups(FNodeInfoContext* Context, TArray& Popups) const override; virtual const FSlateBrush* GetShadowBrush(bool bSelected) const override; - #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6 -virtual void GetOverlayBrushes(bool bSelected, const FVector2D WidgetSize, TArray& Brushes) const override; +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6 + virtual void GetOverlayBrushes(bool bSelected, const FVector2D WidgetSize, TArray& Brushes) const override; #else -virtual void GetOverlayBrushes(bool bSelected, const FVector2f& WidgetSize, TArray& Brushes) const override; + virtual void GetOverlayBrushes(bool bSelected, const FVector2f& WidgetSize, TArray& Brushes) const override; #endif // -- diff --git a/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h b/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h index cabc2636a..6bd95250d 100644 --- a/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h +++ b/Source/FlowEditor/Public/UnrealExtensions/IFlowCuratedNamePropertyCustomization.h @@ -12,7 +12,7 @@ class FLOWEDITOR_API IFlowCuratedNamePropertyCustomization : public IFlowExtendedPropertyTypeCustomization { protected: - // IExtendedPropertyTypeCustomization + // IFlowExtendedPropertyTypeCustomization virtual void CreateHeaderRowWidget(FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; // --- @@ -34,9 +34,9 @@ class FLOWEDITOR_API IFlowCuratedNamePropertyCustomization : public IFlowExtende void AddToCachedTextList(const TSharedPtr Text); void InsertAtHeadOfCachedTextList(const TSharedPtr Text); - bool CustomIsResetToDefaultVisible(TSharedPtr Property) const; - void CustomResetToDefault(TSharedPtr Property); - bool CustomIsEnabled() const; + bool CustomIsResetToDefaultVisible(TSharedPtr Property) const { return CustomIsResetToDefaultVisibleImpl(Property); } + void CustomResetToDefault(TSharedPtr Property) { CustomResetToDefaultImpl(Property); } + bool CustomIsEnabled() const { return CustomIsEnabledImpl(); } // IFlowCuratedNamePropertyCustomization virtual TSharedPtr GetCuratedNamePropertyHandle() const = 0; @@ -44,6 +44,9 @@ class FLOWEDITOR_API IFlowCuratedNamePropertyCustomization : public IFlowExtende virtual bool TryGetCuratedName(FName& OutName) const = 0; virtual TArray GetCuratedNameOptions() const = 0; virtual bool AllowNameNoneIfOtherOptionsExist() const { return true; } + virtual bool CustomIsResetToDefaultVisibleImpl(TSharedPtr Property) const; + virtual void CustomResetToDefaultImpl(TSharedPtr Property); + virtual bool CustomIsEnabledImpl() const; // --- public: From 89c1d18605a659b0d6fd910fc86a5f87fa311517 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:28:02 -0700 Subject: [PATCH 2/6] More files for FlowAssetParams --- Source/Flow/Private/Asset/FlowAssetParams.cpp | 570 ++++++++++++++++++ .../Private/Asset/FlowAssetParamsTypes.cpp | 11 + .../Private/Asset/FlowAssetParamsUtils.cpp | 122 ++++ .../Interfaces/FlowAssetProviderInterface.cpp | 9 + Source/Flow/Public/Asset/FlowAssetParams.h | 96 +++ .../Flow/Public/Asset/FlowAssetParamsTypes.h | 67 ++ .../Flow/Public/Asset/FlowAssetParamsUtils.h | 44 ++ .../Interfaces/FlowAssetProviderInterface.h | 29 + .../FlowNamedPropertiesSupplierInterface.h | 30 + .../Asset/AssetDefinition_FlowAssetParams.cpp | 152 +++++ .../FlowAssetParamsPtrCustomization.cpp | 192 ++++++ .../Asset/AssetDefinition_FlowAssetParams.h | 24 + .../FlowAssetParamsPtrCustomization.h | 24 + 13 files changed, 1370 insertions(+) create mode 100644 Source/Flow/Private/Asset/FlowAssetParams.cpp create mode 100644 Source/Flow/Private/Asset/FlowAssetParamsTypes.cpp create mode 100644 Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp create mode 100644 Source/Flow/Private/Interfaces/FlowAssetProviderInterface.cpp create mode 100644 Source/Flow/Public/Asset/FlowAssetParams.h create mode 100644 Source/Flow/Public/Asset/FlowAssetParamsTypes.h create mode 100644 Source/Flow/Public/Asset/FlowAssetParamsUtils.h create mode 100644 Source/Flow/Public/Interfaces/FlowAssetProviderInterface.h create mode 100644 Source/Flow/Public/Interfaces/FlowNamedPropertiesSupplierInterface.h create mode 100644 Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp create mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowAssetParamsPtrCustomization.cpp create mode 100644 Source/FlowEditor/Public/Asset/AssetDefinition_FlowAssetParams.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowAssetParamsPtrCustomization.h diff --git a/Source/Flow/Private/Asset/FlowAssetParams.cpp b/Source/Flow/Private/Asset/FlowAssetParams.cpp new file mode 100644 index 000000000..a30daeede --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParams.cpp @@ -0,0 +1,570 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParams.h" +#include "FlowAsset.h" +#include "FlowLogChannels.h" +#include "Asset/FlowAssetParamsUtils.h" +#if WITH_EDITOR +#include "SourceControlHelpers.h" +#include "Misc/DataValidation.h" +#endif + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParams) + +void UFlowAssetParams::PostLoad() +{ + Super::PostLoad(); + +#if WITH_EDITOR + const EFlowReconcilePropertiesResult ReconcileResult = ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile ParentParams for %s: %s"), + *GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } +#endif +} + +void UFlowAssetParams::Serialize(FArchive& Ar) +{ +#if WITH_EDITOR + if (Ar.IsCooking()) + { + const EFlowReconcilePropertiesResult ReconcileResult = ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ReconcileResult)) + { + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile ParentParams during cooking for %s: %s"), + *GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); + } + } +#endif + + Super::Serialize(Ar); +} + +UFlowAsset* UFlowAssetParams::ProvideFlowAsset() const +{ +#if WITH_EDITOR + return OwnerFlowAsset.LoadSynchronous(); +#else + // We don't have knowledge of the OwnerFlowAsset in non-editor builds + checkNoEntry(); + return nullptr; +#endif +} + +#if WITH_EDITOR +EDataValidationResult UFlowAssetParams::IsDataValid(FDataValidationContext& Context) const +{ + EDataValidationResult Result = Super::IsDataValid(Context); + + if (OwnerFlowAsset.IsNull()) + { + Context.AddError(FText::FromString(TEXT("OwnerFlowAsset is null"))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + else if (!OwnerFlowAsset.IsValid() && !OwnerFlowAsset.LoadSynchronous()) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Failed to load OwnerFlowAsset: %s"), *OwnerFlowAsset.ToString()))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + const EFlowReconcilePropertiesResult CycleResult = CheckForParentCycle(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(CycleResult)) + { + Context.AddError(FText::FromString(TEXT("Cyclic inheritance detected"))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + TSet SeenGuids; + for (int32 Index = 0; Index < Properties.Num(); ++Index) + { + const FFlowNamedDataPinProperty& Property = Properties[Index]; + if (Property.Name == NAME_None) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid name"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + if (!Property.DataPinProperty.IsValid()) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid DataPinProperty"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + + if (!Property.Guid.IsValid()) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid Guid"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + else if (SeenGuids.Contains(Property.Guid)) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Duplicate Guid found for property at index %d"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + else + { + SeenGuids.Add(Property.Guid); + } + + if (Property.bMayChangeNameAndType) + { + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has bMayChangeNameAndType = true in UFlowAssetParams"), Index))); + Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); + } + } + + return Result; +} + +EFlowReconcilePropertiesResult UFlowAssetParams::ReconcilePropertiesWithStartNode( + const FDateTime& FlowAssetLastSaveTimeStamp, + const TSoftObjectPtr& InOwnerFlowAsset, + TArray& MutablePropertiesFromStartNode) +{ + OwnerFlowAsset = InOwnerFlowAsset; + + if (OwnerFlowAsset.IsNull()) + { + return EFlowReconcilePropertiesResult::Error_InvalidAsset; + } + + const EFlowReconcilePropertiesResult PropertiesMatchResult = FFlowAssetParamsUtils::CheckPropertiesMatch(Properties, MutablePropertiesFromStartNode); + const FDateTime ParamsTimestamp = FFlowAssetParamsUtils::GetLastSavedTimestampForObject(this); + + if (FlowAssetLastSaveTimeStamp >= ParamsTimestamp || + EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(PropertiesMatchResult)) + { + ConfigureFlowAssetParams(InOwnerFlowAsset, nullptr, MutablePropertiesFromStartNode); + + return EFlowReconcilePropertiesResult::ParamsPropertiesUpdated; + } + + MutablePropertiesFromStartNode = Properties; + + FFlowNamedDataPinProperty::ConfigurePropertiesForFlowAssetParams(MutablePropertiesFromStartNode); + + return EFlowReconcilePropertiesResult::AssetPropertyValuesUpdated; +} + +EFlowReconcilePropertiesResult UFlowAssetParams::ReconcilePropertiesWithParentParams() +{ + const EFlowReconcilePropertiesResult CycleResult = CheckForParentCycle(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(CycleResult)) + { + return CycleResult; + } + + if (ParentParams.AssetPtr.IsNull()) + { + return EFlowReconcilePropertiesResult::NoChanges; + } + + UFlowAssetParams* Parent = ParentParams.AssetPtr.LoadSynchronous(); + if (!Parent) + { + UE_LOG(LogFlow, Warning, TEXT("Failed to load ParentParams: %s"), *ParentParams.AssetPtr.ToString()); + + return EFlowReconcilePropertiesResult::Error_UnloadableParent; + } + + const EFlowReconcilePropertiesResult ParentResult = Parent->ReconcilePropertiesWithParentParams(); + if (EFlowReconcilePropertiesResult_Classifiers::IsErrorResult(ParentResult)) + { + return ParentResult; + } + + const TArray& ParentProps = Parent->Properties; + TArray NewProperties; + + for (const FFlowNamedDataPinProperty& ParentProp : ParentProps) + { + FFlowNamedDataPinProperty* LocalProp = FFlowAssetParamsUtils::FindPropertyByGuid(Properties, ParentProp.Guid); + if (LocalProp && LocalProp->bIsOverride) + { + FFlowNamedDataPinProperty UpdatedProp = *LocalProp; + + // Enforce Parent's name + UpdatedProp.Name = ParentProp.Name; + + NewProperties.Add(UpdatedProp); + + continue; + } + + if (LocalProp && FFlowAssetParamsUtils::ArePropertiesEqual(*LocalProp, ParentProp)) + { + LocalProp->bIsOverride = false; + + // Enforce Parent's name + LocalProp->Name = ParentProp.Name; + + NewProperties.Add(*LocalProp); + + continue; + } + + NewProperties.Add(ParentProp); + } + + for (FFlowNamedDataPinProperty& LocalProp : Properties) + { + if (!FFlowAssetParamsUtils::FindPropertyByGuid(ParentProps, LocalProp.Guid)) + { + LocalProp.bIsOverride = true; + + NewProperties.Add(LocalProp); + } + } + + if (FFlowAssetParamsUtils::ArePropertyArraysEqual(NewProperties, Properties)) + { + return EFlowReconcilePropertiesResult::NoChanges; + } + + Properties = NewProperties; + + (void) TryCheckOutFromSourceControl(); + + ModifyAndRebuildPropertiesMap(); + + return EFlowReconcilePropertiesResult::ParamsPropertiesUpdated; +} + +void UFlowAssetParams::ConfigureFlowAssetParams(TSoftObjectPtr OwnerAsset, TSoftObjectPtr InParentParams, const TArray& InProperties) +{ + ParentParams.AssetPtr = InParentParams; + OwnerFlowAsset = OwnerAsset; + Properties = InProperties; + FFlowNamedDataPinProperty::ConfigurePropertiesForFlowAssetParams(Properties); + + ModifyAndRebuildPropertiesMap(); +} + +bool UFlowAssetParams::TryCheckOutFromSourceControl() const +{ + if (!USourceControlHelpers::IsAvailable()) + { + return true; + } + + const FString FileName = USourceControlHelpers::PackageFilename(GetPathName()); + if (!USourceControlHelpers::CheckOutOrAddFile(FileName)) + { + UE_LOG(LogFlow, Warning, TEXT("%s is not checked out; properties updated in-memory only"), *GetPathName()); + return false; + } + + return true; +} + +EFlowReconcilePropertiesResult UFlowAssetParams::CheckForParentCycle() const +{ + TSet> Visited; + TSoftObjectPtr Current = ParentParams.AssetPtr; + + while (!Current.IsNull()) + { + if (Visited.Contains(Current)) + { + UE_LOG(LogFlow, Error, TEXT("Cyclic inheritance detected at: %s"), *Current.ToString()); + return EFlowReconcilePropertiesResult::Error_CyclicInheritance; + } + + Visited.Add(Current); + const UFlowAssetParams* CurrentParams = Current.LoadSynchronous(); + if (!CurrentParams) + { + UE_LOG(LogFlow, Warning, TEXT("Failed to load ParentParams: %s"), *Current.ToString()); + return EFlowReconcilePropertiesResult::Error_UnloadableParent; + } + + Current = CurrentParams->ParentParams.AssetPtr; + } + + return EFlowReconcilePropertiesResult::NoChanges; +} + +void UFlowAssetParams::ModifyAndRebuildPropertiesMap() +{ + Modify(); + + RebuildPropertiesMap(); + + MarkPackageDirty(); +} + +void UFlowAssetParams::RebuildPropertiesMap() +{ + PropertyMap.Reset(); + + for (const FFlowNamedDataPinProperty& Prop : Properties) + { + if (Prop.IsValid()) + { + PropertyMap.Add(Prop.Name, Prop.DataPinProperty); + } + else + { + UE_LOG(LogFlow, Warning, TEXT("Skipping invalid property %s during rebuild for %s"), *Prop.Name.ToString(), *GetPathName()); + } + } +} +#endif + +bool UFlowAssetParams::CanSupplyDataPinValues_Implementation() const +{ + return !PropertyMap.IsEmpty(); +} + +FFlowDataPinResult_Bool UFlowAssetParams::TrySupplyDataPinAsBool_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Bool::StaticStruct())) + { + const FFlowDataPinOutputProperty_Bool& BoolProp = Found->Get(); + return FFlowDataPinResult_Bool(BoolProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Bool pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Bool(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Int UFlowAssetParams::TrySupplyDataPinAsInt_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + const UScriptStruct* Struct = Found->GetScriptStruct(); + if (Struct->IsChildOf(FFlowDataPinOutputProperty_Int64::StaticStruct())) + { + const FFlowDataPinOutputProperty_Int64& IntProp = Found->Get(); + return FFlowDataPinResult_Int(IntProp.Value); + } + else if (Struct->IsChildOf(FFlowDataPinOutputProperty_Int32::StaticStruct())) + { + const FFlowDataPinOutputProperty_Int32& IntProp = Found->Get(); + return FFlowDataPinResult_Int(static_cast(IntProp.Value)); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Int pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Int(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Float UFlowAssetParams::TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + const UScriptStruct* Struct = Found->GetScriptStruct(); + if (Struct->IsChildOf(FFlowDataPinOutputProperty_Double::StaticStruct())) + { + const FFlowDataPinOutputProperty_Double& FloatProp = Found->Get(); + return FFlowDataPinResult_Float(FloatProp.Value); + } + else if (Struct->IsChildOf(FFlowDataPinOutputProperty_Float::StaticStruct())) + { + const FFlowDataPinOutputProperty_Float& FloatProp = Found->Get(); + return FFlowDataPinResult_Float(static_cast(FloatProp.Value)); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Float pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Float(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Name UFlowAssetParams::TrySupplyDataPinAsName_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Name::StaticStruct())) + { + const FFlowDataPinOutputProperty_Name& NameProp = Found->Get(); + return FFlowDataPinResult_Name(NameProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Name pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Name(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_String UFlowAssetParams::TrySupplyDataPinAsString_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_String::StaticStruct())) + { + const FFlowDataPinOutputProperty_String& StringProp = Found->Get(); + return FFlowDataPinResult_String(StringProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for String pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_String(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Text UFlowAssetParams::TrySupplyDataPinAsText_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Text::StaticStruct())) + { + const FFlowDataPinOutputProperty_Text& TextProp = Found->Get(); + return FFlowDataPinResult_Text(TextProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Text pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Text(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Enum UFlowAssetParams::TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Enum::StaticStruct())) + { + const FFlowDataPinOutputProperty_Enum& EnumProp = Found->Get(); + return FFlowDataPinResult_Enum(EnumProp.Value, EnumProp.EnumClass); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Enum pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Enum(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Vector UFlowAssetParams::TrySupplyDataPinAsVector_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Vector::StaticStruct())) + { + const FFlowDataPinOutputProperty_Vector& VectorProp = Found->Get(); + return FFlowDataPinResult_Vector(VectorProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Vector pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Vector(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Rotator UFlowAssetParams::TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Rotator::StaticStruct())) + { + const FFlowDataPinOutputProperty_Rotator& RotatorProp = Found->Get(); + return FFlowDataPinResult_Rotator(RotatorProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Rotator pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Rotator(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Transform UFlowAssetParams::TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Transform::StaticStruct())) + { + const FFlowDataPinOutputProperty_Transform& TransformProp = Found->Get(); + return FFlowDataPinResult_Transform(TransformProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Transform pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Transform(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_GameplayTag UFlowAssetParams::TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_GameplayTag::StaticStruct())) + { + const FFlowDataPinOutputProperty_GameplayTag& TagProp = Found->Get(); + return FFlowDataPinResult_GameplayTag(TagProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for GameplayTag pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_GameplayTag(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_GameplayTagContainer UFlowAssetParams::TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_GameplayTagContainer::StaticStruct())) + { + const FFlowDataPinOutputProperty_GameplayTagContainer& ContainerProp = Found->Get(); + return FFlowDataPinResult_GameplayTagContainer(ContainerProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for GameplayTagContainer pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_GameplayTagContainer(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_InstancedStruct UFlowAssetParams::TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_InstancedStruct::StaticStruct())) + { + const FFlowDataPinOutputProperty_InstancedStruct& StructProp = Found->Get(); + return FFlowDataPinResult_InstancedStruct(StructProp.Value); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for InstancedStruct pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_InstancedStruct(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Object UFlowAssetParams::TrySupplyDataPinAsObject_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Object::StaticStruct())) + { + const FFlowDataPinOutputProperty_Object& ObjectProp = Found->Get(); + return FFlowDataPinResult_Object(ObjectProp.GetObjectValue()); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Object pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Object(EFlowDataPinResolveResult::FailedUnknownPin); +} + +FFlowDataPinResult_Class UFlowAssetParams::TrySupplyDataPinAsClass_Implementation(const FName& PinName) const +{ + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + { + if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Class::StaticStruct())) + { + const FFlowDataPinOutputProperty_Class& ClassProp = Found->Get(); + return FFlowDataPinResult_Class(ClassProp.GetResolvedClass()); + } + + UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Class pin %s in %s"), *PinName.ToString(), *GetPathName()); + } + + return FFlowDataPinResult_Class(EFlowDataPinResolveResult::FailedUnknownPin); +} \ No newline at end of file diff --git a/Source/Flow/Private/Asset/FlowAssetParamsTypes.cpp b/Source/Flow/Private/Asset/FlowAssetParamsTypes.cpp new file mode 100644 index 000000000..48043832b --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParamsTypes.cpp @@ -0,0 +1,11 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParamsTypes.h" +#include "Asset/FlowAssetParams.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParamsTypes) + +UFlowAssetParams* FFlowAssetParamsPtr::ResolveFlowAssetParams() const +{ + return AssetPtr.LoadSynchronous(); +} diff --git a/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp b/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp new file mode 100644 index 000000000..d2ad66b99 --- /dev/null +++ b/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp @@ -0,0 +1,122 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/FlowAssetParamsUtils.h" +#include "Asset/FlowAssetParamsTypes.h" +#include "Types/FlowDataPinProperties.h" +#include "FlowLogChannels.h" +#include "Misc/DateTime.h" +#include "HAL/FileManager.h" +#include "UObject/Class.h" +#include "UObject/UObjectGlobals.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParamsUtils) + +#if WITH_EDITOR +FDateTime FFlowAssetParamsUtils::GetLastSavedTimestampForObject(const UObject* Object) +{ + if (!Object) + { + return FDateTime::MinValue(); + } + + const FString PackagePath = Object->GetPathName(); + return IFileManager::Get().GetTimeStamp(*PackagePath); +} + +EFlowReconcilePropertiesResult FFlowAssetParamsUtils::CheckPropertiesMatch( + const TArray& PropertiesA, + const TArray& PropertiesB) +{ + if (PropertiesA.Num() != PropertiesB.Num()) + { + return EFlowReconcilePropertiesResult::Error_PropertyCountMismatch; + } + + for (int32 Index = 0; Index < PropertiesA.Num(); ++Index) + { + const FFlowNamedDataPinProperty& PropA = PropertiesA[Index]; + const FFlowNamedDataPinProperty& PropB = PropertiesB[Index]; + const UScriptStruct* ScriptStructA = PropA.DataPinProperty.GetScriptStruct(); + const UScriptStruct* ScriptStructB = PropB.DataPinProperty.GetScriptStruct(); + + if (PropA.Name != PropB.Name || + ScriptStructA != ScriptStructB || + !IsValid(ScriptStructA)) + { + return EFlowReconcilePropertiesResult::Error_PropertyTypeMismatch; + } + } + + return EFlowReconcilePropertiesResult::NoChanges; +} + +const FFlowNamedDataPinProperty* FFlowAssetParamsUtils::FindPropertyByGuid( + const TArray& Props, + const FGuid& Guid) +{ + for (const FFlowNamedDataPinProperty& Prop : Props) + { + if (Prop.Guid == Guid) + { + return &Prop; + } + } + + return nullptr; +} + +FFlowNamedDataPinProperty* FFlowAssetParamsUtils::FindPropertyByGuid( + TArray& Props, + const FGuid& Guid) +{ + for (FFlowNamedDataPinProperty& Prop : Props) + { + if (Prop.Guid == Guid) + { + return &Prop; + } + } + + return nullptr; +} + +bool FFlowAssetParamsUtils::ArePropertyArraysEqual( + const TArray& A, + const TArray& B) +{ + if (A.Num() != B.Num()) + { + return false; + } + + for (int32 Index = 0; Index < A.Num(); ++Index) + { + if (!ArePropertiesEqual(A[Index], B[Index])) + { + return false; + } + } + + return true; +} + +bool FFlowAssetParamsUtils::ArePropertiesEqual( + const FFlowNamedDataPinProperty& A, + const FFlowNamedDataPinProperty& B) +{ + if (A.Name != B.Name || A.Guid != B.Guid) + { + return false; + } + + const UScriptStruct* ScriptStructA = A.DataPinProperty.GetScriptStruct(); + const UScriptStruct* ScriptStructB = B.DataPinProperty.GetScriptStruct(); + if (ScriptStructA != ScriptStructB) + { + return false; + } + + return A.DataPinProperty == B.DataPinProperty; +} + +#endif diff --git a/Source/Flow/Private/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/Public/Asset/FlowAssetParams.h b/Source/Flow/Public/Asset/FlowAssetParams.h new file mode 100644 index 000000000..d8053d108 --- /dev/null +++ b/Source/Flow/Public/Asset/FlowAssetParams.h @@ -0,0 +1,96 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Engine/DataAsset.h" +#include "Types/FlowDataPinProperties.h" +#include "Interfaces/FlowDataPinValueSupplierInterface.h" +#include "Interfaces/FlowAssetProviderInterface.h" +#include "Asset/FlowAssetParamsTypes.h" + +#include "FlowAssetParams.generated.h" + +class UFlowAsset; + +/** +* Data asset for storing Flow Graph Start node parameters, supporting external configuration. +*/ +UCLASS(BlueprintType) +class FLOW_API UFlowAssetParams : public UDataAsset, public IFlowDataPinValueSupplierInterface, public IFlowAssetProviderInterface +{ + GENERATED_BODY() + +public: +#if WITH_EDITORONLY_DATA + // Reference to the associated Flow Asset. + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = FlowAssetParams) + TSoftObjectPtr OwnerFlowAsset; + + // Reference to the "Parent" params object to inherit from (if any). + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = FlowAssetParams) + FFlowAssetParamsPtr ParentParams; + + // Array of properties synchronized with the Start node (local adds/overrides; effective flattened via ReconcilePropertiesWithParentParams). + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = FlowAssetParams, meta = (EditFixedSize)) + TArray Properties; +#endif + + UPROPERTY() + TMap> PropertyMap; + +public: + // UObject interface + virtual void PostLoad() override; + virtual void Serialize(FArchive& Ar) override; + // -- + + // IFlowDataPinValueSupplierInterface + virtual bool CanSupplyDataPinValues_Implementation() const override; + virtual FFlowDataPinResult_Bool TrySupplyDataPinAsBool_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Int TrySupplyDataPinAsInt_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Float TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Name TrySupplyDataPinAsName_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_String TrySupplyDataPinAsString_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Text TrySupplyDataPinAsText_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Enum TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Vector TrySupplyDataPinAsVector_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Rotator TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Transform TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_GameplayTag TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_GameplayTagContainer TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_InstancedStruct TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Object TrySupplyDataPinAsObject_Implementation(const FName& PinName) const override; + virtual FFlowDataPinResult_Class TrySupplyDataPinAsClass_Implementation(const FName& PinName) const override; + // -- + + // IFlowAssetProviderInterface + virtual UFlowAsset* ProvideFlowAsset() const override; + // -- + +#if WITH_EDITOR + // UObject interface + virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override; + // -- + + // Generates properties from the associated Start node or updates Start node from params. + EFlowReconcilePropertiesResult ReconcilePropertiesWithStartNode( + const FDateTime& FlowAssetLastSaveTimeStamp, + const TSoftObjectPtr& InOwnerFlowAsset, + TArray& MutablePropertiesFromStartNode); + + void ConfigureFlowAssetParams(TSoftObjectPtr OwnerAsset, TSoftObjectPtr InParentParams, const TArray& InProperties); + +protected: + + // Updates properties from ParentParams, handling inheritance and name enforcement. + EFlowReconcilePropertiesResult ReconcilePropertiesWithParentParams(); + + bool TryCheckOutFromSourceControl() const; + + EFlowReconcilePropertiesResult CheckForParentCycle() const; + + void ModifyAndRebuildPropertiesMap(); + + void RebuildPropertiesMap(); +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/Asset/FlowAssetParamsTypes.h b/Source/Flow/Public/Asset/FlowAssetParamsTypes.h new file mode 100644 index 000000000..6d8ecf13d --- /dev/null +++ b/Source/Flow/Public/Asset/FlowAssetParamsTypes.h @@ -0,0 +1,67 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowEnumUtils.h" +#include "UObject/SoftObjectPtr.h" + +#include "FlowAssetParamsTypes.generated.h" + +class UFlowAssetParams; + +// Result of reconciling Flow Asset Params with Start node or SuperParams. +UENUM(BlueprintType) +enum class EFlowReconcilePropertiesResult : uint8 +{ + NoChanges, + + ParamsPropertiesUpdated, + AssetPropertyValuesUpdated, + + Error_InvalidAsset, + Error_PropertyCountMismatch, + Error_PropertyTypeMismatch, + Error_CyclicInheritance, + Error_UnloadableParent, + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), + + SuccessFirst = NoChanges UMETA(Hidden), + SuccessLast = AssetPropertyValuesUpdated UMETA(Hidden), + + ModifiedFirst = ParamsPropertiesUpdated UMETA(Hidden), + ModifiedLast = AssetPropertyValuesUpdated UMETA(Hidden), + + ErrorFirst = Error_InvalidAsset UMETA(Hidden), + ErrorLast = Error_UnloadableParent UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowReconcilePropertiesResult) + +namespace EFlowReconcilePropertiesResult_Classifiers +{ + FORCEINLINE bool IsSuccessResult(EFlowReconcilePropertiesResult Result) { return FLOW_IS_ENUM_IN_SUBRANGE(Result, EFlowReconcilePropertiesResult::Success); } + FORCEINLINE bool IsModifiedResult(EFlowReconcilePropertiesResult Result) { return FLOW_IS_ENUM_IN_SUBRANGE(Result, EFlowReconcilePropertiesResult::Modified); } + FORCEINLINE bool IsErrorResult(EFlowReconcilePropertiesResult Result) { return FLOW_IS_ENUM_IN_SUBRANGE(Result, EFlowReconcilePropertiesResult::Error); } +} + +// Wrapper for TSoftObjectPtr to enable editor customization. +// +// Supported metadata tags: +// - ShowCreateNew - Should we show the "Create New" button? +// - HideChildParams - When showing a chooser, should we hide "Child" params or not? (Child params have a non-null ParentParams) +USTRUCT(BlueprintType) +struct FFlowAssetParamsPtr +{ + GENERATED_BODY() + + FFlowAssetParamsPtr() = default; + FFlowAssetParamsPtr(TSoftObjectPtr InAssetParamsPtr) : AssetPtr(InAssetParamsPtr) { } + + UFlowAssetParams* ResolveFlowAssetParams() const; + + // Reference to the Flow Asset Params. + UPROPERTY(EditAnywhere, Category = FlowAssetParams, meta = (EditAssetInline)) + TSoftObjectPtr AssetPtr; +}; diff --git a/Source/Flow/Public/Asset/FlowAssetParamsUtils.h b/Source/Flow/Public/Asset/FlowAssetParamsUtils.h new file mode 100644 index 000000000..9c1f966a1 --- /dev/null +++ b/Source/Flow/Public/Asset/FlowAssetParamsUtils.h @@ -0,0 +1,44 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Misc/DateTime.h" +#include "Asset/FlowAssetParamsTypes.h" + +#include "FlowAssetParamsUtils.generated.h" + +class UObject; +struct FFlowNamedDataPinProperty; + +/** +* Utility functions for Flow Asset Params reconciliation and validation. +*/ +USTRUCT() +struct FLOW_API FFlowAssetParamsUtils +{ + GENERATED_BODY() + +#if WITH_EDITOR + static FDateTime GetLastSavedTimestampForObject(const UObject* Object); + + static EFlowReconcilePropertiesResult CheckPropertiesMatch( + const TArray& PropertiesA, + const TArray& PropertiesB); + + static const FFlowNamedDataPinProperty* FindPropertyByGuid( + const TArray& Props, + const FGuid& Guid); + + static FFlowNamedDataPinProperty* FindPropertyByGuid( + TArray& Props, + const FGuid& Guid); + + static bool ArePropertyArraysEqual( + const TArray& A, + const TArray& B); + + static bool ArePropertiesEqual( + const FFlowNamedDataPinProperty& A, + const FFlowNamedDataPinProperty& B); +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/Interfaces/FlowAssetProviderInterface.h b/Source/Flow/Public/Interfaces/FlowAssetProviderInterface.h new file mode 100644 index 000000000..29d9bc95a --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowAssetProviderInterface.h @@ -0,0 +1,29 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" + +#include "FlowAssetProviderInterface.generated.h" + +class UFlowAsset; + +// Interface to define a UFlowAsset provider. +// This is used for filtering in FFlowAssetParamsPtrCustomization +UINTERFACE(MinimalAPI, Blueprintable, DisplayName = "Flow Asset Provider Interface") +class UFlowAssetProviderInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowAssetProviderInterface +{ + GENERATED_BODY() + +public: + + // Provide a FlowAsset for use in FFlowAssetParamsPtr resolution + UFUNCTION(BlueprintImplementableEvent, Category = FlowAssetParams, DisplayName = "ProvideFlowAsset") + UFlowAsset* K2_ProvideFlowAsset() const; + virtual UFlowAsset* ProvideFlowAsset() const; +}; diff --git a/Source/Flow/Public/Interfaces/FlowNamedPropertiesSupplierInterface.h b/Source/Flow/Public/Interfaces/FlowNamedPropertiesSupplierInterface.h new file mode 100644 index 000000000..b41bd87ac --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowNamedPropertiesSupplierInterface.h @@ -0,0 +1,30 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" + +#include "FlowNamedPropertiesSupplierInterface.generated.h" + +struct FFlowNamedDataPinProperty; + +UINTERFACE(Blueprintable) +class FLOW_API UFlowNamedPropertiesSupplierInterface : public UInterface +{ + GENERATED_BODY() +}; + +/** + * Interface for Flow nodes that supply named properties, such as Start or DefineProperties nodes. + */ +class FLOW_API IFlowNamedPropertiesSupplierInterface +{ + GENERATED_BODY() + +public: + +#if WITH_EDITOR + // Returns the array of named properties defined by this node. + virtual TArray& GetMutableNamedProperties() = 0; +#endif +}; diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp new file mode 100644 index 000000000..c42d98ea7 --- /dev/null +++ b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp @@ -0,0 +1,152 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Asset/AssetDefinition_FlowAssetParams.h" +#include "Asset/FlowAssetParams.h" +#include "FlowAsset.h" +#include "FlowEditorLogChannels.h" +#include "FlowEditorModule.h" +#include "Types/FlowDataPinProperties.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "ContentBrowserMenuContexts.h" +#include "ContentBrowserModule.h" +#include "FileHelpers.h" +#include "IContentBrowserSingleton.h" +#include "SourceControlHelpers.h" +#include "ToolMenus.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(AssetDefinition_FlowAssetParams) + +#define LOCTEXT_NAMESPACE "AssetDefinition_FlowAssetParams" + +FText UAssetDefinition_FlowAssetParams::GetAssetDisplayName() const +{ + return LOCTEXT("GetAssetDisplayName", "Flow Asset Params"); +} + +FLinearColor UAssetDefinition_FlowAssetParams::GetAssetColor() const +{ + return FLinearColor(255, 196, 128); +} + +TSoftClassPtr UAssetDefinition_FlowAssetParams::GetAssetClass() const +{ + return UFlowAssetParams::StaticClass(); +} + +TConstArrayView UAssetDefinition_FlowAssetParams::GetAssetCategories() const +{ + static const auto Categories = { FFlowAssetCategoryPaths::Flow }; + return Categories; +} + +FAssetSupportResponse UAssetDefinition_FlowAssetParams::CanLocalize(const FAssetData& InAsset) const +{ + return FAssetSupportResponse::Supported(); +} + +namespace MenuExtension_FlowAssetParams +{ + static void ExecuteCreateChildParams(const FToolMenuContext& InContext) + { + const UContentBrowserAssetContextMenuContext* Context = UContentBrowserAssetContextMenuContext::FindContextWithAssets(InContext); + if (!Context) + { + UE_LOG(LogFlowEditor, Warning, TEXT("No valid context for Create Child Params action")); + return; + } + + const TArray& SelectedParams = Context->LoadSelectedObjects(); + if (SelectedParams.Num() != 1) + { + UE_LOG(LogFlowEditor, Warning, TEXT("Create Child Params requires exactly one selected Flow Asset Params")); + return; + } + + UFlowAssetParams* ParentParams = SelectedParams[0]; + if (!IsValid(ParentParams)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Invalid Flow Asset Params selected for Create Child Params")); + return; + } + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + const FString PackagePath = FPackageName::GetLongPackagePath(ParentParams->GetPackage()->GetPathName()); + const FString BaseAssetName = ParentParams->GetName(); + + FString UniquePackageName, UniqueAssetName; + AssetToolsModule.Get().CreateUniqueAssetName(PackagePath + TEXT("/") + BaseAssetName, TEXT(""), UniquePackageName, UniqueAssetName); + if (UniqueAssetName.IsEmpty()) + { + UE_LOG(LogFlowEditor, Error, TEXT("Failed to generate unique asset name for child params of %s"), *BaseAssetName); + return; + } + + UFlowAssetParams* NewParams = Cast( + AssetToolsModule.Get().CreateAsset(UniqueAssetName, PackagePath, ParentParams->GetClass(), nullptr)); + if (!IsValid(NewParams)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Failed to create child Flow Asset Params: %s"), *UniqueAssetName); + return; + } + + if (USourceControlHelpers::IsAvailable()) + { + const FString FileName = USourceControlHelpers::PackageFilename(NewParams->GetPathName()); + if (!USourceControlHelpers::CheckOutOrAddFile(FileName)) + { + UE_LOG(LogFlowEditor, Warning, TEXT("Failed to check out/add %s; saved in-memory only"), *NewParams->GetPathName()); + } + } + + NewParams->ConfigureFlowAssetParams(ParentParams->OwnerFlowAsset, ParentParams, ParentParams->Properties); + + // Save the package (force save even if not prompted) + UPackage* Package = NewParams->GetPackage(); + TArray PackagesToSave = { Package }; + + // Saves without dialog/prompt + const bool bForceSave = true; + if (!UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, bForceSave)) + { + UE_LOG(LogFlowEditor, Error, TEXT("Failed to save child Flow Asset Params: %s"), *NewParams->GetPathName()); + return; + } + + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistryModule.Get().AssetCreated(NewParams); + TArray AssetsToSync = { NewParams }; + ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true); + } + + static void RegisterContextMenu() + { + UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateLambda([]() + { + FToolMenuOwnerScoped OwnerScoped(UE_MODULE_NAME); + UToolMenu* Menu = UE::ContentBrowser::ExtendToolMenu_AssetContextMenu(UFlowAssetParams::StaticClass()); + + FToolMenuSection& Section = Menu->FindOrAddSection("GetAssetActions"); + Section.AddDynamicEntry("Flow Asset Params Commands", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) + { + const TAttribute Label = LOCTEXT("FlowAssetParams_CreateChildParams", "Create Child Params"); + const TAttribute ToolTip = LOCTEXT("FlowAssetParams_CreateChildParamsTooltip", "Creates a new Flow Asset Params inheriting from the selected params."); + const FSlateIcon Icon = FSlateIcon(); + + FToolUIAction UIAction; + UIAction.ExecuteAction = FToolMenuExecuteAction::CreateStatic(&ExecuteCreateChildParams); + UIAction.CanExecuteAction = FToolMenuCanExecuteAction::CreateLambda([](const FToolMenuContext& InContext) + { + const UContentBrowserAssetContextMenuContext* Context = UContentBrowserAssetContextMenuContext::FindContextWithAssets(InContext); + return Context && Context->SelectedAssets.Num() == 1; + }); + InSection.AddMenuEntry("FlowAssetParams_CreateChildParams", Label, ToolTip, Icon, UIAction); + })); + })); + } + + static FDelayedAutoRegisterHelper DelayedAutoRegister(EDelayedRegisterRunPhase::EndOfEngineInit, &RegisterContextMenu); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/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/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/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 From 0ff147c7b67b5d7eb5fc8f7f6f6f66dcc228f719 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Wed, 8 Oct 2025 09:40:24 -0700 Subject: [PATCH 3/6] [Flow] Added new Data Pin Property wrapper type and subclasses Each of our existing FFlowDataPinProperty* classes adapted to FFlowDataPinValue* (and details customization) & FFlowDataPinType* classes These new wrappers implement IsInputPin as a configurable and add Array support (in anticipation of Flow Data Pin array support) Added registration subsystem for these types (which registers the standard types) These are not used yet, but they will be later in the refactor --- Source/Flow/Flow.Build.cs | 2 + Source/Flow/Private/FlowDataPinSubsystem.cpp | 103 ++++ Source/Flow/Private/Types/FlowDataPinType.cpp | 47 ++ .../Types/FlowDataPinTypeNamesStandard.cpp | 24 + .../Types/FlowDataPinTypesStandard.cpp | 5 + .../Flow/Private/Types/FlowDataPinValue.cpp | 19 + .../Types/FlowDataPinValuesStandard.cpp | 21 + Source/Flow/Public/FlowDataPinSubsystem.h | 50 ++ .../FlowDataPinValueOwnerInterface.h | 56 ++ Source/Flow/Public/Nodes/FlowNodeBase.h | 3 + Source/Flow/Public/Types/FlowDataPinType.h | 47 ++ .../Flow/Public/Types/FlowDataPinTypeName.h | 31 + .../Types/FlowDataPinTypeNamesStandard.h | 33 ++ .../Public/Types/FlowDataPinTypesStandard.h | 291 ++++++++++ Source/Flow/Public/Types/FlowDataPinValue.h | 60 ++ .../Public/Types/FlowDataPinValueTemplates.h | 128 +++++ .../Public/Types/FlowDataPinValuesStandard.h | 465 +++++++++++++++ Source/Flow/Public/Types/FlowPinEnums.h | 14 + Source/Flow/Public/Types/FlowStructUtils.h | 30 + .../FlowDataPinValueCustomization.cpp | 506 +++++++++++++++++ .../FlowDataPinValueCustomization_Class.cpp | 482 ++++++++++++++++ .../FlowDataPinValueCustomization_Enum.cpp | 532 ++++++++++++++++++ ...aPinValueCustomization_InstancedObject.cpp | 519 +++++++++++++++++ ...owDataPinValueCustomization_ObjectBase.cpp | 383 +++++++++++++ .../FlowEditor/Private/FlowEditorModule.cpp | 23 +- .../FlowDataPinValueCustomization.h | 140 +++++ .../FlowDataPinValueCustomization_Class.h | 86 +++ .../FlowDataPinValueCustomization_Enum.h | 107 ++++ ...ataPinValueCustomization_InstancedObject.h | 95 ++++ .../FlowDataPinValueCustomization_Object.h | 28 + ...FlowDataPinValueCustomization_ObjectBase.h | 79 +++ .../FlowDataPinValueStandardCustomizations.h | 28 + .../FlowValueSourcePolicy.h | 72 +++ .../UnrealExtensions/VisibilityArrayBuilder.h | 278 +++++++++ 34 files changed, 4786 insertions(+), 1 deletion(-) create mode 100644 Source/Flow/Private/FlowDataPinSubsystem.cpp create mode 100644 Source/Flow/Private/Types/FlowDataPinType.cpp create mode 100644 Source/Flow/Private/Types/FlowDataPinTypeNamesStandard.cpp create mode 100644 Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp create mode 100644 Source/Flow/Private/Types/FlowDataPinValue.cpp create mode 100644 Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp create mode 100644 Source/Flow/Public/FlowDataPinSubsystem.h create mode 100644 Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h create mode 100644 Source/Flow/Public/Types/FlowDataPinType.h create mode 100644 Source/Flow/Public/Types/FlowDataPinTypeName.h create mode 100644 Source/Flow/Public/Types/FlowDataPinTypeNamesStandard.h create mode 100644 Source/Flow/Public/Types/FlowDataPinTypesStandard.h create mode 100644 Source/Flow/Public/Types/FlowDataPinValue.h create mode 100644 Source/Flow/Public/Types/FlowDataPinValueTemplates.h create mode 100644 Source/Flow/Public/Types/FlowDataPinValuesStandard.h create mode 100644 Source/Flow/Public/Types/FlowStructUtils.h create mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp create mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Class.cpp create mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp create mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp create mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.cpp create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Enum.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowValueSourcePolicy.h create mode 100644 Source/FlowEditor/Public/UnrealExtensions/VisibilityArrayBuilder.h diff --git a/Source/Flow/Flow.Build.cs b/Source/Flow/Flow.Build.cs index 4d8b12253..397cf1bd9 100644 --- a/Source/Flow/Flow.Build.cs +++ b/Source/Flow/Flow.Build.cs @@ -31,7 +31,9 @@ public Flow(ReadOnlyTargetRules target) : base(target) { PublicDependencyModuleNames.AddRange(new[] { + "GraphEditor", "MessageLog", + "PropertyEditor", "UnrealEd", "SourceControl", }); diff --git a/Source/Flow/Private/FlowDataPinSubsystem.cpp b/Source/Flow/Private/FlowDataPinSubsystem.cpp new file mode 100644 index 000000000..fae5b6ca0 --- /dev/null +++ b/Source/Flow/Private/FlowDataPinSubsystem.cpp @@ -0,0 +1,103 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "FlowDataPinSubsystem.h" +#include "FlowLogChannels.h" +#include "Types/FlowDataPinTypesStandard.h" +#include "Engine/Engine.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinSubsystem) + +UFlowDataPinSubsystem* UFlowDataPinSubsystem::Get() +{ + return GEngine->GetEngineSubsystem(); +} + +void UFlowDataPinSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + // Register standard types + TInstancedStruct Type; + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); + + Type.InitializeAs(); + RegisterDataPinType(Type); +} + +void UFlowDataPinSubsystem::Deinitialize() +{ + UnregisterAllDataPinTypes(); + + Super::Deinitialize(); +} + +void UFlowDataPinSubsystem::RegisterDataPinType(const TInstancedStruct& DataPinType) +{ + const FFlowDataPinType& BaseType = DataPinType.Get(); + const FFlowPinTypeName& TypeName = BaseType.GetPinTypeName(); + + DataPinTypes.Add(TypeName, DataPinType); +} + +void UFlowDataPinSubsystem::UnregisterDataPinType(const FFlowPinTypeName& TypeName) +{ + DataPinTypes.Remove(TypeName); +} + +TArray UFlowDataPinSubsystem::GetDataPinTypeNames() const +{ + TArray TypeNames; + DataPinTypes.GetKeys(TypeNames); + + return TypeNames; +} \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowDataPinType.cpp b/Source/Flow/Private/Types/FlowDataPinType.cpp new file mode 100644 index 000000000..e8fd948f3 --- /dev/null +++ b/Source/Flow/Private/Types/FlowDataPinType.cpp @@ -0,0 +1,47 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowDataPinType.h" +#include "Nodes/FlowNodeBase.h" +#include "FlowDataPinSubsystem.h" +#include "FlowLogChannels.h" +#if WITH_EDITOR +#include "PropertyHandle.h" +#endif + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinType) + +const FFlowDataPinType* FFlowDataPinType::LookupDataPinType(const FFlowPinTypeName& DataPinTypeName) +{ + const FFlowDataPinType* DataPinType = UFlowDataPinSubsystem::Get()->FindDataPinType(DataPinTypeName); + + if (!DataPinType) + { + UE_LOG(LogFlow, Error, TEXT("Could not find data pin type %s in FlowDataPinSubsystem"), *DataPinTypeName.ToString()); + return nullptr; + } + + return DataPinType; +} + +bool FFlowDataPinType::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const +{ + Node.LogWarning(FString::Printf(TEXT("Unsupported pin type %s for pin %s"), *GetPinTypeName().ToString(), *PinName.ToString())); + + return false; +} + +bool FFlowDataPinType::PopulateResult(const UFlowNodeBase& Node, const FName& PinName, FFlowDataPinResult& OutResult) const +{ + Node.LogWarning(FString::Printf(TEXT("Unsupported pin type %s for pin %s"), *GetPinTypeName().ToString(), *PinName.ToString())); + + OutResult.Result = EFlowDataPinResolveResult::FailedMissingPin; + + return false; +} + +#if WITH_EDITOR +TSharedPtr FFlowDataPinType::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/FlowDataPinTypeNamesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinTypeNamesStandard.cpp new file mode 100644 index 000000000..66751657d --- /dev/null +++ b/Source/Flow/Private/Types/FlowDataPinTypeNamesStandard.cpp @@ -0,0 +1,24 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowDataPinTypeNamesStandard.h" + +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::UnknownPinTypeName = FName("Unknown"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameExec = FName("Exec"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameBool = FName("Bool"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInt = FName("Int"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInt64 = FName("Int64"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameFloat = FName("Float"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameDouble = FName("Double"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameEnum = FName("Enum"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameName = FName("Name"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameString = FName("String"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameText = FName("Text"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameVector = FName("Vector"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameRotator = FName("Rotator"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameTransform = FName("Transform"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTag = FName("GameplayTag"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTagContainer = FName("GameplayTagContainer"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedStruct = FName("InstancedStruct"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameObject = FName("Object"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedObject = FName("InstancedObject"); +const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameClass = FName("Class"); \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp new file mode 100644 index 000000000..164d2cc99 --- /dev/null +++ b/Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp @@ -0,0 +1,5 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowDataPinTypesStandard.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinTypesStandard) diff --git a/Source/Flow/Private/Types/FlowDataPinValue.cpp b/Source/Flow/Private/Types/FlowDataPinValue.cpp new file mode 100644 index 000000000..6fd7f5568 --- /dev/null +++ b/Source/Flow/Private/Types/FlowDataPinValue.cpp @@ -0,0 +1,19 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowDataPinValue.h" +#include "Types/FlowDataPinResults.h" +#include "Types/FlowDataPinType.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinValue) + +const FFlowDataPinType* FFlowDataPinValue::LookupDataPinType() const +{ + return FFlowDataPinType::LookupDataPinType(GetPinTypeName()); +} + +bool FFlowDataPinValue::PopulateResult(const FProperty* Property, const UObject* Container, FFlowDataPinResult& OutResult) const +{ + OutResult = FFlowDataPinResult(EFlowDataPinResolveResult::FailedMismatchedType); + + return false; +} \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp new file mode 100644 index 000000000..8907dac89 --- /dev/null +++ b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp @@ -0,0 +1,21 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowDataPinValuesStandard.h" +#include "Nodes/FlowPin.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinValuesStandard) + +#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 \ No newline at end of file diff --git a/Source/Flow/Public/FlowDataPinSubsystem.h b/Source/Flow/Public/FlowDataPinSubsystem.h new file mode 100644 index 000000000..5ba3a8558 --- /dev/null +++ b/Source/Flow/Public/FlowDataPinSubsystem.h @@ -0,0 +1,50 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Subsystems/EngineSubsystem.h" +#include "Types/FlowDataPinType.h" +#include "StructUtils/InstancedStruct.h" +#include "Templates/UnrealTypeTraits.h" + +#include "FlowDataPinSubsystem.generated.h" + +UCLASS(MinimalApi) +class UFlowDataPinSubsystem : public UEngineSubsystem +{ + GENERATED_BODY() + +protected: + UPROPERTY(Transient) + TMap> DataPinTypes; + +public: + FLOW_API static UFlowDataPinSubsystem* Get(); + + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + FLOW_API void RegisterDataPinType(const TInstancedStruct& DataPinType); + FLOW_API void UnregisterDataPinType(const FFlowPinTypeName& TypeName); + + template + const T* FindDataPinType(const FFlowPinTypeName& TypeName) const + { + static_assert(TIsDerivedFrom::IsDerived, "T must be derived from FFlowDataPinType"); + + if (const TInstancedStruct* Found = DataPinTypes.Find(TypeName)) + { + return Found->GetPtr(); + } + + return nullptr; + } + + FLOW_API TArray GetDataPinTypeNames() const; + +protected: + FORCEINLINE void UnregisterAllDataPinTypes() + { + DataPinTypes.Empty(); + } +}; \ No newline at end of file diff --git a/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h b/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h new file mode 100644 index 000000000..dc7f7cff3 --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h @@ -0,0 +1,56 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.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 CanModifyFlowDataPinValueType() const { return true; } + + // Determines if the bIsInputPin checkbox should be visible in the Details panel + virtual bool ShowFlowDataPinValueInputPinCheckbox() const { return true; } + + // -------------------------------------------------------------------- + // Class / Enum source visibility & edit policies + // + // These are value-level UI policy hooks (mirroring ShowFlowDataPinValueInputPinCheckbox / CanModifyFlowDataPinValueType) + // to allow higher-level owners (e.g., nodes, assets) to centrally control: + // * Whether the ClassFilter (or Enum source) row is shown at all + // * Whether the "Lock" toggle is shown + // * Whether the ClassFilter / Enum source itself is editable + // + // NOTE: + // - 'Value' may be nullptr if not derivable in a specific context; implementers should null-guard if they inspect it. + // - The per-value struct's own bLockClassFilter (editor-only) is applied AFTER these policies + // and AFTER metadata-based forcing (e.g., MetaClass), to produce final editability. + // - These methods intentionally share naming so the same logic can gate both ClassFilter and Enum source (EnumClass / EnumName). + // -------------------------------------------------------------------- + + // Should the ClassFilter (or analogous Enum source) row be visible? + virtual bool ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const { return true; } + + // Should the "Lock Class Filter" toggle (bLockClassFilter) be visible? + // (If false, the checkbox is hidden; its stored value may still disable editing if already true.) + virtual bool ShowFlowDataPinValueClassFilterLockToggle(const FFlowDataPinValue* Value) const { return true; } + + // Base policy for whether the ClassFilter / Enum source can be edited (before per-value lock flag). + virtual bool CanEditFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const { return true; } +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index 6c54a0a8d..45356e8ac 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -6,6 +6,7 @@ #include "Interfaces/FlowCoreExecutableInterface.h" #include "Interfaces/FlowContextPinSupplierInterface.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" #include "FlowMessageLog.h" #include "FlowTags.h" // used by subclasses #include "FlowTypes.h" @@ -59,6 +60,7 @@ class FLOW_API UFlowNodeBase : public UObject , public IFlowCoreExecutableInterface , public IFlowContextPinSupplierInterface + , public IFlowDataPinValueOwnerInterface { GENERATED_UCLASS_BODY() @@ -404,6 +406,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; diff --git a/Source/Flow/Public/Types/FlowDataPinType.h b/Source/Flow/Public/Types/FlowDataPinType.h new file mode 100644 index 000000000..5eff6cf26 --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinType.h @@ -0,0 +1,47 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowDataPinTypeNamesStandard.h" + +#include "UObject/NameTypes.h" +#include "Math/Color.h" +#if WITH_EDITOR +#include "GraphEditorSettings.h" +#endif + +#include "FlowDataPinType.generated.h" + +class FFormatArgumentValue; +class IPropertyHandle; +class UFlowNodeBase; +struct FFlowDataPinResult; + +USTRUCT(BlueprintType) +struct FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual ~FFlowDataPinType() {} + + // Lookup a registered type by name + FLOW_API static const FFlowDataPinType* LookupDataPinType(const FFlowPinTypeName& DataPinTypeName); + + // Identity + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const PURE_VIRTUAL(GetPinTypeName, return FFlowDataPinTypeNamesStandard::UnknownPinTypeName;) + + // Category / Subcategory + FLOW_API virtual FName GetPinCategory() const PURE_VIRTUAL(GetPinCategory, return NAME_None;) + FLOW_API virtual UObject* GetSubCategoryObject() const { return nullptr; } + + // Value resolution + FLOW_API virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const; + FLOW_API virtual bool PopulateResult(const UFlowNodeBase& Node, const FName& PinName, 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; +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinTypeName.h b/Source/Flow/Public/Types/FlowDataPinTypeName.h new file mode 100644 index 000000000..dcd5b4291 --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinTypeName.h @@ -0,0 +1,31 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/NameTypes.h" + +#include "FlowDataPinTypeName.generated.h" + +USTRUCT(BlueprintType) +struct FFlowPinTypeName +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, Category = FlowPin) + FName Name = NAME_None; + + FFlowPinTypeName() = default; + FFlowPinTypeName(const FName& InName) : Name(InName) {} + + 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/FlowDataPinTypeNamesStandard.h b/Source/Flow/Public/Types/FlowDataPinTypeNamesStandard.h new file mode 100644 index 000000000..935df91b0 --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinTypeNamesStandard.h @@ -0,0 +1,33 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/NameTypes.h" +#include "FlowDataPinTypeName.h" + +struct FFlowDataPinTypeNamesStandard +{ + // Other Standard Pin Types + static const FFlowPinTypeName UnknownPinTypeName; + static const FFlowPinTypeName ValueTypeNameExec; + + // "Standard" Data Pin Types + static const FFlowPinTypeName ValueTypeNameBool; + static const FFlowPinTypeName ValueTypeNameInt; + static const FFlowPinTypeName ValueTypeNameInt64; + static const FFlowPinTypeName ValueTypeNameFloat; + static const FFlowPinTypeName ValueTypeNameDouble; + static const FFlowPinTypeName ValueTypeNameEnum; + static const FFlowPinTypeName ValueTypeNameName; + static const FFlowPinTypeName ValueTypeNameString; + static const FFlowPinTypeName ValueTypeNameText; + static const FFlowPinTypeName ValueTypeNameVector; + static const FFlowPinTypeName ValueTypeNameRotator; + static const FFlowPinTypeName ValueTypeNameTransform; + static const FFlowPinTypeName ValueTypeNameGameplayTag; + static const FFlowPinTypeName ValueTypeNameGameplayTagContainer; + static const FFlowPinTypeName ValueTypeNameInstancedStruct; + static const FFlowPinTypeName ValueTypeNameObject; + static const FFlowPinTypeName ValueTypeNameInstancedObject; + static const FFlowPinTypeName ValueTypeNameClass; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinTypesStandard.h b/Source/Flow/Public/Types/FlowDataPinTypesStandard.h new file mode 100644 index 000000000..c61a54f55 --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinTypesStandard.h @@ -0,0 +1,291 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowDataPinType.h" +#include "Types/FlowDataPinTypeNamesStandard.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 "FlowDataPinTypesStandard.generated.h" + +/* + * Flow Data Pin Type Declarations (State-less) + * + * NOTE: + * - Per latest design update: Pin subcategory objects (enum assets, class filters, etc.) + * are NO LONGER stored on the FFlowDataPinType subclasses. + * Those objects now belong exclusively to the FFlowDataPinProperty subclasses (or pin instance metadata). + * - These type classes now only describe the stable identity: + * * PinTypeName + * * PinCategory + * * (Optional) Color override matching editor schema expectations + * - GetSubCategoryObject() is left as the base implementation (returns nullptr) for all types. + * The pin/property layer is responsible for assigning PinSubCategoryObject (e.g. enum asset, class filter, struct type). + * - Specialized struct coloring (Vector/Rotator/Transform) relies on the pin property assigning the correct + * PinSubCategoryObject (TBaseStructure<...>::Get()) during pin setup + * (see FFlowPin::TrySetStructSubCategoryObjectFromPinType()). + */ + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Exec : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameExec; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Exec; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->ExecutionPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Bool : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameBool; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Boolean; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->BooleanPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Int : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInt; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Int; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->IntPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Int64 : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInt64; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Int64; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->Int64PinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Float : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameFloat; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Float; } +#if WITH_EDITOR + // Using default color; adjust if a distinct Float color is later exposed. + virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Double : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + // Shares Float category (no separate Double category in schema) + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameDouble; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Float; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Name : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameName; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Name; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->NamePinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_String : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameString; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_String; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StringPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Text : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameText; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Text; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->TextPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Enum : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + // EnumClass removed per new design (belongs on property / pin instance) + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameEnum; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Enum; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Vector : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + // SubCategoryObject (FVector struct) supplied by property/pin logic now + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameVector; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->VectorPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Rotator : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameRotator; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->RotatorPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Transform : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameTransform; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->TransformPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_GameplayTag : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTag; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_GameplayTagContainer : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTagContainer; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_InstancedStruct : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedStruct; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Object : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + // ClassFilter removed; belongs on property + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameObject; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Object; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->ObjectPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_InstancedObject : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + // Still uses Object category + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedObject; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Object; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->ObjectPinTypeColor; } +#endif +}; + +USTRUCT(BlueprintType) +struct FFlowDataPinType_Class : public FFlowDataPinType +{ + GENERATED_BODY() + +public: + // MetaClassFilter removed; belongs on property + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameClass; } + virtual FName GetPinCategory() const override { return FFlowPin::PC_Class; } +#if WITH_EDITOR + virtual FLinearColor GetPinColor() const override { return GetDefault()->ClassPinTypeColor; } +#endif +}; \ 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..d86781dcd --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinValue.h @@ -0,0 +1,60 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "FlowPinEnums.h" +#include "FlowDataPinTypeNamesStandard.h" +#include "UObject/NameTypes.h" +#include "UObject/ObjectPtr.h" + +#include "FlowDataPinValue.generated.h" + +struct FFlowDataPinResult; +struct FFlowDataPinType; +class FProperty; +class UObject; +class IPropertyHandle; + +USTRUCT() +struct FFlowDataPinValue +{ + GENERATED_BODY() + + friend class FFlowDataPinValueCustomization; + + typedef void FValueType; + +protected: +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = DataPins) + bool bIsInputPin = false; + + UPROPERTY(EditAnywhere, Category = DataPins) + EFlowDataMultiType MultiType = EFlowDataMultiType::Single; +#endif + +public: + 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 { return nullptr; } + + // Optional SubCategory object source (now moved off the Type class). + // Implementations can return e.g. Enum asset, struct UScriptStruct, etc. Default: nullptr. + FLOW_API virtual UObject* GetSubCategoryObject() const { return nullptr; } +#endif + + // Pin Type Name (identity) + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const PURE_VIRTUAL(GetPinTypeName, return FFlowDataPinTypeNamesStandard::UnknownPinTypeName;) + + // Resolve the registered data pin type + FLOW_API const FFlowDataPinType* LookupDataPinType() const; + + // Populate a result object from this value (property + container context) + FLOW_API bool PopulateResult(const FProperty* Property, const UObject* Container, FFlowDataPinResult& OutResult) const; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinValueTemplates.h b/Source/Flow/Public/Types/FlowDataPinValueTemplates.h new file mode 100644 index 000000000..0e3a543db --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinValueTemplates.h @@ -0,0 +1,128 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/NameTypes.h" +#include "Types/FlowPinEnums.h" +#include "Types/FlowDataPinValue.h" +#include "Types/FlowDataPinResults.h" + +#include + +struct FFlowDataPinResult; + +struct FFlowDataPinValueTemplates +{ +public: +// TODO (gtaylor) adapt these for the data pins refactor +// +// // Generic template for single value extraction using access lambda +// template +// static bool TryGetFirstValue(const FFlowDataPinResult& Result, const FName& ValueTypeName, TInner& OutFirstValue, TAccessFunc AccessFunc) +// { +// if (Result.Result != EFlowDataPinResolveResult::Success || +// Result.ValueArray.IsEmpty() || +// ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) +// { +// return false; +// } +// +// const TFlowDataPinValueSubclass* TypedSubclass = Result.ValueArray[0].GetPtr(); +// AccessFunc(TypedSubclass, OutFirstValue); +// return true; +// } +// +// // Generic template for array value extraction using access lambda +// template +// static bool TryGetArrayValues(const FFlowDataPinResult& Result, const FName& ValueTypeName, TArray& OutArrayValues, TAccessFunc AccessFunc) +// { +// check(OutArrayValues.IsEmpty()); +// +// if (Result.Result != EFlowDataPinResolveResult::Success) +// { +// return false; +// } +// +// if (Result.ValueArray.IsEmpty()) +// { +// return true; +// } +// +// if (ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) +// { +// return false; +// } +// +// OutArrayValues.Reserve(Result.ValueArray.Num()); +// +// for (const TInstancedStruct& DataPinValue : Result.ValueArray) +// { +// checkf(DataPinValue.Get().GetPinTypeName() == ValueTypeName, "These arrays must be homogenous"); +// +// const TFlowDataPinValueSubclass* TypedSubclass = DataPinValue.GetPtr(); +// TInner Value; +// AccessFunc(TypedSubclass, Value); +// OutArrayValues.Add(Value); +// } +// +// return true; +// } +// +// // Template for single enum value extraction +// template +// static bool TryGetFirstEnumValue(const FFlowDataPinResult& Result, const FName& ValueTypeName, FName& OutFirstEnumValueName, UEnum*& OutEnumClass, TAccessFunc AccessFunc) +// { +// if (Result.Result != EFlowDataPinResolveResult::Success || +// Result.ValueArray.IsEmpty() || +// ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) +// { +// return false; +// } +// +// const FFlowDataPinValue_Enum* TypedSubclass = Result.ValueArray[0].GetPtr(); +// AccessFunc(TypedSubclass, OutFirstEnumValueName, OutEnumClass); +// return true; +// } +// +// // Template for enum array value extraction +// template +// static bool TryGetEnumArrayValues(const FFlowDataPinResult& Result, const FName& ValueTypeName, TArray& OutEnumValueNames, UEnum*& OutEnumClass, TAccessFunc AccessFunc) +// { +// check(OutEnumValueNames.IsEmpty()); +// check(OutEnumClass = nullptr); +// +// if (Result.Result != EFlowDataPinResolveResult::Success) +// { +// return false; +// } +// +// if (Result.ValueArray.IsEmpty()) +// { +// return true; +// } +// +// if (ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) +// { +// return false; +// } +// +// const FFlowDataPinValue_Enum* FirstTypedSubclass = Result.ValueArray[0].GetPtr(); +// OutEnumClass = FirstTypedSubclass->EnumClass; +// +// OutEnumValueNames.Reserve(Result.ValueArray.Num()); +// +// for (const TInstancedStruct& DataPinValue : Result.ValueArray) +// { +// checkf(DataPinValue.Get().GetPinTypeName() == ValueTypeName, "These arrays must be homogenous"); +// +// const FFlowDataPinValue_Enum* TypedSubclass = DataPinValue.GetPtr(); +// checkf(TypedSubclass->EnumClass == OutEnumClass, "Enum arrays must use the same EnumClass"); +// +// FName Value; +// AccessFunc(TypedSubclass, Value, OutEnumClass); +// OutEnumValueNames.Add(Value); +// } +// +// return true; +// } +}; \ 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..619620465 --- /dev/null +++ b/Source/Flow/Public/Types/FlowDataPinValuesStandard.h @@ -0,0 +1,465 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowDataPinValue.h" +#include "Types/FlowDataPinTypeNamesStandard.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(BlueprintType, DisplayName = "Bool - Flow DataPin Value", meta = (FlowPinType = "Bool")) +struct FFlowDataPinValue_Bool : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef bool FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ false }; + + FFlowDataPinValue_Bool() {} + FFlowDataPinValue_Bool(bool InValue) : Values({ InValue }) {} + FFlowDataPinValue_Bool(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameBool; } +}; + +// Int (int32) +USTRUCT(BlueprintType, DisplayName = "Int - Flow DataPin Value", meta = (FlowPinType = "Int")) +struct FFlowDataPinValue_Int : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef int32 FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0 }; + + FFlowDataPinValue_Int() {} + FFlowDataPinValue_Int(FValueType InValue) : Values({ InValue }) {} + FFlowDataPinValue_Int(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInt; } +}; + +// Int64 +USTRUCT(BlueprintType, DisplayName = "Int64 - Flow DataPin Value", meta = (FlowPinType = "Int64")) +struct FFlowDataPinValue_Int64 : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef int64 FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0 }; + + FFlowDataPinValue_Int64() {} + FFlowDataPinValue_Int64(int64 InValue) : Values({ InValue }) {} + FFlowDataPinValue_Int64(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInt64; } +}; + +// Float +USTRUCT(BlueprintType, DisplayName = "Float - Flow DataPin Value", meta = (FlowPinType = "Float")) +struct FFlowDataPinValue_Float : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef float FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0.f }; + + FFlowDataPinValue_Float() {} + FFlowDataPinValue_Float(float InValue) : Values({ InValue }) {} + FFlowDataPinValue_Float(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameFloat; } +}; + +// Double +USTRUCT(BlueprintType, DisplayName = "Double - Flow DataPin Value", meta = (FlowPinType = "Double")) +struct FFlowDataPinValue_Double : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef double FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values{ 0.0 }; + + FFlowDataPinValue_Double() {} + FFlowDataPinValue_Double(double InValue) : Values({ InValue }) {} + FFlowDataPinValue_Double(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameDouble; } +}; + +// Enum +USTRUCT(BlueprintType, DisplayName = "Enum - Flow DataPin Value", meta = (FlowPinType = "Enum")) +struct FFlowDataPinValue_Enum : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FName FValueType; + + 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; + + // Lock source (asset/name). Does NOT lock enumerator selection. + UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Enum Class")) + bool bLockEnumClass = false; +#endif + + FFlowDataPinValue_Enum() {} + FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, FName InValue) + : Values({ InValue }), EnumClass(InEnumClass) { + } + FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, const TArray& InValues) + : Values(InValues), EnumClass(InEnumClass) { + } + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameEnum; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return EnumClass.Get(); } + + FLOW_API void OnEnumNameChanged(); +#endif +}; + +// Name +USTRUCT(BlueprintType, DisplayName = "Name - Flow DataPin Value", meta = (FlowPinType = "Name")) +struct FFlowDataPinValue_Name : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FName FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_Name() {} + FFlowDataPinValue_Name(const FName& InValue) : Values({ InValue }) {} + FFlowDataPinValue_Name(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameName; } +}; + +// String +USTRUCT(BlueprintType, DisplayName = "String - Flow DataPin Value", meta = (FlowPinType = "String")) +struct FFlowDataPinValue_String : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FString FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_String() {} + FFlowDataPinValue_String(const FString& InValue) : Values({ InValue }) {} + FFlowDataPinValue_String(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameString; } +}; + +// Text +USTRUCT(BlueprintType, DisplayName = "Text - Flow DataPin Value", meta = (FlowPinType = "Text")) +struct FFlowDataPinValue_Text : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FText FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_Text() {} + FFlowDataPinValue_Text(const FText& InValue) : Values({ InValue }) {} + FFlowDataPinValue_Text(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameText; } +}; + +// Vector +USTRUCT(BlueprintType, DisplayName = "Vector - Flow DataPin Value", meta = (FlowPinType = "Vector")) +struct FFlowDataPinValue_Vector : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FVector FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_Vector() {} + FFlowDataPinValue_Vector(const FVector& InValue) : Values({ InValue }) {} + FFlowDataPinValue_Vector(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameVector; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } +#endif +}; + +// Rotator +USTRUCT(BlueprintType, DisplayName = "Rotator - Flow DataPin Value", meta = (FlowPinType = "Rotator")) +struct FFlowDataPinValue_Rotator : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FRotator FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_Rotator() {} + FFlowDataPinValue_Rotator(const FRotator& InValue) : Values({ InValue }) {} + FFlowDataPinValue_Rotator(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameRotator; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } +#endif +}; + +// Transform +USTRUCT(BlueprintType, DisplayName = "Transform - Flow DataPin Value", meta = (FlowPinType = "Transform")) +struct FFlowDataPinValue_Transform : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FTransform FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_Transform() {} + FFlowDataPinValue_Transform(const FTransform& InValue) : Values({ InValue }) {} + FFlowDataPinValue_Transform(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameTransform; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } +#endif +}; + +// GameplayTag +USTRUCT(BlueprintType, DisplayName = "GameplayTag - Flow DataPin Value", meta = (FlowPinType = "GameplayTag")) +struct FFlowDataPinValue_GameplayTag : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FGameplayTag FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_GameplayTag() {} + FFlowDataPinValue_GameplayTag(const FGameplayTag& InValue) : Values({ InValue }) {} + FFlowDataPinValue_GameplayTag(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTag; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } +#endif +}; + +// GameplayTagContainer +USTRUCT(BlueprintType, DisplayName = "GameplayTagContainer - Flow DataPin Value", meta = (FlowPinType = "GameplayTagContainer")) +struct FFlowDataPinValue_GameplayTagContainer : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FGameplayTagContainer FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_GameplayTagContainer() {} + FFlowDataPinValue_GameplayTagContainer(const FGameplayTagContainer& InValue) : Values({ InValue }) {} + FFlowDataPinValue_GameplayTagContainer(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTagContainer; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } +#endif +}; + +// InstancedStruct +USTRUCT(BlueprintType, DisplayName = "InstancedStruct - Flow DataPin Value", meta = (FlowPinType = "InstancedStruct")) +struct FFlowDataPinValue_InstancedStruct : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef FInstancedStruct FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + + FFlowDataPinValue_InstancedStruct() {} + FFlowDataPinValue_InstancedStruct(const FInstancedStruct& InValue) : Values({ InValue }) {} + FFlowDataPinValue_InstancedStruct(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedStruct; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } +#endif +}; + +// Object (reference) +USTRUCT(BlueprintType, DisplayName = "Object - Flow DataPin Value", meta = (FlowPinType = "Object")) +struct FFlowDataPinValue_Object : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef UObject* FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray> Values; + +#if WITH_EDITORONLY_DATA + // Class filter (advanced) + UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) + TObjectPtr ClassFilter = UObject::StaticClass(); + + UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Class Filter")) + bool bLockClassFilter = false; +#endif + + FFlowDataPinValue_Object() {} + FFlowDataPinValue_Object(UObject* InObject) + { + if (InObject) + { + Values.Add(InObject); + } + } + FFlowDataPinValue_Object(const TArray& InObjects) + { + for (UObject* Obj : InObjects) + { + if (Obj) + { + Values.Add(Obj); + } + } + } + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameObject; } + +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return ClassFilter; } +#endif +}; + +// InstancedObject (inline / instanced editing) +USTRUCT(BlueprintType, DisplayName = "InstancedObject - Flow DataPin Value", meta = (FlowPinType = "InstancedObject")) +struct FFlowDataPinValue_InstancedObject : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef UObject* FValueType; + + UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = DataPins) + TArray> Values; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) + TObjectPtr ClassFilter = UObject::StaticClass(); + + UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Class Filter")) + bool bLockClassFilter = false; +#endif + + FFlowDataPinValue_InstancedObject() {} + FFlowDataPinValue_InstancedObject(UObject* InObject) + { + if (InObject) + { + Values.Add(InObject); + } + } + FFlowDataPinValue_InstancedObject(const TArray& InObjects) + { + for (UObject* Obj : InObjects) + { + if (Obj) + { + Values.Add(Obj); + } + } + } + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedObject; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return ClassFilter; } +#endif +}; + +// Class (soft class paths) +USTRUCT(BlueprintType, DisplayName = "Class - Flow DataPin Value", meta = (FlowPinType = "Class")) +struct FFlowDataPinValue_Class : public FFlowDataPinValue +{ + GENERATED_BODY() + +public: + typedef UClass* FValueType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) + TArray Values; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) + TObjectPtr ClassFilter = UObject::StaticClass(); + + UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Class Filter")) + bool bLockClassFilter = false; +#endif + + FFlowDataPinValue_Class() {} + FFlowDataPinValue_Class(const FSoftClassPath& InPath) : Values({ InPath }) {} + FFlowDataPinValue_Class(const TArray& InValues) : Values(InValues) {} + + virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameClass; } +#if WITH_EDITOR + virtual UObject* GetSubCategoryObject() const override { return ClassFilter; } +#endif +}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowPinEnums.h b/Source/Flow/Public/Types/FlowPinEnums.h index 5035013cd..9577d5248 100644 --- a/Source/Flow/Public/Types/FlowPinEnums.h +++ b/Source/Flow/Public/Types/FlowPinEnums.h @@ -93,3 +93,17 @@ enum class EFlowDataPinResolveResult : uint8 Min = 0 UMETA(Hidden), }; FLOW_ENUM_RANGE_VALUES(EFlowDataPinResolveResult) + +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) \ 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..ccc3d0612 --- /dev/null +++ b/Source/Flow/Public/Types/FlowStructUtils.h @@ -0,0 +1,30 @@ +// 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; + } +} +#endif \ No newline at end of file diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp new file mode 100644 index 000000000..319e0f16d --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp @@ -0,0 +1,506 @@ +// 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 "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Text/STextBlock.h" +#include "Styling/AppStyle.h" +#include "FlowEditorLogChannels.h" +#include "DetailCustomizations/FlowValueSourcePolicy.h" +#include "Types/FlowDataPinValuesStandard.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization" + +static const FText MultiTypeTooltip = LOCTEXT("MultiTypeTooltip", + "Select whether this Data Pin holds a Single value or an Array of values.\n" + "Changing from Array to Single will trim the array to keep only the first element."); + +static const FText InputPinTooltip = LOCTEXT("InputPinTooltip", + "Marks this Data Pin as an Input.\n" + "When checked, the value is expected to be provided externally (upstream / user).\n" + "When unchecked, the pin is treated as an Output / internally produced value."); + +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(); + + if (MultiTypeOptions.Num() == 0) + { + MultiTypeOptions.Add(MakeShareable(new FString("Single"))); + MultiTypeOptions.Add(MakeShareable(new FString("Array"))); + } + + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + const EFlowDataMultiType CurrentType = GetCurrentMultiType(); + SelectedMultiType = MultiTypeOptions[CurrentType == EFlowDataMultiType::Single ? 0 : 1]; + + TSharedRef HeaderBox = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(MultiTypeComboBox, SComboBox>) + .OptionsSource(&MultiTypeOptions) + .OnGenerateWidget(this, &FFlowDataPinValueCustomization::GenerateMultiTypeWidget) + .OnSelectionChanged(this, &FFlowDataPinValueCustomization::OnMultiTypeChanged) + .IsEnabled(this, &FFlowDataPinValueCustomization::GetInputPinCheckboxEnabled) + .ToolTipText(MultiTypeTooltip) + .Content() + [ + SNew(STextBlock) + .Text(this, &FFlowDataPinValueCustomization::GetSelectedMultiTypeText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + + SHorizontalBox::Slot() + .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(InputPinTooltip) + [ + SNew(STextBlock) + .Text(LOCTEXT("InputPin", "Input Pin")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ]; + + AppendHeaderExtensions(HeaderBox); + + HeaderRow + .NameContent() + [ + SNew(STextBlock) + .Text(StructPropertyHandle->GetPropertyDisplayName()) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ColorAndOpacity(GetRowTint()) + ] + .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); + + if (!ValuesHandle.IsValid()) + { + return; + } + + EnsureSingleElementExists(); + BuildSingleBranch(StructBuilder); + BuildArrayBranch(StructBuilder); +} + +void FFlowDataPinValueCustomization::BuildSingleBranch(IDetailChildrenBuilder& StructBuilder) +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + auto FirstElementHandle = ValuesHandle->GetChildHandle(0); + + if (!FirstElementHandle.IsValid()) + { + return; + } + + IDetailPropertyRow& Row = StructBuilder.AddProperty(FirstElementHandle.ToSharedRef()); + Row.ShouldAutoExpand(true); + Row.Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization::GetSingleModeVisibility)); +} + +void FFlowDataPinValueCustomization::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + IDetailPropertyRow& Row = StructBuilder.AddProperty(ValuesHandle.ToSharedRef()); + Row.ShouldAutoExpand(true); + Row.Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization::GetArrayModeVisibility)); +} + +void FFlowDataPinValueCustomization::EnsureSingleElementExists() +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + uint32 NumChildren = 0; + ValuesHandle->GetNumChildren(NumChildren); + + if (NumChildren == 0) + { + if (auto AsArray = ValuesHandle->AsArray()) + { + AsArray->AddItem(); + } + } +} + +void FFlowDataPinValueCustomization::AppendHeaderExtensions(TSharedRef HeaderBox) +{ + const FFlowValueSourcePolicy* Policy = GetSourcePolicy(); + + if (!Policy || !Policy->bShowLockToggle) + { + return; + } + + TSharedPtr LockHandle = StructPropertyHandle->GetChildHandle(TEXT("bLockClassFilter")); + bool bEnum = false; + bool bIsObjectLike = false; + bool bIsClass = false; + + if (LockHandle.IsValid()) + { + if (IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) + { + bIsClass = true; + } + else if (IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle) || + IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) + { + bIsObjectLike = true; + } + } + + if (!LockHandle.IsValid()) + { + LockHandle = StructPropertyHandle->GetChildHandle(TEXT("bLockEnumClass")); + bEnum = LockHandle.IsValid(); + } + + if (!LockHandle.IsValid()) + { + return; + } + + const bool bMetaForced = Policy->bMetaForced; + + FText LockTooltip; + FText MetaTooltip; + + if (bEnum) + { + LockTooltip = LOCTEXT("EnumLockTooltip", + "Lock Enum Class & Name.\nPrevents changing the Enum asset or native enum name.\nEnumerator values remain editable."); + MetaTooltip = LOCTEXT("EnumLockMetaTooltip", "Enum source locked by metadata."); + } + else if (bIsClass) + { + LockTooltip = LOCTEXT("ClassLockTooltip", + "Lock Class Filter.\nPrevents changing the Class Filter.\nClass values remain editable."); + MetaTooltip = LOCTEXT("ClassLockMetaTooltip", "Class filter locked by metadata (MetaClass)."); + } + else if (bIsObjectLike) + { + LockTooltip = LOCTEXT("ObjectLockTooltip", + "Lock Object Class Filter.\nPrevents changing the Class Filter.\nObject references remain editable."); + MetaTooltip = LOCTEXT("ObjectLockMetaTooltip", "Object class filter locked by metadata (MetaClass)."); + } + else + { + LockTooltip = LOCTEXT("GenericLockTooltip", + "Lock source settings (disables changing the source). Values remain editable."); + MetaTooltip = LOCTEXT("GenericMetaTooltip", "Source locked by metadata."); + } + + if (bMetaForced) + { + HeaderBox->AddSlot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4.f, 0.f, 0.f, 0.f) + [ + SNew(SImage) + .Image(FAppStyle::GetBrush("Icons.Lock")) + .ToolTipText(MetaTooltip) + ]; + + return; + } + + HeaderBox->AddSlot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4.f, 0.f, 0.f, 0.f) + [ + SNew(SCheckBox) + .IsChecked_Lambda([LockHandle]() + { + bool bLocked = false; + LockHandle->GetValue(bLocked); + return bLocked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this, LockHandle](ECheckBoxState NewState) + { + if (!LockHandle.IsValid()) + { + return; + } + + const bool bNew = (NewState == ECheckBoxState::Checked); + + FScopedTransaction Tx(LOCTEXT("ToggleSourceLock", "Toggle Source Lock")); + LockHandle->SetValue(bNew); + OnSourceLockToggled(); + }) + .ToolTipText(LockTooltip) + [ + SNew(STextBlock) + .Text(LOCTEXT("LockShortLabel", "Lock")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ]; + + LockHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization::OnSourceLockToggled)); +} + +void FFlowDataPinValueCustomization::OnSourceLockToggled() +{ + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +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)); + + if (auto* Value = GetFlowDataPinValueBeingEdited()) + { + PinType = Value->LookupDataPinType(); + + if (PinType) + { + ValuesHandle = PinType->GetValuesHandle(PropertyHandle); + } + } +} + +void FFlowDataPinValueCustomization::CacheOwnerInterface() +{ + OwnerInterface = nullptr; + + TArray Outers; + StructPropertyHandle->GetOuterObjects(Outers); + + if (Outers.Num() == 1) + { + OwnerInterface = Cast(Outers[0]); + } +} + +void FFlowDataPinValueCustomization::OnMultiTypeChanged(TSharedPtr NewSelection, ESelectInfo::Type /*SelectInfo*/) +{ + if (!NewSelection.IsValid() || !MultiTypeHandle.IsValid()) + { + return; + } + + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + static FString ArrayValueAsString = UEnum::GetDisplayValueAsText(EFlowDataMultiType::Array).ToString(); + + const EFlowDataMultiType NewType = + *NewSelection == ArrayValueAsString ? EFlowDataMultiType::Array : EFlowDataMultiType::Single; + + FScopedTransaction Transaction(LOCTEXT("ChangePinMultiType", "Change Pin MultiType")); + + MultiTypeHandle->NotifyPreChange(); + MultiTypeHandle->SetValue(static_cast(NewType)); + + if (NewType == EFlowDataMultiType::Single) + { + TrimArrayToSingle(); + } + + MultiTypeHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + + if (CustomizationUtils) + { + if (TSharedPtr PropUtils = CustomizationUtils->GetPropertyUtilities()) + { + PropUtils->RequestRefresh(); + } + } + + SelectedMultiType = NewSelection; +} + +void FFlowDataPinValueCustomization::OnInputPinChanged(ECheckBoxState NewState) +{ + if (!IsInputPinHandle.IsValid()) + { + return; + } + + FScopedTransaction Transaction(LOCTEXT("ChangeInputPin", "Change Input Pin")); + + IsInputPinHandle->NotifyPreChange(); + IsInputPinHandle->SetValue(NewState == ECheckBoxState::Checked); + IsInputPinHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + + if (CustomizationUtils) + { + if (TSharedPtr PropUtils = CustomizationUtils->GetPropertyUtilities()) + { + PropUtils->RequestRefresh(); + } + } +} + +void FFlowDataPinValueCustomization::TrimArrayToSingle() +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + if (auto AsArray = ValuesHandle->AsArray()) + { + uint32 NumElements = 0; + AsArray->GetNumElements(NumElements); + + FScopedTransaction Transaction(LOCTEXT("TrimArrayToSingle", "Trim Array to Single")); + + if (NumElements == 0) + { + AsArray->AddItem(); + } + else + { + for (uint32 Index = NumElements - 1; Index >= 1; --Index) + { + AsArray->DeleteItem(Index); + } + } + + if (CustomizationUtils) + { + if (TSharedPtr PropUtils = CustomizationUtils->GetPropertyUtilities()) + { + PropUtils->RequestRefresh(); + } + } + } +} + +EFlowDataMultiType FFlowDataPinValueCustomization::GetCurrentMultiType() const +{ + if (MultiTypeHandle.IsValid()) + { + uint8 Value = 0; + MultiTypeHandle->GetValue(Value); + return static_cast(Value); + } + + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + 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 +{ + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + return GetCurrentMultiType() == EFlowDataMultiType::Single ? EVisibility::Visible : EVisibility::Collapsed; +} + +EVisibility FFlowDataPinValueCustomization::GetArrayModeVisibility() const +{ + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + 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->CanModifyFlowDataPinValueType() : true; +} + +FLinearColor FFlowDataPinValueCustomization::GetRowTint() const +{ + return PinType ? PinType->GetPinColor() : FLinearColor::White; +} + +TSharedRef FFlowDataPinValueCustomization::GenerateMultiTypeWidget(TSharedPtr Item) const +{ + return SNew(STextBlock) + .Text(Item.IsValid() ? FText::FromString(*Item) : FText::GetEmpty()) + .Font(IDetailLayoutBuilder::GetDetailFont()); +} + +FText FFlowDataPinValueCustomization::GetSelectedMultiTypeText() const +{ + return SelectedMultiType.IsValid() ? FText::FromString(*SelectedMultiType) : FText::GetEmpty(); +} + +#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..7f933ae64 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Class.cpp @@ -0,0 +1,482 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization_Class.h" + +#include "DetailLayoutBuilder.h" +#include "IDetailChildrenBuilder.h" +#include "PropertyHandle.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "EditorClassUtils.h" +#include "PropertyCustomizationHelpers.h" +#include "UObject/SoftObjectPath.h" +#include "UnrealExtensions/VisibilityArrayBuilder.h" +#include "IPropertyUtilities.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "ScopedTransaction.h" +#include "DetailCustomizations/FlowValueSourcePolicy.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_Class" + +void FFlowDataPinValueCustomization_Class::BuildValueRows( + TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + + if (!ValuesHandle.IsValid()) + { + return; + } + + ClassFilterHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Class, ClassFilter)); + + TrySetClassFilterFromMetaData(); + ExtractMetadata(); + RefreshEffectiveFilter(); + ComputePolicy(); + + // Source row visible only if not locked/forced and policy allows + if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && !SourcePolicy.bMetaForced && ClassFilterHandle.IsValid()) + { + BuildClassFilterRow(StructBuilder); + + ClassFilterHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Class::OnClassFilterChanged)); + } + + EnsureSingleElementExists(); + BuildSingleBranch(StructBuilder); + BuildArrayBranch(StructBuilder); + + BindValidationDelegates(); + ValidateAllElements(); +} + +void FFlowDataPinValueCustomization_Class::OnSourceLockToggled() +{ + ComputePolicy(); + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +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")); + bMetaClassForced = StructPropertyHandle->HasMetaData(TEXT("MetaClass")); + + if (const FProperty* MetaProp = StructPropertyHandle->GetMetaDataProperty()) + { + bAllowNone = !(MetaProp->PropertyFlags & CPF_NoClear); + } + else + { + bAllowNone = true; + } +} + +void FFlowDataPinValueCustomization_Class::ComputePolicy() +{ + FFlowDataPinValue_Class* ValueStruct = + IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); + + bool bPerValueLock = false; + +#if WITH_EDITORONLY_DATA + if (ValueStruct) + { + bPerValueLock = ValueStruct->bLockClassFilter; + } +#endif + + SourcePolicy = ComputeFlowValueSourcePolicy( + OwnerInterface, + reinterpret_cast(ValueStruct), + bMetaClassForced, + bPerValueLock, + true); +} + +void FFlowDataPinValueCustomization_Class::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) +{ + IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); + Row.DisplayName(LOCTEXT("ClassFilterLabel", "Class Filter")); + Row.IsEnabled(SourcePolicy.bFinalEditableSource); +} + +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(SourcePolicy.bFinalEditableValues) + .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) +{ + TSharedRef ArrayBuilder = + MakeShareable(new FVisibilityArrayBuilder(ValuesHandle.ToSharedRef(), + true, true, true)); + + ArrayBuilder->SetVisibilityGetter([this]() + { + return GetArrayModeVisibility(); + }); + + ArrayBuilder->OnGenerateArrayElementWidget( + FOnGenerateArrayElementWidgetVisible::CreateSP( + this, + &FFlowDataPinValueCustomization_Class::GenerateArrayElementRow)); + + StructBuilder.AddCustomBuilder(ArrayBuilder); +} + +void FFlowDataPinValueCustomization_Class::GenerateArrayElementRow( + TSharedRef ElementHandle, + int32 Index, + IDetailChildrenBuilder& ChildBuilder, + const TAttribute& RowVisibility) +{ + IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); + Row.Visibility(RowVisibility); + + 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(SourcePolicy.bFinalEditableValues) + .SelectedClass_Lambda([this, ElementHandle]() -> const UClass* + { + return GetSelectedClassForHandle(ElementHandle); + }) + .OnSetClass_Lambda([this, ElementHandle](const UClass* NewClass) + { + OnSetClassForHandle(NewClass, ElementHandle); + }) + ]; +} + +void FFlowDataPinValueCustomization_Class::BindValidationDelegates() +{ + 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() +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + UClass* FilterClass = CachedEffectiveFilter.Get(); + + if (!FilterClass) + { + return; + } + + auto AsArray = ValuesHandle->AsArray(); + + if (!AsArray.IsValid()) + { + return; + } + + uint32 Num = 0; + AsArray->GetNumElements(Num); + + TArray> ToClear; + ToClear.Reserve(Num); + + for (uint32 i = 0; i < Num; ++i) + { + TSharedPtr Elem = ValuesHandle->GetChildHandle(i); + + if (!Elem.IsValid()) + { + continue; + } + + FString Path; + + if (!GetElementPathString(Elem, Path) || IsNoneString(Path)) + { + continue; + } + + FSoftClassPath SCP(Path); + + if (UClass* Loaded = SCP.TryLoadClass()) + { + if (!Loaded->IsChildOf(FilterClass)) + { + ToClear.Add(Elem); + } + } + else + { + ToClear.Add(Elem); + } + } + + if (ToClear.Num() > 0) + { + const FScopedTransaction Tx(LOCTEXT("ClearInvalidClassValues", "Clear Invalid Class Values")); + + for (const TSharedPtr& Elem : ToClear) + { + if (Elem.IsValid()) + { + Elem->SetValueFromFormattedString(TEXT("None")); + } + } + } +} + +void FFlowDataPinValueCustomization_Class::ValidateElement(const TSharedPtr& ElementHandle, UClass* FilterClass) +{ + if (!ElementHandle.IsValid() || !FilterClass) + { + return; + } + + FString Path; + + if (!GetElementPathString(ElementHandle, Path) || IsNoneString(Path)) + { + return; + } + + FSoftClassPath SCP(Path); + + if (UClass* Loaded = SCP.TryLoadClass()) + { + if (!Loaded->IsChildOf(FilterClass)) + { + ElementHandle->SetValueFromFormattedString(TEXT("None")); + } + } + else + { + ElementHandle->SetValueFromFormattedString(TEXT("None")); + } +} + +const UClass* FFlowDataPinValueCustomization_Class::GetSelectedClassForHandle(TSharedPtr ElementHandle) const +{ + if (!ElementHandle.IsValid()) + { + return nullptr; + } + + FString Path; + + if (ElementHandle->GetValueAsFormattedString(Path) != FPropertyAccess::Success) + { + return nullptr; + } + + if (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; + } + + const FString NewValue = NewClass ? NewClass->GetPathName() : TEXT("None"); + ElementHandle->SetValueFromFormattedString(NewValue); + + if (Filter) + { + ValidateElement(ElementHandle, const_cast(Filter)); + } +} + +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) const +{ + 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..5ea2a00e5 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp @@ -0,0 +1,532 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization_Enum.h" + +#include "Types/FlowDataPinValuesStandard.h" +#include "DetailLayoutBuilder.h" +#include "IDetailChildrenBuilder.h" +#include "IPropertyUtilities.h" +#include "PropertyHandle.h" +#include "ScopedTransaction.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "DetailCustomizations/FlowValueSourcePolicy.h" + +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_Enum" + +void FFlowDataPinValueCustomization_Enum::BuildValueRows( + TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + CacheEnumHandles(InStructPropertyHandle); + + if (!bMultiTypeDelegateBound && MultiTypeHandle.IsValid()) + { + MultiTypeHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Enum::OnMultiTypeChanged)); + bMultiTypeDelegateBound = true; + } + + LockEnumHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, bLockEnumClass)); + ComputePolicy(); + + // SOURCE rows: only if visible & not locked + if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && EnumClassHandle.IsValid()) + { + IDetailPropertyRow& Row = StructBuilder.AddProperty(EnumClassHandle.ToSharedRef()); + Row.IsEnabled(SourcePolicy.bFinalEditableSource); + Row.ToolTip(GetEnumSourceTooltip()); + + EnumClassHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Enum::OnEnumSourceChanged)); + } + +#if WITH_EDITORONLY_DATA + if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && EnumNameHandle.IsValid()) + { + IDetailPropertyRow& Row = StructBuilder.AddProperty(EnumNameHandle.ToSharedRef()); + Row.IsEnabled(SourcePolicy.bFinalEditableSource); + Row.ToolTip(LOCTEXT("EnumNameTooltip", "Name of native C++ enum type (overrides asset if provided).")); + + EnumNameHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Enum::OnEnumSourceChanged)); + } +#endif + + RebuildEnumData(); + EnsureSingleElementExists(); + BuildSingle(StructBuilder); + BuildArray(StructBuilder); +} + +void FFlowDataPinValueCustomization_Enum::OnSourceLockToggled() +{ + ComputePolicy(); + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +void FFlowDataPinValueCustomization_Enum::ComputePolicy() +{ + FFlowDataPinValue_Enum* EnumStruct = GetEnumValueStruct(); + + bool bPerValueLock = false; + +#if WITH_EDITORONLY_DATA + if (EnumStruct) + { + bPerValueLock = EnumStruct->bLockEnumClass; + } +#endif + + // Enum has no MetaClass forcing, pass false + SourcePolicy = ComputeFlowValueSourcePolicy( + OwnerInterface, + reinterpret_cast(EnumStruct), + false, + bPerValueLock, + true); +} + +void FFlowDataPinValueCustomization_Enum::CacheEnumHandles(const TSharedRef& StructHandle) +{ + EnumClassHandle = StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, EnumClass)); + +#if WITH_EDITORONLY_DATA + EnumNameHandle = StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, EnumName)); +#endif +} + +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()) + { +#if WITH_EDITOR + EnumStruct->OnEnumNameChanged(); +#endif + } + + 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; + ValidNames.Reserve(EnumeratorOptions.Num()); + + for (auto& Opt : EnumeratorOptions) + { + if (Opt.IsValid()) + { + ValidNames.Add(*Opt); + } + } + + if (auto AsArray = ValuesHandle->AsArray()) + { + uint32 Count = 0; + AsArray->GetNumElements(Count); + + if (GetSingleVisibility() == 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; +} + +EVisibility FFlowDataPinValueCustomization_Enum::GetSingleVisibility() const +{ + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + + uint8 Mode = 0; + + if (MultiTypeHandle.IsValid() && + MultiTypeHandle->GetValue(Mode) == FPropertyAccess::Success && + (EFlowDataMultiType)Mode == EFlowDataMultiType::Single) + { + return EVisibility::Visible; + } + + return EVisibility::Collapsed; +} + +EVisibility FFlowDataPinValueCustomization_Enum::GetArrayVisibility() const +{ + FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + + uint8 Mode = 0; + + if (MultiTypeHandle.IsValid() && + MultiTypeHandle->GetValue(Mode) == FPropertyAccess::Success && + (EFlowDataMultiType)Mode == EFlowDataMultiType::Array) + { + return EVisibility::Visible; + } + + return EVisibility::Collapsed; +} + +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::Create( + TAttribute::FGetter::CreateSP(this, &FFlowDataPinValueCustomization_Enum::GetSingleVisibility))) + .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) +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + TSharedRef ArrayBuilder = + MakeShareable(new FVisibilityArrayBuilder(ValuesHandle.ToSharedRef(), true, true, true)); + + ArrayBuilder->SetVisibilityGetter([this]() + { + return GetArrayVisibility(); + }); + + ArrayBuilder->OnGenerateArrayElementWidget( + FOnGenerateArrayElementWidgetVisible::CreateSP( + this, + &FFlowDataPinValueCustomization_Enum::GenerateArrayElementVisible)); + + StructBuilder.AddCustomBuilder(ArrayBuilder); +} + +void FFlowDataPinValueCustomization_Enum::GenerateArrayElementVisible( + TSharedRef ElementHandle, + int32 Index, + IDetailChildrenBuilder& ChildBuilder, + const TAttribute& RowVisibility) +{ + IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); + Row.Visibility(RowVisibility); + + 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()) + ] + ]; +} + +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 WITH_EDITORONLY_DATA + if (!Data->EnumName.IsEmpty()) + { + Source = FString::Printf(TEXT("Native Enum: %s"), *Data->EnumName); + } +#endif + + 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; + } + + 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; + } + + FScopedTransaction Tx(LOCTEXT("SetEnumArrayElement", "Set Enum Array Element")); + ElementHandle->SetValue(*NewSelection); +} + +void FFlowDataPinValueCustomization_Enum::OnMultiTypeChanged() +{ + if (GetArrayVisibility() == EVisibility::Collapsed) + { + EnsureSingleElementExists(); + } + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +FFlowDataPinValue_Enum* FFlowDataPinValueCustomization_Enum::GetEnumValueStruct() const +{ + return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp new file mode 100644 index 000000000..feefdd7bb --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp @@ -0,0 +1,519 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h" + +#include "PropertyHandle.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/SBoxPanel.h" +#include "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "IPropertyUtilities.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/Package.h" +#include "UObject/UObjectIterator.h" +#include "EditorClassUtils.h" +#include "IDetailPropertyRow.h" +#include "IDetailChildrenBuilder.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_InstancedObject" + +// --------------------------- +// Metadata Extraction +// --------------------------- +void FFlowDataPinValueCustomization_InstancedObject::ExtractInterfaceMetadata() +{ + RequiredInterface = nullptr; + + if (!StructPropertyHandle.IsValid()) + { + return; + } + + const FString& MustImplement = StructPropertyHandle->GetMetaData(TEXT("MustImplement")); + + if (!MustImplement.IsEmpty()) + { + RequiredInterface = FEditorClassUtils::GetClassFromString(MustImplement); + } +} + +// --------------------------- +// ClassFilter Row +// --------------------------- +void FFlowDataPinValueCustomization_InstancedObject::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) +{ + if (!ClassFilterHandle.IsValid()) + { + return; + } + + ExtractInterfaceMetadata(); + + IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); + Row.DisplayName(LOCTEXT("InstancedObjClassFilter", "Class Filter")); + + TSharedPtr LocalHandle = ClassFilterHandle; + + TArray> Filters; + BuildClassFilterFilters(Filters); + + Row.CustomWidget() + .NameContent() + [ + LocalHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + SNew(SClassPropertyEntryBox) + .MetaClass(UObject::StaticClass()) + .AllowAbstract(true) // Abstract allowed + .IsBlueprintBaseOnly(false) + .AllowNone(true) + .HideViewOptions(false) + .ShowDisplayNames(true) + .ShowTreeView(true) + .RequiredInterface(nullptr) + .ClassViewerFilters(Filters) + .IsEnabled(SourcePolicy.bFinalEditableSource) + .SelectedClass_Lambda([LocalHandle]() -> const UClass* + { + UObject* ObjVal = nullptr; + + if (LocalHandle->GetValue(ObjVal) == FPropertyAccess::Success && ObjVal) + { + return Cast(ObjVal); + } + + return nullptr; + }) + .OnSetClass_Lambda([this, LocalHandle](const UClass* NewClass) + { + if (!LocalHandle.IsValid()) + { + return; + } + + if (NewClass) + { + // MustImplement interface + if (RequiredInterface && !NewClass->ImplementsInterface(RequiredInterface)) + { + NewClass = nullptr; + } + + // Require EditInlineNew for the filter + if (NewClass && !NewClass->HasAnyClassFlags(CLASS_EditInlineNew)) + { + NewClass = nullptr; + } + } + + LocalHandle->SetValue(const_cast(NewClass)); + OnClassFilterChanged(); + }) + ]; + + Row.IsEnabled(SourcePolicy.bFinalEditableSource); +} + +// --------------------------- +// Value Widget (instantiate if null) +// --------------------------- +TSharedRef FFlowDataPinValueCustomization_InstancedObject::BuildObjectValueWidgetForElement(TSharedPtr ElementHandle) +{ + if (!ElementHandle.IsValid()) + { + return SNew(STextBlock).Text(LOCTEXT("InvalidHandle", "")); + } + + UObject* CurrentObj = nullptr; + ElementHandle->GetValue(CurrentObj); + + if (CurrentObj) + { + BindPerElementValueChange(ElementHandle); + return ElementHandle->CreatePropertyValueWidget(); + } + + ExtractInterfaceMetadata(); + + // Value editing disabled by owner policy (not by lock) + if (!SourcePolicy.bFinalEditableValues) + { + return SNew(STextBlock) + .Text(LOCTEXT("ValuesDisabled", "(Values Locked by Owner Policy)")) + .ColorAndOpacity(FLinearColor::Gray); + } + + // Scan once for instantiables + if (!bInstantiableScanDone) + { + bHasAnyInstantiable = ScanForAnyInstantiable(); + bInstantiableScanDone = true; + } + + if (!bHasAnyInstantiable) + { + return SNew(STextBlock) + .Text(LOCTEXT("NoInstantiableFound", + "No instantiable (EditInlineNew, non-abstract) subclass found.\n" + "Adjust Class Filter or add a concrete EditInlineNew subclass.")) + .AutoWrapText(true); + } + + TArray> Filters; + BuildInstantiationFilters(Filters); + + return SNew(SClassPropertyEntryBox) + .MetaClass(UObject::StaticClass()) + .AllowAbstract(false) + .IsBlueprintBaseOnly(false) + .AllowNone(false) + .HideViewOptions(false) + .ShowDisplayNames(true) + .ShowTreeView(true) + .RequiredInterface(nullptr) + .ClassViewerFilters(Filters) + .SelectedClass_Lambda([]() -> const UClass* { return nullptr; }) + .OnSetClass_Lambda([this, ElementHandle](const UClass* ChosenClass) + { + if (!CanInstantiateClass(ChosenClass)) + { + return; + } + + InstantiateForHandle(ElementHandle, ChosenClass); + }); +} + +// --------------------------- +// Filters +// --------------------------- +bool FFlowDataPinValueCustomization_InstancedObject::FClassFilterRowFilter::IsClassAllowed( + const FClassViewerInitializationOptions&, + const UClass* InClass, + TSharedRef) +{ + if (!InClass) + { + return false; + } + + if (Base.IsValid() && !InClass->IsChildOf(Base.Get())) + { + return false; + } + + if (RequiredInterface.IsValid() && !InClass->ImplementsInterface(RequiredInterface.Get())) + { + return false; + } + + if (!InClass->HasAnyClassFlags(CLASS_EditInlineNew)) + { + return false; + } + + // Abstract allowed here + return true; +} + +bool FFlowDataPinValueCustomization_InstancedObject::FClassFilterRowFilter::IsUnloadedClassAllowed( + const FClassViewerInitializationOptions&, + const TSharedRef, + TSharedRef) +{ + return false; +} + +bool FFlowDataPinValueCustomization_InstancedObject::FInstantiationFilter::IsClassAllowed( + const FClassViewerInitializationOptions&, + const UClass* InClass, + TSharedRef) +{ + if (!InClass) + { + return false; + } + + if (Base.IsValid() && !InClass->IsChildOf(Base.Get())) + { + return false; + } + + if (RequiredInterface.IsValid() && !InClass->ImplementsInterface(RequiredInterface.Get())) + { + return false; + } + + if (InClass->HasAnyClassFlags(CLASS_Abstract)) + { + return false; + } + + if (!InClass->HasAnyClassFlags(CLASS_EditInlineNew)) + { + return false; + } + + return true; +} + +bool FFlowDataPinValueCustomization_InstancedObject::FInstantiationFilter::IsUnloadedClassAllowed( + const FClassViewerInitializationOptions&, + const TSharedRef, + TSharedRef) +{ + return false; +} + +void FFlowDataPinValueCustomization_InstancedObject::BuildClassFilterFilters(TArray>& Out) const +{ + TSharedRef Filter = MakeShared(); + Filter->Base = nullptr; + Filter->RequiredInterface = RequiredInterface; + Out.Add(Filter); +} + +void FFlowDataPinValueCustomization_InstancedObject::BuildInstantiationFilters(TArray>& Out) const +{ + TSharedRef Filter = MakeShared(); + Filter->Base = EffectiveFilterClass; + Filter->RequiredInterface = RequiredInterface; + Out.Add(Filter); +} + +// --------------------------- +// Enumeration for instantiables +// --------------------------- +bool FFlowDataPinValueCustomization_InstancedObject::ScanForAnyInstantiable() const +{ + UClass* BaseClass = EffectiveFilterClass.Get(); + + for (TObjectIterator It; It; ++It) + { + UClass* C = *It; + + if (!C) + { + continue; + } + + if (BaseClass && !C->IsChildOf(BaseClass)) + { + continue; + } + + if (RequiredInterface && !C->ImplementsInterface(RequiredInterface)) + { + continue; + } + + if (C->HasAnyClassFlags(CLASS_Abstract)) + { + continue; + } + + if (!C->HasAnyClassFlags(CLASS_EditInlineNew)) + { + continue; + } + + return true; + } + + return false; +} + +// --------------------------- +// Validation / Instantiation +// --------------------------- +bool FFlowDataPinValueCustomization_InstancedObject::CanInstantiateClass(const UClass* Candidate) const +{ + if (!Candidate) + { + return false; + } + + if (RequiredInterface && !Candidate->ImplementsInterface(RequiredInterface)) + { + return false; + } + + if (EffectiveFilterClass.IsValid() && !Candidate->IsChildOf(EffectiveFilterClass.Get())) + { + return false; + } + + if (Candidate->HasAnyClassFlags(CLASS_Abstract)) + { + return false; + } + + if (!Candidate->HasAnyClassFlags(CLASS_EditInlineNew)) + { + return false; + } + + return true; +} + +void FFlowDataPinValueCustomization_InstancedObject::InstantiateForHandle(TSharedPtr ElementHandle, const UClass* ChosenClass) +{ + if (!ElementHandle.IsValid() || !ChosenClass) + { + return; + } + + if (!CanInstantiateClass(ChosenClass)) + { + return; + } + + FScopedTransaction Tx(LOCTEXT("CreateInstancedObjectPinValue", "Create Instanced Object Pin Value")); + + UObject* Outer = ResolveOuterForNewObject(); + + if (!Outer) + { + Outer = GetTransientPackage(); + } + + Outer->Modify(); + + UObject* NewObj = NewObject( + Outer, + const_cast(ChosenClass), + NAME_None, + RF_Transactional); + + if (!NewObj) + { + return; + } + + NewObj->SetFlags(RF_Transactional); + + if (!SetInstancedObjectHandleDirect(ElementHandle, NewObj)) + { + SetInstancedObjectHandleDirect(ElementHandle, nullptr); + return; + } + + BindPerElementValueChange(ElementHandle); + + if (CustomizationUtils) + { + if (TSharedPtr Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +// Direct pointer write for instanced property +bool FFlowDataPinValueCustomization_InstancedObject::SetInstancedObjectHandleDirect( + TSharedPtr ElementHandle, + UObject* NewValue) +{ + if (!ElementHandle.IsValid()) + { + return false; + } + + void* Address = nullptr; + + if (ElementHandle->GetValueData(Address) != FPropertyAccess::Success || !Address) + { + return false; + } + + TArray Outers; + ElementHandle->GetOuterObjects(Outers); + + if (Outers.Num() > 0 && Outers[0]) + { + Outers[0]->Modify(); + } + + ElementHandle->NotifyPreChange(); + + UObject** Ptr = reinterpret_cast(Address); + *Ptr = NewValue; + + ElementHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + ElementHandle->NotifyFinishedChangingProperties(); + + return true; +} + +UObject* FFlowDataPinValueCustomization_InstancedObject::ResolveOuterForNewObject() const +{ + if (!StructPropertyHandle.IsValid()) + { + return nullptr; + } + + TArray Outers; + StructPropertyHandle->GetOuterObjects(Outers); + + if (Outers.Num() > 0) + { + return Outers[0]; + } + + return nullptr; +} + +void FFlowDataPinValueCustomization_InstancedObject::BindPerElementValueChange(TSharedPtr ElementHandle) +{ + if (!ElementHandle.IsValid()) + { + return; + } + + ElementHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_InstancedObject::OnElementValueChanged, ElementHandle)); +} + +void FFlowDataPinValueCustomization_InstancedObject::OnElementValueChanged(TSharedPtr ElementHandle) +{ + if (!ElementHandle.IsValid()) + { + return; + } + + UClass* Filter = EffectiveFilterClass.Get(); + + if (!Filter) + { + return; + } + + void* Address = nullptr; + + if (ElementHandle->GetValueData(Address) != FPropertyAccess::Success || !Address) + { + return; + } + + UObject* Obj = *reinterpret_cast(Address); + + if (Obj && !Obj->IsA(Filter)) + { + SetInstancedObjectHandleDirect(ElementHandle, nullptr); + } + + if (CustomizationUtils) + { + if (TSharedPtr Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.cpp new file mode 100644 index 000000000..8c60ccd35 --- /dev/null +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.cpp @@ -0,0 +1,383 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h" + +#include "DetailLayoutBuilder.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 "DetailCustomizations/FlowValueSourcePolicy.h" +#include "EditorClassUtils.h" + +#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_ObjectBase" + +void FFlowDataPinValueCustomization_ObjectBase::BuildValueRows( + TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + + if (!ValuesHandle.IsValid()) + { + return; + } + + ClassFilterHandle = StructPropertyHandle->GetChildHandle(TEXT("ClassFilter")); + + TryApplyMetaClass(); + ResolveEffectiveFilter(); + ComputePolicy(); + + if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && !SourcePolicy.bMetaForced && ClassFilterHandle.IsValid()) + { + BuildClassFilterRow(StructBuilder); + + ClassFilterHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::OnClassFilterChanged)); + } + + EnsureSingleElementExists(); + BuildSingleBranch(StructBuilder); + BuildArrayBranch(StructBuilder); + + BindDelegates(); + ValidateAll(); +} + +void FFlowDataPinValueCustomization_ObjectBase::OnSourceLockToggled() +{ + ComputePolicy(); + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +void FFlowDataPinValueCustomization_ObjectBase::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_ObjectBase::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_ObjectBase::ComputePolicy() +{ + bool bPerValueLock = false; + + if (FFlowDataPinValue_Object* ObjStruct = + IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) + { +#if WITH_EDITORONLY_DATA + bPerValueLock = ObjStruct->bLockClassFilter; +#endif + } + else if (FFlowDataPinValue_InstancedObject* InstStruct = + IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) + { +#if WITH_EDITORONLY_DATA + bPerValueLock = InstStruct->bLockClassFilter; +#endif + } + + const FFlowDataPinValue* BaseValue = + IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); + + SourcePolicy = ComputeFlowValueSourcePolicy( + OwnerInterface, + BaseValue, + bMetaClassForced, + bPerValueLock, + true); +} + +void FFlowDataPinValueCustomization_ObjectBase::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) +{ + IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); + Row.DisplayName(LOCTEXT("ObjClassFilter", "Class Filter")); + Row.IsEnabled(SourcePolicy.bFinalEditableSource); +} + +void FFlowDataPinValueCustomization_ObjectBase::BuildSingleBranch(IDetailChildrenBuilder& StructBuilder) +{ + auto First = ValuesHandle->GetChildHandle(0); + + if (!First.IsValid()) + { + return; + } + + StructBuilder.AddCustomRow(LOCTEXT("ObjectSingleSearch", "Object")) + .Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::GetSingleModeVisibility)) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("ObjectValueLabel", "Object")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + BuildObjectValueWidgetForElement(First) + ]; +} + +void FFlowDataPinValueCustomization_ObjectBase::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) +{ + TSharedRef ArrayBuilder = + MakeShareable(new FVisibilityArrayBuilder(ValuesHandle.ToSharedRef(), + true, true, true)); + + ArrayBuilder->SetVisibilityGetter([this]() + { + return GetArrayModeVisibility(); + }); + + ArrayBuilder->OnGenerateArrayElementWidget( + FOnGenerateArrayElementWidgetVisible::CreateSP( + this, + &FFlowDataPinValueCustomization_ObjectBase::GenerateArrayElementRow)); + + StructBuilder.AddCustomBuilder(ArrayBuilder); +} + +void FFlowDataPinValueCustomization_ObjectBase::GenerateArrayElementRow( + TSharedRef ElementHandle, + int32 Index, + IDetailChildrenBuilder& ChildBuilder, + const TAttribute& RowVisibility) +{ + IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); + Row.Visibility(RowVisibility); + + Row.CustomWidget() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("ObjectArrayElemFmt", "Object {0}"), FText::AsNumber(Index))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(250.f) + [ + BuildObjectValueWidgetForElement(ElementHandle) + ]; +} + +TSharedRef FFlowDataPinValueCustomization_ObjectBase::BuildObjectValueWidgetForElement(TSharedPtr ElementHandle) +{ + return SNew(SObjectPropertyEntryBox) + .PropertyHandle(ElementHandle) + .AllowedClass(EffectiveFilterClass.Get() ? EffectiveFilterClass.Get() : UObject::StaticClass()) + .AllowClear(SourcePolicy.bFinalEditableValues) + .IsEnabled(SourcePolicy.bFinalEditableValues) + .ToolTipText(SourcePolicy.bFinalEditableValues + ? LOCTEXT("ObjectPickerTooltip", "Select an object reference (filter may be locked).") + : LOCTEXT("ObjectPickerLockedTooltip", "Object references are not editable by owner policy or metadata.")); +} + +void FFlowDataPinValueCustomization_ObjectBase::BindDelegates() +{ + if (ClassFilterHandle.IsValid()) + { + ClassFilterHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::OnClassFilterChanged)); + } + + if (ValuesHandle.IsValid()) + { + ValuesHandle->SetOnPropertyValueChanged( + FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::OnValuesChanged)); + } +} + +void FFlowDataPinValueCustomization_ObjectBase::OnClassFilterChanged() +{ + ResolveEffectiveFilter(); + ValidateAll(); + + if (CustomizationUtils) + { + if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + { + Utils->RequestRefresh(); + } + } +} + +void FFlowDataPinValueCustomization_ObjectBase::OnValuesChanged() +{ + ValidateAll(); +} + +void FFlowDataPinValueCustomization_ObjectBase::ValidateAll() +{ + if (!ValuesHandle.IsValid()) + { + return; + } + + UClass* Filter = EffectiveFilterClass.Get(); + + if (!Filter) + { + return; + } + + auto AsArray = ValuesHandle->AsArray(); + + if (!AsArray.IsValid()) + { + return; + } + + uint32 Num = 0; + AsArray->GetNumElements(Num); + + TArray> ToClear; + ToClear.Reserve(Num); + + for (uint32 i = 0; i < Num; ++i) + { + auto Elem = ValuesHandle->GetChildHandle(i); + + if (!Elem.IsValid()) + { + continue; + } + + UObject* Obj = GetObjectValue(Elem); + + if (Obj && !Obj->IsA(Filter)) + { + ToClear.Add(Elem); + } + } + + if (ToClear.Num() > 0) + { + const FScopedTransaction Tx(LOCTEXT("ClearInvalidObjects", "Clear Invalid Object References")); + + for (auto& H : ToClear) + { + if (H.IsValid()) + { + SetObjectValue(H, nullptr); + } + } + } +} + +void FFlowDataPinValueCustomization_ObjectBase::ValidateElement(TSharedPtr ElementHandle, UClass* Filter) +{ + if (!ElementHandle.IsValid() || !Filter) + { + return; + } + + UObject* Obj = GetObjectValue(ElementHandle); + + if (Obj && !Obj->IsA(Filter)) + { + SetObjectValue(ElementHandle, nullptr); + } +} + +UObject* FFlowDataPinValueCustomization_ObjectBase::GetObjectValue(TSharedPtr ElementHandle) const +{ + UObject* Obj = nullptr; + + if (ElementHandle.IsValid()) + { + ElementHandle->GetValue(Obj); + } + + return Obj; +} + +void FFlowDataPinValueCustomization_ObjectBase::SetObjectValue(TSharedPtr ElementHandle, UObject* NewObj) +{ + if (!ElementHandle.IsValid()) + { + return; + } + + ElementHandle->SetValue(NewObj); +} + +UClass* FFlowDataPinValueCustomization_ObjectBase::GetCurrentFilterClassProperty() const +{ + if (!ClassFilterHandle.IsValid()) + { + return nullptr; + } + + UObject* Obj = nullptr; + + if (ClassFilterHandle->GetValue(Obj) == FPropertyAccess::Success) + { + return Cast(Obj); + } + + return nullptr; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index c036a4651..87836b2e7 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -33,6 +33,7 @@ #include "DetailCustomizations/FlowPinCustomization.h" #include "DetailCustomizations/FlowNamedDataPinOutputPropertyCustomization.h" #include "DetailCustomizations/FlowAssetParamsPtrCustomization.h" +#include "DetailCustomizations/FlowDataPinValueStandardCustomizations.h" #include "FlowAsset.h" #include "AddOns/FlowNodeAddOn.h" @@ -266,6 +267,26 @@ void FFlowEditorModule::RegisterDetailCustomizations() // Consider implementing details customizations... for every EFlowPinType FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); + // Value-based (new) data pin customizations + 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)); + RegisterCustomStructLayout(*FFlowDataPinValue_InstancedObject::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_InstancedObject::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); } } @@ -328,4 +349,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/Public/DetailCustomizations/FlowDataPinValueCustomization.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h new file mode 100644 index 000000000..0aaf1f1e8 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h @@ -0,0 +1,140 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowDataPinType.h" +#include "Types/FlowDataPinValue.h" +#include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" +#include "IPropertyTypeCustomization.h" +#include "Templates/UnrealTypeTraits.h" +#include "Widgets/Input/SComboBox.h" + +struct FFlowValueSourcePolicy; +class IFlowDataPinValueOwnerInterface; + +/* +* Flow Data Pin Value Customization +* +* Responsibilities: +* - Provides shared header (MultiType selector + Input Pin checkbox + optional lock UI). +* - Builds child rows for single value vs array modes (with auto element creation). +* - Integrates optional source policy (Class / Enum / Object-like specializations derive and supply it). +* - Supplies extensibility points (AppendHeaderExtensions, OnSourceLockToggled, GetSourcePolicy). +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization : public IFlowExtendedPropertyTypeCustomization +{ + using Super = IFlowExtendedPropertyTypeCustomization; + +protected: + // Property handles + TSharedPtr MultiTypeHandle; + TSharedPtr ValuesHandle; + TSharedPtr IsInputPinHandle; + + // Cached context + const FFlowDataPinType* PinType = nullptr; + IPropertyTypeCustomizationUtils* CustomizationUtils = nullptr; + IFlowDataPinValueOwnerInterface* OwnerInterface = nullptr; + // -- + + // MultiType UI state + TArray> MultiTypeOptions; + TSharedPtr SelectedMultiType; + TSharedPtr>> MultiTypeComboBox; + +public: + // Construction / Lifetime + 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: + // High-Level Build Flow + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils); + + virtual void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); + virtual void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); + + void EnsureSingleElementExists(); + // -- + + // Mode / State Queries + EFlowDataMultiType GetCurrentMultiType() const; + EVisibility GetSingleModeVisibility() const; + EVisibility GetArrayModeVisibility() const; + void TrimArrayToSingle(); + // -- + + // Visual / Appearance + FLinearColor GetRowTint() const; + FFlowDataPinValue* GetFlowDataPinValueBeingEdited() const + { + return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); + } + // -- + + // Header Extensions / Source Lock + virtual void AppendHeaderExtensions(TSharedRef HeaderBox); + virtual void OnSourceLockToggled(); + // -- + + // Optional Source Policy (Overridden in specialized subclasses) + virtual const FFlowValueSourcePolicy* GetSourcePolicy() const { return nullptr; } + // -- + + // Input Pin Helpers + ECheckBoxState GetCurrentIsInputPin() const; + EVisibility GetInputPinCheckboxVisibility() const; + bool GetInputPinCheckboxEnabled() const; + // -- + + // UI Generation 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(); + // -- +}; + +// ------------------------------------------------------------------------ +// Generic Template Customization for Simple Value Types +// ------------------------------------------------------------------------ +template +class TFlowDataPinValueCustomization : public FFlowDataPinValueCustomization +{ +public: + TFlowDataPinValueCustomization() = default; + + 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..228a88e9c --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h @@ -0,0 +1,86 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization.h" +#include "DetailCustomizations/FlowValueSourcePolicy.h" + +class SClassPropertyEntryBox; + +/* +* Class value customization using FFlowValueSourcePolicy: +* - Lock hides/disables only ClassFilter (source) — NOT the class value pickers. +* - Class value rows remain editable (subject to base owner policy & metadata), even when filter locked. +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_Class : public FFlowDataPinValueCustomization +{ + using Super = FFlowDataPinValueCustomization; + +public: + FFlowDataPinValueCustomization_Class() = default; + + static TSharedRef MakeInstance() + { + return MakeShareable(new FFlowDataPinValueCustomization_Class()); + } + + // Non-copyable / non-movable + FFlowDataPinValueCustomization_Class(const FFlowDataPinValueCustomization_Class&) = delete; + FFlowDataPinValueCustomization_Class& operator=(const FFlowDataPinValueCustomization_Class&) = delete; + FFlowDataPinValueCustomization_Class(FFlowDataPinValueCustomization_Class&&) = delete; + FFlowDataPinValueCustomization_Class& operator=(FFlowDataPinValueCustomization_Class&&) = delete; + +protected: + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + virtual void OnSourceLockToggled() override; + virtual const FFlowValueSourcePolicy* GetSourcePolicy() const override { return &SourcePolicy; } + +private: + // Property handles + TSharedPtr ClassFilterHandle; + + // Policy + FFlowValueSourcePolicy SourcePolicy; + + // 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 bMetaClassForced = false; + + // Cached effective filter + TWeakObjectPtr CachedEffectiveFilter; + + // Helpers + void ExtractMetadata(); + void ComputePolicy(); + void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder); + void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); + void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); + void GenerateArrayElementRow(TSharedRef ElementHandle, int32 Index, + IDetailChildrenBuilder& ChildBuilder, const TAttribute& RowVisibility); + + void BindValidationDelegates(); + void OnClassFilterChanged(); + void OnValuesChanged(); + + void TrySetClassFilterFromMetaData(); + UClass* DeriveBestClassFilter() const; + void RefreshEffectiveFilter(); + + void ValidateAllElements(); + void ValidateElement(const TSharedPtr& ElementHandle, UClass* FilterClass); + + const UClass* GetSelectedClassForHandle(TSharedPtr ElementHandle) const; + void OnSetClassForHandle(const UClass* NewClass, TSharedPtr ElementHandle); + + bool GetElementPathString(const TSharedPtr& ElementHandle, FString& OutPath) const; + bool IsNoneString(const FString& Str) 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..fc68cf7aa --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Enum.h @@ -0,0 +1,107 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization.h" +#include "DetailCustomizations/FlowValueSourcePolicy.h" +#include "UnrealExtensions/VisibilityArrayBuilder.h" + +class UEnum; +struct FFlowDataPinValue_Enum; + +/* +* Enum customization: +* - Lock affects only EnumClass / EnumName (source). +* - Enumerator value selection remains editable (independent of lock). +* - Uses policy field bFinalEditableSource for source rows. +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_Enum : public FFlowDataPinValueCustomization +{ + using Super = FFlowDataPinValueCustomization; + +public: + FFlowDataPinValueCustomization_Enum() = default; + + static TSharedRef MakeInstance() + { + return MakeShareable(new FFlowDataPinValueCustomization_Enum()); + } + + // Non-copyable / non-movable + FFlowDataPinValueCustomization_Enum(const FFlowDataPinValueCustomization_Enum&) = delete; + FFlowDataPinValueCustomization_Enum& operator=(const FFlowDataPinValueCustomization_Enum&) = delete; + FFlowDataPinValueCustomization_Enum(FFlowDataPinValueCustomization_Enum&&) = delete; + FFlowDataPinValueCustomization_Enum& operator=(FFlowDataPinValueCustomization_Enum&&) = delete; + +protected: + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + virtual void OnSourceLockToggled() override; + virtual const FFlowValueSourcePolicy* GetSourcePolicy() const override { return &SourcePolicy; } + +private: + // Source handles + TSharedPtr EnumClassHandle; + TSharedPtr EnumNameHandle; + + // Policy + FFlowValueSourcePolicy SourcePolicy; + + // Lock / value state + TSharedPtr LockEnumHandle; + TArray> EnumeratorOptions; + bool bEnumResolved = false; + bool bMultiTypeDelegateBound = false; + + // Builders + void BuildSingle(IDetailChildrenBuilder& StructBuilder); + void BuildArray(IDetailChildrenBuilder& StructBuilder); + + // Visibility helpers + EVisibility GetSingleVisibility() const; + EVisibility GetArrayVisibility() const; + + // 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); + + // Policy compute + void ComputePolicy(); + + // Convenience + FFlowDataPinValue_Enum* GetEnumValueStruct() const; + bool HasEnumeratorOptions() const { return bEnumResolved && EnumeratorOptions.Num() > 0; } + bool IsValueEditingEnabled() const { return HasEnumeratorOptions(); } +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h new file mode 100644 index 000000000..b3ac4254d --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h @@ -0,0 +1,95 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h" +#include "ClassViewerFilter.h" +#include "UObject/WeakObjectPtr.h" + +/* +* InstancedObject customization: +* - ClassFilter row: allows abstract classes, but requires EditInlineNew and interface (MustImplement) if specified. +* - Instantiation picker (when value is null): requires non-abstract + EditInlineNew + interface + subclass of EffectiveFilterClass. +* - If no instantiable subclasses exist, shows a message instead of picker. +* - Lock only affects ClassFilter editing (not instantiation). +* - Direct pointer assignment for instanced objects. +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_InstancedObject : public FFlowDataPinValueCustomization_ObjectBase +{ + using Super = FFlowDataPinValueCustomization_ObjectBase; + +public: + FFlowDataPinValueCustomization_InstancedObject() = default; + + static TSharedRef MakeInstance() + { + return MakeShareable(new FFlowDataPinValueCustomization_InstancedObject()); + } + + // Non-copyable / non-movable + FFlowDataPinValueCustomization_InstancedObject(const FFlowDataPinValueCustomization_InstancedObject&) = delete; + FFlowDataPinValueCustomization_InstancedObject& operator=(const FFlowDataPinValueCustomization_InstancedObject&) = delete; + FFlowDataPinValueCustomization_InstancedObject(FFlowDataPinValueCustomization_InstancedObject&&) = delete; + FFlowDataPinValueCustomization_InstancedObject& operator=(FFlowDataPinValueCustomization_InstancedObject&&) = delete; + +protected: + virtual bool SupportsInlineCreation() const override { return true; } + virtual void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) override; + virtual TSharedRef BuildObjectValueWidgetForElement(TSharedPtr ElementHandle) override; + +private: + class FClassFilterRowFilter : public IClassViewerFilter + { + public: + TWeakObjectPtr Base; + TWeakObjectPtr RequiredInterface; + + virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, + const UClass* InClass, + TSharedRef InFilterFuncs) override; + + virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions&, + const TSharedRef InUnloadedClassData, + TSharedRef) override; + }; + + class FInstantiationFilter : public IClassViewerFilter + { + public: + TWeakObjectPtr Base; + TWeakObjectPtr RequiredInterface; + + virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, + const UClass* InClass, + TSharedRef InFilterFuncs) override; + + virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions&, + const TSharedRef InUnloadedClassData, + TSharedRef) override; + }; + + // Metadata (MustImplement interface) + UClass* RequiredInterface = nullptr; + + // Cached scan result for instantiable subclasses + mutable bool bInstantiableScanDone = false; + mutable bool bHasAnyInstantiable = false; + + // Metadata / filtering + void ExtractInterfaceMetadata(); + void BuildClassFilterFilters(TArray>& Out) const; + void BuildInstantiationFilters(TArray>& Out) const; + + // Instantiation scanning + bool ScanForAnyInstantiable() const; + bool CanInstantiateClass(const UClass* Candidate) const; + + // Object creation / assignment + void InstantiateForHandle(TSharedPtr ElementHandle, const UClass* ChosenClass); + UObject* ResolveOuterForNewObject() const; + bool SetInstancedObjectHandleDirect(TSharedPtr ElementHandle, UObject* NewValue); + + // Per-element change tracking + void BindPerElementValueChange(TSharedPtr ElementHandle); + void OnElementValueChanged(TSharedPtr ElementHandle); +}; \ 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..d6f4499a9 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h @@ -0,0 +1,28 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h" + +/* +* Concrete customization for FFlowDataPinValue_Object. +* Relies entirely on ObjectBase shared behavior (no inline creation). +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_Object : public FFlowDataPinValueCustomization_ObjectBase +{ + using Super = FFlowDataPinValueCustomization_ObjectBase; + +public: + FFlowDataPinValueCustomization_Object() = default; + + static TSharedRef MakeInstance() + { + return MakeShareable(new FFlowDataPinValueCustomization_Object()); + } + + // Non-copyable / non-movable + FFlowDataPinValueCustomization_Object(const FFlowDataPinValueCustomization_Object&) = delete; + FFlowDataPinValueCustomization_Object& operator=(const FFlowDataPinValueCustomization_Object&) = delete; + FFlowDataPinValueCustomization_Object(FFlowDataPinValueCustomization_Object&&) = delete; + FFlowDataPinValueCustomization_Object& operator=(FFlowDataPinValueCustomization_Object&&) = delete; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h new file mode 100644 index 000000000..479bb0efd --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h @@ -0,0 +1,79 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "DetailCustomizations/FlowDataPinValueCustomization.h" +#include "DetailCustomizations/FlowValueSourcePolicy.h" + +/* +* Base for object-like pin value customizations (Object, InstancedObject). +* - Computes & applies source policy (lock only affects ClassFilter) +* - Adds (optionally) a ClassFilter property row +* - Validates object reference values against ClassFilter +* - Offers single / array layout; element value widget supplied by derived class +*/ +class FLOWEDITOR_API FFlowDataPinValueCustomization_ObjectBase : public FFlowDataPinValueCustomization +{ + using Super = FFlowDataPinValueCustomization; + +public: + FFlowDataPinValueCustomization_ObjectBase() = default; + virtual ~FFlowDataPinValueCustomization_ObjectBase() = default; + + // Non-copyable / non-movable + FFlowDataPinValueCustomization_ObjectBase(const FFlowDataPinValueCustomization_ObjectBase&) = delete; + FFlowDataPinValueCustomization_ObjectBase& operator=(const FFlowDataPinValueCustomization_ObjectBase&) = delete; + FFlowDataPinValueCustomization_ObjectBase(FFlowDataPinValueCustomization_ObjectBase&&) = delete; + FFlowDataPinValueCustomization_ObjectBase& operator=(FFlowDataPinValueCustomization_ObjectBase&&) = delete; + +protected: + virtual void BuildValueRows(TSharedRef InStructPropertyHandle, + IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + virtual void OnSourceLockToggled() override; + virtual const FFlowValueSourcePolicy* GetSourcePolicy() const override { return &SourcePolicy; } + +protected: + virtual bool SupportsInlineCreation() const { return false; } + virtual TSharedRef BuildObjectValueWidgetForElement(TSharedPtr ElementHandle); + virtual void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder); + +protected: + // Property handles + TSharedPtr ClassFilterHandle; + + // Policy + FFlowValueSourcePolicy SourcePolicy; + + // Metadata / filter state + bool bMetaClassForced = false; + TWeakObjectPtr EffectiveFilterClass; + + // Layout helpers + void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); + void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); + void GenerateArrayElementRow(TSharedRef ElementHandle, + int32 Index, + IDetailChildrenBuilder& ChildBuilder, + const TAttribute& RowVisibility); + + // Policy computation + void ComputePolicy(); + + // Meta-class handling + void TryApplyMetaClass(); + void ResolveEffectiveFilter(); + + // Delegates & validation + void BindDelegates(); + void OnClassFilterChanged(); + void OnValuesChanged(); + void ValidateAll(); + void ValidateElement(TSharedPtr ElementHandle, UClass* Filter); + + // Value access + UObject* GetObjectValue(TSharedPtr ElementHandle) const; + void SetObjectValue(TSharedPtr ElementHandle, UObject* NewObj); + UClass* GetCurrentFilterClassProperty() const; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h new file mode 100644 index 000000000..c58769a80 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h @@ -0,0 +1,28 @@ +// 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" +#include "DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.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/FlowValueSourcePolicy.h b/Source/FlowEditor/Public/DetailCustomizations/FlowValueSourcePolicy.h new file mode 100644 index 000000000..3d9941ad5 --- /dev/null +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowValueSourcePolicy.h @@ -0,0 +1,72 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Interfaces/FlowDataPinValueOwnerInterface.h" + +/* +* Unified source (ClassFilter / Enum source) policy describing visibility & editability. +* Extended to separate source editability from value editability so that +* locking the source (filter) does NOT lock the value pickers. +*/ +struct FFlowValueSourcePolicy +{ + // Show the source row at all (ClassFilter / EnumClass / EnumName) + bool bShowSourceRow = true; + + // Show the header "Lock" control + bool bShowLockToggle = true; + + // Per-value lock flag (NOT including metadata forcing) + bool bLocked = false; + + // Metadata (MetaClass) forces the filter (Class/Object/InstancedObject) + bool bMetaForced = false; + + // Editability prior to per-value lock (applies to both source & values) + bool bBaseEditable = true; + + // Final editability of the SOURCE (filter / enum class+name) after applying lock + bool bFinalEditableSource = true; + + // Final editability of the VALUES (class picks, object refs, enumerators) + // Does NOT consider the per-value lock (lock only affects source). + bool bFinalEditableValues = true; +}; + +/* +* Computes a unified policy. +* Lock affects only the source editability; value editability ignores the lock. +*/ +inline FFlowValueSourcePolicy ComputeFlowValueSourcePolicy( + IFlowDataPinValueOwnerInterface* Owner, + const FFlowDataPinValue* Value, + bool bHasMetaClass, + bool bPerValueLock, + bool bLockFieldExists) +{ + FFlowValueSourcePolicy P; + + P.bMetaForced = bHasMetaClass; + P.bShowSourceRow = Owner ? Owner->ShowFlowDataPinValueClassFilter(Value) : true; + + P.bShowLockToggle = + P.bShowSourceRow && + bLockFieldExists && + (Owner ? Owner->ShowFlowDataPinValueClassFilterLockToggle(Value) : true) && + !P.bMetaForced; + + P.bBaseEditable = + !P.bMetaForced && + (Owner ? Owner->CanEditFlowDataPinValueClassFilter(Value) : true); + + // Lock only impacts source, not values + P.bLocked = bPerValueLock; + + P.bFinalEditableSource = P.bBaseEditable && !P.bLocked; + + // Ignore lock for values + P.bFinalEditableValues = P.bBaseEditable; + + return P; +} \ No newline at end of file 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 From 83068490560694404fe453eadbafeab50dba75b2 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:18:32 -0800 Subject: [PATCH 4/6] Flow Data Pins Refactor Flow: Incremented UFlowGraph::GraphVersion to 2, created a data migration function (UpgradeAllFlowNodePins) FlowPinType namespace templates for the bulk of the "Supply/Resolve" pipeline support for data pins Updated standard FFlowPinType and FFlowDataPinValue subclasses to use them with the new resolve pipeline Reworked FlowSchema's pin compatibility checks to be more orderly, simpler and data-driven connectivity Created policies for schema connectivity rules for the standard types Updated FFlowNamedDataPinProperty to use FFlowDataPinValue as its property payload (including migrate functions from the old data) Updated FlowDataPinBlueprintLibrary with new auto-converts and functions to support data pin manipulation in blueprint Updated FlowNodeBase with the new Resolve pathway entry points & related refactors Updated FlowNode with the new Supply pathway entry points & related refactors Updated FlowPin to deprecateEnum PinType and add ContainerType (for Array data pins) and PinTypeName (the replacement for PinType Enum) Removed overrides TrySupplyDataPinAs... (now replaced by general version) Removed TrySupplyDataPinAs... variants from the IFlowDataPinValueSupplierInterface (leaving only the general replacement) Ported TryResolveDataPinAs... specialized versions to use the general version internally Adapted uses of TryResolveDataPinAs... to the general version TryResolveDataPin FlowNode (and AddOn) details customizations now inherit from TFlowDataPinValueOwnerCustomization, which adds a RequestRebuild() for rebuilding flow node details in a way that correctly rebuilds the FFlowDataPinValue customizations Refactored FlowAsset's automatic pin generation mechanism to be more clean, simple and work with the new system Details customizations for FFlowDataPinValue & specific subclasses Details customization for IFlowDataPinValueOwnerInterface implementers (via template) Added some details customization rebuild hooks into IFlowDataPinValueOwnerInterface to support FFlowDataPinValue details rebuilding Updated some flow nodes to use new Resolve functions (eg, Log, DefineProperties, Start, FormatText, etc.) Reworked FFlowPinSubsystem's api slightly Created new test classes and assets in FlowGraph_DataPinsTest AIFlow: Added FlowBlackboardEntry subclasses for ActorArray and LocationArray Updated all of the UFlowBlackboardEntryValue classes to use FFlowDataPinValue api's Updated SetBlackboardValues/SetBlackboardValuesOnActor to minimally function, and deprecated them Introduced combined and updated SetBlackboardValuesV2 replacement for the former SetBlackboardValues* nodes Updated GetBlackboardValues to work with the new APIs --- Flow.uplugin | 4 +- Source/Flow/Flow.Build.cs | 2 +- Source/Flow/Private/Asset/FlowAssetParams.cpp | 399 +-- .../Private/Asset/FlowAssetParamsUtils.cpp | 16 +- Source/Flow/Private/FlowAsset.cpp | 555 +--- Source/Flow/Private/FlowDataPinSubsystem.cpp | 103 - .../Private/FlowExecutableActorComponent.cpp | 50 + Source/Flow/Private/FlowPinSubsystem.cpp | 76 + .../Nodes/Actor/FlowNode_ExecuteComponent.cpp | 365 +-- .../Actor/FlowNode_PlayLevelSequence.cpp | 5 +- .../Private/Nodes/Developer/FlowNode_Log.cpp | 55 +- Source/Flow/Private/Nodes/FlowNode.cpp | 367 ++- Source/Flow/Private/Nodes/FlowNodeBase.cpp | 736 ++--- Source/Flow/Private/Nodes/FlowPin.cpp | 330 +-- .../Nodes/Graph/FlowNode_CustomInput.cpp | 4 +- .../Nodes/Graph/FlowNode_CustomOutput.cpp | 4 +- .../Nodes/Graph/FlowNode_DefineProperties.cpp | 61 +- .../Nodes/Graph/FlowNode_FormatText.cpp | 73 +- .../Private/Nodes/Graph/FlowNode_Start.cpp | 220 +- .../Private/Nodes/Graph/FlowNode_SubGraph.cpp | 18 +- .../Private/Nodes/Route/FlowNode_Timer.cpp | 16 +- .../Types/FlowAutoDataPinsWorkingData.cpp | 137 + .../Types/FlowDataPinBlueprintLibrary.cpp | 2358 ++++++++++++++++- .../Private/Types/FlowDataPinProperties.cpp | 190 +- .../Flow/Private/Types/FlowDataPinResults.cpp | 14 +- Source/Flow/Private/Types/FlowDataPinType.cpp | 47 - .../Types/FlowDataPinTypeNamesStandard.cpp | 24 - .../Types/FlowDataPinTypesStandard.cpp | 5 - .../Flow/Private/Types/FlowDataPinValue.cpp | 17 +- .../Types/FlowDataPinValuesStandard.cpp | 587 +++- .../Types/FlowNamedDataPinProperty.cpp | 38 + Source/Flow/Private/Types/FlowPinType.cpp | 99 + .../Types/FlowPinTypeNamesStandard.cpp | 162 ++ .../Private/Types/FlowPinTypesStandard.cpp | 503 ++++ Source/Flow/Public/Asset/FlowAssetParams.h | 54 +- .../Flow/Public/Asset/FlowAssetParamsTypes.h | 2 +- .../Public/Asset/FlowPinTypeMatchPolicy.h | 43 + Source/Flow/Public/FlowAsset.h | 59 +- Source/Flow/Public/FlowDataPinSubsystem.h | 50 - .../Public/FlowExecutableActorComponent.h | 61 + Source/Flow/Public/FlowPinSubsystem.h | 60 + .../FlowContextPinSupplierInterface.h | 3 + .../FlowDataPinGeneratorInterface.h | 27 + .../FlowDataPinGeneratorNodeInterface.h | 25 - .../FlowDataPinPropertyProviderInterface.h | 8 +- .../FlowDataPinValueOwnerInterface.h | 37 +- .../FlowDataPinValueSupplierInterface.h | 81 +- .../Nodes/Actor/FlowNode_ExecuteComponent.h | 23 +- .../Public/Nodes/Developer/FlowNode_Log.h | 4 + Source/Flow/Public/Nodes/FlowNode.h | 624 +---- Source/Flow/Public/Nodes/FlowNodeBase.h | 187 +- Source/Flow/Public/Nodes/FlowPin.h | 178 +- .../Public/Nodes/Graph/FlowNode_CustomInput.h | 2 +- .../Nodes/Graph/FlowNode_CustomOutput.h | 2 +- .../Nodes/Graph/FlowNode_DefineProperties.h | 20 +- .../Public/Nodes/Graph/FlowNode_FormatText.h | 4 +- .../Flow/Public/Nodes/Graph/FlowNode_Start.h | 19 +- .../Public/Nodes/Graph/FlowNode_SubGraph.h | 9 +- Source/Flow/Public/Types/FlowArray.h | 17 + .../Types/FlowAutoDataPinsWorkingData.h | 28 + .../Types/FlowDataPinBlueprintLibrary.h | 899 +++++-- .../Flow/Public/Types/FlowDataPinProperties.h | 289 +- .../FlowDataPinPropertyToValueMigration.h | 409 +++ Source/Flow/Public/Types/FlowDataPinResults.h | 85 +- Source/Flow/Public/Types/FlowDataPinType.h | 47 - .../Types/FlowDataPinTypeNamesStandard.h | 33 - .../Public/Types/FlowDataPinTypesStandard.h | 291 -- Source/Flow/Public/Types/FlowDataPinValue.h | 32 +- .../Public/Types/FlowDataPinValueTemplates.h | 128 - .../Public/Types/FlowDataPinValuesStandard.h | 506 ++-- .../Public/Types/FlowNamedDataPinProperty.h | 98 + Source/Flow/Public/Types/FlowPinEnums.h | 110 +- Source/Flow/Public/Types/FlowPinType.h | 55 + ...lowDataPinTypeName.h => FlowPinTypeName.h} | 6 +- .../Public/Types/FlowPinTypeNamesStandard.h | 39 + .../Public/Types/FlowPinTypeNodeTemplates.h | 70 + .../Flow/Public/Types/FlowPinTypeTemplates.h | 1025 +++++++ .../Flow/Public/Types/FlowPinTypesStandard.h | 520 ++++ Source/Flow/Public/Types/FlowStructUtils.h | 72 + Source/FlowEditor/FlowEditor.Build.cs | 3 + .../Asset/AssetDefinition_FlowAsset.cpp | 10 +- .../Asset/AssetDefinition_FlowAssetParams.cpp | 2 +- .../Private/Asset/FlowObjectDiff.cpp | 4 +- Source/FlowEditor/Private/Asset/SFlowDiff.cpp | 122 +- .../FlowDataPinPropertyCustomizationBase.cpp | 32 - ...FlowDataPinProperty_ClassCustomization.cpp | 180 -- .../FlowDataPinProperty_EnumCustomization.cpp | 109 - ...lowDataPinProperty_ObjectCustomization.cpp | 150 -- .../FlowDataPinValueCustomization.cpp | 521 ++-- .../FlowDataPinValueCustomization_Class.cpp | 304 +-- .../FlowDataPinValueCustomization_Enum.cpp | 284 +- ...aPinValueCustomization_InstancedObject.cpp | 519 ---- .../FlowDataPinValueCustomization_Object.cpp | 298 +++ ...owDataPinValueCustomization_ObjectBase.cpp | 383 --- ...FlowNamedDataPinPropertyCustomization.cpp} | 3 +- .../FlowNodeAddOn_Details.cpp | 4 + .../DetailCustomizations/FlowNode_Details.cpp | 8 +- .../FlowEditor/Private/FlowEditorModule.cpp | 45 +- Source/FlowEditor/Private/Graph/FlowGraph.cpp | 38 +- .../Private/Graph/FlowGraphNodesPolicy.cpp | 54 + .../Private/Graph/FlowGraphPinFactory.cpp | 25 - .../Private/Graph/FlowGraphSchema.cpp | 418 +-- .../Private/Graph/FlowGraphSchema_Actions.cpp | 9 +- .../Private/Graph/FlowGraphSettings.cpp | 12 + .../Private/Graph/Nodes/FlowGraphNode.cpp | 73 +- .../Private/Graph/Widgets/SFlowGraphNode.cpp | 10 +- Source/FlowEditor/Public/Asset/SFlowDiff.h | 13 +- .../FlowDataPinPropertyCustomizationBase.h | 24 - .../FlowDataPinPropertyCustomizations.h | 32 - .../FlowDataPinProperty_ClassCustomization.h | 52 - .../FlowDataPinProperty_EnumCustomization.h | 53 - .../FlowDataPinProperty_ObjectCustomization.h | 48 - .../FlowDataPinValueCustomization.h | 99 +- .../FlowDataPinValueCustomization_Class.h | 56 +- .../FlowDataPinValueCustomization_Enum.h | 44 +- ...ataPinValueCustomization_InstancedObject.h | 95 - .../FlowDataPinValueCustomization_Object.h | 63 +- ...FlowDataPinValueCustomization_ObjectBase.h | 79 - .../FlowDataPinValueOwnerCustomization.h | 60 + .../FlowDataPinValueOwnerCustomizations.h | 13 + .../FlowDataPinValueStandardCustomizations.h | 1 - ...> FlowNamedDataPinPropertyCustomization.h} | 2 - .../FlowNodeAddOn_Details.h | 6 +- .../DetailCustomizations/FlowNode_Details.h | 6 +- .../FlowPinCustomization.h | 2 +- .../FlowValueSourcePolicy.h | 72 - Source/FlowEditor/Public/Graph/FlowGraph.h | 4 + .../Public/Graph/FlowGraphNodesPolicy.h | 30 + .../Public/Graph/FlowGraphPinFactory.h | 12 - .../FlowEditor/Public/Graph/FlowGraphSchema.h | 30 +- .../Public/Graph/FlowGraphSettings.h | 8 + .../Public/Graph/Nodes/FlowGraphNode.h | 7 +- 132 files changed, 10732 insertions(+), 7780 deletions(-) delete mode 100644 Source/Flow/Private/FlowDataPinSubsystem.cpp create mode 100644 Source/Flow/Private/FlowExecutableActorComponent.cpp create mode 100644 Source/Flow/Private/FlowPinSubsystem.cpp create mode 100644 Source/Flow/Private/Types/FlowAutoDataPinsWorkingData.cpp delete mode 100644 Source/Flow/Private/Types/FlowDataPinType.cpp delete mode 100644 Source/Flow/Private/Types/FlowDataPinTypeNamesStandard.cpp delete mode 100644 Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp create mode 100644 Source/Flow/Private/Types/FlowNamedDataPinProperty.cpp create mode 100644 Source/Flow/Private/Types/FlowPinType.cpp create mode 100644 Source/Flow/Private/Types/FlowPinTypeNamesStandard.cpp create mode 100644 Source/Flow/Private/Types/FlowPinTypesStandard.cpp create mode 100644 Source/Flow/Public/Asset/FlowPinTypeMatchPolicy.h delete mode 100644 Source/Flow/Public/FlowDataPinSubsystem.h create mode 100644 Source/Flow/Public/FlowExecutableActorComponent.h create mode 100644 Source/Flow/Public/FlowPinSubsystem.h create mode 100644 Source/Flow/Public/Interfaces/FlowDataPinGeneratorInterface.h delete mode 100644 Source/Flow/Public/Interfaces/FlowDataPinGeneratorNodeInterface.h create mode 100644 Source/Flow/Public/Types/FlowAutoDataPinsWorkingData.h create mode 100644 Source/Flow/Public/Types/FlowDataPinPropertyToValueMigration.h delete mode 100644 Source/Flow/Public/Types/FlowDataPinType.h delete mode 100644 Source/Flow/Public/Types/FlowDataPinTypeNamesStandard.h delete mode 100644 Source/Flow/Public/Types/FlowDataPinTypesStandard.h delete mode 100644 Source/Flow/Public/Types/FlowDataPinValueTemplates.h create mode 100644 Source/Flow/Public/Types/FlowNamedDataPinProperty.h create mode 100644 Source/Flow/Public/Types/FlowPinType.h rename Source/Flow/Public/Types/{FlowDataPinTypeName.h => FlowPinTypeName.h} (72%) create mode 100644 Source/Flow/Public/Types/FlowPinTypeNamesStandard.h create mode 100644 Source/Flow/Public/Types/FlowPinTypeNodeTemplates.h create mode 100644 Source/Flow/Public/Types/FlowPinTypeTemplates.h create mode 100644 Source/Flow/Public/Types/FlowPinTypesStandard.h delete mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinPropertyCustomizationBase.cpp delete mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ClassCustomization.cpp delete mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_EnumCustomization.cpp delete mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.cpp delete mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp create mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Object.cpp delete mode 100644 Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.cpp rename Source/FlowEditor/Private/DetailCustomizations/{FlowNamedDataPinOutputPropertyCustomization.cpp => FlowNamedDataPinPropertyCustomization.cpp} (78%) create mode 100644 Source/FlowEditor/Private/Graph/FlowGraphNodesPolicy.cpp delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinPropertyCustomizationBase.h delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinPropertyCustomizations.h delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ClassCustomization.h delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_EnumCustomization.h delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinProperty_ObjectCustomization.h delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomization.h create mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueOwnerCustomizations.h rename Source/FlowEditor/Public/DetailCustomizations/{FlowNamedDataPinOutputPropertyCustomization.h => FlowNamedDataPinPropertyCustomization.h} (92%) delete mode 100644 Source/FlowEditor/Public/DetailCustomizations/FlowValueSourcePolicy.h create mode 100644 Source/FlowEditor/Public/Graph/FlowGraphNodesPolicy.h 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 397cf1bd9..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[] diff --git a/Source/Flow/Private/Asset/FlowAssetParams.cpp b/Source/Flow/Private/Asset/FlowAssetParams.cpp index a30daeede..fd0c89984 100644 --- a/Source/Flow/Private/Asset/FlowAssetParams.cpp +++ b/Source/Flow/Private/Asset/FlowAssetParams.cpp @@ -4,6 +4,9 @@ #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" @@ -11,20 +14,48 @@ #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParams) +#if WITH_EDITOR void UFlowAssetParams::PostLoad() { Super::PostLoad(); -#if WITH_EDITOR + 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 for %s: %s"), + UE_LOG(LogFlow, Error, TEXT("Failed to reconcile ParentParams during PostLoad() for %s: %s"), *GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); } -#endif } +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 @@ -37,8 +68,8 @@ void UFlowAssetParams::Serialize(FArchive& Ar) *GetPathName(), *UEnum::GetDisplayValueAsText(ReconcileResult).ToString()); } } -#endif +#endif Super::Serialize(Ar); } @@ -86,9 +117,9 @@ EDataValidationResult UFlowAssetParams::IsDataValid(FDataValidationContext& Cont Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); } - if (!Property.DataPinProperty.IsValid()) + if (!Property.DataPinValue.IsValid()) { - Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid DataPinProperty"), Index))); + Context.AddError(FText::FromString(FString::Printf(TEXT("Property at index %d has invalid DataPinValue"), Index))); Result = CombineDataValidationResults(Result, EDataValidationResult::Invalid); } @@ -180,31 +211,57 @@ EFlowReconcilePropertiesResult UFlowAssetParams::ReconcilePropertiesWithParentPa for (const FFlowNamedDataPinProperty& ParentProp : ParentProps) { FFlowNamedDataPinProperty* LocalProp = FFlowAssetParamsUtils::FindPropertyByGuid(Properties, ParentProp.Guid); - if (LocalProp && LocalProp->bIsOverride) + if (LocalProp != nullptr) { - FFlowNamedDataPinProperty UpdatedProp = *LocalProp; - - // Enforce Parent's name - UpdatedProp.Name = ParentProp.Name; - - NewProperties.Add(UpdatedProp); - - continue; + // 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; + } + } } - - if (LocalProp && FFlowAssetParamsUtils::ArePropertiesEqual(*LocalProp, ParentProp)) + else { - LocalProp->bIsOverride = false; - - // Enforce Parent's name - LocalProp->Name = ParentProp.Name; - - NewProperties.Add(*LocalProp); - - continue; + // We do not have a version of ParentProp. Just make a non-override copy. + FFlowNamedDataPinProperty& NewProp = NewProperties.Add_GetRef(ParentProp); + NewProp.bIsOverride = false; } - - NewProperties.Add(ParentProp); } for (FFlowNamedDataPinProperty& LocalProp : Properties) @@ -217,15 +274,8 @@ EFlowReconcilePropertiesResult UFlowAssetParams::ReconcilePropertiesWithParentPa } } - if (FFlowAssetParamsUtils::ArePropertyArraysEqual(NewProperties, Properties)) - { - return EFlowReconcilePropertiesResult::NoChanges; - } - Properties = NewProperties; - (void) TryCheckOutFromSourceControl(); - ModifyAndRebuildPropertiesMap(); return EFlowReconcilePropertiesResult::ParamsPropertiesUpdated; @@ -241,23 +291,29 @@ void UFlowAssetParams::ConfigureFlowAssetParams(TSoftObjectPtr Owner ModifyAndRebuildPropertiesMap(); } -bool UFlowAssetParams::TryCheckOutFromSourceControl() const +bool UFlowAssetParams::CanModifyFlowDataPinType() const { - if (!USourceControlHelpers::IsAvailable()) - { - return true; - } + // These are set by the Flow asset, which is authoritative + return false; +} - const FString FileName = USourceControlHelpers::PackageFilename(GetPathName()); - if (!USourceControlHelpers::CheckOutOrAddFile(FileName)) - { - UE_LOG(LogFlow, Warning, TEXT("%s is not checked out; properties updated in-memory only"), *GetPathName()); - return false; - } +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; @@ -302,7 +358,7 @@ void UFlowAssetParams::RebuildPropertiesMap() { if (Prop.IsValid()) { - PropertyMap.Add(Prop.Name, Prop.DataPinProperty); + PropertyMap.Add(Prop.Name, Prop.DataPinValue); } else { @@ -317,254 +373,15 @@ bool UFlowAssetParams::CanSupplyDataPinValues_Implementation() const return !PropertyMap.IsEmpty(); } -FFlowDataPinResult_Bool UFlowAssetParams::TrySupplyDataPinAsBool_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Bool::StaticStruct())) - { - const FFlowDataPinOutputProperty_Bool& BoolProp = Found->Get(); - return FFlowDataPinResult_Bool(BoolProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Bool pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Bool(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Int UFlowAssetParams::TrySupplyDataPinAsInt_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - const UScriptStruct* Struct = Found->GetScriptStruct(); - if (Struct->IsChildOf(FFlowDataPinOutputProperty_Int64::StaticStruct())) - { - const FFlowDataPinOutputProperty_Int64& IntProp = Found->Get(); - return FFlowDataPinResult_Int(IntProp.Value); - } - else if (Struct->IsChildOf(FFlowDataPinOutputProperty_Int32::StaticStruct())) - { - const FFlowDataPinOutputProperty_Int32& IntProp = Found->Get(); - return FFlowDataPinResult_Int(static_cast(IntProp.Value)); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Int pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Int(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Float UFlowAssetParams::TrySupplyDataPinAsFloat_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - const UScriptStruct* Struct = Found->GetScriptStruct(); - if (Struct->IsChildOf(FFlowDataPinOutputProperty_Double::StaticStruct())) - { - const FFlowDataPinOutputProperty_Double& FloatProp = Found->Get(); - return FFlowDataPinResult_Float(FloatProp.Value); - } - else if (Struct->IsChildOf(FFlowDataPinOutputProperty_Float::StaticStruct())) - { - const FFlowDataPinOutputProperty_Float& FloatProp = Found->Get(); - return FFlowDataPinResult_Float(static_cast(FloatProp.Value)); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Float pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Float(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Name UFlowAssetParams::TrySupplyDataPinAsName_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Name::StaticStruct())) - { - const FFlowDataPinOutputProperty_Name& NameProp = Found->Get(); - return FFlowDataPinResult_Name(NameProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Name pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Name(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_String UFlowAssetParams::TrySupplyDataPinAsString_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_String::StaticStruct())) - { - const FFlowDataPinOutputProperty_String& StringProp = Found->Get(); - return FFlowDataPinResult_String(StringProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for String pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_String(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Text UFlowAssetParams::TrySupplyDataPinAsText_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Text::StaticStruct())) - { - const FFlowDataPinOutputProperty_Text& TextProp = Found->Get(); - return FFlowDataPinResult_Text(TextProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Text pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Text(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Enum UFlowAssetParams::TrySupplyDataPinAsEnum_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Enum::StaticStruct())) - { - const FFlowDataPinOutputProperty_Enum& EnumProp = Found->Get(); - return FFlowDataPinResult_Enum(EnumProp.Value, EnumProp.EnumClass); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Enum pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Enum(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Vector UFlowAssetParams::TrySupplyDataPinAsVector_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Vector::StaticStruct())) - { - const FFlowDataPinOutputProperty_Vector& VectorProp = Found->Get(); - return FFlowDataPinResult_Vector(VectorProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Vector pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Vector(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Rotator UFlowAssetParams::TrySupplyDataPinAsRotator_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Rotator::StaticStruct())) - { - const FFlowDataPinOutputProperty_Rotator& RotatorProp = Found->Get(); - return FFlowDataPinResult_Rotator(RotatorProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Rotator pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Rotator(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Transform UFlowAssetParams::TrySupplyDataPinAsTransform_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Transform::StaticStruct())) - { - const FFlowDataPinOutputProperty_Transform& TransformProp = Found->Get(); - return FFlowDataPinResult_Transform(TransformProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Transform pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Transform(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_GameplayTag UFlowAssetParams::TrySupplyDataPinAsGameplayTag_Implementation(const FName& PinName) const +FFlowDataPinResult UFlowAssetParams::TrySupplyDataPin_Implementation(FName PinName) const { - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) + if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_GameplayTag::StaticStruct())) - { - const FFlowDataPinOutputProperty_GameplayTag& TagProp = Found->Get(); - return FFlowDataPinResult_GameplayTag(TagProp.Value); - } + FFlowDataPinResult DataPinResult(EFlowDataPinResolveResult::Success); + DataPinResult.ResultValue = (*Found); - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for GameplayTag pin %s in %s"), *PinName.ToString(), *GetPathName()); + return DataPinResult; } - return FFlowDataPinResult_GameplayTag(EFlowDataPinResolveResult::FailedUnknownPin); + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); } - -FFlowDataPinResult_GameplayTagContainer UFlowAssetParams::TrySupplyDataPinAsGameplayTagContainer_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_GameplayTagContainer::StaticStruct())) - { - const FFlowDataPinOutputProperty_GameplayTagContainer& ContainerProp = Found->Get(); - return FFlowDataPinResult_GameplayTagContainer(ContainerProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for GameplayTagContainer pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_GameplayTagContainer(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_InstancedStruct UFlowAssetParams::TrySupplyDataPinAsInstancedStruct_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_InstancedStruct::StaticStruct())) - { - const FFlowDataPinOutputProperty_InstancedStruct& StructProp = Found->Get(); - return FFlowDataPinResult_InstancedStruct(StructProp.Value); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for InstancedStruct pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_InstancedStruct(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Object UFlowAssetParams::TrySupplyDataPinAsObject_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Object::StaticStruct())) - { - const FFlowDataPinOutputProperty_Object& ObjectProp = Found->Get(); - return FFlowDataPinResult_Object(ObjectProp.GetObjectValue()); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Object pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Object(EFlowDataPinResolveResult::FailedUnknownPin); -} - -FFlowDataPinResult_Class UFlowAssetParams::TrySupplyDataPinAsClass_Implementation(const FName& PinName) const -{ - if (const TInstancedStruct* Found = PropertyMap.Find(PinName)) - { - if (Found->GetScriptStruct()->IsChildOf(FFlowDataPinOutputProperty_Class::StaticStruct())) - { - const FFlowDataPinOutputProperty_Class& ClassProp = Found->Get(); - return FFlowDataPinResult_Class(ClassProp.GetResolvedClass()); - } - - UE_LOG(LogFlow, Warning, TEXT("Type mismatch for Class pin %s in %s"), *PinName.ToString(), *GetPathName()); - } - - return FFlowDataPinResult_Class(EFlowDataPinResolveResult::FailedUnknownPin); -} \ No newline at end of file diff --git a/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp b/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp index d2ad66b99..8b1ae828d 100644 --- a/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp +++ b/Source/Flow/Private/Asset/FlowAssetParamsUtils.cpp @@ -1,13 +1,9 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Asset/FlowAssetParamsUtils.h" -#include "Asset/FlowAssetParamsTypes.h" -#include "Types/FlowDataPinProperties.h" -#include "FlowLogChannels.h" +#include "Types/FlowNamedDataPinProperty.h" #include "Misc/DateTime.h" #include "HAL/FileManager.h" -#include "UObject/Class.h" -#include "UObject/UObjectGlobals.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAssetParamsUtils) @@ -36,8 +32,8 @@ EFlowReconcilePropertiesResult FFlowAssetParamsUtils::CheckPropertiesMatch( { const FFlowNamedDataPinProperty& PropA = PropertiesA[Index]; const FFlowNamedDataPinProperty& PropB = PropertiesB[Index]; - const UScriptStruct* ScriptStructA = PropA.DataPinProperty.GetScriptStruct(); - const UScriptStruct* ScriptStructB = PropB.DataPinProperty.GetScriptStruct(); + const UScriptStruct* ScriptStructA = PropA.DataPinValue.GetScriptStruct(); + const UScriptStruct* ScriptStructB = PropB.DataPinValue.GetScriptStruct(); if (PropA.Name != PropB.Name || ScriptStructA != ScriptStructB || @@ -109,14 +105,14 @@ bool FFlowAssetParamsUtils::ArePropertiesEqual( return false; } - const UScriptStruct* ScriptStructA = A.DataPinProperty.GetScriptStruct(); - const UScriptStruct* ScriptStructB = B.DataPinProperty.GetScriptStruct(); + const UScriptStruct* ScriptStructA = A.DataPinValue.GetScriptStruct(); + const UScriptStruct* ScriptStructB = B.DataPinValue.GetScriptStruct(); if (ScriptStructA != ScriptStructB) { return false; } - return A.DataPinProperty == B.DataPinProperty; + return A.DataPinValue == B.DataPinValue; } #endif diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 9f52f9edb..a26b48efe 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -8,12 +8,15 @@ #include "AddOns/FlowNodeAddOn.h" #include "Asset/FlowAssetParams.h" #include "Asset/FlowAssetParamsUtils.h" -#include "Interfaces/FlowDataPinGeneratorNodeInterface.h" #include "Nodes/FlowNodeBase.h" #include "Nodes/Graph/FlowNode_CustomInput.h" #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" @@ -94,26 +97,30 @@ 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; + TSet NodesToRemoveGUID; - for (auto& [Guid, Node] : GetNodes()) - { - if (!IsValid(Node)) + for (auto& [Guid, Node] : GetNodes()) { - NodesToRemoveGUID.Emplace(Guid); + if (!IsValid(Node)) + { + NodesToRemoveGUID.Emplace(Guid); + } } - } - for (const FGuid& Guid : NodesToRemoveGUID) - { - UnregisterNode(Guid); + for (const FGuid& Guid : NodesToRemoveGUID) + { + UnregisterNode(Guid); + } + + ReconcileBaseAssetParams(FFlowAssetParamsUtils::GetLastSavedTimestampForObject(this)); } - - ReconcileBaseAssetParams(FFlowAssetParamsUtils::GetLastSavedTimestampForObject(this)); } void UFlowAsset::PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext) @@ -490,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; @@ -553,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) @@ -625,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 @@ -1177,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); @@ -1440,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)) { @@ -1655,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/FlowDataPinSubsystem.cpp b/Source/Flow/Private/FlowDataPinSubsystem.cpp deleted file mode 100644 index fae5b6ca0..000000000 --- a/Source/Flow/Private/FlowDataPinSubsystem.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "FlowDataPinSubsystem.h" -#include "FlowLogChannels.h" -#include "Types/FlowDataPinTypesStandard.h" -#include "Engine/Engine.h" - -#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinSubsystem) - -UFlowDataPinSubsystem* UFlowDataPinSubsystem::Get() -{ - return GEngine->GetEngineSubsystem(); -} - -void UFlowDataPinSubsystem::Initialize(FSubsystemCollectionBase& Collection) -{ - Super::Initialize(Collection); - - // Register standard types - TInstancedStruct Type; - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); - - Type.InitializeAs(); - RegisterDataPinType(Type); -} - -void UFlowDataPinSubsystem::Deinitialize() -{ - UnregisterAllDataPinTypes(); - - Super::Deinitialize(); -} - -void UFlowDataPinSubsystem::RegisterDataPinType(const TInstancedStruct& DataPinType) -{ - const FFlowDataPinType& BaseType = DataPinType.Get(); - const FFlowPinTypeName& TypeName = BaseType.GetPinTypeName(); - - DataPinTypes.Add(TypeName, DataPinType); -} - -void UFlowDataPinSubsystem::UnregisterDataPinType(const FFlowPinTypeName& TypeName) -{ - DataPinTypes.Remove(TypeName); -} - -TArray UFlowDataPinSubsystem::GetDataPinTypeNames() const -{ - TArray TypeNames; - DataPinTypes.GetKeys(TypeNames); - - return TypeNames; -} \ No newline at end of file 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/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 2f70c55b0..50926679c 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 @@ -623,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")); @@ -751,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 @@ -882,570 +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) + const FFlowDataPinValue& DataPinValue = NamedDataPinProperty.DataPinValue.Get(); + + const FFlowPinTypeName PinTypeName = DataPinValue.GetPinTypeName(); + if (PinTypeName.IsNone()) { return false; } - const EFlowPinType FlowPinType = FlowDataPinProperty->GetFlowPinType(); - - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - switch (FlowPinType) + const FFlowPinType* PinType = FFlowPinType::LookupPinType(PinTypeName); + if (!PinType) { - 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; + return false; + } - default: break; + FFormatArgumentValue FormatValue; + if (PinType->ResolveAndFormatPinValue(*this, NamedDataPinProperty.Name, FormatValue)) + { + InOutArguments.Add(NamedDataPinProperty.Name.ToString(), FormatValue); + return true; } return false; } -EFlowDataPinResolveResult UFlowNodeBase::TryResolveDataPinPrerequisites(const FName& PinName, const UFlowNode*& FlowNode, const FFlowPin*& FlowPin, EFlowPinType PinType) const +FFlowDataPinResult UFlowNodeBase::TryResolveDataPin(FName PinName) const { - FlowNode = GetFlowNodeSelfOrOwner(); + FFlowDataPinResult DataPinResult(EFlowDataPinResolveResult::Success); - if (!IsValid(FlowNode)) + const UFlowNode* FlowNode = GetFlowNodeSelfOrOwner(); + UFlowNode::TFlowPinValueSupplierDataArray PinValueSupplierDatas; + if (!FlowNode->TryGetFlowDataPinSupplierDatasForPinName(PinName, PinValueSupplierDatas)) { - LogError(FString::Printf(TEXT("Unexpected for %s to not have an associated FlowNode"), *GetName()), EFlowOnScreenMessageType::Temporary); + // If we could not build the PinValueDataSuppliers array, + // then the pin must be disconnected and have no default value available. + DataPinResult.Result = EFlowDataPinResolveResult::FailedWithError; - return EFlowDataPinResolveResult::FailedWithError; - } + LogError(FString::Printf(TEXT("DataPin named '%s' could not be supplied with a value."), *PinName.ToString()), EFlowOnScreenMessageType::Temporary); - FlowPin = FindFlowPinByName(PinName, FlowNode->GetInputPins()); - if (!FlowPin) - { - return EFlowDataPinResolveResult::FailedMissingPin; + return DataPinResult; } - if (FlowPin->GetPinType() != PinType) + // Iterate over the suppliers in inverse order + for (int32 Index = PinValueSupplierDatas.Num() - 1; Index >= 0; --Index) { - return EFlowDataPinResolveResult::FailedMismatchedType; - } + const FFlowPinValueSupplierData& SupplierData = PinValueSupplierDatas[Index]; - return EFlowDataPinResolveResult::Success; -} + DataPinResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPin(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); -// Must implement TryResolveDataPinAs...() for every EFlowPinType -FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - -template -bool TResolveDataPinWorkingData::TrySetupWorkingData(const FName& PinName, const UFlowNodeBase& FlowNodeBase) -{ - DataPinResult.Result = FlowNodeBase.TryResolveDataPinPrerequisites(PinName, FlowNode, FlowPin, PinType); - if (DataPinResult.Result != EFlowDataPinResolveResult::Success) - { - return false; - } - - if (!FlowNode->TryGetFlowDataPinSupplierDatasForPinName(FlowPin->PinName, PinValueSupplierDatas)) - { - return false; + if (DataPinResult.Result == EFlowDataPinResolveResult::Success) + { + return DataPinResult; + } } - // 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 true; + return DataPinResult; } +// #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_TrySupplyDataPinAsBool(CastChecked(SupplierData.PinValueSupplier), SupplierData.SupplierPinName); - - if (WorkData.DataPinResult.Result == EFlowDataPinResolveResult::Success) - { - return WorkData.DataPinResult; - } - } - - return WorkData.DataPinResult; + FFlowDataPinResult_Bool BoolResolveResult; + BoolResolveResult.Result = TryResolveDataPinValue(PinName, BoolResolveResult.Value); + return BoolResolveResult; } FFlowDataPinResult_Int UFlowNodeBase::TryResolveDataPinAsInt(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; - } - } - - return WorkData.DataPinResult; + 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/FlowDataPinType.cpp b/Source/Flow/Private/Types/FlowDataPinType.cpp deleted file mode 100644 index e8fd948f3..000000000 --- a/Source/Flow/Private/Types/FlowDataPinType.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "Types/FlowDataPinType.h" -#include "Nodes/FlowNodeBase.h" -#include "FlowDataPinSubsystem.h" -#include "FlowLogChannels.h" -#if WITH_EDITOR -#include "PropertyHandle.h" -#endif - -#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinType) - -const FFlowDataPinType* FFlowDataPinType::LookupDataPinType(const FFlowPinTypeName& DataPinTypeName) -{ - const FFlowDataPinType* DataPinType = UFlowDataPinSubsystem::Get()->FindDataPinType(DataPinTypeName); - - if (!DataPinType) - { - UE_LOG(LogFlow, Error, TEXT("Could not find data pin type %s in FlowDataPinSubsystem"), *DataPinTypeName.ToString()); - return nullptr; - } - - return DataPinType; -} - -bool FFlowDataPinType::ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const -{ - Node.LogWarning(FString::Printf(TEXT("Unsupported pin type %s for pin %s"), *GetPinTypeName().ToString(), *PinName.ToString())); - - return false; -} - -bool FFlowDataPinType::PopulateResult(const UFlowNodeBase& Node, const FName& PinName, FFlowDataPinResult& OutResult) const -{ - Node.LogWarning(FString::Printf(TEXT("Unsupported pin type %s for pin %s"), *GetPinTypeName().ToString(), *PinName.ToString())); - - OutResult.Result = EFlowDataPinResolveResult::FailedMissingPin; - - return false; -} - -#if WITH_EDITOR -TSharedPtr FFlowDataPinType::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/FlowDataPinTypeNamesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinTypeNamesStandard.cpp deleted file mode 100644 index 66751657d..000000000 --- a/Source/Flow/Private/Types/FlowDataPinTypeNamesStandard.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "Types/FlowDataPinTypeNamesStandard.h" - -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::UnknownPinTypeName = FName("Unknown"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameExec = FName("Exec"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameBool = FName("Bool"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInt = FName("Int"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInt64 = FName("Int64"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameFloat = FName("Float"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameDouble = FName("Double"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameEnum = FName("Enum"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameName = FName("Name"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameString = FName("String"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameText = FName("Text"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameVector = FName("Vector"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameRotator = FName("Rotator"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameTransform = FName("Transform"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTag = FName("GameplayTag"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTagContainer = FName("GameplayTagContainer"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedStruct = FName("InstancedStruct"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameObject = FName("Object"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedObject = FName("InstancedObject"); -const FFlowPinTypeName FFlowDataPinTypeNamesStandard::ValueTypeNameClass = FName("Class"); \ No newline at end of file diff --git a/Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp deleted file mode 100644 index 164d2cc99..000000000 --- a/Source/Flow/Private/Types/FlowDataPinTypesStandard.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "Types/FlowDataPinTypesStandard.h" - -#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinTypesStandard) diff --git a/Source/Flow/Private/Types/FlowDataPinValue.cpp b/Source/Flow/Private/Types/FlowDataPinValue.cpp index 6fd7f5568..b10c3e206 100644 --- a/Source/Flow/Private/Types/FlowDataPinValue.cpp +++ b/Source/Flow/Private/Types/FlowDataPinValue.cpp @@ -2,18 +2,15 @@ #include "Types/FlowDataPinValue.h" #include "Types/FlowDataPinResults.h" -#include "Types/FlowDataPinType.h" +#include "Types/FlowDataPinValuesStandard.h" +#include "Types/FlowPinType.h" +#include "Nodes/FlowPin.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinValue) -const FFlowDataPinType* FFlowDataPinValue::LookupDataPinType() const -{ - return FFlowDataPinType::LookupDataPinType(GetPinTypeName()); -} +const FString FFlowDataPinValue::StringArraySeparator = TEXT(","); -bool FFlowDataPinValue::PopulateResult(const FProperty* Property, const UObject* Container, FFlowDataPinResult& OutResult) const +const FFlowPinType* FFlowDataPinValue::LookupPinType() const { - OutResult = FFlowDataPinResult(EFlowDataPinResolveResult::FailedMismatchedType); - - return false; -} \ No newline at end of file + return FFlowPinType::LookupPinType(GetPinTypeName()); +} diff --git a/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp index 8907dac89..f074f85e3 100644 --- a/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp +++ b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp @@ -2,9 +2,269 @@ #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() { @@ -18,4 +278,329 @@ void FFlowDataPinValue_Enum::OnEnumNameChanged() } } } -#endif \ No newline at end of file +#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 index d8053d108..be14fb535 100644 --- a/Source/Flow/Public/Asset/FlowAssetParams.h +++ b/Source/Flow/Public/Asset/FlowAssetParams.h @@ -3,7 +3,8 @@ #pragma once #include "Engine/DataAsset.h" -#include "Types/FlowDataPinProperties.h" +#include "Types/FlowNamedDataPinProperty.h" +#include "Interfaces/FlowDataPinValueOwnerInterface.h" #include "Interfaces/FlowDataPinValueSupplierInterface.h" #include "Interfaces/FlowAssetProviderInterface.h" #include "Asset/FlowAssetParamsTypes.h" @@ -16,7 +17,11 @@ class UFlowAsset; * Data asset for storing Flow Graph Start node parameters, supporting external configuration. */ UCLASS(BlueprintType) -class FLOW_API UFlowAssetParams : public UDataAsset, public IFlowDataPinValueSupplierInterface, public IFlowAssetProviderInterface +class FLOW_API UFlowAssetParams + : public UDataAsset + , public IFlowAssetProviderInterface + , public IFlowDataPinValueOwnerInterface + , public IFlowDataPinValueSupplierInterface { GENERATED_BODY() @@ -36,31 +41,20 @@ class FLOW_API UFlowAssetParams : public UDataAsset, public IFlowDataPinValueSup #endif UPROPERTY() - TMap> PropertyMap; + 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_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; // -- // IFlowAssetProviderInterface @@ -80,13 +74,33 @@ class FLOW_API UFlowAssetParams : public UDataAsset, public IFlowDataPinValueSup 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(); - bool TryCheckOutFromSourceControl() const; - EFlowReconcilePropertiesResult CheckForParentCycle() const; void ModifyAndRebuildPropertiesMap(); diff --git a/Source/Flow/Public/Asset/FlowAssetParamsTypes.h b/Source/Flow/Public/Asset/FlowAssetParamsTypes.h index 6d8ecf13d..95e50c7e0 100644 --- a/Source/Flow/Public/Asset/FlowAssetParamsTypes.h +++ b/Source/Flow/Public/Asset/FlowAssetParamsTypes.h @@ -52,7 +52,7 @@ namespace EFlowReconcilePropertiesResult_Classifiers // - ShowCreateNew - Should we show the "Create New" button? // - HideChildParams - When showing a chooser, should we hide "Child" params or not? (Child params have a non-null ParentParams) USTRUCT(BlueprintType) -struct FFlowAssetParamsPtr +struct FLOW_API FFlowAssetParamsPtr { GENERATED_BODY() 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 e001873c9..d4713fac2 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -29,36 +29,6 @@ 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. */ @@ -175,24 +145,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: @@ -211,6 +168,8 @@ class FLOW_API UFlowAsset : public UObject return nullptr; } + + TArray GetAllNodes() const; UFUNCTION(BlueprintPure, Category = "FlowAsset") virtual UFlowNode* GetDefaultEntryNode() const; @@ -218,6 +177,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); @@ -394,9 +356,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 @@ -433,7 +396,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; ////////////////////////////////////////////////////////////////////////// diff --git a/Source/Flow/Public/FlowDataPinSubsystem.h b/Source/Flow/Public/FlowDataPinSubsystem.h deleted file mode 100644 index 5ba3a8558..000000000 --- a/Source/Flow/Public/FlowDataPinSubsystem.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "Subsystems/EngineSubsystem.h" -#include "Types/FlowDataPinType.h" -#include "StructUtils/InstancedStruct.h" -#include "Templates/UnrealTypeTraits.h" - -#include "FlowDataPinSubsystem.generated.h" - -UCLASS(MinimalApi) -class UFlowDataPinSubsystem : public UEngineSubsystem -{ - GENERATED_BODY() - -protected: - UPROPERTY(Transient) - TMap> DataPinTypes; - -public: - FLOW_API static UFlowDataPinSubsystem* Get(); - - virtual void Initialize(FSubsystemCollectionBase& Collection) override; - virtual void Deinitialize() override; - - FLOW_API void RegisterDataPinType(const TInstancedStruct& DataPinType); - FLOW_API void UnregisterDataPinType(const FFlowPinTypeName& TypeName); - - template - const T* FindDataPinType(const FFlowPinTypeName& TypeName) const - { - static_assert(TIsDerivedFrom::IsDerived, "T must be derived from FFlowDataPinType"); - - if (const TInstancedStruct* Found = DataPinTypes.Find(TypeName)) - { - return Found->GetPtr(); - } - - return nullptr; - } - - FLOW_API TArray GetDataPinTypeNames() const; - -protected: - FORCEINLINE void UnregisterAllDataPinTypes() - { - DataPinTypes.Empty(); - } -}; \ 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/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 index dc7f7cff3..3ae3136cc 100644 --- a/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h +++ b/Source/Flow/Public/Interfaces/FlowDataPinValueOwnerInterface.h @@ -3,6 +3,7 @@ #pragma once #include "UObject/Interface.h" +#include "Delegates/Delegate.h" #include "FlowDataPinValueOwnerInterface.generated.h" @@ -20,37 +21,23 @@ class FLOW_API IFlowDataPinValueOwnerInterface public: #if WITH_EDITOR - + // Determines if the pin's type properties (bIsInputPin, MultiType) can be modified - virtual bool CanModifyFlowDataPinValueType() const { return true; } + virtual bool CanModifyFlowDataPinType() const { return true; } // Determines if the bIsInputPin checkbox should be visible in the Details panel virtual bool ShowFlowDataPinValueInputPinCheckbox() const { return true; } - // -------------------------------------------------------------------- - // Class / Enum source visibility & edit policies - // - // These are value-level UI policy hooks (mirroring ShowFlowDataPinValueInputPinCheckbox / CanModifyFlowDataPinValueType) - // to allow higher-level owners (e.g., nodes, assets) to centrally control: - // * Whether the ClassFilter (or Enum source) row is shown at all - // * Whether the "Lock" toggle is shown - // * Whether the ClassFilter / Enum source itself is editable - // - // NOTE: - // - 'Value' may be nullptr if not derivable in a specific context; implementers should null-guard if they inspect it. - // - The per-value struct's own bLockClassFilter (editor-only) is applied AFTER these policies - // and AFTER metadata-based forcing (e.g., MetaClass), to produce final editability. - // - These methods intentionally share naming so the same logic can gate both ClassFilter and Enum source (EnumClass / EnumName). - // -------------------------------------------------------------------- - - // Should the ClassFilter (or analogous Enum source) row be visible? + // Should the ClassFilter or EnumClass row be visible? virtual bool ShowFlowDataPinValueClassFilter(const FFlowDataPinValue* Value) const { return true; } - // Should the "Lock Class Filter" toggle (bLockClassFilter) be visible? - // (If false, the checkbox is hidden; its stored value may still disable editing if already true.) - virtual bool ShowFlowDataPinValueClassFilterLockToggle(const FFlowDataPinValue* Value) const { return true; } - - // Base policy for whether the ClassFilter / Enum source can be edited (before per-value lock flag). + // 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 -}; \ No newline at end of file +}; 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/Nodes/Actor/FlowNode_ExecuteComponent.h b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h index a4d0f33b5..6d19c1099 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_ExecuteComponent.h @@ -63,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 @@ -95,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; @@ -113,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 45356e8ac..307ba79d5 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -11,6 +11,7 @@ #include "FlowTags.h" // used by subclasses #include "FlowTypes.h" #include "Types/FlowDataPinResults.h" +#include "Types/FlowPinTypeTemplates.h" #include "FlowNodeBase.generated.h" @@ -22,6 +23,7 @@ class UEdGraphNode; 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 */ @@ -225,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; + + // 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; - UFUNCTION(BlueprintCallable, Category = DataPins, DisplayName = "Try Resolve DataPin As Bool") +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: @@ -334,6 +375,8 @@ class FLOW_API UFlowNodeBase void RequestReconstruction() const { (void) OnReconstructionRequested.ExecuteIfBound(); }; + void SetCanDelete(bool CanDelete) { bCanDelete = CanDelete;} + #endif protected: @@ -365,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) @@ -424,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 c40ed2c69..2d5d21bb8 100644 --- a/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h +++ b/Source/Flow/Public/Nodes/Graph/FlowNode_DefineProperties.h @@ -2,10 +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" @@ -13,9 +12,8 @@ * 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 +class FLOW_API UFlowNode_DefineProperties : public UFlowNode - , public IFlowDataPinGeneratorNodeInterface , public IFlowNamedPropertiesSupplierInterface { GENERATED_UCLASS_BODY() @@ -27,6 +25,8 @@ class FLOW_API UFlowNode_DefineProperties TArray NamedProperties; public: + virtual void PostLoad() override; + #if WITH_EDITOR // IFlowContextPinSupplierInterface virtual bool SupportsContextPins() const override { return Super::SupportsContextPins() || !NamedProperties.IsEmpty(); } @@ -36,8 +36,8 @@ class FLOW_API UFlowNode_DefineProperties 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 @@ -48,9 +48,9 @@ class FLOW_API UFlowNode_DefineProperties 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 24cbed357..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,7 +10,7 @@ * 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() @@ -76,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; @@ -91,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 75407e7a9..a2780a353 100644 --- a/Source/Flow/Public/Types/FlowDataPinProperties.h +++ b/Source/Flow/Public/Types/FlowDataPinProperties.h @@ -8,12 +8,16 @@ #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() @@ -21,118 +25,10 @@ struct 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; - } - - return StructProperty->Struct; - } -#endif -}; - -// Wrapper for FFlowDataPinProperty that is used for flow nodes that add -// dynamic properties, with associated data pins, on the flow node instance -// (as opposed to C++ or blueprint compile-time). -USTRUCT(BlueprintType, DisplayName = "Flow Named DataPin Property") -struct FFlowNamedDataPinProperty -{ - GENERATED_BODY() - -public: - // Name of this instanced property - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins, meta = (EditCondition = "bMayChangeNameAndType", HideEditConditionToggle)) - FName Name = NAME_None; - - // DataPinProperty payload - UPROPERTY(EditAnywhere, Category = DataPins, meta = (ExcludeBaseStruct, NoClear)) - TInstancedStruct DataPinProperty; - -#if WITH_EDITORONLY_DATA - // Unique identifier for property tracking - UPROPERTY() - FGuid Guid = FGuid::NewGuid(); - - // Tracks if this property overrides its super (auto-clears if matches super) - UPROPERTY() - bool bIsOverride = false; - - // TODO (gtaylor) Does not currently police the type, - // because that prevents the instanced struct contents being edited as well, - // which is not what we want from this feature. - // Will try to fix next pass on the details customization. - UPROPERTY() - bool bMayChangeNameAndType = true; -#endif - -public: - FFlowNamedDataPinProperty() = default; - - bool IsValid() const { return Name != NAME_None && DataPinProperty.GetPtr() != nullptr; } - - bool IsInputProperty() const; - bool IsOutputProperty() const; - -#if WITH_EDITOR - FFlowPin CreateFlowPin() const { return FFlowDataPinProperty::CreateFlowPin(Name, DataPinProperty); } - - FLOW_API FText BuildHeaderText() const; - - void ConfigureForFlowAssetParams() - { - bIsOverride = false; - bMayChangeNameAndType = false; - } - - void ConfigureForFlowAssetStartNode() - { - bIsOverride = false; - bMayChangeNameAndType = true; - } - - static void ConfigurePropertiesForFlowAssetParams(TArray& MutableProperties) - { - for (FFlowNamedDataPinProperty& Property : MutableProperties) - { - Property.ConfigureForFlowAssetParams(); - } - } - static void ConfigurePropertiesForFlowAssetStartNode(TArray& MutableProperties) - { - for (FFlowNamedDataPinProperty& Property : MutableProperties) - { - Property.ConfigureForFlowAssetStartNode(); - } - } - -#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() @@ -146,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() @@ -165,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() @@ -184,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() @@ -203,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() @@ -222,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() @@ -241,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() @@ -260,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() @@ -279,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() @@ -314,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() @@ -338,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() @@ -357,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() @@ -376,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() @@ -395,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() @@ -414,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() @@ -433,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). @@ -458,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(); @@ -470,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(); @@ -509,202 +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(); } + 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/FlowDataPinType.h b/Source/Flow/Public/Types/FlowDataPinType.h deleted file mode 100644 index 5eff6cf26..000000000 --- a/Source/Flow/Public/Types/FlowDataPinType.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "FlowDataPinTypeNamesStandard.h" - -#include "UObject/NameTypes.h" -#include "Math/Color.h" -#if WITH_EDITOR -#include "GraphEditorSettings.h" -#endif - -#include "FlowDataPinType.generated.h" - -class FFormatArgumentValue; -class IPropertyHandle; -class UFlowNodeBase; -struct FFlowDataPinResult; - -USTRUCT(BlueprintType) -struct FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual ~FFlowDataPinType() {} - - // Lookup a registered type by name - FLOW_API static const FFlowDataPinType* LookupDataPinType(const FFlowPinTypeName& DataPinTypeName); - - // Identity - FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const PURE_VIRTUAL(GetPinTypeName, return FFlowDataPinTypeNamesStandard::UnknownPinTypeName;) - - // Category / Subcategory - FLOW_API virtual FName GetPinCategory() const PURE_VIRTUAL(GetPinCategory, return NAME_None;) - FLOW_API virtual UObject* GetSubCategoryObject() const { return nullptr; } - - // Value resolution - FLOW_API virtual bool ResolveAndFormatPinValue(const UFlowNodeBase& Node, const FName& PinName, FFormatArgumentValue& OutValue) const; - FLOW_API virtual bool PopulateResult(const UFlowNodeBase& Node, const FName& PinName, 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; -#endif -}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinTypeNamesStandard.h b/Source/Flow/Public/Types/FlowDataPinTypeNamesStandard.h deleted file mode 100644 index 935df91b0..000000000 --- a/Source/Flow/Public/Types/FlowDataPinTypeNamesStandard.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "UObject/NameTypes.h" -#include "FlowDataPinTypeName.h" - -struct FFlowDataPinTypeNamesStandard -{ - // Other Standard Pin Types - static const FFlowPinTypeName UnknownPinTypeName; - static const FFlowPinTypeName ValueTypeNameExec; - - // "Standard" Data Pin Types - static const FFlowPinTypeName ValueTypeNameBool; - static const FFlowPinTypeName ValueTypeNameInt; - static const FFlowPinTypeName ValueTypeNameInt64; - static const FFlowPinTypeName ValueTypeNameFloat; - static const FFlowPinTypeName ValueTypeNameDouble; - static const FFlowPinTypeName ValueTypeNameEnum; - static const FFlowPinTypeName ValueTypeNameName; - static const FFlowPinTypeName ValueTypeNameString; - static const FFlowPinTypeName ValueTypeNameText; - static const FFlowPinTypeName ValueTypeNameVector; - static const FFlowPinTypeName ValueTypeNameRotator; - static const FFlowPinTypeName ValueTypeNameTransform; - static const FFlowPinTypeName ValueTypeNameGameplayTag; - static const FFlowPinTypeName ValueTypeNameGameplayTagContainer; - static const FFlowPinTypeName ValueTypeNameInstancedStruct; - static const FFlowPinTypeName ValueTypeNameObject; - static const FFlowPinTypeName ValueTypeNameInstancedObject; - static const FFlowPinTypeName ValueTypeNameClass; -}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinTypesStandard.h b/Source/Flow/Public/Types/FlowDataPinTypesStandard.h deleted file mode 100644 index c61a54f55..000000000 --- a/Source/Flow/Public/Types/FlowDataPinTypesStandard.h +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "Types/FlowDataPinType.h" -#include "Types/FlowDataPinTypeNamesStandard.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 "FlowDataPinTypesStandard.generated.h" - -/* - * Flow Data Pin Type Declarations (State-less) - * - * NOTE: - * - Per latest design update: Pin subcategory objects (enum assets, class filters, etc.) - * are NO LONGER stored on the FFlowDataPinType subclasses. - * Those objects now belong exclusively to the FFlowDataPinProperty subclasses (or pin instance metadata). - * - These type classes now only describe the stable identity: - * * PinTypeName - * * PinCategory - * * (Optional) Color override matching editor schema expectations - * - GetSubCategoryObject() is left as the base implementation (returns nullptr) for all types. - * The pin/property layer is responsible for assigning PinSubCategoryObject (e.g. enum asset, class filter, struct type). - * - Specialized struct coloring (Vector/Rotator/Transform) relies on the pin property assigning the correct - * PinSubCategoryObject (TBaseStructure<...>::Get()) during pin setup - * (see FFlowPin::TrySetStructSubCategoryObjectFromPinType()). - */ - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Exec : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameExec; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Exec; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->ExecutionPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Bool : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameBool; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Boolean; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->BooleanPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Int : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInt; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Int; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->IntPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Int64 : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInt64; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Int64; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->Int64PinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Float : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameFloat; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Float; } -#if WITH_EDITOR - // Using default color; adjust if a distinct Float color is later exposed. - virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Double : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - // Shares Float category (no separate Double category in schema) - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameDouble; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Float; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Name : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameName; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Name; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->NamePinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_String : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameString; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_String; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->StringPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Text : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameText; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Text; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->TextPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Enum : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - // EnumClass removed per new design (belongs on property / pin instance) - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameEnum; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Enum; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->DefaultPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Vector : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - // SubCategoryObject (FVector struct) supplied by property/pin logic now - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameVector; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->VectorPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Rotator : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameRotator; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->RotatorPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Transform : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameTransform; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->TransformPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_GameplayTag : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTag; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_GameplayTagContainer : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTagContainer; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_InstancedStruct : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedStruct; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Struct; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->StructPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Object : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - // ClassFilter removed; belongs on property - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameObject; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Object; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->ObjectPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_InstancedObject : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - // Still uses Object category - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedObject; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Object; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->ObjectPinTypeColor; } -#endif -}; - -USTRUCT(BlueprintType) -struct FFlowDataPinType_Class : public FFlowDataPinType -{ - GENERATED_BODY() - -public: - // MetaClassFilter removed; belongs on property - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameClass; } - virtual FName GetPinCategory() const override { return FFlowPin::PC_Class; } -#if WITH_EDITOR - virtual FLinearColor GetPinColor() const override { return GetDefault()->ClassPinTypeColor; } -#endif -}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinValue.h b/Source/Flow/Public/Types/FlowDataPinValue.h index d86781dcd..ba53deb9f 100644 --- a/Source/Flow/Public/Types/FlowDataPinValue.h +++ b/Source/Flow/Public/Types/FlowDataPinValue.h @@ -3,17 +3,18 @@ #pragma once #include "FlowPinEnums.h" -#include "FlowDataPinTypeNamesStandard.h" +#include "FlowPinType.h" #include "UObject/NameTypes.h" #include "UObject/ObjectPtr.h" +#include "StructUtils/InstancedStruct.h" #include "FlowDataPinValue.generated.h" struct FFlowDataPinResult; -struct FFlowDataPinType; class FProperty; class UObject; class IPropertyHandle; +class UScriptStruct; USTRUCT() struct FFlowDataPinValue @@ -22,9 +23,12 @@ struct FFlowDataPinValue friend class FFlowDataPinValueCustomization; - typedef void FValueType; +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; -protected: #if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, Category = DataPins) bool bIsInputPin = false; @@ -33,7 +37,6 @@ struct FFlowDataPinValue EFlowDataMultiType MultiType = EFlowDataMultiType::Single; #endif -public: FFlowDataPinValue() {} virtual ~FFlowDataPinValue() {} @@ -42,19 +45,20 @@ struct FFlowDataPinValue 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 { return nullptr; } - - // Optional SubCategory object source (now moved off the Type class). - // Implementations can return e.g. Enum asset, struct UScriptStruct, etc. Default: nullptr. - FLOW_API virtual UObject* GetSubCategoryObject() const { return nullptr; } + 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 FFlowDataPinTypeNamesStandard::UnknownPinTypeName;) + 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 FFlowDataPinType* LookupDataPinType() const; + FLOW_API const FFlowPinType* LookupPinType() const; - // Populate a result object from this value (property + container context) - FLOW_API bool PopulateResult(const FProperty* Property, const UObject* Container, FFlowDataPinResult& OutResult) const; + FLOW_API static const FString StringArraySeparator; }; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinValueTemplates.h b/Source/Flow/Public/Types/FlowDataPinValueTemplates.h deleted file mode 100644 index 0e3a543db..000000000 --- a/Source/Flow/Public/Types/FlowDataPinValueTemplates.h +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "UObject/NameTypes.h" -#include "Types/FlowPinEnums.h" -#include "Types/FlowDataPinValue.h" -#include "Types/FlowDataPinResults.h" - -#include - -struct FFlowDataPinResult; - -struct FFlowDataPinValueTemplates -{ -public: -// TODO (gtaylor) adapt these for the data pins refactor -// -// // Generic template for single value extraction using access lambda -// template -// static bool TryGetFirstValue(const FFlowDataPinResult& Result, const FName& ValueTypeName, TInner& OutFirstValue, TAccessFunc AccessFunc) -// { -// if (Result.Result != EFlowDataPinResolveResult::Success || -// Result.ValueArray.IsEmpty() || -// ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) -// { -// return false; -// } -// -// const TFlowDataPinValueSubclass* TypedSubclass = Result.ValueArray[0].GetPtr(); -// AccessFunc(TypedSubclass, OutFirstValue); -// return true; -// } -// -// // Generic template for array value extraction using access lambda -// template -// static bool TryGetArrayValues(const FFlowDataPinResult& Result, const FName& ValueTypeName, TArray& OutArrayValues, TAccessFunc AccessFunc) -// { -// check(OutArrayValues.IsEmpty()); -// -// if (Result.Result != EFlowDataPinResolveResult::Success) -// { -// return false; -// } -// -// if (Result.ValueArray.IsEmpty()) -// { -// return true; -// } -// -// if (ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) -// { -// return false; -// } -// -// OutArrayValues.Reserve(Result.ValueArray.Num()); -// -// for (const TInstancedStruct& DataPinValue : Result.ValueArray) -// { -// checkf(DataPinValue.Get().GetPinTypeName() == ValueTypeName, "These arrays must be homogenous"); -// -// const TFlowDataPinValueSubclass* TypedSubclass = DataPinValue.GetPtr(); -// TInner Value; -// AccessFunc(TypedSubclass, Value); -// OutArrayValues.Add(Value); -// } -// -// return true; -// } -// -// // Template for single enum value extraction -// template -// static bool TryGetFirstEnumValue(const FFlowDataPinResult& Result, const FName& ValueTypeName, FName& OutFirstEnumValueName, UEnum*& OutEnumClass, TAccessFunc AccessFunc) -// { -// if (Result.Result != EFlowDataPinResolveResult::Success || -// Result.ValueArray.IsEmpty() || -// ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) -// { -// return false; -// } -// -// const FFlowDataPinValue_Enum* TypedSubclass = Result.ValueArray[0].GetPtr(); -// AccessFunc(TypedSubclass, OutFirstEnumValueName, OutEnumClass); -// return true; -// } -// -// // Template for enum array value extraction -// template -// static bool TryGetEnumArrayValues(const FFlowDataPinResult& Result, const FName& ValueTypeName, TArray& OutEnumValueNames, UEnum*& OutEnumClass, TAccessFunc AccessFunc) -// { -// check(OutEnumValueNames.IsEmpty()); -// check(OutEnumClass = nullptr); -// -// if (Result.Result != EFlowDataPinResolveResult::Success) -// { -// return false; -// } -// -// if (Result.ValueArray.IsEmpty()) -// { -// return true; -// } -// -// if (ValueTypeName != Result.ValueArray[0].Get().GetPinTypeName()) -// { -// return false; -// } -// -// const FFlowDataPinValue_Enum* FirstTypedSubclass = Result.ValueArray[0].GetPtr(); -// OutEnumClass = FirstTypedSubclass->EnumClass; -// -// OutEnumValueNames.Reserve(Result.ValueArray.Num()); -// -// for (const TInstancedStruct& DataPinValue : Result.ValueArray) -// { -// checkf(DataPinValue.Get().GetPinTypeName() == ValueTypeName, "These arrays must be homogenous"); -// -// const FFlowDataPinValue_Enum* TypedSubclass = DataPinValue.GetPtr(); -// checkf(TypedSubclass->EnumClass == OutEnumClass, "Enum arrays must use the same EnumClass"); -// -// FName Value; -// AccessFunc(TypedSubclass, Value, OutEnumClass); -// OutEnumValueNames.Add(Value); -// } -// -// return true; -// } -}; \ No newline at end of file diff --git a/Source/Flow/Public/Types/FlowDataPinValuesStandard.h b/Source/Flow/Public/Types/FlowDataPinValuesStandard.h index 619620465..7a788f09b 100644 --- a/Source/Flow/Public/Types/FlowDataPinValuesStandard.h +++ b/Source/Flow/Public/Types/FlowDataPinValuesStandard.h @@ -3,7 +3,8 @@ #pragma once #include "Types/FlowDataPinValue.h" -#include "Types/FlowDataPinTypeNamesStandard.h" +#include "Types/FlowPinTypesStandard.h" +#include "Types/FlowArray.h" #include "StructUtils/InstancedStruct.h" #include "GameplayTagContainer.h" @@ -16,432 +17,466 @@ #include "FlowDataPinValuesStandard.generated.h" +//====================================================================== // Bool -USTRUCT(BlueprintType, DisplayName = "Bool - Flow DataPin Value", meta = (FlowPinType = "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: - typedef bool FValueType; + using PinType = FFlowPinType_Bool; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray Values{ false }; - FFlowDataPinValue_Bool() {} - FFlowDataPinValue_Bool(bool InValue) : Values({ InValue }) {} - FFlowDataPinValue_Bool(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameBool; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // Int (int32) -USTRUCT(BlueprintType, DisplayName = "Int - Flow DataPin Value", meta = (FlowPinType = "Int")) +//====================================================================== +USTRUCT(MinimalApi, BlueprintType, DisplayName = "Int - Flow DataPin Value", meta = (FlowPinType = "Int", HasNativeMake = "/Script/Flow.FlowDataPinBlueprintLibrary.MakeStructInt")) struct FFlowDataPinValue_Int : public FFlowDataPinValue { GENERATED_BODY() public: - typedef int32 FValueType; + using PinType = FFlowPinType_Int; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray Values{ 0 }; - FFlowDataPinValue_Int() {} - FFlowDataPinValue_Int(FValueType InValue) : Values({ InValue }) {} - FFlowDataPinValue_Int(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameInt; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // Int64 -USTRUCT(BlueprintType, DisplayName = "Int64 - Flow DataPin Value", meta = (FlowPinType = "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: - typedef int64 FValueType; + using PinType = FFlowPinType_Int64; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray Values{ 0 }; - FFlowDataPinValue_Int64() {} - FFlowDataPinValue_Int64(int64 InValue) : Values({ InValue }) {} - FFlowDataPinValue_Int64(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameInt64; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // Float -USTRUCT(BlueprintType, DisplayName = "Float - Flow DataPin Value", meta = (FlowPinType = "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: - typedef float FValueType; + using PinType = FFlowPinType_Float; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values{ 0.f }; + TArray Values{ 0.0f }; - FFlowDataPinValue_Float() {} - FFlowDataPinValue_Float(float InValue) : Values({ InValue }) {} - FFlowDataPinValue_Float(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameFloat; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // Double -USTRUCT(BlueprintType, DisplayName = "Double - Flow DataPin Value", meta = (FlowPinType = "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: - typedef double FValueType; + using PinType = FFlowPinType_Double; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray Values{ 0.0 }; - FFlowDataPinValue_Double() {} - FFlowDataPinValue_Double(double InValue) : Values({ InValue }) {} - FFlowDataPinValue_Double(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameDouble; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; -// Enum -USTRUCT(BlueprintType, DisplayName = "Enum - Flow DataPin Value", meta = (FlowPinType = "Enum")) -struct FFlowDataPinValue_Enum : public FFlowDataPinValue +//====================================================================== +// 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: - typedef FName FValueType; + using PinType = FFlowPinType_Name; + 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; + TArray Values{ NAME_None }; -#if WITH_EDITORONLY_DATA - // Native C++ enum name (advanced) - UPROPERTY(EditAnywhere, Category = DataPins, meta = (AdvancedDisplay)) - FString EnumName; - - // Lock source (asset/name). Does NOT lock enumerator selection. - UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Enum Class")) - bool bLockEnumClass = false; -#endif - - FFlowDataPinValue_Enum() {} - FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, FName InValue) - : Values({ InValue }), EnumClass(InEnumClass) { - } - FFlowDataPinValue_Enum(const TSoftObjectPtr& InEnumClass, const TArray& InValues) - : Values(InValues), EnumClass(InEnumClass) { - } + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameEnum; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return EnumClass.Get(); } - - FLOW_API void OnEnumNameChanged(); -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; -// Name -USTRUCT(BlueprintType, DisplayName = "Name - Flow DataPin Value", meta = (FlowPinType = "Name")) -struct FFlowDataPinValue_Name : public FFlowDataPinValue +//====================================================================== +// 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: - typedef FName FValueType; + using PinType = FFlowPinType_String; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values; + TArray Values; - FFlowDataPinValue_Name() {} - FFlowDataPinValue_Name(const FName& InValue) : Values({ InValue }) {} - FFlowDataPinValue_Name(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameName; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; -// String -USTRUCT(BlueprintType, DisplayName = "String - Flow DataPin Value", meta = (FlowPinType = "String")) -struct FFlowDataPinValue_String : public FFlowDataPinValue +//====================================================================== +// 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: - typedef FString FValueType; + using PinType = FFlowPinType_Text; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values; + TArray Values; - FFlowDataPinValue_String() {} - FFlowDataPinValue_String(const FString& InValue) : Values({ InValue }) {} - FFlowDataPinValue_String(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameString; } + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; -// Text -USTRUCT(BlueprintType, DisplayName = "Text - Flow DataPin Value", meta = (FlowPinType = "Text")) -struct FFlowDataPinValue_Text : public FFlowDataPinValue +//====================================================================== +// 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: - typedef FText FValueType; + using PinType = FFlowPinType_Enum; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values; + 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; + } - FFlowDataPinValue_Text() {} - FFlowDataPinValue_Text(const FText& InValue) : Values({ InValue }) {} - FFlowDataPinValue_Text(const TArray& InValues) : Values(InValues) {} + template + EFlowDataPinResolveResult TryGetAllNativeEnumValues(TArray& OutEnumValues, EGetByNameFlags GetByNameFlags = EGetByNameFlags::ErrorIfNotFound) const + { + if (Values.IsEmpty()) + { + return EFlowDataPinResolveResult::FailedInsufficientValues; + } + + UEnum* EnumClassPtr = EnumClass.LoadSynchronous(); + OutEnumValues.Reserve(Values.Num()); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameText; } + for (const ValueType& ValueName : Values) + { + TUnrealNativeEnumType EnumValue; + if (!TryGetEnumValueByName(EnumClassPtr, ValueName, EnumValue, GetByNameFlags)) + { + return EFlowDataPinResolveResult::FailedUnknownEnumValue; + } + OutEnumValues.Add(EnumValue); + } + return EFlowDataPinResolveResult::Success; + } }; +//====================================================================== // Vector -USTRUCT(BlueprintType, DisplayName = "Vector - Flow DataPin Value", meta = (FlowPinType = "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: - typedef FVector FValueType; + using PinType = FFlowPinType_Vector; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values; + TArray Values{ FVector::ZeroVector }; - FFlowDataPinValue_Vector() {} - FFlowDataPinValue_Vector(const FVector& InValue) : Values({ InValue }) {} - FFlowDataPinValue_Vector(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameVector; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // Rotator -USTRUCT(BlueprintType, DisplayName = "Rotator - Flow DataPin Value", meta = (FlowPinType = "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: - typedef FRotator FValueType; + using PinType = FFlowPinType_Rotator; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values; + TArray Values{ FRotator::ZeroRotator }; - FFlowDataPinValue_Rotator() {} - FFlowDataPinValue_Rotator(const FRotator& InValue) : Values({ InValue }) {} - FFlowDataPinValue_Rotator(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameRotator; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // Transform -USTRUCT(BlueprintType, DisplayName = "Transform - Flow DataPin Value", meta = (FlowPinType = "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: - typedef FTransform FValueType; + using PinType = FFlowPinType_Transform; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values; + TArray Values{ FTransform::Identity }; - FFlowDataPinValue_Transform() {} - FFlowDataPinValue_Transform(const FTransform& InValue) : Values({ InValue }) {} - FFlowDataPinValue_Transform(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameTransform; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // GameplayTag -USTRUCT(BlueprintType, DisplayName = "GameplayTag - Flow DataPin Value", meta = (FlowPinType = "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: - typedef FGameplayTag FValueType; + using PinType = FFlowPinType_GameplayTag; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray Values; - FFlowDataPinValue_GameplayTag() {} - FFlowDataPinValue_GameplayTag(const FGameplayTag& InValue) : Values({ InValue }) {} - FFlowDataPinValue_GameplayTag(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTag; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // GameplayTagContainer -USTRUCT(BlueprintType, DisplayName = "GameplayTagContainer - Flow DataPin Value", meta = (FlowPinType = "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: - typedef FGameplayTagContainer FValueType; + using PinType = FFlowPinType_GameplayTagContainer; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) - TArray Values; + FGameplayTagContainer Values; - FFlowDataPinValue_GameplayTagContainer() {} - FFlowDataPinValue_GameplayTagContainer(const FGameplayTagContainer& InValue) : Values({ InValue }) {} - FFlowDataPinValue_GameplayTagContainer(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameGameplayTagContainer; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; +//====================================================================== // InstancedStruct -USTRUCT(BlueprintType, DisplayName = "InstancedStruct - Flow DataPin Value", meta = (FlowPinType = "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: - typedef FInstancedStruct FValueType; + using PinType = FFlowPinType_InstancedStruct; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray Values; - FFlowDataPinValue_InstancedStruct() {} - FFlowDataPinValue_InstancedStruct(const FInstancedStruct& InValue) : Values({ InValue }) {} - FFlowDataPinValue_InstancedStruct(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedStruct; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return TBaseStructure::Get(); } -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } }; -// Object (reference) -USTRUCT(BlueprintType, DisplayName = "Object - Flow DataPin Value", meta = (FlowPinType = "Object")) +//====================================================================== +// 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: - typedef UObject* FValueType; + using PinType = FFlowPinType_Object; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray> Values; #if WITH_EDITORONLY_DATA - // Class filter (advanced) UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) TObjectPtr ClassFilter = UObject::StaticClass(); - - UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Class Filter")) - bool bLockClassFilter = false; #endif - FFlowDataPinValue_Object() {} - FFlowDataPinValue_Object(UObject* InObject) - { - if (InObject) - { - Values.Add(InObject); - } - } - FFlowDataPinValue_Object(const TArray& InObjects) - { - for (UObject* Obj : InObjects) - { - if (Obj) - { - Values.Add(Obj); - } - } - } + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameObject; } - -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return ClassFilter; } -#endif -}; - -// InstancedObject (inline / instanced editing) -USTRUCT(BlueprintType, DisplayName = "InstancedObject - Flow DataPin Value", meta = (FlowPinType = "InstancedObject")) -struct FFlowDataPinValue_InstancedObject : public FFlowDataPinValue -{ - GENERATED_BODY() - -public: - typedef UObject* FValueType; - - UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = DataPins) - TArray> Values; - -#if WITH_EDITORONLY_DATA - UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) - TObjectPtr ClassFilter = UObject::StaticClass(); - - UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Class Filter")) - bool bLockClassFilter = false; -#endif - - FFlowDataPinValue_InstancedObject() {} - FFlowDataPinValue_InstancedObject(UObject* InObject) - { - if (InObject) - { - Values.Add(InObject); - } - } - FFlowDataPinValue_InstancedObject(const TArray& InObjects) - { - for (UObject* Obj : InObjects) - { - if (Obj) - { - Values.Add(Obj); - } - } - } - - virtual const FFlowPinTypeName& GetPinTypeName() const override { return FFlowDataPinTypeNamesStandard::ValueTypeNameInstancedObject; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return ClassFilter; } -#endif + virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + virtual bool TryConvertValuesToString(FString& OutString) const override; }; -// Class (soft class paths) -USTRUCT(BlueprintType, DisplayName = "Class - Flow DataPin Value", meta = (FlowPinType = "Class")) +//====================================================================== +// 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: - typedef UClass* FValueType; + using PinType = FFlowPinType_Class; + using ValueType = PinType::ValueType; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = DataPins) TArray Values; @@ -449,17 +484,14 @@ struct FFlowDataPinValue_Class : public FFlowDataPinValue #if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, Category = DataPins, meta = (AllowAbstract, AdvancedDisplay)) TObjectPtr ClassFilter = UObject::StaticClass(); - - UPROPERTY(EditAnywhere, Category = DataPins, meta = (DisplayName = "Lock Class Filter")) - bool bLockClassFilter = false; #endif - FFlowDataPinValue_Class() {} - FFlowDataPinValue_Class(const FSoftClassPath& InPath) : Values({ InPath }) {} - FFlowDataPinValue_Class(const TArray& InValues) : Values(InValues) {} + 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 FFlowDataPinTypeNamesStandard::ValueTypeNameClass; } -#if WITH_EDITOR - virtual UObject* GetSubCategoryObject() const override { return ClassFilter; } -#endif + 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 9577d5248..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, @@ -94,6 +97,25 @@ enum class EFlowDataPinResolveResult : uint8 }; 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 { @@ -106,4 +128,76 @@ enum class EFlowDataMultiType : uint8 Invalid UMETA(Hidden), Min = 0 UMETA(Hidden), }; -FLOW_ENUM_RANGE_VALUES(EFlowDataMultiType) \ No newline at end of file +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/FlowDataPinTypeName.h b/Source/Flow/Public/Types/FlowPinTypeName.h similarity index 72% rename from Source/Flow/Public/Types/FlowDataPinTypeName.h rename to Source/Flow/Public/Types/FlowPinTypeName.h index dcd5b4291..1531c289c 100644 --- a/Source/Flow/Public/Types/FlowDataPinTypeName.h +++ b/Source/Flow/Public/Types/FlowPinTypeName.h @@ -4,7 +4,7 @@ #include "UObject/NameTypes.h" -#include "FlowDataPinTypeName.generated.h" +#include "FlowPinTypeName.generated.h" USTRUCT(BlueprintType) struct FFlowPinTypeName @@ -16,7 +16,9 @@ struct FFlowPinTypeName FName Name = NAME_None; FFlowPinTypeName() = default; - FFlowPinTypeName(const FName& InName) : Name(InName) {} + 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) { 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 index ccc3d0612..c6054fe73 100644 --- a/Source/Flow/Public/Types/FlowStructUtils.h +++ b/Source/Flow/Public/Types/FlowStructUtils.h @@ -26,5 +26,77 @@ namespace FlowStructUtils 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/FlowEditor.Build.cs b/Source/FlowEditor/FlowEditor.Build.cs index 86bf67a09..6bca53f9a 100644 --- a/Source/FlowEditor/FlowEditor.Build.cs +++ b/Source/FlowEditor/FlowEditor.Build.cs @@ -61,6 +61,9 @@ public FlowEditor(ReadOnlyTargetRules target) : base(target) "Slate", "SlateCore", "SourceControl", + // #UE56Fix + //"StructUtils", + // "ToolMenus", "UnrealEd" }); diff --git a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp index df88d22cb..ce1ef302e 100644 --- a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp +++ b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAsset.cpp @@ -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 index c42d98ea7..1c9748aaa 100644 --- a/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp +++ b/Source/FlowEditor/Private/Asset/AssetDefinition_FlowAssetParams.cpp @@ -5,7 +5,7 @@ #include "FlowAsset.h" #include "FlowEditorLogChannels.h" #include "FlowEditorModule.h" -#include "Types/FlowDataPinProperties.h" +#include "Types/FlowDataPinValuesStandard.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetToolsModule.h" #include "ContentBrowserMenuContexts.h" diff --git a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp index 7871b3514..270eab9ac 100644 --- a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp +++ b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp @@ -1,4 +1,4 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Asset/FlowObjectDiff.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/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 index 319e0f16d..db083ef78 100644 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization.cpp @@ -8,27 +8,30 @@ #include "IDetailPropertyRow.h" #include "Interfaces/FlowDataPinValueOwnerInterface.h" #include "IPropertyUtilities.h" -#include "PropertyCustomizationHelpers.h" #include "ScopedTransaction.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SComboBox.h" -#include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" -#include "Styling/AppStyle.h" #include "FlowEditorLogChannels.h" -#include "DetailCustomizations/FlowValueSourcePolicy.h" #include "Types/FlowDataPinValuesStandard.h" +#include "UnrealExtensions/VisibilityArrayBuilder.h" +#include "UObject/EnumProperty.h" #define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization" -static const FText MultiTypeTooltip = LOCTEXT("MultiTypeTooltip", - "Select whether this Data Pin holds a Single value or an Array of values.\n" - "Changing from Array to Single will trim the array to keep only the first element."); +static const TCHAR HiddenMeta[] = TEXT("Hidden"); -static const FText InputPinTooltip = LOCTEXT("InputPinTooltip", - "Marks this Data Pin as an Input.\n" - "When checked, the value is expected to be provided externally (upstream / user).\n" - "When unchecked, the pin is treated as an Output / internally produced value."); +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() { @@ -43,37 +46,95 @@ void FFlowDataPinValueCustomization::CustomizeHeader(TSharedRef CacheHandles(InStructPropertyHandle, StructCustomizationUtils); CacheOwnerInterface(); + CacheArraySupported(); - if (MultiTypeOptions.Num() == 0) + // Populate MultiTypeOptions from enum (respect bArraySupported) + MultiTypeOptions.Reset(); + if (const UEnum* MultiTypeEnum = StaticEnum()) { - MultiTypeOptions.Add(MakeShareable(new FString("Single"))); - MultiTypeOptions.Add(MakeShareable(new FString("Array"))); + 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)))); + } } - FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + // 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(); - SelectedMultiType = MultiTypeOptions[CurrentType == EFlowDataMultiType::Single ? 0 : 1]; + for (auto& Opt : MultiTypeOptions) + { + if (Opt.IsValid() && static_cast(*Opt) == CurrentType) + { + SelectedMultiType = Opt; + break; + } + } - TSharedRef HeaderBox = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(MultiTypeComboBox, SComboBox>) - .OptionsSource(&MultiTypeOptions) - .OnGenerateWidget(this, &FFlowDataPinValueCustomization::GenerateMultiTypeWidget) - .OnSelectionChanged(this, &FFlowDataPinValueCustomization::OnMultiTypeChanged) - .IsEnabled(this, &FFlowDataPinValueCustomization::GetInputPinCheckboxEnabled) - .ToolTipText(MultiTypeTooltip) - .Content() - [ - SNew(STextBlock) - .Text(this, &FFlowDataPinValueCustomization::GetSelectedMultiTypeText) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - + SHorizontalBox::Slot() + 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) @@ -83,7 +144,7 @@ void FFlowDataPinValueCustomization::CustomizeHeader(TSharedRef .OnCheckStateChanged(this, &FFlowDataPinValueCustomization::OnInputPinChanged) .IsEnabled(this, &FFlowDataPinValueCustomization::GetInputPinCheckboxEnabled) .Visibility(this, &FFlowDataPinValueCustomization::GetInputPinCheckboxVisibility) - .ToolTipText(InputPinTooltip) + .ToolTipText(GetInputPinTooltip()) [ SNew(STextBlock) .Text(LOCTEXT("InputPin", "Input Pin")) @@ -91,15 +152,12 @@ void FFlowDataPinValueCustomization::CustomizeHeader(TSharedRef ] ]; - AppendHeaderExtensions(HeaderBox); - HeaderRow .NameContent() [ SNew(STextBlock) .Text(StructPropertyHandle->GetPropertyDisplayName()) .Font(IDetailLayoutBuilder::GetDetailFont()) - .ColorAndOpacity(GetRowTint()) ] .ValueContent() .MinDesiredWidth(250.f) @@ -120,194 +178,89 @@ void FFlowDataPinValueCustomization::BuildValueRows(TSharedRef IPropertyTypeCustomizationUtils& StructCustomizationUtils) { CacheHandles(InStructPropertyHandle, StructCustomizationUtils); + CacheArraySupported(); if (!ValuesHandle.IsValid()) { return; } - EnsureSingleElementExists(); + if (bArraySupported) + { + EnsureSingleElementExists(); + } + BuildSingleBranch(StructBuilder); - BuildArrayBranch(StructBuilder); + if (bArraySupported) + { + BuildArrayBranch(StructBuilder); + } } void FFlowDataPinValueCustomization::BuildSingleBranch(IDetailChildrenBuilder& StructBuilder) { - if (!ValuesHandle.IsValid()) + if (GetSingleModeVisibility() == EVisibility::Collapsed) { return; } - auto FirstElementHandle = ValuesHandle->GetChildHandle(0); - - if (!FirstElementHandle.IsValid()) + if (!ValuesHandle.IsValid()) { return; } - IDetailPropertyRow& Row = StructBuilder.AddProperty(FirstElementHandle.ToSharedRef()); - Row.ShouldAutoExpand(true); - Row.Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization::GetSingleModeVisibility)); -} + TSharedPtr ValueToShow = bArraySupported + ? ValuesHandle->GetChildHandle(0) + : ValuesHandle; -void FFlowDataPinValueCustomization::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) -{ - if (!ValuesHandle.IsValid()) + if (!ValueToShow.IsValid()) { return; } - IDetailPropertyRow& Row = StructBuilder.AddProperty(ValuesHandle.ToSharedRef()); + IDetailPropertyRow& Row = StructBuilder.AddProperty(ValueToShow.ToSharedRef()); Row.ShouldAutoExpand(true); - Row.Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization::GetArrayModeVisibility)); } -void FFlowDataPinValueCustomization::EnsureSingleElementExists() +void FFlowDataPinValueCustomization::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) { - if (!ValuesHandle.IsValid()) + if (GetArrayModeVisibility() == EVisibility::Collapsed) { return; } - uint32 NumChildren = 0; - ValuesHandle->GetNumChildren(NumChildren); - - if (NumChildren == 0) + if (bArraySupported && ValuesHandle.IsValid() && ValuesHandle->AsArray()) { - if (auto AsArray = ValuesHandle->AsArray()) - { - AsArray->AddItem(); - } + IDetailPropertyRow& Row = StructBuilder.AddProperty(ValuesHandle.ToSharedRef()); + Row.ShouldAutoExpand(true); } } -void FFlowDataPinValueCustomization::AppendHeaderExtensions(TSharedRef HeaderBox) +void FFlowDataPinValueCustomization::RequestRefresh() { - const FFlowValueSourcePolicy* Policy = GetSourcePolicy(); - - if (!Policy || !Policy->bShowLockToggle) + if (PropertyUtilities.IsValid()) { - return; - } - - TSharedPtr LockHandle = StructPropertyHandle->GetChildHandle(TEXT("bLockClassFilter")); - bool bEnum = false; - bool bIsObjectLike = false; - bool bIsClass = false; - - if (LockHandle.IsValid()) - { - if (IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) - { - bIsClass = true; - } - else if (IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle) || - IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) - { - bIsObjectLike = true; - } - } - - if (!LockHandle.IsValid()) - { - LockHandle = StructPropertyHandle->GetChildHandle(TEXT("bLockEnumClass")); - bEnum = LockHandle.IsValid(); - } - - if (!LockHandle.IsValid()) - { - return; - } - - const bool bMetaForced = Policy->bMetaForced; - - FText LockTooltip; - FText MetaTooltip; - - if (bEnum) - { - LockTooltip = LOCTEXT("EnumLockTooltip", - "Lock Enum Class & Name.\nPrevents changing the Enum asset or native enum name.\nEnumerator values remain editable."); - MetaTooltip = LOCTEXT("EnumLockMetaTooltip", "Enum source locked by metadata."); - } - else if (bIsClass) - { - LockTooltip = LOCTEXT("ClassLockTooltip", - "Lock Class Filter.\nPrevents changing the Class Filter.\nClass values remain editable."); - MetaTooltip = LOCTEXT("ClassLockMetaTooltip", "Class filter locked by metadata (MetaClass)."); - } - else if (bIsObjectLike) - { - LockTooltip = LOCTEXT("ObjectLockTooltip", - "Lock Object Class Filter.\nPrevents changing the Class Filter.\nObject references remain editable."); - MetaTooltip = LOCTEXT("ObjectLockMetaTooltip", "Object class filter locked by metadata (MetaClass)."); - } - else - { - LockTooltip = LOCTEXT("GenericLockTooltip", - "Lock source settings (disables changing the source). Values remain editable."); - MetaTooltip = LOCTEXT("GenericMetaTooltip", "Source locked by metadata."); + PropertyUtilities->RequestRefresh(); } +} - if (bMetaForced) +void FFlowDataPinValueCustomization::EnsureSingleElementExists() +{ + if (!ValuesHandle.IsValid()) { - HeaderBox->AddSlot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(4.f, 0.f, 0.f, 0.f) - [ - SNew(SImage) - .Image(FAppStyle::GetBrush("Icons.Lock")) - .ToolTipText(MetaTooltip) - ]; - return; } - HeaderBox->AddSlot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(4.f, 0.f, 0.f, 0.f) - [ - SNew(SCheckBox) - .IsChecked_Lambda([LockHandle]() - { - bool bLocked = false; - LockHandle->GetValue(bLocked); - return bLocked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([this, LockHandle](ECheckBoxState NewState) - { - if (!LockHandle.IsValid()) - { - return; - } - - const bool bNew = (NewState == ECheckBoxState::Checked); - - FScopedTransaction Tx(LOCTEXT("ToggleSourceLock", "Toggle Source Lock")); - LockHandle->SetValue(bNew); - OnSourceLockToggled(); - }) - .ToolTipText(LockTooltip) - [ - SNew(STextBlock) - .Text(LOCTEXT("LockShortLabel", "Lock")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ]; - - LockHandle->SetOnPropertyValueChanged( - FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization::OnSourceLockToggled)); -} - -void FFlowDataPinValueCustomization::OnSourceLockToggled() -{ - if (CustomizationUtils) + if (bArraySupported) { - if (auto Utils = CustomizationUtils->GetPropertyUtilities()) + if (auto AsArray = ValuesHandle->AsArray()) { - Utils->RequestRefresh(); + uint32 Num = 0; + AsArray->GetNumElements(Num); + if (Num == 0) + { + AsArray->AddItem(); + } } } } @@ -318,14 +271,14 @@ void FFlowDataPinValueCustomization::CacheHandles(const TSharedRefGetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue, MultiType)); IsInputPinHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue, bIsInputPin)); + PropertyUtilities = StructCustomizationUtils.GetPropertyUtilities(); if (auto* Value = GetFlowDataPinValueBeingEdited()) { - PinType = Value->LookupDataPinType(); - - if (PinType) + DataPinType = Value->LookupPinType(); + if (DataPinType) { - ValuesHandle = PinType->GetValuesHandle(PropertyHandle); + ValuesHandle = DataPinType->GetValuesHandle(PropertyHandle); } } } @@ -333,7 +286,6 @@ void FFlowDataPinValueCustomization::CacheHandles(const TSharedRef Outers; StructPropertyHandle->GetOuterObjects(Outers); @@ -343,40 +295,58 @@ void FFlowDataPinValueCustomization::CacheOwnerInterface() } } -void FFlowDataPinValueCustomization::OnMultiTypeChanged(TSharedPtr NewSelection, ESelectInfo::Type /*SelectInfo*/) +void FFlowDataPinValueCustomization::CacheArraySupported() +{ + bArraySupported = DataPinType ? DataPinType->SupportsMultiType(EFlowDataMultiType::Array) : true; +} + +void FFlowDataPinValueCustomization::OnMultiTypeChanged(TSharedPtr NewSelection, ESelectInfo::Type) { if (!NewSelection.IsValid() || !MultiTypeHandle.IsValid()) { return; } - FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); - static FString ArrayValueAsString = UEnum::GetDisplayValueAsText(EFlowDataMultiType::Array).ToString(); + if (!bArraySupported) + { + return; + } - const EFlowDataMultiType NewType = - *NewSelection == ArrayValueAsString ? EFlowDataMultiType::Array : EFlowDataMultiType::Single; + const EFlowDataMultiType NewType = static_cast(*NewSelection); - FScopedTransaction Transaction(LOCTEXT("ChangePinMultiType", "Change Pin MultiType")); + 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 (NewType == EFlowDataMultiType::Single) + if (bNeedsTrim) { TrimArrayToSingle(); } - MultiTypeHandle->NotifyPostChange(EPropertyChangeType::ValueSet); - if (CustomizationUtils) + SelectedMultiType = NewSelection; + + // Preferred: trigger owner rebuild +#if WITH_EDITOR + if (OwnerInterface) { - if (TSharedPtr PropUtils = CustomizationUtils->GetPropertyUtilities()) - { - PropUtils->RequestRefresh(); - } + OwnerInterface->RequestFlowDataPinValuesDetailsRebuild(); } - - SelectedMultiType = NewSelection; + else + { + RequestRefresh(); + } +#endif } void FFlowDataPinValueCustomization::OnInputPinChanged(ECheckBoxState NewState) @@ -386,19 +356,21 @@ void FFlowDataPinValueCustomization::OnInputPinChanged(ECheckBoxState NewState) return; } - FScopedTransaction Transaction(LOCTEXT("ChangeInputPin", "Change Input Pin")); + 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(NewState == ECheckBoxState::Checked); + IsInputPinHandle->SetValue(bNewValue); IsInputPinHandle->NotifyPostChange(EPropertyChangeType::ValueSet); - if (CustomizationUtils) - { - if (TSharedPtr PropUtils = CustomizationUtils->GetPropertyUtilities()) - { - PropUtils->RequestRefresh(); - } - } + RequestRefresh(); } void FFlowDataPinValueCustomization::TrimArrayToSingle() @@ -413,27 +385,20 @@ void FFlowDataPinValueCustomization::TrimArrayToSingle() uint32 NumElements = 0; AsArray->GetNumElements(NumElements); - FScopedTransaction Transaction(LOCTEXT("TrimArrayToSingle", "Trim Array to Single")); - if (NumElements == 0) { AsArray->AddItem(); } else { - for (uint32 Index = NumElements - 1; Index >= 1; --Index) + while (NumElements > 1) { - AsArray->DeleteItem(Index); + AsArray->DeleteItem(NumElements - 1); + AsArray->GetNumElements(NumElements); } } - if (CustomizationUtils) - { - if (TSharedPtr PropUtils = CustomizationUtils->GetPropertyUtilities()) - { - PropUtils->RequestRefresh(); - } - } + RequestRefresh(); } } @@ -442,11 +407,11 @@ EFlowDataMultiType FFlowDataPinValueCustomization::GetCurrentMultiType() const if (MultiTypeHandle.IsValid()) { uint8 Value = 0; - MultiTypeHandle->GetValue(Value); - return static_cast(Value); + if (MultiTypeHandle->GetValue(Value) == FPropertyAccess::Success) + { + return static_cast(Value); + } } - - FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); return EFlowDataMultiType::Single; } @@ -458,19 +423,20 @@ ECheckBoxState FFlowDataPinValueCustomization::GetCurrentIsInputPin() const IsInputPinHandle->GetValue(Value); return Value ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } - return ECheckBoxState::Unchecked; } EVisibility FFlowDataPinValueCustomization::GetSingleModeVisibility() const { - FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); return GetCurrentMultiType() == EFlowDataMultiType::Single ? EVisibility::Visible : EVisibility::Collapsed; } EVisibility FFlowDataPinValueCustomization::GetArrayModeVisibility() const { - FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); + if (!bArraySupported) + { + return EVisibility::Collapsed; + } return GetCurrentMultiType() == EFlowDataMultiType::Array ? EVisibility::Visible : EVisibility::Collapsed; } @@ -483,24 +449,101 @@ EVisibility FFlowDataPinValueCustomization::GetInputPinCheckboxVisibility() cons bool FFlowDataPinValueCustomization::GetInputPinCheckboxEnabled() const { - return OwnerInterface ? OwnerInterface->CanModifyFlowDataPinValueType() : true; -} - -FLinearColor FFlowDataPinValueCustomization::GetRowTint() const -{ - return PinType ? PinType->GetPinColor() : FLinearColor::White; + return OwnerInterface ? OwnerInterface->CanModifyFlowDataPinType() : true; } -TSharedRef FFlowDataPinValueCustomization::GenerateMultiTypeWidget(TSharedPtr Item) const +TSharedRef FFlowDataPinValueCustomization::GenerateMultiTypeWidget(TSharedPtr Item) const { + const UEnum* MultiTypeEnum = StaticEnum(); return SNew(STextBlock) - .Text(Item.IsValid() ? FText::FromString(*Item) : FText::GetEmpty()) + .Text(Item.IsValid() && MultiTypeEnum + ? MultiTypeEnum->GetDisplayNameTextByValue(*Item) + : FText::GetEmpty()) .Font(IDetailLayoutBuilder::GetDetailFont()); } FText FFlowDataPinValueCustomization::GetSelectedMultiTypeText() const { - return SelectedMultiType.IsValid() ? FText::FromString(*SelectedMultiType) : FText::GetEmpty(); + 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 index 7f933ae64..0974a1048 100644 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Class.cpp +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Class.cpp @@ -3,27 +3,45 @@ #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 "PropertyCustomizationHelpers.h" #include "UObject/SoftObjectPath.h" #include "UnrealExtensions/VisibilityArrayBuilder.h" #include "IPropertyUtilities.h" #include "Interfaces/FlowDataPinValueOwnerInterface.h" #include "ScopedTransaction.h" -#include "DetailCustomizations/FlowValueSourcePolicy.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; @@ -34,36 +52,22 @@ void FFlowDataPinValueCustomization_Class::BuildValueRows( TrySetClassFilterFromMetaData(); ExtractMetadata(); RefreshEffectiveFilter(); - ComputePolicy(); - // Source row visible only if not locked/forced and policy allows - if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && !SourcePolicy.bMetaForced && ClassFilterHandle.IsValid()) + const bool bShowSource = ShouldShowSourceRow(); + if (bShowSource) { - BuildClassFilterRow(StructBuilder); - - ClassFilterHandle->SetOnPropertyValueChanged( - FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Class::OnClassFilterChanged)); + BuildClassFilterRow(StructBuilder, IsSourceEditable()); } EnsureSingleElementExists(); BuildSingleBranch(StructBuilder); - BuildArrayBranch(StructBuilder); - - BindValidationDelegates(); - ValidateAllElements(); -} - -void FFlowDataPinValueCustomization_Class::OnSourceLockToggled() -{ - ComputePolicy(); - - if (CustomizationUtils) + if (bArraySupported) { - if (auto Utils = CustomizationUtils->GetPropertyUtilities()) - { - Utils->RequestRefresh(); - } + BuildArrayBranch(StructBuilder); } + + BindDelegates(); + ValidateAllElements(); } void FFlowDataPinValueCustomization_Class::ExtractMetadata() @@ -82,7 +86,8 @@ void FFlowDataPinValueCustomization_Class::ExtractMetadata() bShowTreeView = StructPropertyHandle->HasMetaData(TEXT("ShowTreeView")); bHideViewOptions = StructPropertyHandle->HasMetaData(TEXT("HideViewOptions")); bShowDisplayNames = StructPropertyHandle->HasMetaData(TEXT("ShowDisplayNames")); - bMetaClassForced = StructPropertyHandle->HasMetaData(TEXT("MetaClass")); + + bHasMetaClass = !StructPropertyHandle->GetMetaData(TEXT("MetaClass")).IsEmpty(); if (const FProperty* MetaProp = StructPropertyHandle->GetMetaDataProperty()) { @@ -94,39 +99,24 @@ void FFlowDataPinValueCustomization_Class::ExtractMetadata() } } -void FFlowDataPinValueCustomization_Class::ComputePolicy() +void FFlowDataPinValueCustomization_Class::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder, bool bSourceEditable) { - FFlowDataPinValue_Class* ValueStruct = - IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); - - bool bPerValueLock = false; - -#if WITH_EDITORONLY_DATA - if (ValueStruct) + if (!ClassFilterHandle.IsValid()) { - bPerValueLock = ValueStruct->bLockClassFilter; + return; } -#endif - - SourcePolicy = ComputeFlowValueSourcePolicy( - OwnerInterface, - reinterpret_cast(ValueStruct), - bMetaClassForced, - bPerValueLock, - true); -} -void FFlowDataPinValueCustomization_Class::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) -{ IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); Row.DisplayName(LOCTEXT("ClassFilterLabel", "Class Filter")); - Row.IsEnabled(SourcePolicy.bFinalEditableSource); + 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; @@ -152,7 +142,7 @@ void FFlowDataPinValueCustomization_Class::BuildSingleBranch(IDetailChildrenBuil .ShowTreeView(bShowTreeView) .HideViewOptions(bHideViewOptions) .ShowDisplayNames(bShowDisplayNames) - .IsEnabled(SourcePolicy.bFinalEditableValues) + .IsEnabled(AreValuesEditable()) .SelectedClass_Lambda([this, First]() -> const UClass* { return GetSelectedClassForHandle(First); @@ -166,71 +156,53 @@ void FFlowDataPinValueCustomization_Class::BuildSingleBranch(IDetailChildrenBuil void FFlowDataPinValueCustomization_Class::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) { - TSharedRef ArrayBuilder = - MakeShareable(new FVisibilityArrayBuilder(ValuesHandle.ToSharedRef(), - true, true, true)); - - ArrayBuilder->SetVisibilityGetter([this]() + BuildVisibilityAwareArray(StructBuilder, + ValuesHandle, + [this](TSharedRef ElementHandle, int32 Index, IDetailChildrenBuilder& ChildBuilder, const TAttribute& RowVis) { - return GetArrayModeVisibility(); - }); - - ArrayBuilder->OnGenerateArrayElementWidget( - FOnGenerateArrayElementWidgetVisible::CreateSP( - this, - &FFlowDataPinValueCustomization_Class::GenerateArrayElementRow)); - - StructBuilder.AddCustomBuilder(ArrayBuilder); + 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::GenerateArrayElementRow( - TSharedRef ElementHandle, - int32 Index, - IDetailChildrenBuilder& ChildBuilder, - const TAttribute& RowVisibility) -{ - IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); - Row.Visibility(RowVisibility); - - 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(SourcePolicy.bFinalEditableValues) - .SelectedClass_Lambda([this, ElementHandle]() -> const UClass* - { - return GetSelectedClassForHandle(ElementHandle); - }) - .OnSetClass_Lambda([this, ElementHandle](const UClass* NewClass) - { - OnSetClassForHandle(NewClass, ElementHandle); - }) - ]; -} - -void FFlowDataPinValueCustomization_Class::BindValidationDelegates() +void FFlowDataPinValueCustomization_Class::BindDelegates() { if (ClassFilterHandle.IsValid()) { ClassFilterHandle->SetOnPropertyValueChanged( FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Class::OnClassFilterChanged)); } - if (ValuesHandle.IsValid()) { ValuesHandle->SetOnPropertyValueChanged( @@ -265,7 +237,6 @@ void FFlowDataPinValueCustomization_Class::TrySetClassFilterFromMetaData() } const FString& MetaClassName = StructPropertyHandle->GetMetaData(TEXT("MetaClass")); - if (MetaClassName.IsEmpty()) { return; @@ -275,7 +246,6 @@ void FFlowDataPinValueCustomization_Class::TrySetClassFilterFromMetaData() { UObject* Existing = nullptr; ClassFilterHandle->GetValue(Existing); - if (Existing != MetaClass) { ClassFilterHandle->SetValue(MetaClass, EPropertyValueSetFlags::DefaultFlags); @@ -291,7 +261,6 @@ UClass* FFlowDataPinValueCustomization_Class::DeriveBestClassFilter() const } const FString& MetaClassName = StructPropertyHandle->GetMetaData(TEXT("MetaClass")); - if (!MetaClassName.IsEmpty()) { if (UClass* MetaClass = FEditorClassUtils::GetClassFromString(MetaClassName)) @@ -303,7 +272,6 @@ UClass* FFlowDataPinValueCustomization_Class::DeriveBestClassFilter() const if (ClassFilterHandle.IsValid()) { UObject* Raw = nullptr; - if (ClassFilterHandle->GetValue(Raw) == FPropertyAccess::Success && Raw) { return Cast(Raw); @@ -320,103 +288,45 @@ void FFlowDataPinValueCustomization_Class::RefreshEffectiveFilter() void FFlowDataPinValueCustomization_Class::ValidateAllElements() { - if (!ValuesHandle.IsValid()) - { - return; - } - - UClass* FilterClass = CachedEffectiveFilter.Get(); - - if (!FilterClass) - { - return; - } - - auto AsArray = ValuesHandle->AsArray(); - - if (!AsArray.IsValid()) - { - return; - } - - uint32 Num = 0; - AsArray->GetNumElements(Num); - - TArray> ToClear; - ToClear.Reserve(Num); - - for (uint32 i = 0; i < Num; ++i) - { - TSharedPtr Elem = ValuesHandle->GetChildHandle(i); - - if (!Elem.IsValid()) + ValidateArrayElements(ValuesHandle, + [this](TSharedPtr Elem) { - continue; - } - - FString Path; - - if (!GetElementPathString(Elem, Path) || IsNoneString(Path)) - { - continue; - } - - FSoftClassPath SCP(Path); - - if (UClass* Loaded = SCP.TryLoadClass()) - { - if (!Loaded->IsChildOf(FilterClass)) - { - ToClear.Add(Elem); - } - } - else - { - ToClear.Add(Elem); - } - } - - if (ToClear.Num() > 0) - { - const FScopedTransaction Tx(LOCTEXT("ClearInvalidClassValues", "Clear Invalid Class Values")); - - for (const TSharedPtr& Elem : ToClear) + return IsElementValid(Elem); + }, + [](TSharedPtr Elem) { if (Elem.IsValid()) { Elem->SetValueFromFormattedString(TEXT("None")); } - } - } + }); } -void FFlowDataPinValueCustomization_Class::ValidateElement(const TSharedPtr& ElementHandle, UClass* FilterClass) +bool FFlowDataPinValueCustomization_Class::IsElementValid(TSharedPtr ElementHandle) const { - if (!ElementHandle.IsValid() || !FilterClass) + if (!ElementHandle.IsValid()) { - return; + return true; } - FString Path; + UClass* FilterClass = CachedEffectiveFilter.Get(); + if (!FilterClass) + { + return true; + } + FString Path; if (!GetElementPathString(ElementHandle, Path) || IsNoneString(Path)) { - return; + return true; } FSoftClassPath SCP(Path); - if (UClass* Loaded = SCP.TryLoadClass()) { - if (!Loaded->IsChildOf(FilterClass)) - { - ElementHandle->SetValueFromFormattedString(TEXT("None")); - } - } - else - { - ElementHandle->SetValueFromFormattedString(TEXT("None")); + return Loaded->IsChildOf(FilterClass); } + return false; } const UClass* FFlowDataPinValueCustomization_Class::GetSelectedClassForHandle(TSharedPtr ElementHandle) const @@ -427,17 +337,10 @@ const UClass* FFlowDataPinValueCustomization_Class::GetSelectedClassForHandle(TS } FString Path; - - if (ElementHandle->GetValueAsFormattedString(Path) != FPropertyAccess::Success) - { - return nullptr; - } - - if (IsNoneString(Path)) + if (ElementHandle->GetValueAsFormattedString(Path) != FPropertyAccess::Success || IsNoneString(Path)) { return nullptr; } - return FEditorClassUtils::GetClassFromString(Path); } @@ -449,19 +352,21 @@ void FFlowDataPinValueCustomization_Class::OnSetClassForHandle(const UClass* New } 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"); - ElementHandle->SetValueFromFormattedString(NewValue); - - if (Filter) + if (Current == NewValue) { - ValidateElement(ElementHandle, const_cast(Filter)); + return; } + + FScopedTransaction Tx(LOCTEXT("SetClassArrayElement", "Set Class Value")); + ElementHandle->SetValueFromFormattedString(NewValue); } bool FFlowDataPinValueCustomization_Class::GetElementPathString(const TSharedPtr& ElementHandle, FString& OutPath) const @@ -470,11 +375,10 @@ bool FFlowDataPinValueCustomization_Class::GetElementPathString(const TSharedPtr { return false; } - return ElementHandle->GetValueAsFormattedString(OutPath) == FPropertyAccess::Success; } -bool FFlowDataPinValueCustomization_Class::IsNoneString(const FString& Str) const +bool FFlowDataPinValueCustomization_Class::IsNoneString(const FString& Str) { return Str.IsEmpty() || Str.Equals(TEXT("None"), ESearchCase::IgnoreCase); } diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp index 5ea2a00e5..49269a4e5 100644 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp +++ b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_Enum.cpp @@ -4,24 +4,41 @@ #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 "DetailCustomizations/FlowValueSourcePolicy.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()) @@ -31,80 +48,40 @@ void FFlowDataPinValueCustomization_Enum::BuildValueRows( bMultiTypeDelegateBound = true; } - LockEnumHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, bLockEnumClass)); - ComputePolicy(); + const bool bShowSource = ShouldShowSourceRow(); + const bool bSourceEditable = IsSourceEditable(); - // SOURCE rows: only if visible & not locked - if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && EnumClassHandle.IsValid()) + if (bShowSource && EnumClassHandle.IsValid()) { - IDetailPropertyRow& Row = StructBuilder.AddProperty(EnumClassHandle.ToSharedRef()); - Row.IsEnabled(SourcePolicy.bFinalEditableSource); - Row.ToolTip(GetEnumSourceTooltip()); - + IDetailPropertyRow& RowEnumClass = StructBuilder.AddProperty(EnumClassHandle.ToSharedRef()); + RowEnumClass.IsEnabled(bSourceEditable); + RowEnumClass.ToolTip(GetEnumSourceTooltip()); EnumClassHandle->SetOnPropertyValueChanged( FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_Enum::OnEnumSourceChanged)); } -#if WITH_EDITORONLY_DATA - if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && EnumNameHandle.IsValid()) + if (bShowSource && EnumNameHandle.IsValid()) { - IDetailPropertyRow& Row = StructBuilder.AddProperty(EnumNameHandle.ToSharedRef()); - Row.IsEnabled(SourcePolicy.bFinalEditableSource); - Row.ToolTip(LOCTEXT("EnumNameTooltip", "Name of native C++ enum type (overrides asset if provided).")); - + 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)); } -#endif RebuildEnumData(); EnsureSingleElementExists(); BuildSingle(StructBuilder); - BuildArray(StructBuilder); -} - -void FFlowDataPinValueCustomization_Enum::OnSourceLockToggled() -{ - ComputePolicy(); - - if (CustomizationUtils) - { - if (auto Utils = CustomizationUtils->GetPropertyUtilities()) - { - Utils->RequestRefresh(); - } - } -} - -void FFlowDataPinValueCustomization_Enum::ComputePolicy() -{ - FFlowDataPinValue_Enum* EnumStruct = GetEnumValueStruct(); - - bool bPerValueLock = false; - -#if WITH_EDITORONLY_DATA - if (EnumStruct) + if (bArraySupported) { - bPerValueLock = EnumStruct->bLockEnumClass; + BuildArray(StructBuilder); } -#endif - - // Enum has no MetaClass forcing, pass false - SourcePolicy = ComputeFlowValueSourcePolicy( - OwnerInterface, - reinterpret_cast(EnumStruct), - false, - bPerValueLock, - true); } void FFlowDataPinValueCustomization_Enum::CacheEnumHandles(const TSharedRef& StructHandle) { EnumClassHandle = StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, EnumClass)); - -#if WITH_EDITORONLY_DATA EnumNameHandle = StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FFlowDataPinValue_Enum, EnumName)); -#endif } void FFlowDataPinValueCustomization_Enum::OnEnumSourceChanged() @@ -127,9 +104,7 @@ void FFlowDataPinValueCustomization_Enum::RebuildEnumData() if (FFlowDataPinValue_Enum* EnumStruct = GetEnumValueStruct()) { -#if WITH_EDITOR EnumStruct->OnEnumNameChanged(); -#endif } if (UEnum* EnumObj = ResolveEnum()) @@ -144,7 +119,6 @@ void FFlowDataPinValueCustomization_Enum::RebuildEnumData() UEnum* FFlowDataPinValueCustomization_Enum::ResolveEnum() const { const FFlowDataPinValue_Enum* Data = GetEnumValueStruct(); - return Data ? Data->EnumClass.LoadSynchronous() : nullptr; } @@ -159,7 +133,6 @@ void FFlowDataPinValueCustomization_Enum::CollectEnumerators(UEnum& EnumObj) { continue; } - if (EnumObj.HasMetaData(HiddenKey, Index)) { continue; @@ -178,8 +151,6 @@ void FFlowDataPinValueCustomization_Enum::ValidateStoredValues() } TArray ValidNames; - ValidNames.Reserve(EnumeratorOptions.Num()); - for (auto& Opt : EnumeratorOptions) { if (Opt.IsValid()) @@ -193,7 +164,7 @@ void FFlowDataPinValueCustomization_Enum::ValidateStoredValues() uint32 Count = 0; AsArray->GetNumElements(Count); - if (GetSingleVisibility() == EVisibility::Visible && Count == 0) + if (GetSingleModeVisibility() == EVisibility::Visible && Count == 0) { AsArray->AddItem(); AsArray->GetNumElements(Count); @@ -202,14 +173,12 @@ void FFlowDataPinValueCustomization_Enum::ValidateStoredValues() 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)) @@ -227,7 +196,6 @@ bool FFlowDataPinValueCustomization_Enum::IsValueValid(const FName& Candidate) c { return EnumeratorOptions.Num() == 0; } - for (auto& Opt : EnumeratorOptions) { if (Opt.IsValid() && *Opt == Candidate) @@ -235,7 +203,6 @@ bool FFlowDataPinValueCustomization_Enum::IsValueValid(const FName& Candidate) c return true; } } - return false; } @@ -248,42 +215,9 @@ TSharedPtr FFlowDataPinValueCustomization_Enum::FindEnumeratorMatch(const return Opt; } } - return nullptr; } -EVisibility FFlowDataPinValueCustomization_Enum::GetSingleVisibility() const -{ - FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); - - uint8 Mode = 0; - - if (MultiTypeHandle.IsValid() && - MultiTypeHandle->GetValue(Mode) == FPropertyAccess::Success && - (EFlowDataMultiType)Mode == EFlowDataMultiType::Single) - { - return EVisibility::Visible; - } - - return EVisibility::Collapsed; -} - -EVisibility FFlowDataPinValueCustomization_Enum::GetArrayVisibility() const -{ - FLOW_ASSERT_ENUM_MAX(EFlowDataMultiType, 2); - - uint8 Mode = 0; - - if (MultiTypeHandle.IsValid() && - MultiTypeHandle->GetValue(Mode) == FPropertyAccess::Success && - (EFlowDataMultiType)Mode == EFlowDataMultiType::Array) - { - return EVisibility::Visible; - } - - return EVisibility::Collapsed; -} - void FFlowDataPinValueCustomization_Enum::BuildSingle(IDetailChildrenBuilder& StructBuilder) { if (!ValuesHandle.IsValid()) @@ -292,7 +226,6 @@ void FFlowDataPinValueCustomization_Enum::BuildSingle(IDetailChildrenBuilder& St } auto First = ValuesHandle->GetChildHandle(0); - if (!First.IsValid()) { if (auto AsArray = ValuesHandle->AsArray()) @@ -308,8 +241,7 @@ void FFlowDataPinValueCustomization_Enum::BuildSingle(IDetailChildrenBuilder& St } StructBuilder.AddCustomRow(LOCTEXT("EnumSingleSearch", "Value")) - .Visibility(TAttribute::Create( - TAttribute::FGetter::CreateSP(this, &FFlowDataPinValueCustomization_Enum::GetSingleVisibility))) + .Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_Enum::GetSingleModeVisibility)) .NameContent() [ SNew(STextBlock) @@ -327,12 +259,10 @@ void FFlowDataPinValueCustomization_Enum::BuildSingle(IDetailChildrenBuilder& St .InitiallySelectedItem([this, First]() { FName Current; - if (First->GetValue(Current) == FPropertyAccess::Success) { return FindEnumeratorMatch(Current); } - return EnumeratorOptions.Num() > 0 ? EnumeratorOptions[0] : nullptr; }()) .Content() @@ -341,12 +271,10 @@ void FFlowDataPinValueCustomization_Enum::BuildSingle(IDetailChildrenBuilder& St .Text_Lambda([this, First]() { FName Current; - if (First->GetValue(Current) == FPropertyAccess::Success && !Current.IsNone()) { return GetEnumeratorDisplayText(Current); } - return LOCTEXT("EnumNonePlaceholder", ""); }) .Font(IDetailLayoutBuilder::GetDetailFont()) @@ -357,88 +285,62 @@ void FFlowDataPinValueCustomization_Enum::BuildSingle(IDetailChildrenBuilder& St void FFlowDataPinValueCustomization_Enum::BuildArray(IDetailChildrenBuilder& StructBuilder) { - if (!ValuesHandle.IsValid()) - { - return; - } - - TSharedRef ArrayBuilder = - MakeShareable(new FVisibilityArrayBuilder(ValuesHandle.ToSharedRef(), true, true, true)); - - ArrayBuilder->SetVisibilityGetter([this]() + BuildVisibilityAwareArray(StructBuilder, + ValuesHandle, + [this](TSharedRef ElementHandle, int32 Index, IDetailChildrenBuilder& ChildBuilder, const TAttribute& RowVis) { - return GetArrayVisibility(); - }); - - ArrayBuilder->OnGenerateArrayElementWidget( - FOnGenerateArrayElementWidgetVisible::CreateSP( - this, - &FFlowDataPinValueCustomization_Enum::GenerateArrayElementVisible)); - - StructBuilder.AddCustomBuilder(ArrayBuilder); -} - -void FFlowDataPinValueCustomization_Enum::GenerateArrayElementVisible( - TSharedRef ElementHandle, - int32 Index, - IDetailChildrenBuilder& ChildBuilder, - const TAttribute& RowVisibility) -{ - IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); - Row.Visibility(RowVisibility); - - 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); - } + IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); + Row.Visibility(RowVis); - return EnumeratorOptions.Num() > 0 ? EnumeratorOptions[0] : nullptr; - }()) - .Content() + Row.CustomWidget() + .NameContent() [ SNew(STextBlock) - .Text_Lambda([this, ElementHandle]() + .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 && !Current.IsNone()) + if (ElementHandle->GetValue(Current) == FPropertyAccess::Success) { - return GetEnumeratorDisplayText(Current); + return FindEnumeratorMatch(Current); } - - return LOCTEXT("EnumNonePlaceholder", ""); - }) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(GetEnumSourceTooltip()) - ] - ]; + 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()); @@ -452,31 +354,24 @@ FText FFlowDataPinValueCustomization_Enum::GetEnumeratorDisplayText(const FName& FText FFlowDataPinValueCustomization_Enum::GetEnumSourceTooltip() const { const FFlowDataPinValue_Enum* Data = GetEnumValueStruct(); - if (!Data) { return LOCTEXT("EnumTooltipMissing", "Enum value struct not available."); } FString Source; - -#if WITH_EDITORONLY_DATA if (!Data->EnumName.IsEmpty()) { Source = FString::Printf(TEXT("Native Enum: %s"), *Data->EnumName); } -#endif - 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); } @@ -490,6 +385,13 @@ void FFlowDataPinValueCustomization_Enum::OnSingleValueChanged( return; } + FName Current; + ElementHandle->GetValue(Current); + if (Current == *NewSelection) + { + return; + } + FScopedTransaction Tx(LOCTEXT("SetEnumSingleValue", "Set Enum Value")); ElementHandle->SetValue(*NewSelection); } @@ -504,13 +406,26 @@ void FFlowDataPinValueCustomization_Enum::OnArrayElementChanged( 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 (GetArrayVisibility() == EVisibility::Collapsed) + // If array not supported, ignore switching + if (!bArraySupported) + { + return; + } + + if (GetArrayModeVisibility() == EVisibility::Collapsed) { EnsureSingleElementExists(); } @@ -524,9 +439,4 @@ void FFlowDataPinValueCustomization_Enum::OnMultiTypeChanged() } } -FFlowDataPinValue_Enum* FFlowDataPinValueCustomization_Enum::GetEnumValueStruct() const -{ - return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE diff --git a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp deleted file mode 100644 index feefdd7bb..000000000 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.cpp +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h" - -#include "PropertyHandle.h" -#include "Widgets/Text/STextBlock.h" -#include "Widgets/SBoxPanel.h" -#include "PropertyCustomizationHelpers.h" -#include "ScopedTransaction.h" -#include "IPropertyUtilities.h" -#include "Types/FlowDataPinValuesStandard.h" -#include "Interfaces/FlowDataPinValueOwnerInterface.h" -#include "UObject/UObjectGlobals.h" -#include "UObject/Package.h" -#include "UObject/UObjectIterator.h" -#include "EditorClassUtils.h" -#include "IDetailPropertyRow.h" -#include "IDetailChildrenBuilder.h" - -#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_InstancedObject" - -// --------------------------- -// Metadata Extraction -// --------------------------- -void FFlowDataPinValueCustomization_InstancedObject::ExtractInterfaceMetadata() -{ - RequiredInterface = nullptr; - - if (!StructPropertyHandle.IsValid()) - { - return; - } - - const FString& MustImplement = StructPropertyHandle->GetMetaData(TEXT("MustImplement")); - - if (!MustImplement.IsEmpty()) - { - RequiredInterface = FEditorClassUtils::GetClassFromString(MustImplement); - } -} - -// --------------------------- -// ClassFilter Row -// --------------------------- -void FFlowDataPinValueCustomization_InstancedObject::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) -{ - if (!ClassFilterHandle.IsValid()) - { - return; - } - - ExtractInterfaceMetadata(); - - IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); - Row.DisplayName(LOCTEXT("InstancedObjClassFilter", "Class Filter")); - - TSharedPtr LocalHandle = ClassFilterHandle; - - TArray> Filters; - BuildClassFilterFilters(Filters); - - Row.CustomWidget() - .NameContent() - [ - LocalHandle->CreatePropertyNameWidget() - ] - .ValueContent() - .MinDesiredWidth(250.f) - [ - SNew(SClassPropertyEntryBox) - .MetaClass(UObject::StaticClass()) - .AllowAbstract(true) // Abstract allowed - .IsBlueprintBaseOnly(false) - .AllowNone(true) - .HideViewOptions(false) - .ShowDisplayNames(true) - .ShowTreeView(true) - .RequiredInterface(nullptr) - .ClassViewerFilters(Filters) - .IsEnabled(SourcePolicy.bFinalEditableSource) - .SelectedClass_Lambda([LocalHandle]() -> const UClass* - { - UObject* ObjVal = nullptr; - - if (LocalHandle->GetValue(ObjVal) == FPropertyAccess::Success && ObjVal) - { - return Cast(ObjVal); - } - - return nullptr; - }) - .OnSetClass_Lambda([this, LocalHandle](const UClass* NewClass) - { - if (!LocalHandle.IsValid()) - { - return; - } - - if (NewClass) - { - // MustImplement interface - if (RequiredInterface && !NewClass->ImplementsInterface(RequiredInterface)) - { - NewClass = nullptr; - } - - // Require EditInlineNew for the filter - if (NewClass && !NewClass->HasAnyClassFlags(CLASS_EditInlineNew)) - { - NewClass = nullptr; - } - } - - LocalHandle->SetValue(const_cast(NewClass)); - OnClassFilterChanged(); - }) - ]; - - Row.IsEnabled(SourcePolicy.bFinalEditableSource); -} - -// --------------------------- -// Value Widget (instantiate if null) -// --------------------------- -TSharedRef FFlowDataPinValueCustomization_InstancedObject::BuildObjectValueWidgetForElement(TSharedPtr ElementHandle) -{ - if (!ElementHandle.IsValid()) - { - return SNew(STextBlock).Text(LOCTEXT("InvalidHandle", "")); - } - - UObject* CurrentObj = nullptr; - ElementHandle->GetValue(CurrentObj); - - if (CurrentObj) - { - BindPerElementValueChange(ElementHandle); - return ElementHandle->CreatePropertyValueWidget(); - } - - ExtractInterfaceMetadata(); - - // Value editing disabled by owner policy (not by lock) - if (!SourcePolicy.bFinalEditableValues) - { - return SNew(STextBlock) - .Text(LOCTEXT("ValuesDisabled", "(Values Locked by Owner Policy)")) - .ColorAndOpacity(FLinearColor::Gray); - } - - // Scan once for instantiables - if (!bInstantiableScanDone) - { - bHasAnyInstantiable = ScanForAnyInstantiable(); - bInstantiableScanDone = true; - } - - if (!bHasAnyInstantiable) - { - return SNew(STextBlock) - .Text(LOCTEXT("NoInstantiableFound", - "No instantiable (EditInlineNew, non-abstract) subclass found.\n" - "Adjust Class Filter or add a concrete EditInlineNew subclass.")) - .AutoWrapText(true); - } - - TArray> Filters; - BuildInstantiationFilters(Filters); - - return SNew(SClassPropertyEntryBox) - .MetaClass(UObject::StaticClass()) - .AllowAbstract(false) - .IsBlueprintBaseOnly(false) - .AllowNone(false) - .HideViewOptions(false) - .ShowDisplayNames(true) - .ShowTreeView(true) - .RequiredInterface(nullptr) - .ClassViewerFilters(Filters) - .SelectedClass_Lambda([]() -> const UClass* { return nullptr; }) - .OnSetClass_Lambda([this, ElementHandle](const UClass* ChosenClass) - { - if (!CanInstantiateClass(ChosenClass)) - { - return; - } - - InstantiateForHandle(ElementHandle, ChosenClass); - }); -} - -// --------------------------- -// Filters -// --------------------------- -bool FFlowDataPinValueCustomization_InstancedObject::FClassFilterRowFilter::IsClassAllowed( - const FClassViewerInitializationOptions&, - const UClass* InClass, - TSharedRef) -{ - if (!InClass) - { - return false; - } - - if (Base.IsValid() && !InClass->IsChildOf(Base.Get())) - { - return false; - } - - if (RequiredInterface.IsValid() && !InClass->ImplementsInterface(RequiredInterface.Get())) - { - return false; - } - - if (!InClass->HasAnyClassFlags(CLASS_EditInlineNew)) - { - return false; - } - - // Abstract allowed here - return true; -} - -bool FFlowDataPinValueCustomization_InstancedObject::FClassFilterRowFilter::IsUnloadedClassAllowed( - const FClassViewerInitializationOptions&, - const TSharedRef, - TSharedRef) -{ - return false; -} - -bool FFlowDataPinValueCustomization_InstancedObject::FInstantiationFilter::IsClassAllowed( - const FClassViewerInitializationOptions&, - const UClass* InClass, - TSharedRef) -{ - if (!InClass) - { - return false; - } - - if (Base.IsValid() && !InClass->IsChildOf(Base.Get())) - { - return false; - } - - if (RequiredInterface.IsValid() && !InClass->ImplementsInterface(RequiredInterface.Get())) - { - return false; - } - - if (InClass->HasAnyClassFlags(CLASS_Abstract)) - { - return false; - } - - if (!InClass->HasAnyClassFlags(CLASS_EditInlineNew)) - { - return false; - } - - return true; -} - -bool FFlowDataPinValueCustomization_InstancedObject::FInstantiationFilter::IsUnloadedClassAllowed( - const FClassViewerInitializationOptions&, - const TSharedRef, - TSharedRef) -{ - return false; -} - -void FFlowDataPinValueCustomization_InstancedObject::BuildClassFilterFilters(TArray>& Out) const -{ - TSharedRef Filter = MakeShared(); - Filter->Base = nullptr; - Filter->RequiredInterface = RequiredInterface; - Out.Add(Filter); -} - -void FFlowDataPinValueCustomization_InstancedObject::BuildInstantiationFilters(TArray>& Out) const -{ - TSharedRef Filter = MakeShared(); - Filter->Base = EffectiveFilterClass; - Filter->RequiredInterface = RequiredInterface; - Out.Add(Filter); -} - -// --------------------------- -// Enumeration for instantiables -// --------------------------- -bool FFlowDataPinValueCustomization_InstancedObject::ScanForAnyInstantiable() const -{ - UClass* BaseClass = EffectiveFilterClass.Get(); - - for (TObjectIterator It; It; ++It) - { - UClass* C = *It; - - if (!C) - { - continue; - } - - if (BaseClass && !C->IsChildOf(BaseClass)) - { - continue; - } - - if (RequiredInterface && !C->ImplementsInterface(RequiredInterface)) - { - continue; - } - - if (C->HasAnyClassFlags(CLASS_Abstract)) - { - continue; - } - - if (!C->HasAnyClassFlags(CLASS_EditInlineNew)) - { - continue; - } - - return true; - } - - return false; -} - -// --------------------------- -// Validation / Instantiation -// --------------------------- -bool FFlowDataPinValueCustomization_InstancedObject::CanInstantiateClass(const UClass* Candidate) const -{ - if (!Candidate) - { - return false; - } - - if (RequiredInterface && !Candidate->ImplementsInterface(RequiredInterface)) - { - return false; - } - - if (EffectiveFilterClass.IsValid() && !Candidate->IsChildOf(EffectiveFilterClass.Get())) - { - return false; - } - - if (Candidate->HasAnyClassFlags(CLASS_Abstract)) - { - return false; - } - - if (!Candidate->HasAnyClassFlags(CLASS_EditInlineNew)) - { - return false; - } - - return true; -} - -void FFlowDataPinValueCustomization_InstancedObject::InstantiateForHandle(TSharedPtr ElementHandle, const UClass* ChosenClass) -{ - if (!ElementHandle.IsValid() || !ChosenClass) - { - return; - } - - if (!CanInstantiateClass(ChosenClass)) - { - return; - } - - FScopedTransaction Tx(LOCTEXT("CreateInstancedObjectPinValue", "Create Instanced Object Pin Value")); - - UObject* Outer = ResolveOuterForNewObject(); - - if (!Outer) - { - Outer = GetTransientPackage(); - } - - Outer->Modify(); - - UObject* NewObj = NewObject( - Outer, - const_cast(ChosenClass), - NAME_None, - RF_Transactional); - - if (!NewObj) - { - return; - } - - NewObj->SetFlags(RF_Transactional); - - if (!SetInstancedObjectHandleDirect(ElementHandle, NewObj)) - { - SetInstancedObjectHandleDirect(ElementHandle, nullptr); - return; - } - - BindPerElementValueChange(ElementHandle); - - if (CustomizationUtils) - { - if (TSharedPtr Utils = CustomizationUtils->GetPropertyUtilities()) - { - Utils->RequestRefresh(); - } - } -} - -// Direct pointer write for instanced property -bool FFlowDataPinValueCustomization_InstancedObject::SetInstancedObjectHandleDirect( - TSharedPtr ElementHandle, - UObject* NewValue) -{ - if (!ElementHandle.IsValid()) - { - return false; - } - - void* Address = nullptr; - - if (ElementHandle->GetValueData(Address) != FPropertyAccess::Success || !Address) - { - return false; - } - - TArray Outers; - ElementHandle->GetOuterObjects(Outers); - - if (Outers.Num() > 0 && Outers[0]) - { - Outers[0]->Modify(); - } - - ElementHandle->NotifyPreChange(); - - UObject** Ptr = reinterpret_cast(Address); - *Ptr = NewValue; - - ElementHandle->NotifyPostChange(EPropertyChangeType::ValueSet); - ElementHandle->NotifyFinishedChangingProperties(); - - return true; -} - -UObject* FFlowDataPinValueCustomization_InstancedObject::ResolveOuterForNewObject() const -{ - if (!StructPropertyHandle.IsValid()) - { - return nullptr; - } - - TArray Outers; - StructPropertyHandle->GetOuterObjects(Outers); - - if (Outers.Num() > 0) - { - return Outers[0]; - } - - return nullptr; -} - -void FFlowDataPinValueCustomization_InstancedObject::BindPerElementValueChange(TSharedPtr ElementHandle) -{ - if (!ElementHandle.IsValid()) - { - return; - } - - ElementHandle->SetOnPropertyValueChanged( - FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_InstancedObject::OnElementValueChanged, ElementHandle)); -} - -void FFlowDataPinValueCustomization_InstancedObject::OnElementValueChanged(TSharedPtr ElementHandle) -{ - if (!ElementHandle.IsValid()) - { - return; - } - - UClass* Filter = EffectiveFilterClass.Get(); - - if (!Filter) - { - return; - } - - void* Address = nullptr; - - if (ElementHandle->GetValueData(Address) != FPropertyAccess::Success || !Address) - { - return; - } - - UObject* Obj = *reinterpret_cast(Address); - - if (Obj && !Obj->IsA(Filter)) - { - SetInstancedObjectHandleDirect(ElementHandle, nullptr); - } - - if (CustomizationUtils) - { - if (TSharedPtr Utils = CustomizationUtils->GetPropertyUtilities()) - { - Utils->RequestRefresh(); - } - } -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file 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/FlowDataPinValueCustomization_ObjectBase.cpp b/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.cpp deleted file mode 100644 index 8c60ccd35..000000000 --- a/Source/FlowEditor/Private/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.cpp +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#include "DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h" - -#include "DetailLayoutBuilder.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 "DetailCustomizations/FlowValueSourcePolicy.h" -#include "EditorClassUtils.h" - -#define LOCTEXT_NAMESPACE "FlowDataPinValueCustomization_ObjectBase" - -void FFlowDataPinValueCustomization_ObjectBase::BuildValueRows( - TSharedRef InStructPropertyHandle, - IDetailChildrenBuilder& StructBuilder, - IPropertyTypeCustomizationUtils& StructCustomizationUtils) -{ - CacheHandles(InStructPropertyHandle, StructCustomizationUtils); - - if (!ValuesHandle.IsValid()) - { - return; - } - - ClassFilterHandle = StructPropertyHandle->GetChildHandle(TEXT("ClassFilter")); - - TryApplyMetaClass(); - ResolveEffectiveFilter(); - ComputePolicy(); - - if (SourcePolicy.bShowSourceRow && !SourcePolicy.bLocked && !SourcePolicy.bMetaForced && ClassFilterHandle.IsValid()) - { - BuildClassFilterRow(StructBuilder); - - ClassFilterHandle->SetOnPropertyValueChanged( - FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::OnClassFilterChanged)); - } - - EnsureSingleElementExists(); - BuildSingleBranch(StructBuilder); - BuildArrayBranch(StructBuilder); - - BindDelegates(); - ValidateAll(); -} - -void FFlowDataPinValueCustomization_ObjectBase::OnSourceLockToggled() -{ - ComputePolicy(); - - if (CustomizationUtils) - { - if (auto Utils = CustomizationUtils->GetPropertyUtilities()) - { - Utils->RequestRefresh(); - } - } -} - -void FFlowDataPinValueCustomization_ObjectBase::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_ObjectBase::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_ObjectBase::ComputePolicy() -{ - bool bPerValueLock = false; - - if (FFlowDataPinValue_Object* ObjStruct = - IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) - { -#if WITH_EDITORONLY_DATA - bPerValueLock = ObjStruct->bLockClassFilter; -#endif - } - else if (FFlowDataPinValue_InstancedObject* InstStruct = - IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle)) - { -#if WITH_EDITORONLY_DATA - bPerValueLock = InstStruct->bLockClassFilter; -#endif - } - - const FFlowDataPinValue* BaseValue = - IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); - - SourcePolicy = ComputeFlowValueSourcePolicy( - OwnerInterface, - BaseValue, - bMetaClassForced, - bPerValueLock, - true); -} - -void FFlowDataPinValueCustomization_ObjectBase::BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) -{ - IDetailPropertyRow& Row = StructBuilder.AddProperty(ClassFilterHandle.ToSharedRef()); - Row.DisplayName(LOCTEXT("ObjClassFilter", "Class Filter")); - Row.IsEnabled(SourcePolicy.bFinalEditableSource); -} - -void FFlowDataPinValueCustomization_ObjectBase::BuildSingleBranch(IDetailChildrenBuilder& StructBuilder) -{ - auto First = ValuesHandle->GetChildHandle(0); - - if (!First.IsValid()) - { - return; - } - - StructBuilder.AddCustomRow(LOCTEXT("ObjectSingleSearch", "Object")) - .Visibility(TAttribute::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::GetSingleModeVisibility)) - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("ObjectValueLabel", "Object")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(250.f) - [ - BuildObjectValueWidgetForElement(First) - ]; -} - -void FFlowDataPinValueCustomization_ObjectBase::BuildArrayBranch(IDetailChildrenBuilder& StructBuilder) -{ - TSharedRef ArrayBuilder = - MakeShareable(new FVisibilityArrayBuilder(ValuesHandle.ToSharedRef(), - true, true, true)); - - ArrayBuilder->SetVisibilityGetter([this]() - { - return GetArrayModeVisibility(); - }); - - ArrayBuilder->OnGenerateArrayElementWidget( - FOnGenerateArrayElementWidgetVisible::CreateSP( - this, - &FFlowDataPinValueCustomization_ObjectBase::GenerateArrayElementRow)); - - StructBuilder.AddCustomBuilder(ArrayBuilder); -} - -void FFlowDataPinValueCustomization_ObjectBase::GenerateArrayElementRow( - TSharedRef ElementHandle, - int32 Index, - IDetailChildrenBuilder& ChildBuilder, - const TAttribute& RowVisibility) -{ - IDetailPropertyRow& Row = ChildBuilder.AddProperty(ElementHandle); - Row.Visibility(RowVisibility); - - Row.CustomWidget() - .NameContent() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("ObjectArrayElemFmt", "Object {0}"), FText::AsNumber(Index))) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(250.f) - [ - BuildObjectValueWidgetForElement(ElementHandle) - ]; -} - -TSharedRef FFlowDataPinValueCustomization_ObjectBase::BuildObjectValueWidgetForElement(TSharedPtr ElementHandle) -{ - return SNew(SObjectPropertyEntryBox) - .PropertyHandle(ElementHandle) - .AllowedClass(EffectiveFilterClass.Get() ? EffectiveFilterClass.Get() : UObject::StaticClass()) - .AllowClear(SourcePolicy.bFinalEditableValues) - .IsEnabled(SourcePolicy.bFinalEditableValues) - .ToolTipText(SourcePolicy.bFinalEditableValues - ? LOCTEXT("ObjectPickerTooltip", "Select an object reference (filter may be locked).") - : LOCTEXT("ObjectPickerLockedTooltip", "Object references are not editable by owner policy or metadata.")); -} - -void FFlowDataPinValueCustomization_ObjectBase::BindDelegates() -{ - if (ClassFilterHandle.IsValid()) - { - ClassFilterHandle->SetOnPropertyValueChanged( - FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::OnClassFilterChanged)); - } - - if (ValuesHandle.IsValid()) - { - ValuesHandle->SetOnPropertyValueChanged( - FSimpleDelegate::CreateSP(this, &FFlowDataPinValueCustomization_ObjectBase::OnValuesChanged)); - } -} - -void FFlowDataPinValueCustomization_ObjectBase::OnClassFilterChanged() -{ - ResolveEffectiveFilter(); - ValidateAll(); - - if (CustomizationUtils) - { - if (auto Utils = CustomizationUtils->GetPropertyUtilities()) - { - Utils->RequestRefresh(); - } - } -} - -void FFlowDataPinValueCustomization_ObjectBase::OnValuesChanged() -{ - ValidateAll(); -} - -void FFlowDataPinValueCustomization_ObjectBase::ValidateAll() -{ - if (!ValuesHandle.IsValid()) - { - return; - } - - UClass* Filter = EffectiveFilterClass.Get(); - - if (!Filter) - { - return; - } - - auto AsArray = ValuesHandle->AsArray(); - - if (!AsArray.IsValid()) - { - return; - } - - uint32 Num = 0; - AsArray->GetNumElements(Num); - - TArray> ToClear; - ToClear.Reserve(Num); - - for (uint32 i = 0; i < Num; ++i) - { - auto Elem = ValuesHandle->GetChildHandle(i); - - if (!Elem.IsValid()) - { - continue; - } - - UObject* Obj = GetObjectValue(Elem); - - if (Obj && !Obj->IsA(Filter)) - { - ToClear.Add(Elem); - } - } - - if (ToClear.Num() > 0) - { - const FScopedTransaction Tx(LOCTEXT("ClearInvalidObjects", "Clear Invalid Object References")); - - for (auto& H : ToClear) - { - if (H.IsValid()) - { - SetObjectValue(H, nullptr); - } - } - } -} - -void FFlowDataPinValueCustomization_ObjectBase::ValidateElement(TSharedPtr ElementHandle, UClass* Filter) -{ - if (!ElementHandle.IsValid() || !Filter) - { - return; - } - - UObject* Obj = GetObjectValue(ElementHandle); - - if (Obj && !Obj->IsA(Filter)) - { - SetObjectValue(ElementHandle, nullptr); - } -} - -UObject* FFlowDataPinValueCustomization_ObjectBase::GetObjectValue(TSharedPtr ElementHandle) const -{ - UObject* Obj = nullptr; - - if (ElementHandle.IsValid()) - { - ElementHandle->GetValue(Obj); - } - - return Obj; -} - -void FFlowDataPinValueCustomization_ObjectBase::SetObjectValue(TSharedPtr ElementHandle, UObject* NewObj) -{ - if (!ElementHandle.IsValid()) - { - return; - } - - ElementHandle->SetValue(NewObj); -} - -UClass* FFlowDataPinValueCustomization_ObjectBase::GetCurrentFilterClassProperty() const -{ - if (!ClassFilterHandle.IsValid()) - { - return nullptr; - } - - UObject* Obj = nullptr; - - if (ClassFilterHandle->GetValue(Obj) == FPropertyAccess::Success) - { - return Cast(Obj); - } - - return nullptr; -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file 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 87836b2e7..282fab0d2 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -26,13 +26,10 @@ #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" @@ -43,6 +40,7 @@ #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" @@ -100,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() @@ -227,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)); @@ -237,37 +235,7 @@ 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)); - RegisterCustomStructLayout(*FFlowAssetParamsPtr::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowAssetParamsPtrCustomization::MakeInstance)); - - // Consider implementing details customizations... for every EFlowPinType - FLOW_ASSERT_ENUM_MAX(EFlowPinType, 16); - - // Value-based (new) data pin customizations 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)); @@ -285,7 +253,6 @@ void FFlowEditorModule::RegisterDetailCustomizations() 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)); - RegisterCustomStructLayout(*FFlowDataPinValue_InstancedObject::StaticStruct(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFlowDataPinValueCustomization_InstancedObject::MakeInstance)); PropertyModule.NotifyCustomizationModuleChanged(); } 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 99a8203dc..e6a1aa8d4 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -194,6 +194,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); + } + } } } @@ -344,6 +352,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(); @@ -884,13 +897,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()) @@ -911,13 +924,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()) @@ -1112,10 +1124,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 { @@ -1491,6 +1499,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(); @@ -1507,6 +1519,12 @@ void UFlowGraphNode::OnSubNodeAdded(UFlowGraphNode* SubNode) void UFlowGraphNode::RemoveSubNode(UFlowGraphNode* SubNode) { Modify(); + + if (SubNode->NodeInstance) + { + SubNode->NodeInstance->OnAddOnRequestedParentReconstruction.Unbind(); + } + SubNodes.RemoveSingle(SubNode); RebuildRuntimeAddOnsFromEditorSubNodes(); @@ -1516,6 +1534,14 @@ void UFlowGraphNode::RemoveSubNode(UFlowGraphNode* SubNode) void UFlowGraphNode::RemoveAllSubNodes() { + for (UFlowGraphNode* SubNode : SubNodes) + { + if (SubNode->NodeInstance) + { + SubNode->NodeInstance->OnAddOnRequestedParentReconstruction.Unbind(); + } + } + SubNodes.Reset(); RebuildRuntimeAddOnsFromEditorSubNodes(); @@ -1619,7 +1645,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; } @@ -1709,12 +1735,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 @@ -1783,6 +1807,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(); @@ -1803,10 +1831,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(); @@ -1816,7 +1846,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/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/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 index 0aaf1f1e8..461a4f790 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization.h @@ -2,24 +2,28 @@ #pragma once -#include "Types/FlowDataPinType.h" +#include "Types/FlowPinType.h" #include "Types/FlowDataPinValue.h" +#include "Types/FlowPinEnums.h" #include "UnrealExtensions/IFlowExtendedPropertyTypeCustomization.h" #include "IPropertyTypeCustomization.h" -#include "Templates/UnrealTypeTraits.h" #include "Widgets/Input/SComboBox.h" +#include "Templates/Function.h" -struct FFlowValueSourcePolicy; class IFlowDataPinValueOwnerInterface; /* * Flow Data Pin Value Customization * * Responsibilities: -* - Provides shared header (MultiType selector + Input Pin checkbox + optional lock UI). -* - Builds child rows for single value vs array modes (with auto element creation). -* - Integrates optional source policy (Class / Enum / Object-like specializations derive and supply it). -* - Supplies extensibility points (AppendHeaderExtensions, OnSourceLockToggled, GetSourcePolicy). +* - 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 { @@ -30,30 +34,30 @@ class FLOWEDITOR_API FFlowDataPinValueCustomization : public IFlowExtendedProper TSharedPtr MultiTypeHandle; TSharedPtr ValuesHandle; TSharedPtr IsInputPinHandle; + TSharedPtr PropertyUtilities; // Cached context - const FFlowDataPinType* PinType = nullptr; - IPropertyTypeCustomizationUtils* CustomizationUtils = nullptr; - IFlowDataPinValueOwnerInterface* OwnerInterface = nullptr; - // -- + const FFlowPinType* DataPinType = nullptr; + IPropertyTypeCustomizationUtils* CustomizationUtils = nullptr; + IFlowDataPinValueOwnerInterface* OwnerInterface = nullptr; + + // MultiType UI state (enum values) + TArray> MultiTypeOptions; + TSharedPtr SelectedMultiType; + TSharedPtr>> MultiTypeComboBox; - // MultiType UI state - TArray> MultiTypeOptions; - TSharedPtr SelectedMultiType; - TSharedPtr>> MultiTypeComboBox; + // Cached flag whether this pin type supports Array mode + bool bArraySupported = true; public: - // Construction / Lifetime 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, @@ -63,76 +67,73 @@ class FLOWEDITOR_API FFlowDataPinValueCustomization : public IFlowExtendedProper virtual void CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - // -- protected: - // High-Level Build Flow + // Build flow virtual void BuildValueRows(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils); virtual void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); - virtual void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); + virtual void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); // Skips if !bArraySupported void EnsureSingleElementExists(); - // -- - // Mode / State Queries + void OnPropertyValueChanged(); + void OnChildPropertyValueChanged(); + void RequestRefresh(); + + // Mode / State EFlowDataMultiType GetCurrentMultiType() const; EVisibility GetSingleModeVisibility() const; EVisibility GetArrayModeVisibility() const; void TrimArrayToSingle(); - // -- - // Visual / Appearance - FLinearColor GetRowTint() const; + // Appearance FFlowDataPinValue* GetFlowDataPinValueBeingEdited() const { return IFlowExtendedPropertyTypeCustomization::TryGetTypedStructValue(StructPropertyHandle); } - // -- - - // Header Extensions / Source Lock - virtual void AppendHeaderExtensions(TSharedRef HeaderBox); - virtual void OnSourceLockToggled(); - // -- - - // Optional Source Policy (Overridden in specialized subclasses) - virtual const FFlowValueSourcePolicy* GetSourcePolicy() const { return nullptr; } - // -- - // Input Pin Helpers + // Input Pin ECheckBoxState GetCurrentIsInputPin() const; EVisibility GetInputPinCheckboxVisibility() const; bool GetInputPinCheckboxEnabled() const; - // -- - // UI Generation Helpers - TSharedRef GenerateMultiTypeWidget(TSharedPtr Item) const; + // MultiType UI Helpers + TSharedRef GenerateMultiTypeWidget(TSharedPtr Item) const; FText GetSelectedMultiTypeText() const; - // -- // Change Handlers - void OnMultiTypeChanged(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + 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(); }; -// ------------------------------------------------------------------------ -// Generic Template Customization for Simple Value Types -// ------------------------------------------------------------------------ +// Template customization for simple scalar value structs template class TFlowDataPinValueCustomization : public FFlowDataPinValueCustomization { public: - TFlowDataPinValueCustomization() = default; - static TSharedRef MakeInstance() { return MakeShareable(new TFlowDataPinValueCustomization()); diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h index 228a88e9c..a9fc9d82f 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Class.h @@ -3,48 +3,35 @@ #pragma once #include "DetailCustomizations/FlowDataPinValueCustomization.h" -#include "DetailCustomizations/FlowValueSourcePolicy.h" class SClassPropertyEntryBox; /* -* Class value customization using FFlowValueSourcePolicy: -* - Lock hides/disables only ClassFilter (source) — NOT the class value pickers. -* - Class value rows remain editable (subject to base owner policy & metadata), even when filter locked. +* 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: - FFlowDataPinValueCustomization_Class() = default; - static TSharedRef MakeInstance() { return MakeShareable(new FFlowDataPinValueCustomization_Class()); } - // Non-copyable / non-movable - FFlowDataPinValueCustomization_Class(const FFlowDataPinValueCustomization_Class&) = delete; - FFlowDataPinValueCustomization_Class& operator=(const FFlowDataPinValueCustomization_Class&) = delete; - FFlowDataPinValueCustomization_Class(FFlowDataPinValueCustomization_Class&&) = delete; - FFlowDataPinValueCustomization_Class& operator=(FFlowDataPinValueCustomization_Class&&) = delete; - protected: virtual void BuildValueRows(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - virtual void OnSourceLockToggled() override; - virtual const FFlowValueSourcePolicy* GetSourcePolicy() const override { return &SourcePolicy; } - private: // Property handles TSharedPtr ClassFilterHandle; - // Policy - FFlowValueSourcePolicy SourcePolicy; - // Metadata-derived flags const UClass* RequiredInterface = nullptr; bool bAllowAbstract = true; @@ -53,34 +40,45 @@ class FLOWEDITOR_API FFlowDataPinValueCustomization_Class : public FFlowDataPinV bool bShowTreeView = false; bool bHideViewOptions = false; bool bShowDisplayNames = false; - bool bMetaClassForced = false; + bool bHasMetaClass = false; - // Cached effective filter + // Effective filter TWeakObjectPtr CachedEffectiveFilter; // Helpers void ExtractMetadata(); - void ComputePolicy(); - void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder); + 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); - void BindValidationDelegates(); + // Delegates / validation + void BindDelegates(); void OnClassFilterChanged(); void OnValuesChanged(); - void TrySetClassFilterFromMetaData(); - UClass* DeriveBestClassFilter() const; - void RefreshEffectiveFilter(); - void ValidateAllElements(); - void ValidateElement(const TSharedPtr& ElementHandle, UClass* FilterClass); + 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; - bool IsNoneString(const FString& Str) 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 index fc68cf7aa..2c73e2b0b 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Enum.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Enum.h @@ -3,66 +3,46 @@ #pragma once #include "DetailCustomizations/FlowDataPinValueCustomization.h" -#include "DetailCustomizations/FlowValueSourcePolicy.h" -#include "UnrealExtensions/VisibilityArrayBuilder.h" class UEnum; -struct FFlowDataPinValue_Enum; /* * Enum customization: -* - Lock affects only EnumClass / EnumName (source). -* - Enumerator value selection remains editable (independent of lock). -* - Uses policy field bFinalEditableSource for source rows. +* - 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: - FFlowDataPinValueCustomization_Enum() = default; - static TSharedRef MakeInstance() { return MakeShareable(new FFlowDataPinValueCustomization_Enum()); } - // Non-copyable / non-movable - FFlowDataPinValueCustomization_Enum(const FFlowDataPinValueCustomization_Enum&) = delete; - FFlowDataPinValueCustomization_Enum& operator=(const FFlowDataPinValueCustomization_Enum&) = delete; - FFlowDataPinValueCustomization_Enum(FFlowDataPinValueCustomization_Enum&&) = delete; - FFlowDataPinValueCustomization_Enum& operator=(FFlowDataPinValueCustomization_Enum&&) = delete; - protected: virtual void BuildValueRows(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - virtual void OnSourceLockToggled() override; - virtual const FFlowValueSourcePolicy* GetSourcePolicy() const override { return &SourcePolicy; } - private: // Source handles TSharedPtr EnumClassHandle; TSharedPtr EnumNameHandle; - // Policy - FFlowValueSourcePolicy SourcePolicy; - - // Lock / value state - TSharedPtr LockEnumHandle; + // Enumerator state TArray> EnumeratorOptions; bool bEnumResolved = false; bool bMultiTypeDelegateBound = false; - // Builders + // Build helpers void BuildSingle(IDetailChildrenBuilder& StructBuilder); void BuildArray(IDetailChildrenBuilder& StructBuilder); - // Visibility helpers - EVisibility GetSingleVisibility() const; - EVisibility GetArrayVisibility() const; - // Enum resolution void CacheEnumHandles(const TSharedRef& StructHandle); void OnEnumSourceChanged(); @@ -97,11 +77,13 @@ class FLOWEDITOR_API FFlowDataPinValueCustomization_Enum : public FFlowDataPinVa ESelectInfo::Type SelectInfo, TSharedPtr ElementHandle); - // Policy compute - void ComputePolicy(); - // Convenience - FFlowDataPinValue_Enum* GetEnumValueStruct() const; + 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_InstancedObject.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h deleted file mode 100644 index b3ac4254d..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h" -#include "ClassViewerFilter.h" -#include "UObject/WeakObjectPtr.h" - -/* -* InstancedObject customization: -* - ClassFilter row: allows abstract classes, but requires EditInlineNew and interface (MustImplement) if specified. -* - Instantiation picker (when value is null): requires non-abstract + EditInlineNew + interface + subclass of EffectiveFilterClass. -* - If no instantiable subclasses exist, shows a message instead of picker. -* - Lock only affects ClassFilter editing (not instantiation). -* - Direct pointer assignment for instanced objects. -*/ -class FLOWEDITOR_API FFlowDataPinValueCustomization_InstancedObject : public FFlowDataPinValueCustomization_ObjectBase -{ - using Super = FFlowDataPinValueCustomization_ObjectBase; - -public: - FFlowDataPinValueCustomization_InstancedObject() = default; - - static TSharedRef MakeInstance() - { - return MakeShareable(new FFlowDataPinValueCustomization_InstancedObject()); - } - - // Non-copyable / non-movable - FFlowDataPinValueCustomization_InstancedObject(const FFlowDataPinValueCustomization_InstancedObject&) = delete; - FFlowDataPinValueCustomization_InstancedObject& operator=(const FFlowDataPinValueCustomization_InstancedObject&) = delete; - FFlowDataPinValueCustomization_InstancedObject(FFlowDataPinValueCustomization_InstancedObject&&) = delete; - FFlowDataPinValueCustomization_InstancedObject& operator=(FFlowDataPinValueCustomization_InstancedObject&&) = delete; - -protected: - virtual bool SupportsInlineCreation() const override { return true; } - virtual void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder) override; - virtual TSharedRef BuildObjectValueWidgetForElement(TSharedPtr ElementHandle) override; - -private: - class FClassFilterRowFilter : public IClassViewerFilter - { - public: - TWeakObjectPtr Base; - TWeakObjectPtr RequiredInterface; - - virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, - const UClass* InClass, - TSharedRef InFilterFuncs) override; - - virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions&, - const TSharedRef InUnloadedClassData, - TSharedRef) override; - }; - - class FInstantiationFilter : public IClassViewerFilter - { - public: - TWeakObjectPtr Base; - TWeakObjectPtr RequiredInterface; - - virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, - const UClass* InClass, - TSharedRef InFilterFuncs) override; - - virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions&, - const TSharedRef InUnloadedClassData, - TSharedRef) override; - }; - - // Metadata (MustImplement interface) - UClass* RequiredInterface = nullptr; - - // Cached scan result for instantiable subclasses - mutable bool bInstantiableScanDone = false; - mutable bool bHasAnyInstantiable = false; - - // Metadata / filtering - void ExtractInterfaceMetadata(); - void BuildClassFilterFilters(TArray>& Out) const; - void BuildInstantiationFilters(TArray>& Out) const; - - // Instantiation scanning - bool ScanForAnyInstantiable() const; - bool CanInstantiateClass(const UClass* Candidate) const; - - // Object creation / assignment - void InstantiateForHandle(TSharedPtr ElementHandle, const UClass* ChosenClass); - UObject* ResolveOuterForNewObject() const; - bool SetInstancedObjectHandleDirect(TSharedPtr ElementHandle, UObject* NewValue); - - // Per-element change tracking - void BindPerElementValueChange(TSharedPtr ElementHandle); - void OnElementValueChanged(TSharedPtr ElementHandle); -}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h index d6f4499a9..0c678d48d 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_Object.h @@ -2,27 +2,66 @@ #pragma once -#include "DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h" +#include "DetailCustomizations/FlowDataPinValueCustomization.h" /* -* Concrete customization for FFlowDataPinValue_Object. -* Relies entirely on ObjectBase shared behavior (no inline creation). +* 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_ObjectBase +class FLOWEDITOR_API FFlowDataPinValueCustomization_Object : public FFlowDataPinValueCustomization { - using Super = FFlowDataPinValueCustomization_ObjectBase; + using Super = FFlowDataPinValueCustomization; public: - FFlowDataPinValueCustomization_Object() = default; - static TSharedRef MakeInstance() { return MakeShareable(new FFlowDataPinValueCustomization_Object()); } - // Non-copyable / non-movable - FFlowDataPinValueCustomization_Object(const FFlowDataPinValueCustomization_Object&) = delete; - FFlowDataPinValueCustomization_Object& operator=(const FFlowDataPinValueCustomization_Object&) = delete; - FFlowDataPinValueCustomization_Object(FFlowDataPinValueCustomization_Object&&) = delete; - FFlowDataPinValueCustomization_Object& operator=(FFlowDataPinValueCustomization_Object&&) = delete; +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/FlowDataPinValueCustomization_ObjectBase.h b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h deleted file mode 100644 index 479bb0efd..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueCustomization_ObjectBase.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "DetailCustomizations/FlowDataPinValueCustomization.h" -#include "DetailCustomizations/FlowValueSourcePolicy.h" - -/* -* Base for object-like pin value customizations (Object, InstancedObject). -* - Computes & applies source policy (lock only affects ClassFilter) -* - Adds (optionally) a ClassFilter property row -* - Validates object reference values against ClassFilter -* - Offers single / array layout; element value widget supplied by derived class -*/ -class FLOWEDITOR_API FFlowDataPinValueCustomization_ObjectBase : public FFlowDataPinValueCustomization -{ - using Super = FFlowDataPinValueCustomization; - -public: - FFlowDataPinValueCustomization_ObjectBase() = default; - virtual ~FFlowDataPinValueCustomization_ObjectBase() = default; - - // Non-copyable / non-movable - FFlowDataPinValueCustomization_ObjectBase(const FFlowDataPinValueCustomization_ObjectBase&) = delete; - FFlowDataPinValueCustomization_ObjectBase& operator=(const FFlowDataPinValueCustomization_ObjectBase&) = delete; - FFlowDataPinValueCustomization_ObjectBase(FFlowDataPinValueCustomization_ObjectBase&&) = delete; - FFlowDataPinValueCustomization_ObjectBase& operator=(FFlowDataPinValueCustomization_ObjectBase&&) = delete; - -protected: - virtual void BuildValueRows(TSharedRef InStructPropertyHandle, - IDetailChildrenBuilder& StructBuilder, - IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - - virtual void OnSourceLockToggled() override; - virtual const FFlowValueSourcePolicy* GetSourcePolicy() const override { return &SourcePolicy; } - -protected: - virtual bool SupportsInlineCreation() const { return false; } - virtual TSharedRef BuildObjectValueWidgetForElement(TSharedPtr ElementHandle); - virtual void BuildClassFilterRow(IDetailChildrenBuilder& StructBuilder); - -protected: - // Property handles - TSharedPtr ClassFilterHandle; - - // Policy - FFlowValueSourcePolicy SourcePolicy; - - // Metadata / filter state - bool bMetaClassForced = false; - TWeakObjectPtr EffectiveFilterClass; - - // Layout helpers - void BuildSingleBranch(IDetailChildrenBuilder& StructBuilder); - void BuildArrayBranch(IDetailChildrenBuilder& StructBuilder); - void GenerateArrayElementRow(TSharedRef ElementHandle, - int32 Index, - IDetailChildrenBuilder& ChildBuilder, - const TAttribute& RowVisibility); - - // Policy computation - void ComputePolicy(); - - // Meta-class handling - void TryApplyMetaClass(); - void ResolveEffectiveFilter(); - - // Delegates & validation - void BindDelegates(); - void OnClassFilterChanged(); - void OnValuesChanged(); - void ValidateAll(); - void ValidateElement(TSharedPtr ElementHandle, UClass* Filter); - - // Value access - UObject* GetObjectValue(TSharedPtr ElementHandle) const; - void SetObjectValue(TSharedPtr ElementHandle, UObject* NewObj); - UClass* GetCurrentFilterClassProperty() const; -}; \ 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 index c58769a80..0d8767e61 100644 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h +++ b/Source/FlowEditor/Public/DetailCustomizations/FlowDataPinValueStandardCustomizations.h @@ -9,7 +9,6 @@ #include "DetailCustomizations/FlowDataPinValueCustomization_Enum.h" #include "DetailCustomizations/FlowDataPinValueCustomization_Class.h" #include "DetailCustomizations/FlowDataPinValueCustomization_Object.h" -#include "DetailCustomizations/FlowDataPinValueCustomization_InstancedObject.h" // Scalar / simple using aliases using FFlowDataPinValueCustomization_Bool = 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/DetailCustomizations/FlowValueSourcePolicy.h b/Source/FlowEditor/Public/DetailCustomizations/FlowValueSourcePolicy.h deleted file mode 100644 index 3d9941ad5..000000000 --- a/Source/FlowEditor/Public/DetailCustomizations/FlowValueSourcePolicy.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors - -#pragma once - -#include "Interfaces/FlowDataPinValueOwnerInterface.h" - -/* -* Unified source (ClassFilter / Enum source) policy describing visibility & editability. -* Extended to separate source editability from value editability so that -* locking the source (filter) does NOT lock the value pickers. -*/ -struct FFlowValueSourcePolicy -{ - // Show the source row at all (ClassFilter / EnumClass / EnumName) - bool bShowSourceRow = true; - - // Show the header "Lock" control - bool bShowLockToggle = true; - - // Per-value lock flag (NOT including metadata forcing) - bool bLocked = false; - - // Metadata (MetaClass) forces the filter (Class/Object/InstancedObject) - bool bMetaForced = false; - - // Editability prior to per-value lock (applies to both source & values) - bool bBaseEditable = true; - - // Final editability of the SOURCE (filter / enum class+name) after applying lock - bool bFinalEditableSource = true; - - // Final editability of the VALUES (class picks, object refs, enumerators) - // Does NOT consider the per-value lock (lock only affects source). - bool bFinalEditableValues = true; -}; - -/* -* Computes a unified policy. -* Lock affects only the source editability; value editability ignores the lock. -*/ -inline FFlowValueSourcePolicy ComputeFlowValueSourcePolicy( - IFlowDataPinValueOwnerInterface* Owner, - const FFlowDataPinValue* Value, - bool bHasMetaClass, - bool bPerValueLock, - bool bLockFieldExists) -{ - FFlowValueSourcePolicy P; - - P.bMetaForced = bHasMetaClass; - P.bShowSourceRow = Owner ? Owner->ShowFlowDataPinValueClassFilter(Value) : true; - - P.bShowLockToggle = - P.bShowSourceRow && - bLockFieldExists && - (Owner ? Owner->ShowFlowDataPinValueClassFilterLockToggle(Value) : true) && - !P.bMetaForced; - - P.bBaseEditable = - !P.bMetaForced && - (Owner ? Owner->CanEditFlowDataPinValueClassFilter(Value) : true); - - // Lock only impacts source, not values - P.bLocked = bPerValueLock; - - P.bFinalEditableSource = P.bBaseEditable && !P.bLocked; - - // Ignore lock for values - P.bFinalEditableValues = P.bBaseEditable; - - return P; -} \ No newline at end of file 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 6c5e3d9ca..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; @@ -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 From 475752a1c4a78ebfc772b1ea908aa8b8f4df18a5 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:34:51 -0800 Subject: [PATCH 5/6] removed some commented-out code that slipped in. removed some commented-out code that slipped in. --- Source/FlowEditor/FlowEditor.Build.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/FlowEditor/FlowEditor.Build.cs b/Source/FlowEditor/FlowEditor.Build.cs index 6bca53f9a..86bf67a09 100644 --- a/Source/FlowEditor/FlowEditor.Build.cs +++ b/Source/FlowEditor/FlowEditor.Build.cs @@ -61,9 +61,6 @@ public FlowEditor(ReadOnlyTargetRules target) : base(target) "Slate", "SlateCore", "SourceControl", - // #UE56Fix - //"StructUtils", - // "ToolMenus", "UnrealEd" }); From 573ffd7eb860a39908846cb6f21fc53605834d23 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:27:25 -0800 Subject: [PATCH 6/6] Missing file? --- Source/Flow/Public/FlowAsset.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index d4713fac2..2b5ec6f2e 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -91,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;