Files
UnrealEngineUWP/Engine/Plugins/Runtime/StateTree/Source/StateTreeModule/Private/StateTreeReference.cpp
Yoan StAmant c135ebcd95 [StateTree]
+ Added StateTree parameters usable for data bindings and that could be overriden on context initialization by a StateTreeReference
+ Added StateTreeReference struct to hold a reference to a StateTree asset along with a list of values to parameterized the tree.
+ Added named external data items that are defined bt the Schema and for which values must be provided at runtime through the execution context.
+ Added delegate OnPostCompile after successful compilation. The StateTreeReference listens to it to validate its parameters
+ EditorData now contains its own version of the schema and parameters. On successful compilation they are copied over the StateTree own properties.
#rnx
#rb mikko.mononen
#preflight 6255d281647ad886b3593cb0

[CL 19727363 by Yoan StAmant in ue5-main branch]
2022-04-12 15:55:39 -04:00

144 lines
4.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StateTreeReference.h"
#include "StateTree.h"
#include "StateTreeDelegates.h"
#if WITH_EDITOR
#include "Editor.h"
#endif
#if WITH_EDITOR
void UStateTreeReferenceWrapper::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
UObject::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.MemberProperty && PropertyChangedEvent.Property)
{
if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FStateTreeReference, StateTree))
{
StateTreeReference.SyncParameters();
}
}
}
void UStateTreeReferenceWrapper::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
UObject::PostEditChangeChainProperty(PropertyChangedEvent);
}
void UStateTreeReferenceWrapper::PostInitProperties()
{
UObject::PostInitProperties();
// Registers a delegate to be notified when the associated StateTree asset get successfully recompiled
// to make sure that the parameters in the StateTreeReference are still valid.
PostCompileHandle = UE::StateTree::Delegates::OnPostCompile.AddLambda([this](const UStateTree& InStateTree)
{
if (StateTreeReference.StateTree == &InStateTree)
{
if (StateTreeReference.SyncParameters())
{
MarkPackageDirty();
}
}
});
// Make sure any modifications to the parameters since the last sync (PostLoad) won't cause runtime issues.
// This shouldn't be necessary if only parameters values could be modified but until that is enforce this will
// prevent parameters inconsistencies at runtime.
PIEHandle = FEditorDelegates::PreBeginPIE.AddLambda([this](const bool bBegan)
{
const bool bModified = StateTreeReference.SyncParameters();
UE_CLOG(bModified, LogStateTree, Warning, TEXT("Parameters for StateTree '%s' stored in %s were auto-fixed to be usable at runtime."),
*GetNameSafe(StateTreeReference.StateTree), *GetName());
});
}
void UStateTreeReferenceWrapper::PostLoad()
{
UObject::PostLoad();
// This might modify the object but we don't want to dirty on load
StateTreeReference.SyncParameters();
}
void UStateTreeReferenceWrapper::BeginDestroy()
{
UObject::BeginDestroy();
// Unregister all our delegates
UE::StateTree::Delegates::OnPostCompile.Remove(PostCompileHandle);
FEditorDelegates::PreBeginPIE.Remove(PIEHandle);
}
bool FStateTreeReference::SyncParameters()
{
TArray<FStateTreeParameterDesc>& Params = Parameters.Parameters;
if (StateTree == nullptr)
{
const bool bHadParameters = !Params.IsEmpty();
Params.Reset();
return bHadParameters;
}
// First implementation is to make sure that the StateTreeReference holds values for each parameter of the StateTree.
// This is the easiest way to expose them to the user but that also means that user can't specify only a subset of parameters
// and use default value for the others.
const TConstArrayView<FStateTreeParameterDesc> AssetParameters = StateTree ? StateTree->GetParameterDescs() : TConstArrayView<FStateTreeParameterDesc>();
bool bAreParametersSynced = AssetParameters.Num() == Params.Num();
if (bAreParametersSynced)
{
for (int32 i = 0; i < Params.Num(); ++i)
{
if (!Params[i].IsMatching(AssetParameters[i]))
{
// Ok to mismatch only by the name if both ID and Type match. Simply update it since
// it must also match when passed to the ExecutionContext at runtime.
if (Params[i].ID == AssetParameters[i].ID && Params[i].IsSameType(AssetParameters[i]))
{
UE_LOG(LogStateTree, Warning, TEXT("StateTree parameter '%s' name was updated to match StateTree parameter '%s'."),
*LexToString(Params[i]), *LexToString(AssetParameters[i]));
Params[i].Name = AssetParameters[i].Name;
checkf(Params[i].IsMatching(AssetParameters[i]),
TEXT("After fixing the name the parameters should match."
" If this fails it indicates that same ID+Type is no longer the only required condition."));
}
else
{
bAreParametersSynced = false;
break;
}
}
}
}
if (!bAreParametersSynced)
{
TArray<FStateTreeParameterDesc> DeprecatedParameters = MoveTemp(Params);
Params = AssetParameters;
for (int32 i = 0; i < Params.Num(); ++i)
{
FStateTreeParameterDesc& ParameterDesc = Params[i];
// Find parameter corresponding index in the deprecated parameters using 'Type' and 'Id'.
// Not using IsMatching() here since 'Name' might have change in addition to parameters added/removed since the last sync.
const int32 IndexInDeprecated = DeprecatedParameters.IndexOfByPredicate([&ParameterDesc](const FStateTreeParameterDesc& DeprecatedDesc)
{
return DeprecatedDesc.ID == ParameterDesc.ID && DeprecatedDesc.IsSameType(ParameterDesc);
});
if (IndexInDeprecated != INDEX_NONE)
{
// Only update value
ParameterDesc.Parameter = DeprecatedParameters[IndexInDeprecated].Parameter;
DeprecatedParameters.RemoveAtSwap(IndexInDeprecated);
}
}
}
return !bAreParametersSynced;
}
#endif // WITH_EDITOR