From ee8b2e79372e45878959bd0de6c2b7450169bf82 Mon Sep 17 00:00:00 2001 From: MaksymKapelianovych Date: Wed, 26 Mar 2025 02:35:55 +0200 Subject: [PATCH] Flow debugger update fixed bugs: - fix pausing at breakpoint - select correct flow asset instance, instead of a first one spawned with the same name in MP - breadcrumbs were not visible if asset editor was opened before PIE start changes: - world selection for inspected instances(hidden if there is only one world). If some world is selected, breakpoints will only be triggered for assets inside that world - in the top right corner of the graph PIE status will be displayed, based of selected world/instance - if there is an inspected instance of an asset, breakpoints will only be triggered for that instance - if there is no inspected instance of an asset, but there are active breakpoints, they will be triggered for all instances of an asset, debugger will temporarily set paused instance as inspected and clear after resume - world prefix for instances in dropdown - automatic reselection of previously inspected instance between PIE sessions (disabled by default, can be enabled in Editor Settings) - visual changes for breadcrumbs (added trailing delimiter, background color, max width) - clicking on breadcrumb now will focus SubGraph node, that created inspected instance next in chain - pausing at breakpoint will focus paused asset window (open if needed) and focus node, that has triggered breakpoint - node breakpoint is no longer triggered if there is triggered breakpoint for any pin at the same time - "enable/disable/remove all breakpoints" is added to "debug" menu section - button "Go to Parent" was removed from the toolbar (the same can be achieved with breadcrumbs), but command was kept in order to let the ability to use shortcut for this action --- Source/Flow/Private/FlowAsset.cpp | 116 +++++-- Source/Flow/Private/FlowUserSettings.cpp | 10 + Source/Flow/Private/Nodes/FlowNode.cpp | 4 +- Source/Flow/Public/FlowAsset.h | 27 +- Source/Flow/Public/FlowUserSettings.h | 29 ++ .../Debugger/FlowDebuggerSubsystem.cpp | 133 +++++++- .../Public/Debugger/FlowDebuggerSubsystem.h | 13 +- .../Private/Asset/FlowAssetEditor.cpp | 140 ++++++-- .../Private/Asset/FlowAssetToolbar.cpp | 314 ++++++++++++++---- .../Asset/FlowDebugEditorSubsystem.cpp | 98 +++++- .../FlowEditor/Private/FlowEditorCommands.cpp | 11 +- Source/FlowEditor/Private/FlowEditorStyle.cpp | 10 +- .../Private/Graph/FlowGraphEditor.cpp | 51 ++- .../Private/Graph/FlowGraphUtils.cpp | 19 +- .../FlowEditor/Public/Asset/FlowAssetEditor.h | 17 +- .../Public/Asset/FlowAssetToolbar.h | 85 ++++- .../Public/Asset/FlowDebugEditorSubsystem.h | 9 +- Source/FlowEditor/Public/FlowEditorCommands.h | 8 +- .../FlowEditor/Public/Graph/FlowGraphEditor.h | 1 + .../Public/Graph/FlowGraphEditorSettings.h | 2 +- .../FlowEditor/Public/Graph/FlowGraphUtils.h | 2 + 21 files changed, 907 insertions(+), 192 deletions(-) create mode 100644 Source/Flow/Private/FlowUserSettings.cpp create mode 100644 Source/Flow/Public/FlowUserSettings.h diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index feae76a92..c41300574 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -5,6 +5,7 @@ #include "FlowLogChannels.h" #include "FlowSettings.h" #include "FlowSubsystem.h" +#include "FlowUserSettings.h" #include "AddOns/FlowNodeAddOn.h" #include "Interfaces/FlowDataPinGeneratorNodeInterface.h" @@ -17,6 +18,7 @@ #include "Engine/World.h" #include "Serialization/MemoryReader.h" #include "Serialization/MemoryWriter.h" +#include "Algo/AnyOf.h" #if WITH_EDITOR #include "Editor.h" @@ -1039,7 +1041,16 @@ int32 UFlowAsset::RemoveInstance(UFlowAsset* Instance) #if WITH_EDITOR if (InspectedInstance.IsValid() && InspectedInstance.Get() == Instance) { - SetInspectedInstance(NAME_None); + if (UFlowUserSettings::Get()->bKeepLastInspectedInstance) + { + FString LastPath = LastInspectedInstanceName; + SetInspectedInstance(nullptr, false); + LastInspectedInstanceName = LastPath; + } + else + { + SetInspectedInstance(nullptr); + } } #endif @@ -1052,7 +1063,16 @@ void UFlowAsset::ClearInstances() #if WITH_EDITOR if (InspectedInstance.IsValid()) { - SetInspectedInstance(NAME_None); + if (UFlowUserSettings::Get()->bKeepLastInspectedInstance) + { + FString LastPath = LastInspectedInstanceName; + SetInspectedInstance(nullptr, false); + LastInspectedInstanceName = LastPath; + } + else + { + SetInspectedInstance(nullptr); + } } #endif @@ -1068,36 +1088,78 @@ void UFlowAsset::ClearInstances() } #if WITH_EDITOR -void UFlowAsset::GetInstanceDisplayNames(TArray>& OutDisplayNames) const +FString UFlowAsset::GetDebugName() const { - for (const UFlowAsset* Instance : ActiveInstances) + auto GetNumLocalWorlds = []() { - OutDisplayNames.Emplace(MakeShareable(new FName(Instance->GetDisplayName()))); + int32 LocalWorldCount = 0; + for (const FWorldContext& Context : GEngine->GetWorldContexts()) + { + if (Context.WorldType == EWorldType::PIE && Context.World() != nullptr) + { + ++LocalWorldCount; + } + } + return LocalWorldCount; + }; + + FString Name = GetDisplayName().ToString(); + + if (GetNumLocalWorlds() > 1 || GetWorld()->GetNetMode() == NM_ListenServer) + { + FString Context = GetDebugStringForWorld(GetWorld()); + if (!Context.IsEmpty()) + { + Name = FString::Printf(TEXT("%s (%s)"), *Name, *Context); + } + + return Name; } + + return Name; } -void UFlowAsset::SetInspectedInstance(const FName& NewInspectedInstanceName) +void UFlowAsset::SetInspectedInstance(TWeakObjectPtr NewInspectedInstance, bool bRefreshDebugger) { - if (NewInspectedInstanceName.IsNone()) + if (NewInspectedInstance.IsValid()) + { + if (InspectedInstance == NewInspectedInstance) + { + // Nothing changed + return; + } + + bool bIsNewInstancePresent = Algo::AnyOf(ActiveInstances, [NewInspectedInstance](const UFlowAsset* ActiveInstance) + { + return ActiveInstance && ActiveInstance == NewInspectedInstance; + }); + + if (!ensureMsgf(bIsNewInstancePresent, TEXT("Trying to set %s as InspectedInstance, but it is not one of the ActiveInstances"), *NewInspectedInstance->GetName())) + { + NewInspectedInstance = nullptr; + } + } + + InspectedInstance = NewInspectedInstance; + + if (InspectedInstance.IsValid()) { - InspectedInstance = nullptr; + LastInspectedInstanceName = NewInspectedInstance->GetDebugName(); } else { - for (UFlowAsset* ActiveInstance : ActiveInstances) - { - if (ActiveInstance && ActiveInstance->GetDisplayName() == NewInspectedInstanceName) - { - if (!InspectedInstance.IsValid() || InspectedInstance != ActiveInstance) - { - InspectedInstance = ActiveInstance; - } - break; - } - } + LastInspectedInstanceName = FString(); } - BroadcastDebuggerRefresh(); + if (bRefreshDebugger) + { + BroadcastDebuggerRefresh(); + } +} + +void UFlowAsset::SetWorldBeingDebugged(const TWeakObjectPtr NewWorld) +{ + CurrentWorldBeingDebugged = NewWorld; } void UFlowAsset::BroadcastDebuggerRefresh() const @@ -1164,16 +1226,16 @@ void UFlowAsset::PreStartFlow() #if WITH_EDITOR check(IsInstanceInitialized()); - if (TemplateAsset->ActiveInstances.Num() == 1) + bool bCanSetInstanceAsInspected = UFlowUserSettings::Get()->bSetFirstAssetInstanceAsInspected && TemplateAsset->ActiveInstances.Num() == 1; + bool bKeepLastInstance = UFlowUserSettings::Get()->bKeepLastInspectedInstance && TemplateAsset->GetLastInspectedInstanceName().IsEmpty(); + if (bCanSetInstanceAsInspected && !bKeepLastInstance) { // this instance is the only active one, set it directly as Inspected Instance - TemplateAsset->SetInspectedInstance(GetDisplayName()); - } - else - { - // request to refresh list to show newly created instance - TemplateAsset->BroadcastDebuggerRefresh(); + TemplateAsset->SetInspectedInstance(this, false); } + + // request to refresh list to show newly created instance + TemplateAsset->BroadcastDebuggerRefresh(); #endif } diff --git a/Source/Flow/Private/FlowUserSettings.cpp b/Source/Flow/Private/FlowUserSettings.cpp new file mode 100644 index 000000000..7127e261a --- /dev/null +++ b/Source/Flow/Private/FlowUserSettings.cpp @@ -0,0 +1,10 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "FlowUserSettings.h" + +UFlowUserSettings::UFlowUserSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , bSetFirstAssetInstanceAsInspected(true) + , bKeepLastInspectedInstance(false) +{ +} \ No newline at end of file diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index b53c2a9a4..6e0da0b5c 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -809,7 +809,7 @@ void UFlowNode::TriggerInput(const FName& PinName, const EFlowPinActivationType if (const UFlowAsset* FlowAssetTemplate = GetFlowAsset()->GetTemplateAsset()) { - (void)FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(NodeGuid, PinName); + (void)FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(GetFlowAsset(), NodeGuid, PinName); } #endif } @@ -875,7 +875,7 @@ void UFlowNode::TriggerOutput(const FName PinName, const bool bFinish /*= false* if (const UFlowAsset* FlowAssetTemplate = GetFlowAsset()->GetTemplateAsset()) { - FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(NodeGuid, PinName); + FlowAssetTemplate->OnPinTriggered.ExecuteIfBound(GetFlowAsset(), NodeGuid, PinName); } } else diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index f0736c287..82d0f815b 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -22,9 +22,11 @@ class UEdGraph; class UEdGraphNode; class UFlowAsset; +class UWorld; + #if !UE_BUILD_SHIPPING DECLARE_DELEGATE(FFlowGraphEvent); -DECLARE_DELEGATE_TwoParams(FFlowSignalEvent, const FGuid& /*NodeGuid*/, const FName& /*PinName*/); +DECLARE_DELEGATE_ThreeParams(FFlowSignalEvent, const UFlowAsset* /*Instance*/, const FGuid& /*NodeGuid*/, const FName& /*PinName*/); #endif // Working Data struct for the Harvest Data Pins operation @@ -276,7 +278,12 @@ class FLOW_API UFlowAsset : public UObject TArray> ActiveInstances; #if WITH_EDITORONLY_DATA - TWeakObjectPtr InspectedInstance; + TWeakObjectPtr InspectedInstance; + + FString LastInspectedInstanceName; + + /** Current world being debugged for this asset */ + TWeakObjectPtr CurrentWorldBeingDebugged; // Message log for storing runtime errors/notes/warnings that will only last until the next game run // Log lives in the asset template, so it can be inspected after ending the PIE @@ -286,15 +293,25 @@ class FLOW_API UFlowAsset : public UObject public: void AddInstance(UFlowAsset* Instance); int32 RemoveInstance(UFlowAsset* Instance); + TConstArrayView> GetActiveInstances() const { return ActiveInstances; } void ClearInstances(); int32 GetInstancesNum() const { return ActiveInstances.Num(); } #if WITH_EDITOR - void GetInstanceDisplayNames(TArray>& OutDisplayNames) const; + FString GetDebugName() const; + + void SetInspectedInstance(TWeakObjectPtr NewInspectedInstance, bool bRefreshDebugger = true); + const UFlowAsset* GetInspectedInstance() const { return InspectedInstance.IsValid() ? InspectedInstance.Get() : nullptr; } - void SetInspectedInstance(const FName& NewInspectedInstanceName); - UFlowAsset* GetInspectedInstance() const { return InspectedInstance.IsValid() ? InspectedInstance.Get() : nullptr; } + /** @return debug name of instance that should be debugged, may be from previous PIE session */ + const FStringView GetLastInspectedInstanceName() const + { + return LastInspectedInstanceName; + } + + void SetWorldBeingDebugged(const TWeakObjectPtr NewWorld); + const TWeakObjectPtr GetWorldBeingDebugged() const { return CurrentWorldBeingDebugged; } DECLARE_EVENT(UFlowAsset, FRefreshDebuggerEvent); diff --git a/Source/Flow/Public/FlowUserSettings.h b/Source/Flow/Public/FlowUserSettings.h new file mode 100644 index 000000000..b9f7023ae --- /dev/null +++ b/Source/Flow/Public/FlowUserSettings.h @@ -0,0 +1,29 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "Engine/DeveloperSettings.h" +#include "FlowUserSettings.generated.h" + +/** + * + */ +UCLASS(Config = EditorPerProjectUserSettings, meta = (DisplayName = "Flow")) +class FLOW_API UFlowUserSettings : public UDeveloperSettings +{ + GENERATED_UCLASS_BODY() + + static UFlowUserSettings* Get() { return CastChecked(UFlowUserSettings::StaticClass()->GetDefaultObject()); } + + UPROPERTY(EditDefaultsOnly, config, Category = "Debug") + bool bSetFirstAssetInstanceAsInspected; + + // Keep last inspected instance of this FlowAsset between PIE sessions + UPROPERTY(EditDefaultsOnly, config, Category = "Debug") + bool bKeepLastInspectedInstance; + + virtual FName GetCategoryName() const override { return FName("Flow Graph"); } +#if WITH_EDITORONLY_DATA + virtual FText GetSectionText() const override { return INVTEXT("User Settings"); } +#endif +}; diff --git a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp index 0b977d536..5351f69eb 100644 --- a/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp +++ b/Source/FlowDebugger/Private/Debugger/FlowDebuggerSubsystem.cpp @@ -44,15 +44,13 @@ void UFlowDebuggerSubsystem::OnInstancedTemplateRemoved(UFlowAsset* AssetTemplat AssetTemplate->OnPinTriggered.Unbind(); } -void UFlowDebuggerSubsystem::OnPinTriggered(const FGuid& NodeGuid, const FName& PinName) +void UFlowDebuggerSubsystem::OnPinTriggered(const UFlowAsset* Instance, const FGuid& NodeGuid, const FName& PinName) { - if (FindBreakpoint(NodeGuid, PinName)) + if (!TryMarkAsHit(NodeGuid, PinName)) { - MarkAsHit(NodeGuid, PinName); + // Node breakpoints waits on any pin triggered, but check it only if there is no hit pin breakpoint + TryMarkAsHit(NodeGuid); } - - // Node breakpoints waits on any pin triggered - MarkAsHit(NodeGuid); } void UFlowDebuggerSubsystem::AddBreakpoint(const FGuid& NodeGuid) @@ -74,6 +72,19 @@ void UFlowDebuggerSubsystem::AddBreakpoint(const FGuid& NodeGuid, const FName& P SaveSettings(); } +void UFlowDebuggerSubsystem::RemoveAllBreakpoints(const UFlowAsset* Asset) +{ + UFlowDebuggerSettings* Settings = GetMutableDefault(); + for (auto& [NodeGuid, Node] : Asset->GetNodes()) + { + if (Settings->NodeBreakpoints.Contains(NodeGuid)) + { + Settings->NodeBreakpoints.Remove(NodeGuid); + } + } + SaveSettings(); +} + void UFlowDebuggerSubsystem::RemoveAllBreakpoints(const FGuid& NodeGuid) { UFlowDebuggerSettings* Settings = GetMutableDefault(); @@ -187,6 +198,20 @@ void UFlowDebuggerSubsystem::ToggleBreakpoint(const FGuid& NodeGuid, const FName } } +bool UFlowDebuggerSubsystem::HasAnyBreakpoints(const UFlowAsset* Asset) const +{ + UFlowDebuggerSettings* Settings = GetMutableDefault(); + for (auto& [NodeGuid, Node] : Asset->GetNodes()) + { + if (Settings->NodeBreakpoints.Find(NodeGuid)) + { + return true; + } + } + + return false; +} + FFlowBreakpoint* UFlowDebuggerSubsystem::FindBreakpoint(const FGuid& NodeGuid) { UFlowDebuggerSettings* Settings = GetMutableDefault(); @@ -206,6 +231,27 @@ FFlowBreakpoint* UFlowDebuggerSubsystem::FindBreakpoint(const FGuid& NodeGuid, c return NodeBreakpoint ? NodeBreakpoint->PinBreakpoints.Find(PinName) : nullptr; } +void UFlowDebuggerSubsystem::SetAllBreakpointsEnabled(const UFlowAsset* Asset, bool bEnabled) +{ + UFlowDebuggerSettings* Settings = GetMutableDefault(); + for (auto& [NodeGuid, Node] : Asset->GetNodes()) + { + if (FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(NodeGuid)) + { + if (NodeBreakpoint->Breakpoint.IsActive()) + { + NodeBreakpoint->Breakpoint.SetEnabled(bEnabled); + } + + for (auto& [Name, PinBreakpoint] : NodeBreakpoint->PinBreakpoints) + { + PinBreakpoint.SetEnabled(bEnabled); + } + } + } + SaveSettings(); +} + void UFlowDebuggerSubsystem::SetBreakpointEnabled(const FGuid& NodeGuid, const bool bEnabled) { if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(NodeGuid)) @@ -224,6 +270,56 @@ void UFlowDebuggerSubsystem::SetBreakpointEnabled(const FGuid& NodeGuid, const F } } +bool UFlowDebuggerSubsystem::HasAnyBreakpointsDisabled(const UFlowAsset* Asset) const +{ + UFlowDebuggerSettings* Settings = GetMutableDefault(); + for (auto& [NodeGuid, Node] : Asset->GetNodes()) + { + if (FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(NodeGuid)) + { + if (NodeBreakpoint->Breakpoint.IsActive() && !NodeBreakpoint->Breakpoint.IsEnabled()) + { + return true; + } + + for (auto& [Name, PinBreakpoint] : NodeBreakpoint->PinBreakpoints) + { + if (!PinBreakpoint.IsEnabled()) + { + return true; + } + } + } + } + + return false; +} + +bool UFlowDebuggerSubsystem::HasAnyBreakpointsEnabled(const UFlowAsset* Asset) const +{ + UFlowDebuggerSettings* Settings = GetMutableDefault(); + for (auto& [NodeGuid, Node] : Asset->GetNodes()) + { + if (FNodeBreakpoint* NodeBreakpoint = Settings->NodeBreakpoints.Find(NodeGuid)) + { + if (NodeBreakpoint->Breakpoint.IsActive() && NodeBreakpoint->Breakpoint.IsEnabled()) + { + return true; + } + + for (auto& [Name, PinBreakpoint] : NodeBreakpoint->PinBreakpoints) + { + if (PinBreakpoint.IsEnabled()) + { + return true; + } + } + } + } + + return false; +} + bool UFlowDebuggerSubsystem::IsBreakpointEnabled(const FGuid& NodeGuid) { if (const FFlowBreakpoint* PinBreakpoint = FindBreakpoint(NodeGuid)) @@ -244,25 +340,36 @@ bool UFlowDebuggerSubsystem::IsBreakpointEnabled(const FGuid& NodeGuid, const FN return false; } -void UFlowDebuggerSubsystem::MarkAsHit(const FGuid& NodeGuid) +bool UFlowDebuggerSubsystem::TryMarkAsHit(const FGuid& NodeGuid) { if (FFlowBreakpoint* NodeBreakpoint = FindBreakpoint(NodeGuid)) { - NodeBreakpoint->MarkAsHit(true); - PauseSession(); + if (NodeBreakpoint->IsEnabled()) + { + NodeBreakpoint->MarkAsHit(true); + PauseSession(NodeGuid); + return true; + } } + + return false; } -void UFlowDebuggerSubsystem::MarkAsHit(const FGuid& NodeGuid, const FName& PinName) +bool UFlowDebuggerSubsystem::TryMarkAsHit(const FGuid& NodeGuid, const FName& PinName) { if (FFlowBreakpoint* PinBreakpoint = FindBreakpoint(NodeGuid, PinName)) { - PinBreakpoint->MarkAsHit(true); - PauseSession(); + if (PinBreakpoint->IsEnabled()) + { + PinBreakpoint->MarkAsHit(true); + PauseSession(NodeGuid); + return true; + } } + return false; } -void UFlowDebuggerSubsystem::PauseSession() +void UFlowDebuggerSubsystem::PauseSession(const FGuid& FromNode) { SetPause(true); } diff --git a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h index c83e004cd..88413ee9b 100644 --- a/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h +++ b/Source/FlowDebugger/Public/Debugger/FlowDebuggerSubsystem.h @@ -28,12 +28,13 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem virtual void OnInstancedTemplateAdded(UFlowAsset* AssetTemplate); virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) const; - virtual void OnPinTriggered(const FGuid& NodeGuid, const FName& PinName); + virtual void OnPinTriggered(const UFlowAsset* Instance, const FGuid& NodeGuid, const FName& PinName); public: virtual void AddBreakpoint(const FGuid& NodeGuid); virtual void AddBreakpoint(const FGuid& NodeGuid, const FName& PinName); + virtual void RemoveAllBreakpoints(const UFlowAsset* Asset); virtual void RemoveAllBreakpoints(const FGuid& NodeGuid); virtual void RemoveNodeBreakpoint(const FGuid& NodeGuid); virtual void RemovePinBreakpoint(const FGuid& NodeGuid, const FName& PinName); @@ -46,20 +47,24 @@ class FLOWDEBUGGER_API UFlowDebuggerSubsystem : public UEngineSubsystem virtual void ToggleBreakpoint(const FGuid& NodeGuid); virtual void ToggleBreakpoint(const FGuid& NodeGuid, const FName& PinName); + bool HasAnyBreakpoints(const UFlowAsset* Asset) const; virtual FFlowBreakpoint* FindBreakpoint(const FGuid& NodeGuid); virtual FFlowBreakpoint* FindBreakpoint(const FGuid& NodeGuid, const FName& PinName); + virtual void SetAllBreakpointsEnabled(const UFlowAsset* Asset, bool bEnabled); virtual void SetBreakpointEnabled(const FGuid& NodeGuid, bool bEnabled); virtual void SetBreakpointEnabled(const FGuid& NodeGuid, const FName& PinName, bool bEnabled); + bool HasAnyBreakpointsDisabled(const UFlowAsset* Asset) const; + bool HasAnyBreakpointsEnabled(const UFlowAsset* Asset) const; virtual bool IsBreakpointEnabled(const FGuid& NodeGuid); 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 bool TryMarkAsHit(const FGuid& NodeGuid); + virtual bool TryMarkAsHit(const FGuid& NodeGuid, const FName& PinName); - virtual void PauseSession(); + virtual void PauseSession(const FGuid& FromNode); virtual void ResumeSession(); void SetPause(const bool bPause); diff --git a/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp b/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp index c825e33be..39af3e556 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp @@ -11,6 +11,7 @@ #include "Graph/FlowGraphEditor.h" #include "Graph/FlowGraphSchema.h" #include "Graph/Widgets/SFlowPalette.h" +#include "Debugger/FlowDebuggerSubsystem.h" #include "FlowAsset.h" @@ -46,6 +47,7 @@ const FName FFlowAssetEditor::ValidationLogTab(TEXT("ValidationLog")); FFlowAssetEditor::FFlowAssetEditor() : FlowAsset(nullptr) { + DebuggerSubsystem = GEngine->GetEngineSubsystem(); } FFlowAssetEditor::~FFlowAssetEditor() @@ -308,7 +310,8 @@ void FFlowAssetEditor::InitFlowAssetEditor(const EToolkitMode::Type Mode, const UFlowGraphSchema::SubscribeToAssetChanges(); FlowAsset->OnDetailsRefreshRequested.BindThreadSafeSP(this, &FFlowAssetEditor::RefreshDetails); - BindToolbarCommands(); + BindEditorCommands(); + RegisterMenus(); CreateToolbar(); CreateWidgets(); @@ -369,6 +372,31 @@ void FFlowAssetEditor::InitFlowAssetEditor(const EToolkitMode::Type Mode, const RegenerateMenusAndToolbars(); } +void FFlowAssetEditor::RegisterMenus() +{ + const FName MainMenuName = GetToolMenuName(); + + FToolMenuSection& Section = UToolMenus::Get()->ExtendMenu(MainMenuName)->FindOrAddSection(NAME_None); + + if (!Section.FindEntry("Debug")) + { + Section.AddSubMenu( + "Debug", + LOCTEXT("DebugMenu", "Debug"), + LOCTEXT("DebugMenu_ToolTip", "Open the debug menu"), + FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) + { + { + FToolMenuSection& Section = InMenu->AddSection("DebugBreakpoints", LOCTEXT("DebugMenu_BreakpointHeading", "Breakpoints")); + Section.AddMenuEntry( FFlowEditorCommands::Get().EnableAllBreakpoints ); + Section.AddMenuEntry( FFlowEditorCommands::Get().DisableAllBreakpoints ); + Section.AddMenuEntry( FFlowEditorCommands::Get().RemoveAllBreakpoints ); + } + }) + ).InsertPosition = FToolMenuInsert("Edit", EToolMenuInsertType::After); + } +} + void FFlowAssetEditor::CreateToolbar() { FName ParentToolbarName; @@ -387,37 +415,55 @@ void FFlowAssetEditor::CreateToolbar() } } -void FFlowAssetEditor::BindToolbarCommands() +void FFlowAssetEditor::BindEditorCommands() { - FFlowToolbarCommands::Register(); - const FFlowToolbarCommands& ToolbarCommands = FFlowToolbarCommands::Get(); - - // Editing - ToolkitCommands->MapAction(ToolbarCommands.RefreshAsset, - FExecuteAction::CreateSP(this, &FFlowAssetEditor::RefreshAsset), - FCanExecuteAction::CreateStatic(&FFlowAssetEditor::CanEdit)); + FFlowEditorCommands::Register(); + const FFlowEditorCommands& ToolbarCommands = FFlowEditorCommands::Get(); - ToolkitCommands->MapAction(ToolbarCommands.ValidateAsset, - FExecuteAction::CreateSP(this, &FFlowAssetEditor::ValidateAsset_Internal), - FCanExecuteAction()); + // Toolbar + { + // Editing + ToolkitCommands->MapAction(ToolbarCommands.RefreshAsset, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::RefreshAsset), + FCanExecuteAction::CreateStatic(&FFlowAssetEditor::CanEdit)); + + ToolkitCommands->MapAction(ToolbarCommands.ValidateAsset, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::ValidateAsset_Internal), + FCanExecuteAction()); - ToolkitCommands->MapAction(ToolbarCommands.SearchInAsset, - FExecuteAction::CreateSP(this, &FFlowAssetEditor::SearchInAsset), - FCanExecuteAction()); - - ToolkitCommands->MapAction(ToolbarCommands.EditAssetDefaults, - FExecuteAction::CreateSP(this, &FFlowAssetEditor::EditAssetDefaults_Clicked), - FCanExecuteAction()); - - // Engine's Play commands - ToolkitCommands->Append(FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef()); + ToolkitCommands->MapAction(ToolbarCommands.SearchInAsset, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::SearchInAsset), + FCanExecuteAction()); + + ToolkitCommands->MapAction(ToolbarCommands.EditAssetDefaults, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::EditAssetDefaults_Clicked), + FCanExecuteAction()); + + // Engine's Play commands + ToolkitCommands->Append(FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef()); + + // Debugging + ToolkitCommands->MapAction(ToolbarCommands.GoToParentInstance, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::GoToParentInstance), + FCanExecuteAction::CreateSP(this, &FFlowAssetEditor::CanGoToParentInstance), + FIsActionChecked(), + FIsActionButtonVisible::CreateSP(this, &FFlowAssetEditor::CanGoToParentInstance)); + } - // Debugging - ToolkitCommands->MapAction(ToolbarCommands.GoToParentInstance, - FExecuteAction::CreateSP(this, &FFlowAssetEditor::GoToParentInstance), - FCanExecuteAction::CreateSP(this, &FFlowAssetEditor::CanGoToParentInstance), - FIsActionChecked(), - FIsActionButtonVisible::CreateSP(this, &FFlowAssetEditor::CanGoToParentInstance)); + // Debug menu + { + ToolkitCommands->MapAction(ToolbarCommands.DisableAllBreakpoints, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::DisableAllBreakpoints), + FCanExecuteAction::CreateSP(this, &FFlowAssetEditor::HasAnyEnabledBreakpoints)); + + ToolkitCommands->MapAction(ToolbarCommands.EnableAllBreakpoints, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::EnableAllBreakpoints), + FCanExecuteAction::CreateSP(this, &FFlowAssetEditor::HasAnyDisabledBreakpoints)); + + ToolkitCommands->MapAction(ToolbarCommands.RemoveAllBreakpoints, + FExecuteAction::CreateSP(this, &FFlowAssetEditor::ClearAllBreakpoints), + FCanExecuteAction::CreateSP(this, &FFlowAssetEditor::HasAnyBreakpoints)); + } } void FFlowAssetEditor::RefreshAsset() @@ -477,7 +523,7 @@ void FFlowAssetEditor::GoToParentInstance() const UFlowAsset* AssetThatInstancedThisAsset = FlowAsset->GetInspectedInstance()->GetParentInstance(); GEditor->GetEditorSubsystem()->OpenEditorForAsset(AssetThatInstancedThisAsset->GetTemplateAsset()); - AssetThatInstancedThisAsset->GetTemplateAsset()->SetInspectedInstance(AssetThatInstancedThisAsset->GetDisplayName()); + AssetThatInstancedThisAsset->GetTemplateAsset()->SetInspectedInstance(AssetThatInstancedThisAsset); } bool FFlowAssetEditor::CanGoToParentInstance() @@ -485,6 +531,42 @@ bool FFlowAssetEditor::CanGoToParentInstance() return FlowAsset->GetInspectedInstance() && FlowAsset->GetInspectedInstance()->GetNodeOwningThisAssetInstance() != nullptr; } +void FFlowAssetEditor::EnableAllBreakpoints() +{ + check(DebuggerSubsystem.IsValid()); + DebuggerSubsystem->SetAllBreakpointsEnabled(FlowAsset, true); +} + +bool FFlowAssetEditor::HasAnyDisabledBreakpoints() +{ + check(DebuggerSubsystem.IsValid()); + return DebuggerSubsystem->HasAnyBreakpointsDisabled(FlowAsset); +} + +void FFlowAssetEditor::DisableAllBreakpoints() +{ + check(DebuggerSubsystem.IsValid()); + DebuggerSubsystem->SetAllBreakpointsEnabled(FlowAsset, false); +} + +bool FFlowAssetEditor::HasAnyEnabledBreakpoints() +{ + check(DebuggerSubsystem.IsValid()); + return DebuggerSubsystem->HasAnyBreakpointsEnabled(FlowAsset); +} + +void FFlowAssetEditor::ClearAllBreakpoints() +{ + check(DebuggerSubsystem.IsValid()); + DebuggerSubsystem->RemoveAllBreakpoints(FlowAsset); +} + +bool FFlowAssetEditor::HasAnyBreakpoints() +{ + check(DebuggerSubsystem.IsValid()); + return DebuggerSubsystem->HasAnyBreakpoints(FlowAsset); +} + void FFlowAssetEditor::CreateWidgets() { // Details View diff --git a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp index c794ad0e5..d6550aec6 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetToolbar.cpp @@ -2,12 +2,15 @@ #include "Asset/FlowAssetToolbar.h" +#include "Graph/FlowGraphUtils.h" #include "Asset/FlowAssetEditor.h" #include "Asset/FlowAssetEditorContext.h" #include "Asset/SAssetRevisionMenu.h" #include "FlowEditorCommands.h" #include "FlowAsset.h" +#include "Nodes/Graph/FlowNode_SubGraph.h" +#include "FlowUserSettings.h" #include "Kismet2/DebuggerCommands.h" #include "Misc/Attribute.h" @@ -30,22 +33,30 @@ // Flow Asset Instance List FText SFlowAssetInstanceList::NoInstanceSelectedText = LOCTEXT("NoInstanceSelected", "No instance selected"); +FText SFlowAssetInstanceList::AllWorldsText = LOCTEXT("AllWorlds", "All Worlds"); void SFlowAssetInstanceList::Construct(const FArguments& InArgs, const TWeakObjectPtr InTemplateAsset) { TemplateAsset = InTemplateAsset; - if (TemplateAsset.IsValid()) - { - TemplateAsset->OnDebuggerRefresh().AddSP(this, &SFlowAssetInstanceList::RefreshInstances); - RefreshInstances(); - } - // create dropdown - SAssignNew(Dropdown, SComboBox>) - .OptionsSource(&InstanceNames) - .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) - .OnGenerateWidget(this, &SFlowAssetInstanceList::OnGenerateWidget) - .OnSelectionChanged(this, &SFlowAssetInstanceList::OnSelectionChanged) + DebugWorldsComboBox = SNew(SComboBox>) + .OptionsSource(&DebugWorlds) + .Visibility_Static(&SFlowAssetInstanceList::GetWorldComboVisibility) + .OnComboBoxOpening(this, &SFlowAssetInstanceList::GenerateDebugWorldNames) + .OnGenerateWidget(this, &SFlowAssetInstanceList::GenerateWorldItemWidget) + .OnSelectionChanged(this, &SFlowAssetInstanceList::DebugWorldSelectionChanged) + .ContentPadding(FMargin(0.f, 2.f)) + [ + SNew(STextBlock) + .Text(this, &SFlowAssetInstanceList::GetSelectedWorldName) + ]; + + DebugInstancesComboBox = SNew(SComboBox>) + .OptionsSource(&DebugInstances) + .OnComboBoxOpening(this, &SFlowAssetInstanceList::GenerateDebugInstances) + .OnGenerateWidget(this, &SFlowAssetInstanceList::GenerateInstanceItemWidget) + .OnSelectionChanged(this, &SFlowAssetInstanceList::DebugInstanceSelectionChanged) + .ContentPadding(FMargin(0.f, 2.f)) [ SNew(STextBlock) .Text(this, &SFlowAssetInstanceList::GetSelectedInstanceName) @@ -53,8 +64,27 @@ void SFlowAssetInstanceList::Construct(const FArguments& InArgs, const TWeakObje ChildSlot [ - Dropdown.ToSharedRef() + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + DebugWorldsComboBox.ToSharedRef() + ] + + SHorizontalBox::Slot() + .Padding(8.0, 0.f, 4.f, 0.f) + .AutoWidth() + [ + DebugInstancesComboBox.ToSharedRef() + ] ]; + + if (TemplateAsset.IsValid()) + { + TemplateAsset->OnDebuggerRefresh().AddSP(this, &SFlowAssetInstanceList::GenerateDebugWorldNames); + TemplateAsset->OnDebuggerRefresh().AddSP(this, &SFlowAssetInstanceList::GenerateDebugInstances); + GenerateDebugWorldNames(); + GenerateDebugInstances(); + } } SFlowAssetInstanceList::~SFlowAssetInstanceList() @@ -65,59 +95,188 @@ SFlowAssetInstanceList::~SFlowAssetInstanceList() } } -void SFlowAssetInstanceList::RefreshInstances() +EVisibility SFlowAssetInstanceList::GetWorldComboVisibility() { - // collect instance names of this Flow Asset - InstanceNames = {MakeShareable(new FName(*NoInstanceSelectedText.ToString()))}; - TemplateAsset->GetInstanceDisplayNames(InstanceNames); - - // select instance - if (const UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance()) + if (GEditor->PlayWorld != nullptr) { - const FName& InspectedInstanceName = InspectedInstance->GetDisplayName(); - for (const TSharedPtr& Instance : InstanceNames) + auto GetNumLocalWorlds = []() { - if (*Instance == InspectedInstanceName) + int32 LocalWorldCount = 0; + for (const FWorldContext& Context : GEngine->GetWorldContexts()) { - SelectedInstance = Instance; - break; + if (Context.WorldType == EWorldType::PIE && Context.World() != nullptr) + { + ++LocalWorldCount; + } } + return LocalWorldCount; + }; + + if (GetNumLocalWorlds() > 1) + { + return EVisibility::Visible; + } + } + + return EVisibility::Collapsed; +} + +void SFlowAssetInstanceList::GenerateDebugWorldNames() +{ + DebugWorlds.Empty(); + DebugWorlds.Add(MakeShareable(new FFlowDebugWorld(nullptr, AllWorldsText.ToString()))); + + for (const FWorldContext& PieContext : GEngine->GetWorldContexts()) + { + UWorld* PlayWorld = PieContext.World(); + if (PlayWorld && PlayWorld->IsGameWorld()) + { + FString WorldName = GetDebugStringForWorld(PlayWorld); + DebugWorlds.Add(MakeShareable(new FFlowDebugWorld(PlayWorld, WorldName))); } } - else + + TSharedPtr LastSelection = GetDebugWorld(); + DebugWorldsComboBox->SetSelectedItem(LastSelection); +} + +TSharedRef SFlowAssetInstanceList::GenerateWorldItemWidget(TSharedPtr Item) const +{ + return SNew(STextBlock) + .Text(FText::FromString(Item->WorldLabel)); +} + +void SFlowAssetInstanceList::DebugWorldSelectionChanged(TSharedPtr SelectedItem, ESelectInfo::Type SelectionType) +{ + check(TemplateAsset.IsValid()); + if (SelectionType != ESelectInfo::Direct) { - // default object is always available - SelectedInstance = InstanceNames[0]; + check(SelectedItem.IsValid()); + TemplateAsset->SetWorldBeingDebugged(SelectedItem->WorldPtr); + TemplateAsset->SetInspectedInstance(nullptr); } } -EVisibility SFlowAssetInstanceList::GetDebuggerVisibility() +FText SFlowAssetInstanceList::GetSelectedWorldName() const { - return GEditor->PlayWorld ? EVisibility::Visible : EVisibility::Collapsed; + return FText::FromString(DebugWorldsComboBox->GetSelectedItem()->WorldLabel); } -TSharedRef SFlowAssetInstanceList::OnGenerateWidget(const TSharedPtr Item) const +TSharedPtr SFlowAssetInstanceList::GetDebugWorld() const { - return SNew(STextBlock).Text(FText::FromName(*Item.Get())); + check(TemplateAsset.IsValid()); + TWeakObjectPtr World = TemplateAsset->GetWorldBeingDebugged(); + if (!World.IsExplicitlyNull()) + { + for (const TSharedPtr& DebugWorld : DebugWorlds) + { + if (ensure(DebugWorld.IsValid()) && DebugWorld->WorldPtr == World) + { + return DebugWorld; + } + } + } + + check(DebugWorlds.Num() > 0); + return DebugWorlds[0]; } -void SFlowAssetInstanceList::OnSelectionChanged(const TSharedPtr SelectedItem, const ESelectInfo::Type SelectionType) +void SFlowAssetInstanceList::GenerateDebugInstances() { - if (SelectionType != ESelectInfo::Direct) + check(TemplateAsset.IsValid()); + + TSharedPtr LastSelection; + if (UFlowUserSettings::Get()->bKeepLastInspectedInstance) { - SelectedInstance = SelectedItem; + LastSelection = GetDebugInstance(); + } + + DebugInstances.Empty(); + DebugInstances.Add(MakeShareable(new FFlowDebugInstance(nullptr, *NoInstanceSelectedText.ToString()))); - if (TemplateAsset.IsValid()) + TWeakObjectPtr DebugWorld = DebugWorldsComboBox->GetSelectedItem()->WorldPtr; + + // collect active instances of this Flow Asset + for (const UFlowAsset* ActiveInstance: TemplateAsset->GetActiveInstances()) + { + if (DebugWorld.IsValid() && DebugWorld.Get() != ActiveInstance->GetWorld()) { - const FName NewSelectedInstanceName = (SelectedInstance.IsValid() && *SelectedInstance != *InstanceNames[0]) ? *SelectedInstance : NAME_None; - TemplateAsset->SetInspectedInstance(NewSelectedInstanceName); + continue; } + + TSharedPtr NewInstance = MakeShareable(new FFlowDebugInstance(ActiveInstance, ActiveInstance->GetDebugName())); + DebugInstances.Add(NewInstance); + } + + TSharedPtr Selection = GetDebugInstance(); + if (Selection.IsValid() && !Selection->IsEmptyObject()) + { + // If our new selection matches the actual debug instance, set it + if (LastSelection.IsValid() && LastSelection->InstanceLabel == Selection->InstanceLabel) + { + // new selection is the same as our selected instance from previous PIE session, set it as inspected + TemplateAsset->SetInspectedInstance(Selection->InstancePtr); + } + DebugInstancesComboBox->SetSelectedItem(Selection); + } + else if (LastSelection.IsValid() && !LastSelection->IsEmptyObject()) + { + // Re-add the desired runtime instance, even though it is currently null + DebugInstances.Add(LastSelection); + DebugInstancesComboBox->SetSelectedItem(LastSelection); + } + + // Finally ensure we have a valid selection, this will set to all objects as a backup + TSharedPtr CurrentSelection = DebugInstancesComboBox->GetSelectedItem(); + if (DebugInstances.Find(CurrentSelection) == INDEX_NONE) + { + check(DebugInstances.Num() > 0); + DebugInstancesComboBox->SetSelectedItem(DebugInstances[0]); + } +} + +TSharedRef SFlowAssetInstanceList::GenerateInstanceItemWidget(const TSharedPtr Item) const +{ + return SNew(STextBlock) + .Text(FText::FromString(Item->InstanceLabel)); +} + +void SFlowAssetInstanceList::DebugInstanceSelectionChanged(const TSharedPtr SelectedItem, const ESelectInfo::Type SelectionType) +{ + check(TemplateAsset.IsValid()); + if (SelectionType != ESelectInfo::Direct) + { + check(SelectedItem.IsValid()); + TWeakObjectPtr Instance = SelectedItem->InstancePtr; + TemplateAsset->SetInspectedInstance(Instance); } } FText SFlowAssetInstanceList::GetSelectedInstanceName() const { - return SelectedInstance.IsValid() ? FText::FromName(*SelectedInstance) : NoInstanceSelectedText; + return FText::FromString(DebugInstancesComboBox->GetSelectedItem()->InstanceLabel); +} + +TSharedPtr SFlowAssetInstanceList::GetDebugInstance() const +{ + check(TemplateAsset.IsValid()); + const FStringView DebugName = TemplateAsset->GetLastInspectedInstanceName(); + if (!DebugName.IsEmpty()) + { + for (int32 ObjectIndex = 0; ObjectIndex < DebugInstances.Num(); ++ObjectIndex) + { + if (ensure(DebugInstances[ObjectIndex].IsValid()) && DebugName.Equals(DebugInstances[ObjectIndex]->InstanceLabel)) + { + return DebugInstances[ObjectIndex]; + } + } + } + + if (DebugInstances.Num() > 0) + { + return DebugInstances[0]; + } + return nullptr; } ////////////////////////////////////////////////////////////////////////// @@ -130,30 +289,43 @@ void SFlowAssetBreadcrumb::Construct(const FArguments& InArgs, const TWeakObject // create breadcrumb SAssignNew(BreadcrumbTrail, SBreadcrumbTrail) .OnCrumbClicked(this, &SFlowAssetBreadcrumb::OnCrumbClicked) - .Visibility_Static(&SFlowAssetInstanceList::GetDebuggerVisibility) - .ButtonStyle(FAppStyle::Get(), "FlatButton") - .DelimiterImage(FAppStyle::GetBrush("Sequencer.BreadcrumbIcon")) - .PersistentBreadcrumbs(true) - .TextStyle(FAppStyle::Get(), "Sequencer.BreadcrumbText"); + .ButtonStyle(FAppStyle::Get(), "SimpleButton") + .TextStyle(FAppStyle::Get(), "NormalText") + .ButtonContentPadding( FMargin(2.f, 4.f) ) + .DelimiterImage( FAppStyle::GetBrush("Icons.ChevronRight") ) + .ShowLeadingDelimiter(true) + .PersistentBreadcrumbs(true); ChildSlot [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .HAlign(HAlign_Right) - .VAlign(VAlign_Center) - .AutoHeight() - .Padding(25.0f, 10.0f) + SNew(SBorder) + .Visibility(this, &SFlowAssetBreadcrumb::GetBreadcrumbVisibility) + .BorderImage(new FSlateRoundedBoxBrush(FStyleColors::Transparent, 4, FStyleColors::InputOutline, 1)) [ - BreadcrumbTrail.ToSharedRef() + SNew(SBox) + .MaxDesiredWidth(500.f) + [ + BreadcrumbTrail.ToSharedRef() + ] ] ]; - // fill breadcrumb + check(TemplateAsset.IsValid()); + TemplateAsset->OnDebuggerRefresh().AddSP(this, &SFlowAssetBreadcrumb::FillBreadcrumb); + FillBreadcrumb(); +} + +EVisibility SFlowAssetBreadcrumb::GetBreadcrumbVisibility() const +{ + return GEditor->PlayWorld && TemplateAsset->GetInspectedInstance() ? EVisibility::Visible : EVisibility::Collapsed; +} + +void SFlowAssetBreadcrumb::FillBreadcrumb() +{ BreadcrumbTrail->ClearCrumbs(); - if (UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance()) + if (const UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance()) { - TArray> InstancesFromRoot = {InspectedInstance}; + TArray> InstancesFromRoot = {InspectedInstance}; const UFlowAsset* CheckedInstance = InspectedInstance; while (UFlowAsset* ParentInstance = CheckedInstance->GetParentInstance()) @@ -162,13 +334,12 @@ void SFlowAssetBreadcrumb::Construct(const FArguments& InArgs, const TWeakObject CheckedInstance = ParentInstance; } - for (TWeakObjectPtr Instance : InstancesFromRoot) + for (int32 Index = 0; Index < InstancesFromRoot.Num(); Index++) { - if (Instance.IsValid()) - { - const FFlowBreadcrumb NewBreadcrumb = FFlowBreadcrumb(Instance); - BreadcrumbTrail->PushCrumb(FText::FromName(NewBreadcrumb.InstanceName), FFlowBreadcrumb(Instance)); - } + TWeakObjectPtr Instance = InstancesFromRoot[Index]; + TWeakObjectPtr ChildInstance = Index < InstancesFromRoot.Num() - 1 ? InstancesFromRoot[Index + 1] : nullptr; + + BreadcrumbTrail->PushCrumb(FText::FromName(Instance->GetDisplayName()), FFlowBreadcrumb(Instance, ChildInstance)); } } } @@ -176,9 +347,19 @@ void SFlowAssetBreadcrumb::Construct(const FArguments& InArgs, const TWeakObject void SFlowAssetBreadcrumb::OnCrumbClicked(const FFlowBreadcrumb& Item) const { const UFlowAsset* InspectedInstance = TemplateAsset->GetInspectedInstance(); - if (InspectedInstance == nullptr || Item.InstanceName != InspectedInstance->GetDisplayName()) + if (InspectedInstance == nullptr || Item.CurrentInstance != TemplateAsset) { - GEditor->GetEditorSubsystem()->OpenEditorForAsset(Item.AssetPathName); + const TWeakObjectPtr ClickedInstance = Item.CurrentInstance; + UFlowAsset* ClickedTemplateAsset = ClickedInstance->GetTemplateAsset(); + + if (GEditor->GetEditorSubsystem()->OpenEditorForAsset(ClickedTemplateAsset)) + { + ClickedTemplateAsset->SetInspectedInstance(ClickedInstance); + if (const TSharedPtr FlowAssetEditor = FFlowGraphUtils::GetFlowAssetEditor(ClickedTemplateAsset)) + { + FlowAssetEditor->JumpToNode(Item.ChildInstance->GetNodeOwningThisAssetInstance()->GetGraphNode()); + } + } } } @@ -199,9 +380,9 @@ void FFlowAssetToolbar::BuildAssetToolbar(UToolMenu* ToolbarMenu) const Section.InsertPosition = FToolMenuInsert("Asset", EToolMenuInsertType::After); // add buttons - Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFlowToolbarCommands::Get().RefreshAsset)); - Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFlowToolbarCommands::Get().ValidateAsset)); - Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFlowToolbarCommands::Get().EditAssetDefaults)); + Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFlowEditorCommands::Get().RefreshAsset)); + Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFlowEditorCommands::Get().ValidateAsset)); + Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFlowEditorCommands::Get().EditAssetDefaults)); } { @@ -229,7 +410,7 @@ void FFlowAssetToolbar::BuildAssetToolbar(UToolMenu* ToolbarMenu) const })); Section.AddEntry(FToolMenuEntry::InitToolBarButton( - FFlowToolbarCommands::Get().SearchInAsset, + FFlowEditorCommands::Get().SearchInAsset, TAttribute(), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.FindResults") @@ -325,7 +506,8 @@ void FFlowAssetToolbar::BuildDebuggerToolbar(UToolMenu* ToolbarMenu) const InSection.AddEntry(FToolMenuEntry::InitWidget("AssetInstances", SNew(SFlowAssetInstanceList, Context->GetFlowAsset()), FText(), true)); - InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FFlowToolbarCommands::Get().GoToParentInstance)); + InSection.AddSeparator(NAME_None).StyleNameOverride = FName("Toolbar.BackplateRight"); + InSection.AddEntry(FToolMenuEntry::InitWidget("AssetBreadcrumb", SNew(SFlowAssetBreadcrumb, Context->GetFlowAsset()), FText(), true)); } })); diff --git a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp index b30394a0e..f46754ba2 100644 --- a/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp +++ b/Source/FlowEditor/Private/Asset/FlowDebugEditorSubsystem.cpp @@ -3,6 +3,7 @@ #include "Asset/FlowDebugEditorSubsystem.h" #include "Asset/FlowAssetEditor.h" #include "Asset/FlowMessageLogListing.h" +#include "Graph/FlowGraphUtils.h" #include "Editor/UnrealEdEngine.h" #include "Engine/Engine.h" @@ -17,6 +18,8 @@ #define LOCTEXT_NAMESPACE "FlowDebugEditorSubsystem" UFlowDebugEditorSubsystem::UFlowDebugEditorSubsystem() + : bOverrideInspectedInstance(false) + , bPausedAtFlowBreakpoint(false) { FEditorDelegates::BeginPIE.AddUObject(this, &ThisClass::OnBeginPIE); FEditorDelegates::ResumePIE.AddUObject(this, &ThisClass::OnResumePIE); @@ -41,6 +44,48 @@ void UFlowDebugEditorSubsystem::OnInstancedTemplateRemoved(UFlowAsset* AssetTemp Super::OnInstancedTemplateRemoved(AssetTemplate); } +void UFlowDebugEditorSubsystem::OnPinTriggered(const UFlowAsset* Instance, const FGuid& NodeGuid, const FName& PinName) +{ + if (bPausedAtFlowBreakpoint) + { + return; + } + + if (ensure(Instance)) + { + const UFlowAsset* InspectedInstance = Instance->GetTemplateAsset()->GetInspectedInstance(); + const FStringView InstanceName = Instance->GetTemplateAsset()->GetLastInspectedInstanceName(); + TWeakObjectPtr DebugWorld = Instance->GetTemplateAsset()->GetWorldBeingDebugged(); + + bOverrideInspectedInstance = false; + + if (InspectedInstance) + { + if (InspectedInstance != Instance) + { + return; + } + } + else if (!InstanceName.IsEmpty() && InstanceName != Instance->GetDebugName()) + { + // there is other inspected asset, from previous PIE session. Wait for it + return; + } + else if (DebugWorld.IsValid() && DebugWorld.Get() != Instance->GetWorld()) + { + return; + } + else + { + bOverrideInspectedInstance = true; + } + + OverrideInstancePtr = Instance; + + Super::OnPinTriggered(Instance, NodeGuid, PinName); + } +} + void UFlowDebugEditorSubsystem::OnRuntimeMessageAdded(const UFlowAsset* AssetTemplate, const TSharedRef& Message) const { const TSharedPtr Log = RuntimeLogs.FindRef(AssetTemplate); @@ -59,12 +104,12 @@ void UFlowDebugEditorSubsystem::OnBeginPIE(const bool bIsSimulating) void UFlowDebugEditorSubsystem::OnResumePIE(const bool bIsSimulating) { - ClearHitBreakpoints(); + ClearPausedState(); } void UFlowDebugEditorSubsystem::OnEndPIE(const bool bIsSimulating) { - ClearHitBreakpoints(); + ClearPausedState(); for (const TPair, TSharedPtr>& Log : RuntimeLogs) { @@ -92,12 +137,57 @@ void UFlowDebugEditorSubsystem::OnEndPIE(const bool bIsSimulating) } } -void UFlowDebugEditorSubsystem::PauseSession() +void UFlowDebugEditorSubsystem::PauseSession(const FGuid& FromNode) { - if (!GUnrealEd->SetPIEWorldsPaused(true)) + if (GEditor->ShouldEndPlayMap()) + { + return; + } + + if (GUnrealEd->SetPIEWorldsPaused(true)) { + bPausedAtFlowBreakpoint = true; + + check(OverrideInstancePtr.IsValid()); + UFlowAsset* TemplateInstance = OverrideInstancePtr->GetTemplateAsset(); + + if (bOverrideInspectedInstance) + { + TemplateInstance->SetInspectedInstance(OverrideInstancePtr); + } + + UFlowNode* FlowNode = TemplateInstance->GetNode(FromNode); + check(FlowNode); + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + if (AssetEditorSubsystem->OpenEditorForAsset(TemplateInstance)) + { + if (const TSharedPtr FlowAssetEditor = FFlowGraphUtils::GetFlowAssetEditor(TemplateInstance)) + { + FlowAssetEditor->JumpToNode(FlowNode->GetGraphNode()); + } + } + GUnrealEd->PlaySessionPaused(); } } +void UFlowDebugEditorSubsystem::ClearPausedState() +{ + ClearHitBreakpoints(); + + if (bPausedAtFlowBreakpoint && bOverrideInspectedInstance) + { + bOverrideInspectedInstance = false; + + // do not reset inspected instance if it does not match temporary OverrideInstance. It means user selected other instance when editor was in debug mode + if (ensure(OverrideInstancePtr.IsValid()) && OverrideInstancePtr == OverrideInstancePtr->GetTemplateAsset()->GetInspectedInstance()) + { + OverrideInstancePtr->GetTemplateAsset()->SetInspectedInstance(nullptr); + } + } + + bPausedAtFlowBreakpoint = false; +} + #undef LOCTEXT_NAMESPACE diff --git a/Source/FlowEditor/Private/FlowEditorCommands.cpp b/Source/FlowEditor/Private/FlowEditorCommands.cpp index 8e681edec..cd9af270c 100644 --- a/Source/FlowEditor/Private/FlowEditorCommands.cpp +++ b/Source/FlowEditor/Private/FlowEditorCommands.cpp @@ -11,12 +11,12 @@ #define LOCTEXT_NAMESPACE "FlowGraphCommands" -FFlowToolbarCommands::FFlowToolbarCommands() - : TCommands("FlowToolbar", LOCTEXT("FlowToolbar", "Flow Toobar"), NAME_None, FFlowEditorStyle::GetStyleSetName()) +FFlowEditorCommands::FFlowEditorCommands() + : TCommands("FlowEditor", LOCTEXT("FlowEditor", "Flow Editor"), NAME_None, FFlowEditorStyle::GetStyleSetName()) { } -void FFlowToolbarCommands::RegisterCommands() +void FFlowEditorCommands::RegisterCommands() { UI_COMMAND(RefreshAsset, "Refresh", "Refresh asset and all nodes", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(ValidateAsset, "Validate", "Validate asset and all nodes", EUserInterfaceActionType::Button, FInputChord()); @@ -25,6 +25,11 @@ void FFlowToolbarCommands::RegisterCommands() UI_COMMAND(EditAssetDefaults, "Asset Defaults", "Edit the FlowAsset default properties", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(GoToParentInstance, "Go To Parent", "Open editor for the Flow Asset that created this Flow instance", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(EnableAllBreakpoints,"Enable All Breakpoints", "Enable all breakpoints", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(DisableAllBreakpoints, "Disable All Breakpoints", "Disable all breakpoints", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(RemoveAllBreakpoints, "Delete All Breakpoints", "Delete all breakpoints", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::F9)); + } FFlowGraphCommands::FFlowGraphCommands() diff --git a/Source/FlowEditor/Private/FlowEditorStyle.cpp b/Source/FlowEditor/Private/FlowEditorStyle.cpp index 23c067776..d9544bd66 100644 --- a/Source/FlowEditor/Private/FlowEditorStyle.cpp +++ b/Source/FlowEditor/Private/FlowEditorStyle.cpp @@ -31,13 +31,11 @@ void FFlowEditorStyle::Initialize() // engine assets StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate/")); - StyleSet->Set("FlowToolbar.RefreshAsset", new IMAGE_BRUSH_SVG( "Starship/Common/Apply", Icon20)); - StyleSet->Set("FlowToolbar.ValidateAsset", new IMAGE_BRUSH_SVG( "Starship/Common/Debug", Icon20)); + StyleSet->Set("FlowEditor.RefreshAsset", new IMAGE_BRUSH_SVG( "Starship/Common/Apply", Icon20)); + StyleSet->Set("FlowEditor.ValidateAsset", new IMAGE_BRUSH_SVG( "Starship/Common/Debug", Icon20)); - StyleSet->Set("FlowToolbar.SearchInAsset", new IMAGE_BRUSH_SVG( "Starship/Common/Search", Icon20)); - StyleSet->Set("FlowToolbar.EditAssetDefaults", new IMAGE_BRUSH_SVG("Starship/Common/Details", Icon20)); - - StyleSet->Set("FlowToolbar.GoToParentInstance", new IMAGE_BRUSH("Icons/icon_DebugStepOut_40x", Icon40)); + StyleSet->Set("FlowEditor.SearchInAsset", new IMAGE_BRUSH_SVG( "Starship/Common/Search", Icon20)); + StyleSet->Set("FlowEditor.EditAssetDefaults", new IMAGE_BRUSH_SVG("Starship/Common/Details", Icon20)); StyleSet->Set("FlowGraph.BreakpointEnabled", new IMAGE_BRUSH("Old/Kismet2/Breakpoint_Valid", FVector2D(24.0f, 24.0f))); StyleSet->Set("FlowGraph.BreakpointDisabled", new IMAGE_BRUSH("Old/Kismet2/Breakpoint_Disabled", FVector2D(24.0f, 24.0f))); diff --git a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp index 4cf5e1449..27a4be131 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp @@ -38,7 +38,7 @@ void SFlowGraphEditor::Construct(const FArguments& InArgs, const TSharedPtr::CreateSP(this, &SFlowGraphEditor::GetGraphAppearanceInfo); Arguments._GraphToEdit = FlowAsset->GetGraph(); Arguments._GraphEvents = InArgs._GraphEvents; Arguments._AutoExpandActionMenu = true; @@ -261,11 +261,7 @@ FGraphAppearanceInfo SFlowGraphEditor::GetGraphAppearanceInfo() const { FGraphAppearanceInfo AppearanceInfo; AppearanceInfo.CornerText = GetCornerText(); - - if (IsPlaySessionPaused()) - { - AppearanceInfo.PIENotifyText = LOCTEXT("PausedLabel", "PAUSED"); - } + AppearanceInfo.PIENotifyText = GetPIEStatus(); return AppearanceInfo; } @@ -275,6 +271,45 @@ FText SFlowGraphEditor::GetCornerText() const return LOCTEXT("AppearanceCornerText_FlowAsset", "FLOW"); } +FText SFlowGraphEditor::GetPIEStatus() const +{ + ENetMode NetMode = NM_Standalone; + const UWorld* World = nullptr; + if (FlowAsset.IsValid()) + { + TWeakObjectPtr DebugWorld = FlowAsset->GetWorldBeingDebugged(); + if (DebugWorld.IsValid()) + { + World = DebugWorld.Get(); + NetMode = DebugWorld->GetNetMode(); + } + else + { + TWeakObjectPtr InspectedInstance = FlowAsset->GetInspectedInstance(); + if (InspectedInstance.IsValid()) + { + World = InspectedInstance->GetWorld(); + if (World) // not sure if it is okay if we don't have a valid world for InspectedInstance + { + NetMode = World->GetNetMode(); + } + } + } + } + + if (NetMode == NM_ListenServer || NetMode == NM_DedicatedServer) + { + return LOCTEXT("PIEStatusServerSimulating", "SERVER - SIMULATING"); + } + else if (NetMode == NM_Client) + { + FWorldContext* PIEContext = GEngine->GetWorldContextFromWorld(World); + return FText::Format(LOCTEXT("PIEStatusClientSimulatingFormat", "CLIENT {0} - SIMULATING"), FText::AsNumber(PIEContext->PIEInstance)); + } + + return LOCTEXT("PIEStatusSimulating", "SIMULATING"); +} + void SFlowGraphEditor::UndoGraphAction() { GEditor->UndoTransaction(); @@ -358,7 +393,7 @@ void SFlowGraphEditor::OnSelectedNodesChanged(const TSet& Nodes) { if (const UFlowGraphNode* GraphNode = Cast(*SetIt)) { - SelectedObjects.Add(Cast(GraphNode->GetFlowNodeBase())); + SelectedObjects.Add(GraphNode->GetFlowNodeBase()); } else { @@ -988,7 +1023,7 @@ void SFlowGraphEditor::OnNodeDoubleClicked(class UEdGraphNode* Node) const const TWeakObjectPtr SubFlowInstance = SubGraphNode->GetFlowAsset()->GetFlowInstance(SubGraphNode); if (SubFlowInstance.IsValid()) { - SubGraphNode->GetFlowAsset()->GetTemplateAsset()->SetInspectedInstance(SubFlowInstance->GetDisplayName()); + SubFlowInstance->GetTemplateAsset()->SetInspectedInstance(SubFlowInstance); } } } diff --git a/Source/FlowEditor/Private/Graph/FlowGraphUtils.cpp b/Source/FlowEditor/Private/Graph/FlowGraphUtils.cpp index ac1672261..5712c0dce 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphUtils.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphUtils.cpp @@ -16,11 +16,20 @@ TSharedPtr FFlowGraphUtils::GetFlowAssetEditor(const UEdGraph* TSharedPtr FlowAssetEditor; if (const UFlowAsset* FlowAsset = Cast(Graph)->GetFlowAsset()) { - const TSharedPtr FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(FlowAsset); - if (FoundAssetEditor.IsValid()) - { - FlowAssetEditor = StaticCastSharedPtr(FoundAssetEditor); - } + FlowAssetEditor = GetFlowAssetEditor(FlowAsset); + } + return FlowAssetEditor; +} + +TSharedPtr FFlowGraphUtils::GetFlowAssetEditor(const UFlowAsset* FlowAsset) +{ + check(FlowAsset); + + TSharedPtr FlowAssetEditor; + const TSharedPtr FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(FlowAsset); + if (FoundAssetEditor.IsValid()) + { + FlowAssetEditor = StaticCastSharedPtr(FoundAssetEditor); } return FlowAssetEditor; } diff --git a/Source/FlowEditor/Public/Asset/FlowAssetEditor.h b/Source/FlowEditor/Public/Asset/FlowAssetEditor.h index 881f6c26e..e3d8561ef 100644 --- a/Source/FlowEditor/Public/Asset/FlowAssetEditor.h +++ b/Source/FlowEditor/Public/Asset/FlowAssetEditor.h @@ -15,6 +15,7 @@ class SFlowGraphEditor; class SFlowPalette; class UFlowAsset; class UFlowGraphNode; +class UFlowDebuggerSubsystem; class IDetailsView; class SDockableTab; @@ -59,6 +60,8 @@ class FLOWEDITOR_API FFlowAssetEditor : public FAssetEditorToolkit, public FEdit TSharedPtr ValidationLog; TSharedPtr ValidationLogListing; + TWeakObjectPtr DebuggerSubsystem; + private: /** The current UI selection state of this editor */ FName CurrentUISelection; @@ -124,8 +127,11 @@ class FLOWEDITOR_API FFlowAssetEditor : public FAssetEditorToolkit, public FEdit void InitFlowAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UObject* ObjectToEdit); protected: + virtual void RegisterMenus(); + virtual void CreateToolbar(); - virtual void BindToolbarCommands(); + + virtual void BindEditorCommands(); virtual void RefreshAsset(); virtual void RefreshDetails(); @@ -142,6 +148,15 @@ class FLOWEDITOR_API FFlowAssetEditor : public FAssetEditorToolkit, public FEdit virtual void GoToParentInstance(); virtual bool CanGoToParentInstance(); + void EnableAllBreakpoints(); + bool HasAnyDisabledBreakpoints(); + + void DisableAllBreakpoints(); + bool HasAnyEnabledBreakpoints(); + + void ClearAllBreakpoints(); + bool HasAnyBreakpoints(); + virtual void CreateWidgets(); virtual void CreateGraphWidget(); diff --git a/Source/FlowEditor/Public/Asset/FlowAssetToolbar.h b/Source/FlowEditor/Public/Asset/FlowAssetToolbar.h index 0a867e672..495872d6f 100644 --- a/Source/FlowEditor/Public/Asset/FlowAssetToolbar.h +++ b/Source/FlowEditor/Public/Asset/FlowAssetToolbar.h @@ -7,10 +7,53 @@ #include "FlowAsset.h" +class UFlowNode_SubGraph; class FFlowAssetEditor; class UFlowAssetEditorContext; class UToolMenu; +struct FFlowDebugWorld +{ + /** Actual World object */ + TWeakObjectPtr WorldPtr; + + /** Friendly label for debug world */ + FString WorldLabel; + + FFlowDebugWorld(const TWeakObjectPtr& InWorldPtr, const FString& InWorldLabel) + : WorldPtr(InWorldPtr) + , WorldLabel(InWorldLabel) + { + } + + /** Returns true if this is the special entry for no specific world */ + bool IsEmptyObject() const + { + return WorldPtr.IsExplicitlyNull(); + } +}; + +struct FFlowDebugInstance +{ + /** Actual FlowAsset instance */ + TWeakObjectPtr InstancePtr; + + /** Friendly label for debug instance */ + FString InstanceLabel; + + FFlowDebugInstance(const TWeakObjectPtr& InInstancePtr, const FString& InInstanceLabel) + : InstancePtr(InInstancePtr) + , InstanceLabel(InInstanceLabel) + { + } + + /** Returns true if this is the special entry for no specific instance */ + bool IsEmptyObject() const + { + return InstancePtr.IsExplicitlyNull(); + } +}; + ////////////////////////////////////////////////////////////////////////// // Flow Asset Instance List @@ -23,22 +66,32 @@ class FLOWEDITOR_API SFlowAssetInstanceList : public SCompoundWidget void Construct(const FArguments& InArgs, const TWeakObjectPtr InTemplateAsset); virtual ~SFlowAssetInstanceList() override; - static EVisibility GetDebuggerVisibility(); - private: - void RefreshInstances(); + static EVisibility GetWorldComboVisibility(); + + void GenerateDebugWorldNames(); + TSharedRef GenerateWorldItemWidget(TSharedPtr Item) const; + void DebugWorldSelectionChanged(TSharedPtr SelectedItem, ESelectInfo::Type SelectionType); + FText GetSelectedWorldName() const; + TSharedPtr GetDebugWorld() const; - TSharedRef OnGenerateWidget(TSharedPtr Item) const; - void OnSelectionChanged(TSharedPtr SelectedItem, ESelectInfo::Type SelectionType); + void GenerateDebugInstances(); + TSharedRef GenerateInstanceItemWidget(TSharedPtr Item) const; + void DebugInstanceSelectionChanged(TSharedPtr SelectedItem, ESelectInfo::Type SelectionType); FText GetSelectedInstanceName() const; + TSharedPtr GetDebugInstance() const; + TWeakObjectPtr TemplateAsset; - TSharedPtr>> Dropdown; + + TSharedPtr>> DebugWorldsComboBox; + TSharedPtr>> DebugInstancesComboBox; - TArray> InstanceNames; - TSharedPtr SelectedInstance; + TArray> DebugWorlds; + TArray> DebugInstances; static FText NoInstanceSelectedText; + static FText AllWorldsText; }; ////////////////////////////////////////////////////////////////////////// @@ -49,17 +102,17 @@ class FLOWEDITOR_API SFlowAssetInstanceList : public SCompoundWidget */ struct FLOWEDITOR_API FFlowBreadcrumb { - const FString AssetPathName; - const FName InstanceName; + const TWeakObjectPtr CurrentInstance; + const TWeakObjectPtr ChildInstance; FFlowBreadcrumb() - : AssetPathName(FString()) - , InstanceName(NAME_None) + : CurrentInstance(nullptr) + , ChildInstance(nullptr) {} - explicit FFlowBreadcrumb(const TWeakObjectPtr FlowAsset) - : AssetPathName(FlowAsset->GetTemplateAsset()->GetPathName()) - , InstanceName(FlowAsset->GetDisplayName()) + explicit FFlowBreadcrumb(const TWeakObjectPtr InCurrentInstance, const TWeakObjectPtr InChildInstance) + : CurrentInstance(InCurrentInstance) + , ChildInstance(InChildInstance) {} }; @@ -72,6 +125,8 @@ class FLOWEDITOR_API SFlowAssetBreadcrumb : public SCompoundWidget void Construct(const FArguments& InArgs, const TWeakObjectPtr InTemplateAsset); private: + EVisibility GetBreadcrumbVisibility() const; + void FillBreadcrumb(); void OnCrumbClicked(const FFlowBreadcrumb& Item) const; TWeakObjectPtr TemplateAsset; diff --git a/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h b/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h index 3cbe13fa8..8ef7229e7 100644 --- a/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h +++ b/Source/FlowEditor/Public/Asset/FlowDebugEditorSubsystem.h @@ -27,11 +27,18 @@ class FLOWEDITOR_API UFlowDebugEditorSubsystem : public UFlowDebuggerSubsystem virtual void OnInstancedTemplateAdded(UFlowAsset* AssetTemplate) override; virtual void OnInstancedTemplateRemoved(UFlowAsset* AssetTemplate) const override; + virtual void OnPinTriggered(const UFlowAsset* Instance, const FGuid& NodeGuid, const FName& PinName) override; + void OnRuntimeMessageAdded(const UFlowAsset* AssetTemplate, const TSharedRef& Message) const; virtual void OnBeginPIE(const bool bIsSimulating); virtual void OnResumePIE(const bool bIsSimulating); virtual void OnEndPIE(const bool bIsSimulating); - virtual void PauseSession() override; + virtual void PauseSession(const FGuid& FromNode) override; + void ClearPausedState(); +private: + bool bOverrideInspectedInstance; + bool bPausedAtFlowBreakpoint; + TWeakObjectPtr OverrideInstancePtr; }; diff --git a/Source/FlowEditor/Public/FlowEditorCommands.h b/Source/FlowEditor/Public/FlowEditorCommands.h index d83f84d5b..e26ef38b4 100644 --- a/Source/FlowEditor/Public/FlowEditorCommands.h +++ b/Source/FlowEditor/Public/FlowEditorCommands.h @@ -7,10 +7,10 @@ #include "Framework/Commands/UICommandInfo.h" #include "Templates/SharedPointer.h" -class FLOWEDITOR_API FFlowToolbarCommands : public TCommands +class FLOWEDITOR_API FFlowEditorCommands : public TCommands { public: - FFlowToolbarCommands(); + FFlowEditorCommands(); TSharedPtr RefreshAsset; TSharedPtr ValidateAsset; @@ -20,6 +20,10 @@ class FLOWEDITOR_API FFlowToolbarCommands : public TCommands GoToParentInstance; + TSharedPtr EnableAllBreakpoints; + TSharedPtr DisableAllBreakpoints; + TSharedPtr RemoveAllBreakpoints; + virtual void RegisterCommands() override; }; diff --git a/Source/FlowEditor/Public/Graph/FlowGraphEditor.h b/Source/FlowEditor/Public/Graph/FlowGraphEditor.h index ac575ea86..268f1f53e 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphEditor.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphEditor.h @@ -41,6 +41,7 @@ class FLOWEDITOR_API SFlowGraphEditor : public SGraphEditor virtual FGraphAppearanceInfo GetGraphAppearanceInfo() const; virtual FText GetCornerText() const; + virtual FText GetPIEStatus() const; private: static void UndoGraphAction(); diff --git a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h index f19656734..5a33fb576 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h @@ -69,5 +69,5 @@ class FLOWEDITOR_API UFlowGraphEditorSettings : public UDeveloperSettings public: virtual FName GetCategoryName() const override { return FName("Flow Graph"); } - virtual FText GetSectionText() const override { return INVTEXT("User Settings"); } + virtual FText GetSectionText() const override { return INVTEXT("Graph User Settings"); } }; diff --git a/Source/FlowEditor/Public/Graph/FlowGraphUtils.h b/Source/FlowEditor/Public/Graph/FlowGraphUtils.h index bb7731dac..fecb73ee5 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphUtils.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphUtils.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "Templates/SharedPointer.h" +class UFlowAsset; class FFlowAssetEditor; class SFlowGraphEditor; class UEdGraph; @@ -15,6 +16,7 @@ class FLOWEDITOR_API FFlowGraphUtils FFlowGraphUtils() {} static TSharedPtr GetFlowAssetEditor(const UEdGraph* Graph); + static TSharedPtr GetFlowAssetEditor(const UFlowAsset* FlowAsset); static TSharedPtr GetFlowGraphEditor(const UEdGraph* Graph); static FString RemovePrefixFromNodeText(const FText& Source);