From 6d7ac95b2fdce4b304527876afd224ffdae624b0 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:57:16 -0800 Subject: [PATCH 1/9] Search improvements vs. flow mainline --- .../Nodes/Graph/FlowNode_FormatText.cpp | 6 +- .../Private/Asset/FlowObjectDiff.cpp | 2 +- Source/FlowEditor/Private/Asset/SFlowDiff.cpp | 51 +- Source/FlowEditor/Private/Find/FindInFlow.cpp | 1007 +++++++++++++---- .../Private/Find/SFindInFlowFilterPopup.cpp | 145 +++ .../FlowEditor/Private/FlowEditorModule.cpp | 41 + .../Private/Graph/Widgets/SFlowGraphNode.cpp | 4 +- Source/FlowEditor/Public/Find/FindInFlow.h | 150 ++- .../FlowEditor/Public/Find/FindInFlowEnums.h | 48 + .../Public/Find/SFindInFlowFilterPopup.h | 42 + Source/FlowEditor/Public/FlowEditorModule.h | 9 +- .../Public/Graph/FlowGraphEditorSettings.h | 10 + .../FlowEditor/Public/Graph/FlowGraphSchema.h | 3 + 13 files changed, 1210 insertions(+), 308 deletions(-) create mode 100644 Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp create mode 100644 Source/FlowEditor/Public/Find/FindInFlowEnums.h create mode 100644 Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp index e6564105d..13c055433 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp @@ -7,6 +7,8 @@ #define LOCTEXT_NAMESPACE "FlowNode_FormatText" +const FName UFlowNode_FormatText::OUTPIN_TextOutput("Formatted Text"); + UFlowNode_FormatText::UFlowNode_FormatText(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -15,12 +17,12 @@ UFlowNode_FormatText::UFlowNode_FormatText(const FObjectInitializer& ObjectIniti NodeDisplayStyle = FlowNodeStyle::Terminal; #endif - OutputPins.Add(FFlowPin(TEXT("Formatted Text"), FFlowPinType_Text::GetPinTypeNameStatic())); + OutputPins.Add(FFlowPin(OUTPIN_TextOutput, FFlowPinType_Text::GetPinTypeNameStatic())); } FFlowDataPinResult UFlowNode_FormatText::TrySupplyDataPin_Implementation(FName PinName) const { - if (PinName == TEXT("Formatted Text")) + if (PinName == OUTPIN_TextOutput) { FText FormattedText; const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); diff --git a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp index 270eab9ac..dd94960d2 100644 --- a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp +++ b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp @@ -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 96540b6dc..2b1be2976 100644 --- a/Source/FlowEditor/Private/Asset/SFlowDiff.cpp +++ b/Source/FlowEditor/Private/Asset/SFlowDiff.cpp @@ -1,9 +1,10 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Asset/SFlowDiff.h" -#include "Asset/FlowDiffControl.h" +#include "Asset/FlowDiffControl.h" #include "FlowAsset.h" +#include "Graph/Nodes/FlowGraphNode.h" #include "EdGraphUtilities.h" #include "Editor.h" @@ -38,11 +39,11 @@ static int32 GetCurrentIndex(SListView> const& Lis const TArray>& Selected = ListView.GetSelectedItems(); if (Selected.Num() == 1) { - for (const TSharedPtr& Diff : ListViewSource) + for (int32 Index = 0; Index < ListViewSource.Num(); ++Index) { - if (Diff == Selected[0]) + if (ListViewSource[Index] == Selected[0]) { - return 0; + return Index; } } } @@ -52,23 +53,21 @@ static int32 GetCurrentIndex(SListView> const& Lis void FlowDiffUtils::SelectNextRow(SListView>& ListView, const TArray>& ListViewSource) { const int32 CurrentIndex = GetCurrentIndex(ListView, ListViewSource); - if (CurrentIndex == ListViewSource.Num() - 1) + const int32 NextIndex = CurrentIndex + 1; + if (ListViewSource.IsValidIndex(NextIndex)) { - return; + ListView.SetSelection(ListViewSource[NextIndex]); } - - ListView.SetSelection(ListViewSource[CurrentIndex + 1]); } void FlowDiffUtils::SelectPrevRow(SListView>& ListView, const TArray>& ListViewSource) { const int32 CurrentIndex = GetCurrentIndex(ListView, ListViewSource); - if (CurrentIndex == 0) + const int32 PrevIndex = CurrentIndex - 1; + if (ListViewSource.IsValidIndex(PrevIndex)) { - return; + ListView.SetSelection(ListViewSource[PrevIndex]); } - - ListView.SetSelection(ListViewSource[CurrentIndex - 1]); } bool FlowDiffUtils::HasNextDifference(const SListView>& ListView, const TArray>& ListViewSource) @@ -538,16 +537,28 @@ void FFlowDiffPanel::GeneratePanel(UEdGraph* Graph, TSharedPtr(); - GraphEditorCommands->MapAction(FGenericCommands::Get().Copy, - FExecuteAction::CreateRaw(this, &FFlowDiffPanel::CopySelectedNodes), - FCanExecuteAction::CreateRaw(this, &FFlowDiffPanel::CanCopyNodes) + GraphEditorCommands->MapAction( + FGenericCommands::Get().Copy, + FExecuteAction::CreateRaw(this, &FFlowDiffPanel::CopySelectedNodes), + FCanExecuteAction::CreateRaw(this, &FFlowDiffPanel::CanCopyNodes) ); } @@ -683,14 +694,14 @@ void SFlowDiff::HandleGraphChanged(const FString& GraphPath) const TAttribute FocusedDiffResult = TAttribute::CreateLambda( [this, RealDifferencesStartIndex]() { - int32 FocusedDiffResult = INDEX_NONE; + int32 FocusedIndex = INDEX_NONE; if (RealDifferencesStartIndex != INDEX_NONE) { - FocusedDiffResult = DiffTreeView::CurrentDifference(DifferencesTreeView.ToSharedRef(), RealDifferences) - RealDifferencesStartIndex; + FocusedIndex = DiffTreeView::CurrentDifference(DifferencesTreeView.ToSharedRef(), RealDifferences) - RealDifferencesStartIndex; } // find selected index in all the graphs, and subtract the index of the first entry in this graph - return FocusedDiffResult; + return FocusedIndex; }); // only regenerate PanelOld if the old graph has changed @@ -934,4 +945,4 @@ void SFlowDiff::OnModeChanged(const FName& InNewViewMode) const UpdateTopSectionVisibility(InNewViewMode); } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/Find/FindInFlow.cpp b/Source/FlowEditor/Private/Find/FindInFlow.cpp index d098da8d5..648728c78 100644 --- a/Source/FlowEditor/Private/Find/FindInFlow.cpp +++ b/Source/FlowEditor/Private/Find/FindInFlow.cpp @@ -2,14 +2,20 @@ #include "Find/FindInFlow.h" #include "Asset/FlowAssetEditor.h" +#include "Find/SFindInFlowFilterPopup.h" #include "Graph/FlowGraphEditor.h" +#include "Graph/FlowGraphEditorSettings.h" #include "Graph/FlowGraphUtils.h" #include "Graph/Nodes/FlowGraphNode.h" - #include "FlowAsset.h" +#include "FlowEditorModule.h" #include "Nodes/FlowNode.h" +#include "Nodes/FlowNodeBase.h" +#include "AddOns/FlowNodeAddOn.h" #include "Nodes/Graph/FlowNode_SubGraph.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetRegistry/ARFilter.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "Framework/Application/SlateApplication.h" @@ -22,16 +28,22 @@ #include "Layout/WidgetPath.h" #include "Math/Color.h" #include "Misc/Attribute.h" +#include "Misc/EnumRange.h" +#include "Misc/ScopedSlowTask.h" #include "SlotBase.h" +#include "Subsystems/AssetEditorSubsystem.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Templates/Casts.h" #include "Types/SlateStructs.h" #include "UObject/Class.h" #include "UObject/ObjectPtr.h" +#include "UObject/TopLevelAssetPath.h" #include "Widgets/Images/SImage.h" -#include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SSearchBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SSpinBox.h" +#include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" @@ -39,22 +51,58 @@ #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/STableRow.h" -class ITableRow; -class SWidget; -struct FSlateBrush; - #define LOCTEXT_NAMESPACE "FindInFlow" +////////////////////////////////////////////////////////////////////////// +// FFindInFlowCache + +TMap, TMap>> FFindInFlowCache::CategoryStringCache; + +void FFindInFlowCache::OnFlowAssetChanged(UFlowAsset& ChangedFlowAsset) +{ + TArray> EntriesToRemove; + + for (const auto& KV : CategoryStringCache) + { + const TWeakObjectPtr& EdNodePtr = KV.Key; + + UEdGraphNode* EdNode = EdNodePtr.Get(); + + if (!IsValid(EdNode)) + { + EntriesToRemove.Add(EdNodePtr); + + continue; + } + + UEdGraph* EdGraph = ChangedFlowAsset.GetGraph(); + if (EdGraph->Nodes.Contains(EdNode)) + { + EntriesToRemove.Add(EdNodePtr); + } + } + + for (const TWeakObjectPtr& EdNodePtr : EntriesToRemove) + { + CategoryStringCache.Remove(EdNodePtr); + } +} + ////////////////////////////////////////////////////////////////////////// // FFindInFlowResult -FFindInFlowResult::FFindInFlowResult(const FString& InValue) - : Value(InValue), GraphNode(nullptr) +FFindInFlowResult::FFindInFlowResult(const FString& InValue, UFlowAsset* InOwningFlowAsset) + : Value(InValue) + , OwningFlowAsset(InOwningFlowAsset) { } -FFindInFlowResult::FFindInFlowResult(const FString& InValue, TSharedPtr& InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode) - : Value(InValue), GraphNode(InNode), Parent(InParent), bIsSubGraphNode(bInIsSubGraphNode) +FFindInFlowResult::FFindInFlowResult(const FString& InValue, TSharedPtr InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode, UFlowAsset* InOwningFlowAsset) + : Value(InValue) + , GraphNode(InNode) + , OwningFlowAsset(InOwningFlowAsset) + , Parent(InParent) + , bIsSubGraphNode(bInIsSubGraphNode) { } @@ -68,50 +116,49 @@ TSharedRef FFindInFlowResult::CreateIcon() const .ColorAndOpacity(IconColor); } -FReply FFindInFlowResult::OnClick(TWeakPtr FlowAssetEditorPtr, TSharedPtr Root) +FReply FFindInFlowResult::OnClick(TWeakPtr FlowAssetEditorPtr) { - if (FlowAssetEditorPtr.IsValid() && GraphNode.IsValid()) + if (GraphNode.IsValid()) { - if (Parent.IsValid() && !bIsSubGraphNode) - { - FlowAssetEditorPtr.Pin()->JumpToNode(GraphNode.Get()); - } - else + if (UEdGraph* Graph = GraphNode->GetGraph()) { - FlowAssetEditorPtr.Pin()->JumpToNode(Parent.Pin()->GraphNode.Get()); + if (UFlowAsset* Asset = Cast(Graph->GetOuter())) + { + GEditor->GetEditorSubsystem()->OpenEditorForAsset(Asset); + if (TSharedPtr Editor = FFlowGraphUtils::GetFlowAssetEditor(Graph)) + { + Editor->JumpToNode(GraphNode.Get()); + } + } } } - + else if (OwningFlowAsset.IsValid()) + { + GEditor->GetEditorSubsystem()->OpenEditorForAsset(OwningFlowAsset.Get()); + } return FReply::Handled(); } -FReply FFindInFlowResult::OnDoubleClick(TSharedPtr Root) const +FReply FFindInFlowResult::OnDoubleClick() const { - if (!Parent.IsValid() || !bIsSubGraphNode) + if (bIsSubGraphNode && Parent.IsValid()) { - return FReply::Handled(); - } - const UFlowGraphNode* ParentGraphNode = Cast(Parent.Pin()->GraphNode); - if (!ParentGraphNode || !ParentGraphNode->GetFlowNodeBase()) - { - return FReply::Handled(); - } - - if (UFlowNode* FlowNode = Cast(ParentGraphNode->GetFlowNodeBase())) - { - if (UObject* AssetToEdit = FlowNode->GetAssetToEdit()) + if (const UFlowGraphNode* ParentNode = Cast(Parent.Pin()->GraphNode.Get())) { - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - if (AssetEditorSubsystem->OpenEditorForAsset(AssetToEdit)) + if (UFlowNode_SubGraph* SubGraph = Cast(ParentNode->GetFlowNodeBase())) { - if (const TSharedPtr FlowAssetEditor = FFlowGraphUtils::GetFlowAssetEditor(GraphNode->GetGraph())) + if (UObject* Target = SubGraph->GetAssetToEdit()) { - FlowAssetEditor->JumpToNode(GraphNode.Get()); + GEditor->GetEditorSubsystem()->OpenEditorForAsset(Target); + if (TSharedPtr Editor = FFlowGraphUtils::GetFlowAssetEditor(GraphNode->GetGraph())) + { + Editor->JumpToNode(GraphNode.Get()); + } } } } } - + return FReply::Handled(); } @@ -127,304 +174,789 @@ FString FFindInFlowResult::GetDescriptionText() const FString FFindInFlowResult::GetCommentText() const { - if (GraphNode.IsValid()) - { - return GraphNode->NodeComment; - } - - return FString(); + return GraphNode.IsValid() ? GraphNode->NodeComment : FString(); } FString FFindInFlowResult::GetNodeTypeText() const { - if (GraphNode.IsValid()) + if (!GraphNode.IsValid()) { - FString NodeClassName; - const UFlowGraphNode* FlowGraphNode = Cast(GraphNode.Get()); - if (FlowGraphNode && FlowGraphNode->GetFlowNodeBase()) - { - NodeClassName = FlowGraphNode->GetFlowNodeBase()->GetClass()->GetName(); - } - else - { - NodeClassName = GraphNode->GetClass()->GetName(); - } - const int32 Pos = NodeClassName.Find("_"); - if (Pos == INDEX_NONE) - { - return NodeClassName; - } - else + return FString(); + } + + if (const UFlowGraphNode* FlowGraphNode = Cast(GraphNode.Get())) + { + if (UFlowNodeBase* Base = FlowGraphNode->GetFlowNodeBase()) { - return NodeClassName.RightChop(Pos + 1); + return Base->GetClass()->GetDisplayNameText().ToString(); } } - return FString(); + return GraphNode->GetClass()->GetDisplayNameText().ToString(); } FText FFindInFlowResult::GetToolTipText() const { - FString ToolTipStr = TEXT("Click to focus on nodes."); - if (bIsSubGraphNode) + FString Tip = GetNodeTypeText() + TEXT("\n") + GetDescriptionText(); + + if (!GetCommentText().IsEmpty()) { - ToolTipStr += TEXT("\nDouble click to focus on subgraph nodes"); + Tip += TEXT("\n") + GetCommentText(); } - return FText::FromString(ToolTipStr); + + if (!MatchedPropertySnippet.IsEmpty()) + { + Tip += TEXT("\n\nMatched: ") + MatchedPropertySnippet; + } + + return FText::FromString(Tip); +} + +FText FFindInFlowResult::GetMatchedSnippet() const +{ + return FText::FromString(MatchedPropertySnippet); +} + +FText FFindInFlowResult::GetMatchedCategoriesText() const +{ + if (MatchedFlags == EFlowSearchFlags::None) + { + return FText::GetEmpty(); + } + + TArray DisplayNames; + + for (EFlowSearchFlags Flag : MakeFlagsRange(EFlowSearchFlags::All)) + { + if (EnumHasAnyFlags(MatchedFlags, Flag)) + { + FText DisplayName = UEnum::GetDisplayValueAsText(Flag); + if (!DisplayName.IsEmpty()) + { + DisplayNames.Add(DisplayName); + } + } + } + + if (DisplayNames.Num() == 0) + { + return FText::GetEmpty(); + } + + return FText::Join(FText::FromString(TEXT(", ")), DisplayNames); } ////////////////////////////////////////////////////////////////////////// // SFindInFlow -void SFindInFlow::Construct( const FArguments& InArgs, TSharedPtr InFlowAssetEditor) +void SFindInFlow::Construct(const FArguments& InArgs, TSharedPtr InFlowAssetEditor) { FlowAssetEditorPtr = InFlowAssetEditor; + SearchResults.Setup(); + + // Load INI settings + const UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get(); + if (ensure(Settings)) + { + MaxSearchDepth = Settings->DefaultMaxSearchDepth; + SearchFlags = static_cast(Settings->DefaultSearchFlags); + } - this->ChildSlot + // Populate scope options + FLOW_ASSERT_ENUM_MAX(EFlowSearchScope, 3); + for (EFlowSearchScope Scope : TEnumRange()) + { + if (FlowEnum::IsValidEnumValue(Scope)) + { + ScopeOptionList.Add(MakeShareable(new EFlowSearchScope(Scope))); + } + } + SelectedScopeOption = ScopeOptionList[0]; + + SAssignNew(SearchTextField, SSearchBox) + .OnTextCommitted(this, &SFindInFlow::OnSearchTextCommitted); + + SAssignNew(SearchButton, SButton) + .Text(LOCTEXT("SearchButton", "Search")) + .OnClicked(this, &SFindInFlow::OnSearchButtonClicked); + + SAssignNew(MaxDepthSpinBox, SSpinBox) + .MinValue(0) + .MaxValue(10) + .Value(MaxSearchDepth) + .OnValueChanged(this, &SFindInFlow::OnMaxDepthChanged) + .ToolTipText(LOCTEXT("MaxDepthTooltip", "Maximum recursion depth when searching inside objects")); + + SAssignNew(TreeView, STreeViewType) + .TreeItemsSource(&SearchResults.ItemsFound) + .OnGenerateRow(this, &SFindInFlow::OnGenerateRow) + .OnGetChildren(this, &SFindInFlow::OnGetChildren) + .OnSelectionChanged(this, &SFindInFlow::OnTreeSelectionChanged) + .OnMouseButtonDoubleClick(this, &SFindInFlow::OnTreeSelectionDoubleClicked); + + ChildSlot [ SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .FillWidth(1) - [ - SAssignNew(SearchTextField, SSearchBox) - .HintText(LOCTEXT("FlowEditorSearchHint", "Enter text to find nodes...")) - .OnTextChanged(this, &SFindInFlow::OnSearchTextChanged) - .OnTextCommitted(this, &SFindInFlow::OnSearchTextCommitted) - ] - +SHorizontalBox::Slot() - .Padding(10,0,5,0) - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(LOCTEXT("FlowEditorSubGraphSearchText", "Find In SubGraph ")) - ] - +SHorizontalBox::Slot() - .AutoWidth() + + SVerticalBox::Slot() + .AutoHeight() [ - SNew(SCheckBox) - .OnCheckStateChanged(this, &SFindInFlow::OnFindInSubGraphStateChanged) - .ToolTipText(LOCTEXT("FlowEditorSubGraphSearchHint", "Checkin means search also in sub graph.")) + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SearchTextField.ToSharedRef() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SearchButton.ToSharedRef() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(LOCTEXT("FiltersLabel", "Filters:")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(SButton) + .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") + .ToolTipText(LOCTEXT("EditFiltersTooltip", "Edit search filters")) + .OnClicked_Lambda([this]() + { + FFindInFlowApplyDelegate OnSaveAsDefault = FFindInFlowApplyDelegate::CreateLambda([this](EFlowSearchFlags Flags) + { + if (UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get()) + { + Settings->DefaultSearchFlags = static_cast(Flags); + Settings->SaveConfig(); + } + }); + + TSharedRef FilterPopup = SNew(SFindInFlowFilterPopup) + .OnApply(FFindInFlowApplyDelegate::CreateLambda([this](EFlowSearchFlags NewSearchFlags) + { + SearchFlags = NewSearchFlags; + + InitiateSearch(); + })) + .OnSaveAsDefault(OnSaveAsDefault) + .InitialFlags(SearchFlags); + + FSlateApplication::Get().PushMenu( + AsShared(), + FWidgetPath(), + FilterPopup, + FSlateApplication::Get().GetCursorPos(), + FPopupTransitionEffect::ContextMenu); + + return FReply::Handled(); + }) + [ + SNew(STextBlock) + .Text_Lambda([this]() + { + int32 ActiveCount = FMath::CountBits(static_cast(SearchFlags)); + return FText::Format(LOCTEXT("ActiveFilters", "{0} Active"), FText::AsNumber(ActiveCount)); + }) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(SComboBox>) + .OptionsSource(&ScopeOptionList) + .OnGenerateWidget(this, &SFindInFlow::GenerateScopeWidget) + .OnSelectionChanged(this, &SFindInFlow::OnScopeChanged) + [ + SNew(STextBlock).Text(this, &SFindInFlow::GetCurrentScopeText) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(LOCTEXT("MaxDepthLabel", "Max Depth:")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + MaxDepthSpinBox.ToSharedRef() + ] ] - ] - +SVerticalBox::Slot() - .FillHeight(1.0f) - .Padding(0.f, 4.f, 0.f, 0.f) - [ - SNew(SBorder) - .BorderImage(FAppStyle::GetBrush("Menu.Background")) + + SVerticalBox::Slot() + .FillHeight(1.0f) [ - SAssignNew(TreeView, STreeViewType) - .TreeItemsSource(&ItemsFound) - .OnGenerateRow(this, &SFindInFlow::OnGenerateRow) - .OnGetChildren(this, &SFindInFlow::OnGetChildren) - .OnSelectionChanged(this, &SFindInFlow::OnTreeSelectionChanged) - .OnMouseButtonDoubleClick(this, &SFindInFlow::OnTreeSelectionDoubleClicked) - .SelectionMode(ESelectionMode::Multi) + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + [ + TreeView.ToSharedRef() + ] ] - ] ]; } void SFindInFlow::FocusForUse() const { - // NOTE: Careful, GeneratePathToWidget can be reentrant in that it can call visibility delegates and such - FWidgetPath FilterTextBoxWidgetPath; - FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchTextField.ToSharedRef(), FilterTextBoxWidgetPath); - - // Set keyboard focus directly - FSlateApplication::Get().SetKeyboardFocus(FilterTextBoxWidgetPath, EFocusCause::SetDirectly); + if (SearchTextField.IsValid()) + { + FSlateApplication::Get().SetKeyboardFocus(SearchTextField.ToSharedRef()); + SearchTextField->SelectAllText(); + } } void SFindInFlow::OnSearchTextChanged(const FText& Text) { SearchValue = Text.ToString(); - +} + +void SFindInFlow::OnSearchTextCommitted(const FText& Text, ETextCommit::Type) +{ + SearchValue = Text.ToString(); + + InitiateSearch(); +} + +FReply SFindInFlow::OnSearchButtonClicked() +{ InitiateSearch(); + + return FReply::Handled(); +} + +void SFindInFlow::OnScopeChanged(TSharedPtr NewSelection, ESelectInfo::Type) +{ + SelectedScopeOption = NewSelection; + SearchScope = *NewSelection; } -void SFindInFlow::OnSearchTextCommitted(const FText& Text, ETextCommit::Type CommitType) +void SFindInFlow::OnMaxDepthChanged(int32 NewDepth) { - OnSearchTextChanged(Text); + MaxSearchDepth = NewDepth; + + // Save to INI + if (UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get()) + { + Settings->DefaultMaxSearchDepth = NewDepth; + Settings->SaveConfig(); + } +} + +TSharedRef SFindInFlow::GenerateScopeWidget(TSharedPtr Item) const +{ + return SNew(STextBlock) + .Text(UEnum::GetDisplayValueAsText(*Item.Get())); +} + +FText SFindInFlow::GetCurrentScopeText() const +{ + return UEnum::GetDisplayValueAsText(*SelectedScopeOption.Get()); } void SFindInFlow::InitiateSearch() { + FFlowEditorModule* FlowEditorModule = &FModuleManager::LoadModuleChecked("FlowEditor"); + if (ensure(FlowEditorModule)) + { + FlowEditorModule->RegisterForAssetChanges(); + } + + SearchResults.Reset(); + + HighlightText = FText::FromString(SearchValue); + TreeView->RequestTreeRefresh(); + + if (SearchValue.IsEmpty()) + { + return; + } + TArray Tokens; SearchValue.ParseIntoArray(Tokens, TEXT(" "), true); + for (FString& Token : Tokens) + { + Token = Token.ToUpper(); + } + + TSharedPtr Editor = FlowAssetEditorPtr.Pin(); + if (!Editor.IsValid()) + { + return; + } - for (auto It(ItemsFound.CreateIterator()); It; ++It) + UFlowAsset* CurrentAsset = Editor->GetFlowAsset(); + if (!CurrentAsset || !CurrentAsset->GetGraph()) { - TreeView->SetItemExpansion(*It, false); + return; } - ItemsFound.Empty(); - if (Tokens.Num() > 0) + + const TSubclassOf CurrentAssetClass = CurrentAsset->GetClass(); + + constexpr int32 Depth = 0; + + switch (SearchScope) { - HighlightText = FText::FromString(SearchValue); - MatchTokens(Tokens); + case EFlowSearchScope::ThisAssetOnly: + { + FSearchResult AssetRoot = MakeShareable(new FFindInFlowResult(CurrentAsset->GetName(), CurrentAsset)); + ProcessAsset(CurrentAsset, AssetRoot, Tokens, Depth); + + if (AssetRoot->Children.Num() > 0) + { + SearchResults.ItemsFound.Add(AssetRoot); + + // Auto-expand the current asset's results + TreeView->SetItemExpansion(AssetRoot, true); + } + } + break; + + case EFlowSearchScope::AllOfThisType: + case EFlowSearchScope::AllFlowAssets: + { + FAssetRegistryModule& Registry = FModuleManager::LoadModuleChecked("AssetRegistry"); + TArray Assets; + FARFilter Filter; + Filter.bRecursiveClasses = true; + + if (SearchScope == EFlowSearchScope::AllFlowAssets) + { + Filter.ClassPaths.Add(FTopLevelAssetPath(UFlowAsset::StaticClass()->GetClassPathName())); + } + else + { + Filter.ClassPaths.Add(FTopLevelAssetPath(CurrentAsset->GetClass()->GetClassPathName())); + } + + Registry.Get().GetAssets(Filter, Assets); + + FScopedSlowTask Task(Assets.Num(), LOCTEXT("SearchingAssets", "Searching Flow Assets...")); + Task.MakeDialog(); + + int32 CurrentAssetIndex = 0; + + for (const FAssetData& Data : Assets) + { + UFlowAsset* Asset = Cast(Data.GetAsset()); + if (!IsValid(Asset)) + { + continue; + } + + CurrentAssetIndex++; + + Task.EnterProgressFrame(1, FText::Format(LOCTEXT("SearchingAsset", "Searching {0}/{1}: {2}..."), CurrentAssetIndex, Assets.Num(), FText::FromString(Asset->GetName()))); + + FSearchResult AssetRoot = MakeShareable(new FFindInFlowResult(Asset->GetName(), Asset)); + ProcessAsset(Asset, AssetRoot, Tokens, Depth); + + if (AssetRoot->Children.Num() > 0) + { + SearchResults.ItemsFound.Add(AssetRoot); + + // Auto-expand only the current asset + if (Asset == CurrentAsset) + { + TreeView->SetItemExpansion(AssetRoot, true); + } + } + } + } + break; + + default: + checkNoEntry(); + break; } - // Insert a fake result to inform user if none found - if (ItemsFound.Num() == 0) + // Add "No results" placeholder if nothing found + if (SearchResults.ItemsFound.IsEmpty()) { - ItemsFound.Add(MakeShared(LOCTEXT("FlowEditorSearchNoResults", "No Results found").ToString())); + FSearchResult NoResults = MakeShareable(new FFindInFlowResult(TEXT("No results found"))); + SearchResults.ItemsFound.Add(NoResults); } TreeView->RequestTreeRefresh(); +} + +bool SFindInFlow::ProcessAsset(UFlowAsset* Asset, FSearchResult ParentResult, const TArray& Tokens, int32 Depth) +{ + if (!Asset || !Asset->GetGraph() || Depth >= MaxSearchDepth || SearchResults.VisitedAssets.Contains(Asset)) + { + return false; + } + + SearchResults.VisitedAssets.Add(Asset); + + bool bAnyMatches = false; - for (auto It(ItemsFound.CreateIterator()); It; ++It) + for (UEdGraphNode* EdNode : Asset->GetGraph()->Nodes) { - TreeView->SetItemExpansion(*It, true); + const TMap>* CategoryStrings = BuildCategoryStrings(EdNode, Depth); + + if (!CategoryStrings) + { + continue; + } + + EFlowSearchFlags NodeMatchedFlags = EFlowSearchFlags::None; + + for (const TPair>& Pair : *CategoryStrings) + { + const TSet& StringSet = Pair.Value; + if (EnumHasAnyFlags(SearchFlags, Pair.Key) && StringSetMatchesSearchTokens(Tokens, StringSet)) + { + EnumAddFlags(NodeMatchedFlags, Pair.Key); + } + } + + if (NodeMatchedFlags != EFlowSearchFlags::None) + { + FString Title = EdNode->GetNodeTitle(ENodeTitleType::ListView).ToString(); + if (Title.IsEmpty()) + { + Title = EdNode->GetClass()->GetName(); + } + + FSearchResult Result = MakeShareable(new FFindInFlowResult(Title, ParentResult, EdNode, Depth > 0, Asset)); + Result->MatchedFlags = NodeMatchedFlags; + ParentResult->Children.Add(Result); + + bAnyMatches = true; + } + + bAnyMatches |= RecurseIntoSubgraphsIfEnabled(EdNode, ParentResult, Tokens, Depth); } + + return bAnyMatches; } -void SFindInFlow::MatchTokens(const TArray& Tokens) +bool SFindInFlow::RecurseIntoSubgraphsIfEnabled(UEdGraphNode* EdNode, FSearchResult ParentResult, const TArray& Tokens, int32 Depth) { - RootSearchResult.Reset(); - - const UEdGraph* Graph = nullptr; - const TSharedPtr FocusedGraphEditor = FlowAssetEditorPtr.Pin()->GetFlowGraph(); - if (FocusedGraphEditor.IsValid()) + if (!EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Subgraphs)) + { + return false; + } + + UFlowGraphNode* FlowGraphNode = Cast(EdNode); + if (!FlowGraphNode || !FlowGraphNode->GetFlowNodeBase()) + { + return false; + } + + UFlowNode_SubGraph* SubGraph = Cast(FlowGraphNode->GetFlowNodeBase()); + if (!SubGraph) + { + return false; + } + + UFlowAsset* SubAsset = Cast(SubGraph->GetAssetToEdit()); + if (!SubAsset) + { + return false; + } + + const FString SubgraphStr = + SearchResults.VisitedAssets.Contains(SubAsset) ? + TEXT(" (repeat subgraph)") : + TEXT(" (Subgraph)"); + + const FString SubTitle = SubAsset->GetName() + SubgraphStr; + FSearchResult SubResult = MakeShareable(new FFindInFlowResult(SubTitle, ParentResult, EdNode, true, SubAsset)); + + // Subgraphs don't count against depth + if (ProcessAsset(SubAsset, SubResult, Tokens, Depth)) + { + ParentResult->Children.Add(SubResult); + + return true; + } + + return false; +} + +const TMap>* SFindInFlow::BuildCategoryStrings(UEdGraphNode* EdNode, int32 Depth) const +{ + if (!IsValid(EdNode)) + { + return nullptr; + } + + // Check cache first + if (const TMap>* Cached = FFindInFlowCache::CategoryStringCache.Find(EdNode)) + { + return Cached; + } + + TMap> NewResultMap; + + UpdateSearchFlagToStringMapForEdGraphNode(*EdNode, NewResultMap, Depth); + + UFlowGraphNode* FlowGraphNode = Cast(EdNode); + if (IsValid(FlowGraphNode)) + { + UFlowNodeBase* FlowNodeBase = FlowGraphNode->GetFlowNodeBase(); + if (IsValid(FlowNodeBase)) + { + UpdateSearchFlagToStringMapForFlowNodeBase(*FlowNodeBase, NewResultMap, Depth); + } + } + + // Now add the new map to the search cache + const TMap>* AddedResultMap = &FFindInFlowCache::CategoryStringCache.Add(EdNode, NewResultMap); + return AddedResultMap; +} + +void SFindInFlow::UpdateSearchFlagToStringMapForEdGraphNode(const UEdGraphNode& EdGraphNode, TMap>& SearchFlagToStringMap, int32 Depth) const +{ + // Comments + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Comments)) + { + TSet& CommentsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Comments); + CommentsSet.Add(EdGraphNode.NodeComment); + } +} + +void SFindInFlow::UpdateSearchFlagToStringMapForFlowNodeBase(const UFlowNodeBase& FlowNodeBase, TMap>& SearchFlagToStringMap, int32 Depth) const +{ + // Node Titles + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Titles)) + { + TSet& TitlesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Titles); + TitlesSet.Add(FlowNodeBase.GetNodeTitle().ToString()); + } + + // Tooltips + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Tooltips)) + { + TSet& TooltipsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Tooltips); + TooltipsSet.Add(FlowNodeBase.GetNodeToolTip().ToString()); + } + + // Classes + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Classes)) + { + TSet& ClassesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Classes); + + const FString DisplayName = FlowNodeBase.GetClass()->GetDisplayNameText().ToString(); + ClassesSet.Add(DisplayName); + + const FString NativeName = FlowNodeBase.GetClass()->GetName(); + ClassesSet.Add(NativeName); + } + + // Descriptions + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Descriptions)) + { + TSet& DescriptionsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Descriptions); + + DescriptionsSet.Add(FlowNodeBase.GetNodeDescription()); + } + + // Config Text + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::ConfigText)) + { + TSet& ConfigSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::ConfigText); + ConfigSet.Add(FlowNodeBase.GetNodeConfigText().ToString()); + } + + // Property-based scouring + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::PropertiesFlags)) + { + AppendPropertyValues(&FlowNodeBase, FlowNodeBase.GetClass(), &FlowNodeBase, SearchFlagToStringMap, Depth); + } + + // AddOns + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::AddOns)) + { + TSet& AddOnsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::AddOns); + + FlowNodeBase.ForEachAddOnConst([AddOnsSet, this, &SearchFlagToStringMap, &Depth](const UFlowNodeAddOn& AddOn) + { + // No depth penalty for AddOns + UpdateSearchFlagToStringMapForFlowNodeBase(AddOn, SearchFlagToStringMap, Depth); + + return EFlowForEachAddOnFunctionReturnValue::Continue; + }); + } +} + +void SFindInFlow::AppendPropertyValues(const void* Container, const UStruct* Struct, const UObject* ParentObject, TMap>& SearchFlagToStringMap, int32 Depth) const +{ + int32 MaxDepth = 1; + if (const UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get()) { - Graph = FocusedGraphEditor->GetCurrentGraph(); + MaxDepth = Settings->DefaultMaxSearchDepth; } - if (Graph == nullptr) + if (!Container || !Struct || !ParentObject || Depth >= MaxDepth) { return; } - - RootSearchResult = MakeShared(FString("FlowEditorRoot")); - for (auto It(Graph->Nodes.CreateConstIterator()); It; ++It) + for (TFieldIterator It(Struct, EFieldIteratorFlags::IncludeSuper); It; ++It) { - UEdGraphNode* Node = *It; - - const FString NodeName = Node->GetNodeTitle(ENodeTitleType::ListView).ToString(); - FSearchResult NodeResult(new FFindInFlowResult(NodeName, RootSearchResult, Node)); - FString NodeSearchString = NodeName + Node->GetClass()->GetName() + Node->NodeComment; + FProperty* Prop = *It; + if (!Prop->HasAnyPropertyFlags(CPF_Edit | CPF_SimpleDisplay | CPF_AdvancedDisplay | CPF_BlueprintVisible | CPF_Config)) + { + continue; + } - if (const UFlowGraphNode* FlowGraphNode = Cast(Node)) + const void* ValuePtr = Prop->ContainerPtrToValuePtr(Container); + + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::PropertyNames)) { - FString NodeDescription = FlowGraphNode->GetNodeDescription(); - NodeSearchString += NodeDescription; - - UFlowNode_SubGraph* SubGraphNode = Cast(FlowGraphNode->GetFlowNodeBase()); - if (bFindInSubGraph && SubGraphNode) + TSet& PropertyNamesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::PropertyNames); + + const FString DisplayName = Prop->GetMetaData(TEXT("DisplayName")); + + if (!DisplayName.IsEmpty()) { - if (const UFlowAsset* FlowAsset = Cast(SubGraphNode->GetAssetToEdit()); FlowAsset && FlowAsset->GetGraph()) - { - for (auto ChildIt(FlowAsset->GetGraph()->Nodes.CreateConstIterator()); ChildIt; ++ChildIt) - { - MatchTokensInChild(Tokens, *ChildIt, NodeResult); - } - } + PropertyNamesSet.Add(DisplayName); } + + PropertyNamesSet.Add(Prop->GetName()); + } + + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::PropertyValues)) + { + TSet& PropertyValuesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::PropertyValues); + + FString ValueStr; + UObject* MutableParentObject = const_cast(ParentObject); + Prop->ExportText_InContainer(0, ValueStr, Container, nullptr, MutableParentObject, PPF_None); + ValueStr = ValueStr.Replace(TEXT("\""), TEXT("")).TrimStartAndEnd(); + + PropertyValuesSet.Add(ValueStr); } - NodeSearchString = NodeSearchString.Replace(TEXT(" "), TEXT("")); - const bool bNodeMatchesSearch = StringMatchesSearchTokens(Tokens, NodeSearchString); - - if ((NodeResult->Children.Num() > 0) || bNodeMatchesSearch) + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Tooltips)) { - ItemsFound.Add(NodeResult); + TSet& TooltipsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Tooltips); + TooltipsSet.Add(Prop->GetMetaData(TEXT("ToolTip"))); + } + + if (FStructProperty* StructProp = CastField(Prop)) + { + // Recurse into structs (no depth penalty) + AppendPropertyValues(ValuePtr, StructProp->Struct, ParentObject, SearchFlagToStringMap, Depth); + } + else if (FObjectProperty* ObjProp = CastField(Prop)) + { + // Recurse into inline objects (incurs a depth penalty) + UObject* Obj = ObjProp->GetObjectPropertyValue(ValuePtr); + if (IsValid(Obj) && !Obj->HasAnyFlags(RF_ClassDefaultObject)) + { + AppendPropertyValues(Obj, Obj->GetClass(), Obj, SearchFlagToStringMap, Depth + 1); + } } } } -void SFindInFlow::MatchTokensInChild(const TArray& Tokens, UEdGraphNode* Child, FSearchResult ParentNode) +bool SFindInFlow::StringMatchesSearchTokens(const TArray& Tokens, const FString& ComparisonString) { - if (Child == nullptr) + int32 MatchedTokenCount = 0; + const int32 TotalTokenCount = Tokens.Num(); + + // Must match all tokens + for (const FString& Token : Tokens) { - return; + if (ComparisonString.Contains(Token)) + { + ++MatchedTokenCount; + } + else + { + break; + } } - const FString ChildName = Child->GetNodeTitle(ENodeTitleType::ListView).ToString(); - FString ChildSearchString = ChildName + Child->GetClass()->GetName() + Child->NodeComment; - if (const UFlowGraphNode* FlowGraphNode = Cast(Child)) + if (MatchedTokenCount == TotalTokenCount) { - FString NodeDescription = FlowGraphNode->GetNodeDescription(); - ChildSearchString += NodeDescription; + return true; } - ChildSearchString = ChildSearchString.Replace(TEXT(" "), TEXT("")); - if (StringMatchesSearchTokens(Tokens, ChildSearchString)) + else { - const FSearchResult DecoratorResult(new FFindInFlowResult(ChildName, ParentNode, Child, true)); - ParentNode->Children.Add(DecoratorResult); + return false; + } +} + +bool SFindInFlow::StringSetMatchesSearchTokens(const TArray& Tokens, const TSet& StringSet) +{ + for (const FString& StringFromSet : StringSet) + { + if (StringMatchesSearchTokens(Tokens, StringFromSet)) + { + return true; + } } + + return false; } -TSharedRef SFindInFlow::OnGenerateRow( FSearchResult InItem, const TSharedRef& OwnerTable ) +TSharedRef SFindInFlow::OnGenerateRow(FSearchResult InItem, const TSharedRef& OwnerTable) { - return SNew(STableRow< TSharedPtr >, OwnerTable) + return SNew(STableRow, OwnerTable) .ToolTip(SNew(SToolTip).Text(InItem->GetToolTipText())) [ SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SBox) - .MinDesiredWidth(300) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 0) [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .AutoWidth() - [ - InItem->CreateIcon() - ] - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - .AutoWidth() - .Padding(2, 0) - [ - SNew(STextBlock) + InItem->CreateIcon() + ] + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) .Text(FText::FromString(InItem->Value)) .HighlightText(HighlightText) - ] ] - ] - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(InItem->GetDescriptionText())) - .HighlightText(HighlightText) - ] - +SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(InItem->GetNodeTypeText())) - .HighlightText(HighlightText) - ] - +SHorizontalBox::Slot() - .HAlign(HAlign_Right) - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(InItem->GetCommentText())) - .ColorAndOpacity(FLinearColor::Yellow) - .HighlightText(HighlightText) - ] + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(FText::FromString(InItem->GetNodeTypeText())) + .ColorAndOpacity(FSlateColor(FLinearColor(0.6f, 0.8f, 1.0f))) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(InItem->GetMatchedCategoriesText()) + .ColorAndOpacity(FSlateColor(FLinearColor(0.8f, 0.8f, 0.8f))) + ] ]; } -void SFindInFlow::OnGetChildren(FSearchResult InItem, TArray< FSearchResult >& OutChildren) +void SFindInFlow::OnGetChildren(FSearchResult InItem, TArray& OutChildren) { - OutChildren += InItem->Children; + OutChildren = InItem->Children; } -void SFindInFlow::OnTreeSelectionChanged(FSearchResult Item , ESelectInfo::Type) +void SFindInFlow::OnTreeSelectionChanged(FSearchResult Item, ESelectInfo::Type) { if (Item.IsValid()) { - Item->OnClick(FlowAssetEditorPtr, RootSearchResult); + Item->OnClick(FlowAssetEditorPtr); } } @@ -432,33 +964,8 @@ void SFindInFlow::OnTreeSelectionDoubleClicked(FSearchResult Item) { if (Item.IsValid()) { - Item->OnDoubleClick(RootSearchResult); + Item->OnDoubleClick(); } } -void SFindInFlow::OnFindInSubGraphStateChanged(ECheckBoxState CheckBoxState) -{ - bFindInSubGraph = CheckBoxState == ECheckBoxState::Checked; - InitiateSearch(); -} - -bool SFindInFlow::StringMatchesSearchTokens(const TArray& Tokens, const FString& ComparisonString) -{ - bool bFoundAllTokens = true; - - //search the entry for each token, it must have all of them to pass - for (auto TokItr(Tokens.CreateConstIterator()); TokItr; ++TokItr) - { - const FString& Token = *TokItr; - if (!ComparisonString.Contains(Token)) - { - bFoundAllTokens = false; - break; - } - } - return bFoundAllTokens; -} - -///////////////////////////////////////////////////// - #undef LOCTEXT_NAMESPACE diff --git a/Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp b/Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp new file mode 100644 index 000000000..7735a2d90 --- /dev/null +++ b/Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp @@ -0,0 +1,145 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Find/SFindInFlowFilterPopup.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Framework/Application/SlateApplication.h" + +#define LOCTEXT_NAMESPACE "FindInFlow" + +void SFindInFlowFilterPopup::Construct(const FArguments& InArgs) +{ + OnApplyDelegate = InArgs._OnApply; + OnSaveAsDefaultDelegate = InArgs._OnSaveAsDefault; + ProposedFlags = InArgs._InitialFlags; + + // Build the checkbox container with slots added during construction + SAssignNew(CheckBoxContainer, SVerticalBox); + + for (EFlowSearchFlags Flag : MakeFlagsRange(EFlowSearchFlags::All)) + { + CheckBoxContainer->AddSlot() + .AutoHeight() + [ + SNew(SCheckBox) + .IsChecked(this, &SFindInFlowFilterPopup::GetCheckState, Flag) + .OnCheckStateChanged_Lambda([this, Flag](ECheckBoxState NewState) + { + if (NewState == ECheckBoxState::Checked) + { + EnumAddFlags(ProposedFlags, Flag); + } + else + { + EnumRemoveFlags(ProposedFlags, Flag); + } + }) + [ + SNew(STextBlock) + .Text(UEnum::GetDisplayValueAsText(Flag)) + ] + ]; + } + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(10) + [ + SNew(STextBlock) + .Text(LOCTEXT("FilterPopupTitle", "Select Search Filters:")) + .Font(FAppStyle::GetFontStyle("NormalFontBold")) + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + .Padding(10, 5) + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + CheckBoxContainer.ToSharedRef() + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(10) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("ToggleAllFilters", "Toggle All")) + .OnClicked(this, &SFindInFlowFilterPopup::OnToggleAllClicked) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("SaveAsDefaultFilters", "Save as Default")) + .OnClicked(this, &SFindInFlowFilterPopup::OnSaveAsDefaultClicked) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(10) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("CancelFilters", "Cancel")) + .OnClicked(this, &SFindInFlowFilterPopup::OnCancelClicked) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("ApplyFilters", "Apply")) + .OnClicked(this, &SFindInFlowFilterPopup::OnApplyClicked) + ] + ] + ]; +} + +FReply SFindInFlowFilterPopup::OnApplyClicked() +{ + OnApplyDelegate.ExecuteIfBound(ProposedFlags); + FSlateApplication::Get().DismissAllMenus(); + return FReply::Handled(); +} + +FReply SFindInFlowFilterPopup::OnCancelClicked() +{ + FSlateApplication::Get().DismissAllMenus(); + return FReply::Handled(); +} + +FReply SFindInFlowFilterPopup::OnToggleAllClicked() +{ + if (ProposedFlags != EFlowSearchFlags::None) + { + ProposedFlags = EFlowSearchFlags::None; + } + else + { + ProposedFlags = EFlowSearchFlags::All; + } + + CheckBoxContainer->Invalidate(EInvalidateWidgetReason::Layout); + + return FReply::Handled(); +} + +FReply SFindInFlowFilterPopup::OnSaveAsDefaultClicked() +{ + OnSaveAsDefaultDelegate.ExecuteIfBound(ProposedFlags); + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index 282fab0d2..74f5e5536 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -35,6 +35,7 @@ #include "FlowAsset.h" #include "AddOns/FlowNodeAddOn.h" #include "Asset/FlowAssetParamsTypes.h" +#include "Find/FindInFlow.h" #include "Nodes/Actor/FlowNode_ComponentObserver.h" #include "Nodes/Actor/FlowNode_PlayLevelSequence.h" #include "Nodes/Graph/FlowNode_CustomInput.h" @@ -43,6 +44,7 @@ #include "Types/FlowNamedDataPinProperty.h" #include "AssetToolsModule.h" +#include "AssetRegistry/AssetRegistryModule.h" #include "EdGraphUtilities.h" #include "IAssetSearchModule.h" #include "Framework/MultiBox/MultiBoxBuilder.h" @@ -100,6 +102,20 @@ void FFlowEditorModule::StartupModule() ModulesChangedHandle = FModuleManager::Get().OnModulesChanged().AddRaw(this, &FFlowEditorModule::ModulesChangesCallback); } +void FFlowEditorModule::RegisterForAssetChanges() +{ + if (!bIsRegisteredForAssetChanges) + { + // Register asset change detection for search cache invalidation + FAssetRegistryModule& AssetRegistry = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistry.Get().OnAssetUpdated().AddRaw(this, &FFlowEditorModule::OnAssetUpdated); + AssetRegistry.Get().OnAssetRenamed().AddRaw(this, &FFlowEditorModule::OnAssetRenamed); + AssetRegistry.Get().OnAssetRemoved().AddRaw(this, &FFlowEditorModule::OnAssetUpdated); + + bIsRegisteredForAssetChanges = true; + } +} + void FFlowEditorModule::ShutdownModule() { MenuExtensibilityManager.Reset(); @@ -116,6 +132,18 @@ void FFlowEditorModule::ShutdownModule() SequencerModule.UnRegisterTrackEditor(FlowTrackCreateEditorHandle); FModuleManager::Get().OnModulesChanged().Remove(ModulesChangedHandle); + + if (bIsRegisteredForAssetChanges && FModuleManager::Get().IsModuleLoaded("AssetRegistry")) + { + // Unregister asset change detection + FAssetRegistryModule& AssetRegistry = FModuleManager::Get().GetModuleChecked("AssetRegistry"); + + AssetRegistry.Get().OnAssetUpdated().RemoveAll(this); + AssetRegistry.Get().OnAssetRenamed().RemoveAll(this); + AssetRegistry.Get().OnAssetRemoved().RemoveAll(this); + + bIsRegisteredForAssetChanges = false; + } } void FFlowEditorModule::TrySetFlowNodeDisplayStyleDefaults() const @@ -314,6 +342,19 @@ TSharedRef FFlowEditorModule::CreateFlowAssetEditor(const EToo return NewFlowAssetEditor; } +void FFlowEditorModule::OnAssetUpdated(const FAssetData& AssetData) +{ + if (UFlowAsset* FlowAsset = Cast(AssetData.GetAsset())) + { + FFindInFlowCache::OnFlowAssetChanged(*FlowAsset); + } +} + +void FFlowEditorModule::OnAssetRenamed(const FAssetData& AssetData, const FString& OldObjectPath) +{ + OnAssetUpdated(AssetData); +} + #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FFlowEditorModule, FlowEditor) \ No newline at end of file diff --git a/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp index b6d4cfea7..78c54ff23 100644 --- a/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp @@ -416,7 +416,7 @@ FSlateColor SFlowGraphNode::GetConfigBoxBackgroundColor() const void SFlowGraphNode::CreateBelowPinControls(const TSharedPtr InnerVerticalBox) { - static const FMargin ConfigBoxPadding = FMargin(2.0f, 0.0f, 1.0f, 0.0); + static const FMargin ConfigBoxPadding = FMargin(2.0f, 0.0f, 1.0f, 0.0f); // Add a box to wrap around the Config Text area to make it a more visually distinct part of the node TSharedPtr BelowPinsBox; @@ -1224,4 +1224,4 @@ void SFlowGraphNode::SetOwner(const TSharedRef& OwnerPanel) } } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Public/Find/FindInFlow.h b/Source/FlowEditor/Public/Find/FindInFlow.h index 848bcff4d..9c8321416 100644 --- a/Source/FlowEditor/Public/Find/FindInFlow.h +++ b/Source/FlowEditor/Public/Find/FindInFlow.h @@ -20,32 +20,38 @@ #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" +#include "Widgets/Input/SSpinBox.h" #include "Widgets/Views/STableViewBase.h" #include "Widgets/Views/STreeView.h" +#include "Types/FlowEnumUtils.h" +#include "FindInFlowEnums.h" + class ITableRow; class SWidget; class UFlowGraphNode; class UEdGraphNode; +class UFlowAsset; +class UFlowNodeBase; /** Item that matched the search results */ class FFindInFlowResult { -public: +public: /** Create a root (or only text) result */ - FFindInFlowResult(const FString& InValue); - + FFindInFlowResult(const FString& InValue, UFlowAsset* InOwningFlowAsset = nullptr); + /** Create a flow node result */ - FFindInFlowResult(const FString& InValue, TSharedPtr& InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode = false); + FFindInFlowResult(const FString& InValue, TSharedPtr InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode = false, UFlowAsset* InOwningFlowAsset = nullptr); /** Called when user clicks on the search item */ - FReply OnClick(TWeakPtr FlowAssetEditor, TSharedPtr Root); - + FReply OnClick(TWeakPtr FlowAssetEditorPtr); + /** Called when user double clicks on the search item */ - FReply OnDoubleClick(TSharedPtr Root) const; + FReply OnDoubleClick() const; /** Create an icon to represent the result */ - TSharedRef CreateIcon() const; + TSharedRef CreateIcon() const; /** Gets the description on flow node if any */ FString GetDescriptionText() const; @@ -59,15 +65,30 @@ class FFindInFlowResult /** Gets the node tool tip */ FText GetToolTipText() const; - /** Any children listed under this flow node (decorators and services) */ + /** Returns a snippet of the matched property/value for tooltip */ + FText GetMatchedSnippet() const; + + /** Human-readable list of categories this result matched in */ + FText GetMatchedCategoriesText() const; + + /** Any children listed under this flow node (decorators, services, addons, subnodes) */ TArray< TSharedPtr > Children; /** The string value for this result */ FString Value; + /** Stores a snippet of the matched property/value (e.g. "Damage:50") */ + FString MatchedPropertySnippet; + + /** Which search categories actually produced a hit for this item */ + EFlowSearchFlags MatchedFlags = EFlowSearchFlags::None; + /** The graph node that this search result refers to */ TWeakObjectPtr GraphNode; + /** The owning flow asset for this result */ + TWeakObjectPtr OwningFlowAsset; + /** Search result parent */ TWeakPtr Parent; @@ -75,11 +96,46 @@ class FFindInFlowResult bool bIsSubGraphNode = false; }; +struct FFindInFlowCache +{ + /** Removes all cached data for the changed flow asset */ + static void OnFlowAssetChanged(UFlowAsset& ChangedFlowAsset); + + /** Cache searchable strings per node (for repeat searches) */ + static TMap, TMap>> CategoryStringCache; +}; + +struct FFindInFlowAllResults +{ + typedef TSharedPtr FSearchResult; + + /** we need to keep a handle on the root result, because it won't show up in the tree */ + FSearchResult RootSearchResult; + + /** This buffer stores the currently displayed results */ + TArray ItemsFound; + + /** Visited assets to prevent cycles in subgraph recursion */ + TSet VisitedAssets; + + void Setup() + { + RootSearchResult = MakeShareable(new FFindInFlowResult(TEXT("Root"))); + } + + void Reset() + { + ItemsFound.Empty(); + RootSearchResult->Children.Empty(); + VisitedAssets.Empty(); + } +}; + /** Widget for searching for (Flow nodes) across focused FlowNodes */ class SFindInFlow : public SCompoundWidget { public: - SLATE_BEGIN_ARGS(SFindInFlow){} + SLATE_BEGIN_ARGS(SFindInFlow) {} SLATE_END_ARGS() void Construct(const FArguments& InArgs, TSharedPtr InFlowAssetEditor); @@ -87,7 +143,8 @@ class SFindInFlow : public SCompoundWidget /** Focuses this widget's search box */ void FocusForUse() const; -private: +protected: + typedef TSharedPtr FSearchResult; typedef STreeView STreeViewType; @@ -97,55 +154,84 @@ class SFindInFlow : public SCompoundWidget /** Called when user commits text */ void OnSearchTextCommitted(const FText& Text, ETextCommit::Type CommitType); + /** Called when search button is clicked */ + FReply OnSearchButtonClicked(); + /** Get the children of a row */ void OnGetChildren(FSearchResult InItem, TArray& OutChildren); /** Called when user clicks on a new result */ void OnTreeSelectionChanged(FSearchResult Item, ESelectInfo::Type SelectInfo); - + /* Called when user double clicks on a new result */ - void OnTreeSelectionDoubleClicked( FSearchResult Item ); + void OnTreeSelectionDoubleClicked(FSearchResult Item); - /** Called when whether find in sub graph changed */ - void OnFindInSubGraphStateChanged(ECheckBoxState CheckBoxState); + /** Called when scope selection changed */ + void OnScopeChanged(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + + /** Called when max depth changed */ + void OnMaxDepthChanged(int32 NewDepth); /** Called when a new row is being generated */ TSharedRef OnGenerateRow(FSearchResult InItem, const TSharedRef& OwnerTable); /** Begins the search based on the SearchValue */ void InitiateSearch(); - - /** Find any results that contain all of the tokens */ - void MatchTokens(const TArray& Tokens); - /** Find if child contains all of the tokens and add a result accordingly */ - static void MatchTokensInChild(const TArray& Tokens, UEdGraphNode* Child, FSearchResult ParentNode); - + /** Build searchable string from node and its FlowNodeBase + AddOns */ + const TMap>* BuildCategoryStrings(UEdGraphNode* Node, int32 Depth) const; + /** Determines if a string matches the search tokens */ static bool StringMatchesSearchTokens(const TArray& Tokens, const FString& ComparisonString); + static bool StringSetMatchesSearchTokens(const TArray& Tokens, const TSet& StringSet); + + /** Generate widget for scope combo */ + TSharedRef GenerateScopeWidget(TSharedPtr Item) const; + + /** Get current scope display text */ + FText GetCurrentScopeText() const; + + bool ProcessAsset(UFlowAsset* Asset, FSearchResult ParentResult, const TArray& Tokens, int32 Depth); + + bool RecurseIntoSubgraphsIfEnabled(UEdGraphNode* EdNode, FSearchResult ParentResult, const TArray& Tokens, int32 Depth); -private: + void UpdateSearchFlagToStringMapForEdGraphNode(const UEdGraphNode& EdGraphNode, TMap>& SearchFlagToStringMap, int32 Depth) const; + void UpdateSearchFlagToStringMapForFlowNodeBase(const UFlowNodeBase& FlowNodeBase, TMap>& SearchFlagToStringMap, int32 Depth) const; + void AppendPropertyValues(const void* Container, const UStruct* Struct, const UObject* ParentObject, TMap>& SearchFlagToStringMap, int32 Depth) const; + +protected: /** Pointer back to the flow editor that owns us */ TWeakPtr FlowAssetEditorPtr; - + /** The tree view displays the results */ TSharedPtr TreeView; /** The search text box */ TSharedPtr SearchTextField; - - /** This buffer stores the currently displayed results */ - TArray ItemsFound; - /** we need to keep a handle on the root result, because it won't show up in the tree */ - FSearchResult RootSearchResult; + /** The search button */ + TSharedPtr SearchButton; + + /** Struct with all of the search results */ + FFindInFlowAllResults SearchResults; + + /** Repeat Search Caching */ + FFindInFlowCache SearchCache; /** The string to highlight in the results */ FText HighlightText; /** The string to search for */ - FString SearchValue; + FString SearchValue; - /** Using to control whether search in sub graph */ - bool bFindInSubGraph = false; -}; + /** Search configuration */ + EFlowSearchFlags SearchFlags = EFlowSearchFlags::DefaultSearchFlags; + + TSharedPtr> MaxDepthSpinBox; + int32 MaxSearchDepth = 3; + + /** Scope selection */ + TArray> ScopeOptionList; + TSharedPtr SelectedScopeOption; + EFlowSearchScope SearchScope = EFlowSearchScope::ThisAssetOnly; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/Find/FindInFlowEnums.h b/Source/FlowEditor/Public/Find/FindInFlowEnums.h new file mode 100644 index 000000000..e0c7a6887 --- /dev/null +++ b/Source/FlowEditor/Public/Find/FindInFlowEnums.h @@ -0,0 +1,48 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowEnumUtils.h" + +#include "FindInFlowEnums.generated.h" + +/** Bitflags controlling what parts of a Flow node are included in search */ +UENUM(Meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true")) +enum class EFlowSearchFlags : uint32 +{ + None = 0 UMETA(Hidden), + + Titles = 1 << 0 UMETA(DisplayName = "Titles"), + Tooltips = 1 << 1 UMETA(DisplayName = "Tooltips"), + Classes = 1 << 2 UMETA(DisplayName = "Classes"), + Comments = 1 << 3 UMETA(DisplayName = "Comments"), + Descriptions = 1 << 4 UMETA(DisplayName = "Descriptions"), + ConfigText = 1 << 5 UMETA(DisplayName = "Config Text"), + PropertyNames = 1 << 6 UMETA(DisplayName = "Property Names"), + PropertyValues = 1 << 7 UMETA(DisplayName = "Property Values"), + AddOns = 1 << 8 UMETA(DisplayName = "Add-Ons"), + Subgraphs = 1 << 9 UMETA(DisplayName = "Subgraphs"), + + All = + Titles | Tooltips | Classes | Comments | Descriptions | ConfigText | + PropertyNames | PropertyValues | AddOns | Subgraphs UMETA(Hidden), + + // Default mask — used at startup and for "reset" + DefaultSearchFlags = All UMETA(Hidden), + PropertiesFlags = PropertyNames | PropertyValues | Tooltips UMETA(Hidden), +}; +ENUM_CLASS_FLAGS(EFlowSearchFlags); + +/** Search scope — intentionally minimal */ +UENUM() +enum class EFlowSearchScope : uint8 +{ + ThisAssetOnly UMETA(DisplayName = "This Asset", ToolTip = "Search only the currently open Flow Asset"), + AllOfThisType UMETA(DisplayName = "All Flow Assets of This Type",ToolTip = "Search all Flow Assets of this type (or subclasses)"), + AllFlowAssets UMETA(DisplayName = "All Flow Assets", ToolTip = "Search every Flow Asset in the project"), + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowSearchScope); \ No newline at end of file diff --git a/Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h b/Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h new file mode 100644 index 000000000..49843b068 --- /dev/null +++ b/Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h @@ -0,0 +1,42 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SBoxPanel.h" + +#include "FindInFlowEnums.h" + +DECLARE_DELEGATE_OneParam(FFindInFlowApplyDelegate, EFlowSearchFlags); + +class SFindInFlowFilterPopup : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SFindInFlowFilterPopup) {} + SLATE_ARGUMENT(FFindInFlowApplyDelegate, OnApply) + SLATE_ARGUMENT(FFindInFlowApplyDelegate, OnSaveAsDefault) + SLATE_ARGUMENT(EFlowSearchFlags, InitialFlags) + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + +protected: + + EFlowSearchFlags ProposedFlags = EFlowSearchFlags::DefaultSearchFlags; + + FFindInFlowApplyDelegate OnApplyDelegate; + FFindInFlowApplyDelegate OnSaveAsDefaultDelegate; + + TSharedPtr CheckBoxContainer; + + ECheckBoxState GetCheckState(EFlowSearchFlags Flag) const + { + return EnumHasAnyFlags(ProposedFlags, Flag) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + + FReply OnApplyClicked(); + FReply OnCancelClicked(); + FReply OnToggleAllClicked(); + FReply OnSaveAsDefaultClicked(); +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 8dec688cf..e1266b1b3 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -34,6 +34,8 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen TSharedPtr MenuExtensibilityManager; TSharedPtr ToolBarExtensibilityManager; + bool bIsRegisteredForAssetChanges = false; + public: virtual void StartupModule() override; virtual void ShutdownModule() override; @@ -41,6 +43,8 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen virtual TSharedPtr GetMenuExtensibilityManager() override { return MenuExtensibilityManager; } virtual TSharedPtr GetToolBarExtensibilityManager() override { return ToolBarExtensibilityManager; } + void RegisterForAssetChanges(); + private: void TrySetFlowNodeDisplayStyleDefaults() const; @@ -65,4 +69,7 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen public: static TSharedRef CreateFlowAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UFlowAsset* FlowAsset); -}; + + void OnAssetUpdated(const FAssetData& AssetData); + void OnAssetRenamed(const FAssetData& AssetData, const FString& OldObjectPath); +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h index f19656734..8695702ca 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h @@ -3,6 +3,8 @@ #pragma once #include "Engine/DeveloperSettings.h" +#include "Find/FindInFlowEnums.h" + #include "FlowGraphEditorSettings.generated.h" UENUM() @@ -67,6 +69,14 @@ class FLOWEDITOR_API UFlowGraphEditorSettings : public UDeveloperSettings UPROPERTY(EditAnywhere, config, Category = "Wires") bool bHighlightOutputWiresOfSelectedNodes; + // Default search filter flags for the Flow Editor + UPROPERTY(VisibleAnywhere, config, Category = "Search", meta = (Bitmask, BitmaskEnum = "/Script/Flow.EFlowSearchFlags")) + uint32 DefaultSearchFlags = uint32(EFlowSearchFlags::DefaultSearchFlags); + + // Max search depth for inline objects in the Flow Editor + UPROPERTY(EditAnywhere, config, Category = "Search", meta = (ClampMin = 1)) + int32 DefaultMaxSearchDepth = 1; + public: virtual FName GetCategoryName() const override { return FName("Flow Graph"); } virtual FText GetSectionText() const override { return INVTEXT("User Settings"); } diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h index a912b60a4..78918f786 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" From cc5b9c8fe6fe3c8bfa6b3fe9eb5ced7c14469123 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:04:55 -0800 Subject: [PATCH 2/9] Flow Debugger Fixes Data Pin Values displayed in Tooltips Flow debugger stops at breakpoints bugfix --- Flow.uplugin | 2 +- Source/Flow/Private/FlowAsset.cpp | 21 ++ .../Private/Interfaces/FlowExecutionGate.cpp | 134 +++++++++++ Source/Flow/Private/Nodes/FlowNode.cpp | 10 +- Source/Flow/Private/Nodes/FlowPin.cpp | 1 - .../Flow/Private/Types/FlowDataPinValue.cpp | 2 +- .../Types/FlowDataPinValuesStandard.cpp | 38 ++++ Source/Flow/Public/FlowAsset.h | 6 +- .../Public/Interfaces/FlowExecutionGate.h | 45 ++++ Source/Flow/Public/Nodes/FlowPin.h | 1 - .../Public/Types/FlowDataPinValuesStandard.h | 67 +++--- .../Public/Types/FlowNamedDataPinProperty.h | 2 +- .../Debugger/FlowDebuggerSubsystem.cpp | 214 ++++++++++++++---- .../Public/Debugger/FlowDebuggerSubsystem.h | 63 ++++-- .../Private/Asset/FlowAssetToolbar.cpp | 13 +- .../Asset/FlowDebugEditorSubsystem.cpp | 108 ++++++++- .../Private/Graph/FlowGraphEditor.cpp | 161 ++++++++----- .../Private/Graph/Nodes/FlowGraphNode.cpp | 90 ++++++-- .../FlowEditor/Public/Asset/FlowAssetEditor.h | 2 +- .../Public/Asset/FlowDebugEditorSubsystem.h | 9 +- .../FlowEditor/Public/Graph/FlowGraphEditor.h | 8 + 21 files changed, 817 insertions(+), 180 deletions(-) create mode 100644 Source/Flow/Private/Interfaces/FlowExecutionGate.cpp create mode 100644 Source/Flow/Public/Interfaces/FlowExecutionGate.h diff --git a/Flow.uplugin b/Flow.uplugin index ec3fc0671..f4eb2d4c3 100644 --- a/Flow.uplugin +++ b/Flow.uplugin @@ -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/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index df435c480..44b672dd1 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -8,6 +8,7 @@ #include "AddOns/FlowNodeAddOn.h" #include "Asset/FlowAssetParams.h" #include "Asset/FlowAssetParamsUtils.h" +#include "Interfaces/FlowExecutionGate.h" #include "Nodes/FlowNodeBase.h" #include "Nodes/Graph/FlowNode_CustomInput.h" #include "Nodes/Graph/FlowNode_CustomOutput.h" @@ -965,6 +966,11 @@ void UFlowAsset::PreStartFlow() void UFlowAsset::StartFlow(IFlowDataPinValueSupplierInterface* DataPinValueSupplier) { + if (FFlowExecutionGate::IsHalted()) + { + return; + } + PreStartFlow(); if (UFlowNode* ConnectedEntryNode = GetDefaultEntryNode()) @@ -1028,6 +1034,11 @@ TWeakObjectPtr UFlowAsset::GetFlowInstance(UFlowNode_SubGraph* SubGr void UFlowAsset::TriggerCustomInput_FromSubGraph(UFlowNode_SubGraph* SubGraphNode, const FName& EventName) const { + if (FFlowExecutionGate::IsHalted()) + { + return; + } + // NOTE (gtaylor) Custom Input nodes cannot currently add data pins (like Start or DefineProperties nodes can) // but we may want to allow them to source parameters, so I am providing the subgraph node as the // IFlowDataPinValueSupplierInterface when triggering the node (even though it's not used at this time). @@ -1041,6 +1052,11 @@ void UFlowAsset::TriggerCustomInput_FromSubGraph(UFlowNode_SubGraph* SubGraphNod void UFlowAsset::TriggerCustomInput(const FName& EventName, IFlowDataPinValueSupplierInterface* DataPinValueSupplier) { + if (FFlowExecutionGate::IsHalted()) + { + return; + } + for (UFlowNode_CustomInput* CustomInputNode : CustomInputNodes) { if (CustomInputNode->EventName == EventName) @@ -1080,6 +1096,11 @@ void UFlowAsset::TriggerCustomOutput(const FName& EventName) void UFlowAsset::TriggerInput(const FGuid& NodeGuid, const FName& PinName, const FConnectedPin& FromPin) { + if (FFlowExecutionGate::EnqueueDeferredTriggerInput(this, NodeGuid, PinName, FromPin)) + { + return; + } + if (UFlowNode* Node = Nodes.FindRef(NodeGuid)) { if (!ActiveNodes.Contains(Node)) diff --git a/Source/Flow/Private/Interfaces/FlowExecutionGate.cpp b/Source/Flow/Private/Interfaces/FlowExecutionGate.cpp new file mode 100644 index 000000000..44fd864f2 --- /dev/null +++ b/Source/Flow/Private/Interfaces/FlowExecutionGate.cpp @@ -0,0 +1,134 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Interfaces/FlowExecutionGate.h" + +#include "FlowAsset.h" +#include "Nodes/FlowPin.h" + +namespace FlowExecutionGate_Private +{ + struct FDeferredTriggerInput + { + TWeakObjectPtr FlowAssetInstance; + FGuid NodeGuid; + FName PinName; + FConnectedPin FromPin; + }; + + static TArray DeferredTriggerInputs; + static bool bIsFlushing = false; +} + +IFlowExecutionGate* FFlowExecutionGate::Gate = nullptr; + +void FFlowExecutionGate::SetGate(IFlowExecutionGate* InGate) +{ + Gate = InGate; +} + +IFlowExecutionGate* FFlowExecutionGate::GetGate() +{ + return Gate; +} + +bool FFlowExecutionGate::IsHalted() +{ + return (Gate != nullptr) && Gate->IsFlowExecutionHalted(); +} + +bool FFlowExecutionGate::EnqueueDeferredTriggerInput(UFlowAsset* FlowAssetInstance, const FGuid& NodeGuid, const FName& PinName, const FConnectedPin& FromPin) +{ + using namespace FlowExecutionGate_Private; + + // If we're halted, always enqueue (even during flushing). The whole point is to stop propagation. + if (IsHalted()) + { + if (!IsValid(FlowAssetInstance)) + { + return true; // treat as handled while halted + } + + FDeferredTriggerInput& Entry = DeferredTriggerInputs.AddDefaulted_GetRef(); + Entry.FlowAssetInstance = FlowAssetInstance; + Entry.NodeGuid = NodeGuid; + Entry.PinName = PinName; + Entry.FromPin = FromPin; + + return true; + } + + // Not halted: + // During flush we must not enqueue "normal" triggers (we want them to execute now), + // otherwise we can get infinite deferral. + if (bIsFlushing) + { + return false; + } + + return false; +} + +void FFlowExecutionGate::FlushDeferredTriggerInputs() +{ + using namespace FlowExecutionGate_Private; + + if (bIsFlushing) + { + return; + } + + // Do not flush while halted; callers should clear the halt first. + if (IsHalted()) + { + return; + } + + if (DeferredTriggerInputs.IsEmpty()) + { + return; + } + + bIsFlushing = true; + + // Move into a local array so new deferred triggers can be added while we flush. + TArray Local = MoveTemp(DeferredTriggerInputs); + DeferredTriggerInputs.Reset(); + + for (int32 Index = 0; Index < Local.Num(); ++Index) + { + // If a breakpoint was hit during this flush, stop immediately and re-queue remaining work. + if (IsHalted()) + { + const int32 Remaining = Local.Num() - Index; + if (Remaining > 0) + { + TArray RemainingItems; + RemainingItems.Reserve(Remaining); + + for (int32 j = Index; j < Local.Num(); ++j) + { + RemainingItems.Add(Local[j]); + } + + // RemainingItems should run before any items that may already be queued. + if (!DeferredTriggerInputs.IsEmpty()) + { + RemainingItems.Append(MoveTemp(DeferredTriggerInputs)); + } + + DeferredTriggerInputs = MoveTemp(RemainingItems); + } + + bIsFlushing = false; + return; + } + + const FDeferredTriggerInput& Entry = Local[Index]; + if (UFlowAsset* Asset = Entry.FlowAssetInstance.Get()) + { + Asset->TriggerDeferredInputFromDebugger(Entry.NodeGuid, Entry.PinName, Entry.FromPin); + } + } + + bIsFlushing = false; +} \ No newline at end of file diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index be876bee5..0121cb170 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -883,9 +883,10 @@ void UFlowNode::TriggerInput(const FName& PinName, const EFlowPinActivationType TArray& Records = InputRecords.FindOrAdd(PinName); Records.Add(FPinRecord(FApp::GetCurrentTime(), ActivationType)); - if (const UFlowAsset* FlowAssetTemplate = GetFlowAsset()->GetTemplateAsset()) + UFlowAsset* FlowAssetInstance = GetFlowAsset(); + if (const UFlowAsset* FlowAssetTemplate = FlowAssetInstance->GetTemplateAsset()) { - (void)FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(NodeGuid, PinName); + (void) FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(FlowAssetInstance, NodeGuid, PinName); } #endif } @@ -949,9 +950,10 @@ void UFlowNode::TriggerOutput(const FName PinName, const bool bFinish /*= false* TArray& Records = OutputRecords.FindOrAdd(PinName); Records.Add(FPinRecord(FApp::GetCurrentTime(), ActivationType)); - if (const UFlowAsset* FlowAssetTemplate = GetFlowAsset()->GetTemplateAsset()) + UFlowAsset* FlowAssetInstance = GetFlowAsset(); + if (const UFlowAsset* FlowAssetTemplate = FlowAssetInstance->GetTemplateAsset()) { - FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(NodeGuid, PinName); + FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(FlowAssetInstance, NodeGuid, PinName); } } else diff --git a/Source/Flow/Private/Nodes/FlowPin.cpp b/Source/Flow/Private/Nodes/FlowPin.cpp index 84a3351ad..8c2a34cd5 100644 --- a/Source/Flow/Private/Nodes/FlowPin.cpp +++ b/Source/Flow/Private/Nodes/FlowPin.cpp @@ -18,7 +18,6 @@ // Pin Record #if !UE_BUILD_SHIPPING -FString FPinRecord::NoActivations = TEXT("No activations"); FString FPinRecord::PinActivations = TEXT("Pin activations"); FString FPinRecord::ForcedActivation = TEXT(" (forced activation)"); FString FPinRecord::PassThroughActivation = TEXT(" (pass-through activation)"); diff --git a/Source/Flow/Private/Types/FlowDataPinValue.cpp b/Source/Flow/Private/Types/FlowDataPinValue.cpp index b10c3e206..34de3e9f7 100644 --- a/Source/Flow/Private/Types/FlowDataPinValue.cpp +++ b/Source/Flow/Private/Types/FlowDataPinValue.cpp @@ -8,7 +8,7 @@ #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDataPinValue) -const FString FFlowDataPinValue::StringArraySeparator = TEXT(","); +const FString FFlowDataPinValue::StringArraySeparator = TEXT(", "); const FFlowPinType* FFlowDataPinValue::LookupPinType() const { diff --git a/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp index 2d8b4db79..b7bec0109 100644 --- a/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp +++ b/Source/Flow/Private/Types/FlowDataPinValuesStandard.cpp @@ -479,6 +479,44 @@ FFlowDataPinValue_InstancedStruct::FFlowDataPinValue_InstancedStruct(const TArra #endif } +bool FFlowDataPinValue_InstancedStruct::TryConvertValuesToString(FString& OutString) const +{ + const FInstancedStruct DefaultValue; + + OutString = FlowArray::FormatArrayString( + Values, + [&DefaultValue](const FInstancedStruct& InstancedStruct) + { + FString ExportedString; + + constexpr UObject* ParentObject = nullptr; + constexpr UObject* ExportRootScope = nullptr; + + const bool bExported = InstancedStruct.ExportTextItem( + ExportedString, + DefaultValue, + ParentObject, + PPF_None, + ExportRootScope); + + if (!bExported) + { + // Fallback: just show the contained struct type name (or None) + if (const UScriptStruct* ScriptStruct = InstancedStruct.GetScriptStruct()) + { + return ScriptStruct->GetName(); + } + + return FString(); + } + + return ExportedString; + }, + StringArraySeparator); + + return true; +} + //====================================================================== // Object //====================================================================== diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index d562b8d8b..6de5ba0cf 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -26,7 +26,7 @@ class UFlowAssetParams; #if !UE_BUILD_SHIPPING DECLARE_DELEGATE(FFlowGraphEvent); -DECLARE_DELEGATE_TwoParams(FFlowSignalEvent, const FGuid& /*NodeGuid*/, const FName& /*PinName*/); +DECLARE_DELEGATE_ThreeParams(FFlowSignalEvent, UFlowAsset* /*FlowAsset*/, const FGuid& /*NodeGuid*/, const FName& /*PinName*/); #endif /** @@ -353,6 +353,10 @@ class FLOW_API UFlowAsset : public UObject // Get Flow Asset instance created by the given SubGraph node TWeakObjectPtr GetFlowInstance(UFlowNode_SubGraph* SubGraphNode) const; + // Public trigger input signature for the FFlowExecutionGate mechanism in the Flow Debugger + FORCEINLINE void TriggerDeferredInputFromDebugger(const FGuid& NodeGuid, const FName& PinName, const FConnectedPin& FromPin) + { TriggerInput(NodeGuid, PinName, FromPin); } + protected: void TriggerCustomInput_FromSubGraph(UFlowNode_SubGraph* Node, const FName& EventName) const; void TriggerCustomOutput(const FName& EventName); diff --git a/Source/Flow/Public/Interfaces/FlowExecutionGate.h b/Source/Flow/Public/Interfaces/FlowExecutionGate.h new file mode 100644 index 000000000..8e1abdf66 --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowExecutionGate.h @@ -0,0 +1,45 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "CoreMinimal.h" + +class UFlowAsset; + +/** + * Implemented by a debugger/runtime system (in another module) that can halt Flow execution. + * Flow runtime queries this through FFlowExecutionGate without depending on the debugger module. + */ +class FLOW_API IFlowExecutionGate +{ +public: + virtual ~IFlowExecutionGate() = default; + + /** Return true when Flow execution should be halted globally. */ + virtual bool IsFlowExecutionHalted() const = 0; +}; + +/** + * Global registry + minimal deferred-execution queue for Flow runtime. + */ +class FLOW_API FFlowExecutionGate +{ +public: + static void SetGate(IFlowExecutionGate* InGate); + static IFlowExecutionGate* GetGate(); + + /** True if a gate exists and it currently wants Flow execution halted. */ + static bool IsHalted(); + + /** If halted, queues the trigger for later. Returns true if queued (caller should early-out). */ + static bool EnqueueDeferredTriggerInput(UFlowAsset* FlowAssetInstance, const FGuid& NodeGuid, const FName& PinName, const struct FConnectedPin& FromPin); + + /** + * Flushes queued trigger inputs (FIFO). + * Safe to call even if nothing is queued. + */ + static void FlushDeferredTriggerInputs(); + +private: + static IFlowExecutionGate* Gate; +}; \ No newline at end of file diff --git a/Source/Flow/Public/Nodes/FlowPin.h b/Source/Flow/Public/Nodes/FlowPin.h index 465a2cf14..9aa88d484 100644 --- a/Source/Flow/Public/Nodes/FlowPin.h +++ b/Source/Flow/Public/Nodes/FlowPin.h @@ -388,7 +388,6 @@ struct FLOW_API FPinRecord FString HumanReadableTime; EFlowPinActivationType ActivationType; - static FString NoActivations; static FString PinActivations; static FString ForcedActivation; static FString PassThroughActivation; diff --git a/Source/Flow/Public/Types/FlowDataPinValuesStandard.h b/Source/Flow/Public/Types/FlowDataPinValuesStandard.h index bfe5a18a2..9757fbb45 100644 --- a/Source/Flow/Public/Types/FlowDataPinValuesStandard.h +++ b/Source/Flow/Public/Types/FlowDataPinValuesStandard.h @@ -35,8 +35,8 @@ struct FFlowDataPinValue_Bool : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Bool(ValueType InValue); FLOW_API FFlowDataPinValue_Bool(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -58,8 +58,8 @@ struct FFlowDataPinValue_Int : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Int(ValueType InValue); FLOW_API FFlowDataPinValue_Int(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -81,8 +81,8 @@ struct FFlowDataPinValue_Int64 : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Int64(ValueType InValue); FLOW_API FFlowDataPinValue_Int64(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -104,8 +104,8 @@ struct FFlowDataPinValue_Float : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Float(ValueType InValue); FLOW_API FFlowDataPinValue_Float(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -127,8 +127,8 @@ struct FFlowDataPinValue_Double : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Double(ValueType InValue); FLOW_API FFlowDataPinValue_Double(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -150,8 +150,8 @@ struct FFlowDataPinValue_Name : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Name(const ValueType& InValue); FLOW_API FFlowDataPinValue_Name(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -173,8 +173,8 @@ struct FFlowDataPinValue_String : public FFlowDataPinValue FLOW_API FFlowDataPinValue_String(const ValueType& InValue); FLOW_API FFlowDataPinValue_String(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -196,8 +196,8 @@ struct FFlowDataPinValue_Text : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Text(const ValueType& InValue); FLOW_API FFlowDataPinValue_Text(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -233,9 +233,9 @@ struct FFlowDataPinValue_Enum : public FFlowDataPinValue FLOW_API void OnEnumNameChanged(); #endif - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } virtual UField* GetFieldType() const override; - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; // Helper templates template @@ -315,8 +315,8 @@ struct FFlowDataPinValue_Vector : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Vector(const ValueType& InValue); FLOW_API FFlowDataPinValue_Vector(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -338,8 +338,8 @@ struct FFlowDataPinValue_Rotator : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Rotator(const ValueType& InValue); FLOW_API FFlowDataPinValue_Rotator(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -361,8 +361,8 @@ struct FFlowDataPinValue_Transform : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Transform(const ValueType& InValue); FLOW_API FFlowDataPinValue_Transform(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -384,8 +384,8 @@ struct FFlowDataPinValue_GameplayTag : public FFlowDataPinValue FLOW_API FFlowDataPinValue_GameplayTag(const ValueType& InValue); FLOW_API FFlowDataPinValue_GameplayTag(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -409,8 +409,8 @@ struct FFlowDataPinValue_GameplayTagContainer : public FFlowDataPinValue FLOW_API FFlowDataPinValue_GameplayTagContainer(const TArray& InValues); FLOW_API FFlowDataPinValue_GameplayTagContainer(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -432,7 +432,8 @@ struct FFlowDataPinValue_InstancedStruct : public FFlowDataPinValue FLOW_API FFlowDataPinValue_InstancedStruct(const ValueType& InValue); FLOW_API FFlowDataPinValue_InstancedStruct(const TArray& InValues); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -461,8 +462,8 @@ struct FFlowDataPinValue_Object : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Object(AActor* InActor, UClass* InClassFilter = nullptr /* nullptr here defaults to AActor::StaticClass() */ ); FLOW_API FFlowDataPinValue_Object(const TArray& InActors, UClass* InClassFilter = nullptr /* nullptr here defaults to AActor::StaticClass() */); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API virtual bool TryConvertValuesToString(FString& OutString) const override; }; //====================================================================== @@ -491,6 +492,6 @@ struct FFlowDataPinValue_Class : public FFlowDataPinValue FLOW_API FFlowDataPinValue_Class(const UClass* InClass, UClass* InClassFilter = UObject::StaticClass()); FLOW_API FFlowDataPinValue_Class(const TArray& InClasses, UClass* InClassFilter = UObject::StaticClass()); - virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } - virtual bool TryConvertValuesToString(FString& OutString) const override; + FLOW_API virtual const FFlowPinTypeName& GetPinTypeName() const override { return PinType::GetPinTypeNameStatic(); } + FLOW_API 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 index 939b67fa1..0b6031186 100644 --- a/Source/Flow/Public/Types/FlowNamedDataPinProperty.h +++ b/Source/Flow/Public/Types/FlowNamedDataPinProperty.h @@ -27,7 +27,7 @@ struct FFlowNamedDataPinProperty private: // DataPinProperty payload - UPROPERTY(VisibleAnywhere, Category = DataPins, meta = (DeprecatedProperty)) + UPROPERTY(meta = (DeprecatedProperty)) TInstancedStruct DataPinProperty; public: diff --git a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp index b9543a4aa..ed54ba520 100644 --- a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp +++ b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp @@ -21,6 +21,23 @@ UFlowDebuggerSubsystem::UFlowDebuggerSubsystem() UFlowSubsystem::OnInstancedTemplateRemoved.BindUObject(this, &ThisClass::OnInstancedTemplateRemoved); } +void UFlowDebuggerSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + FFlowExecutionGate::SetGate(this); +} + +void UFlowDebuggerSubsystem::Deinitialize() +{ + if (FFlowExecutionGate::GetGate() == this) + { + FFlowExecutionGate::SetGate(nullptr); + } + + Super::Deinitialize(); +} + bool UFlowDebuggerSubsystem::ShouldCreateSubsystem(UObject* Outer) const { // Only create an instance if there is no override implementation defined elsewhere @@ -36,23 +53,31 @@ bool UFlowDebuggerSubsystem::ShouldCreateSubsystem(UObject* Outer) const void UFlowDebuggerSubsystem::OnInstancedTemplateAdded(UFlowAsset* AssetTemplate) { + check(IsValid(AssetTemplate)); + AssetTemplate->OnPinTriggered.BindUObject(this, &ThisClass::OnPinTriggered); } -void UFlowDebuggerSubsystem::OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) const +void UFlowDebuggerSubsystem::OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) { + check(IsValid(AssetTemplate)); + AssetTemplate->OnPinTriggered.Unbind(); + + OnDebuggerFlowAssetTemplateRemoved.Broadcast(*AssetTemplate); } -void UFlowDebuggerSubsystem::OnPinTriggered(const FGuid& NodeGuid, const FName& PinName) +void UFlowDebuggerSubsystem::OnPinTriggered(UFlowAsset* FlowAsset, const FGuid& NodeGuid, const FName& PinName) { + check(IsValid(FlowAsset)); + if (FindBreakpoint(NodeGuid, PinName)) { - MarkAsHit(NodeGuid, PinName); + MarkAsHit(*FlowAsset, NodeGuid, PinName); } // Node breakpoints waits on any pin triggered - MarkAsHit(NodeGuid); + MarkAsHit(*FlowAsset, NodeGuid); } void UFlowDebuggerSubsystem::AddBreakpoint(const FGuid& NodeGuid) @@ -140,15 +165,23 @@ void UFlowDebuggerSubsystem::RemoveObsoletePinBreakpoints(const UEdGraphNode* No PinNames.Emplace(Pin->PinName); } - for (TPair& PinBreakpoint : NodeBreakpoint->PinBreakpoints) + TArray PinsToRemove; + PinsToRemove.Reserve(NodeBreakpoint->PinBreakpoints.Num()); + + for (const TPair& PinBreakpoint : NodeBreakpoint->PinBreakpoints) { if (!PinNames.Contains(PinBreakpoint.Key)) { - NodeBreakpoint->PinBreakpoints.Remove(PinBreakpoint.Key); - bAnythingRemoved = true; + PinsToRemove.Add(PinBreakpoint.Key); } } + for (const FName& PinName : PinsToRemove) + { + NodeBreakpoint->PinBreakpoints.Remove(PinName); + bAnythingRemoved = true; + } + if (NodeBreakpoint->IsEmpty()) { Settings->NodeBreakpoints.Remove(Node->NodeGuid); @@ -244,71 +277,169 @@ bool UFlowDebuggerSubsystem::IsBreakpointEnabled(const FGuid& NodeGuid, const FN return false; } -void UFlowDebuggerSubsystem::MarkAsHit(const FGuid& NodeGuid) +void UFlowDebuggerSubsystem::RequestHaltFlowExecution(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid) +{ + bHaltFlowExecution = true; + HaltedOnFlowAssetInstance = &FlowAssetInstance; + HaltedOnNodeGuid = NodeGuid; +} + +void UFlowDebuggerSubsystem::ClearHaltFlowExecution() +{ + bHaltFlowExecution = false; + HaltedOnFlowAssetInstance.Reset(); + HaltedOnNodeGuid.Invalidate(); +} + +void UFlowDebuggerSubsystem::ClearLastHitBreakpoint() +{ + if (!LastHitNodeGuid.IsValid()) + { + return; + } + + // Pin breakpoint "hit" state lives in the PinBreakpoints map, node breakpoint "hit" lives on NodeBreakpoint.Breakpoint. + if (!LastHitPinName.IsNone()) + { + if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(LastHitNodeGuid, LastHitPinName)) + { + PinBreakpoint->MarkAsHit(false); + } + } + else + { + if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(LastHitNodeGuid)) + { + NodeBreakpoint->MarkAsHit(false); + } + } + + LastHitNodeGuid.Invalidate(); + LastHitPinName = NAME_None; +} + +void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& NodeGuid) { if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(NodeGuid)) { if (NodeBreakpoint->IsEnabled()) { + // Ensure only one breakpoint location is "hit" at a time. + ClearLastHitBreakpoint(); + NodeBreakpoint->MarkAsHit(true); - PauseSession(); + + LastHitNodeGuid = NodeGuid; + LastHitPinName = NAME_None; + + RequestHaltFlowExecution(FlowAsset, NodeGuid); + + OnDebuggerBreakpointHit.Broadcast(FlowAsset, NodeGuid); + + PauseSession(FlowAsset); } } } -void UFlowDebuggerSubsystem::MarkAsHit(const FGuid& NodeGuid, const FName& PinName) +void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& NodeGuid, const FName& PinName) { if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(NodeGuid, PinName)) { if (PinBreakpoint->IsEnabled()) { + // Ensure only one breakpoint location is "hit" at a time. + ClearLastHitBreakpoint(); + PinBreakpoint->MarkAsHit(true); - PauseSession(); + + LastHitNodeGuid = NodeGuid; + LastHitPinName = PinName; + + RequestHaltFlowExecution(FlowAsset, NodeGuid); + + OnDebuggerBreakpointHit.Broadcast(FlowAsset, NodeGuid); + + PauseSession(FlowAsset); } } } -void UFlowDebuggerSubsystem::PauseSession() +void UFlowDebuggerSubsystem::PauseSession(const UFlowAsset& FlowAsset) { - SetPause(true); + SetPause(FlowAsset, true); } -void UFlowDebuggerSubsystem::ResumeSession() +void UFlowDebuggerSubsystem::ResumeSession(const UFlowAsset& FlowAsset) { - SetPause(false); + SetPause(FlowAsset, false); } -void UFlowDebuggerSubsystem::SetPause(const bool bPause) +void UFlowDebuggerSubsystem::SetPause(const UFlowAsset& FlowAsset, const bool bPause) { - // experimental implementation, untested, shows intent for future development - // here be dragons: same as APlayerController::SetPause, but we allow debugger to pause on clients - if (const UWorld* World = GEngine->GetWorldFromContextObject(this, EGetWorldErrorMode::LogAndReturnNull)) + // Default bWasPaused to opposite of bPause + // (which we hope to get a better measure if we can get access to what we need) + bool bWasPaused = !bPause; + + AGameModeBase* GameMode = nullptr; + APlayerController* PlayerController = nullptr; + + const UWorld* World = FlowAsset.GetWorld(); + if (IsValid(World)) { - if (const UGameInstance* GameInstance = World->GetGameInstance()) + GameMode = World->GetAuthGameMode(); + + if (IsValid(GameMode)) { - if (APlayerController* PlayerController = GameInstance->GetFirstLocalPlayerController()) + bWasPaused = GameMode->IsPaused(); + } + + const UGameInstance* GameInstance = World->GetGameInstance(); + if (IsValid(GameInstance)) + { + PlayerController = GameInstance->GetFirstLocalPlayerController(); + } + } + + if (bWasPaused != bPause) + { + if (bPause) + { + // Pausing (from an unpaused state) + + if (IsValid(PlayerController)) { - if (AGameModeBase* const GameMode = GetWorld()->GetAuthGameMode()) + if (IsValid(GameMode)) + { + GameMode->SetPause(PlayerController); + } + + if (AWorldSettings* WorldSettings = PlayerController->GetWorldSettings()) { - const bool bCurrentPauseState = PlayerController->IsPaused(); - if (bPause && !bCurrentPauseState) - { - GameMode->SetPause(PlayerController); - - if (AWorldSettings* WorldSettings = PlayerController->GetWorldSettings()) - { - WorldSettings->ForceNetUpdate(); - } - } - else if (!bPause && bCurrentPauseState) - { - if (GameMode->ClearPause()) - { - ClearHitBreakpoints(); - } - } + WorldSettings->ForceNetUpdate(); } } + + // Broadcast the Pause event + OnDebuggerPaused.Broadcast(FlowAsset); + } + else + { + // Resuming (from a paused state) + + ClearHaltFlowExecution(); + + // Replay any Flow propagation that was deferred while execution was halted. + FFlowExecutionGate::FlushDeferredTriggerInputs(); + + // Intentionally do NOT clear hit flags here. The editor-specific resume path will clear the last-hit + // breakpoint safely (without racing against immediate breakpoint hits during flush). + if (IsValid(GameMode)) + { + (void) GameMode->ClearPause(); + } + + // Broadcast the Resume event + OnDebuggerResumed.Broadcast(FlowAsset); } } } @@ -326,6 +457,9 @@ void UFlowDebuggerSubsystem::ClearHitBreakpoints() PinBreakpoint.Value.MarkAsHit(false); } } + + LastHitNodeGuid.Invalidate(); + LastHitPinName = NAME_None; } bool UFlowDebuggerSubsystem::IsBreakpointHit(const FGuid& NodeGuid) @@ -352,4 +486,4 @@ void UFlowDebuggerSubsystem::SaveSettings() { UFlowDebuggerSettings* Settings = GetMutableDefault(); Settings->SaveConfig(); -} +} \ No newline at end of file diff --git a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h index c83e004cd..2fb20e89e 100644 --- a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h +++ b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h @@ -5,32 +5,44 @@ #include "Subsystems/EngineSubsystem.h" #include "Debugger/FlowDebuggerTypes.h" +#include "Interfaces/FlowExecutionGate.h" + #include "FlowDebuggerSubsystem.generated.h" class UEdGraphNode; class UFlowAsset; +DECLARE_MULTICAST_DELEGATE_OneParam(FFlowAssetDebuggerEvent, const UFlowAsset& /*FlowAsset*/); +DECLARE_MULTICAST_DELEGATE_TwoParams(FFlowAssetDebuggerBreakpointHitEvent, const UFlowAsset& /*FlowAsset*/, const FGuid& /*NodeGuid*/); + /** - * Persistent subsystem supporting Flow Graph debugging. - * It might be utilized to use cook-specific graph debugger. - */ +* Persistent subsystem supporting Flow Graph debugging. +* It might be utilized to use cook-specific graph debugger. +*/ UCLASS() -class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem +class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem, public IFlowExecutionGate { GENERATED_BODY() public: UFlowDebuggerSubsystem(); + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + virtual bool ShouldCreateSubsystem(UObject* Outer) const override; protected: virtual void OnInstancedTemplateAdded(UFlowAsset* AssetTemplate); - virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) const; + virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate); - virtual void OnPinTriggered(const FGuid& NodeGuid, const FName& PinName); + virtual void OnPinTriggered(UFlowAsset* FlowAsset, const FGuid& NodeGuid, const FName& PinName); public: + // IFlowExecutionGate + virtual bool IsFlowExecutionHalted() const override { return bHaltFlowExecution; } + // -- + virtual void AddBreakpoint(const FGuid& NodeGuid); virtual void AddBreakpoint(const FGuid& NodeGuid, const FName& PinName); @@ -56,20 +68,45 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem virtual bool IsBreakpointEnabled(const FGuid& NodeGuid, const FName& PinName); protected: - virtual void MarkAsHit(const FGuid& NodeGuid); - virtual void MarkAsHit(const FGuid& NodeGuid, const FName& PinName); - - virtual void PauseSession(); - virtual void ResumeSession(); - void SetPause(const bool bPause); + virtual void MarkAsHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid); + virtual void MarkAsHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid, const FName& PinName); + + virtual void PauseSession(const UFlowAsset& FlowAssetInstance); + virtual void ResumeSession(const UFlowAsset& FlowAssetInstance); + void SetPause(const UFlowAsset& FlowAssetInstance, const bool bPause); + + /** + * Clears the "currently hit" breakpoint only (node or pin). + * This avoids races where blanket-clearing all hit flags can erase a newly-hit breakpoint during resume/flush. + */ + void ClearLastHitBreakpoint(); + /** Clears hit state for all breakpoints. Prefer ClearLastHitBreakpoint() for resume/step logic. */ virtual void ClearHitBreakpoints(); +protected: + void RequestHaltFlowExecution(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid); + void ClearHaltFlowExecution(); + public: virtual bool IsBreakpointHit(const FGuid& NodeGuid); virtual bool IsBreakpointHit(const FGuid& NodeGuid, const FName& PinName); + // Delegates for debugger events (broadcast when pausing, resuming, or hitting breakpoints) + FFlowAssetDebuggerEvent OnDebuggerPaused; + FFlowAssetDebuggerEvent OnDebuggerResumed; + FFlowAssetDebuggerBreakpointHitEvent OnDebuggerBreakpointHit; + FFlowAssetDebuggerEvent OnDebuggerFlowAssetTemplateRemoved; + private: + bool bHaltFlowExecution = false; + TWeakObjectPtr HaltedOnFlowAssetInstance; + FGuid HaltedOnNodeGuid; + + // Track the single breakpoint location that is currently "hit" (node or pin). + FGuid LastHitNodeGuid; + FName LastHitPinName; + /** Saves any modifications made to breakpoints */ virtual void SaveSettings(); -}; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp index c794ad0e5..12e54d6a2 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp @@ -67,6 +67,11 @@ SFlowAssetInstanceList::~SFlowAssetInstanceList() void SFlowAssetInstanceList::RefreshInstances() { + if (!TemplateAsset.IsValid()) + { + return; + } + // collect instance names of this Flow Asset InstanceNames = {MakeShareable(new FName(*NoInstanceSelectedText.ToString()))}; TemplateAsset->GetInstanceDisplayNames(InstanceNames); @@ -77,7 +82,7 @@ void SFlowAssetInstanceList::RefreshInstances() const FName& InspectedInstanceName = InspectedInstance->GetDisplayName(); for (const TSharedPtr& Instance : InstanceNames) { - if (*Instance == InspectedInstanceName) + if (Instance.IsValid() && *Instance == InspectedInstanceName) { SelectedInstance = Instance; break; @@ -109,7 +114,11 @@ void SFlowAssetInstanceList::OnSelectionChanged(const TSharedPtr Selected if (TemplateAsset.IsValid()) { - const FName NewSelectedInstanceName = (SelectedInstance.IsValid() && *SelectedInstance != *InstanceNames[0]) ? *SelectedInstance : NAME_None; + const bool bIsNoInstance = + (!SelectedInstance.IsValid()) || + (InstanceNames.Num() > 0 && SelectedInstance.IsValid() && *SelectedInstance == *InstanceNames[0]); + + const FName NewSelectedInstanceName = bIsNoInstance ? NAME_None : *SelectedInstance; TemplateAsset->SetInspectedInstance(NewSelectedInstanceName); } } diff --git a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp index b30394a0e..0f0428457 100644 --- a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp +++ b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp @@ -3,11 +3,18 @@ #include "Asset/FlowDebugEditorSubsystem.h" #include "Asset/FlowAssetEditor.h" #include "Asset/FlowMessageLogListing.h" +#include "Graph/FlowGraph.h" +#include "Graph/FlowGraphEditor.h" +#include "Graph/FlowGraphUtils.h" +#include "Graph/Nodes/FlowGraphNode.h" +#include "Interfaces/FlowExecutionGate.h" +#include "FlowAsset.h" #include "Editor/UnrealEdEngine.h" #include "Engine/Engine.h" #include "Engine/World.h" #include "Framework/Notifications/NotificationManager.h" +#include "Subsystems/AssetEditorSubsystem.h" #include "Templates/Function.h" #include "UnrealEdGlobals.h" #include "Widgets/Notifications/SNotificationList.h" @@ -21,6 +28,8 @@ UFlowDebugEditorSubsystem::UFlowDebugEditorSubsystem() FEditorDelegates::BeginPIE.AddUObject(this, &ThisClass::OnBeginPIE); FEditorDelegates::ResumePIE.AddUObject(this, &ThisClass::OnResumePIE); FEditorDelegates::EndPIE.AddUObject(this, &ThisClass::OnEndPIE); + + OnDebuggerBreakpointHit.AddUObject(this, &ThisClass::OnBreakpointHit); } void UFlowDebugEditorSubsystem::OnInstancedTemplateAdded(UFlowAsset* AssetTemplate) @@ -34,7 +43,7 @@ void UFlowDebugEditorSubsystem::OnInstancedTemplateAdded(UFlowAsset* AssetTempla } } -void UFlowDebugEditorSubsystem::OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) const +void UFlowDebugEditorSubsystem::OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) { AssetTemplate->OnRuntimeMessageAdded().RemoveAll(this); @@ -53,19 +62,34 @@ void UFlowDebugEditorSubsystem::OnRuntimeMessageAdded(const UFlowAsset* AssetTem void UFlowDebugEditorSubsystem::OnBeginPIE(const bool bIsSimulating) { - // clear all logs from a previous session + // Clear all logs from a previous session RuntimeLogs.Empty(); + + // Clear any stale "hit" state from previous run + ClearHitBreakpoints(); } void UFlowDebugEditorSubsystem::OnResumePIE(const bool bIsSimulating) { - ClearHitBreakpoints(); + // Editor-level resume event (also used by Advance Single Frame). + // This does not necessarily flow through AGameModeBase::ClearPause(), so we must unhalt Flow here. + // + // Clear only the last-hit breakpoint to return to enabled/disabled visuals without racing against + // a newly hit breakpoint during FlushDeferredTriggerInputs(). + ClearHaltFlowExecution(); + ClearLastHitBreakpoint(); + + FFlowExecutionGate::FlushDeferredTriggerInputs(); } void UFlowDebugEditorSubsystem::OnEndPIE(const bool bIsSimulating) { + // Ensure we don't carry over a halted state between PIE sessions. ClearHitBreakpoints(); + ClearHaltFlowExecution(); + FFlowExecutionGate::FlushDeferredTriggerInputs(); + for (const TPair, TSharedPtr>& Log : RuntimeLogs) { if (Log.Key.IsValid() && Log.Value->NumMessages(EMessageSeverity::Warning) > 0) @@ -92,12 +116,84 @@ void UFlowDebugEditorSubsystem::OnEndPIE(const bool bIsSimulating) } } -void UFlowDebugEditorSubsystem::PauseSession() +void UFlowDebugEditorSubsystem::PauseSession(const UFlowAsset& FlowAssetInstance) { - if (!GUnrealEd->SetPIEWorldsPaused(true)) + Super::PauseSession(FlowAssetInstance); + + constexpr bool bShouldBePaused = true; + const bool bWasPaused = GUnrealEd->SetPIEWorldsPaused(bShouldBePaused); + if (!bWasPaused) { GUnrealEd->PlaySessionPaused(); } } -#undef LOCTEXT_NAMESPACE +void UFlowDebugEditorSubsystem::ResumeSession(const UFlowAsset& FlowAssetInstance) +{ + Super::ResumeSession(FlowAssetInstance); + + constexpr bool bShouldBePaused = false; + const bool bWasPaused = GUnrealEd->SetPIEWorldsPaused(bShouldBePaused); + if (bWasPaused) + { + GUnrealEd->PlaySessionResumed(); + } +} + +void UFlowDebugEditorSubsystem::OnBreakpointHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid) const +{ + UFlowAsset* TemplateAsset = const_cast(FlowAssetInstance.GetTemplateAsset()); + if (!IsValid(TemplateAsset)) + { + return; + } + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor ? GEditor->GetEditorSubsystem() : nullptr; + if (!AssetEditorSubsystem) + { + return; + } + + if (!AssetEditorSubsystem->OpenEditorForAsset(TemplateAsset)) + { + return; + } + + TemplateAsset->SetInspectedInstance(FlowAssetInstance.GetDisplayName()); + + UFlowGraph* FlowGraph = Cast(TemplateAsset->GetGraph()); + if (!IsValid(FlowGraph)) + { + return; + } + + // NOTE: This may be redundant call, but it ensures Slate re-queries breakpoint hit state and updates node overlays immediately. + FlowGraph->NotifyGraphChanged(); + + UEdGraphNode* NodeToFocus = nullptr; + for (UEdGraphNode* Node : FlowGraph->Nodes) + { + UFlowGraphNode* FlowGraphNode = Cast(Node); + if (IsValid(FlowGraphNode) && FlowGraphNode->NodeGuid == NodeGuid) + { + NodeToFocus = FlowGraphNode; + break; + } + } + + if (!NodeToFocus) + { + return; + } + + const TSharedPtr GraphEditor = FFlowGraphUtils::GetFlowGraphEditor(FlowGraph); + if (GraphEditor.IsValid()) + { + constexpr bool bRequestRename = false; + constexpr bool bSelectNode = true; + + GraphEditor->JumpToNode(NodeToFocus, bRequestRename, bSelectNode); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp index ccdd093fb..674e035fb 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp @@ -53,6 +53,69 @@ void SFlowGraphEditor::Construct(const FArguments& InArgs, const TSharedPtrPinType.PinCategory)) + { + return false; + } + + // - If the owning node is not a UFlowGraphNode, allow it. + // - If it is a UFlowGraphNode, require it to allow breakpoints. + const UEdGraphNode* EdNode = Pin->GetOwningNode(); + if (!EdNode) + { + return false; + } + + const UFlowGraphNode* FlowNode = Cast(EdNode); + if (FlowNode && !FlowNode->CanPlaceBreakpoints()) + { + return false; + } + + OutNodeGuid = EdNode->NodeGuid; + OutPinName = Pin->PinName; + return true; +} + +const FFlowBreakpoint* SFlowGraphEditor::FindPinBreakpoint(UFlowDebuggerSubsystem* InDebuggerSubsystem, const UEdGraphPin* Pin) +{ + if (!InDebuggerSubsystem) + { + return nullptr; + } + + FGuid NodeGuid; + FName PinName; + if (!GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName)) + { + return nullptr; + } + + return InDebuggerSubsystem->FindBreakpoint(NodeGuid, PinName); +} + +bool SFlowGraphEditor::HasPinBreakpoint(UFlowDebuggerSubsystem* InDebuggerSubsystem, const UEdGraphPin* Pin) +{ + return FindPinBreakpoint(InDebuggerSubsystem, Pin) != nullptr; +} + +bool SFlowGraphEditor::HasEnabledPinBreakpoint(UFlowDebuggerSubsystem* InDebuggerSubsystem, const UEdGraphPin* Pin) +{ + if (const FFlowBreakpoint* BP = FindPinBreakpoint(InDebuggerSubsystem, Pin)) + { + return BP->IsEnabled(); + } + + return false; +} + void SFlowGraphEditor::BindGraphCommands() { FGraphEditorCommands::Register(); @@ -1104,11 +1167,14 @@ void SFlowGraphEditor::OnAddPinBreakpoint() check(DebuggerSubsystem.IsValid()); if (const UEdGraphPin* Pin = GetGraphPinForMenu()) { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) + FGuid NodeGuid; + FName PinName; + if (!GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName)) { - DebuggerSubsystem->AddBreakpoint(Pin->GetOwningNode()->NodeGuid, Pin->PinName); + return; } + + DebuggerSubsystem->AddBreakpoint(NodeGuid, PinName); } } @@ -1134,11 +1200,14 @@ bool SFlowGraphEditor::CanAddPinBreakpoint() check(DebuggerSubsystem.IsValid()); if (const UEdGraphPin* Pin = GetGraphPinForMenu()) { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) + FGuid NodeGuid; + FName PinName; + if (!GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName)) { - return DebuggerSubsystem->FindBreakpoint(Pin->GetOwningNode()->NodeGuid, Pin->PinName) == nullptr; + return false; } + + return DebuggerSubsystem->FindBreakpoint(NodeGuid, PinName) == nullptr; } return false; @@ -1161,11 +1230,14 @@ void SFlowGraphEditor::OnRemovePinBreakpoint() check(DebuggerSubsystem.IsValid()); if (const UEdGraphPin* Pin = GetGraphPinForMenu()) { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) + FGuid NodeGuid; + FName PinName; + if (!GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName)) { - DebuggerSubsystem->RemovePinBreakpoint(Pin->GetOwningNode()->NodeGuid, Pin->PinName); + return; } + + DebuggerSubsystem->RemovePinBreakpoint(NodeGuid, PinName); } } @@ -1189,16 +1261,7 @@ bool SFlowGraphEditor::CanRemoveBreakpoint() const bool SFlowGraphEditor::CanRemovePinBreakpoint() { check(DebuggerSubsystem.IsValid()); - if (const UEdGraphPin* Pin = GetGraphPinForMenu()) - { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) - { - return DebuggerSubsystem->FindBreakpoint(Pin->GetOwningNode()->NodeGuid, Pin->PinName) != nullptr; - } - } - - return false; + return HasPinBreakpoint(DebuggerSubsystem.Get(), GetGraphPinForMenu()); } void SFlowGraphEditor::OnEnableBreakpoint() const @@ -1218,11 +1281,14 @@ void SFlowGraphEditor::OnEnablePinBreakpoint() check(DebuggerSubsystem.IsValid()); if (const UEdGraphPin* Pin = GetGraphPinForMenu()) { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) + FGuid NodeGuid; + FName PinName; + if (!GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName)) { - DebuggerSubsystem->SetBreakpointEnabled(Pin->GetOwningNode()->NodeGuid, Pin->PinName, true); + return; } + + DebuggerSubsystem->SetBreakpointEnabled(NodeGuid, PinName, true); } } @@ -1245,17 +1311,8 @@ bool SFlowGraphEditor::CanEnableBreakpoint() const bool SFlowGraphEditor::CanEnablePinBreakpoint() { - if (const UEdGraphPin* Pin = GetGraphPinForMenu()) - { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) - { - const FFlowBreakpoint* Breakpoint = DebuggerSubsystem->FindBreakpoint(Pin->GetOwningNode()->NodeGuid, Pin->PinName); - return Breakpoint && !Breakpoint->IsEnabled(); - } - } - - return false; + return HasPinBreakpoint(DebuggerSubsystem.Get(), GetGraphPinForMenu()) + && !HasEnabledPinBreakpoint(DebuggerSubsystem.Get(), GetGraphPinForMenu()); } void SFlowGraphEditor::OnDisableBreakpoint() const @@ -1275,11 +1332,14 @@ void SFlowGraphEditor::OnDisablePinBreakpoint() check(DebuggerSubsystem.IsValid()); if (const UEdGraphPin* Pin = GetGraphPinForMenu()) { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) + FGuid NodeGuid; + FName PinName; + if (!GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName)) { - DebuggerSubsystem->SetBreakpointEnabled(Pin->GetOwningNode()->NodeGuid, Pin->PinName, false); + return; } + + DebuggerSubsystem->SetBreakpointEnabled(NodeGuid, PinName, false); } } @@ -1304,17 +1364,7 @@ bool SFlowGraphEditor::CanDisableBreakpoint() const bool SFlowGraphEditor::CanDisablePinBreakpoint() { check(DebuggerSubsystem.IsValid()); - if (const UEdGraphPin* Pin = GetGraphPinForMenu()) - { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) - { - const FFlowBreakpoint* Breakpoint = DebuggerSubsystem->FindBreakpoint(Pin->GetOwningNode()->NodeGuid, Pin->PinName); - return Breakpoint && Breakpoint->IsEnabled(); - } - } - - return false; + return HasEnabledPinBreakpoint(DebuggerSubsystem.Get(), GetGraphPinForMenu()); } void SFlowGraphEditor::OnToggleBreakpoint() const @@ -1334,11 +1384,14 @@ void SFlowGraphEditor::OnTogglePinBreakpoint() check(DebuggerSubsystem.IsValid()); if (const UEdGraphPin* Pin = GetGraphPinForMenu()) { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) + FGuid NodeGuid; + FName PinName; + if (!GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName)) { - DebuggerSubsystem->ToggleBreakpoint(Pin->GetOwningNode()->NodeGuid, Pin->PinName); + return; } + + DebuggerSubsystem->ToggleBreakpoint(NodeGuid, PinName); } } @@ -1359,11 +1412,9 @@ bool SFlowGraphEditor::CanTogglePinBreakpoint() { if (const UEdGraphPin* Pin = GetGraphPinForMenu()) { - const UFlowGraphNode* OwningNode = Cast(Pin->GetOwningNode()); - if (!OwningNode || OwningNode->CanPlaceBreakpoints()) - { - return true; - } + FGuid NodeGuid; + FName PinName; + return GetValidExecBreakpointPinContext(Pin, NodeGuid, PinName); } return false; diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index 420b5bdc7..211efdd24 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -15,6 +15,8 @@ #include "Graph/FlowGraphSettings.h" #include "Graph/Widgets/SFlowGraphNode.h" #include "Graph/Widgets/SGraphEditorActionMenuFlow.h" +#include "Interfaces/FlowDataPinValueSupplierInterface.h" +#include "Types/FlowDataPinValue.h" #include "BlueprintNodeHelpers.h" #include "Developer/ToolMenus/Public/ToolMenus.h" @@ -1082,8 +1084,10 @@ void UFlowGraphNode::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextO // start with the default hover text (from the pin's tool-tip) Super::GetPinHoverText(Pin, HoverTextOut); + const bool bHasValidPlayWorld = IsValid(GEditor->PlayWorld); + // add information on pin activations - if (GEditor->PlayWorld) + if (bHasValidPlayWorld) { if (const UFlowNode* InspectedNodeInstance = GetInspectedNodeInstance()) { @@ -1093,11 +1097,7 @@ void UFlowGraphNode::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextO } const TArray& PinRecords = InspectedNodeInstance->GetPinRecords(Pin.PinName, Pin.Direction); - if (PinRecords.Num() == 0) - { - HoverTextOut.Append(FPinRecord::NoActivations); - } - else + if (PinRecords.Num() > 0) { HoverTextOut.Append(FPinRecord::PinActivations); for (int32 i = 0; i < PinRecords.Num(); i++) @@ -1107,22 +1107,78 @@ void UFlowGraphNode::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextO switch (PinRecords[i].ActivationType) { - case EFlowPinActivationType::Default: - break; - case EFlowPinActivationType::Forced: - HoverTextOut.Append(FPinRecord::ForcedActivation); - break; - case EFlowPinActivationType::PassThrough: - HoverTextOut.Append(FPinRecord::PassThroughActivation); - break; - default: ; + case EFlowPinActivationType::Default: + break; + case EFlowPinActivationType::Forced: + HoverTextOut.Append(FPinRecord::ForcedActivation); + break; + case EFlowPinActivationType::PassThrough: + HoverTextOut.Append(FPinRecord::PassThroughActivation); + break; + default:; } } } } } -} + // add information on data pin values (only for data pins) + const bool bIsDataPinCategory = !FFlowPin::IsExecPinCategory(Pin.PinType.PinCategory); + if (bIsDataPinCategory) + { + const UEdGraphPin* GraphPinObj = &Pin; + + // Prefer showing runtime values when PIE (consistent with activation history) + const UFlowNodeBase* FlowNodeBase = GetFlowNodeBase(); + + if (bHasValidPlayWorld) + { + FlowNodeBase = GetInspectedNodeInstance(); + } + + FFlowDataPinResult DataResult(EFlowDataPinResolveResult::FailedNullFlowNodeBase); + + if (IsValid(FlowNodeBase)) + { + if (GraphPinObj->Direction == EGPD_Input) + { + // Input pins: do a pin resolve to source the value + DataResult = FlowNodeBase->TryResolveDataPin(GraphPinObj->PinName); + } + else + { + // Output pins: ask this node what it supplies for that output data pin + const UFlowNode* FlowNode = Cast(FlowNodeBase); + if (FlowNode) + { + DataResult = IFlowDataPinValueSupplierInterface::Execute_TrySupplyDataPin(FlowNode, GraphPinObj->PinName); + } + } + } + + FString ValueString; + + if (FlowPinType::IsSuccess(DataResult.Result) && DataResult.ResultValue.IsValid()) + { + const FFlowDataPinValue& Value = DataResult.ResultValue.Get(); + if (!Value.TryConvertValuesToString(ValueString)) + { + ValueString = TEXT(""); + } + } + else + { + ValueString = TEXT(""); + } + + if (!HoverTextOut.IsEmpty()) + { + HoverTextOut.Append(LINE_TERMINATOR).Append(LINE_TERMINATOR); + } + + HoverTextOut.Appendf(TEXT("Value: %s"), *ValueString); + } +} void UFlowGraphNode::ForcePinActivation(const FEdGraphPinReference PinReference) const { @@ -1990,4 +2046,4 @@ bool UFlowGraphNode::CanAcceptSubNodeAsChild(const UFlowGraphNode& OtherSubNode, return false; } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Public/Asset/FlowAssetEditor.h b/Source/FlowEditor/Public/Asset/FlowAssetEditor.h index 8c479b8e0..8c10beb1d 100644 --- a/Source/FlowEditor/Public/Asset/FlowAssetEditor.h +++ b/Source/FlowEditor/Public/Asset/FlowAssetEditor.h @@ -121,7 +121,7 @@ class FLOWEDITOR_API FFlowAssetEditor : public FAssetEditorToolkit, public FEdit public: /** Edits the specified FlowAsset object */ - void InitFlowAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UObject* ObjectToEdit); + virtual void InitFlowAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UObject* ObjectToEdit); protected: virtual void CreateToolbar(); diff --git a/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h b/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h index 3cbe13fa8..41e9230ec 100644 --- a/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h +++ b/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h @@ -25,7 +25,7 @@ class FLOWEDITOR_API UFlowDebugEditorSubsystem : public UFlowDebuggerSubsystem TMap, TSharedPtr> RuntimeLogs; virtual void OnInstancedTemplateAdded(UFlowAsset* AssetTemplate) override; - virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) const override; + virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) override; void OnRuntimeMessageAdded(const UFlowAsset* AssetTemplate, const TSharedRef& Message) const; @@ -33,5 +33,8 @@ class FLOWEDITOR_API UFlowDebugEditorSubsystem : public UFlowDebuggerSubsystem virtual void OnResumePIE(const bool bIsSimulating); virtual void OnEndPIE(const bool bIsSimulating); - virtual void PauseSession() override; -}; + virtual void PauseSession(const UFlowAsset& FlowAssetInstance) override; + virtual void ResumeSession(const UFlowAsset& FlowAssetInstance) override; + + void OnBreakpointHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid) const; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/Graph/FlowGraphEditor.h b/Source/FlowEditor/Public/Graph/FlowGraphEditor.h index 331ebb1e1..739ecb8a2 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphEditor.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphEditor.h @@ -10,7 +10,9 @@ class FFlowAssetEditor; class IDetailsView; +class UEdGraphPin; class UFlowDebuggerSubsystem; +struct FFlowBreakpoint; /** * @@ -107,6 +109,12 @@ class FLOWEDITOR_API SFlowGraphEditor : public SGraphEditor virtual void ReconstructNode() const; virtual bool CanReconstructNode() const; + // ---- Pin breakpoint helpers ---- + static bool GetValidExecBreakpointPinContext(const UEdGraphPin* Pin, FGuid& OutNodeGuid, FName& OutPinName); + static const FFlowBreakpoint* FindPinBreakpoint(UFlowDebuggerSubsystem* InDebuggerSubsystem, const UEdGraphPin* Pin); + static bool HasPinBreakpoint(UFlowDebuggerSubsystem* InDebuggerSubsystem, const UEdGraphPin* Pin); + static bool HasEnabledPinBreakpoint(UFlowDebuggerSubsystem* InDebuggerSubsystem, const UEdGraphPin* Pin); + private: void AddInput() const; bool CanAddInput() const; From 8e4fcf64dc5f877b5e8a15d63448ec50608778fe Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:11:34 -0800 Subject: [PATCH 3/9] Reverted this fix for PR --- Source/Flow/Public/Types/FlowNamedDataPinProperty.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Flow/Public/Types/FlowNamedDataPinProperty.h b/Source/Flow/Public/Types/FlowNamedDataPinProperty.h index 0b6031186..939b67fa1 100644 --- a/Source/Flow/Public/Types/FlowNamedDataPinProperty.h +++ b/Source/Flow/Public/Types/FlowNamedDataPinProperty.h @@ -27,7 +27,7 @@ struct FFlowNamedDataPinProperty private: // DataPinProperty payload - UPROPERTY(meta = (DeprecatedProperty)) + UPROPERTY(VisibleAnywhere, Category = DataPins, meta = (DeprecatedProperty)) TInstancedStruct DataPinProperty; public: From 15fb2809b37aa6b9041e89f542f9713970935514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Justy=C5=84ski?= Date: Tue, 27 Jan 2026 22:08:21 +0100 Subject: [PATCH 4/9] restore changes from other PRs --- .../Private/Asset/FlowAssetToolbar.cpp | 38 +------------------ Source/FlowEditor/Public/FlowEditorModule.h | 5 --- 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp index 4cb239390..4ef7e0220 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp @@ -92,37 +92,6 @@ SFlowAssetInstanceList::~SFlowAssetInstanceList() } } -void SFlowAssetInstanceList::RefreshInstances() -{ - if (!TemplateAsset.IsValid()) - { - return; - } - - // collect instance names of this Flow Asset - InstanceNames = {MakeShareable(new FName(*NoInstanceSelectedText.ToString()))}; - TemplateAsset->GetInstanceDisplayNames(InstanceNames); - - // select instance - if (const UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance()) - { - const FName& InspectedInstanceName = InspectedInstance->GetDisplayName(); - for (const TSharedPtr& Instance : InstanceNames) - { - if (Instance.IsValid() && *Instance == InspectedInstanceName) - { - SelectedInstance = Instance; - break; - } - } - } - else - { - // default object is always available - SelectedInstance = InstanceNames[0]; - } -} - EVisibility SFlowAssetInstanceList::GetDebuggerVisibility() { return GEditor->PlayWorld ? EVisibility::Visible : EVisibility::Collapsed; @@ -258,12 +227,7 @@ void SFlowAssetInstanceList::OnInstanceSelectionChanged(const TSharedPtr(SelectedInstance->ResolveObjectPtr()); if (TemplateAsset.IsValid()) { - const bool bIsNoInstance = - (!SelectedInstance.IsValid()) || - (InstanceNames.Num() > 0 && SelectedInstance.IsValid() && *SelectedInstance == *InstanceNames[0]); - - const FName NewSelectedInstanceName = bIsNoInstance ? NAME_None : *SelectedInstance; - TemplateAsset->SetInspectedInstance(NewSelectedInstanceName); + TemplateAsset->SetInspectedInstance(Instance); } } } diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 16370e74c..9e32e6605 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -32,11 +32,6 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen TSet CustomStructLayouts; bool bIsRegisteredForAssetChanges = false; - TSharedPtr MenuExtensibilityManager; - TSharedPtr ToolBarExtensibilityManager; - - bool bIsRegisteredForAssetChanges = false; - public: virtual void StartupModule() override; virtual void ShutdownModule() override; From ee0bcffc4029d925c0ad973b6d37f8e3c2c2e642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Justy=C5=84ski?= Date: Tue, 27 Jan 2026 22:30:12 +0100 Subject: [PATCH 5/9] integrated usage of Flow Node as main parameter passed with breakpoint hit --- Source/Flow/Private/Nodes/FlowNode.cpp | 10 +- Source/Flow/Public/FlowAsset.h | 2 +- .../Debugger/FlowDebuggerSubsystem.cpp | 136 +++++++++--------- .../Public/Debugger/FlowDebuggerSubsystem.h | 16 +-- .../Asset/FlowDebugEditorSubsystem.cpp | 20 +-- .../Public/Asset/FlowDebugEditorSubsystem.h | 6 +- 6 files changed, 91 insertions(+), 99 deletions(-) diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index 8685f8345..753f36e93 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -894,10 +894,9 @@ void UFlowNode::TriggerInput(const FName& PinName, const EFlowPinActivationType TArray& Records = InputRecords.FindOrAdd(PinName); Records.Add(FPinRecord(FApp::GetCurrentTime(), ActivationType)); - UFlowAsset* FlowAssetInstance = GetFlowAsset(); - if (const UFlowAsset* FlowAssetTemplate = FlowAssetInstance->GetTemplateAsset()) + if (const UFlowAsset* FlowAssetTemplate = GetFlowAsset()->GetTemplateAsset()) { - (void) FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(FlowAssetInstance, NodeGuid, PinName); + (void)FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(this, PinName); } #endif } @@ -961,10 +960,9 @@ void UFlowNode::TriggerOutput(const FName PinName, const bool bFinish /*= false* TArray& Records = OutputRecords.FindOrAdd(PinName); Records.Add(FPinRecord(FApp::GetCurrentTime(), ActivationType)); - UFlowAsset* FlowAssetInstance = GetFlowAsset(); - if (const UFlowAsset* FlowAssetTemplate = FlowAssetInstance->GetTemplateAsset()) + if (const UFlowAsset* FlowAssetTemplate = GetFlowAsset()->GetTemplateAsset()) { - FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(FlowAssetInstance, NodeGuid, PinName); + FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(this, PinName); } } else diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index 1414afd49..bd16210e7 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -26,7 +26,7 @@ class UFlowAssetParams; #if !UE_BUILD_SHIPPING DECLARE_DELEGATE(FFlowGraphEvent); -DECLARE_DELEGATE_ThreeParams(FFlowSignalEvent, UFlowAsset* /*FlowAsset*/, const FGuid& /*NodeGuid*/, const FName& /*PinName*/); +DECLARE_DELEGATE_TwoParams(FFlowSignalEvent, UFlowNode* /*FlowNode*/, const FName& /*PinName*/); #endif /** diff --git a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp index 0578516a7..086b40b5f 100644 --- a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp +++ b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp @@ -68,17 +68,15 @@ void UFlowDebuggerSubsystem::OnInstancedTemplateRemoved(UFlowAsset* AssetTemplat OnDebuggerFlowAssetTemplateRemoved.Broadcast(*AssetTemplate); } -void UFlowDebuggerSubsystem::OnPinTriggered(UFlowAsset* FlowAsset, const FGuid& NodeGuid, const FName& PinName) +void UFlowDebuggerSubsystem::OnPinTriggered(UFlowNode* FlowNode, const FName& PinName) { - check(IsValid(FlowAsset)); - - if (FindBreakpoint(NodeGuid, PinName)) + if (FindBreakpoint(FlowNode->NodeGuid, PinName)) { - MarkAsHit(*FlowAsset, NodeGuid, PinName); + MarkAsHit(FlowNode, PinName); } // Node breakpoints waits on any pin triggered - MarkAsHit(*FlowAsset, NodeGuid); + MarkAsHit(FlowNode); } void UFlowDebuggerSubsystem::AddBreakpoint(const FGuid& NodeGuid) @@ -328,48 +326,7 @@ bool UFlowDebuggerSubsystem::IsBreakpointEnabled(const FGuid& NodeGuid, const FN return false; } -void UFlowDebuggerSubsystem::RequestHaltFlowExecution(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid) -{ - bHaltFlowExecution = true; - HaltedOnFlowAssetInstance = &FlowAssetInstance; - HaltedOnNodeGuid = NodeGuid; -} - -void UFlowDebuggerSubsystem::ClearHaltFlowExecution() -{ - bHaltFlowExecution = false; - HaltedOnFlowAssetInstance.Reset(); - HaltedOnNodeGuid.Invalidate(); -} - -void UFlowDebuggerSubsystem::ClearLastHitBreakpoint() -{ - if (!LastHitNodeGuid.IsValid()) - { - return; - } - - // Pin breakpoint "hit" state lives in the PinBreakpoints map, node breakpoint "hit" lives on NodeBreakpoint.Breakpoint. - if (!LastHitPinName.IsNone()) - { - if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(LastHitNodeGuid, LastHitPinName)) - { - PinBreakpoint->MarkAsHit(false); - } - } - else - { - if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(LastHitNodeGuid)) - { - NodeBreakpoint->MarkAsHit(false); - } - } - - LastHitNodeGuid.Invalidate(); - LastHitPinName = NAME_None; -} - -void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& NodeGuid) +bool UFlowDebuggerSubsystem::HasAnyBreakpointsEnabled(const TWeakObjectPtr FlowAsset) { UFlowDebuggerSettings* Settings = GetMutableDefault(); for (const TPair& Node : FlowAsset->GetNodes()) @@ -419,9 +376,9 @@ bool UFlowDebuggerSubsystem::HasAnyBreakpointsDisabled(const TWeakObjectPtrNodeGuid)) + if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(FlowNode->NodeGuid)) { if (NodeBreakpoint->IsEnabled()) { @@ -430,23 +387,21 @@ bool UFlowDebuggerSubsystem::TryMarkAsHit(const UFlowNode* Node) NodeBreakpoint->MarkAsHit(true); - LastHitNodeGuid = NodeGuid; + LastHitNodeGuid = FlowNode->NodeGuid; LastHitPinName = NAME_None; - RequestHaltFlowExecution(FlowAsset, NodeGuid); + RequestHaltFlowExecution(FlowNode); - OnDebuggerBreakpointHit.Broadcast(FlowAsset, NodeGuid); + OnDebuggerBreakpointHit.Broadcast(FlowNode); - PauseSession(FlowAsset); + PauseSession(*FlowNode); } } - - return false; } -void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& NodeGuid, const FName& PinName) +void UFlowDebuggerSubsystem::MarkAsHit(const UFlowNode* FlowNode, const FName& PinName) { - if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(Node->NodeGuid, PinName)) + if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(FlowNode->NodeGuid, PinName)) { if (PinBreakpoint->IsEnabled()) { @@ -455,31 +410,69 @@ void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& PinBreakpoint->MarkAsHit(true); - LastHitNodeGuid = NodeGuid; + LastHitNodeGuid = FlowNode->NodeGuid; LastHitPinName = PinName; - RequestHaltFlowExecution(FlowAsset, NodeGuid); + RequestHaltFlowExecution(FlowNode); + OnDebuggerBreakpointHit.Broadcast(FlowNode); + + PauseSession(*FlowNode); + } + } +} + +void UFlowDebuggerSubsystem::RequestHaltFlowExecution(const UFlowNode* Node) +{ + bHaltFlowExecution = true; + HaltedOnFlowAssetInstance = Node->GetFlowAsset(); + HaltedOnNodeGuid = Node->NodeGuid; +} - OnDebuggerBreakpointHit.Broadcast(FlowAsset, NodeGuid); +void UFlowDebuggerSubsystem::ClearHaltFlowExecution() +{ + bHaltFlowExecution = false; + HaltedOnFlowAssetInstance.Reset(); + HaltedOnNodeGuid.Invalidate(); +} - PauseSession(FlowAsset); +void UFlowDebuggerSubsystem::ClearLastHitBreakpoint() +{ + if (!LastHitNodeGuid.IsValid()) + { + return; + } + + // Pin breakpoint "hit" state lives in the PinBreakpoints map, node breakpoint "hit" lives on NodeBreakpoint.Breakpoint. + if (!LastHitPinName.IsNone()) + { + if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(LastHitNodeGuid, LastHitPinName)) + { + PinBreakpoint->MarkAsHit(false); + } + } + else + { + if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(LastHitNodeGuid)) + { + NodeBreakpoint->MarkAsHit(false); } } - return false; + LastHitNodeGuid.Invalidate(); + LastHitPinName = NAME_None; } -void UFlowDebuggerSubsystem::PauseSession(const UFlowAsset& FlowAsset) +void UFlowDebuggerSubsystem::PauseSession(const UFlowNode& FlowNode) { - SetPause(FlowAsset, true); + SetPause(FlowNode, true); } -void UFlowDebuggerSubsystem::ResumeSession(const UFlowAsset& FlowAsset) +void UFlowDebuggerSubsystem::ResumeSession(const UFlowNode& FlowNode) { - SetPause(FlowAsset, false); + SetPause(FlowNode, false); } -void UFlowDebuggerSubsystem::SetPause(const UFlowAsset& FlowAsset, const bool bPause) +void UFlowDebuggerSubsystem::SetPause(const UFlowNode& FlowNode, const bool bPause) { // Default bWasPaused to opposite of bPause // (which we hope to get a better measure if we can get access to what we need) @@ -488,7 +481,8 @@ void UFlowDebuggerSubsystem::SetPause(const UFlowAsset& FlowAsset, const bool bP AGameModeBase* GameMode = nullptr; APlayerController* PlayerController = nullptr; - const UWorld* World = FlowAsset.GetWorld(); + const UFlowAsset* FlowAssetInstance = FlowNode.GetFlowAsset(); + const UWorld* World = FlowAssetInstance->GetWorld(); if (IsValid(World)) { GameMode = World->GetAuthGameMode(); @@ -525,7 +519,7 @@ void UFlowDebuggerSubsystem::SetPause(const UFlowAsset& FlowAsset, const bool bP } // Broadcast the Pause event - OnDebuggerPaused.Broadcast(FlowAsset); + OnDebuggerPaused.Broadcast(*FlowAssetInstance); } else { @@ -544,7 +538,7 @@ void UFlowDebuggerSubsystem::SetPause(const UFlowAsset& FlowAsset, const bool bP } // Broadcast the Resume event - OnDebuggerResumed.Broadcast(FlowAsset); + OnDebuggerResumed.Broadcast(*FlowAssetInstance); } } } diff --git a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h index 3b92114ba..a5f0027c1 100644 --- a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h +++ b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h @@ -15,7 +15,7 @@ class UFlowAsset; class UFlowNode; DECLARE_MULTICAST_DELEGATE_OneParam(FFlowAssetDebuggerEvent, const UFlowAsset& /*FlowAsset*/); -DECLARE_MULTICAST_DELEGATE_TwoParams(FFlowAssetDebuggerBreakpointHitEvent, const UFlowAsset& /*FlowAsset*/, const FGuid& /*NodeGuid*/); +DECLARE_MULTICAST_DELEGATE_OneParam(FFlowAssetDebuggerBreakpointHitEvent, const UFlowNode* /*FlowNode*/); /** * Persistent subsystem supporting Flow Graph debugging. @@ -41,7 +41,7 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem, public virtual void OnInstancedTemplateAdded(UFlowAsset* AssetTemplate); virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate); - virtual void OnPinTriggered(UFlowAsset* FlowAsset, const FGuid& NodeGuid, const FName& PinName); + virtual void OnPinTriggered(UFlowNode* FlowNode, const FName& PinName); public: // IFlowExecutionGate @@ -78,12 +78,12 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem, public static bool HasAnyBreakpointsDisabled(const TWeakObjectPtr FlowAsset); protected: - virtual void MarkAsHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid); - virtual void MarkAsHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid, const FName& PinName); + virtual void MarkAsHit(const UFlowNode* FlowNode); + virtual void MarkAsHit(const UFlowNode* FlowNode, const FName& PinName); - virtual void PauseSession(const UFlowAsset& FlowAssetInstance); - virtual void ResumeSession(const UFlowAsset& FlowAssetInstance); - void SetPause(const UFlowAsset& FlowAssetInstance, const bool bPause); + virtual void PauseSession(const UFlowNode& FlowNode); + virtual void ResumeSession(const UFlowNode& FlowNode); + void SetPause(const UFlowNode& FlowNode, const bool bPause); /** * Clears the "currently hit" breakpoint only (node or pin). @@ -95,7 +95,7 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem, public virtual void ClearHitBreakpoints(); protected: - void RequestHaltFlowExecution(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid); + void RequestHaltFlowExecution(const UFlowNode* Node); void ClearHaltFlowExecution(); public: diff --git a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp index 80f1d7e15..09cee9ff7 100644 --- a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp +++ b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp @@ -116,9 +116,9 @@ void UFlowDebugEditorSubsystem::OnEndPIE(const bool bIsSimulating) } } -void UFlowDebugEditorSubsystem::PauseSession(const UFlowAsset& FlowAssetInstance) +void UFlowDebugEditorSubsystem::PauseSession(const UFlowNode& FlowNode) { - Super::PauseSession(FlowAssetInstance); + Super::PauseSession(FlowNode); constexpr bool bShouldBePaused = true; const bool bWasPaused = GUnrealEd->SetPIEWorldsPaused(bShouldBePaused); @@ -131,7 +131,7 @@ void UFlowDebugEditorSubsystem::PauseSession(const UFlowAsset& FlowAssetInstance { bPausedAtFlowBreakpoint = true; - const UFlowAsset* HitInstance = Node->GetFlowAsset(); + const UFlowAsset* HitInstance = FlowNode.GetFlowAsset(); if (ensure(HitInstance)) { UFlowAsset* AssetTemplate = HitInstance->GetTemplateAsset(); @@ -142,7 +142,7 @@ void UFlowDebugEditorSubsystem::PauseSession(const UFlowAsset& FlowAssetInstance { if (const TSharedPtr FlowAssetEditor = FFlowGraphUtils::GetFlowAssetEditor(AssetTemplate)) { - FlowAssetEditor->JumpToNode(Node->GetGraphNode()); + FlowAssetEditor->JumpToNode(FlowNode.GetGraphNode()); } } } @@ -151,9 +151,9 @@ void UFlowDebugEditorSubsystem::PauseSession(const UFlowAsset& FlowAssetInstance } } -void UFlowDebugEditorSubsystem::ResumeSession(const UFlowAsset& FlowAssetInstance) +void UFlowDebugEditorSubsystem::ResumeSession(const UFlowNode& FlowNode) { - Super::ResumeSession(FlowAssetInstance); + Super::ResumeSession(FlowNode); constexpr bool bShouldBePaused = false; const bool bWasPaused = GUnrealEd->SetPIEWorldsPaused(bShouldBePaused); @@ -163,9 +163,9 @@ void UFlowDebugEditorSubsystem::ResumeSession(const UFlowAsset& FlowAssetInstanc } } -void UFlowDebugEditorSubsystem::OnBreakpointHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid) const +void UFlowDebugEditorSubsystem::OnBreakpointHit(const UFlowNode* FlowNode) const { - UFlowAsset* TemplateAsset = const_cast(FlowAssetInstance.GetTemplateAsset()); + UFlowAsset* TemplateAsset = const_cast(FlowNode->GetFlowAsset()->GetTemplateAsset()); if (!IsValid(TemplateAsset)) { return; @@ -182,7 +182,7 @@ void UFlowDebugEditorSubsystem::OnBreakpointHit(const UFlowAsset& FlowAssetInsta return; } - TemplateAsset->SetInspectedInstance(FlowAssetInstance.GetDisplayName()); + TemplateAsset->SetInspectedInstance(FlowNode->GetFlowAsset()); UFlowGraph* FlowGraph = Cast(TemplateAsset->GetGraph()); if (!IsValid(FlowGraph)) @@ -197,7 +197,7 @@ void UFlowDebugEditorSubsystem::OnBreakpointHit(const UFlowAsset& FlowAssetInsta for (UEdGraphNode* Node : FlowGraph->Nodes) { UFlowGraphNode* FlowGraphNode = Cast(Node); - if (IsValid(FlowGraphNode) && FlowGraphNode->NodeGuid == NodeGuid) + if (IsValid(FlowGraphNode) && FlowGraphNode->NodeGuid == FlowNode->NodeGuid) { NodeToFocus = FlowGraphNode; break; diff --git a/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h b/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h index a1788fa13..4b2f839be 100644 --- a/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h +++ b/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h @@ -33,8 +33,8 @@ class FLOWEDITOR_API UFlowDebugEditorSubsystem : public UFlowDebuggerSubsystem virtual void OnResumePIE(const bool bIsSimulating); virtual void OnEndPIE(const bool bIsSimulating); - virtual void PauseSession(const UFlowAsset& FlowAssetInstance) override; - virtual void ResumeSession(const UFlowAsset& FlowAssetInstance) override; + virtual void PauseSession(const UFlowNode& FlowNode) override; + virtual void ResumeSession(const UFlowNode& FlowNode) override; - void OnBreakpointHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid) const; + void OnBreakpointHit(const UFlowNode* FlowNode) const; }; From a5df9946444a5d52cbe9cbb27de92717aa86a6af Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:40:32 -0800 Subject: [PATCH 6/9] Integrations with our version --- Source/Flow/Public/AddOns/FlowNodeAddOn.h | 2 +- .../Debugger/FlowDebuggerSubsystem.cpp | 108 ++++++----- .../Public/Debugger/FlowDebuggerSubsystem.h | 8 +- .../Private/Asset/FlowAssetToolbar.cpp | 167 +++++++----------- .../Asset/FlowDebugEditorSubsystem.cpp | 25 +-- .../Private/Graph/FlowGraphEditor.cpp | 3 + Source/FlowEditor/Public/FlowEditorModule.h | 1 - 7 files changed, 124 insertions(+), 190 deletions(-) diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn.h b/Source/Flow/Public/AddOns/FlowNodeAddOn.h index aabea5c4d..e2d2931be 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn.h @@ -41,7 +41,7 @@ class UFlowNodeAddOn : public UFlowNodeBase // UFlowNodeBase #if WITH_EDITOR - virtual UEdGraphNode* GetGraphNode() const override; + FLOW_API virtual UEdGraphNode* GetGraphNode() const override; #endif // AddOns may opt in to be eligible for a given parent diff --git a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp index 0578516a7..38a30bb96 100644 --- a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp +++ b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp @@ -16,7 +16,6 @@ #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowDebuggerSubsystem) UFlowDebuggerSubsystem::UFlowDebuggerSubsystem() - : bPausedAtFlowBreakpoint(false) { UFlowSubsystem::OnInstancedTemplateAdded.BindUObject(this, &ThisClass::OnInstancedTemplateAdded); UFlowSubsystem::OnInstancedTemplateRemoved.BindUObject(this, &ThisClass::OnInstancedTemplateRemoved); @@ -371,57 +370,7 @@ void UFlowDebuggerSubsystem::ClearLastHitBreakpoint() void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& NodeGuid) { - UFlowDebuggerSettings* Settings = GetMutableDefault(); - for (const TPair& Node : FlowAsset->GetNodes()) - { - if (FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(Node.Key)) - { - if (NodeBreakpoint->Breakpoint.IsActive() && NodeBreakpoint->Breakpoint.IsEnabled()) - { - return true; - } - - for (auto& [Name, PinBreakpoint] : NodeBreakpoint->PinBreakpoints) - { - if (PinBreakpoint.IsEnabled()) - { - return true; - } - } - } - } - - return false; -} - -bool UFlowDebuggerSubsystem::HasAnyBreakpointsDisabled(const TWeakObjectPtr FlowAsset) -{ - UFlowDebuggerSettings* Settings = GetMutableDefault(); - for (const TPair& Node : FlowAsset->GetNodes()) - { - if (FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(Node.Key)) - { - if (NodeBreakpoint->Breakpoint.IsActive() && !NodeBreakpoint->Breakpoint.IsEnabled()) - { - return true; - } - - for (auto& [Name, PinBreakpoint] : NodeBreakpoint->PinBreakpoints) - { - if (!PinBreakpoint.IsEnabled()) - { - return true; - } - } - } - } - - return false; -} - -bool UFlowDebuggerSubsystem::TryMarkAsHit(const UFlowNode* Node) -{ - if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(Node->NodeGuid)) + if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(NodeGuid)) { if (NodeBreakpoint->IsEnabled()) { @@ -440,13 +389,11 @@ bool UFlowDebuggerSubsystem::TryMarkAsHit(const UFlowNode* Node) PauseSession(FlowAsset); } } - - return false; } void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& NodeGuid, const FName& PinName) { - if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(Node->NodeGuid, PinName)) + if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(NodeGuid, PinName)) { if (PinBreakpoint->IsEnabled()) { @@ -465,8 +412,54 @@ void UFlowDebuggerSubsystem::MarkAsHit(const UFlowAsset& FlowAsset, const FGuid& PauseSession(FlowAsset); } } +} - return false; +bool UFlowDebuggerSubsystem::HasAnyBreakpointsEnabled(const TWeakObjectPtr& FlowAsset) +{ + return HasAnyBreakpointsMatching(FlowAsset, true); +} + +bool UFlowDebuggerSubsystem::HasAnyBreakpointsDisabled(const TWeakObjectPtr& FlowAsset) +{ + return HasAnyBreakpointsMatching(FlowAsset, false); +} + +bool UFlowDebuggerSubsystem::HasAnyBreakpointsMatching(const TWeakObjectPtr& FlowAsset, bool bDesiresEnabled) +{ + if (!FlowAsset.IsValid()) + { + return false; + } + + const UFlowDebuggerSettings* Settings = GetDefault(); + if (!Settings) + { + return false; + } + + for (const TPair& NodePair : FlowAsset->GetNodes()) + { + if (const FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(NodePair.Key)) + { + // Node-level breakpoint must be active to count (matches original behavior) + if (NodeBreakpoint->Breakpoint.IsActive() && + (NodeBreakpoint->Breakpoint.IsEnabled() == bDesiresEnabled)) + { + return true; + } + + // Pin-level breakpoints + for (const auto& PinPair : NodeBreakpoint->PinBreakpoints) + { + if (PinPair.Value.IsEnabled() == bDesiresEnabled) + { + return true; + } + } + } + } + + return false; } void UFlowDebuggerSubsystem::PauseSession(const UFlowAsset& FlowAsset) @@ -551,9 +544,8 @@ void UFlowDebuggerSubsystem::SetPause(const UFlowAsset& FlowAsset, const bool bP void UFlowDebuggerSubsystem::ClearHitBreakpoints() { - bPausedAtFlowBreakpoint = false; - UFlowDebuggerSettings* Settings = GetMutableDefault(); + for (TPair& NodeBreakpoint : Settings->NodeBreakpoints) { NodeBreakpoint.Value.Breakpoint.MarkAsHit(false); diff --git a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h index 3b92114ba..1b1878d20 100644 --- a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h +++ b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h @@ -34,9 +34,6 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem, public virtual bool ShouldCreateSubsystem(UObject* Outer) const override; -protected: - bool bPausedAtFlowBreakpoint; - protected: virtual void OnInstancedTemplateAdded(UFlowAsset* AssetTemplate); virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate); @@ -74,8 +71,9 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem, public virtual bool IsBreakpointEnabled(const FGuid& NodeGuid); virtual bool IsBreakpointEnabled(const FGuid& NodeGuid, const FName& PinName); - static bool HasAnyBreakpointsEnabled(const TWeakObjectPtr FlowAsset); - static bool HasAnyBreakpointsDisabled(const TWeakObjectPtr FlowAsset); + static bool HasAnyBreakpointsEnabled(const TWeakObjectPtr& FlowAsset); + static bool HasAnyBreakpointsDisabled(const TWeakObjectPtr& FlowAsset); + static bool HasAnyBreakpointsMatching(const TWeakObjectPtr& FlowAsset, bool bDesiresEnabled); protected: virtual void MarkAsHit(const UFlowAsset& FlowAssetInstance, const FGuid& NodeGuid); diff --git a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp index 4cb239390..0fd6ce003 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp @@ -11,6 +11,7 @@ #include "FlowAsset.h" #include "Nodes/Graph/FlowNode_SubGraph.h" +#include "Brushes/SlateRoundedBoxBrush.h" #include "Kismet2/DebuggerCommands.h" #include "Misc/Attribute.h" #include "Misc/MessageDialog.h" @@ -52,7 +53,7 @@ void SFlowAssetInstanceList::Construct(const FArguments& InArgs, const TWeakObje .ContentPadding(FMargin(0.f, 2.f)) [ SNew(STextBlock) - .Text(this, &SFlowAssetInstanceList::GetSelectedContextName) + .Text(this, &SFlowAssetInstanceList::GetSelectedContextName) ]; InstanceComboBox = SNew(SComboBox>) @@ -62,26 +63,26 @@ void SFlowAssetInstanceList::Construct(const FArguments& InArgs, const TWeakObje .ContentPadding(FMargin(0.f, 2.f)) [ SNew(STextBlock) - .Text(this, &SFlowAssetInstanceList::GetSelectedInstanceName) + .Text(this, &SFlowAssetInstanceList::GetSelectedInstanceName) ]; ChildSlot - [ - SNew(SHorizontalBox) - .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(0.0f, 0.0f, 8.0f, 0.0f) - [ - ContextComboBox.ToSharedRef() - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ - InstanceComboBox.ToSharedRef() - ] - ]; + SNew(SHorizontalBox) + .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 8.0f, 0.0f) + [ + ContextComboBox.ToSharedRef() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 4.0f, 0.0f) + [ + InstanceComboBox.ToSharedRef() + ] + ]; } SFlowAssetInstanceList::~SFlowAssetInstanceList() @@ -92,37 +93,6 @@ SFlowAssetInstanceList::~SFlowAssetInstanceList() } } -void SFlowAssetInstanceList::RefreshInstances() -{ - if (!TemplateAsset.IsValid()) - { - return; - } - - // collect instance names of this Flow Asset - InstanceNames = {MakeShareable(new FName(*NoInstanceSelectedText.ToString()))}; - TemplateAsset->GetInstanceDisplayNames(InstanceNames); - - // select instance - if (const UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance()) - { - const FName& InspectedInstanceName = InspectedInstance->GetDisplayName(); - for (const TSharedPtr& Instance : InstanceNames) - { - if (Instance.IsValid() && *Instance == InspectedInstanceName) - { - SelectedInstance = Instance; - break; - } - } - } - else - { - // default object is always available - SelectedInstance = InstanceNames[0]; - } -} - EVisibility SFlowAssetInstanceList::GetDebuggerVisibility() { return GEditor->PlayWorld ? EVisibility::Visible : EVisibility::Collapsed; @@ -258,12 +228,7 @@ void SFlowAssetInstanceList::OnInstanceSelectionChanged(const TSharedPtr(SelectedInstance->ResolveObjectPtr()); if (TemplateAsset.IsValid()) { - const bool bIsNoInstance = - (!SelectedInstance.IsValid()) || - (InstanceNames.Num() > 0 && SelectedInstance.IsValid() && *SelectedInstance == *InstanceNames[0]); - - const FName NewSelectedInstanceName = bIsNoInstance ? NAME_None : *SelectedInstance; - TemplateAsset->SetInspectedInstance(NewSelectedInstanceName); + TemplateAsset->SetInspectedInstance(Instance); } } } @@ -305,28 +270,28 @@ void SFlowAssetBreadcrumb::Construct(const FArguments& InArgs, const TWeakObject // create breadcrumb SAssignNew(BreadcrumbTrail, SBreadcrumbTrail) - .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) - .OnCrumbClicked(this, &SFlowAssetBreadcrumb::OnCrumbClicked) - .ButtonStyle(FAppStyle::Get(), "SimpleButton") - .TextStyle(FAppStyle::Get(), "NormalText") - .ButtonContentPadding(FMargin(2.0f, 4.0f)) - .DelimiterImage(FAppStyle::GetBrush("Icons.ChevronRight")) - .ShowLeadingDelimiter(true) - .PersistentBreadcrumbs(true); + .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) + .OnCrumbClicked(this, &SFlowAssetBreadcrumb::OnCrumbClicked) + .ButtonStyle(FAppStyle::Get(), "SimpleButton") + .TextStyle(FAppStyle::Get(), "NormalText") + .ButtonContentPadding(FMargin(2.0f, 4.0f)) + .DelimiterImage(FAppStyle::GetBrush("Icons.ChevronRight")) + .ShowLeadingDelimiter(true) + .PersistentBreadcrumbs(true); ChildSlot - [ - SNew(SBorder) - .Visibility(this, &SFlowAssetBreadcrumb::GetBreadcrumbVisibility) - .BorderImage(new FSlateRoundedBoxBrush(FStyleColors::Transparent, 4, FStyleColors::InputOutline, 1)) [ - SNew(SBox) - .MaxDesiredWidth(500.f) - [ - BreadcrumbTrail.ToSharedRef() - ] - ] - ]; + SNew(SBorder) + .Visibility(this, &SFlowAssetBreadcrumb::GetBreadcrumbVisibility) + .BorderImage(new FSlateRoundedBoxBrush(FStyleColors::Transparent, 4, FStyleColors::InputOutline, 1)) + [ + SNew(SBox) + .MaxDesiredWidth(500.f) + [ + BreadcrumbTrail.ToSharedRef() + ] + ] + ]; TemplateAsset->OnDebuggerRefresh().AddSP(this, &SFlowAssetBreadcrumb::FillBreadcrumb); FillBreadcrumb(); @@ -342,7 +307,7 @@ void SFlowAssetBreadcrumb::FillBreadcrumb() const BreadcrumbTrail->ClearCrumbs(); if (const UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance()) { - TArray> InstancesFromRoot = {InspectedInstance}; + TArray> InstancesFromRoot = { InspectedInstance }; const UFlowAsset* CheckedInstance = InspectedInstance; while (UFlowAsset* ParentInstance = CheckedInstance->GetParentInstance()) @@ -411,23 +376,23 @@ void FFlowAssetToolbar::BuildAssetToolbar(UToolMenu* ToolbarMenu) const // Visual Diff: menu to choose asset revision compared with the current one Section.AddDynamicEntry("SourceControlCommands", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) - { - const UFlowAssetEditorContext* Context = InSection.FindContext(); - if (Context && Context->FlowAssetEditor.IsValid()) { - InSection.InsertPosition = FToolMenuInsert(); - FToolMenuEntry DiffEntry = FToolMenuEntry::InitComboButton( - "Diff", - FUIAction(), - FOnGetContent::CreateStatic(&FFlowAssetToolbar::MakeDiffMenu, Context), - LOCTEXT("Diff", "Diff"), - LOCTEXT("FlowAssetEditorDiffToolTip", "Diff against previous revisions"), - FSlateIcon(FAppStyle::Get().GetStyleSetName(), "BlueprintDiff.ToolbarIcon") - ); - DiffEntry.StyleNameOverride = "CalloutToolbar"; - InSection.AddEntry(DiffEntry); - } - })); + const UFlowAssetEditorContext* Context = InSection.FindContext(); + if (Context && Context->FlowAssetEditor.IsValid()) + { + InSection.InsertPosition = FToolMenuInsert(); + FToolMenuEntry DiffEntry = FToolMenuEntry::InitComboButton( + "Diff", + FUIAction(), + FOnGetContent::CreateStatic(&FFlowAssetToolbar::MakeDiffMenu, Context), + LOCTEXT("Diff", "Diff"), + LOCTEXT("FlowAssetEditorDiffToolTip", "Diff against previous revisions"), + FSlateIcon(FAppStyle::Get().GetStyleSetName(), "BlueprintDiff.ToolbarIcon") + ); + DiffEntry.StyleNameOverride = "CalloutToolbar"; + InSection.AddEntry(DiffEntry); + } + })); Section.AddEntry(FToolMenuEntry::InitToolBarButton( FFlowToolbarCommands::Get().SearchInAsset, @@ -467,8 +432,8 @@ static void OnDiffRevisionPicked(FRevisionInfo const& RevisionInfo, const FStrin if (PreviousAsset) { const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); - const FRevisionInfo OldRevision = {Revision->GetRevision(), Revision->GetCheckInIdentifier(), Revision->GetDate()}; - const FRevisionInfo CurrentRevision = {TEXT(""), Revision->GetCheckInIdentifier(), Revision->GetDate()}; + const FRevisionInfo OldRevision = { Revision->GetRevision(), Revision->GetCheckInIdentifier(), Revision->GetDate() }; + const FRevisionInfo CurrentRevision = { TEXT(""), Revision->GetCheckInIdentifier(), Revision->GetDate() }; AssetToolsModule.Get().DiffAssets(PreviousAsset, CurrentAsset.Get(), OldRevision, CurrentRevision); } } @@ -518,16 +483,16 @@ void FFlowAssetToolbar::BuildDebuggerToolbar(UToolMenu* ToolbarMenu) const Section.InsertPosition = FToolMenuInsert("View", EToolMenuInsertType::After); Section.AddDynamicEntry("DebuggingCommands", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) - { - const UFlowAssetEditorContext* Context = InSection.FindContext(); - if (Context && Context->GetFlowAsset()) { - FPlayWorldCommands::BuildToolbar(InSection); + const UFlowAssetEditorContext* Context = InSection.FindContext(); + if (Context && Context->GetFlowAsset()) + { + FPlayWorldCommands::BuildToolbar(InSection); - InSection.AddEntry(FToolMenuEntry::InitWidget("AssetInstances", SNew(SFlowAssetInstanceList, Context->GetFlowAsset()), FText(), true)); - InSection.AddEntry(FToolMenuEntry::InitWidget("AssetBreadcrumb", SNew(SFlowAssetBreadcrumb, Context->GetFlowAsset()), FText(), true)); - } - })); + InSection.AddEntry(FToolMenuEntry::InitWidget("AssetInstances", SNew(SFlowAssetInstanceList, Context->GetFlowAsset()), FText(), true)); + InSection.AddEntry(FToolMenuEntry::InitWidget("AssetBreadcrumb", SNew(SFlowAssetBreadcrumb, Context->GetFlowAsset()), FText(), true)); + } + })); } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp index 80f1d7e15..dc7d9858e 100644 --- a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp +++ b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp @@ -124,29 +124,6 @@ void UFlowDebugEditorSubsystem::PauseSession(const UFlowAsset& FlowAssetInstance const bool bWasPaused = GUnrealEd->SetPIEWorldsPaused(bShouldBePaused); if (!bWasPaused) { - return; - } - - if (GUnrealEd->SetPIEWorldsPaused(true)) - { - bPausedAtFlowBreakpoint = true; - - const UFlowAsset* HitInstance = Node->GetFlowAsset(); - if (ensure(HitInstance)) - { - UFlowAsset* AssetTemplate = HitInstance->GetTemplateAsset(); - AssetTemplate->SetInspectedInstance(HitInstance); - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - if (AssetEditorSubsystem->OpenEditorForAsset(AssetTemplate)) - { - if (const TSharedPtr FlowAssetEditor = FFlowGraphUtils::GetFlowAssetEditor(AssetTemplate)) - { - FlowAssetEditor->JumpToNode(Node->GetGraphNode()); - } - } - } - GUnrealEd->PlaySessionPaused(); } } @@ -182,7 +159,7 @@ void UFlowDebugEditorSubsystem::OnBreakpointHit(const UFlowAsset& FlowAssetInsta return; } - TemplateAsset->SetInspectedInstance(FlowAssetInstance.GetDisplayName()); + TemplateAsset->SetInspectedInstance(&FlowAssetInstance); UFlowGraph* FlowGraph = Cast(TemplateAsset->GetGraph()); if (!IsValid(FlowGraph)) diff --git a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp index 5217b4990..0e9a97c82 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp @@ -19,6 +19,9 @@ #include "LevelEditor.h" #include "Modules/ModuleManager.h" #include "ScopedTransaction.h" +#include "ToolMenu.h" +#include "ToolMenuDelegates.h" +#include "ToolMenus.h" #include "UnrealEdGlobals.h" #include "Widgets/Docking/SDockTab.h" #include "Algo/AnyOf.h" diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 16370e74c..c25a3e190 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -30,7 +30,6 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen TArray> RegisteredAssetActions; TSet CustomClassLayouts; TSet CustomStructLayouts; - bool bIsRegisteredForAssetChanges = false; TSharedPtr MenuExtensibilityManager; TSharedPtr ToolBarExtensibilityManager; From ad4063a6eaf246282e3e2a9a84bdf26cd1a67f56 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:32:03 -0800 Subject: [PATCH 7/9] compile fixes --- Source/Flow/Private/Nodes/FlowNode.cpp | 154 +++++++-------- Source/Flow/Private/Nodes/FlowNodeBase.cpp | 2 + .../Debugger/FlowDebuggerSubsystem.cpp | 177 +++++++----------- .../Asset/FlowDebugEditorSubsystem.cpp | 1 - Source/FlowEditor/Public/FlowEditorModule.h | 2 + 5 files changed, 145 insertions(+), 191 deletions(-) diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index 753f36e93..e9dbc076c 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -415,36 +415,54 @@ void UFlowNode::SetAutoOutputDataPins(const TArray& AutoOutputPins) AutoOutputDataPins = AutoOutputPins; } +void UFlowNode::SetConnections(const TMap& InConnections) +{ + const TMap OldConnections = Connections; + Connections = InConnections; + OnConnectionsChanged(OldConnections); +} + #endif // WITH_EDITOR -FFlowDataPinResult UFlowNode::TrySupplyDataPin_Implementation(FName PinName) const +// #FlowDataPinLegacy +void UFlowNode::FixupDataPinTypes() { - const FFlowPin* FlowPin = FindOutputPinByName(PinName); - if (!FlowPin) + FixupDataPinTypesForArray(InputPins); + FixupDataPinTypesForArray(OutputPins); +#if WITH_EDITOR + FixupDataPinTypesForArray(AutoInputDataPins); + FixupDataPinTypesForArray(AutoOutputDataPins); +#endif +} + +void UFlowNode::FixupDataPinTypesForArray(TArray& MutableDataPinArray) +{ + for (FFlowPin& MutableFlowPin : MutableDataPinArray) { - // Also look in the Input Pins (for supplying default values for unconnected pins) - FlowPin = FindInputPinByName(PinName); - if (!FlowPin) - { - return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); - } + FixupDataPinTypesForPin(MutableFlowPin); } +} - const FFlowPinType* DataPinType = FlowPin->ResolveFlowPinType(); - if (!DataPinType) +void UFlowNode::FixupDataPinTypesForPin(FFlowPin& MutableDataPin) +{ + const FFlowPinTypeName NewPinTypeName = FFlowPin::GetPinTypeNameForLegacyPinType(MutableDataPin.PinType); + + if (!NewPinTypeName.IsNone()) { - return FFlowDataPinResult(EFlowDataPinResolveResult::FailedMismatchedType); + MutableDataPin.SetPinTypeName(NewPinTypeName); } - FFlowDataPinResult SuppliedResult; - if (TryGatherPropertyOwnersAndPopulateResult(PinName, *DataPinType, *FlowPin, SuppliedResult)) + if (MutableDataPin.GetPinTypeName().IsNone()) { - return SuppliedResult; + // Ensure we have a pin type even if the enum was invalid before + MutableDataPin.SetPinTypeName(FFlowPinType_Exec::GetPinTypeNameStatic()); } - return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); + MutableDataPin.PinType = EFlowPinType::Invalid; } +// -- + bool UFlowNode::TryFindPropertyByPinName( const UObject& PropertyOwnerObject, const FName& PinName, @@ -488,6 +506,34 @@ void UFlowNode::GatherPotentialPropertyOwnersForDataPins(TArray& 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, @@ -513,7 +559,6 @@ bool UFlowNode::TryGatherPropertyOwnersAndPopulateResult( return false; } -// -- bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName(const FName& PinName, TFlowPinValueSupplierDataArray& InOutPinValueSupplierDatas) const { @@ -574,66 +619,6 @@ bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName(const FName& PinName, T return !InOutPinValueSupplierDatas.IsEmpty(); } -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); - } -} - -// #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; -} -// -- - -void UFlowNode::SetConnections(const TMap& InConnections) -{ - const TMap OldConnections = Connections; - Connections = InConnections; - OnConnectionsChanged(OldConnections); -} - TSet UFlowNode::GatherConnectedNodes() const { TSet Result; @@ -1102,6 +1087,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 ec0b0e2eb..35e406f8a 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -958,6 +958,7 @@ bool UFlowNodeBase::BuildMessage(FString& Message) const } #endif +#if WITH_EDITOR EDataValidationResult UFlowNodeBase::ValidateNode() { EDataValidationResult ValidationResult = EDataValidationResult::NotValidated; @@ -969,6 +970,7 @@ EDataValidationResult UFlowNodeBase::ValidateNode() return ValidationResult; } +#endif bool UFlowNodeBase::TryAddValueToFormatNamedArguments(const FFlowNamedDataPinProperty& NamedDataPinProperty, FFormatNamedArguments& InOutArguments) const { diff --git a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp index b18ceeae3..e014fe8e1 100644 --- a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp +++ b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp @@ -325,21 +325,44 @@ bool UFlowDebuggerSubsystem::IsBreakpointEnabled(const FGuid& NodeGuid, const FN return false; } -bool UFlowDebuggerSubsystem::HasAnyBreakpointsEnabled(const TWeakObjectPtr FlowAsset) +bool UFlowDebuggerSubsystem::HasAnyBreakpointsEnabled(const TWeakObjectPtr& FlowAsset) { - UFlowDebuggerSettings* Settings = GetMutableDefault(); - for (const TPair& Node : FlowAsset->GetNodes()) + return HasAnyBreakpointsMatching(FlowAsset, true); +} + +bool UFlowDebuggerSubsystem::HasAnyBreakpointsDisabled(const TWeakObjectPtr& FlowAsset) +{ + return HasAnyBreakpointsMatching(FlowAsset, false); +} + +bool UFlowDebuggerSubsystem::HasAnyBreakpointsMatching(const TWeakObjectPtr& FlowAsset, bool bDesiresEnabled) +{ + if (!FlowAsset.IsValid()) { - if (FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(Node.Key)) + return false; + } + + const UFlowDebuggerSettings* Settings = GetDefault(); + if (!Settings) + { + return false; + } + + for (const TPair& NodePair : FlowAsset->GetNodes()) + { + if (const FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(NodePair.Key)) { - if (NodeBreakpoint->Breakpoint.IsActive() && NodeBreakpoint->Breakpoint.IsEnabled()) + // Node-level breakpoint must be active to count (matches original behavior) + if (NodeBreakpoint->Breakpoint.IsActive() && + (NodeBreakpoint->Breakpoint.IsEnabled() == bDesiresEnabled)) { return true; } - for (auto& [Name, PinBreakpoint] : NodeBreakpoint->PinBreakpoints) + // Pin-level breakpoints + for (const auto& PinPair : NodeBreakpoint->PinBreakpoints) { - if (PinBreakpoint.IsEnabled()) + if (PinPair.Value.IsEnabled() == bDesiresEnabled) { return true; } @@ -350,29 +373,45 @@ bool UFlowDebuggerSubsystem::HasAnyBreakpointsEnabled(const TWeakObjectPtr FlowAsset) +void UFlowDebuggerSubsystem::RequestHaltFlowExecution(const UFlowNode* Node) { - UFlowDebuggerSettings* Settings = GetMutableDefault(); - for (const TPair& Node : FlowAsset->GetNodes()) + bHaltFlowExecution = true; + HaltedOnFlowAssetInstance = Node->GetFlowAsset(); + HaltedOnNodeGuid = Node->NodeGuid; +} + +void UFlowDebuggerSubsystem::ClearHaltFlowExecution() +{ + bHaltFlowExecution = false; + HaltedOnFlowAssetInstance.Reset(); + HaltedOnNodeGuid.Invalidate(); +} + +void UFlowDebuggerSubsystem::ClearLastHitBreakpoint() +{ + if (!LastHitNodeGuid.IsValid()) { - if (FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(Node.Key)) - { - if (NodeBreakpoint->Breakpoint.IsActive() && !NodeBreakpoint->Breakpoint.IsEnabled()) - { - return true; - } + return; + } - for (auto& [Name, PinBreakpoint] : NodeBreakpoint->PinBreakpoints) - { - if (!PinBreakpoint.IsEnabled()) - { - return true; - } - } + // Pin breakpoint "hit" state lives in the PinBreakpoints map, node breakpoint "hit" lives on NodeBreakpoint.Breakpoint. + if (!LastHitPinName.IsNone()) + { + if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(LastHitNodeGuid, LastHitPinName)) + { + PinBreakpoint->MarkAsHit(false); + } + } + else + { + if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(LastHitNodeGuid)) + { + NodeBreakpoint->MarkAsHit(false); } } - return false; + LastHitNodeGuid.Invalidate(); + LastHitPinName = NAME_None; } void UFlowDebuggerSubsystem::MarkAsHit(const UFlowNode* FlowNode) @@ -413,6 +452,7 @@ void UFlowDebuggerSubsystem::MarkAsHit(const UFlowNode* FlowNode, const FName& P LastHitPinName = PinName; RequestHaltFlowExecution(FlowNode); + OnDebuggerBreakpointHit.Broadcast(FlowNode); PauseSession(*FlowNode); @@ -420,95 +460,6 @@ void UFlowDebuggerSubsystem::MarkAsHit(const UFlowNode* FlowNode, const FName& P } } -void UFlowDebuggerSubsystem::RequestHaltFlowExecution(const UFlowNode* Node) -{ - bHaltFlowExecution = true; - HaltedOnFlowAssetInstance = Node->GetFlowAsset(); - HaltedOnNodeGuid = Node->NodeGuid; -} - -void UFlowDebuggerSubsystem::ClearHaltFlowExecution() -{ - bHaltFlowExecution = false; - HaltedOnFlowAssetInstance.Reset(); - HaltedOnNodeGuid.Invalidate(); -} - -void UFlowDebuggerSubsystem::ClearLastHitBreakpoint() -{ - if (!LastHitNodeGuid.IsValid()) - { - return; - } - - // Pin breakpoint "hit" state lives in the PinBreakpoints map, node breakpoint "hit" lives on NodeBreakpoint.Breakpoint. - if (!LastHitPinName.IsNone()) - { - if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(LastHitNodeGuid, LastHitPinName)) - { - PinBreakpoint->MarkAsHit(false); - } - } - else - { - if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(LastHitNodeGuid)) - { - NodeBreakpoint->MarkAsHit(false); - } - } -} - -bool UFlowDebuggerSubsystem::HasAnyBreakpointsEnabled(const TWeakObjectPtr& FlowAsset) -{ - return HasAnyBreakpointsMatching(FlowAsset, true); -} - -bool UFlowDebuggerSubsystem::HasAnyBreakpointsDisabled(const TWeakObjectPtr& FlowAsset) -{ - return HasAnyBreakpointsMatching(FlowAsset, false); -} - -bool UFlowDebuggerSubsystem::HasAnyBreakpointsMatching(const TWeakObjectPtr& FlowAsset, bool bDesiresEnabled) -{ - if (!FlowAsset.IsValid()) - { - return false; - } - - const UFlowDebuggerSettings* Settings = GetDefault(); - if (!Settings) - { - return false; - } - - for (const TPair& NodePair : FlowAsset->GetNodes()) - { - if (const FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(NodePair.Key)) - { - // Node-level breakpoint must be active to count (matches original behavior) - if (NodeBreakpoint->Breakpoint.IsActive() && - (NodeBreakpoint->Breakpoint.IsEnabled() == bDesiresEnabled)) - { - return true; - } - - // Pin-level breakpoints - for (const auto& PinPair : NodeBreakpoint->PinBreakpoints) - { - if (PinPair.Value.IsEnabled() == bDesiresEnabled) - { - return true; - } - } - } - } - - return false; - - LastHitNodeGuid.Invalidate(); - LastHitPinName = NAME_None; -} - void UFlowDebuggerSubsystem::PauseSession(const UFlowNode& FlowNode) { SetPause(FlowNode, true); diff --git a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp index 675f86805..90cac2020 100644 --- a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp +++ b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp @@ -159,7 +159,6 @@ void UFlowDebugEditorSubsystem::OnBreakpointHit(const UFlowNode* FlowNode) const return; } - TemplateAsset->SetInspectedInstance(&FlowAssetInstance); TemplateAsset->SetInspectedInstance(FlowNode->GetFlowAsset()); UFlowGraph* FlowGraph = Cast(TemplateAsset->GetGraph()); diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 325b8493d..b75187d91 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -31,6 +31,8 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen TSet CustomClassLayouts; TSet CustomStructLayouts; + bool bIsRegisteredForAssetChanges = false; + public: virtual void StartupModule() override; virtual void ShutdownModule() override; From 7d4b31f3363003effc6708fc8a4c8124d7d9bfd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Justy=C5=84ski?= Date: Wed, 28 Jan 2026 17:20:44 +0100 Subject: [PATCH 8/9] reverted formatting changes --- .../Private/Asset/FlowAssetToolbar.cpp | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp index 0fd6ce003..633a52816 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp @@ -53,7 +53,7 @@ void SFlowAssetInstanceList::Construct(const FArguments& InArgs, const TWeakObje .ContentPadding(FMargin(0.f, 2.f)) [ SNew(STextBlock) - .Text(this, &SFlowAssetInstanceList::GetSelectedContextName) + .Text(this, &SFlowAssetInstanceList::GetSelectedContextName) ]; InstanceComboBox = SNew(SComboBox>) @@ -63,26 +63,26 @@ void SFlowAssetInstanceList::Construct(const FArguments& InArgs, const TWeakObje .ContentPadding(FMargin(0.f, 2.f)) [ SNew(STextBlock) - .Text(this, &SFlowAssetInstanceList::GetSelectedInstanceName) + .Text(this, &SFlowAssetInstanceList::GetSelectedInstanceName) ]; ChildSlot + [ + SNew(SHorizontalBox) + .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 8.0f, 0.0f) [ - SNew(SHorizontalBox) - .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(0.0f, 0.0f, 8.0f, 0.0f) - [ - ContextComboBox.ToSharedRef() - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(0.0f, 0.0f, 4.0f, 0.0f) - [ - InstanceComboBox.ToSharedRef() - ] - ]; + ContextComboBox.ToSharedRef() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 4.0f, 0.0f) + [ + InstanceComboBox.ToSharedRef() + ] + ]; } SFlowAssetInstanceList::~SFlowAssetInstanceList() @@ -270,28 +270,28 @@ void SFlowAssetBreadcrumb::Construct(const FArguments& InArgs, const TWeakObject // create breadcrumb SAssignNew(BreadcrumbTrail, SBreadcrumbTrail) - .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) - .OnCrumbClicked(this, &SFlowAssetBreadcrumb::OnCrumbClicked) - .ButtonStyle(FAppStyle::Get(), "SimpleButton") - .TextStyle(FAppStyle::Get(), "NormalText") - .ButtonContentPadding(FMargin(2.0f, 4.0f)) - .DelimiterImage(FAppStyle::GetBrush("Icons.ChevronRight")) - .ShowLeadingDelimiter(true) - .PersistentBreadcrumbs(true); + .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) + .OnCrumbClicked(this, &SFlowAssetBreadcrumb::OnCrumbClicked) + .ButtonStyle(FAppStyle::Get(), "SimpleButton") + .TextStyle(FAppStyle::Get(), "NormalText") + .ButtonContentPadding(FMargin(2.0f, 4.0f)) + .DelimiterImage(FAppStyle::GetBrush("Icons.ChevronRight")) + .ShowLeadingDelimiter(true) + .PersistentBreadcrumbs(true); ChildSlot + [ + SNew(SBorder) + .Visibility(this, &SFlowAssetBreadcrumb::GetBreadcrumbVisibility) + .BorderImage(new FSlateRoundedBoxBrush(FStyleColors::Transparent, 4, FStyleColors::InputOutline, 1)) [ - SNew(SBorder) - .Visibility(this, &SFlowAssetBreadcrumb::GetBreadcrumbVisibility) - .BorderImage(new FSlateRoundedBoxBrush(FStyleColors::Transparent, 4, FStyleColors::InputOutline, 1)) - [ - SNew(SBox) - .MaxDesiredWidth(500.f) - [ - BreadcrumbTrail.ToSharedRef() - ] - ] - ]; + SNew(SBox) + .MaxDesiredWidth(500.f) + [ + BreadcrumbTrail.ToSharedRef() + ] + ] + ]; TemplateAsset->OnDebuggerRefresh().AddSP(this, &SFlowAssetBreadcrumb::FillBreadcrumb); FillBreadcrumb(); @@ -307,7 +307,7 @@ void SFlowAssetBreadcrumb::FillBreadcrumb() const BreadcrumbTrail->ClearCrumbs(); if (const UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance()) { - TArray> InstancesFromRoot = { InspectedInstance }; + TArray> InstancesFromRoot = {InspectedInstance}; const UFlowAsset* CheckedInstance = InspectedInstance; while (UFlowAsset* ParentInstance = CheckedInstance->GetParentInstance()) @@ -376,23 +376,23 @@ void FFlowAssetToolbar::BuildAssetToolbar(UToolMenu* ToolbarMenu) const // Visual Diff: menu to choose asset revision compared with the current one Section.AddDynamicEntry("SourceControlCommands", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) + { + const UFlowAssetEditorContext* Context = InSection.FindContext(); + if (Context && Context->FlowAssetEditor.IsValid()) { - const UFlowAssetEditorContext* Context = InSection.FindContext(); - if (Context && Context->FlowAssetEditor.IsValid()) - { - InSection.InsertPosition = FToolMenuInsert(); - FToolMenuEntry DiffEntry = FToolMenuEntry::InitComboButton( - "Diff", - FUIAction(), - FOnGetContent::CreateStatic(&FFlowAssetToolbar::MakeDiffMenu, Context), - LOCTEXT("Diff", "Diff"), - LOCTEXT("FlowAssetEditorDiffToolTip", "Diff against previous revisions"), - FSlateIcon(FAppStyle::Get().GetStyleSetName(), "BlueprintDiff.ToolbarIcon") - ); - DiffEntry.StyleNameOverride = "CalloutToolbar"; - InSection.AddEntry(DiffEntry); - } - })); + InSection.InsertPosition = FToolMenuInsert(); + FToolMenuEntry DiffEntry = FToolMenuEntry::InitComboButton( + "Diff", + FUIAction(), + FOnGetContent::CreateStatic(&FFlowAssetToolbar::MakeDiffMenu, Context), + LOCTEXT("Diff", "Diff"), + LOCTEXT("FlowAssetEditorDiffToolTip", "Diff against previous revisions"), + FSlateIcon(FAppStyle::Get().GetStyleSetName(), "BlueprintDiff.ToolbarIcon") + ); + DiffEntry.StyleNameOverride = "CalloutToolbar"; + InSection.AddEntry(DiffEntry); + } + })); Section.AddEntry(FToolMenuEntry::InitToolBarButton( FFlowToolbarCommands::Get().SearchInAsset, @@ -432,8 +432,8 @@ static void OnDiffRevisionPicked(FRevisionInfo const& RevisionInfo, const FStrin if (PreviousAsset) { const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); - const FRevisionInfo OldRevision = { Revision->GetRevision(), Revision->GetCheckInIdentifier(), Revision->GetDate() }; - const FRevisionInfo CurrentRevision = { TEXT(""), Revision->GetCheckInIdentifier(), Revision->GetDate() }; + const FRevisionInfo OldRevision = {Revision->GetRevision(), Revision->GetCheckInIdentifier(), Revision->GetDate()}; + const FRevisionInfo CurrentRevision = {TEXT(""), Revision->GetCheckInIdentifier(), Revision->GetDate()}; AssetToolsModule.Get().DiffAssets(PreviousAsset, CurrentAsset.Get(), OldRevision, CurrentRevision); } } @@ -483,16 +483,16 @@ void FFlowAssetToolbar::BuildDebuggerToolbar(UToolMenu* ToolbarMenu) const Section.InsertPosition = FToolMenuInsert("View", EToolMenuInsertType::After); Section.AddDynamicEntry("DebuggingCommands", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) + { + const UFlowAssetEditorContext* Context = InSection.FindContext(); + if (Context && Context->GetFlowAsset()) { - const UFlowAssetEditorContext* Context = InSection.FindContext(); - if (Context && Context->GetFlowAsset()) - { - FPlayWorldCommands::BuildToolbar(InSection); + FPlayWorldCommands::BuildToolbar(InSection); - InSection.AddEntry(FToolMenuEntry::InitWidget("AssetInstances", SNew(SFlowAssetInstanceList, Context->GetFlowAsset()), FText(), true)); - InSection.AddEntry(FToolMenuEntry::InitWidget("AssetBreadcrumb", SNew(SFlowAssetBreadcrumb, Context->GetFlowAsset()), FText(), true)); - } - })); + InSection.AddEntry(FToolMenuEntry::InitWidget("AssetInstances", SNew(SFlowAssetInstanceList, Context->GetFlowAsset()), FText(), true)); + InSection.AddEntry(FToolMenuEntry::InitWidget("AssetBreadcrumb", SNew(SFlowAssetBreadcrumb, Context->GetFlowAsset()), FText(), true)); + } + })); } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE From 1c00344c9ac551da5f90dfed68f1ab34f2802f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Justy=C5=84ski?= Date: Wed, 28 Jan 2026 18:06:48 +0100 Subject: [PATCH 9/9] restored previous class layout --- Source/Flow/Private/Nodes/FlowNode.cpp | 165 +++++++++++++------------ 1 file changed, 87 insertions(+), 78 deletions(-) diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index e9dbc076c..aa319b35a 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -415,54 +415,36 @@ void UFlowNode::SetAutoOutputDataPins(const TArray& AutoOutputPins) AutoOutputDataPins = AutoOutputPins; } -void UFlowNode::SetConnections(const TMap& InConnections) -{ - const TMap OldConnections = Connections; - Connections = InConnections; - OnConnectionsChanged(OldConnections); -} - #endif // WITH_EDITOR -// #FlowDataPinLegacy -void UFlowNode::FixupDataPinTypes() -{ - FixupDataPinTypesForArray(InputPins); - FixupDataPinTypesForArray(OutputPins); -#if WITH_EDITOR - FixupDataPinTypesForArray(AutoInputDataPins); - FixupDataPinTypesForArray(AutoOutputDataPins); -#endif -} - -void UFlowNode::FixupDataPinTypesForArray(TArray& MutableDataPinArray) +FFlowDataPinResult UFlowNode::TrySupplyDataPin_Implementation(FName PinName) const { - for (FFlowPin& MutableFlowPin : MutableDataPinArray) + const FFlowPin* FlowPin = FindOutputPinByName(PinName); + if (!FlowPin) { - FixupDataPinTypesForPin(MutableFlowPin); + // Also look in the Input Pins (for supplying default values for unconnected pins) + FlowPin = FindInputPinByName(PinName); + if (!FlowPin) + { + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); + } } -} -void UFlowNode::FixupDataPinTypesForPin(FFlowPin& MutableDataPin) -{ - const FFlowPinTypeName NewPinTypeName = FFlowPin::GetPinTypeNameForLegacyPinType(MutableDataPin.PinType); - - if (!NewPinTypeName.IsNone()) + const FFlowPinType* DataPinType = FlowPin->ResolveFlowPinType(); + if (!DataPinType) { - MutableDataPin.SetPinTypeName(NewPinTypeName); + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedMismatchedType); } - if (MutableDataPin.GetPinTypeName().IsNone()) + FFlowDataPinResult SuppliedResult; + if (TryGatherPropertyOwnersAndPopulateResult(PinName, *DataPinType, *FlowPin, SuppliedResult)) { - // Ensure we have a pin type even if the enum was invalid before - MutableDataPin.SetPinTypeName(FFlowPinType_Exec::GetPinTypeNameStatic()); + return SuppliedResult; } - MutableDataPin.PinType = EFlowPinType::Invalid; + return FFlowDataPinResult(EFlowDataPinResolveResult::FailedUnknownPin); } -// -- - bool UFlowNode::TryFindPropertyByPinName( const UObject& PropertyOwnerObject, const FName& PinName, @@ -506,34 +488,6 @@ void UFlowNode::GatherPotentialPropertyOwnersForDataPins(TArray& 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, @@ -559,6 +513,7 @@ bool UFlowNode::TryGatherPropertyOwnersAndPopulateResult( return false; } +// -- bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName(const FName& PinName, TFlowPinValueSupplierDataArray& InOutPinValueSupplierDatas) const { @@ -619,6 +574,70 @@ bool UFlowNode::TryGetFlowDataPinSupplierDatasForPinName(const FName& PinName, T return !InOutPinValueSupplierDatas.IsEmpty(); } +#if WITH_EDITOR +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 + +// #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; +} +// -- + +#if WITH_EDITOR +void UFlowNode::SetConnections(const TMap& InConnections) +{ + const TMap OldConnections = Connections; + Connections = InConnections; + OnConnectionsChanged(OldConnections); +} +#endif + TSet UFlowNode::GatherConnectedNodes() const { TSet Result; @@ -928,7 +947,7 @@ void UFlowNode::TriggerOutput(const FName PinName, const bool bFinish /*= false* if (HasFinished()) { // do not trigger output if node is already finished or aborted - LogError(TEXT("Trying to TriggerOutput after finished or aborted")); + LogError(TEXT("Trying to TriggerOutput after finished or aborted"), EFlowOnScreenMessageType::Disabled); return; } @@ -1063,6 +1082,11 @@ void UFlowNode::OnPassThrough_Implementation() Finish(); } +bool UFlowNode::ShouldSave_Implementation() +{ + return GetActivationState() == EFlowNodeState::Active; +} + #if WITH_EDITOR TMap UFlowNode::GetWireRecords() const { @@ -1087,21 +1111,6 @@ 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)