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

391 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SceneStateMachineCompiler.h"
#include "Algo/Count.h"
#include "EdGraphUtilities.h"
#include "ISceneStateMachineCompilerContext.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Misc/EnumerateRange.h"
#include "Nodes/SceneStateMachineConduitNode.h"
#include "Nodes/SceneStateMachineEntryNode.h"
#include "Nodes/SceneStateMachineExitNode.h"
#include "Nodes/SceneStateMachineStateNode.h"
#include "Nodes/SceneStateMachineTransitionNode.h"
#include "Nodes/SceneStateTransitionResultNode.h"
#include "SceneState.h"
#include "SceneStateGeneratedClass.h"
#include "SceneStateMachine.h"
#include "SceneStateMachineConduitCompiler.h"
#include "SceneStateMachineGraph.h"
#include "SceneStateMachineTaskCompiler.h"
#include "SceneStateMachineTransitionCompiler.h"
#include "SceneStateTransitionGraph.h"
#include "SceneStateUtils.h"
#include "Tasks/SceneStateBlueprintableTask.h"
#include "UObject/UObjectThreadContext.h"
namespace UE::SceneState::Editor
{
namespace Private
{
// Converts Transition Parameters to map of absolute index to the parameter property bag, only if they're valid.
// A map is used because most likely there will be much more transitions without parameters than transitions with parameters.
TMap<uint16, FInstancedPropertyBag> ConvertParameterMap(FSceneStateRange InTransitionRange, TArrayView<FInstancedPropertyBag> InParametersList)
{
TMap<uint16, FInstancedPropertyBag> TransitionParameterMap;
TransitionParameterMap.Reserve(Algo::CountIf(InParametersList,
[](const FInstancedPropertyBag& InParameters)
{
return InParameters.IsValid();
}));
for (TEnumerateRef<FInstancedPropertyBag> Parameters : EnumerateRange(InParametersList))
{
if (Parameters->IsValid())
{
// Convert the index to absolute
const uint16 AbsoluteIndex = InTransitionRange.Index + Parameters.GetIndex();
TransitionParameterMap.Add(AbsoluteIndex, MoveTemp(*Parameters));
}
}
return TransitionParameterMap;
}
} // Private
FStateMachineCompiler::FStateMachineCompiler(USceneStateMachineGraph* InGraph, IStateMachineCompilerContext& InCompilerContext)
: StateMachineGraph(InGraph)
, Context(InCompilerContext)
{
GeneratedClass = InCompilerContext.GetGeneratedClass();
check(StateMachineGraph && GeneratedClass);
Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(InGraph);
}
FSceneStateMachine FStateMachineCompiler::Compile()
{
check(StateMachineGraph && GeneratedClass);
FSceneStateMachine StateMachine;
USceneStateMachineEntryNode* EntryNode = StateMachineGraph->GetEntryNode();
if (!EntryNode)
{
return StateMachine;
}
USceneStateMachineStateNode* EntryState = EntryNode->GetStateNode();
if (!EntryState)
{
return StateMachine;
}
StateNodesToProcess.Reset();
StateNodesToProcess.Emplace(EntryState);
StateMachine.EntryIndex = 0;
StateMachine.Parameters = StateMachineGraph->Parameters;
// Step #1: Compile States
while (!StateNodesToProcess.IsEmpty())
{
if (const USceneStateMachineStateNode* StateNode = StateNodesToProcess.Pop())
{
CompileState(StateNode);
}
}
// Step #2: Compile Conduits
for (const USceneStateMachineConduitNode* ConduitNode : ConduitNodesToCompile)
{
check(ConduitNodeIndexMap.Contains(ConduitNode));
CompileConduit(ConduitNode);
}
// Step #3: Compile State Transitions
// State array is filled with all the states, so they can be identified by indices now
for (const TPair<FObjectKey, uint16>& Pair : StateNodeIndexMap)
{
const USceneStateMachineStateNode* StateNode = CastChecked<USceneStateMachineStateNode>(Pair.Key.ResolveObjectPtr());
FSceneState& SceneState = States[Pair.Value];
CompileStateTransitions(SceneState, StateNode);
}
StateMachine.StateRange.Index = GeneratedClass->States.Num();
StateMachine.StateRange.Count = States.Num();
GeneratedClass->States.Append(MoveTemp(States));
GeneratedClass->StateMetadata.Append(MoveTemp(StateMetadata));
States.Reset();
StateMetadata.Reset();
// Upgrade the map to absolute indices before baking it to the generated class' map
ToAbsoluteIndexMap(StateNodeIndexMap, StateMachine.StateRange.Index);
GeneratedClass->StateNodeToIndex.Append(MoveTemp(StateNodeIndexMap));
StateMachine.ConduitRange.Index = GeneratedClass->Conduits.Num();
StateMachine.ConduitRange.Count = Conduits.Num();
GeneratedClass->Conduits.Append(MoveTemp(Conduits));
GeneratedClass->ConduitLinks.Append(MoveTemp(ConduitLinks));
Conduits.Reset();
ConduitLinks.Reset();
return StateMachine;
}
void FStateMachineCompiler::CompileState(const USceneStateMachineStateNode* InStateNode)
{
if (!InStateNode->IsNodeEnabled())
{
return;
}
if (StateNodeIndexMap.Contains(InStateNode))
{
return;
}
const int32 StateIndex = States.AddDefaulted();
const int32 StateMetadataIndex = StateMetadata.AddDefaulted();
check(StateIndex == StateMetadataIndex);
FSceneState& NewState = States[StateIndex];
FSceneStateMetadata& NewStateMetadata = StateMetadata[StateIndex];
NewStateMetadata.StateName = InStateNode->GetNodeName().ToString();
StateNodeIndexMap.Add(InStateNode, StateIndex);
CompileSubStateMachines(NewState, InStateNode);
CompileTasks(NewState, InStateNode);
CompileEventHandlers(NewState, InStateNode);
// Gather more states to process by following the linked transitions
const TArray<USceneStateMachineTransitionNode*> TransitionNodes = InStateNode->GatherTransitions(/*bInSortList*/true);
FollowTransitions(TransitionNodes);
}
void FStateMachineCompiler::FollowTransitions(TConstArrayView<USceneStateMachineTransitionNode*> InExitTransitions)
{
StateNodesToProcess.Reserve(StateNodesToProcess.Num() + InExitTransitions.Num());
for (USceneStateMachineTransitionNode* const TransitionNode : InExitTransitions)
{
check(TransitionNode);
const USceneStateMachineNode* TargetNode = TransitionNode->GetTargetNode();
if (const USceneStateMachineStateNode* TargetStateNode = Cast<USceneStateMachineStateNode>(TargetNode))
{
StateNodesToProcess.Add(TargetStateNode);
}
else if (const USceneStateMachineConduitNode* TargetConduitNode = Cast<USceneStateMachineConduitNode>(TargetNode))
{
AddConduitToCompile(TargetConduitNode);
}
}
}
void FStateMachineCompiler::CompileSubStateMachines(FSceneState& InNewState, const USceneStateMachineStateNode* InStateNode)
{
// Compile Sub State Machine Graphs into Runtime State Machines owned by this new State
TArray<UEdGraph*> SubGraphs = InStateNode->GetSubGraphs();
TArray<FSceneStateMachine> SubStateMachines;
SubStateMachines.Reserve(SubGraphs.Num());
TMap<FObjectKey, uint16> StateMachineGraphToIndex;
StateMachineGraphToIndex.Reserve(SubGraphs.Num());
for (UEdGraph* SubGraph : SubGraphs)
{
USceneStateMachineGraph* NewStateMachineGraph = Cast<USceneStateMachineGraph>(SubGraph);
if (!NewStateMachineGraph)
{
continue;
}
FStateMachineCompiler StateMachineCompiler(NewStateMachineGraph, Context);
FSceneStateMachine NewStateMachine = StateMachineCompiler.Compile();
if (NewStateMachine.IsValid())
{
StateMachineGraphToIndex.Emplace(NewStateMachineGraph, StateMachineGraphToIndex.Num());
SubStateMachines.Emplace(MoveTemp(NewStateMachine));
}
}
InNewState.StateMachineRange.Index = GeneratedClass->StateMachines.Num();
InNewState.StateMachineRange.Count = SubStateMachines.Num();
GeneratedClass->StateMachines.Append(MoveTemp(SubStateMachines));
// Upgrade the map to absolute indices before baking it to the generated class' map
ToAbsoluteIndexMap(StateMachineGraphToIndex, InNewState.StateMachineRange.Index);
GeneratedClass->StateMachineGraphToIndex.Append(MoveTemp(StateMachineGraphToIndex));
}
void FStateMachineCompiler::AddConduitToCompile(const USceneStateMachineConduitNode* InConduitNode)
{
if (!InConduitNode->IsNodeEnabled())
{
return;
}
// Conduit already added to nodes to process list
if (ConduitNodeIndexMap.Contains(InConduitNode))
{
return;
}
// No exit transitions, skip compile
TArray<USceneStateMachineTransitionNode*> ConduitTransitions = InConduitNode->GatherTransitions(/*bInSortList*/true);
if (ConduitTransitions.IsEmpty())
{
return;
}
// Add defaulted conduit for now for indexing/discovery.
const int32 ConduitIndex = ConduitNodesToCompile.Add(InConduitNode);
ConduitNodeIndexMap.Add(InConduitNode, ConduitIndex);
// Gather more nodes to process by following the exit transitions of the conduit
FollowTransitions(ConduitTransitions);
}
void FStateMachineCompiler::CompileTasks(FSceneState& InNewState, const USceneStateMachineStateNode* InStateNode)
{
FStateMachineTaskCompiler::FCompileResult TaskCompileResult;
{
FStateMachineTaskCompiler TaskCompiler(InStateNode->GetTaskPin(), /*Outer*/GeneratedClass);
TaskCompiler.Compile(TaskCompileResult);
}
InNewState.TaskRange.Index = GeneratedClass->Tasks.Num();
InNewState.TaskRange.Count = TaskCompileResult.Tasks.Num();
// Convert the prerequisite range from relative to absolute
const uint16 TaskPrerequisiteIndex = GeneratedClass->TaskPrerequisites.Num();
for (FInstancedStruct& Task : TaskCompileResult.Tasks)
{
Task.GetMutable<FSceneStateTask>().PrerequisiteRange.Index += TaskPrerequisiteIndex;
}
// todo: bigger chunks to reduce FInstancedStructContainer realloc
GeneratedClass->Tasks.Append(MoveTemp(TaskCompileResult.Tasks));
GeneratedClass->TaskInstances.Append(TaskCompileResult.TaskInstances);
GeneratedClass->TaskMetadata.Append(MoveTemp(TaskCompileResult.TaskMetadata));
GeneratedClass->TaskPrerequisites.Append(MoveTemp(TaskCompileResult.TaskPrerequisites));
InNewState.InstanceTaskObjects(GeneratedClass
, GetStructViews(GeneratedClass->TaskInstances, InNewState.TaskRange)
, TaskCompileResult.TaskInstances
, [this](FObjectDuplicationParameters& InParams)->UObject*
{
return this->DuplicateObject(InParams);
});
ToAbsoluteIndexMap(TaskCompileResult.TaskToIndexMap, InNewState.TaskRange.Index);
GeneratedClass->TaskNodeToIndex.Append(MoveTemp(TaskCompileResult.TaskToIndexMap));
}
void FStateMachineCompiler::CompileEventHandlers(FSceneState& InNewState, const USceneStateMachineStateNode* InStateNode)
{
InNewState.EventHandlerRange.Index = GeneratedClass->EventHandlers.Num();
InNewState.EventHandlerRange.Count = InStateNode->EventHandlers.Num();
GeneratedClass->EventHandlers.Append(InStateNode->EventHandlers);
}
void FStateMachineCompiler::FinishTransitionCompilation(FSceneStateRange& OutTransitionRange, FStateMachineTransitionCompileResult&& InCompileResult)
{
OutTransitionRange.Index = GeneratedClass->Transitions.Num();
OutTransitionRange.Count = InCompileResult.Transitions.Num();
GeneratedClass->Transitions.Append(MoveTemp(InCompileResult.Transitions));
GeneratedClass->TransitionLinks.Append(MoveTemp(InCompileResult.Links));
GeneratedClass->TransitionMetadata.Append(MoveTemp(InCompileResult.Metadata));
GeneratedClass->TransitionParameters.Append(Private::ConvertParameterMap(OutTransitionRange, InCompileResult.Parameters));
}
void FStateMachineCompiler::CompileStateTransitions(FSceneState& InNewState, const USceneStateMachineStateNode* InStateNode)
{
FStateMachineTransitionCompileResult TransitionCompileResult;
const FStateMachineTransitionCompiler::FCompileParams CompileParams
{
.Context = Context,
.Node = InStateNode,
.StateNodeIndexMap = StateNodeIndexMap,
.ConduitNodeIndexMap = ConduitNodeIndexMap,
};
FStateMachineTransitionCompiler TransitionCompiler(CompileParams);
TransitionCompiler.Compile(TransitionCompileResult);
FinishTransitionCompilation(InNewState.TransitionRange, MoveTemp(TransitionCompileResult));
}
void FStateMachineCompiler::CompileConduit(const USceneStateMachineConduitNode* InConduitNode)
{
FStateMachineConduitCompileResult ConduitCompileResult;
const FStateMachineConduitCompiler::FCompileParams CompileParams
{
.Context = Context,
.ConduitNode = InConduitNode,
.StateNodeIndexMap = StateNodeIndexMap,
.ConduitNodeIndexMap = ConduitNodeIndexMap,
};
FStateMachineConduitCompiler ConduitCompiler(CompileParams);
if (!ConduitCompiler.Compile(ConduitCompileResult))
{
return;
}
const uint16 ConduitIndex = ConduitNodeIndexMap[InConduitNode];
Conduits.Insert(MoveTemp(ConduitCompileResult.Conduit), ConduitIndex);
ConduitLinks.Insert(MoveTemp(ConduitCompileResult.ConduitLink), ConduitIndex);
FinishTransitionCompilation(Conduits[ConduitIndex].TransitionRange, MoveTemp(ConduitCompileResult.TransitionCompileResult));
}
UObject* FStateMachineCompiler::DuplicateObject(FObjectDuplicationParameters& InDuplicationParams)
{
TMap<UObject*, UObject*> DuplicationMap;
// if recompiling the BP on load, skip post load and defer it to the loading process
FUObjectSerializeContext* LinkerLoadingContext = nullptr;
if (Blueprint && Blueprint->bIsRegeneratingOnLoad)
{
if (FLinkerLoad* Linker = Blueprint->GetLinker())
{
LinkerLoadingContext = FUObjectThreadContext::Get().GetSerializeContext();
}
InDuplicationParams.bSkipPostLoad = true;
InDuplicationParams.CreatedObjects = &DuplicationMap;
}
UObject* DuplicateObject = StaticDuplicateObjectEx(InDuplicationParams);
// if we have anything in here after duplicate, then hook them in the loading process so they get post loaded
if (LinkerLoadingContext)
{
TArray<UObject*> DupObjects;
DuplicationMap.GenerateValueArray(DupObjects);
LinkerLoadingContext->AddUniqueLoadedObjects(DupObjects);
}
return DuplicateObject;
}
} // UE::SceneState::Editor