Files
UnrealEngine/Engine/Source/Editor/AIGraph/Private/AIGraph.cpp
2025-05-18 13:04:45 +08:00

282 lines
6.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AIGraph.h"
#include "AIGraphModule.h"
#include "AIGraphNode.h"
#include "AIGraphTypes.h"
#include "Containers/Array.h"
#include "Containers/EnumAsByte.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraph/EdGraphSchema.h"
#include "HAL/PlatformMath.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Serialization/Archive.h"
#include "Templates/Casts.h"
#include "Trace/Detail/Channel.h"
#include "UObject/Object.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/UObjectHash.h"
UAIGraph::UAIGraph(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
bLockUpdates = false;
}
void UAIGraph::UpdateAsset(int32 UpdateFlags)
{
// empty in base class
}
void UAIGraph::OnCreated()
{
MarkVersion();
}
void UAIGraph::OnLoaded()
{
UpdateErrorMessages();
UpdateUnknownNodeClasses();
}
void UAIGraph::Initialize()
{
UpdateVersion();
}
void UAIGraph::UpdateVersion()
{
if (GraphVersion == 1)
{
return;
}
MarkVersion();
Modify();
}
void UAIGraph::MarkVersion()
{
GraphVersion = 1;
}
bool UAIGraph::UpdateUnknownNodeClasses()
{
bool bUpdated = false;
for (int32 NodeIdx = 0; NodeIdx < Nodes.Num(); NodeIdx++)
{
UAIGraphNode* MyNode = Cast<UAIGraphNode>(Nodes[NodeIdx]);
if (MyNode)
{
const bool bUpdatedNode = MyNode->RefreshNodeClass();
bUpdated = bUpdated || bUpdatedNode;
for (int32 SubNodeIdx = 0; SubNodeIdx < MyNode->SubNodes.Num(); SubNodeIdx++)
{
if (MyNode->SubNodes[SubNodeIdx])
{
const bool bUpdatedSubNode = MyNode->SubNodes[SubNodeIdx]->RefreshNodeClass();
bUpdated = bUpdated || bUpdatedSubNode;
}
}
}
}
return bUpdated;
}
void UpdateAIGraphNodeErrorMessage(UAIGraphNode& Node)
{
// Broke out setting error message in to own function so it can be reused when iterating nodes collection.
if (Node.NodeInstance)
{
Node.ErrorMessage = FGraphNodeClassHelper::GetDeprecationMessage(Node.NodeInstance->GetClass());
// Only check for node-specific errors if the node is not deprecated
if (Node.ErrorMessage.IsEmpty())
{
Node.UpdateErrorMessage();
// For node-specific validation we don't want to spam the log with errors
return;
}
}
else
{
// Null instance. Do we have any meaningful class data?
FString StoredClassName = Node.ClassData.GetClassName();
StoredClassName.RemoveFromEnd(TEXT("_C"));
if (!StoredClassName.IsEmpty())
{
// There is class data here but the instance was not be created.
static const FString IsMissingClassMessage(" class missing. Referenced by ");
Node.ErrorMessage = StoredClassName + IsMissingClassMessage + Node.GetFullName();
}
}
if (Node.HasErrors())
{
UE_LOG(LogAIGraph, Error, TEXT("%s"), *Node.ErrorMessage);
}
}
// deprecated
void UAIGraph::UpdateDeprecatedClasses()
{
UpdateErrorMessages();
}
void UAIGraph::UpdateErrorMessages()
{
for (int32 Idx = 0, IdxNum = Nodes.Num(); Idx < IdxNum; ++Idx)
{
UAIGraphNode* Node = Cast<UAIGraphNode>(Nodes[Idx]);
if (Node != nullptr)
{
UpdateAIGraphNodeErrorMessage(*Node);
for (int32 SubIdx = 0, SubIdxNum = Node->SubNodes.Num(); SubIdx < SubIdxNum; ++SubIdx)
{
if (Node->SubNodes[SubIdx] != nullptr)
{
UpdateAIGraphNodeErrorMessage(*Node->SubNodes[SubIdx]);
}
}
}
}
}
void UAIGraph::Serialize(FArchive& Ar)
{
// Overridden to flags up errors in the behavior tree while cooking.
Super::Serialize(Ar);
// Execute UpdateErrorMessages only when saving to persistent storage,
// otherwise node instances might not be fully created (i.e. transaction buffer while loading the asset).
if ((Ar.IsSaving() && Ar.IsPersistent()) || Ar.IsCooking())
{
// Logging of errors happens in UpdateErrorMessages
UpdateErrorMessages();
}
}
void UAIGraph::UpdateClassData()
{
for (int32 Idx = 0; Idx < Nodes.Num(); Idx++)
{
UAIGraphNode* Node = Cast<UAIGraphNode>(Nodes[Idx]);
if (Node)
{
Node->UpdateNodeClassData();
for (int32 SubIdx = 0; SubIdx < Node->SubNodes.Num(); SubIdx++)
{
if (UAIGraphNode* SubNode = Node->SubNodes[SubIdx])
{
SubNode->UpdateNodeClassData();
}
}
}
}
}
void UAIGraph::CollectAllNodeInstances(TSet<UObject*>& NodeInstances)
{
for (int32 Idx = 0; Idx < Nodes.Num(); Idx++)
{
UAIGraphNode* MyNode = Cast<UAIGraphNode>(Nodes[Idx]);
if (MyNode)
{
NodeInstances.Add(MyNode->NodeInstance);
for (int32 SubIdx = 0; SubIdx < MyNode->SubNodes.Num(); SubIdx++)
{
if (MyNode->SubNodes[SubIdx])
{
NodeInstances.Add(MyNode->SubNodes[SubIdx]->NodeInstance);
}
}
}
}
}
bool UAIGraph::CanRemoveNestedObject(UObject* TestObject) const
{
return !TestObject->IsA(UEdGraphNode::StaticClass()) &&
!TestObject->IsA(UEdGraph::StaticClass()) &&
!TestObject->IsA(UEdGraphSchema::StaticClass());
}
void UAIGraph::RemoveOrphanedNodes()
{
TSet<UObject*> NodeInstances;
CollectAllNodeInstances(NodeInstances);
NodeInstances.Remove(nullptr);
// Obtain a list of all nodes actually in the asset and discard unused nodes
TArray<UObject*> AllInners;
const bool bIncludeNestedObjects = false;
GetObjectsWithOuter(GetOuter(), AllInners, bIncludeNestedObjects);
for (auto InnerIt = AllInners.CreateConstIterator(); InnerIt; ++InnerIt)
{
UObject* TestObject = *InnerIt;
if (!NodeInstances.Contains(TestObject) && CanRemoveNestedObject(TestObject))
{
OnNodeInstanceRemoved(TestObject);
TestObject->SetFlags(RF_Transient);
TestObject->Rename(NULL, GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional);
}
}
}
void UAIGraph::OnNodeInstanceRemoved(UObject* NodeInstance)
{
// empty in base class
}
void UAIGraph::OnNodesPasted(const FString& ImportStr)
{
// empty in base class
}
UEdGraphPin* UAIGraph::FindGraphNodePin(UEdGraphNode* Node, EEdGraphPinDirection Dir)
{
UEdGraphPin* Pin = nullptr;
for (int32 Idx = 0; Idx < Node->Pins.Num(); Idx++)
{
if (Node->Pins[Idx]->Direction == Dir)
{
Pin = Node->Pins[Idx];
break;
}
}
return Pin;
}
bool UAIGraph::IsLocked() const
{
return bLockUpdates;
}
void UAIGraph::LockUpdates()
{
bLockUpdates = true;
}
void UAIGraph::UnlockUpdates()
{
bLockUpdates = false;
UpdateAsset();
}
void UAIGraph::OnSubNodeDropped()
{
NotifyGraphChanged();
}