Files
2025-05-18 13:04:45 +08:00

1215 lines
47 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UncookedOnlyUtils.h"
#include "K2Node_CallFunction.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Compilation/AnimNextGetVariableCompileContext.h"
#include "Module/AnimNextModule.h"
#include "Module/AnimNextModule_EditorData.h"
#include "Serialization/MemoryReader.h"
#include "RigVMCore/RigVM.h"
#include "AnimNextRigVMAsset.h"
#include "AnimNextRigVMAssetEditorData.h"
#include "Entries/AnimNextRigVMAssetEntry.h"
#include "AnimNextUncookedOnlyModule.h"
#include "IAnimNextRigVMExportInterface.h"
#include "Logging/StructuredLog.h"
#include "AnimNextAssetWorkspaceAssetUserData.h"
#include "IWorkspaceEditor.h"
#include "IWorkspaceEditorModule.h"
#include "Misc/EnumerateRange.h"
#include "Module/RigUnit_AnimNextModuleEvents.h"
#include "Variables/AnimNextProgrammaticVariable.h"
#include "Variables/IVariableBindingType.h"
#include "Variables/RigUnit_CopyModuleProxyVariables.h"
#include "WorkspaceAssetRegistryInfo.h"
#include "Entries/AnimNextDataInterfaceEntry.h"
#include "DataInterface/AnimNextDataInterface.h"
#include "DataInterface/AnimNextDataInterface_EditorData.h"
#include "Logging/MessageLog.h"
#include "RigVMFunctions/Execution/RigVMFunction_UserDefinedEvent.h"
#include "UObject/AssetRegistryTagsContext.h"
#define LOCTEXT_NAMESPACE "AnimNextUncookedOnlyUtils"
namespace UE::AnimNext::UncookedOnly
{
TAutoConsoleVariable<bool> CVarDumpProgrammaticGraphs(
TEXT("AnimNext.DumpProgrammaticGraphs"),
false,
TEXT("When true the transient programmatic graphs will be automatically opened for any that are generated."));
void FUtils::RecreateVM(UAnimNextRigVMAsset* InAsset)
{
if (InAsset->VM == nullptr)
{
InAsset->VM = NewObject<URigVM>(InAsset, TEXT("VM"), RF_NoFlags);
}
InAsset->VM->Reset(InAsset->ExtendedExecuteContext);
InAsset->RigVM = InAsset->VM; // Local serialization
}
void FUtils::CompileVariables(const FRigVMCompileSettings& InSettings, UAnimNextRigVMAsset* InAsset, FAnimNextGetVariableCompileContext& OutCompileContext)
{
check(InAsset);
UAnimNextDataInterface* DataInterface = Cast<UAnimNextDataInterface>(InAsset);
if(DataInterface == nullptr)
{
// Currently only support data interface types (TODO: could make UAnimNextDataInterface the common base rather than UAnimNextRigVMAsset)
return;
}
FMessageLog Log("AnimNextCompilerResults");
UAnimNextDataInterface_EditorData* EditorData = GetEditorData<UAnimNextDataInterface_EditorData>(DataInterface);
// Gather programmatic variables regenerated each compile
EditorData->OnPreCompileGetProgrammaticVariables(InSettings, OutCompileContext);
const TArray<FAnimNextProgrammaticVariable>& ProgrammaticVariables = OutCompileContext.GetProgrammaticVariables();
struct FStructEntryInfo
{
const UAnimNextDataInterface* FromDataInterface = nullptr;
const UScriptStruct* NativeInterface = nullptr;
FName Name;
FAnimNextParamType Type;
EAnimNextExportAccessSpecifier AccessSpecifier = EAnimNextExportAccessSpecifier::Private;
bool bAutoBindDataInterfaceToHost = false;
TConstArrayView<uint8> Value;
EPropertyFlags PropertyFlags = CPF_Edit;
};
// Gather all variables in this asset.
// Variables are harvested from the valid entries and data interfaces.
// Data interface harvesting is performed recursively
// The topmost value for a data interface 'wins' if a value is to be supplied
TMap<FName, int32> EntryInfoIndexMap;
TArray<FStructEntryInfo> StructEntryInfos;
StructEntryInfos.Reserve(EditorData->Entries.Num() + ProgrammaticVariables.Num());
int32 NumPublicVariables = 0;
auto AddVariable = [&Log, &NumPublicVariables, &StructEntryInfos, &EntryInfoIndexMap](const UAnimNextVariableEntry* InVariable, const UAnimNextDataInterfaceEntry* InFromInterfaceEntry, const UAnimNextDataInterface* InFromInterface, bool bInAutoBindInterface)
{
const FName Name = InVariable->GetExportName();
const FAnimNextParamType& Type = InVariable->GetExportType();
if(!Type.IsValid())
{
Log.Error(FText::Format(LOCTEXT("InvalidVariableTypeFound", "Variable '{0}' with invalid type found"), FText::FromName(Name)));
return;
}
const EAnimNextExportAccessSpecifier AccessSpecifier = InVariable->GetExportAccessSpecifier();
// Check for type conflicts
int32* ExistingIndexPtr = EntryInfoIndexMap.Find(Name);
if(ExistingIndexPtr)
{
const FStructEntryInfo& ExistingInfo = StructEntryInfos[*ExistingIndexPtr];
if(ExistingInfo.Type != Type)
{
Log.Error(FText::Format(LOCTEXT("ConflictingVariableTypeFound", "Variable '{0}' with conflicting type found ({1} vs {2})"), FText::FromName(Name), FText::FromString(ExistingInfo.Type.ToString()), FText::FromString(Type.ToString())));
return;
}
if(ExistingInfo.AccessSpecifier != AccessSpecifier)
{
Log.Error(FText::Format(LOCTEXT("ConflictingVariableAccessFound", "Variable '{0}' with conflicting access specifier found ({1} vs {2})"), FText::FromName(Name), FText::FromString(UEnum::GetValueAsString(ExistingInfo.AccessSpecifier)), FText::FromString(UEnum::GetValueAsString(AccessSpecifier))));
return;
}
}
else
{
if(AccessSpecifier == EAnimNextExportAccessSpecifier::Public)
{
NumPublicVariables++;
}
}
// Check the overrides to see if this variable's default is overriden
const FProperty* OverrideProperty = nullptr;
TConstArrayView<uint8> OverrideValue;
if(InFromInterfaceEntry != nullptr)
{
InFromInterfaceEntry->FindValueOverrideRecursive(Name, OverrideProperty, OverrideValue);
}
TConstArrayView<uint8> Value;
if(!OverrideValue.IsEmpty())
{
Value = OverrideValue;
}
else
{
Value = TConstArrayView<uint8>(InVariable->GetValuePtr(), Type.GetSize());
}
if(ExistingIndexPtr)
{
// Found a variable of the same name/type, overwrite its value
StructEntryInfos[*ExistingIndexPtr].Value = Value;
}
else
{
// This is a new variable, check if it belongs to a native interface
const UAnimNextDataInterface_EditorData* FromInterfaceEditorData = GetEditorData<const UAnimNextDataInterface_EditorData>(InFromInterface);
check(FromInterfaceEditorData != nullptr);
bool bFound = false;
for (const UScriptStruct* NativeInterfaceStruct : FromInterfaceEditorData->NativeInterfaces)
{
if (NativeInterfaceStruct != nullptr && NativeInterfaceStruct->FindPropertyByName(Name) != nullptr)
{
// Found it
int32 Index = StructEntryInfos.Add(
{
InFromInterface,
NativeInterfaceStruct,
Name,
FAnimNextParamType(Type.GetValueType(), Type.GetContainerType(), Type.GetValueTypeObject()),
AccessSpecifier,
bInAutoBindInterface,
Value,
CPF_Edit
});
EntryInfoIndexMap.Add(Name, Index);
bFound = true;
break;
}
}
if (!bFound)
{
// Legacy codepath
int32 Index = StructEntryInfos.Add(
{
InFromInterface,
nullptr,
Name,
FAnimNextParamType(Type.GetValueType(), Type.GetContainerType(), Type.GetValueTypeObject()),
AccessSpecifier,
bInAutoBindInterface,
Value,
CPF_Edit
});
EntryInfoIndexMap.Add(Name, Index);
}
}
};
auto AddDataInterface = [&Log, &AddVariable, &DataInterface](const UAnimNextDataInterface* InDataInterface, const UAnimNextDataInterfaceEntry* InDataInterfaceEntry, bool bInPublicOnly, bool bInAutoBindInterface, auto& InAddDataInterface) -> void
{
const UAnimNextDataInterface_EditorData* DataInterfaceEditorData = GetEditorData<const UAnimNextDataInterface_EditorData>(InDataInterface);
check(DataInterfaceEditorData != nullptr);
for(UAnimNextRigVMAssetEntry* OtherEntry : DataInterfaceEditorData->Entries)
{
if(const UAnimNextVariableEntry* VariableEntry = Cast<UAnimNextVariableEntry>(OtherEntry))
{
if(!bInPublicOnly || VariableEntry->GetExportAccessSpecifier() == EAnimNextExportAccessSpecifier::Public)
{
AddVariable(VariableEntry, InDataInterfaceEntry, InDataInterface, bInAutoBindInterface);
}
}
else if(const UAnimNextDataInterfaceEntry* DataInterfaceEntry = Cast<UAnimNextDataInterfaceEntry>(OtherEntry))
{
UAnimNextDataInterface* SubDataInterface = DataInterfaceEntry->GetDataInterface();
if(SubDataInterface == nullptr)
{
Log.Error(FText::Format(LOCTEXT("MissingDataInterfaceWarning", "Invalid data interface found: {0}"), FText::FromString(DataInterfaceEntry->GetDataInterfacePath().ToString())));
return;
}
else if(DataInterface == SubDataInterface)
{
Log.Error(FText::Format(LOCTEXT("CircularDataInterfaceRefError", "Circular data interface reference found: {0}"), FText::FromString(DataInterfaceEntry->GetDataInterfacePath().ToString())));
return;
}
else
{
bool bAutoBindInterface = DataInterfaceEntry->AutomaticBinding == EAnimNextDataInterfaceAutomaticBindingMode::BindSharedInterfaces;
InAddDataInterface(SubDataInterface, DataInterfaceEntry, true, bAutoBindInterface, InAddDataInterface);
}
}
}
};
AddDataInterface(DataInterface, nullptr, false, true, AddDataInterface);
auto AddProgrammaticVariable = [&Log, &StructEntryInfos, &EntryInfoIndexMap](const FAnimNextProgrammaticVariable& ProgrammaticVariable)
{
const FName Name = ProgrammaticVariable.Name;
const FAnimNextParamType& Type = ProgrammaticVariable.Type;
if(!Type.IsValid())
{
Log.Error(FText::Format(LOCTEXT("InvalidProgrammaticVariableTypeFound", "Programmatic Variable '{0}' with invalid type found"), FText::FromName(Name)));
return;
}
// Check for type conflicts
int32* ExistingIndexPtr = EntryInfoIndexMap.Find(Name);
if(ExistingIndexPtr)
{
Log.Error(FText::Format(LOCTEXT("ConflictingProgrammaticVariableFound", "Programmatic Variable '{0}' already exists, should be created new each compile with no conflicts"), FText::FromName(Name)));
return;
}
TConstArrayView<uint8> Value = TConstArrayView<uint8>(ProgrammaticVariable.GetValuePtr(), Type.GetSize());
int32 Index = StructEntryInfos.Add(
{
nullptr,
nullptr,
Name,
FAnimNextParamType(Type.GetValueType(), Type.GetContainerType(), Type.GetValueTypeObject()),
EAnimNextExportAccessSpecifier::Private,
false,
Value,
CPF_AdvancedDisplay
});
EntryInfoIndexMap.Add(Name, Index);
};
for (const FAnimNextProgrammaticVariable& ProgrammaticVariable : ProgrammaticVariables)
{
AddProgrammaticVariable(ProgrammaticVariable);
}
// Sort public entries first, then by data interface & then by size, largest first, for better packing
static_assert(EAnimNextExportAccessSpecifier::Private < EAnimNextExportAccessSpecifier::Public, "Private must be less than Public as parameters are sorted internally according to this assumption");
StructEntryInfos.Sort([](const FStructEntryInfo& InLHS, const FStructEntryInfo& InRHS)
{
if(InLHS.AccessSpecifier != InRHS.AccessSpecifier)
{
return InLHS.AccessSpecifier > InRHS.AccessSpecifier;
}
else if(InLHS.FromDataInterface != InRHS.FromDataInterface)
{
// If we have a null (Ex: programmatic variables), compare ptrs so that those without interfaces are last
if (!InLHS.FromDataInterface || !InRHS.FromDataInterface)
{
return InLHS.FromDataInterface > InRHS.FromDataInterface;
}
return InLHS.FromDataInterface->GetFName().LexicalLess(InRHS.FromDataInterface->GetFName());
}
else if(InLHS.Type.GetSize() != InRHS.Type.GetSize())
{
return InLHS.Type.GetSize() > InRHS.Type.GetSize();
}
else
{
return InLHS.Name.LexicalLess(InRHS.Name);
}
});
DataInterface->DefaultInjectionSiteIndex = INDEX_NONE;
if(StructEntryInfos.Num() > 0)
{
// Build PropertyDescs and values to batch-create the property bag
TArray<FPropertyBagPropertyDesc> PropertyDescs;
PropertyDescs.Reserve(StructEntryInfos.Num());
TArray<TConstArrayView<uint8>> Values;
Values.Reserve(StructEntryInfos.Num());
DataInterface->ImplementedInterfaces.Empty();
for (TEnumerateRef<const FStructEntryInfo> StructEntryInfo : EnumerateRange(StructEntryInfos))
{
PropertyDescs.Emplace(StructEntryInfo->Name, StructEntryInfo->Type.ContainerType, StructEntryInfo->Type.ValueType, StructEntryInfo->Type.ValueTypeObject, StructEntryInfo->PropertyFlags);
Values.Add(StructEntryInfo->Value);
// Note: string comparison here because otherwise we would have a circular dependency
if( EditorData->DefaultInjectionSite == StructEntryInfo->Name &&
StructEntryInfo->Type.ValueTypeObject->GetPathName() == TEXT("/Script/AnimNextAnimGraph.AnimNextAnimGraph"))
{
DataInterface->DefaultInjectionSiteIndex = StructEntryInfo.GetIndex();
}
if(StructEntryInfo->AccessSpecifier != EAnimNextExportAccessSpecifier::Public)
{
continue;
}
// Now process any data interfaces (sets of public variables)
auto CheckForExistingDataInterface = [&StructEntryInfo](const FAnimNextImplementedDataInterface& InImplementedDataInterface)
{
return InImplementedDataInterface.DataInterface == StructEntryInfo->FromDataInterface;
};
FAnimNextImplementedDataInterface* ExistingImplementedDataInterface = DataInterface->ImplementedInterfaces.FindByPredicate(CheckForExistingDataInterface);
if(ExistingImplementedDataInterface == nullptr)
{
FAnimNextImplementedDataInterface& NewImplementedDataInterface = DataInterface->ImplementedInterfaces.AddDefaulted_GetRef();
NewImplementedDataInterface.DataInterface = StructEntryInfo->FromDataInterface;
NewImplementedDataInterface.NativeInterface = StructEntryInfo->NativeInterface;
NewImplementedDataInterface.VariableIndex = StructEntryInfo.GetIndex();
NewImplementedDataInterface.NumVariables = 1;
NewImplementedDataInterface.bAutoBindToHost = StructEntryInfo->bAutoBindDataInterfaceToHost;
}
else
{
ExistingImplementedDataInterface->NumVariables++;
}
}
if(EditorData->DefaultInjectionSite != NAME_None && DataInterface->DefaultInjectionSiteIndex == INDEX_NONE)
{
Log.Error(FText::Format(LOCTEXT("MissingDefaultInjectionSiteWarning", "Could not find default injection site: {0}"), FText::FromName(EditorData->DefaultInjectionSite)));
}
// Create new property bags and migrate
EPropertyBagResult Result = DataInterface->VariableDefaults.ReplaceAllPropertiesAndValues(PropertyDescs, Values);
check(Result == EPropertyBagResult::Success);
if(NumPublicVariables > 0)
{
TConstArrayView<FPropertyBagPropertyDesc> PublicPropertyDescs(PropertyDescs.GetData(), NumPublicVariables);
TConstArrayView<TConstArrayView<uint8>> PublicValues(Values.GetData(), NumPublicVariables);
Result = DataInterface->PublicVariableDefaults.ReplaceAllPropertiesAndValues(PublicPropertyDescs, PublicValues);
check(Result == EPropertyBagResult::Success);
}
else
{
DataInterface->PublicVariableDefaults.Reset();
}
// Rebuild external variables
DataInterface->VM->SetExternalVariableDefs(DataInterface->GetExternalVariablesImpl(false));
}
else
{
DataInterface->ImplementedInterfaces.Empty();
DataInterface->VariableDefaults.Reset();
DataInterface->PublicVariableDefaults.Reset();
DataInterface->VM->ClearExternalVariables(DataInterface->ExtendedExecuteContext);
DataInterface->DefaultInjectionSiteIndex = INDEX_NONE;
}
}
void FUtils::CompileVariableBindings(const FRigVMCompileSettings& InSettings, UAnimNextRigVMAsset* InAsset, TArray<URigVMGraph*>& OutGraphs)
{
CompileVariableBindingsInternal(InSettings, InAsset, OutGraphs, true);
CompileVariableBindingsInternal(InSettings, InAsset, OutGraphs, false);
}
void FUtils::CompileVariableBindingsInternal(const FRigVMCompileSettings& InSettings, UAnimNextRigVMAsset* InAsset, TArray<URigVMGraph*>& OutGraphs, bool bInThreadSafe)
{
check(InAsset);
FModule& Module = FModuleManager::LoadModuleChecked<FModule>("AnimNextUncookedOnly");
UAnimNextRigVMAssetEditorData* EditorData = GetEditorData(InAsset);
TMap<IVariableBindingType*, TArray<IVariableBindingType::FBindingGraphInput>> BindingGroups;
for(const UAnimNextRigVMAssetEntry* Entry : EditorData->Entries)
{
const IAnimNextRigVMVariableInterface* Variable = Cast<IAnimNextRigVMVariableInterface>(Entry);
if(Variable == nullptr)
{
continue;
}
TConstStructView<FAnimNextVariableBindingData> Binding = Variable->GetBinding();
if(!Binding.IsValid() || !Binding.Get<FAnimNextVariableBindingData>().IsValid())
{
continue;
}
if(Binding.Get<FAnimNextVariableBindingData>().IsThreadSafe() != bInThreadSafe)
{
continue;
}
TSharedPtr<IVariableBindingType> BindingType = Module.FindVariableBindingType(Binding.GetScriptStruct());
if(!BindingType.IsValid())
{
continue;
}
TArray<IVariableBindingType::FBindingGraphInput>& Group = BindingGroups.FindOrAdd(BindingType.Get());
FRigVMTemplateArgumentType RigVMArg = Variable->GetType().ToRigVMTemplateArgument();
Group.Add({ Variable->GetVariableName(), RigVMArg.GetBaseCPPType(), RigVMArg.CPPTypeObject, Binding});
}
const bool bHasBindings = BindingGroups.Num() > 0;
const bool bHasPublicVariablesToCopy = EditorData->IsA<UAnimNextModule_EditorData>() && EditorData->HasPublicVariables() && bInThreadSafe;
if(!bHasBindings && !bHasPublicVariablesToCopy)
{
// Nothing to do here
return;
}
URigVMGraph* BindingGraph = NewObject<URigVMGraph>(EditorData, NAME_None, RF_Transient);
FRigVMClient* VMClient = EditorData->GetRigVMClient();
URigVMController* Controller = VMClient->GetOrCreateController(BindingGraph);
UScriptStruct* BindingsNodeType = bInThreadSafe ? FRigUnit_AnimNextExecuteBindings_WT::StaticStruct() : FRigUnit_AnimNextExecuteBindings_GT::StaticStruct();
URigVMNode* ExecuteBindingsNode = Controller->AddUnitNode(BindingsNodeType, FRigVMStruct::ExecuteName, FVector2D::ZeroVector, FString(), false);
if(ExecuteBindingsNode == nullptr)
{
InSettings.ReportError(TEXT("Could not spawn Execute Bindings node"));
return;
}
URigVMPin* ExecuteBindingsExecPin = ExecuteBindingsNode->FindPin(FRigVMStruct::ExecuteContextName.ToString());
if(ExecuteBindingsExecPin == nullptr)
{
InSettings.ReportError(TEXT("Could not find execute pin on Execute Bindings node"));
return;
}
URigVMPin* ExecPin = ExecuteBindingsExecPin;
// Copy public vars in the WT event
if(bHasPublicVariablesToCopy && bInThreadSafe)
{
URigVMNode* CopyProxyVariablesNode = Controller->AddUnitNode(FRigUnit_CopyModuleProxyVariables::StaticStruct(), FRigVMStruct::ExecuteName, FVector2D(200, 0.0f), FString(), false);
if(CopyProxyVariablesNode == nullptr)
{
InSettings.ReportError(TEXT("Could not spawn Copy Module Proxy Variables node"));
return;
}
URigVMPin* CopyProxyVariablesExecPin = CopyProxyVariablesNode->FindPin(FRigVMStruct::ExecuteContextName.ToString());
if(ExecPin == nullptr)
{
InSettings.ReportError(TEXT("Could not find execute pin on Copy Module Proxy Variables node"));
return;
}
bool bLinkAdded = Controller->AddLink(ExecuteBindingsExecPin, CopyProxyVariablesExecPin, false);
if(!bLinkAdded)
{
InSettings.ReportError(TEXT("Could not link Copy Module Proxy Variables node"));
return;
}
ExecPin = CopyProxyVariablesExecPin;
}
IVariableBindingType::FBindingGraphFragmentArgs Args;
Args.Event = BindingsNodeType;
Args.Controller = Controller;
Args.BindingGraph = BindingGraph;
Args.ExecTail = ExecPin;
Args.bThreadSafe = bInThreadSafe;
FVector2D Location(0.0f, 0.0f);
for(const TPair<IVariableBindingType*, TArray<IVariableBindingType::FBindingGraphInput>>& BindingGroupPair : BindingGroups)
{
Args.Inputs = BindingGroupPair.Value;
BindingGroupPair.Key->BuildBindingGraphFragment(InSettings, Args, ExecPin, Location);
}
OutGraphs.Add(BindingGraph);
}
UAnimNextRigVMAsset* FUtils::GetAsset(UAnimNextRigVMAssetEditorData* InEditorData)
{
check(InEditorData);
return CastChecked<UAnimNextRigVMAsset>(InEditorData->GetOuter());
}
UAnimNextRigVMAssetEditorData* FUtils::GetEditorData(UAnimNextRigVMAsset* InAsset)
{
check(InAsset);
return CastChecked<UAnimNextRigVMAssetEditorData>(InAsset->EditorData);
}
FAnimNextParamType FUtils::GetParamTypeFromPinType(const FEdGraphPinType& InPinType)
{
FAnimNextParamType::EValueType ValueType = FAnimNextParamType::EValueType::None;
FAnimNextParamType::EContainerType ContainerType = FAnimNextParamType::EContainerType::None;
UObject* ValueTypeObject = nullptr;
if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean)
{
ValueType = FAnimNextParamType::EValueType::Bool;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Byte)
{
if (UEnum* Enum = Cast<UEnum>(InPinType.PinSubCategoryObject.Get()))
{
ValueType = FAnimNextParamType::EValueType::Enum;
ValueTypeObject = Enum;
}
else
{
ValueType = FAnimNextParamType::EValueType::Byte;
}
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Int)
{
ValueType = FAnimNextParamType::EValueType::Int32;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Int64)
{
ValueType = FAnimNextParamType::EValueType::Int64;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Real)
{
if (InPinType.PinSubCategory == UEdGraphSchema_K2::PC_Float)
{
ValueType = FAnimNextParamType::EValueType::Float;
}
else if (InPinType.PinSubCategory == UEdGraphSchema_K2::PC_Double)
{
ValueType = FAnimNextParamType::EValueType::Double;
}
else
{
ensure(false); // Reals should be either floats or doubles
}
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Float)
{
ValueType = FAnimNextParamType::EValueType::Float;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Double)
{
ValueType = FAnimNextParamType::EValueType::Double;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Name)
{
ValueType = FAnimNextParamType::EValueType::Name;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_String)
{
ValueType = FAnimNextParamType::EValueType::String;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Text)
{
ValueType = FAnimNextParamType::EValueType::Text;
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Enum)
{
ValueType = FAnimNextParamType::EValueType::Enum;
ValueTypeObject = Cast<UEnum>(InPinType.PinSubCategoryObject.Get());
ensure(ValueTypeObject);
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
ValueType = FAnimNextParamType::EValueType::Struct;
ValueTypeObject = Cast<UScriptStruct>(InPinType.PinSubCategoryObject.Get());
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Object || InPinType.PinCategory == UEdGraphSchema_K2::AllObjectTypes)
{
ValueType = FAnimNextParamType::EValueType::Object;
ValueTypeObject = Cast<UClass>(InPinType.PinSubCategoryObject.Get());
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject)
{
ValueType = FAnimNextParamType::EValueType::SoftObject;
ValueTypeObject = Cast<UClass>(InPinType.PinSubCategoryObject.Get());
ensure(ValueTypeObject);
}
else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass)
{
ValueType = FAnimNextParamType::EValueType::SoftClass;
ValueTypeObject = Cast<UClass>(InPinType.PinSubCategoryObject.Get());
ensure(ValueTypeObject);
}
if(InPinType.ContainerType == EPinContainerType::Array)
{
ContainerType = FAnimNextParamType::EContainerType::Array;
}
else if(InPinType.ContainerType == EPinContainerType::Set)
{
ensureMsgf(false, TEXT("Set pins are not yet supported"));
}
if(InPinType.ContainerType == EPinContainerType::Map)
{
ensureMsgf(false, TEXT("Map pins are not yet supported"));
}
return FAnimNextParamType(ValueType, ContainerType, ValueTypeObject);
}
FEdGraphPinType FUtils::GetPinTypeFromParamType(const FAnimNextParamType& InParamType)
{
FEdGraphPinType PinType;
PinType.PinSubCategory = NAME_None;
// Container type
switch (InParamType.ContainerType)
{
case FAnimNextParamType::EContainerType::Array:
PinType.ContainerType = EPinContainerType::Array;
break;
default:
PinType.ContainerType = EPinContainerType::None;
}
// Value type
switch (InParamType.ValueType)
{
case EPropertyBagPropertyType::Bool:
PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
break;
case EPropertyBagPropertyType::Byte:
PinType.PinCategory = UEdGraphSchema_K2::PC_Byte;
break;
case EPropertyBagPropertyType::Int32:
PinType.PinCategory = UEdGraphSchema_K2::PC_Int;
break;
case EPropertyBagPropertyType::Int64:
PinType.PinCategory = UEdGraphSchema_K2::PC_Int64;
break;
case EPropertyBagPropertyType::Float:
PinType.PinCategory = UEdGraphSchema_K2::PC_Real;
PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float;
break;
case EPropertyBagPropertyType::Double:
PinType.PinCategory = UEdGraphSchema_K2::PC_Real;
PinType.PinSubCategory = UEdGraphSchema_K2::PC_Double;
break;
case EPropertyBagPropertyType::Name:
PinType.PinCategory = UEdGraphSchema_K2::PC_Name;
break;
case EPropertyBagPropertyType::String:
PinType.PinCategory = UEdGraphSchema_K2::PC_String;
break;
case EPropertyBagPropertyType::Text:
PinType.PinCategory = UEdGraphSchema_K2::PC_Text;
break;
case EPropertyBagPropertyType::Enum:
// @todo: some pin coloring is not correct due to this (byte-as-enum vs enum).
PinType.PinCategory = UEdGraphSchema_K2::PC_Enum;
PinType.PinSubCategoryObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Struct:
PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
PinType.PinSubCategoryObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Object:
PinType.PinCategory = UEdGraphSchema_K2::PC_Object;
PinType.PinSubCategoryObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::SoftObject:
PinType.PinCategory = UEdGraphSchema_K2::PC_SoftObject;
PinType.PinSubCategoryObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Class:
PinType.PinCategory = UEdGraphSchema_K2::PC_Class;
PinType.PinSubCategoryObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::SoftClass:
PinType.PinCategory = UEdGraphSchema_K2::PC_SoftClass;
PinType.PinSubCategoryObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
default:
break;
}
return PinType;
}
FRigVMTemplateArgumentType FUtils::GetRigVMArgTypeFromParamType(const FAnimNextParamType& InParamType)
{
FRigVMTemplateArgumentType ArgType;
FString CPPTypeString;
// Value type
switch (InParamType.ValueType)
{
case EPropertyBagPropertyType::Bool:
CPPTypeString = RigVMTypeUtils::BoolType;
break;
case EPropertyBagPropertyType::Byte:
CPPTypeString = RigVMTypeUtils::UInt8Type;
break;
case EPropertyBagPropertyType::Int32:
CPPTypeString = RigVMTypeUtils::UInt32Type;
break;
case EPropertyBagPropertyType::Int64:
ensureMsgf(false, TEXT("Unhandled value type %d"), InParamType.ValueType);
break;
case EPropertyBagPropertyType::Float:
CPPTypeString = RigVMTypeUtils::FloatType;
break;
case EPropertyBagPropertyType::Double:
CPPTypeString = RigVMTypeUtils::DoubleType;
break;
case EPropertyBagPropertyType::Name:
CPPTypeString = RigVMTypeUtils::FNameType;
break;
case EPropertyBagPropertyType::String:
CPPTypeString = RigVMTypeUtils::FStringType;
break;
case EPropertyBagPropertyType::Text:
ensureMsgf(false, TEXT("Unhandled value type %d"), InParamType.ValueType);
break;
case EPropertyBagPropertyType::Enum:
CPPTypeString = RigVMTypeUtils::CPPTypeFromEnum(Cast<UEnum>(InParamType.ValueTypeObject.Get()));
ArgType.CPPTypeObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Struct:
CPPTypeString = RigVMTypeUtils::GetUniqueStructTypeName(Cast<UScriptStruct>(InParamType.ValueTypeObject.Get()));
ArgType.CPPTypeObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Object:
CPPTypeString = RigVMTypeUtils::CPPTypeFromObject(Cast<UClass>(InParamType.ValueTypeObject.Get()), RigVMTypeUtils::EClassArgType::AsObject);
ArgType.CPPTypeObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::SoftObject:
ensureMsgf(false, TEXT("Unhandled value type %d"), InParamType.ValueType);
break;
case EPropertyBagPropertyType::Class:
CPPTypeString = RigVMTypeUtils::CPPTypeFromObject(Cast<UClass>(InParamType.ValueTypeObject.Get()), RigVMTypeUtils::EClassArgType::AsClass);
ArgType.CPPTypeObject = const_cast<UObject*>(InParamType.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::SoftClass:
ensureMsgf(false, TEXT("Unhandled value type %d"), InParamType.ValueType);
break;
default:
ensureMsgf(false, TEXT("Unhandled value type %d"), InParamType.ValueType);
break;
}
// Container type
switch (InParamType.ContainerType)
{
case FAnimNextParamType::EContainerType::None:
break;
case FAnimNextParamType::EContainerType::Array:
CPPTypeString = FString::Printf(RigVMTypeUtils::TArrayTemplate, *CPPTypeString);
break;
default:
ensureMsgf(false, TEXT("Unhandled container type %d"), InParamType.ContainerType);
break;
}
ArgType.CPPType = *CPPTypeString;
return ArgType;
}
void FUtils::SetupEventGraph(URigVMController* InController, UScriptStruct* InEventStruct, FName InEventName, bool bPrintPythonCommand)
{
// Clear the graph
InController->RemoveNodes(InController->GetGraph()->GetNodes());
if (InEventStruct->IsChildOf(FRigUnit_AnimNextUserEvent::StaticStruct()))
{
FRigUnit_AnimNextUserEvent Defaults;
Defaults.Name = InEventName;
Defaults.SortOrder = InEventName.GetNumber();
InController->AddUnitNodeWithDefaults(InEventStruct, Defaults, FRigVMStruct::ExecuteName, FVector2D(-200.0f, 0.0f), FString(), false);
}
else if (InEventStruct == FRigVMFunction_UserDefinedEvent::StaticStruct())
{
FRigVMFunction_UserDefinedEvent Defaults;
Defaults.EventName = InEventName;
InController->AddUnitNodeWithDefaults(InEventStruct, Defaults, FRigVMStruct::ExecuteName, FVector2D(-200.0f, 0.0f), FString(), false);
}
else
{
InController->AddUnitNode(InEventStruct, FRigVMStruct::ExecuteName, FVector2D(-200.0f, 0.0f), FString(), false);
}
}
FAnimNextParamType FUtils::GetParameterTypeFromName(FName InName)
{
// Query the asset registry for other params
TMap<FAssetData, FAnimNextAssetRegistryExports> ExportMap;
GetExportedVariablesFromAssetRegistry(ExportMap);
for(const TPair<FAssetData, FAnimNextAssetRegistryExports>& ExportPair : ExportMap)
{
for(const FAnimNextAssetRegistryExportedVariable& Parameter : ExportPair.Value.Variables)
{
if(Parameter.Name == InName)
{
return Parameter.Type;
}
}
}
return FAnimNextParamType();
}
bool FUtils::GetExportedVariablesForAsset(const FAssetData& InAsset, FAnimNextAssetRegistryExports& OutExports)
{
const FString TagValue = InAsset.GetTagValueRef<FString>(UE::AnimNext::ExportsAnimNextAssetRegistryTag);
return FAnimNextAssetRegistryExports::StaticStruct()->ImportText(*TagValue, &OutExports, nullptr, PPF_None, nullptr, FAnimNextAssetRegistryExports::StaticStruct()->GetName()) != nullptr;
}
bool FUtils::GetExportedVariablesFromAssetRegistry(TMap<FAssetData, FAnimNextAssetRegistryExports>& OutExports)
{
TArray<FAssetData> AssetData;
IAssetRegistry::GetChecked().GetAssetsByTags({UE::AnimNext::ExportsAnimNextAssetRegistryTag}, AssetData);
for (const FAssetData& Asset : AssetData)
{
const FString TagValue = Asset.GetTagValueRef<FString>(UE::AnimNext::ExportsAnimNextAssetRegistryTag);
FAnimNextAssetRegistryExports AssetExports;
if (FAnimNextAssetRegistryExports::StaticStruct()->ImportText(*TagValue, &AssetExports, nullptr, PPF_None, nullptr, FAnimNextAssetRegistryExports::StaticStruct()->GetName()) != nullptr)
{
OutExports.Add(Asset, MoveTemp(AssetExports));
}
}
return OutExports.Num() > 0;
}
void FUtils::GetAssetFunctions(const UAnimNextRigVMAssetEditorData* InEditorData, FRigVMGraphFunctionHeaderArray& OutExports)
{
for (const FRigVMGraphFunctionData& FunctionData : InEditorData->GraphFunctionStore.PublicFunctions)
{
if (FunctionData.CompilationData.IsValid())
{
OutExports.Headers.Add(FunctionData.Header);
}
}
}
void FUtils::GetAssetPrivateFunctions(const UAnimNextRigVMAssetEditorData* InEditorData, FRigVMGraphFunctionHeaderArray& OutExports)
{
for (const FRigVMGraphFunctionData& FunctionData : InEditorData->GraphFunctionStore.PrivateFunctions)
{
// Note: We dont check compilation data here as private functions are not compiled if they are not referenced
OutExports.Headers.Add(FunctionData.Header);
}
}
bool FUtils::GetExportedFunctionsForAsset(const FAssetData& InAsset, FName Tag, FRigVMGraphFunctionHeaderArray& OutExports)
{
const FString TagValue = InAsset.GetTagValueRef<FString>(Tag);
const FArrayProperty* HeadersProperty = CastField<FArrayProperty>(FRigVMGraphFunctionHeaderArray::StaticStruct()->FindPropertyByName(TEXT("Headers")));
HeadersProperty->ImportText_Direct(*TagValue, &OutExports, nullptr, EPropertyPortFlags::PPF_None);
return OutExports.Headers.Num() > 0;
}
bool FUtils::GetExportedFunctionsFromAssetRegistry(FName Tag, TMap<FAssetData, FRigVMGraphFunctionHeaderArray>& OutExports)
{
TArray<FAssetData> AssetData;
IAssetRegistry::GetChecked().GetAssetsByTags({ Tag }, AssetData);
const FArrayProperty* HeadersProperty = CastField<FArrayProperty>(FRigVMGraphFunctionHeaderArray::StaticStruct()->FindPropertyByName(TEXT("Headers")));
for (const FAssetData& Asset : AssetData)
{
const FString TagValue = Asset.GetTagValueRef<FString>(Tag);
FRigVMGraphFunctionHeaderArray AssetExports;
if (HeadersProperty->ImportText_Direct(*TagValue, &AssetExports, nullptr, EPropertyPortFlags::PPF_None) != nullptr)
{
if (AssetExports.Headers.Num() > 0)
{
FRigVMGraphFunctionHeaderArray& AssetArray = OutExports.FindOrAdd(Asset);
AssetArray.Headers.Append(MoveTemp(AssetExports.Headers));
}
}
}
return OutExports.Num() > 0;
}
static void AddParamToSet(const FAnimNextAssetRegistryExportedVariable& InNewParam, TSet<FAnimNextAssetRegistryExportedVariable>& OutExports)
{
if(FAnimNextAssetRegistryExportedVariable* ExistingEntry = OutExports.Find(InNewParam))
{
if(ExistingEntry->Type != InNewParam.Type)
{
UE_LOGFMT(LogAnimation, Warning, "Type mismatch between parameter {ParameterName}. {ParamType1} vs {ParamType1}", InNewParam.Name, InNewParam.Type.ToString(), ExistingEntry->Type.ToString());
}
ExistingEntry->Flags |= InNewParam.Flags;
}
else
{
OutExports.Add(InNewParam);
}
}
void FUtils::GetAssetVariables(const UAnimNextRigVMAssetEditorData* EditorData, FAnimNextAssetRegistryExports& OutExports)
{
OutExports.Variables.Reset();
OutExports.Variables.Reserve(EditorData->Entries.Num());
TSet<FAnimNextAssetRegistryExportedVariable> ExportSet;
GetAssetVariables(EditorData, ExportSet);
OutExports.Variables = ExportSet.Array();
}
void FUtils::GetAssetVariables(const UAnimNextRigVMAssetEditorData* InEditorData, TSet<FAnimNextAssetRegistryExportedVariable>& OutExports)
{
for(const UAnimNextRigVMAssetEntry* Entry : InEditorData->Entries)
{
if(const IAnimNextRigVMExportInterface* ExportInterface = Cast<IAnimNextRigVMExportInterface>(Entry))
{
EAnimNextExportedVariableFlags Flags = EAnimNextExportedVariableFlags::Declared;
if(ExportInterface->GetExportAccessSpecifier() == EAnimNextExportAccessSpecifier::Public)
{
Flags |= EAnimNextExportedVariableFlags::Public;
FAnimNextAssetRegistryExportedVariable NewParam(ExportInterface->GetExportName(), ExportInterface->GetExportType(), Flags);
AddParamToSet(NewParam, OutExports);
}
}
else if(const UAnimNextDataInterfaceEntry* DataInterfaceEntry = Cast<UAnimNextDataInterfaceEntry>(Entry))
{
if(DataInterfaceEntry->DataInterface)
{
UAnimNextDataInterface_EditorData* EditorData = GetEditorData<UAnimNextDataInterface_EditorData>(DataInterfaceEntry->DataInterface.Get());
GetAssetVariables(EditorData, OutExports);
}
}
}
}
void FUtils::GetAssetOutlinerItems(const UAnimNextRigVMAssetEditorData* EditorData, FWorkspaceOutlinerItemExports& OutExports, FAssetRegistryTagsContext Context)
{
FWorkspaceOutlinerItemExport AssetIdentifier = FWorkspaceOutlinerItemExport(EditorData->GetOuter()->GetFName(), EditorData->GetOuter());
for(UAnimNextRigVMAssetEntry* Entry : EditorData->Entries)
{
if(IAnimNextRigVMGraphInterface* GraphInterface = Cast<IAnimNextRigVMGraphInterface>(Entry))
{
if (Entry->IsHiddenInOutliner())
{
if (URigVMEdGraph* RigVMEdGraph = GraphInterface->GetEdGraph())
{
CreateSubGraphsOutlinerItemsRecursive(EditorData, OutExports, AssetIdentifier, INDEX_NONE, RigVMEdGraph, Context);
}
}
else
{
FWorkspaceOutlinerItemExport& Export = OutExports.Exports.Add_GetRef(FWorkspaceOutlinerItemExport(Entry->GetEntryName(), AssetIdentifier));
Export.GetData().InitializeAsScriptStruct(FAnimNextGraphOutlinerData::StaticStruct());
FAnimNextGraphOutlinerData& GraphData = Export.GetData().GetMutable<FAnimNextGraphOutlinerData>();
GraphData.SoftEntryPtr = Entry;
if (URigVMEdGraph* RigVMEdGraph = GraphInterface->GetEdGraph())
{
int32 ExportIndex = OutExports.Exports.Num() - 1;
CreateSubGraphsOutlinerItemsRecursive(EditorData, OutExports, AssetIdentifier, ExportIndex, RigVMEdGraph, Context);
}
}
}
}
CreateFunctionLibraryOutlinerItemsRecursive(EditorData, OutExports, AssetIdentifier, INDEX_NONE, EditorData->GetRigVMGraphFunctionStore()->PublicFunctions, EditorData->GetRigVMGraphFunctionStore()->PrivateFunctions);
}
void ProcessPinAssetReferences(const URigVMPin* InPin, FWorkspaceOutlinerItemExports& OutExports, FWorkspaceOutlinerItemExport& RootExport, int32 ParentExportIndex)
{
if (InPin)
{
const UObject* TypeObject = InPin->GetCPPTypeObject();
if (Cast<UClass>(TypeObject))
{
const FSoftObjectPath ObjectPath(InPin->GetDefaultValue());
if (ObjectPath.IsValid())
{
// Only add export if object is loaded, or the path actually points to an asset
FAssetData ReferenceAssetData;
if (ObjectPath.ResolveObject() || IAssetRegistry::GetChecked().TryGetAssetByObjectPath(ObjectPath, ReferenceAssetData) == UE::AssetRegistry::EExists::Exists)
{
FWorkspaceOutlinerItemExport& ParentExport = ParentExportIndex == INDEX_NONE ? RootExport : OutExports.Exports[ParentExportIndex];
FWorkspaceOutlinerItemExport& ReferenceExport = OutExports.Exports.Add_GetRef(FWorkspaceOutlinerItemExport(FName(ObjectPath.ToString()), ParentExport));
ReferenceExport.GetData().InitializeAs<FWorkspaceOutlinerAssetReferenceItemData>();
ReferenceExport.GetData().GetMutable<FWorkspaceOutlinerAssetReferenceItemData>().ReferredObjectPath = ObjectPath;
}
}
}
for (const URigVMPin* SubPin : InPin->GetSubPins())
{
ProcessPinAssetReferences(SubPin, OutExports, RootExport, ParentExportIndex);
}
}
}
void FUtils::CreateSubGraphsOutlinerItemsRecursive(const UAnimNextRigVMAssetEditorData* EditorData, FWorkspaceOutlinerItemExports& OutExports, FWorkspaceOutlinerItemExport& RootExport, int32 ParentExportIndex, URigVMEdGraph* RigVMEdGraph, FAssetRegistryTagsContext Context)
{
if (RigVMEdGraph == nullptr)
{
return;
}
// Handle pin asset references (disabled during save as GetMetaData can cause StaticFindFast calls which is prohibited during save)
if (!Context.IsSaving())
{
for (const TObjectPtr<class UEdGraphNode>& Node : RigVMEdGraph->Nodes)
{
if (URigVMEdGraphNode* RigVMEdNode = Cast<URigVMEdGraphNode>(Node))
{
if (URigVMTemplateNode* TemplateRigVMNode = Cast<URigVMTemplateNode>(RigVMEdNode->GetModelNode()))
{
if (TemplateRigVMNode->GetScriptStruct() && TemplateRigVMNode->GetScriptStruct()->IsChildOf(FRigUnit_AnimNextBase::StaticStruct()))
{
for (URigVMPin* ModelPin : TemplateRigVMNode->GetPins())
{
if (ModelPin->GetDirection() == ERigVMPinDirection::Input)
{
auto HandlePin = [&OutExports, &RootExport, ParentExportIndex](const URigVMPin* InPin)
{
if (InPin->GetMetaData(TEXT("ExportAsReference")) == TEXT("true"))
{
ProcessPinAssetReferences(InPin, OutExports, RootExport, ParentExportIndex);
}
};
HandlePin(ModelPin);
for (URigVMPin* TraitPin : ModelPin->GetSubPins())
{
HandlePin(TraitPin);
}
}
}
}
}
}
}
}
// ---- Collapsed graphs ---
for (const TObjectPtr<UEdGraph>& SubGraph : RigVMEdGraph->SubGraphs)
{
URigVMEdGraph* EditorObject = Cast<URigVMEdGraph>(SubGraph);
if (IsValid(EditorObject))
{
if(ensure(EditorObject->GetModel()))
{
URigVMCollapseNode* CollapseNode = CastChecked<URigVMCollapseNode>(EditorObject->GetModel()->GetOuter());
FWorkspaceOutlinerItemExport& ParentExport = ParentExportIndex == INDEX_NONE ? RootExport : OutExports.Exports[ParentExportIndex];
FWorkspaceOutlinerItemExport& Export = OutExports.Exports.Add_GetRef(FWorkspaceOutlinerItemExport(CollapseNode->GetFName(), ParentExport));
Export.GetData().InitializeAsScriptStruct(FAnimNextCollapseGraphOutlinerData::StaticStruct());
FAnimNextCollapseGraphOutlinerData& FnGraphData = Export.GetData().GetMutable<FAnimNextCollapseGraphOutlinerData>();
FnGraphData.SoftEditorObject = EditorObject;
int32 ExportIndex = OutExports.Exports.Num() - 1;
CreateSubGraphsOutlinerItemsRecursive(EditorData, OutExports, RootExport, ExportIndex, EditorObject, Context);
}
}
}
// ---- Function References ---
TArray<URigVMEdGraphNode*> EdNodes;
RigVMEdGraph->GetNodesOfClass(EdNodes);
for (URigVMEdGraphNode* EdNode : EdNodes)
{
if (URigVMFunctionReferenceNode* FunctionReferenceNode = Cast<URigVMFunctionReferenceNode>(EdNode->GetModelNode()))
{
// Only export referenced functions which are part of same outer
const FRigVMGraphFunctionIdentifier FunctionIdentifier = FunctionReferenceNode->GetFunctionIdentifier();
if (FunctionIdentifier.HostObject == FSoftObjectPath(EditorData))
{
const URigVMLibraryNode* FunctionNode = EditorData->RigVMClient.GetFunctionLibrary()->FindFunction(FunctionIdentifier.GetFunctionFName());
if (URigVMGraph* ContainedGraph = FunctionNode->GetContainedGraph())
{
FWorkspaceOutlinerItemExport& ParentExport = ParentExportIndex == INDEX_NONE ? RootExport : OutExports.Exports[ParentExportIndex];
FWorkspaceOutlinerItemExport& Export = OutExports.Exports.Add_GetRef(FWorkspaceOutlinerItemExport(FunctionNode->GetFName(), ParentExport));
Export.GetData().InitializeAsScriptStruct(FAnimNextGraphFunctionOutlinerData::StaticStruct());
FAnimNextGraphFunctionOutlinerData& FnGraphData = Export.GetData().GetMutable<FAnimNextGraphFunctionOutlinerData>();
if (URigVMEdGraph* ContainedEdGraph = Cast<URigVMEdGraph>(EditorData->GetEditorObjectForRigVMGraph(ContainedGraph)))
{
FnGraphData.SoftEditorObject = ContainedEdGraph;
FnGraphData.SoftEdGraphNode = EdNode;
int32 ExportIndex = OutExports.Exports.Num() - 1;
CreateSubGraphsOutlinerItemsRecursive(EditorData, OutExports, RootExport, ExportIndex, ContainedEdGraph, Context);
}
}
}
}
}
}
void FUtils::CreateFunctionLibraryOutlinerItemsRecursive(const UAnimNextRigVMAssetEditorData* EditorData, FWorkspaceOutlinerItemExports& OutExports, FWorkspaceOutlinerItemExport& RootExport, int32 ParentExportIndex, const TArray<FRigVMGraphFunctionData>& PublicFunctions, const TArray<FRigVMGraphFunctionData>& PrivateFunctions)
{
if (PrivateFunctions.Num() > 0 || PublicFunctions.Num() > 0)
{
CreateFunctionsOutlinerItemsRecursive(EditorData, OutExports, RootExport, ParentExportIndex, PrivateFunctions, false);
CreateFunctionsOutlinerItemsRecursive(EditorData, OutExports, RootExport, ParentExportIndex, PublicFunctions, true);
}
}
void FUtils::CreateFunctionsOutlinerItemsRecursive(const UAnimNextRigVMAssetEditorData* EditorData, FWorkspaceOutlinerItemExports& OutExports, FWorkspaceOutlinerItemExport& RootExport, int32 ParentExportIndex, const TArray<FRigVMGraphFunctionData>& Functions, bool bPublicFunctions)
{
for (const FRigVMGraphFunctionData& FunctionData : Functions)
{
const URigVMLibraryNode* FunctionNode = EditorData->RigVMClient.GetFunctionLibrary()->FindFunction(FunctionData.Header.LibraryPointer.GetFunctionFName());
if (FunctionNode)
{
if (URigVMGraph* ContainedModelGraph = FunctionNode->GetContainedGraph())
{
if (URigVMEdGraph* EditorObject = Cast<URigVMEdGraph>(EditorData->GetEditorObjectForRigVMGraph(ContainedModelGraph)))
{
FWorkspaceOutlinerItemExport& ParentExport = ParentExportIndex == INDEX_NONE ? RootExport : OutExports.Exports[ParentExportIndex];
FWorkspaceOutlinerItemExport& Export = OutExports.Exports.Add_GetRef(FWorkspaceOutlinerItemExport(FunctionData.Header.Name, ParentExport));
Export.GetData().InitializeAsScriptStruct(FAnimNextGraphFunctionOutlinerData::StaticStruct());
FAnimNextGraphFunctionOutlinerData& FnGraphData = Export.GetData().GetMutable<FAnimNextGraphFunctionOutlinerData>();
FnGraphData.SoftEditorObject = EditorObject;
}
}
}
}
}
const FText& FUtils::GetFunctionLibraryDisplayName()
{
static const FText FunctionLibraryName = LOCTEXT("WorkspaceFunctionLibraryName", "Function Library");
return FunctionLibraryName;
}
#if WITH_EDITOR
void FUtils::OpenProgrammaticGraphs(UAnimNextRigVMAssetEditorData* EditorData, const TArray<URigVMGraph*>& ProgrammaticGraphs)
{
UAnimNextRigVMAsset* OwningAsset = FUtils::GetAsset(EditorData);
UE::Workspace::IWorkspaceEditorModule& WorkspaceEditorModule = FModuleManager::LoadModuleChecked<UE::Workspace::IWorkspaceEditorModule>("WorkspaceEditor");
if(UE::Workspace::IWorkspaceEditor* WorkspaceEditor = WorkspaceEditorModule.OpenWorkspaceForObject(OwningAsset, UE::Workspace::EOpenWorkspaceMethod::Default))
{
TArray<UObject*> Graphs;
for(URigVMGraph* ProgrammaticGraph : ProgrammaticGraphs)
{
// Some explanation needed here!
// URigVMEdGraph caches its underlying model internally in GetModel depending on its outer if it is no attached to a RigVMClient
// So here we rename the graph into the transient package so we dont get any notifications
ProgrammaticGraph->Rename(nullptr, GetTransientPackage(), REN_ForceNoResetLoaders | REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
// then create the graph (transient so it outers to the RigVMGraph)
URigVMEdGraph* EdGraph = CastChecked<URigVMEdGraph>(EditorData->CreateEdGraph(ProgrammaticGraph, true));
// Then cache the model
EdGraph->GetModel();
Graphs.Add(EdGraph);
// Now rename into this asset again to be able to correctly create a controller (needed to view the graph and interact with it)
ProgrammaticGraph->Rename(nullptr, EditorData, REN_ForceNoResetLoaders | REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
URigVMController* ProgrammaticController = EditorData->GetOrCreateController(ProgrammaticGraph);
// Resend notifications to rebuild the EdGraph
ProgrammaticController->ResendAllNotifications();
}
WorkspaceEditor->OpenObjects(Graphs);
}
}
#endif // WITH_EDITOR
FString FUtils::MakeFunctionWrapperVariableName(FName InFunctionName, FName InVariableName)
{
// We assume the function name is enough for variable name uniqueness in this graph (We don't yet desire global uniqueness).
return TEXT("__InternalVar_") + InFunctionName.ToString() + "_" + InVariableName.ToString();
}
FString FUtils::MakeFunctionWrapperEventName(FName InFunctionName)
{
return TEXT("__InternalCall_") + InFunctionName.ToString();
}
}
#undef LOCTEXT_NAMESPACE