You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#jira UE-222447 #rb Yoan.StAmant #p4v-cherrypick 36157962 [CL 36331606 by patrick boutot in 5.5 branch]
337 lines
9.6 KiB
C++
337 lines
9.6 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "StateTreeEditingSubsystem.h"
|
|
|
|
#include "SStateTreeView.h"
|
|
#include "StateTreeCompiler.h"
|
|
#include "StateTreeCompilerLog.h"
|
|
#include "StateTreeDelegates.h"
|
|
#include "StateTreeEditorData.h"
|
|
#include "StateTreeEditorModule.h"
|
|
#include "StateTreeObjectHash.h"
|
|
#include "StateTreeTaskBase.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
|
|
|
|
UStateTreeEditingSubsystem::UStateTreeEditingSubsystem()
|
|
{
|
|
PostGarbageCollectHandle = FCoreUObjectDelegates::GetPostGarbageCollect().AddUObject(this, &UStateTreeEditingSubsystem::HandlePostGarbageCollect);
|
|
}
|
|
|
|
void UStateTreeEditingSubsystem::BeginDestroy()
|
|
{
|
|
FCoreUObjectDelegates::GetPostGarbageCollect().Remove(PostGarbageCollectHandle);
|
|
Super::BeginDestroy();
|
|
}
|
|
|
|
bool UStateTreeEditingSubsystem::CompileStateTree(const gsl::not_null<UStateTree*> InStateTree, FStateTreeCompilerLog& InOutLog)
|
|
{
|
|
ValidateStateTree(InStateTree);
|
|
const uint32 EditorDataHash = CalculateStateTreeHash(InStateTree);
|
|
|
|
FStateTreeCompiler Compiler(InOutLog);
|
|
|
|
const bool bCompilationResult = Compiler.Compile(*InStateTree);
|
|
if(bCompilationResult)
|
|
{
|
|
// Success
|
|
InStateTree->LastCompiledEditorDataHash = EditorDataHash;
|
|
UE::StateTree::Delegates::OnPostCompile.Broadcast(*InStateTree);
|
|
UE_LOG(LogStateTreeEditor, Log, TEXT("Compile StateTree '%s' succeeded."), *InStateTree->GetFullName());
|
|
}
|
|
else
|
|
{
|
|
// Make sure not to leave stale data on failed compile.
|
|
InStateTree->ResetCompiled();
|
|
InStateTree->LastCompiledEditorDataHash = 0;
|
|
|
|
UE_LOG(LogStateTreeEditor, Error, TEXT("Failed to compile '%s', errors follow."), *InStateTree->GetFullName());
|
|
InOutLog.DumpToLog(LogStateTreeEditor);
|
|
}
|
|
|
|
return bCompilationResult;
|
|
}
|
|
|
|
TSharedRef<FStateTreeViewModel> UStateTreeEditingSubsystem::FindOrAddViewModel(const gsl::not_null<UStateTree*> InStateTree)
|
|
{
|
|
const FObjectKey StateTreeKey = InStateTree;
|
|
TSharedPtr<FStateTreeViewModel> ViewModelPtr = StateTreeViewModels.FindRef(StateTreeKey);
|
|
if (ViewModelPtr)
|
|
{
|
|
// The StateTree could be re-instantiated. Can occur when the object is destroyed and recreated in a pool or when reloaded in editor.
|
|
//The object might have the same pointer value or the same path but it's a new object and all weakptr are now invalid.
|
|
if (ViewModelPtr->GetStateTree() == InStateTree)
|
|
{
|
|
return ViewModelPtr.ToSharedRef();
|
|
}
|
|
else
|
|
{
|
|
StateTreeViewModels.Remove(StateTreeKey);
|
|
ViewModelPtr = nullptr;
|
|
}
|
|
}
|
|
|
|
TSharedRef<FStateTreeViewModel> SharedModel = StateTreeViewModels.Add(StateTreeKey, MakeShared<FStateTreeViewModel>()).ToSharedRef();
|
|
UStateTreeEditorData* EditorData = Cast<UStateTreeEditorData>(InStateTree->EditorData);
|
|
if (EditorData == nullptr)
|
|
{
|
|
EditorData = NewObject<UStateTreeEditorData>(InStateTree, FName(), RF_Transactional);
|
|
EditorData->AddRootState();
|
|
InStateTree->EditorData = EditorData;
|
|
|
|
FStateTreeCompilerLog Log;
|
|
CompileStateTree(InStateTree, Log);
|
|
}
|
|
|
|
for (UStateTreeState* SubTree : EditorData->SubTrees)
|
|
{
|
|
TArray<UStateTreeState*> Stack;
|
|
|
|
Stack.Add(SubTree);
|
|
while (!Stack.IsEmpty())
|
|
{
|
|
if (UStateTreeState* State = Stack.Pop())
|
|
{
|
|
State->SetFlags(RF_Transactional);
|
|
|
|
for (UStateTreeState* ChildState : State->Children)
|
|
{
|
|
Stack.Add(ChildState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SharedModel->Init(EditorData);
|
|
|
|
return SharedModel;
|
|
}
|
|
|
|
TSharedRef<SWidget> UStateTreeEditingSubsystem::GetStateTreeView(TSharedRef<FStateTreeViewModel> InViewModel, const TSharedRef<FUICommandList>& TreeViewCommandList)
|
|
{
|
|
return SNew(SStateTreeView, InViewModel, TreeViewCommandList);
|
|
}
|
|
|
|
void UStateTreeEditingSubsystem::ValidateStateTree(const gsl::not_null<UStateTree*> InStateTree)
|
|
{
|
|
auto FixChangedStateLinkName = [](FStateTreeStateLink& StateLink, const TMap<FGuid, FName>& IDToName) -> bool
|
|
{
|
|
if (StateLink.ID.IsValid())
|
|
{
|
|
const FName* Name = IDToName.Find(StateLink.ID);
|
|
if (Name == nullptr)
|
|
{
|
|
// Missing link, we'll show these in the UI
|
|
return false;
|
|
}
|
|
if (StateLink.Name != *Name)
|
|
{
|
|
// Name changed, fix!
|
|
StateLink.Name = *Name;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
auto ValidateLinkedStates = [FixChangedStateLinkName](const UStateTree& StateTree)
|
|
{
|
|
UStateTreeEditorData* TreeData = Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
if (!TreeData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
constexpr bool bMarkDirty = false;
|
|
TreeData->Modify(bMarkDirty);
|
|
|
|
// Make sure all state links are valid and update the names if needed.
|
|
|
|
// Create ID to state name map.
|
|
TMap<FGuid, FName> IDToName;
|
|
|
|
TreeData->VisitHierarchy([&IDToName](const UStateTreeState& State, UStateTreeState* /*ParentState*/)
|
|
{
|
|
IDToName.Add(State.ID, State.Name);
|
|
return EStateTreeVisitor::Continue;
|
|
});
|
|
|
|
// Fix changed names.
|
|
TreeData->VisitHierarchy([&IDToName, FixChangedStateLinkName](UStateTreeState& State, UStateTreeState* /*ParentState*/)
|
|
{
|
|
constexpr bool bMarkDirty = false;
|
|
State.Modify(bMarkDirty);
|
|
if (State.Type == EStateTreeStateType::Linked)
|
|
{
|
|
FixChangedStateLinkName(State.LinkedSubtree, IDToName);
|
|
}
|
|
|
|
for (FStateTreeTransition& Transition : State.Transitions)
|
|
{
|
|
FixChangedStateLinkName(Transition.State, IDToName);
|
|
}
|
|
|
|
return EStateTreeVisitor::Continue;
|
|
});
|
|
};
|
|
|
|
auto UpdateParents = [](const UStateTree& StateTree)
|
|
{
|
|
UStateTreeEditorData* TreeData = Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
if (!TreeData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
constexpr bool bMarkDirty = false;
|
|
TreeData->Modify(bMarkDirty);
|
|
TreeData->ReparentStates();
|
|
};
|
|
|
|
auto ApplySchema =[](const UStateTree& StateTree)
|
|
{
|
|
UStateTreeEditorData* TreeData = Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
if (!TreeData)
|
|
{
|
|
return;
|
|
|
|
}
|
|
const UStateTreeSchema* Schema = TreeData->Schema;
|
|
if (!Schema)
|
|
{
|
|
return;
|
|
}
|
|
|
|
constexpr bool bMarkDirty = false;
|
|
TreeData->Modify(bMarkDirty);
|
|
|
|
// Clear evaluators if not allowed.
|
|
if (Schema->AllowEvaluators() == false && TreeData->Evaluators.Num() > 0)
|
|
{
|
|
UE_LOG(LogStateTreeEditor, Warning, TEXT("%s: Resetting Evaluators due to current schema restrictions."), *GetNameSafe(&StateTree));
|
|
TreeData->Evaluators.Reset();
|
|
}
|
|
|
|
|
|
TreeData->VisitHierarchy([&StateTree, Schema](UStateTreeState& State, UStateTreeState* /*ParentState*/)
|
|
{
|
|
constexpr bool bMarkDirty = false;
|
|
State.Modify(bMarkDirty);
|
|
|
|
// Clear enter conditions if not allowed.
|
|
if (Schema->AllowEnterConditions() == false && State.EnterConditions.Num() > 0)
|
|
{
|
|
UE_LOG(LogStateTreeEditor, Warning, TEXT("%s: Resetting Enter Conditions in state %s due to current schema restrictions."), *GetNameSafe(&StateTree), *GetNameSafe(&State));
|
|
State.EnterConditions.Reset();
|
|
}
|
|
|
|
// Clear Utility if not allowed
|
|
if (Schema->AllowUtilityConsiderations() == false && State.Considerations.Num() > 0)
|
|
{
|
|
UE_LOG(LogStateTreeEditor, Warning, TEXT("%s: Resetting Utility Considerations in state %s due to current schema restrictions."), *GetNameSafe(&StateTree), *GetNameSafe(&State));
|
|
State.Considerations.Reset();
|
|
}
|
|
|
|
// Keep single and many tasks based on what is allowed.
|
|
if (Schema->AllowMultipleTasks() == false)
|
|
{
|
|
if (State.Tasks.Num() > 0)
|
|
{
|
|
State.Tasks.Reset();
|
|
UE_LOG(LogStateTreeEditor, Warning, TEXT("%s: Resetting Tasks in state %s due to current schema restrictions."), *GetNameSafe(&StateTree), *GetNameSafe(&State));
|
|
}
|
|
|
|
// Task name is the same as state name.
|
|
if (FStateTreeTaskBase* Task = State.SingleTask.Node.GetMutablePtr<FStateTreeTaskBase>())
|
|
{
|
|
Task->Name = State.Name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (State.SingleTask.Node.IsValid())
|
|
{
|
|
State.SingleTask.Reset();
|
|
UE_LOG(LogStateTreeEditor, Warning, TEXT("%s: Resetting Single Task in state %s due to current schema restrictions."), *GetNameSafe(&StateTree), *GetNameSafe(&State));
|
|
}
|
|
}
|
|
|
|
return EStateTreeVisitor::Continue;
|
|
});
|
|
};
|
|
|
|
|
|
auto RemoveUnusedBindings = [](const UStateTree& StateTree)
|
|
{
|
|
UStateTreeEditorData* TreeData = Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
if (!TreeData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TMap<FGuid, const FStateTreeDataView> AllStructValues;
|
|
TreeData->GetAllStructValues(AllStructValues);
|
|
constexpr bool bMarkDirty = false;
|
|
TreeData->Modify(bMarkDirty);
|
|
TreeData->GetPropertyEditorBindings()->RemoveUnusedBindings(AllStructValues);
|
|
};
|
|
|
|
auto UpdateLinkedStateParameters = [](const UStateTree& StateTree)
|
|
{
|
|
UStateTreeEditorData* TreeData = Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
if (!TreeData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
constexpr bool bMarkDirty = false;
|
|
TreeData->Modify(bMarkDirty);
|
|
|
|
const EStateTreeVisitor Result = TreeData->VisitHierarchy([](UStateTreeState& State, UStateTreeState* /*ParentState*/)
|
|
{
|
|
if (State.Type == EStateTreeStateType::Linked
|
|
|| State.Type == EStateTreeStateType::LinkedAsset)
|
|
{
|
|
State.Modify();
|
|
State.UpdateParametersFromLinkedSubtree();
|
|
}
|
|
return EStateTreeVisitor::Continue;
|
|
});
|
|
};
|
|
|
|
|
|
UpdateParents(*InStateTree);
|
|
ApplySchema(*InStateTree);
|
|
RemoveUnusedBindings(*InStateTree);
|
|
ValidateLinkedStates(*InStateTree);
|
|
UpdateLinkedStateParameters(*InStateTree);
|
|
}
|
|
|
|
uint32 UStateTreeEditingSubsystem::CalculateStateTreeHash(const gsl::not_null<const UStateTree*> InStateTree)
|
|
{
|
|
uint32 EditorDataHash = 0;
|
|
if (InStateTree->EditorData != nullptr)
|
|
{
|
|
FStateTreeObjectCRC32 Archive;
|
|
EditorDataHash = Archive.Crc32(InStateTree->EditorData, 0);
|
|
}
|
|
|
|
return EditorDataHash;
|
|
}
|
|
|
|
void UStateTreeEditingSubsystem::HandlePostGarbageCollect()
|
|
{
|
|
// Remove the stale viewmodels
|
|
for (TMap<FObjectKey, TSharedPtr<FStateTreeViewModel>>::TIterator It(StateTreeViewModels); It; ++It)
|
|
{
|
|
if (!It.Key().ResolveObjectPtr())
|
|
{
|
|
It.RemoveCurrent();
|
|
}
|
|
else if (!It.Value() || !It.Value()->GetStateTree())
|
|
{
|
|
It.RemoveCurrent();
|
|
}
|
|
}
|
|
}
|