// 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 InClass) const { const TObjectPtr* 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>& 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> 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> 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> BindingDescs; GetTaskBindingDescs(*this, *TargetTaskNode, BindingDescs); OutStructDescs.Append(MoveTemp(BindingDescs)); } } bool USceneStateBlueprint::GetBindableStructByID(const FGuid InStructId, TInstancedStruct& OutStructDesc) const { using namespace UE::SceneState::Graph; TInstancedStruct 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 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& OutAllowedChildrenOfClasses, TSet& 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 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 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()); } } }