// Copyright Epic Games, Inc. All Rights Reserved. #include "ConversationDebugger.h" #define USE_CONVERSATION_DEBUGGER 0 // #include "Conversation/BTNode.h" // #include "Conversation/BTTaskNode.h" // #include "Conversation/BTAuxiliaryNode.h" //#include "ConversationGraphNode_CompositeDecorator.h" #include "ConversationEditor.h" #include "Editor/UnrealEdEngine.h" #include "Selection.h" #include "UnrealEdGlobals.h" //#include "ConversationGraphNode_Requirement.h" //#include "ConversationGraphNode_Task.h" //#include "Conversation/Conversation.h" //#include "ConversationDelegates.h" #include "Framework/Application/SlateApplication.h" #include "ConversationDatabase.h" FConversationDebugger::FConversationDebugger() { TreeAsset = NULL; bIsPIEActive = false; bIsCurrentSubtree = false; StepForwardIntoIdx = INDEX_NONE; StepForwardOverIdx = INDEX_NONE; StepBackIntoIdx = INDEX_NONE; StepBackOverIdx = INDEX_NONE; StepOutIdx = INDEX_NONE; SavedTimestamp = 0.0f; CurrentTimestamp = 0.0f; FEditorDelegates::BeginPIE.AddRaw(this, &FConversationDebugger::OnBeginPIE); FEditorDelegates::EndPIE.AddRaw(this, &FConversationDebugger::OnEndPIE); FEditorDelegates::PausePIE.AddRaw(this, &FConversationDebugger::OnPausePIE); #if USE_CONVERSATION_DEBUGGER UConversationComponent::ActiveDebuggerCounter++; #endif } FConversationDebugger::~FConversationDebugger() { FEditorDelegates::BeginPIE.RemoveAll(this); FEditorDelegates::EndPIE.RemoveAll(this); FEditorDelegates::PausePIE.RemoveAll(this); USelection::SelectObjectEvent.RemoveAll(this); // FConversationDelegates::OnTreeStarted.RemoveAll(this); // FConversationDelegates::OnDebugLocked.RemoveAll(this); // FConversationDelegates::OnDebugSelected.RemoveAll(this); #if USE_CONVERSATION_DEBUGGER UConversationComponent::ActiveDebuggerCounter--; #endif } void FConversationDebugger::CacheRootNode() { // if(RootNode.IsValid() || TreeAsset == nullptr || TreeAsset->BTGraph == nullptr) // { // return; // } // // for (const auto& Node : TreeAsset->BTGraph->Nodes) // { // RootNode = Cast(Node); // if (RootNode.IsValid()) // { // break; // } // } } void FConversationDebugger::Setup(UConversationDatabase* InTreeAsset, TSharedRef InEditorOwner) { EditorOwner = InEditorOwner; TreeAsset = InTreeAsset; DebuggerInstanceIndex = INDEX_NONE; ActiveStepIndex = 0; LastValidStepId = INDEX_NONE; ActiveBreakpoints.Reset(); // KnownInstances.Reset(); CacheRootNode(); #if USE_CONVERSATION_DEBUGGER if (IsPIESimulating()) { OnBeginPIE(GEditor->bIsSimulatingInEditor); Refresh(); } #endif } void FConversationDebugger::Refresh() { CacheRootNode(); // if (IsPIESimulating() && IsDebuggerReady()) // { // // make sure is grabs data if currently paused // if (IsPlaySessionPaused() && TreeInstance.IsValid()) // { // FindLockedDebugActor(GEditor->PlayWorld); // // UpdateDebuggerInstance(); // UpdateAvailableActions(); // // if (DebuggerInstanceIndex != INDEX_NONE) // { // UpdateDebuggerViewOnStepChange(); // UpdateDebuggerViewOnTick(); // // const FConversationDebuggerInstance& ShowInstance = TreeInstance->DebuggerSteps[ActiveStepIndex].InstanceStack[DebuggerInstanceIndex]; // OnActiveNodeChanged(ShowInstance.ActivePath, HasContinuousPrevStep() ? // TreeInstance->DebuggerSteps[ActiveStepIndex - 1].InstanceStack[DebuggerInstanceIndex].ActivePath : // TArray()); // // UpdateAssetFlags(ShowInstance, RootNode.Get(), ActiveStepIndex); // } // } // } } void FConversationDebugger::Tick(float DeltaTime) { if (TreeAsset == NULL || IsPlaySessionPaused()) { return; } // if (!TreeInstance.IsValid()) // { // // clear state when active tree is lost // if (DebuggerInstanceIndex != INDEX_NONE) // { // ClearDebuggerState(); // } // // return; // } #if USE_CONVERSATION_DEBUGGER TArray EmptyPath; int32 TestStepIndex = 0; for (int32 Idx = TreeInstance->DebuggerSteps.Num() - 1; Idx >= 0; Idx--) { const FConversationExecutionStep& Step = TreeInstance->DebuggerSteps[Idx]; if (Step.StepIndex == LastValidStepId) { TestStepIndex = Idx; break; } } // find index of previously displayed state and notify about all changes in between to give breakpoints a chance to trigger for (int32 i = TestStepIndex; i < TreeInstance->DebuggerSteps.Num(); i++) { const FConversationExecutionStep& Step = TreeInstance->DebuggerSteps[i]; if (Step.StepIndex > DisplayedStepIndex) { ActiveStepIndex = i; LastValidStepId = Step.StepIndex; UpdateDebuggerInstance(); UpdateAvailableActions(); if (DebuggerInstanceIndex != INDEX_NONE) { UpdateDebuggerViewOnStepChange(); const FConversationDebuggerInstance& ShowInstance = TreeInstance->DebuggerSteps[ActiveStepIndex].InstanceStack[DebuggerInstanceIndex]; OnActiveNodeChanged(ShowInstance.ActivePath, HasContinuousPrevStep() ? TreeInstance->DebuggerSteps[ActiveStepIndex - 1].InstanceStack[DebuggerInstanceIndex].ActivePath : EmptyPath); } } // skip rest of them if breakpoint hit if (IsPlaySessionPaused()) { break; } } UpdateDebuggerInstance(); if (DebuggerInstanceIndex != INDEX_NONE) { const FConversationDebuggerInstance& ShowInstance = TreeInstance->DebuggerSteps[ActiveStepIndex].InstanceStack[DebuggerInstanceIndex]; if (DisplayedStepIndex != TreeInstance->DebuggerSteps[ActiveStepIndex].StepIndex) { UpdateAssetFlags(ShowInstance, RootNode.Get(), ActiveStepIndex); } // collect current runtime descriptions for every node TArray RuntimeDescriptions; TreeInstance->StoreDebuggerRuntimeValues(RuntimeDescriptions, ShowInstance.RootNode, DebuggerInstanceIndex); UpdateAssetRuntimeDescription(RuntimeDescriptions, RootNode.Get()); } UpdateDebuggerViewOnTick(); #endif } bool FConversationDebugger::IsTickable() const { return IsDebuggerReady(); } void FConversationDebugger::OnBeginPIE(const bool bIsSimulating) { bIsPIEActive = true; if(EditorOwner.IsValid()) { TSharedPtr EditorOwnerPtr = EditorOwner.Pin(); EditorOwnerPtr->RegenerateMenusAndToolbars(); //EditorOwnerPtr->DebuggerUpdateGraph(); } ActiveBreakpoints.Reset(); //CollectBreakpointsFromAsset(RootNode.Get()); FindMatchingTreeInstance(); // remove these delegates first as we can get multiple calls to OnBeginPIE() USelection::SelectObjectEvent.RemoveAll(this); // FConversationDelegates::OnTreeStarted.RemoveAll(this); // FConversationDelegates::OnDebugSelected.RemoveAll(this); USelection::SelectObjectEvent.AddRaw(this, &FConversationDebugger::OnObjectSelected); // FConversationDelegates::OnTreeStarted.AddRaw(this, &FConversationDebugger::OnTreeStarted); // FConversationDelegates::OnDebugSelected.AddRaw(this, &FConversationDebugger::OnAIDebugSelected); } void FConversationDebugger::OnEndPIE(const bool bIsSimulating) { bIsPIEActive = false; if(EditorOwner.IsValid()) { EditorOwner.Pin()->RegenerateMenusAndToolbars(); } USelection::SelectObjectEvent.RemoveAll(this); // FConversationDelegates::OnTreeStarted.RemoveAll(this); // FConversationDelegates::OnDebugSelected.RemoveAll(this); ClearDebuggerState(); ActiveBreakpoints.Reset(); // FConversationDebuggerInstance EmptyData; // UpdateAssetFlags(EmptyData, RootNode.Get(), INDEX_NONE); UpdateDebuggerViewOnInstanceChange(); } void FConversationDebugger::OnPausePIE(const bool bIsSimulating) { #if USE_CONVERSATION_DEBUGGER // We might have paused while executing a sub-tree, so make sure that the editor is showing the correct tree TSharedPtr EditorOwnerPin = EditorOwner.Pin(); if (EditorOwnerPin.IsValid() && TreeInstance.IsValid() && TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex)) { const FConversationExecutionStep& StepInfo = TreeInstance->DebuggerSteps[ActiveStepIndex]; const int32 LastInstanceIndex = StepInfo.InstanceStack.Num() - 1; if (StepInfo.InstanceStack.IsValidIndex(LastInstanceIndex) && StepInfo.InstanceStack[LastInstanceIndex].TreeAsset != TreeAsset) { EditorOwnerPin->DebuggerSwitchAsset(StepInfo.InstanceStack[LastInstanceIndex].TreeAsset); } } #endif } void FConversationDebugger::OnObjectSelected(UObject* Object) { // if (Object && Object->IsSelected()) // { // UConversationComponent* InstanceComp = FindInstanceInActor(Cast(Object)); // if (InstanceComp) // { // ClearDebuggerState(); // TreeInstance = InstanceComp; // // UpdateDebuggerViewOnInstanceChange(); // } // } } void FConversationDebugger::OnAIDebugSelected(const APawn* Pawn) { // UConversationComponent* TestComp = FindInstanceInActor((APawn*)Pawn); // if (TestComp) // { // ClearDebuggerState(); // TreeInstance = TestComp; // // UpdateDebuggerViewOnInstanceChange(); // } } //void FConversationDebugger::OnTreeStarted(const UConversationComponent& OwnerComp, const UConversation& InTreeAsset) //{ // // start debugging if tree asset matches, and no other actor was selected // if (!TreeInstance.IsValid() && TreeAsset && TreeAsset == &InTreeAsset) // { // ClearDebuggerState(); // TreeInstance = MakeWeakObjectPtr(const_cast(&OwnerComp)); // // UpdateDebuggerViewOnInstanceChange(); // } // // // update known instances // TWeakObjectPtr KnownComp = const_cast(&OwnerComp); // KnownInstances.AddUnique(KnownComp); //} void FConversationDebugger::ClearDebuggerState(bool bKeepSubtree) { // LastValidStepId = bKeepSubtree ? LastValidStepId : INDEX_NONE; // // DebuggerInstanceIndex = INDEX_NONE; // ActiveStepIndex = 0; // DisplayedStepIndex = INDEX_NONE; // // if (TreeAsset && RootNode.IsValid()) // { // FConversationDebuggerInstance EmptyData; // UpdateAssetFlags(EmptyData, RootNode.Get(), INDEX_NONE); // } } void FConversationDebugger::UpdateDebuggerInstance() { #if 0 int32 PrevStackIndex = DebuggerInstanceIndex; DebuggerInstanceIndex = INDEX_NONE; if (TreeInstance.IsValid()) { #if USE_CONVERSATION_DEBUGGER if (TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex)) { const FConversationExecutionStep& StepInfo = TreeInstance->DebuggerSteps[ActiveStepIndex]; for (int32 i = 0; i < StepInfo.InstanceStack.Num(); i++) { if (StepInfo.InstanceStack[i].TreeAsset == TreeAsset) { DebuggerInstanceIndex = i; break; } } } #endif UpdateCurrentSubtree(); } if (DebuggerInstanceIndex != PrevStackIndex) { UpdateDebuggerViewOnInstanceChange(); } #endif } // void FConversationDebugger::SetNodeFlags(const struct FConversationDebuggerInstance& Data, class UConversationGraphNode* Node, class UBTNode* NodeInstance) // { // const bool bIsNodeActivePath = Data.ActivePath.Contains(NodeInstance->GetExecutionIndex()); // const bool bIsNodeActiveAdditional = Data.AdditionalActiveNodes.Contains(NodeInstance->GetExecutionIndex()); // const bool bIsNodeActive = bIsNodeActivePath || bIsNodeActiveAdditional; // const bool bIsShowingCurrentState = IsShowingCurrentState(); // // Node->DebuggerUpdateCounter = DisplayedStepIndex; // Node->bDebuggerMarkCurrentlyActive = bIsNodeActive && bIsShowingCurrentState; // Node->bDebuggerMarkPreviouslyActive = bIsNodeActive && !bIsShowingCurrentState; // // const bool bIsTaskNode = NodeInstance->IsA(UBTTaskNode::StaticClass()); // Node->bDebuggerMarkFlashActive = bIsNodeActivePath && bIsTaskNode && IsPlaySessionRunning(); // Node->bDebuggerMarkSearchTrigger = false; // Node->bDebuggerMarkSearchFailedTrigger = false; // // Node->bDebuggerMarkBreakpointTrigger = NodeInstance->GetExecutionIndex() == StoppedOnBreakpointExecutionIndex; // if (Node->bDebuggerMarkBreakpointTrigger) // { // if(EditorOwner.IsValid()) // { // EditorOwner.Pin()->JumpToNode(Node); // } // } // // int32 SearchPathIdx = INDEX_NONE; // int32 NumTriggers = 0; // bool bTriggerOnly = false; // // for (int32 i = 0; i < Data.PathFromPrevious.Num(); i++) // { // const FConversationDebuggerInstance::FNodeFlowData& SearchStep = Data.PathFromPrevious[i]; // const bool bMatchesNodeIndex = (SearchStep.ExecutionIndex == NodeInstance->GetExecutionIndex()); // if (SearchStep.bTrigger || SearchStep.bDiscardedTrigger) // { // NumTriggers++; // if (bMatchesNodeIndex) // { // Node->bDebuggerMarkSearchTrigger = SearchStep.bTrigger; // Node->bDebuggerMarkSearchFailedTrigger = SearchStep.bDiscardedTrigger; // bTriggerOnly = true; // } // } // else if (bMatchesNodeIndex) // { // SearchPathIdx = i; // bTriggerOnly = false; // } // } // // Node->bDebuggerMarkSearchSucceeded = (SearchPathIdx != INDEX_NONE) && Data.PathFromPrevious[SearchPathIdx].bPassed; // Node->bDebuggerMarkSearchFailed = (SearchPathIdx != INDEX_NONE) && !Data.PathFromPrevious[SearchPathIdx].bPassed; // Node->DebuggerSearchPathIndex = bTriggerOnly ? 0 : FMath::Max(-1, SearchPathIdx - NumTriggers); // Node->DebuggerSearchPathSize = Data.PathFromPrevious.Num() - NumTriggers; // } void FConversationDebugger::SetCompositeDecoratorFlags(const struct FConversationDebuggerInstance& Data, class UConversationGraphNode_CompositeDecorator* Node) { // const bool bIsShowingCurrentState = IsShowingCurrentState(); // bool bIsNodeActive = false; // for (int32 i = 0; i < Data.AdditionalActiveNodes.Num(); i++) // { // if (Node->FirstExecutionIndex <= Data.AdditionalActiveNodes[i] && Node->LastExecutionIndex >= Data.AdditionalActiveNodes[i]) // { // bIsNodeActive = true; // break; // } // } // // Node->DebuggerUpdateCounter = DisplayedStepIndex; // Node->bDebuggerMarkCurrentlyActive = bIsNodeActive && bIsShowingCurrentState; // Node->bDebuggerMarkPreviouslyActive = bIsNodeActive && !bIsShowingCurrentState; // // Node->bDebuggerMarkFlashActive = false; // Node->bDebuggerMarkSearchTrigger = false; // Node->bDebuggerMarkSearchFailedTrigger = false; // // int32 SearchPathIdx = INDEX_NONE; // int32 NumTriggers = 0; // bool bTriggerOnly = false; // for (int32 i = 0; i < Data.PathFromPrevious.Num(); i++) // { // const FConversationDebuggerInstance::FNodeFlowData& SearchStep = Data.PathFromPrevious[i]; // const bool bMatchesNodeIndex = (Node->FirstExecutionIndex <= SearchStep.ExecutionIndex && Node->LastExecutionIndex >= SearchStep.ExecutionIndex); // if (SearchStep.bTrigger || SearchStep.bDiscardedTrigger) // { // NumTriggers++; // if (bMatchesNodeIndex) // { // Node->bDebuggerMarkSearchTrigger = SearchStep.bTrigger; // Node->bDebuggerMarkSearchFailedTrigger = SearchStep.bDiscardedTrigger; // bTriggerOnly = true; // } // } // else if (bMatchesNodeIndex) // { // SearchPathIdx = i; // bTriggerOnly = false; // } // } // // Node->bDebuggerMarkSearchSucceeded = (SearchPathIdx != INDEX_NONE) && Data.PathFromPrevious[SearchPathIdx].bPassed; // Node->bDebuggerMarkSearchFailed = (SearchPathIdx != INDEX_NONE) && !Data.PathFromPrevious[SearchPathIdx].bPassed; // Node->DebuggerSearchPathIndex = bTriggerOnly ? 0 : FMath::Max(-1, SearchPathIdx - NumTriggers); // Node->DebuggerSearchPathSize = Data.PathFromPrevious.Num() - NumTriggers; } void FConversationDebugger::UpdateAssetFlags(const struct FConversationDebuggerInstance& Data, class UConversationGraphNode* Node, int32 StepIdx) { // if (Node == NULL) // { // return; // } // // // special case for marking root when out of nodes // if (Node == RootNode.Get()) // { // const bool bIsNodeActive = (Data.ActivePath.Num() == 0) && (StepIdx >= 0); // const bool bIsShowingCurrentState = IsShowingCurrentState(); // // Node->bDebuggerMarkCurrentlyActive = bIsNodeActive && bIsShowingCurrentState; // Node->bDebuggerMarkPreviouslyActive = bIsNodeActive && !bIsShowingCurrentState; // DisplayedStepIndex = StepIdx; // } // // for (int32 PinIdx = 0; PinIdx < Node->Pins.Num(); PinIdx++) // { // UEdGraphPin* Pin = Node->Pins[PinIdx]; // if (Pin->Direction != EGPD_Output) // { // continue; // } // // for (int32 i = 0; i < Pin->LinkedTo.Num(); i++) // { // UConversationGraphNode* LinkedNode = Cast(Pin->LinkedTo[i]->GetOwningNode()); // if (LinkedNode) // { // UBTNode* BTNode = Cast(LinkedNode->NodeInstance); // if (BTNode) // { // SetNodeFlags(Data, LinkedNode, BTNode); // SetNodeRuntimeDescription(Data.RuntimeDesc, LinkedNode, BTNode); // } // // for (int32 iAux = 0; iAux < LinkedNode->Decorators.Num(); iAux++) // { // UConversationGraphNode_Decorator* DecoratorNode = Cast(LinkedNode->Decorators[iAux]); // UBTAuxiliaryNode* AuxNode = DecoratorNode ? Cast(DecoratorNode->NodeInstance) : NULL; // if (AuxNode) // { // SetNodeFlags(Data, DecoratorNode, AuxNode); // SetNodeRuntimeDescription(Data.RuntimeDesc, DecoratorNode, AuxNode); // // // pass restart trigger to parent graph node for drawing // LinkedNode->bDebuggerMarkSearchTrigger |= DecoratorNode->bDebuggerMarkSearchTrigger; // LinkedNode->bDebuggerMarkSearchFailedTrigger |= DecoratorNode->bDebuggerMarkSearchFailedTrigger; // } // // UConversationGraphNode_CompositeDecorator* CompDecoratorNode = Cast(LinkedNode->Decorators[iAux]); // if (CompDecoratorNode) // { // SetCompositeDecoratorFlags(Data, CompDecoratorNode); // SetCompositeDecoratorRuntimeDescription(Data.RuntimeDesc, CompDecoratorNode); // // // pass restart trigger to parent graph node for drawing // LinkedNode->bDebuggerMarkSearchTrigger |= CompDecoratorNode->bDebuggerMarkSearchTrigger; // LinkedNode->bDebuggerMarkSearchFailedTrigger |= CompDecoratorNode->bDebuggerMarkSearchFailedTrigger; // } // } // // for (int32 iAux = 0; iAux < LinkedNode->Services.Num(); iAux++) // { // UConversationGraphNode_Service* ServiceNode = Cast(LinkedNode->Services[iAux]); // UBTAuxiliaryNode* AuxNode = ServiceNode ? Cast(ServiceNode->NodeInstance) : NULL; // if (AuxNode) // { // SetNodeFlags(Data, ServiceNode, AuxNode); // SetNodeRuntimeDescription(Data.RuntimeDesc, ServiceNode, AuxNode); // } // } // // UpdateAssetFlags(Data, LinkedNode, StepIdx); // } // } // } } void FConversationDebugger::UpdateAssetRuntimeDescription(const TArray& RuntimeDescriptions, class UConversationGraphNode* Node) { // if (Node == NULL) // { // return; // } // // for (int32 PinIdx = 0; PinIdx < Node->Pins.Num(); PinIdx++) // { // UEdGraphPin* Pin = Node->Pins[PinIdx]; // if (Pin->Direction != EGPD_Output) // { // continue; // } // // for (int32 i = 0; i < Pin->LinkedTo.Num(); i++) // { // UConversationGraphNode* LinkedNode = Cast(Pin->LinkedTo[i]->GetOwningNode()); // if (LinkedNode) // { // UBTNode* BTNode = Cast(LinkedNode->NodeInstance); // if (BTNode) // { // SetNodeRuntimeDescription(RuntimeDescriptions, LinkedNode, BTNode); // } // // for (int32 iAux = 0; iAux < LinkedNode->Decorators.Num(); iAux++) // { // UConversationGraphNode_Decorator* DecoratorNode = Cast(LinkedNode->Decorators[iAux]); // UBTAuxiliaryNode* AuxNode = DecoratorNode ? Cast(DecoratorNode->NodeInstance) : NULL; // if (AuxNode) // { // SetNodeRuntimeDescription(RuntimeDescriptions, DecoratorNode, AuxNode); // } // // UConversationGraphNode_CompositeDecorator* CompDecoratorNode = Cast(LinkedNode->Decorators[iAux]); // if (CompDecoratorNode) // { // SetCompositeDecoratorRuntimeDescription(RuntimeDescriptions, CompDecoratorNode); // } // } // // for (int32 iAux = 0; iAux < LinkedNode->Services.Num(); iAux++) // { // UConversationGraphNode_Service* ServiceNode = Cast(LinkedNode->Services[iAux]); // UBTAuxiliaryNode* AuxNode = ServiceNode ? Cast(ServiceNode->NodeInstance) : NULL; // if (AuxNode) // { // SetNodeRuntimeDescription(RuntimeDescriptions, ServiceNode, AuxNode); // } // } // // UpdateAssetRuntimeDescription(RuntimeDescriptions, LinkedNode); // } // } // } } // void FConversationDebugger::SetNodeRuntimeDescription(const TArray& RuntimeDescriptions, class UConversationGraphNode* Node, class UBTNode* NodeInstance) // { // Node->DebuggerRuntimeDescription = RuntimeDescriptions.IsValidIndex(NodeInstance->GetExecutionIndex()) ? // RuntimeDescriptions[NodeInstance->GetExecutionIndex()] : FString(); // } // void FConversationDebugger::SetCompositeDecoratorRuntimeDescription(const TArray& RuntimeDescriptions, class UConversationGraphNode_CompositeDecorator* Node) // { // Node->DebuggerRuntimeDescription.Empty(); // for (int32 i = Node->FirstExecutionIndex; i <= Node->LastExecutionIndex; i++) // { // if (RuntimeDescriptions.IsValidIndex(i) && RuntimeDescriptions[i].Len()) // { // if (Node->DebuggerRuntimeDescription.Len()) // { // Node->DebuggerRuntimeDescription.AppendChar(TEXT('\n')); // } // // Node->DebuggerRuntimeDescription += FString::Printf(TEXT("[%d] %s"), i, *RuntimeDescriptions[i].Replace(TEXT("\n"), TEXT(", "))); // } // } // } void FConversationDebugger::CollectBreakpointsFromAsset(class UConversationGraphNode* Node) { // if (Node == NULL) // { // return; // } // // for (int32 PinIdx = 0; PinIdx < Node->Pins.Num(); PinIdx++) // { // UEdGraphPin* Pin = Node->Pins[PinIdx]; // if (Pin->Direction != EGPD_Output) // { // continue; // } // // for (int32 i = 0; i < Pin->LinkedTo.Num(); i++) // { // UConversationGraphNode* LinkedNode = Cast(Pin->LinkedTo[i]->GetOwningNode()); // if (LinkedNode) // { // UBTNode* BTNode = Cast(LinkedNode->NodeInstance); // if (BTNode && LinkedNode->bHasBreakpoint && LinkedNode->bIsBreakpointEnabled) // { // ActiveBreakpoints.Add(BTNode->GetExecutionIndex()); // } // // CollectBreakpointsFromAsset(LinkedNode); // } // } // } } // int32 FConversationDebugger::FindMatchingDebuggerStack(UConversationComponent& TestInstance) const // { // #if USE_CONVERSATION_DEBUGGER // if (TestInstance.DebuggerSteps.Num()) // { // const FConversationExecutionStep& StepInfo = TestInstance.DebuggerSteps.Last(); // for (int32 i = 0; i < StepInfo.InstanceStack.Num(); i++) // { // if (StepInfo.InstanceStack[i].TreeAsset == TreeAsset) // { // return i; // } // } // } // #endif // // return INDEX_NONE; // } // // UConversationComponent* FConversationDebugger::FindInstanceInActor(AActor* TestActor) // { // UConversationComponent* FoundInstance = NULL; // if (TestActor) // { // APawn* TestPawn = Cast(TestActor); // if (TestPawn && TestPawn->GetController()) // { // FoundInstance = TestPawn->GetController()->FindComponentByClass(); // } // // if (FoundInstance == NULL) // { // FoundInstance = TestActor->FindComponentByClass(); // } // } // // return FoundInstance; // } void FConversationDebugger::FindLockedDebugActor(UWorld* World) { #if 0 APlayerController* LocalPC = GEngine->GetFirstLocalPlayerController(World); if (LocalPC && LocalPC->GetHUD() && LocalPC->GetPawnOrSpectator()) { APawn* SelectedPawn = NULL; #if WITH_ENGINE const UEditorEngine* EEngine = Cast(GEngine); for (FSelectionIterator It = EEngine->GetSelectedActorIterator(); It; ++It) { SelectedPawn = Cast(*It); if (SelectedPawn) { break; } } #endif //WITH_ENGINE UConversationComponent* TestInstance = FindInstanceInActor((APawn*)SelectedPawn); if (TestInstance) { TreeInstance = TestInstance; #if USE_CONVERSATION_DEBUGGER ActiveStepIndex = TestInstance->DebuggerSteps.Num() - 1; #endif } } #endif } void FConversationDebugger::FindMatchingTreeInstance() { #if 0 KnownInstances.Reset(); // Find the world for the dedicated server if any, otherwise fallback to the PIE world UWorld* PlayWorld = nullptr; for (const FWorldContext& PieContext : GEditor->GetWorldContexts()) { if (PieContext.WorldType == EWorldType::PIE && PieContext.World() != nullptr) { if (PieContext.RunAsDedicated) { PlayWorld = PieContext.World(); break; } else if(!PlayWorld) { PlayWorld = PieContext.World(); // Need to continue to see if their is a dedicated server. } } } if (PlayWorld == NULL) { return; } UConversationComponent* MatchingComp = NULL; for (FActorIterator It(PlayWorld); It; ++It) { AActor* TestActor = *It; UConversationComponent* TestComp = TestActor ? TestActor->FindComponentByClass() : nullptr; if (TestComp) { KnownInstances.Add(TestComp); const int32 MatchingIdx = FindMatchingDebuggerStack(*TestComp); if (MatchingIdx != INDEX_NONE) { MatchingComp = TestComp; if (TestActor->IsSelected()) { TreeInstance = TestComp; return; } } } } if (MatchingComp != TreeInstance) { TreeInstance = MatchingComp; UpdateDebuggerViewOnInstanceChange(); } #endif } bool FConversationDebugger::IsDebuggerReady() const { return bIsPIEActive; } bool FConversationDebugger::IsDebuggerRunning() const { // return TreeInstance.IsValid() && (ActiveStepIndex != INDEX_NONE); return false; } bool FConversationDebugger::IsShowingCurrentState() const { #if USE_CONVERSATION_DEBUGGER if (TreeInstance.IsValid() && TreeInstance->DebuggerSteps.Num()) { return (TreeInstance->DebuggerSteps.Num() - 1) == ActiveStepIndex; } #endif return false; } int32 FConversationDebugger::GetShownStateIndex() const { #if USE_CONVERSATION_DEBUGGER if (TreeInstance.IsValid()) { return (TreeInstance->DebuggerSteps.Num() - 1) - ActiveStepIndex; } #endif return 0; } void FConversationDebugger::StepForwardInto() { #if USE_CONVERSATION_DEBUGGER UpdateCurrentStep(ActiveStepIndex, StepForwardIntoIdx); #endif } static void ForEachGameWorld(const TFunction& Func) { for (const FWorldContext& PieContext : GUnrealEd->GetWorldContexts()) { UWorld* PlayWorld = PieContext.World(); if (PlayWorld && PlayWorld->IsGameWorld()) { Func(PlayWorld); } } } static bool AreAllGameWorldPaused() { bool bPaused = true; ForEachGameWorld([&](UWorld* World) { bPaused = bPaused && World->bDebugPauseExecution; }); return bPaused; } bool FConversationDebugger::CanStepForwardInto() const { return AreAllGameWorldPaused() && (StepForwardIntoIdx != INDEX_NONE); } void FConversationDebugger::StepForwardOver() { #if USE_CONVERSATION_DEBUGGER UpdateCurrentStep(ActiveStepIndex, StepForwardOverIdx); #endif } bool FConversationDebugger::CanStepForwardOver() const { return AreAllGameWorldPaused() && (StepForwardOverIdx != INDEX_NONE); } void FConversationDebugger::StepOut() { #if USE_CONVERSATION_DEBUGGER UpdateCurrentStep(ActiveStepIndex, StepOutIdx); #endif } bool FConversationDebugger::CanStepOut() const { return AreAllGameWorldPaused() && (StepOutIdx != INDEX_NONE); } void FConversationDebugger::StepBackInto() { #if USE_CONVERSATION_DEBUGGER UpdateCurrentStep(ActiveStepIndex, StepBackIntoIdx); #endif } bool FConversationDebugger::CanStepBackInto() const { return AreAllGameWorldPaused() && (StepBackIntoIdx != INDEX_NONE); } void FConversationDebugger::StepBackOver() { #if USE_CONVERSATION_DEBUGGER UpdateCurrentStep(ActiveStepIndex, StepBackOverIdx); #endif } bool FConversationDebugger::CanStepBackOver() const { return AreAllGameWorldPaused() && (StepBackOverIdx != INDEX_NONE); } void FConversationDebugger::UpdateCurrentStep(int32 PrevStepIdx, int32 NewStepIdx) { #if USE_CONVERSATION_DEBUGGER if (TreeInstance.IsValid() && TreeInstance->DebuggerSteps.IsValidIndex(NewStepIdx)) { const int32 CurInstaceIdx = FindActiveInstanceIdx(PrevStepIdx); const int32 NewInstanceIdx = FindActiveInstanceIdx(NewStepIdx); const FConversationExecutionStep& CurStepInfo = TreeInstance->DebuggerSteps[PrevStepIdx]; const FConversationExecutionStep& NewStepInfo = TreeInstance->DebuggerSteps[NewStepIdx]; ActiveStepIndex = NewStepIdx; if (NewInstanceIdx != INDEX_NONE && NewStepInfo.InstanceStack[NewInstanceIdx].TreeAsset != TreeAsset) { if (CurInstaceIdx != NewInstanceIdx || CurStepInfo.InstanceStack[CurInstaceIdx].TreeAsset != NewStepInfo.InstanceStack[NewInstanceIdx].TreeAsset) { if(EditorOwner.IsValid()) { EditorOwner.Pin()->DebuggerSwitchAsset(NewStepInfo.InstanceStack[NewInstanceIdx].TreeAsset); } UpdateCurrentSubtree(); } } if (NewStepInfo.InstanceStack.IsValidIndex(DebuggerInstanceIndex)) { const FConversationDebuggerInstance& ShowInstance = NewStepInfo.InstanceStack[DebuggerInstanceIndex]; UpdateAssetFlags(ShowInstance, RootNode.Get(), ActiveStepIndex); } else { ActiveStepIndex = INDEX_NONE; FConversationDebuggerInstance EmptyData; UpdateAssetFlags(EmptyData, RootNode.Get(), INDEX_NONE); } UpdateDebuggerViewOnStepChange(); UpdateAvailableActions(); } #endif } bool FConversationDebugger::HasContinuousNextStep() const { #if USE_CONVERSATION_DEBUGGER if (TreeInstance.IsValid() && TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex + 1)) { const FConversationExecutionStep& NextStepInfo = TreeInstance->DebuggerSteps[ActiveStepIndex + 1]; const FConversationExecutionStep& CurStepInfo = TreeInstance->DebuggerSteps[ActiveStepIndex]; if (CurStepInfo.InstanceStack.IsValidIndex(DebuggerInstanceIndex) && CurStepInfo.InstanceStack.Num() == NextStepInfo.InstanceStack.Num() && CurStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset == NextStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset) { return true; } } #endif return false; } bool FConversationDebugger::HasContinuousPrevStep() const { #if USE_CONVERSATION_DEBUGGER if (TreeInstance.IsValid() && TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex - 1)) { const FConversationExecutionStep& PrevStepInfo = TreeInstance->DebuggerSteps[ActiveStepIndex - 1]; const FConversationExecutionStep& CurStepInfo = TreeInstance->DebuggerSteps[ActiveStepIndex]; if (CurStepInfo.InstanceStack.IsValidIndex(DebuggerInstanceIndex) && CurStepInfo.InstanceStack.Num() == PrevStepInfo.InstanceStack.Num() && CurStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset == PrevStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset) { return true; } } #endif return false; } void FConversationDebugger::OnActiveNodeChanged(const TArray& ActivePath, const TArray& PrevStepPath) { bool bShouldPause = false; StoppedOnBreakpointExecutionIndex = MAX_uint16; // breakpoints: check only nodes, that have changed from previous state // (e.g. breakpoint on sequence, it would break multiple times for every child // but we want only once: when it becomes active) for (int32 i = 0; i < ActivePath.Num(); i++) { const uint16 TestExecutionIndex = ActivePath[i]; if (!PrevStepPath.Contains(TestExecutionIndex)) { if (ActiveBreakpoints.Contains(TestExecutionIndex)) { bShouldPause = true; StoppedOnBreakpointExecutionIndex = TestExecutionIndex; break; } } } if (bShouldPause) { if (EditorOwner.IsValid()) { EditorOwner.Pin()->FocusWindow(TreeAsset); } PausePlaySession(); } } void FConversationDebugger::StopPlaySession() { if (GUnrealEd->PlayWorld) { GEditor->RequestEndPlayMap(); // @TODO: we need a unified flow to leave debugging mode from the different debuggers to prevent strong coupling between modules. // Each debugger (Blueprint & BehaviorTree for now) could then take the appropriate actions to resume the session. if (FSlateApplication::Get().InKismetDebuggingMode()) { FSlateApplication::Get().LeaveDebuggingMode(); } } } void FConversationDebugger::PausePlaySession() { if (GUnrealEd->SetPIEWorldsPaused(true)) { GUnrealEd->PlaySessionPaused(); } } void FConversationDebugger::ResumePlaySession() { if (GUnrealEd->SetPIEWorldsPaused(false)) { // @TODO: we need a unified flow to leave debugging mode from the different debuggers to prevent strong coupling between modules. // Each debugger (Blueprint & BehaviorTree for now) could then take the appropriate actions to resume the session. if (FSlateApplication::Get().InKismetDebuggingMode()) { FSlateApplication::Get().LeaveDebuggingMode(); } GUnrealEd->PlaySessionResumed(); } } bool FConversationDebugger::IsPlaySessionPaused() { return AreAllGameWorldPaused(); } bool FConversationDebugger::IsPlaySessionRunning() { return !AreAllGameWorldPaused(); } bool FConversationDebugger::IsPIESimulating() { return GEditor->bIsSimulatingInEditor || GEditor->PlayWorld; } bool FConversationDebugger::IsPIENotSimulating() { return !GEditor->bIsSimulatingInEditor && (GEditor->PlayWorld == NULL); } void FConversationDebugger::OnBreakpointAdded(class UConversationGraphNode* Node) { // if (IsDebuggerReady()) // { // UBTNode* BTNode = Cast(Node->NodeInstance); // if (BTNode) // { // ActiveBreakpoints.AddUnique(BTNode->GetExecutionIndex()); // } // } } void FConversationDebugger::OnBreakpointRemoved(class UConversationGraphNode* Node) { // if (IsDebuggerReady()) // { // UBTNode* BTNode = Cast(Node->NodeInstance); // if (BTNode) // { // ActiveBreakpoints.RemoveSingleSwap(BTNode->GetExecutionIndex()); // } // } } void FConversationDebugger::UpdateDebuggerViewOnInstanceChange() { #if USE_CONVERSATION_DEBUGGER UBlackboardData* BBAsset = EditorOwner.IsValid() ? EditorOwner.Pin()->GetBlackboardData() : nullptr; if (TreeInstance.IsValid() && TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex) && TreeInstance->DebuggerSteps[ActiveStepIndex].InstanceStack.IsValidIndex(DebuggerInstanceIndex)) { const FConversationDebuggerInstance& ShowInstance = TreeInstance->DebuggerSteps[ActiveStepIndex].InstanceStack[DebuggerInstanceIndex]; if (ShowInstance.TreeAsset) { BBAsset = ShowInstance.TreeAsset->BlackboardAsset; } } OnDebuggedBlackboardChangedEvent.Broadcast(BBAsset); if (DebuggerInstanceIndex != INDEX_NONE) { Refresh(); } else { ClearDebuggerState(/*bKeepSubtreeData=*/true); } #endif } void FConversationDebugger::UpdateDebuggerViewOnStepChange() { #if USE_CONVERSATION_DEBUGGER if (IsDebuggerRunning() && TreeInstance.IsValid() && TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex)) { const FConversationExecutionStep& ShowStep = TreeInstance->DebuggerSteps[ActiveStepIndex]; SavedTimestamp = ShowStep.TimeStamp; SavedValues = ShowStep.BlackboardValues; } #endif } void FConversationDebugger::UpdateDebuggerViewOnTick() { #if USE_CONVERSATION_DEBUGGER if (IsDebuggerRunning() && TreeInstance.IsValid()) { const float GameTime = GEditor && GEditor->PlayWorld ? GEditor->PlayWorld->GetTimeSeconds() : 0.0f; CurrentTimestamp = GameTime; TreeInstance->StoreDebuggerBlackboard(CurrentValues); } #endif } FText FConversationDebugger::FindValueForKey(const FName& InKeyName, bool bUseCurrentState) const { #if USE_CONVERSATION_DEBUGGER if (IsDebuggerRunning() && TreeInstance.IsValid()) { const TMap* MapToQuery = nullptr; if(bUseCurrentState) { MapToQuery = &CurrentValues; } else if(TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex)) { MapToQuery = &TreeInstance->DebuggerSteps[ActiveStepIndex].BlackboardValues; } if(MapToQuery != nullptr) { const FString* FindValue = MapToQuery->Find(InKeyName); if(FindValue != nullptr) { return FText::FromString(*FindValue); } } } return FText(); #endif return FText(); } float FConversationDebugger::GetTimeStamp(bool bUseCurrentState) const { return bUseCurrentState ? CurrentTimestamp : SavedTimestamp; } FString FConversationDebugger::GetDebuggedInstanceDesc() const { // UConversationComponent* BTComponent = TreeInstance.Get(); // return BTComponent ? DescribeInstance(*BTComponent) : return NSLOCTEXT("BlueprintEditor", "DebugActorNothingSelected", "No debug object selected").ToString(); } // FString FConversationDebugger::DescribeInstance(UConversationComponent& InstanceToDescribe) const // { // FString ActorDesc; // if (InstanceToDescribe.GetOwner()) // { // AController* TestController = Cast(InstanceToDescribe.GetOwner()); // ActorDesc = TestController ? // TestController->GetName() : // InstanceToDescribe.GetOwner()->GetActorLabel(); // } // // return ActorDesc; // } // void FConversationDebugger::OnInstanceSelectedInDropdown(UConversationComponent* SelectedInstance) // { // if (SelectedInstance) // { // ClearDebuggerState(); // // AController* OldController = TreeInstance.IsValid() ? Cast(TreeInstance->GetOwner()) : NULL; // APawn* OldPawn = OldController != NULL ? OldController->GetPawn() : NULL; // USelection* SelectedActors = GEditor ? GEditor->GetSelectedActors() : NULL; // if (SelectedActors) // { // SelectedActors->DeselectAll(); // } // // TreeInstance = SelectedInstance; // // if (SelectedActors && SelectedInstance && SelectedInstance->GetOwner()) // { // AController* TestController = Cast(SelectedInstance->GetOwner()); // APawn* Pawn = TestController != NULL ? TestController->GetPawn() : NULL; // if (Pawn) // { // SelectedActors->Select(Pawn); // } // } // // Refresh(); // } // } // // void FConversationDebugger::GetMatchingInstances(TArray& MatchingInstances) // { // for (int32 i = KnownInstances.Num() - 1; i >= 0; i--) // { // UConversationComponent* TestInstance = KnownInstances[i].Get(); // if (TestInstance == NULL) // { // KnownInstances.RemoveAt(i); // continue; // } // // const int32 StackIdx = FindMatchingDebuggerStack(*TestInstance); // if (StackIdx != INDEX_NONE) // { // MatchingInstances.Add(TestInstance); // } // } // } void FConversationDebugger::InitializeFromParent(class FConversationDebugger* ParentDebugger) { ClearDebuggerState(); #if USE_CONVERSATION_DEBUGGER TreeInstance = ParentDebugger->TreeInstance; ActiveStepIndex = ParentDebugger->ActiveStepIndex; UpdateDebuggerInstance(); UpdateAvailableActions(); if (TreeInstance.IsValid() && TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex) && TreeInstance->DebuggerSteps[ActiveStepIndex].InstanceStack.IsValidIndex(DebuggerInstanceIndex)) { const FConversationDebuggerInstance& ShowInstance = TreeInstance->DebuggerSteps[ActiveStepIndex].InstanceStack[DebuggerInstanceIndex]; UpdateAssetFlags(ShowInstance, RootNode.Get(), ActiveStepIndex); } #endif } int32 FConversationDebugger::FindActiveInstanceIdx(int32 StepIdx) const { #if USE_CONVERSATION_DEBUGGER const FConversationExecutionStep& StepInfo = TreeInstance->DebuggerSteps[StepIdx]; for (int32 i = StepInfo.InstanceStack.Num() - 1; i >= 0; i--) { if (StepInfo.InstanceStack[i].IsValid()) { return i; } } #endif return INDEX_NONE; } void FConversationDebugger::UpdateCurrentSubtree() { bIsCurrentSubtree = false; #if USE_CONVERSATION_DEBUGGER if (TreeInstance->DebuggerSteps.IsValidIndex(ActiveStepIndex)) { const FConversationExecutionStep& StepInfo = TreeInstance->DebuggerSteps[ActiveStepIndex]; // assume that top instance is always valid, so it won't take away step buttons when tree is finished as out of nodes // current subtree = no child instances, or child instances are not valid bIsCurrentSubtree = ((DebuggerInstanceIndex == 0) || (StepInfo.InstanceStack.IsValidIndex(DebuggerInstanceIndex) && StepInfo.InstanceStack[DebuggerInstanceIndex].IsValid())) && (!StepInfo.InstanceStack.IsValidIndex(DebuggerInstanceIndex + 1) || !StepInfo.InstanceStack[DebuggerInstanceIndex + 1].IsValid()); } #endif } // static int32 GetNumActiveInstances(const FConversationExecutionStep& StepInfo, class UConversationDatabase*& ActiveSubtree) // { // for (int32 Idx = StepInfo.InstanceStack.Num() - 1; Idx >= 0; Idx--) // { // //if (StepInfo.InstanceStack[Idx].ActivePath.Num()) // { // ActiveSubtree = StepInfo.InstanceStack[Idx].TreeAsset; // return Idx + 1; // } // } // // ActiveSubtree = NULL; // return 0; // } void FConversationDebugger::UpdateAvailableActions() { StepForwardIntoIdx = INDEX_NONE; StepForwardOverIdx = INDEX_NONE; StepBackIntoIdx = INDEX_NONE; StepBackOverIdx = INDEX_NONE; StepOutIdx = INDEX_NONE; #if USE_CONVERSATION_DEBUGGER UConversationComponent* TreeInstancePtr = TreeInstance.Get(); if (TreeInstancePtr && TreeInstancePtr->DebuggerSteps.IsValidIndex(ActiveStepIndex) && DebuggerInstanceIndex >= 0) { const FConversationExecutionStep& CurStepInfo = TreeInstancePtr->DebuggerSteps[ActiveStepIndex]; if (TreeInstancePtr->DebuggerSteps.IsValidIndex(ActiveStepIndex - 1)) { StepBackIntoIdx = ActiveStepIndex - 1; } if (TreeInstancePtr->DebuggerSteps.IsValidIndex(ActiveStepIndex + 1)) { StepForwardIntoIdx = ActiveStepIndex + 1; } UConversationDatabase* CurTree = CurStepInfo.InstanceStack.IsValidIndex(DebuggerInstanceIndex) ? CurStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset : NULL; const int32 CurStepInstances = DebuggerInstanceIndex + 1; for (int32 TestStepIndex = ActiveStepIndex - 1; TestStepIndex >= 0; TestStepIndex--) { const FConversationExecutionStep& TestStepInfo = TreeInstancePtr->DebuggerSteps[TestStepIndex]; UConversationDatabase* TestTree = NULL; const int32 TestStepInstances = GetNumActiveInstances(TestStepInfo, TestTree); StepBackOverIdx = TestStepIndex; // keep going only if the execution is moving to a sub-tree if (TestStepInstances <= CurStepInstances || TestStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset != CurStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset) { break; } } for (int32 TestStepIndex = ActiveStepIndex + 1; TestStepIndex < TreeInstancePtr->DebuggerSteps.Num(); TestStepIndex++) { const FConversationExecutionStep& TestStepInfo = TreeInstancePtr->DebuggerSteps[TestStepIndex]; UConversationDatabase* TestTree = NULL; int32 TestStepInstances = GetNumActiveInstances(TestStepInfo, TestTree); StepForwardOverIdx = TestStepIndex; // keep going only if the execution is moving to a sub-tree if (TestStepInstances <= CurStepInstances || TestStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset != CurStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset) { break; } } if (CurStepInfo.InstanceStack.IsValidIndex(DebuggerInstanceIndex) && CurStepInfo.InstanceStack[DebuggerInstanceIndex].ActivePath.Num()) { for (int32 TestStepIndex = ActiveStepIndex + 1; TestStepIndex < TreeInstancePtr->DebuggerSteps.Num(); TestStepIndex++) { const FConversationExecutionStep& TestStepInfo = TreeInstancePtr->DebuggerSteps[TestStepIndex]; UConversationDatabase* TestTree = NULL; int32 TestStepInstances = GetNumActiveInstances(TestStepInfo, TestTree); if (TestStepInstances < CurStepInstances || TestStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset != CurStepInfo.InstanceStack[DebuggerInstanceIndex].TreeAsset) { // execution left current subtree StepOutIdx = TestStepIndex; break; } } } } #endif }