Files
UnrealEngine/Engine/Plugins/Experimental/SceneState/Source/SceneStateBlueprint/Private/SceneStateBlueprint.cpp
2025-05-18 13:04:45 +08:00

294 lines
9.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SceneStateBlueprint.h"
#include "Blueprint/BlueprintExtension.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Modules/ModuleManager.h"
#include "Nodes/SceneStateMachineStateNode.h"
#include "Nodes/SceneStateMachineTaskNode.h"
#include "PropertyBindingDataView.h"
#include "SceneStateBindingDesc.h"
#include "SceneStateBindingUtils.h"
#include "SceneStateBlueprintBindingUtils.h"
#include "SceneStateBlueprintDelegates.h"
#include "SceneStateBlueprintLog.h"
#include "SceneStateBlueprintUtils.h"
#include "SceneStateGeneratedClass.h"
#include "SceneStateMachineGraph.h"
USceneStateBlueprint::USceneStateBlueprint(const FObjectInitializer& InObjectInitializer)
: Super(InObjectInitializer)
{
RootId = FGuid::NewGuid();
OnRenameVariableReferencesHandle = FBlueprintEditorUtils::OnRenameVariableReferencesEvent.AddUObject(this
, &USceneStateBlueprint::OnRenameVariableReferences);
OnGraphParametersChangedHandle = USceneStateMachineGraph::OnParametersChanged().AddUObject(this
, &USceneStateBlueprint::OnGraphParametersChanged);
#if WITH_EDITOR
BindingCollection.SetBindingsOwner(this);
#endif // WITH_EDITOR
}
UBlueprintExtension* USceneStateBlueprint::FindExtension(TSubclassOf<UBlueprintExtension> InClass) const
{
const TObjectPtr<UBlueprintExtension>* FoundExtension = GetExtensions().FindByPredicate(
[InClass](UBlueprintExtension* InExtension)
{
return InExtension && InExtension->IsA(InClass);
});
if (FoundExtension)
{
return *FoundExtension;
}
return nullptr;
}
FSceneStateBindingDesc USceneStateBlueprint::CreateRootBinding() const
{
using namespace UE::SceneState::Graph;
return CreateBindingDesc(*this);
}
void USceneStateBlueprint::GetBindableStructs(const FGuid InTargetStructId, TArray<TInstancedStruct<FPropertyBindingBindableStructDescriptor>>& OutStructDescs) const
{
using namespace UE::SceneState::Graph;
// Add State Machine Bindable Structs if the Target Struct Id matches the State Machine Id
if (USceneStateMachineGraph* SceneStateMachine = FindStateMachineMatchingId(*this, InTargetStructId))
{
TArray<TInstancedStruct<FSceneStateBindingDesc>> BindingDescs;
GetStateMachineBindingDescs(*this, *SceneStateMachine, BindingDescs);
OutStructDescs.Append(MoveTemp(BindingDescs));
return;
}
// Add Transition Bindable Structs if the Target Struct Id matches the Transition Parameters Id
if (USceneStateMachineTransitionNode* TransitionNode = FindTransitionMatchingId(*this, InTargetStructId))
{
TArray<TInstancedStruct<FSceneStateBindingDesc>> BindingDescs;
GetTransitionBindingDescs(*this, *TransitionNode, BindingDescs);
OutStructDescs.Append(MoveTemp(BindingDescs));
return;
}
// Add the Task Bindable Structs if the target struct is either:
// - The task itself (so the task id must match the target id)
// - A struct within a Task Instance that is not stable across sessions, and so require their own ID.
// One example of this Instanced Property Bags, as the underlying UPropertyBag (UScriptStruct) is not serialized.
if (const USceneStateMachineTaskNode* TargetTaskNode = FindTaskNodeContainingId(*this, InTargetStructId))
{
TArray<TInstancedStruct<FSceneStateBindingDesc>> BindingDescs;
GetTaskBindingDescs(*this, *TargetTaskNode, BindingDescs);
OutStructDescs.Append(MoveTemp(BindingDescs));
}
}
bool USceneStateBlueprint::GetBindableStructByID(const FGuid InStructId, TInstancedStruct<FPropertyBindingBindableStructDescriptor>& OutStructDesc) const
{
using namespace UE::SceneState::Graph;
TInstancedStruct<FSceneStateBindingDesc> BindingDesc;
if (FindBindingDescById(*this, InStructId, BindingDesc))
{
OutStructDesc = MoveTemp(BindingDesc);
return true;
}
return false;
}
bool USceneStateBlueprint::GetBindingDataViewByID(const FGuid InStructId, FPropertyBindingDataView& OutDataView) const
{
using namespace UE::SceneState::Graph;
return FindDataViewById(*this, InStructId, OutDataView);
}
FPropertyBindingBindingCollection* USceneStateBlueprint::GetEditorPropertyBindings()
{
return &BindingCollection;
}
const FPropertyBindingBindingCollection* USceneStateBlueprint::GetEditorPropertyBindings() const
{
return &BindingCollection;
}
bool USceneStateBlueprint::CanCreateParameter(const FGuid InStructId) const
{
using namespace UE::SceneState::Graph;
// Only support creating parameters in BP Variables and State machine parameters
return InStructId == GetRootId() || FindStateMachineMatchingId(*this, InStructId);
}
void USceneStateBlueprint::CreateParametersForStruct(const FGuid InStructId, TArrayView<UE::PropertyBinding::FPropertyCreationDescriptor> InOutCreationDescs)
{
using namespace UE::SceneState::Graph;
if (InStructId == GetRootId())
{
CreateBlueprintVariables(this, InOutCreationDescs);
return;
}
if (USceneStateMachineGraph* StateMachineGraph = FindStateMachineMatchingId(*this, InStructId))
{
CreateUniquelyNamedPropertiesInPropertyBag(InOutCreationDescs, StateMachineGraph->Parameters);
}
}
void USceneStateBlueprint::SetObjectBeingDebugged(UObject* InNewObject)
{
using namespace UE::SceneState;
Super::SetObjectBeingDebugged(InNewObject);
Graph::FBlueprintDebugObjectChange Change;
Change.Blueprint = this;
Change.DebugObject = InNewObject;
Graph::OnBlueprintDebugObjectChanged.Broadcast(Change);
}
UClass* USceneStateBlueprint::GetBlueprintClass() const
{
return USceneStateGeneratedClass::StaticClass();
}
void USceneStateBlueprint::GetReparentingRules(TSet<const UClass*>& OutAllowedChildrenOfClasses, TSet<const UClass*>& OutDisallowedChildrenOfClasses) const
{
OutAllowedChildrenOfClasses.Add(USceneStateGeneratedClass::StaticClass());
}
bool USceneStateBlueprint::SupportedByDefaultBlueprintFactory() const
{
return false;
}
void USceneStateBlueprint::LoadModulesRequiredForCompilation()
{
// Load the module holding the scene state blueprint compiler
// todo: consider moving the compiler to its own module to load that instead of the entire editor module
constexpr const TCHAR* SceneStateBlueprintEditorModule = TEXT("SceneStateBlueprintEditor");
FModuleManager::Get().LoadModule(SceneStateBlueprintEditorModule);
}
bool USceneStateBlueprint::IsValidForBytecodeOnlyRecompile() const
{
return false;
}
void USceneStateBlueprint::BeginDestroy()
{
Super::BeginDestroy();
FBlueprintEditorUtils::OnRenameVariableReferencesEvent.Remove(OnRenameVariableReferencesHandle);
OnRenameVariableReferencesHandle.Reset();
USceneStateMachineGraph::OnParametersChanged().Remove(OnGraphParametersChangedHandle);
OnGraphParametersChangedHandle.Reset();
}
void USceneStateBlueprint::OnRenameVariableReferences(UBlueprint* InBlueprint, UClass* InVariableClass, const FName& InOldVariableName, const FName& InNewVariableName)
{
if (InBlueprint != this)
{
return;
}
// Note: no need to call Blueprint Modify here. It's already been called in FBlueprintEditorUtils::RenameMemberVariable
BindingCollection.ForEachMutableBinding(
[This=this, InOldVariableName, InNewVariableName](FPropertyBindingBinding& InBinding)
{
This->RenameVariableReferenceInPath(InBinding.GetMutableSourcePath(), InOldVariableName, InNewVariableName);
This->RenameVariableReferenceInPath(InBinding.GetMutableTargetPath(), InOldVariableName, InNewVariableName);
});
}
void USceneStateBlueprint::RenameVariableReferenceInPath(FPropertyBindingPath& InPath, FName InOldVariableName, FName InNewVariableName)
{
// Only consider fixing paths that are set to this blueprint class (as the rename here is for blueprint variables only)
if (InPath.GetStructID() != GetRootId())
{
return;
}
TArrayView<FPropertyBindingPathSegment> Segments = InPath.GetMutableSegments();
if (Segments.IsEmpty())
{
return;
}
// Only need to consider the first segment of the path (i.e. the segment containing the blueprint variable)
FPropertyBindingPathSegment& Segment = Segments[0];
if (Segment.GetName() == InOldVariableName)
{
const FString OldPath = InPath.ToString();
Segment.SetName(InNewVariableName);
UE_LOG(LogSceneStateBlueprint, Log, TEXT("Renamed blueprint variable binding segment '%s' to '%s'. (OldPath: %s ---> New Path: %s)")
, *InOldVariableName.ToString()
, *InNewVariableName.ToString()
, *OldPath
, *InPath.ToString());
}
}
void USceneStateBlueprint::OnGraphParametersChanged(USceneStateMachineGraph* InGraph)
{
BindingCollection.ForEachMutableBinding(
[This=this, InGraph](FPropertyBindingBinding& InBinding)
{
This->UpdateGraphParametersBindings(InBinding.GetMutableSourcePath(), InGraph);
This->UpdateGraphParametersBindings(InBinding.GetMutableTargetPath(), InGraph);
});
}
void USceneStateBlueprint::UpdateGraphParametersBindings(FPropertyBindingPath& InPath, USceneStateMachineGraph* InGraph)
{
// Only consider fixing paths that are set to the graph parameters
if (InPath.GetStructID() != InGraph->ParametersId)
{
return;
}
TArrayView<FPropertyBindingPathSegment> Segments = InPath.GetMutableSegments();
if (Segments.IsEmpty())
{
return;
}
// Only need to consider the first segment of the path (i.e. the segment that might've been renamed)
FPropertyBindingPathSegment& Segment = Segments[0];
if (const FPropertyBagPropertyDesc* Property = InGraph->Parameters.FindPropertyDescByID(Segment.GetPropertyGuid()))
{
const FName SegmentName = Segment.GetName();
if (SegmentName != Property->Name)
{
if (GUndo)
{
Modify();
}
const FString OldPath = InPath.ToString();
Segment.SetName(Property->Name);
UE_LOG(LogSceneStateBlueprint, Log, TEXT("Renamed parameter variable binding segment '%s' to '%s'. (OldPath: %s ---> New Path: %s)")
, *SegmentName.ToString()
, *Property->Name.ToString()
, *OldPath
, *InPath.ToString());
}
}
}