// Copyright Epic Games, Inc. All Rights Reserved. #include "RigVMAsset.h" #include "RigVMBlueprintLegacy.h" #include "RigVMBlueprintGeneratedClass.h" #include "EdGraph/EdGraph.h" #include "EdGraphNode_Comment.h" #include "BlueprintActionDatabaseRegistrar.h" #include "EdGraph/RigVMEdGraph.h" #include "EdGraph/RigVMEdGraphNode.h" #include "EdGraph/RigVMEdGraphSchema.h" #include "UObject/AssetRegistryTagsContext.h" #include "UObject/ObjectSaveContext.h" #include "UObject/UObjectGlobals.h" #include "RigVMObjectVersion.h" #include "BlueprintCompilationManager.h" #include "RigVMCompiler/RigVMCompiler.h" #include "RigVMCore/RigVMRegistry.h" #include "AssetRegistry/AssetRegistryModule.h" #include "RigVMPythonUtils.h" #include "RigVMTypeUtils.h" #include "Algo/Count.h" #include "RigVMModel/RigVMControllerActions.h" #include "RigVMModel/Nodes/RigVMAggregateNode.h" #include "RigVMModel/Nodes/RigVMDispatchNode.h" #include "UObject/UE5MainStreamObjectVersion.h" #include "Stats/StatsHierarchical.h" #include "UObject/UObjectIterator.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(RigVMAsset) #if WITH_EDITOR #include "Kismet2/KismetDebugUtilities.h" #include "Kismet2/WatchedPin.h" #include "Kismet2/BlueprintEditorUtils.h" #include "RigVMBlueprintUtils.h" #include "Editor/UnrealEdEngine.h" #include "Editor/Transactor.h" #include "CookOnTheSide/CookOnTheFlyServer.h" #include "RigVMEditorModule.h" #include "ScopedTransaction.h" #if !WITH_RIGVMLEGACYEDITOR #include "RigVMEditor/Private/Editor/Kismet/RigVMBlueprintCompilationManager.h" #endif #endif//WITH_EDITOR #define LOCTEXT_NAMESPACE "RigVMAsset" TAutoConsoleVariable CVarRigVMEnablePreLoadFiltering( TEXT("RigVM.EnablePreLoadFiltering"), true, TEXT("When true the RigVMGraphs will be skipped during preload to speed up load times.")); TAutoConsoleVariable CVarRigVMEnablePostLoadHashing( TEXT("RigVM.EnablePostLoadHashing"), true, TEXT("When true refreshing the RigVMGraphs will be skipped if the hash matches the serialized hash.")); static TArray GetClassObjectsInPackage(UPackage* InPackage) { TArray Objects; GetObjectsWithOuter(InPackage, Objects, false); TArray ClassObjects; for (UObject* Object : Objects) { if (UClass* Class = Cast(Object)) { ClassObjects.Add(Class); } } return ClassObjects; } FRigVMEdGraphDisplaySettings::FRigVMEdGraphDisplaySettings(): bShowNodeInstructionIndex(false) , bShowNodeRunCounts(false) , NodeRunLowerBound(0) , NodeRunLimit(256) , MinMicroSeconds(0.0) , MaxMicroSeconds(1.0) , TotalMicroSeconds(0.0) , AverageFrames(64) , bAutoDetermineRange(true) , LastMinMicroSeconds(0.0) , LastMaxMicroSeconds(1.0) , MinDurationColor(FLinearColor::Green) , MaxDurationColor(FLinearColor::Red) , TagDisplayMode(ERigVMTagDisplayMode::All) { } FRigVMEdGraphDisplaySettings::~FRigVMEdGraphDisplaySettings() = default; void FRigVMEdGraphDisplaySettings::SetTotalMicroSeconds(double InTotalMicroSeconds) { TotalMicroSeconds = AggregateAverage(TotalMicroSecondsFrames, TotalMicroSeconds, InTotalMicroSeconds); } void FRigVMEdGraphDisplaySettings::SetLastMinMicroSeconds(double InMinMicroSeconds) { LastMinMicroSeconds = AggregateAverage(MinMicroSecondsFrames, LastMinMicroSeconds, InMinMicroSeconds); } void FRigVMEdGraphDisplaySettings::SetLastMaxMicroSeconds(double InMaxMicroSeconds) { LastMaxMicroSeconds = AggregateAverage(MaxMicroSecondsFrames, LastMaxMicroSeconds, InMaxMicroSeconds); } double FRigVMEdGraphDisplaySettings::AggregateAverage(TArray& InFrames, double InPrevious, double InNext) const { const int32 NbFrames = FMath::Min(AverageFrames, 256); if(NbFrames < 2) { InFrames.Reset(); return InNext; } InFrames.Add(InNext); if(InFrames.Num() >= NbFrames) { double Average = 0; for(const double Value : InFrames) { Average += Value; } Average /= double(NbFrames); InFrames.Reset(); return Average; } if(InPrevious == DBL_MAX || InPrevious < -SMALL_NUMBER) { return InNext; } return InPrevious; } FRigVMAssetInterfacePtr IRigVMAssetInterface::GetInterfaceOuter(const UObject* InObject) { if (!InObject) { return nullptr; } UObject* Outer = InObject->GetOuter(); while (Outer) { if (Outer->Implements()) { return FRigVMAssetInterfacePtr(Outer); } Outer = Outer->GetOuter(); } return nullptr; } FSoftObjectPath IRigVMAssetInterface::PreDuplicateAssetPath; FSoftObjectPath IRigVMAssetInterface::PreDuplicateHostPath; TArray IRigVMAssetInterface::sCurrentlyOpenedRigVMBlueprints; #if WITH_EDITOR FCriticalSection IRigVMAssetInterface::QueuedCompilerMessageDelegatesMutex; TArray IRigVMAssetInterface::QueuedCompilerMessageDelegates; #endif IRigVMAssetInterface::IRigVMAssetInterface(const FObjectInitializer& ObjectInitializer) { bSuspendModelNotificationsForSelf = false; bSuspendAllNotifications = false; bSuspendPythonMessagesForRigVMClient = true; bMarkBlueprintAsStructurallyModifiedPending = false; bAutoRecompileVM = true; bVMRecompilationRequired = false; bIsCompiling = false; VMRecompilationBracket = 0; bSkipDirtyBlueprintStatus = false; bDirtyDuringLoad = false; bErrorsDuringCompilation = false; #if WITH_EDITOR TArray DelegatesForReportFromCompiler; { FScopeLock Lock(&QueuedCompilerMessageDelegatesMutex); Swap(QueuedCompilerMessageDelegates, DelegatesForReportFromCompiler); } for(const FOnRigVMReportCompilerMessage::FDelegate& Delegate : DelegatesForReportFromCompiler) { ReportCompilerMessageEvent.Add(Delegate); } #endif } void IRigVMAssetInterface::Clear() { FCoreUObjectDelegates::OnEndLoadPackage.RemoveAll(this); FRigVMRegistry::Get().OnRigVMRegistryChanged().RemoveAll(this); } TScriptInterface IRigVMAssetInterface::GetRigVMClientHost() { return GetObject(); } void IRigVMAssetInterface::RequestClientHostRigVMInit() { if (TScriptInterface ClientHost = GetRigVMClientHost()) { return ClientHost->RequestRigVMInit(); } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const FRigVMClient* InClient) const { check(InClient); const TArray Graphs = InClient->GetAllModels(true, true); for(const URigVMGraph* Graph : Graphs) { CollectExternalDependencies(OutDependencies, InCategory, Graph); } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const FRigVMGraphFunctionStore* InFunctionStore) const { check(InFunctionStore); for(const FRigVMGraphFunctionData& Function : InFunctionStore->PublicFunctions) { CollectExternalDependencies(OutDependencies, InCategory, &Function); } for(const FRigVMGraphFunctionData& Function : InFunctionStore->PrivateFunctions) { CollectExternalDependencies(OutDependencies, InCategory, &Function); } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const FRigVMGraphFunctionData* InFunction) const { check(InFunction); CollectExternalDependencies(OutDependencies, InCategory, &InFunction->Header); CollectExternalDependencies(OutDependencies, InCategory, &InFunction->CompilationData); } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const FRigVMGraphFunctionHeader* InHeader) const { check(InHeader); if(InCategory == IRigVMExternalDependencyManager::RigVMGraphFunctionCategory) { OutDependencies.AddUnique({InHeader->LibraryPointer.GetLibraryNodePath(), InCategory}); } for(const FRigVMGraphFunctionArgument& Argument : InHeader->Arguments) { CollectExternalDependenciesForCPPTypeObject(OutDependencies, InCategory, Argument.CPPTypeObject.Get()); } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const FRigVMFunctionCompilationData* InFunction) const { check(InFunction); for(const FRigVMFunctionCompilationPropertyDescription& Property : InFunction->LiteralPropertyDescriptions) { CollectExternalDependenciesForCPPTypeObject(OutDependencies, InCategory, Property.CPPTypeObject.Get()); } for(const FRigVMFunctionCompilationPropertyDescription& Property : InFunction->WorkPropertyDescriptions) { CollectExternalDependenciesForCPPTypeObject(OutDependencies, InCategory, Property.CPPTypeObject.Get()); } const FRigVMRegistry& Registry = FRigVMRegistry::Get(); for(const FName& FunctionName : InFunction->FunctionNames) { if(const FRigVMFunction* Function = Registry.FindFunction(*FunctionName.ToString())) { for(const FRigVMFunctionArgument& Argument : Function->Arguments) { const FRigVMTemplateArgumentType& ArgumentType = Registry.FindTypeFromCPPType(Argument.Type); if(ArgumentType.IsValid()) { CollectExternalDependenciesForCPPTypeObject(OutDependencies, InCategory, ArgumentType.CPPTypeObject.Get()); } } } } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const URigVMGraph* InGraph) const { for(const URigVMNode* Node : InGraph->GetNodes()) { CollectExternalDependencies(OutDependencies, InCategory, Node); } const TArray LocalVariables = InGraph->GetLocalVariables(); for(const FRigVMGraphVariableDescription& LocalVariable : LocalVariables) { CollectExternalDependenciesForCPPTypeObject(OutDependencies, InCategory, LocalVariable.CPPTypeObject.Get()); } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const URigVMNode* InNode) const { for(const URigVMPin* Pin : InNode->GetPins()) { CollectExternalDependencies(OutDependencies, InCategory, Pin); } if(const URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(InNode)) { if(const FRigVMGraphFunctionData* Function = FunctionReferenceNode->GetReferencedFunctionData(true)) { CollectExternalDependencies(OutDependencies, InCategory, Function); } } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const URigVMPin* InPin) const { CollectExternalDependenciesForCPPTypeObject(OutDependencies, InCategory, InPin->GetCPPTypeObject()); for(const URigVMPin* SubPin : InPin->GetSubPins()) { CollectExternalDependencies(OutDependencies, InCategory, SubPin); } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const UStruct* InStruct) const { check(InStruct); if(InCategory == IRigVMExternalDependencyManager::UserDefinedStructCategory) { if(const UUserDefinedStruct* UserDefinedStruct = Cast(InStruct)) { OutDependencies.AddUnique({UserDefinedStruct->GetPathName(), InCategory}); } } for (TFieldIterator PropertyIt(InStruct); PropertyIt; ++PropertyIt) { const FProperty* Property = *PropertyIt; while(const FArrayProperty* ArrayProperty = CastField(Property)) { Property = ArrayProperty->Inner; } if(const FStructProperty* StructProperty = CastField(Property)) { CollectExternalDependencies(OutDependencies, InCategory, StructProperty->Struct); } else if(const FEnumProperty* EnumProperty = CastField(Property)) { if(const UEnum* Enum = EnumProperty->GetEnum()) { CollectExternalDependencies(OutDependencies, InCategory, Enum); } } else if(const FByteProperty* ByteProperty = CastField(Property)) { if(const UEnum* Enum = ByteProperty->Enum) { CollectExternalDependencies(OutDependencies, InCategory, Enum); } } } } void IRigVMAssetInterface::CollectExternalDependencies(TArray& OutDependencies, const FName& InCategory, const UEnum* InEnum) const { check(InEnum); if(InCategory == IRigVMExternalDependencyManager::UserDefinedEnumCategory) { if(const UUserDefinedEnum* UserDefinedEnum = Cast(InEnum)) { OutDependencies.AddUnique({UserDefinedEnum->GetPathName(), InCategory}); } } } void IRigVMAssetInterface::CollectExternalDependenciesForCPPTypeObject(TArray& OutDependencies, const FName& InCategory, const UObject* InObject) const { if(InObject) { if(const UEnum* Enum = Cast(InObject)) { CollectExternalDependencies(OutDependencies, InCategory, Enum); } else if(const UStruct* Struct = Cast(InObject)) { CollectExternalDependencies(OutDependencies, InCategory, Struct); } } } void IRigVMAssetInterface::CommonInitialization(const FObjectInitializer& ObjectInitializer) { // guard against this running multiple times TScriptInterface Host(ObjectInitializer.GetObj()); FRigVMClient* RigVMClient = Host->GetRigVMClient(); check(RigVMClient->GetDefaultSchemaClass() == nullptr); RigVMClient->SetDefaultSchemaClass(Host->GetRigVMSchemaClass()); RigVMClient->SetDefaultExecuteContextStruct(Host->GetRigVMExecuteContextStruct()); for(UEdGraph* UberGraph : GetUberGraphs()) { if(URigVMEdGraph* EdGraph = Cast(UberGraph)) { EdGraph->Schema = Host->GetRigVMEdGraphSchemaClass(); } } RigVMClient->SetOuterClientHost(GetObject(), TEXT("RigVMClient")); { TGuardValue DisableClientNotifs(RigVMClient->bSuspendNotifications, true); RigVMClient->GetOrCreateFunctionLibrary(false, &ObjectInitializer, false); RigVMClient->AddModel(FRigVMClient::RigVMModelPrefix, false, &ObjectInitializer, false); } TObjectPtr& FunctionLibraryEdGraph = GetFunctionLibraryEdGraph(); FunctionLibraryEdGraph = Cast(ObjectInitializer.CreateDefaultSubobject(ObjectInitializer.GetObj(), TEXT("RigVMFunctionLibraryEdGraph"), Host->GetRigVMEdGraphClass(), Host->GetRigVMEdGraphClass(), true, true)); FunctionLibraryEdGraph->Schema = Host->GetRigVMEdGraphSchemaClass(); FunctionLibraryEdGraph->bAllowRenaming = 0; FunctionLibraryEdGraph->bEditable = 0; FunctionLibraryEdGraph->bAllowDeletion = 0; FunctionLibraryEdGraph->bIsFunctionDefinition = false; FunctionLibraryEdGraph->ModelNodePath = RigVMClient->GetFunctionLibrary()->GetNodePath(); FunctionLibraryEdGraph->InitializeFromAsset(ObjectInitializer.GetObj()); } void IRigVMAssetInterface::InitializeModelIfRequired(bool bRecompileVM) { DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC() FRigVMClient* RigVMClient = GetRigVMClient(); if (RigVMClient->GetController(0) == nullptr) { const TArray Models = RigVMClient->GetAllModels(true, false); for(const URigVMGraph* Model : Models) { RigVMClient->GetOrCreateController(Model); } bool bRecompileRequired = false; TArray> UbergraphPages = GetUberGraphs(); for (int32 i = 0; i < UbergraphPages.Num(); ++i) { if (URigVMEdGraph* Graph = Cast(UbergraphPages[i])) { if (bRecompileVM) { bRecompileRequired = true; } Graph->InitializeFromAsset(GetObject()); } } if(bRecompileRequired) { RecompileVM(); } GetFunctionLibraryEdGraph()->InitializeFromAsset(GetObject()); } } bool IRigVMAssetInterface::ExportGraphToText(UEdGraph* InEdGraph, FString& OutText) { OutText.Empty(); if (URigVMGraph* RigGraph = GetModel(InEdGraph)) { if (URigVMCollapseNode* CollapseNode = Cast(RigGraph->GetOuter())) { if (URigVMController* Controller = GetOrCreateController(CollapseNode->GetGraph())) { TArray NodeNamesToExport; NodeNamesToExport.Add(CollapseNode->GetFName()); OutText = Controller->ExportNodesToText(NodeNamesToExport); } } } // always return true so that the default mechanism doesn't take over return true; } bool IRigVMAssetInterface::CanImportGraphFromText(const FString& InClipboardText) { return GetTemplateController(true)->CanImportNodesFromText(InClipboardText); } bool IRigVMAssetInterface::RequiresForceLoadMembers(UObject* InObject) const { // only filter if the console variable is enabled if(!CVarRigVMEnablePreLoadFiltering->GetBool()) { return RequiresForceLoadMembersSuper(InObject); } // we can stop traversing when hitting a URigVMNode // except for collapse nodes - since they contain a graphs again // and variable nodes - since they are needed during preload by the BP compiler if(InObject->IsA()) { if(!InObject->IsA() && !InObject->IsA()) { return false; } } return RequiresForceLoadMembersSuper(InObject); } void IRigVMAssetInterface::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) { PostEditChangeChainPropertyEvent.Broadcast(PropertyChangedEvent); } void IRigVMAssetInterface::PostRename(UObject* OldOuter, const FName OldName) { // Whenever the asset is renamed/moved, generated classes parented to the old package // are not moved to the new package automatically (see FAssetRenameManager), so we // have to manually perform the move/rename, to avoid invalid reference to the old package // Note: while asset duplication doesn't duplicate the classes either, it is not a problem there // because we always recompile in post duplicate. TArray ClassObjects = GetClassObjectsInPackage(OldOuter->GetPackage()); for (UClass* ClassObject : ClassObjects) { if (URigVMMemoryStorageGeneratorClass* MemoryClass = Cast(ClassObject)) { MemoryClass->Rename(nullptr, GetPackage(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional); } } const FString OldAssetPath = FString::Printf(TEXT("%s.%s"), *OldOuter->GetPathName(), *OldName.ToString()); ReplaceFunctionIdentifiers(OldAssetPath, GetObject()->GetPathName()); } void IRigVMAssetInterface::GetPreloadDependencies(TArray& OutDeps) { TArray ClassObjects = GetClassObjectsInPackage(GetPackage()); for (UClass* ClassObject : ClassObjects) { if (URigVMMemoryStorageGeneratorClass* MemoryClass = Cast(ClassObject)) { OutDeps.Add(MemoryClass); } } } UObject* IRigVMAssetInterface::GetEditorObjectForRigVMGraph(const URigVMGraph* InVMGraph) const { if(InVMGraph) { if(InVMGraph->GetOutermost() != GetObject()->GetOutermost()) { return nullptr; } if(InVMGraph->IsA()) { return GetFunctionLibraryEdGraph(); } TArray EdGraphs; GetAllEdGraphs(EdGraphs); bool bIsFunctionDefinition = false; if (URigVMLibraryNode* LibraryNode = Cast(InVMGraph->GetOuter())) { bIsFunctionDefinition = LibraryNode->GetGraph()->IsA(); } for (UEdGraph* EdGraph : EdGraphs) { if (URigVMEdGraph* RigGraph = Cast(EdGraph)) { if (RigGraph->bIsFunctionDefinition != bIsFunctionDefinition) { continue; } if ((RigGraph->ModelNodePath == InVMGraph->GetNodePath()) || (RigGraph->ModelNodePath.IsEmpty() && (GetRigVMClient()->GetDefaultModel() == InVMGraph))) { return RigGraph; } } } } return nullptr; } URigVMGraph* IRigVMAssetInterface::GetRigVMGraphForEditorObject(UObject* InObject) const { if(URigVMEdGraph* RigVMEdGraph = Cast(InObject)) { const FRigVMClient* RigVMClient = GetRigVMClient(); if (RigVMEdGraph->bIsFunctionDefinition) { if (URigVMLibraryNode* LibraryNode = RigVMClient->GetFunctionLibrary()->FindFunction(*RigVMEdGraph->ModelNodePath)) { return LibraryNode->GetContainedGraph(); } } else { return RigVMClient->GetModel(RigVMEdGraph->ModelNodePath); } } return nullptr; } void IRigVMAssetInterface::HandleRigVMGraphAdded(const FRigVMClient* InClient, const FString& InNodePath) { if(URigVMGraph* Model = InClient->GetModel(InNodePath)) { if(!GetObject()->HasAnyFlags(RF_ClassDefaultObject | RF_NeedInitialization | RF_NeedLoad | RF_NeedPostLoad) && GetObject()->GetOuter() != GetTransientPackage() && !GIsTransacting) { CreateEdGraph(Model, true); RecompileVM(); } #if WITH_EDITOR if(!bSuspendPythonMessagesForRigVMClient) { const FString BlueprintName = InClient->GetDefaultSchema()->GetSanitizedName(GetObject()->GetName(), true, false); RigVMPythonUtils::Print(BlueprintName, FString::Printf(TEXT("blueprint.add_model('%s')"), *Model->GetName())); } #endif } } void IRigVMAssetInterface::HandleRigVMGraphRemoved(const FRigVMClient* InClient, const FString& InNodePath) { if(URigVMGraph* Model = InClient->GetModel(InNodePath)) { RemoveEdGraph(Model); RecompileVM(); #if WITH_EDITOR if(!bSuspendPythonMessagesForRigVMClient) { const FString BlueprintName = InClient->GetDefaultSchema()->GetSanitizedName(GetObject()->GetName(), true, false); RigVMPythonUtils::Print(BlueprintName, FString::Printf(TEXT("blueprint.remove_model('%s')"), *Model->GetName())); } #endif } } void IRigVMAssetInterface::HandleRigVMGraphRenamed(const FRigVMClient* InClient, const FString& InOldNodePath, const FString& InNewNodePath) { if(InClient->GetModel(InNewNodePath)) { TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* EdGraph : EdGraphs) { if (URigVMEdGraph* RigGraph = Cast(EdGraph)) { RigGraph->HandleRigVMGraphRenamed(InOldNodePath, InNewNodePath); } } MarkAssetAsModified(); } } void IRigVMAssetInterface::HandleConfigureRigVMController(const FRigVMClient* InClient, URigVMController* InControllerToConfigure) { InControllerToConfigure->OnModified().AddRaw(this, &IRigVMAssetInterface::HandleModifiedEvent); TWeakObjectPtr WeakThis(GetObject()); // this delegate is used by the controller to determine variable validity // during a bind process. the controller itself doesn't own the variables, // so we need a delegate to request them from the owning blueprint InControllerToConfigure->GetExternalVariablesDelegate.BindLambda([](URigVMGraph* InGraph) -> TArray { if (InGraph) { if(IRigVMAssetInterface* Asset = InGraph->GetImplementingOuter()) { return Asset->GetExternalVariables(true /* rely on variables within blueprint */); } } return TArray(); }); // this delegate is used by the controller to retrieve the current bytecode of the VM InControllerToConfigure->GetCurrentByteCodeDelegate.BindLambda([WeakThis]() -> const FRigVMByteCode* { if (UObject* Object = WeakThis.Get()) { if (FRigVMAssetInterfacePtr Asset = Object) { if (URigVM* VM = Asset->GetVM(false)) { return &VM->GetByteCode(); } } } return nullptr; }); #if WITH_EDITOR // this sets up three delegates: // a) get external variables (mapped to Controller->GetExternalVariables) // b) bind pin to variable (mapped to Controller->BindPinToVariable) // c) create external variable (mapped to the passed in tfunction) // the last one is defined within the blueprint since the controller // doesn't own the variables and can't create one itself. InControllerToConfigure->SetupDefaultUnitNodeDelegates(TDelegate::CreateLambda( [WeakThis](FRigVMExternalVariable InVariableToCreate, FString InDefaultValue) -> FName { if (UObject* Object = WeakThis.Get()) { if (Object->Implements()) { FRigVMAssetInterfacePtr Asset = Object; return Asset->AddHostMemberVariableFromExternal(InVariableToCreate, InDefaultValue); } } return NAME_None; } )); TWeakObjectPtr WeakController = InControllerToConfigure; InControllerToConfigure->RequestBulkEditDialogDelegate.BindLambda([WeakThis, WeakController](URigVMLibraryNode* InFunction, ERigVMControllerBulkEditType InEditType) -> FRigVMController_BulkEditResult { if (UObject* Object = WeakThis.Get()) { if (FRigVMAssetInterfacePtr Asset = Object) { if (URigVMController* Controller = WeakController.Get()) { if(Asset->OnRequestBulkEditDialog().IsBound()) { return Asset->OnRequestBulkEditDialog().Execute(Asset, Controller, InFunction, InEditType); } } } } return FRigVMController_BulkEditResult(); }); InControllerToConfigure->RequestBreakLinksDialogDelegate.BindLambda([WeakThis, WeakController](TArray InLinks) -> bool { if (UObject* Object = WeakThis.Get()) { if (Object->Implements()) { FRigVMAssetInterfacePtr BaseAsset = Object; if (URigVMController* Controller = WeakController.Get()) { if(BaseAsset->OnRequestBreakLinksDialog().IsBound()) { return BaseAsset->OnRequestBreakLinksDialog().Execute(InLinks); } } } } return false; }); InControllerToConfigure->RequestPinTypeSelectionDelegate.BindLambda([WeakThis](const TArray& InTypes) -> TRigVMTypeIndex { if (UObject* Object = WeakThis.Get()) { if (Object->Implements()) { FRigVMAssetInterfacePtr BaseAsset = Object; if(BaseAsset->OnRequestPinTypeSelectionDialog().IsBound()) { return BaseAsset->OnRequestPinTypeSelectionDialog().Execute(InTypes); } } } return INDEX_NONE; }); InControllerToConfigure->RequestNewExternalVariableDelegate.BindLambda([WeakThis](FRigVMGraphVariableDescription InVariable, bool bInIsPublic, bool bInIsReadOnly) -> FName { if (UObject* Object = WeakThis.Get()) { if (Object->Implements()) { FRigVMAssetInterfacePtr BaseAsset = Object; TArray ExternalVariables = BaseAsset->GetExternalVariables(true); for (FRigVMExternalVariable& ExistingVariable : ExternalVariables) { if (ExistingVariable.Name == InVariable.Name) { return FName(); } } FRigVMExternalVariable ExternalVariable = InVariable.ToExternalVariable(); return BaseAsset->AddMemberVariable(InVariable.Name, ExternalVariable.TypeObject ? ExternalVariable.TypeObject->GetPathName() : ExternalVariable.TypeName.ToString(), bInIsPublic, bInIsReadOnly, InVariable.DefaultValue); } } return FName(); }); InControllerToConfigure->RequestJumpToHyperlinkDelegate.BindLambda([WeakThis](const UObject* InSubject) { if (UObject* Object = WeakThis.Get()) { if (Object->Implements()) { FRigVMAssetInterfacePtr BaseAsset = Object; if(BaseAsset->OnRequestJumpToHyperlink().IsBound()) { BaseAsset->OnRequestJumpToHyperlink().Execute(InSubject); } } } }); #endif } UObject* IRigVMAssetInterface::ResolveUserDefinedTypeById(const FString& InTypeName) const { const TMap& Info = GetUserDefinedStructGuidToPathName(false); const FSoftObjectPath* ResultPathPtr = Info.Find(InTypeName); if (ResultPathPtr == nullptr) { return nullptr; } if (UObject* TypeObject = ResultPathPtr->TryLoad()) { // Ensure we have a hold on this type so it doesn't get nixed on the next GC. const_cast(this)->GetUserDefinedTypesInUse(false).Add(TypeObject); return TypeObject; } return nullptr; } bool IRigVMAssetInterface::TryImportGraphFromText(const FString& InClipboardText, UEdGraph** OutGraphPtr) { if (OutGraphPtr) { *OutGraphPtr = nullptr; } if (URigVMController* FunctionLibraryController = GetOrCreateController(GetLocalFunctionLibrary())) { TGuardValue RequestLocalizeDelegateGuard( FunctionLibraryController->RequestLocalizeFunctionDelegate, FRigVMController_RequestLocalizeFunctionDelegate::CreateLambda([this](FRigVMGraphFunctionIdentifier& InFunctionToLocalize) { BroadcastRequestLocalizeFunctionDialog(InFunctionToLocalize); const URigVMLibraryNode* LocalizedFunctionNode = GetLocalFunctionLibrary()->FindPreviouslyLocalizedFunction(InFunctionToLocalize); return LocalizedFunctionNode != nullptr; }) ); TArray ImportedNodeNames = FunctionLibraryController->ImportNodesFromText(InClipboardText, true, true); if (ImportedNodeNames.Num() == 0) { return false; } URigVMCollapseNode* CollapseNode = Cast(GetLocalFunctionLibrary()->FindFunction(ImportedNodeNames[0])); if (ImportedNodeNames.Num() > 1 || CollapseNode == nullptr || CollapseNode->GetContainedGraph() == nullptr) { FunctionLibraryController->Undo(); return false; } UEdGraph* EdGraph = GetEdGraph(CollapseNode->GetContainedGraph()); if (OutGraphPtr) { *OutGraphPtr = EdGraph; } BroadcastGraphImported(EdGraph); } // always return true so that the default mechanism doesn't take over return true; } URigVMEditorSettings* IRigVMAssetInterface::GetRigVMEditorSettings() const { return GetMutableDefault(GetRigVMClientHost()->GetRigVMEditorSettingsClass()); } #if WITH_EDITOR const FLazyName& IRigVMAssetInterface::GetPanelNodeFactoryName() const { return RigVMPanelNodeFactoryName; } const FLazyName& IRigVMAssetInterface::GetPanelPinFactoryName() const { return RigVMPanelPinFactoryName; } bool IRigVMAssetInterface::SetEarlyExitInstruction(URigVMNode* InNodeToExitEarlyAfter, int32 InInstruction, bool bRequestHyperLink) { if (InNodeToExitEarlyAfter == nullptr) { return ResetEarlyExitInstruction(true); } const FRigVMByteCode* ByteCode = GetController()->GetCurrentByteCode(); if (ByteCode == nullptr) { return false; } int32 InstructionIndex = InInstruction; const TArray& InstructionIndices = ByteCode->GetAllInstructionIndicesForSubject(InNodeToExitEarlyAfter); if (!InstructionIndices.IsEmpty()) { // find the last consecutive instruction for a node in the first block int32 IndexInArray = 0; if (InInstruction != INDEX_NONE) { IndexInArray = InstructionIndices.Find(InInstruction); if (IndexInArray == INDEX_NONE) { IndexInArray = 0; } } while (IndexInArray < InstructionIndices.Num() - 1 && InstructionIndices[IndexInArray] == InstructionIndices[IndexInArray + 1] - 1) { IndexInArray++; } InstructionIndex = InstructionIndices[IndexInArray]; } const bool bResetResult = ResetEarlyExitInstruction(InstructionIndex == INDEX_NONE); if (InstructionIndex == INDEX_NONE) { return bResetResult; } if (InstructionIndex < 0 || InstructionIndex >= ByteCode->GetNumInstructions()) { return false; } GetDebugInfo().SetInstructionForEarlyExit(InstructionIndex); if (URigVMHost* DebuggedHost = GetDebuggedRigVMHost()) { DebuggedHost->GetDebugInfo().CopyFrom(GetDebugInfo()); DebuggedHost->SetIsInDebugMode(true); } TArray IncludedNodes; TSet VisitedNodes; IncludedNodes.Add(InNodeToExitEarlyAfter); for (int32 Index = 0; Index < IncludedNodes.Num(); Index++) { VisitedNodes.Add(IncludedNodes[Index]); const TArray SourceNodes = IncludedNodes[Index]->GetLinkedSourceNodes(); for (URigVMNode* SourceNode : SourceNodes) { if (VisitedNodes.Contains(SourceNode)) { continue; } IncludedNodes.AddUnique(SourceNode); } } InNodeToExitEarlyAfter->SetHasEarlyExitMarker(true); TArray AllNodes = InNodeToExitEarlyAfter->GetGraph()->GetNodes(); for (URigVMNode* Node : AllNodes) { Node->SetHasEarlyExitMarker(Node == InNodeToExitEarlyAfter); Node->SetIsExcludedByEarlyExit(!VisitedNodes.Contains(Node)); } GetRigGraphDisplaySettings().bShowNodeRunCounts = true; LastPreviewHereNodes.Emplace(InNodeToExitEarlyAfter, InstructionIndex); if (bRequestHyperLink) { if (FRigVMClient* Client = GetRigVMClient()) { if (URigVMController* Controller = Client->GetOrCreateController(InNodeToExitEarlyAfter->GetGraph())) { Controller->RequestJumpToHyperLink(InNodeToExitEarlyAfter); } } } return true; } bool IRigVMAssetInterface::ResetEarlyExitInstruction(bool bResetCallstack) { GetDebugInfo().Reset(); if (URigVMHost* DebuggedHost = GetDebuggedRigVMHost()) { DebuggedHost->GetDebugInfo().CopyFrom(GetDebugInfo()); } TArray AllGraphs = GetAllModels(); for (URigVMGraph* Graph : AllGraphs) { for (URigVMNode* Node : Graph->GetNodes()) { Node->SetHasEarlyExitMarker(false); Node->SetIsExcludedByEarlyExit(false); } } GetRigGraphDisplaySettings().bShowNodeRunCounts = false;; if (bResetCallstack) { LastPreviewHereNodes.Reset(); } return true; } void IRigVMAssetInterface::TogglePreviewHere(const URigVMGraph* InGraph) { if (!InGraph) { return; } URigVMHost* DebuggedHost = Cast(GetObjectBeingDebugged()); if (!DebuggedHost) { return; } const int32 CurrentEarlyExistInstruction = DebuggedHost->GetDebugInfo().GetInstructionForEarlyExit(); if (CurrentEarlyExistInstruction != INDEX_NONE) { if (CurrentEarlyExistInstruction == GetPreviewNodeInstructionIndexFromSelection(InGraph)) { ResetEarlyExitInstruction(true); return; } } const TArray SelectedNodeNames = InGraph->GetSelectNodes(); if (SelectedNodeNames.Num() == 0) { ResetEarlyExitInstruction(true); return; } URigVMNode* Node = InGraph->FindNodeByName(SelectedNodeNames[0]); if (!Node) { return; } if (!Node->IsMutable()) { return; } SetEarlyExitInstruction(Node); } void IRigVMAssetInterface::PreviewHereStepForward() { URigVMHost* DebuggedHost = Cast(GetObjectBeingDebugged()); if (!DebuggedHost) { return; } int32 CurrentInstructionIndex = DebuggedHost->GetDebugInfo().GetInstructionForEarlyExit(); if (CurrentInstructionIndex == INDEX_NONE) { return; } const URigVM* VM = DebuggedHost->GetVM(); if (!VM) { return; } URigVMNode* Node = nullptr; if (!LastPreviewHereNodes.IsEmpty()) { const TPair,int32> PreviousEntry = LastPreviewHereNodes.Pop(); Node = PreviousEntry.Key.Get(); if (Node) { CurrentInstructionIndex = PreviousEntry.Value; } } if (!Node) { Node = Cast(VM->GetByteCode().GetSubjectForInstruction(CurrentInstructionIndex)); } if (!Node) { return; } struct Local { static URigVMNode* FindNextNodeOnCompletedPin(URigVMNode* Node, TSet& VisitedNodes) { if (VisitedNodes.Contains(Node)) { return nullptr; } VisitedNodes.Add(Node); for (const URigVMPin* Pin : Node->GetPins()) { if (Pin->GetDirection() == ERigVMPinDirection::Input || Pin->GetDirection() == ERigVMPinDirection::IO) { if (!Pin->IsExecuteContext()) { continue; } const TArray SourcePins = Pin->GetLinkedSourcePins(); if (SourcePins.IsEmpty()) { continue; } URigVMNode* SourceNode = SourcePins[0]->GetNode(); if (SourcePins[0]->GetFName() == FRigVMStruct::ControlFlowCompletedName) { return FindNextNodeOnCompletedPin(SourceNode,VisitedNodes); } TArray PinsToFollow; if (const URigVMPin* CompletedPin = SourceNode->FindRootPinByName(FRigVMStruct::ControlFlowCompletedName)) { PinsToFollow.Add(CompletedPin); } else { // since we don't have a completed pin, try to the find the next execute pin after this one const int32 SourcePinIndex = SourcePins[0]->GetPinIndex(); for (int32 Index = SourcePinIndex + 1; Index < SourceNode->GetPins().Num(); Index++) { const URigVMPin* NextExecutePin = SourceNode->GetPins()[Index]; if (NextExecutePin->GetDirection() == ERigVMPinDirection::Output && NextExecutePin->IsExecuteContext()) { PinsToFollow.Add(NextExecutePin); } } } for (const URigVMPin* NextExecutePin : PinsToFollow) { const TArray TargetPins = NextExecutePin->GetLinkedTargetPins(); if (TargetPins.IsEmpty()) { continue; } return TargetPins[0]->GetNode(); } return FindNextNodeOnCompletedPin(SourceNode,VisitedNodes); } } return nullptr; } static URigVMNode* FindNextNode(URigVMNode* Node, TSet& VisitedNodes) { if (!Node->IsMutable()) { return nullptr; } for (const URigVMPin* Pin : Node->GetPins()) { if (Pin->GetDirection() == ERigVMPinDirection::Output || Pin->GetDirection() == ERigVMPinDirection::IO) { if (!Pin->IsExecuteContext()) { continue; } const TArray TargetPins = Pin->GetLinkedTargetPins(); if (TargetPins.IsEmpty()) { continue; } if (!TargetPins[0]->IsExecuteContext()) { continue; } return TargetPins[0]->GetNode(); } } return FindNextNodeOnCompletedPin(Node, VisitedNodes); } }; TSet VisitedNodes; URigVMNode* NextNode = Local::FindNextNode(Node, VisitedNodes); while (NextNode && NextNode->IsA()) { const TArray TargetNodes = NextNode->GetLinkedTargetNodes(); if (TargetNodes.IsEmpty()) { break; } NextNode = TargetNodes[0]; } if (!NextNode) { LastPreviewHereNodes.Emplace(Node, CurrentInstructionIndex); return; } if (NextNode->IsA()) { int32 InstructionIndex = CurrentInstructionIndex; NextNode = nullptr; while (NextNode == nullptr && (++InstructionIndex) < VM->GetByteCode().GetNumInstructions()) { NextNode = Cast(VM->GetByteCode().GetSubjectForInstruction(InstructionIndex)); if (Cast(NextNode)) { NextNode = nullptr; } } if (NextNode == nullptr) { return; } } bool bFoundNextNode = false; while (!bFoundNextNode && (++CurrentInstructionIndex) < VM->GetByteCode().GetNumInstructions()) { const TArray>* Callstack = VM->GetByteCode().GetCallstackForInstruction(CurrentInstructionIndex); if (Callstack) { for (const TWeakObjectPtr& WeakObjectPtr : *Callstack) { if (UObject* WeakObject = WeakObjectPtr.Get()) { if (WeakObject == NextNode) { bFoundNextNode = true; break; } } } } } SetEarlyExitInstruction(NextNode, CurrentInstructionIndex, true); } bool IRigVMAssetInterface::CanPreviewHereStepForward() const { URigVMHost* DebuggedHost = Cast(GetObjectBeingDebugged()); if (!DebuggedHost) { return false; } return DebuggedHost->GetDebugInfo().GetInstructionForEarlyExit() != INDEX_NONE; } int32 IRigVMAssetInterface::GetPreviewNodeInstructionIndexFromSelection(const URigVMGraph* InGraph) const { if (!InGraph) { return INDEX_NONE; } URigVMHost* DebuggedHost = Cast(GetObjectBeingDebugged()); if (!DebuggedHost) { return INDEX_NONE; } const TArray SelectedNodeNames = InGraph->GetSelectNodes(); if (SelectedNodeNames.Num() == 0) { return INDEX_NONE; } URigVMNode* Node = InGraph->FindNodeByName(SelectedNodeNames[0]); if (!Node || !Node->IsMutable()) { return INDEX_NONE; } const URigVM* VM = DebuggedHost->GetVM(); if (!VM) { return INDEX_NONE; } const TArray& InstructionIndices = VM->GetByteCode().GetAllInstructionIndicesForSubject(Node); if (InstructionIndices.IsEmpty()) { return INDEX_NONE; } // find the last consecutive instruction for a node in the first block int32 IndexInArray = 0; while (IndexInArray < InstructionIndices.Num() - 1 && InstructionIndices[IndexInArray] == InstructionIndices[IndexInArray + 1] - 1) { IndexInArray++; } return InstructionIndices[IndexInArray]; } IRigVMEditorModule* IRigVMAssetInterface::GetEditorModule() const { return &IRigVMEditorModule::Get(); } #endif void IRigVMAssetInterface::SerializeImpl(FArchive& Ar) { UE_RIGVM_ARCHIVETRACE_SCOPE(Ar, FString::Printf(TEXT("IRigVMAssetInterface(%s)"), *GetObject()->GetName())); Ar.UsingCustomVersion(FRigVMObjectVersion::GUID); if(IsValidChecked(GetObject())) { TScriptInterface Host = GetRigVMClientHost(); Host->GetRigVMClient()->SetOuterClientHost(GetObject(), TEXT("RigVMClient")); } SerializeSuper(Ar); UE_RIGVM_ARCHIVETRACE_ENTRY(Ar, TEXT("Super::Serialize")); if(Ar.IsObjectReferenceCollector()) { Ar.UsingCustomVersion(FRigVMObjectVersion::GUID); #if WITH_EDITORONLY_DATA if (Ar.IsCooking() && IsReferencedObjectPathsStored()) { for (FSoftObjectPath ObjectPath : GetReferencedObjectPaths()) { ObjectPath.Serialize(Ar); } } else #endif { TArray ReferencedFunctionHosts = GetReferencedFunctionHosts(false); for(IRigVMGraphFunctionHost* ReferencedFunctionHost : ReferencedFunctionHosts) { if (URigVMBlueprintGeneratedClass* BPGeneratedClass = Cast(ReferencedFunctionHost)) { Ar << BPGeneratedClass; } } } } if(Ar.IsLoading()) { TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* EdGraph : EdGraphs) { EdGraph->Schema = GetRigVMClientHost()->GetRigVMEdGraphSchemaClass(); if (URigVMEdGraph* RigVMEdGraph = Cast(EdGraph)) { RigVMEdGraph->CachedModelGraph.Reset(); } } if (Ar.CustomVer(FRigVMObjectVersion::GUID) < FRigVMObjectVersion::AddVariantToRigVMAssets) { GetAssetVariant().Guid = FRigVMVariant::GenerateGUID(GetPackage()->GetPathName()); } } PostSerialize(Ar); } void IRigVMAssetInterface::PreSave(FObjectPreSaveContext ObjectSaveContext) { GetRigVMClient()->PreSave(ObjectSaveContext); UpdateSupportedEventNames(); if (TScriptInterface FunctionHost = GetRigVMClientHost()->GetRigVMGraphFunctionHost()) { FRigVMGraphFunctionStore* Store = FunctionHost->GetRigVMGraphFunctionStore(); GetPublicGraphFunctions().Reset(); GetPublicGraphFunctions().SetNum(Store->PublicFunctions.Num()); for (int32 i=0; iPublicFunctions[i].Header; } URigVMFunctionLibrary* FunctionLibrary = GetLocalFunctionLibrary(); FunctionLibrary->FunctionToVariant.Reset(); for (int32 Pass=0; Pass<2; ++Pass) { const TArray& Functions = (Pass == 0) ? Store->PrivateFunctions : Store->PublicFunctions; for (const FRigVMGraphFunctionData& Function : Functions) { FunctionLibrary->FunctionToVariant.Add(Function.Header.Name, Function.Header.Variant); } } } #if WITH_EDITORONLY_DATA GetReferencedObjectPaths().Reset(); TArray ReferencedFunctionHosts = GetReferencedFunctionHosts(false); for(IRigVMGraphFunctionHost* ReferencedFunctionHost : ReferencedFunctionHosts) { if (URigVMBlueprintGeneratedClass* BPGeneratedClass = Cast(ReferencedFunctionHost)) { GetReferencedObjectPaths().AddUnique(BPGeneratedClass); } } SetReferencedObjectPathsStored(true); #endif GetFunctionReferenceNodeData() = GetReferenceNodeData(); IAssetRegistry::GetChecked().AssetTagsFinalized(*GetObject()); CachedAssetTags.Reset(); // also store the user defined struct guid to path name on the blueprint itself // to aid the controller when recovering from user defined struct name changes or // guid changes. GetUserDefinedStructGuidToPathName(false).Reset(); GetUserDefinedEnumToPathName(false).Reset(); GetUserDefinedTypesInUse(false).Reset(); TArray AllModels = GetAllModels(); for(const URigVMGraph* Graph : AllModels) { for(const URigVMNode* Node : Graph->GetNodes()) { const TArray AllPins = Node->GetAllPinsRecursively(); for(const URigVMPin* Pin : AllPins) { if(const UUserDefinedStruct* UserDefinedStruct = Cast(Pin->GetCPPTypeObject())) { const FString GuidBasedName = RigVMTypeUtils::GetUniqueStructTypeName(UserDefinedStruct); GetUserDefinedStructGuidToPathName(false).FindOrAdd(GuidBasedName) = FSoftObjectPath(UserDefinedStruct); } else if (const UUserDefinedEnum* UserDefinedEnum = Cast(Pin->GetCPPTypeObject())) { const FString EnumName = RigVMTypeUtils::CPPTypeFromEnum(UserDefinedEnum); GetUserDefinedEnumToPathName(false).FindOrAdd(EnumName, UserDefinedEnum); } } } } #if WITH_EDITORONLY_DATA OldMemoryStorageGeneratorClasses.Reset(); #endif } void IRigVMAssetInterface::PostLoad() { FRigVMRegistry::Get().RefreshEngineTypesIfRequired(); bVMRecompilationRequired = true; { TGuardValue IsCompilingGuard(bIsCompiling, true); TArray ReferencedFunctionHosts = GetReferencedFunctionHosts(true); // PostLoad all referenced function hosts so that their function data are fully loaded // and ready to be inlined into this BP during compilation for (IRigVMGraphFunctionHost* FunctionHost : ReferencedFunctionHosts) { if (URigVMBlueprintGeneratedClass* BPGeneratedClass = Cast(FunctionHost)) { if (BPGeneratedClass->HasAllFlags(RF_NeedPostLoad)) { BPGeneratedClass->ConditionalPostLoad(); } } } // temporarily disable default value validation during load time, serialized values should always be accepted TGuardValue DisablePinDefaultValueValidation(GetOrCreateController()->bValidatePinDefaults, false); // remove all non-controlrig-graphs TArray> NewUberGraphPages; for (UEdGraph* Graph : GetUberGraphs()) { URigVMEdGraph* RigGraph = Cast(Graph); if (RigGraph && RigGraph->GetClass() == GetRigVMClientHost()->GetRigVMEdGraphClass()) { NewUberGraphPages.Add(RigGraph); } else { // We are renaming an object to a new outer while we may still be loading. Since we // are destroying the object, pass REN_AllowPackageLinkerMismatch to avoid forcing // the load to complete since that is wasteful. Graph->MarkAsGarbage(); Graph->Rename(nullptr, GetTransientPackage(), REN_AllowPackageLinkerMismatch); } } SetUberGraphs(NewUberGraphPages); TArray> EditableGraphGuards; { for (URigVMGraph* Graph : GetAllModels()) { EditableGraphGuards.Emplace(Graph->bEditable, true); } } InitializeModelIfRequired(false /* recompile vm */); { TGuardValue GuardNotifications(bSuspendModelNotificationsForSelf, true); const FRigVMClientPatchResult PatchResult = GetRigVMClient()->PatchModelsOnLoad(); if(PatchResult.RequiresToMarkPackageDirty()) { (void)MarkPackageDirty(); bDirtyDuringLoad = true; } GetRigVMClient()->PatchFunctionReferencesOnLoad(); GetFunctionReferenceNodeData() = GetReferenceNodeData(); PatchVariableNodesOnLoad(); PatchVariableNodesWithIncorrectType(); PathDomainSpecificContentOnLoad(); PatchBoundVariables(); PatchParameterNodesOnLoad(); PatchLinksWithCast(); TMap OldHeaders; // Backwards compatibility. Store public access in the model TArray BackwardsCompatiblePublicFunctions; GetBackwardsCompatibilityPublicFunctions(BackwardsCompatiblePublicFunctions, OldHeaders); GetRigVMClient()->PatchFunctionsOnLoad(GetRigVMClientHost()->GetRigVMGraphFunctionHost().GetInterface(), BackwardsCompatiblePublicFunctions, OldHeaders); const FRigVMClientPatchResult PinDefaultValuePatchResult = GetRigVMClient()->PatchPinDefaultValues(); if(PinDefaultValuePatchResult.RequiresToMarkPackageDirty()) { (void)MarkPackageDirty(); bDirtyDuringLoad = true; } } #if WITH_EDITOR { TGuardValue GuardNotifications(bSuspendModelNotificationsForSelf, true); // refresh the graph such that the pin hierarchies matches their CPPTypeObject // this step is needed everytime we open a BP in the editor, b/c even after load // model data can change while the Control Rig BP is not opened // for example, if a user defined struct changed after BP load, // any pin that references the struct needs to be regenerated RefreshAllModels(); } // at this point we may still have links which are detached. we may or may not be able to // reattach them. GetRigVMClient()->ProcessDetachedLinks(); GetRigVMClientHost()->GetRigVMGraphFunctionHost()->GetRigVMGraphFunctionStore()->RemoveAllCompilationData(); // perform backwards compat value upgrades TArray GraphsToValidate = GetAllModels(); for (int32 GraphIndex = 0; GraphIndex < GraphsToValidate.Num(); GraphIndex++) { URigVMGraph* GraphToValidate = GraphsToValidate[GraphIndex]; if(GraphToValidate == nullptr) { continue; } for(URigVMNode* Node : GraphToValidate->GetNodes()) { URigVMController* Controller = GetOrCreateController(GraphToValidate); FRigVMControllerNotifGuard NotifGuard(Controller, true); Controller->RemoveUnusedOrphanedPins(Node, true); } for(URigVMNode* Node : GraphToValidate->GetNodes()) { // avoid function reference related validation for temp assets, a temp asset may get generated during // certain content validation process. It is usually just a simple file-level copy of the source asset // so these references are usually not fixed-up properly. Thus, it is meaningless to validate them. // They should not be allowed to dirty the source asset either. if (!GetPackage()->GetName().StartsWith("/Temp/")) { if(URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(Node)) { if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { BuildData->RegisterFunctionReference(FunctionReferenceNode); } } } } } CompileLog.Messages.Reset(); CompileLog.NumErrors = CompileLog.NumWarnings = 0; #endif } // remove invalid class objects that were parented to the rigvmbp object RemoveDeprecatedVMMemoryClass(); #if WITH_EDITOR if(GIsEditor) { // delay compilation until the package has been loaded FCoreUObjectDelegates::OnEndLoadPackage.AddRaw(this, &IRigVMAssetInterface::HandlePackageDone); } #else RecompileVMIfRequired(); #endif GetRigVMClientHost()->RequestRigVMInit(); if (!GetAssetVariant().Guid.IsValid()) { GetAssetVariant().Guid = FRigVMVariant::GenerateGUID(); } if (UPackage* Package = GetPackage()) { Package->SetDirtyFlag(bDirtyDuringLoad); } #if WITH_EDITOR // if we are running with -game we are in editor code, // but GIsEditor is turned off if(!GIsEditor) { HandlePackageDone(); } #endif // RigVMRegistry changes can be triggered when new user defined types(structs/enums) are added/removed // in which case we have to refresh the model FRigVMRegistry::Get().OnRigVMRegistryChanged().RemoveAll(this); FRigVMRegistry::Get().OnRigVMRegistryChanged().AddRaw(this, &IRigVMAssetInterface::OnRigVMRegistryChanged); } #if WITH_EDITORONLY_DATA void IRigVMAssetInterface::DeclareConstructClasses(TArray& OutConstructClasses, const UClass* SpecificSubclass) { OutConstructClasses.Add(FTopLevelAssetPath(URigVMController::StaticClass())); } #endif #if WITH_EDITOR void IRigVMAssetInterface::HandlePackageDone(const FEndLoadPackageContext& Context) { if (!Context.LoadedPackages.Contains(GetPackage())) { return; } HandlePackageDone(); } void IRigVMAssetInterface::HandlePackageDone() { FCoreUObjectDelegates::OnEndLoadPackage.RemoveAll(this); if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { if(URigVMFunctionLibrary* FunctionLibrary = GetRigVMClient()->GetFunctionLibrary()) { // for backwards compatibility load the function references from the // model's storage over to the centralized build data if(!FunctionLibrary->FunctionReferences_DEPRECATED.IsEmpty()) { // let's also update the asset data of the dependents IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked(); for(const TTuple< TObjectPtr, FRigVMFunctionReferenceArray >& Pair : FunctionLibrary->FunctionReferences_DEPRECATED) { TSoftObjectPtr FunctionKey(Pair.Key); for(int32 ReferenceIndex = 0; ReferenceIndex < Pair.Value.Num(); ReferenceIndex++) { // update the build data BuildData->RegisterFunctionReference(FunctionKey->GetFunctionIdentifier(), Pair.Value[ReferenceIndex]); // find all control rigs matching the reference node FAssetData AssetData = AssetRegistry.GetAssetByObjectPath( Pair.Value[ReferenceIndex].ToSoftObjectPath().GetWithoutSubPath()); // if the asset has never been loaded - make sure to load it once and mark as dirty if(AssetData.IsValid() && !AssetData.IsAssetLoaded()) { if(FRigVMAssetInterfacePtr Dependent = AssetData.GetAsset()) { if(Dependent != this) { (void)Dependent->MarkPackageDirty(); } } } } } FunctionLibrary->FunctionReferences_DEPRECATED.Reset(); (void)MarkPackageDirty(); } } // update the build data from the current function references const TArray ReferenceNodeDatas = GetReferenceNodeData(); for(const FRigVMReferenceNodeData& ReferenceNodeData : ReferenceNodeDatas) { BuildData->RegisterFunctionReference(ReferenceNodeData); } BuildData->ClearInvalidReferences(); } { const FRigVMCompileSettingsDuringLoadGuard Guard(GetVMCompileSettings()); RecompileVM(); } GetRigVMClientHost()->RequestRigVMInit(); BroadcastRigVMPackageDone(); } void IRigVMAssetInterface::BroadcastRigVMPackageDone() { TArray ArchetypeInstances = GetArchetypeInstances(true, false); for (UObject* Instance : ArchetypeInstances) { URigVMHost* InstanceHost = Cast(Instance); if (!URigVMHost::IsGarbageOrDestroyed(InstanceHost)) { InstanceHost->BroadCastEndLoadPackage(); } } } void IRigVMAssetInterface::RemoveDeprecatedVMMemoryClass() { TArray Objects; GetObjectsWithOuter(GetObject(), Objects, false); #if WITH_EDITORONLY_DATA OldMemoryStorageGeneratorClasses.Reserve(Objects.Num()); for (UObject* Object : Objects) { if (URigVMMemoryStorageGeneratorClass* DeprecatedClass = Cast(Object)) { // Making sure it is fully loaded before removing it to avoid ambiguity regarding load order DeprecatedClass->ConditionalPostLoad(); DeprecatedClass->Rename(nullptr, GetTransientPackage(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional); OldMemoryStorageGeneratorClasses.Add(DeprecatedClass); } } #endif } #endif void IRigVMAssetInterface::GenerateUserDefinedDependenciesData(FRigVMExtendedExecuteContext& Context) { if (URigVM* VM = GetVM(true)) { const TArray UserDefinedDependencies = VM->GetUserDefinedDependencies({ VM->GetDefaultMemoryByType(ERigVMMemoryType::Literal), VM->GetDefaultMemoryByType(ERigVMMemoryType::Work) }); GetUserDefinedStructGuidToPathName(true).Reset(); GetUserDefinedEnumToPathName(true).Reset(); GetUserDefinedTypesInUse(true).Reset(); for (const UObject* UserDefinedDependency : UserDefinedDependencies) { if (const UUserDefinedStruct* UserDefinedStruct = Cast(UserDefinedDependency)) { const FString GuidBasedName = RigVMTypeUtils::GetUniqueStructTypeName(UserDefinedStruct); GetUserDefinedStructGuidToPathName(true).Add(GuidBasedName, UserDefinedStruct); } else if (const UUserDefinedEnum* UserDefinedEnum = Cast(UserDefinedDependency)) { const FString EnumName = RigVMTypeUtils::CPPTypeFromEnum(UserDefinedEnum); GetUserDefinedEnumToPathName(true).Add(EnumName, UserDefinedEnum); } } } } void IRigVMAssetInterface::RecompileVM() { if(bIsCompiling) { return; } TGuardValue CompilingGuard(bIsCompiling, true); bErrorsDuringCompilation = false; FRigVMEdGraphDisplaySettings& RigGraphDisplaySettings = GetRigGraphDisplaySettings(); if(RigGraphDisplaySettings.bAutoDetermineRange) { RigGraphDisplaySettings.MinMicroSeconds = RigGraphDisplaySettings.LastMinMicroSeconds = DBL_MAX; RigGraphDisplaySettings.MaxMicroSeconds = RigGraphDisplaySettings.LastMaxMicroSeconds = (double)INDEX_NONE; } else if(RigGraphDisplaySettings.MaxMicroSeconds < RigGraphDisplaySettings.MinMicroSeconds) { RigGraphDisplaySettings.MinMicroSeconds = 0; RigGraphDisplaySettings.MaxMicroSeconds = 5; } RigGraphDisplaySettings.TotalMicroSeconds = 0.0; RigGraphDisplaySettings.MinMicroSecondsFrames.Reset(); RigGraphDisplaySettings.MaxMicroSecondsFrames.Reset(); RigGraphDisplaySettings.TotalMicroSecondsFrames.Reset(); URigVM* VM = GetVM(true); FRigVMExtendedExecuteContext* Context = GetRigVMExtendedExecuteContext(); if (VM && Context) { FRigVMClient* RigVMClient = GetRigVMClient(); TGuardValue ReentrantGuardSelf(bSuspendModelNotificationsForSelf, true); TGuardValue ReentrantGuardOthers(RigVMClient->bSuspendModelNotificationsForOthers, true); ResetEarlyExitInstruction(true); PreCompile(); GetObject()->Modify(false); VM->Reset(*Context); // Clear all Errors CompileLog.Messages.Reset(); CompileLog.NumErrors = CompileLog.NumWarnings = 0; TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* Graph : EdGraphs) { URigVMEdGraph* RigGraph = Cast(Graph); if (RigGraph == nullptr) { continue; } for (UEdGraphNode* GraphNode : Graph->Nodes) { if (URigVMEdGraphNode* RigVMEdGraphNode = Cast(GraphNode)) { RigVMEdGraphNode->ClearErrorInfo(); } } } URigVMCompiler* Compiler = URigVMCompiler::StaticClass()->GetDefaultObject(); GetVMCompileSettings().SetExecuteContextStruct(RigVMClient->GetDefaultExecuteContextStruct()); const FRigVMCompileSettings Settings = GetVMCompileSettings(); Compiler->Compile(Settings, RigVMClient->GetAllModels(false, false), GetOrCreateController(), VM, *Context, GetExternalVariables(false), &PinToOperandMap); VM->Initialize(*Context); GenerateUserDefinedDependenciesData(*Context); if (bErrorsDuringCompilation) { if(Settings.SurpressErrors) { Settings.Reportf(EMessageSeverity::Info, GetObject(), TEXT("Compilation Errors may be suppressed for ControlRigBlueprint: %s. See VM Compile Setting in Class Settings for more Details"), *GetObject()->GetName()); } bVMRecompilationRequired = false; if(VM) { VMCompiledEvent.Broadcast(GetObject(), VM, *Context); } return; } InitializeArchetypeInstances(); bVMRecompilationRequired = false; VMCompiledEvent.Broadcast(GetObject(), VM, *Context); } } void IRigVMAssetInterface::RecompileVMIfRequired() { if (bVMRecompilationRequired) { RecompileVM(); } } void IRigVMAssetInterface::RequestAutoVMRecompilation() { bVMRecompilationRequired = true; if (bAutoRecompileVM && VMRecompilationBracket == 0) { RecompileVMIfRequired(); } } void IRigVMAssetInterface::IncrementVMRecompileBracket() { VMRecompilationBracket++; } void IRigVMAssetInterface::DecrementVMRecompileBracket() { if (VMRecompilationBracket == 1) { if (bAutoRecompileVM) { RecompileVMIfRequired(); } VMRecompilationBracket = 0; } else if (VMRecompilationBracket > 0) { VMRecompilationBracket--; } } void IRigVMAssetInterface::RefreshAllModels(ERigVMLoadType InLoadType) { const bool bEnablePostLoadHashing = CVarRigVMEnablePostLoadHashing->GetBool(); GetRigVMClient()->RefreshAllModels(InLoadType, bEnablePostLoadHashing, bIsCompiling); } void IRigVMAssetInterface::OnRigVMRegistryChanged() { RefreshAllModels(); RebuildGraphFromModel(); // avoids slate crash RefreshAllNodes(); } void IRigVMAssetInterface::HandleReportFromCompiler(EMessageSeverity::Type InSeverity, UObject* InSubject, const FString& InMessage) { UObject* SubjectForMessage = InSubject; if(URigVMNode* ModelNode = Cast(SubjectForMessage)) { if(IRigVMAssetInterface* RigBlueprint = ModelNode->GetImplementingOuter()) { if(URigVMEdGraph* EdGraph = Cast(RigBlueprint->GetEdGraph(ModelNode->GetGraph()))) { if(UEdGraphNode* EdNode = EdGraph->FindNodeForModelNodeName(ModelNode->GetFName())) { SubjectForMessage = EdNode; } } } } FCompilerResultsLog* CurrentMessageLog = GetCurrentMessageLog(); FCompilerResultsLog* Log = CurrentMessageLog ? CurrentMessageLog : &CompileLog; if (InSeverity == EMessageSeverity::Error) { SetAssetStatus(ERigVMAssetStatus::RVMA_Error); (void)MarkPackageDirty(); // see UnitTest "ControlRig.Basics.OrphanedPins" to learn why errors are suppressed this way if (GetVMCompileSettings().SurpressErrors) { Log->bSilentMode = true; } if(InMessage.Contains(TEXT("@@"))) { Log->Error(*InMessage, SubjectForMessage); } else { Log->Error(*InMessage); } BroadCastReportCompilerMessage(InSeverity, InSubject, InMessage); // see UnitTest "ControlRig.Basics.OrphanedPins" to learn why errors are suppressed this way if (!GetVMCompileSettings().SurpressErrors) { FScriptExceptionHandler::Get().HandleException(ELogVerbosity::Error, *InMessage, *FString()); } bErrorsDuringCompilation = true; } else if (InSeverity == EMessageSeverity::Warning) { if(InMessage.Contains(TEXT("@@"))) { Log->Warning(*InMessage, SubjectForMessage); } else { Log->Warning(*InMessage); } BroadCastReportCompilerMessage(InSeverity, InSubject, InMessage); FScriptExceptionHandler::Get().HandleException(ELogVerbosity::Warning, *InMessage, *FString()); } else { if(InMessage.Contains(TEXT("@@"))) { Log->Note(*InMessage, SubjectForMessage); } else { Log->Note(*InMessage); } static const FString Error = TEXT("Error"); static const FString Warning = TEXT("Warning"); if(InMessage.Contains(Error, ESearchCase::IgnoreCase) || InMessage.Contains(Warning, ESearchCase::IgnoreCase)) { BroadCastReportCompilerMessage(InSeverity, InSubject, InMessage); } UE_LOG(LogRigVMDeveloper, Display, TEXT("%s"), *InMessage); } if (URigVMEdGraphNode* EdGraphNode = Cast(SubjectForMessage)) { EdGraphNode->SetErrorInfo(InSeverity, InMessage); EdGraphNode->bHasCompilerMessage = EdGraphNode->ErrorType <= int32(EMessageSeverity::Info); } } TArray IRigVMAssetInterface::GetReferencedFunctionHosts(bool bForceLoad) const { TArray ReferencedBlueprints; TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* EdGraph : EdGraphs) { for(UEdGraphNode* Node : EdGraph->Nodes) { if(URigVMEdGraphNode* RigNode = Cast(Node)) { if(URigVMFunctionReferenceNode* FunctionRefNode = Cast(RigNode->GetModelNode())) { IRigVMGraphFunctionHost* Host = nullptr; if (bForceLoad || FunctionRefNode->IsReferencedFunctionHostLoaded()) { // Load the function host Host = FunctionRefNode->GetReferencedFunctionHeader().GetFunctionHost(); } else if (bForceLoad || FunctionRefNode->IsReferencedNodeLoaded()) { // Load the reference library node if(const URigVMLibraryNode* ReferencedNode = FunctionRefNode->LoadReferencedNode()) { if(URigVMFunctionLibrary* ReferencedFunctionLibrary = ReferencedNode->GetLibrary()) { FSoftObjectPath FunctionHostPath = ReferencedFunctionLibrary->GetFunctionHostObjectPath(); if (UObject* FunctionHostObj = FunctionHostPath.TryLoad()) { Host = Cast(FunctionHostObj); } } } } if (Host != nullptr && Host != GetRigVMClientHost()->GetRigVMGraphFunctionHost().GetInterface()) { ReferencedBlueprints.Add(Host); } } } } } return ReferencedBlueprints; } #if WITH_EDITOR TArray IRigVMAssetInterface::GetReferenceNodeData() const { TArray Data; const TArray AllModels = GetAllModels(); for (URigVMGraph* ModelToVisit : AllModels) { for(URigVMNode* Node : ModelToVisit->GetNodes()) { if(URigVMFunctionReferenceNode* ReferenceNode = Cast(Node)) { Data.Add(FRigVMReferenceNodeData(ReferenceNode)); } } } return Data; } #endif void IRigVMAssetInterface::SetupDefaultObjectDuringCompilation(URigVMHost* InCDO) { InCDO->PostInitInstanceIfRequired(); InCDO->VMRuntimeSettings = GetVMRuntimeSettings(); } void IRigVMAssetInterface::MarkDirtyDuringLoad() { bDirtyDuringLoad = true; } bool IRigVMAssetInterface::IsMarkedDirtyDuringLoad() const { return bDirtyDuringLoad; } void IRigVMAssetInterface::RequestRigVMInit() const { TArray Instances = GetArchetypeInstances(true, true); for (UObject* Instance : Instances) { URigVMHost* RigVMHost = Cast(Instance); if (!URigVMHost::IsGarbageOrDestroyed(RigVMHost)) { RigVMHost->RequestInit(); } } } URigVMGraph* IRigVMAssetInterface::GetModel(const UEdGraph* InEdGraph) const { const FRigVMClient* RigVMClient = GetRigVMClient(); #if WITH_EDITORONLY_DATA if (InEdGraph != nullptr && InEdGraph == GetFunctionLibraryEdGraph()) { return RigVMClient->GetFunctionLibrary(); } #endif return RigVMClient->GetModel(InEdGraph); } URigVMGraph* IRigVMAssetInterface::GetModel(const FString& InNodePath) const { return GetRigVMClient()->GetModel(InNodePath); } URigVMGraph* IRigVMAssetInterface::GetDefaultModel() const { return GetRigVMClient()->GetDefaultModel(); } TArray IRigVMAssetInterface::GetAllModels() const { return GetRigVMClient()->GetAllModels(true, true); } URigVMFunctionLibrary* IRigVMAssetInterface::GetLocalFunctionLibrary() const { return GetRigVMClient()->GetFunctionLibrary(); } URigVMFunctionLibrary* IRigVMAssetInterface::GetOrCreateLocalFunctionLibrary(bool bSetupUndoRedo) { return GetRigVMClient()->GetOrCreateFunctionLibrary(bSetupUndoRedo); } URigVMGraph* IRigVMAssetInterface::AddModel(FString InName, bool bSetupUndoRedo, bool bPrintPythonCommand) { TGuardValue EnablePythonPrint(bSuspendPythonMessagesForRigVMClient, !bPrintPythonCommand); return GetRigVMClient()->AddModel(InName, bSetupUndoRedo, bPrintPythonCommand); } bool IRigVMAssetInterface::RemoveModel(FString InName, bool bSetupUndoRedo, bool bPrintPythonCommand) { TGuardValue EnablePythonPrint(bSuspendPythonMessagesForRigVMClient, !bPrintPythonCommand); return GetRigVMClient()->RemoveModel(InName, bSetupUndoRedo, bPrintPythonCommand); } FRigVMGetFocusedGraph& IRigVMAssetInterface::OnGetFocusedGraph() { return GetRigVMClient()->OnGetFocusedGraph(); } const FRigVMGetFocusedGraph& IRigVMAssetInterface::OnGetFocusedGraph() const { return GetRigVMClient()->OnGetFocusedGraph(); } URigVMHost* IRigVMAssetInterface::GetDebuggedRigVMHost() { return Cast(GetObjectBeingDebugged()); } URigVMGraph* IRigVMAssetInterface::GetFocusedModel() const { return GetRigVMClient()->GetFocusedModel(); } URigVMController* IRigVMAssetInterface::GetController(const URigVMGraph* InGraph) const { return GetRigVMClient()->GetController(InGraph); } URigVMController* IRigVMAssetInterface::GetControllerByName(const FString InGraphName) const { return GetRigVMClient()->GetControllerByName(InGraphName); } URigVMController* IRigVMAssetInterface::GetOrCreateController(URigVMGraph* InGraph) { return GetRigVMClient()->GetOrCreateController(InGraph); } URigVMController* IRigVMAssetInterface::GetController(const UEdGraph* InEdGraph) const { return GetRigVMClient()->GetController(InEdGraph); } URigVMController* IRigVMAssetInterface::GetOrCreateController(const UEdGraph* InEdGraph) { return GetRigVMClient()->GetOrCreateController(InEdGraph); } TArray IRigVMAssetInterface::GeneratePythonCommands(const FString InNewBlueprintName) { TArray InternalCommands; if(GetObject()->GetClass() == GetObject()->StaticClass()) { InternalCommands.Add(TEXT("import unreal")); InternalCommands.Add(TEXT("unreal.load_module('RigVMDeveloper')")); InternalCommands.Add(TEXT("blueprint = unreal.RigVMBlueprint()")); InternalCommands.Add(TEXT("hierarchy = blueprint.hierarchy")); InternalCommands.Add(TEXT("hierarchy_controller = hierarchy.get_controller()")); } InternalCommands.Add(TEXT("library = blueprint.get_local_function_library()")); InternalCommands.Add(TEXT("library_controller = blueprint.get_controller(library)")); InternalCommands.Add(TEXT("blueprint.set_auto_vm_recompile(False)")); // Add variables for (const FRigVMExternalVariable& ExternalVariable : GetExternalVariables(true)) { FString CPPType; UObject* CPPTypeObject = nullptr; RigVMTypeUtils::CPPTypeFromExternalVariable(ExternalVariable, CPPType, &CPPTypeObject); if (CPPTypeObject) { if (ExternalVariable.bIsArray) { CPPType = RigVMTypeUtils::ArrayTypeFromBaseType(CPPTypeObject->GetPathName()); } else { CPPType = CPPTypeObject->GetPathName(); } } // FName AddMemberVariable(const FName& InName, const FString& InCPPType, bool bIsPublic = false, bool bIsReadOnly = false, FString InDefaultValue = TEXT("")); InternalCommands.Add(FString::Printf(TEXT("blueprint.add_member_variable('%s', '%s', %s, %s)"), *ExternalVariable.Name.ToString(), *CPPType, ExternalVariable.bIsPublic ? TEXT("True") : TEXT("False"), ExternalVariable.bIsReadOnly ? TEXT("True") : TEXT("False"))); } // Create graphs { TArray AllModels = GetAllModels(); AllModels.RemoveAll([](const URigVMGraph* GraphToRemove) -> bool { return GraphToRemove->GetTypedOuter() != nullptr; }); // Find all graphs to process and sort them by dependencies TArray ProcessedGraphs; while (ProcessedGraphs.Num() < AllModels.Num()) { for (URigVMGraph* Graph : AllModels) { if (ProcessedGraphs.Contains(Graph)) { continue; } bool bFoundUnprocessedReference = false; for (auto Node : Graph->GetNodes()) { if (URigVMFunctionReferenceNode* Reference = Cast(Node)) { if (Reference->GetReferencedFunctionHeader().LibraryPointer.HostObject != GetRigVMClientHost()->GetRigVMGraphFunctionHost().GetObject()) { continue; } URigVMLibraryNode* LibraryNode = Cast(Reference->GetReferencedFunctionHeader().LibraryPointer.GetNodeSoftPath().ResolveObject()); if (!ProcessedGraphs.Contains(LibraryNode->GetContainedGraph())) { bFoundUnprocessedReference = true; break; } } else if (URigVMCollapseNode* CollapseNode = Cast(Node)) { if(!CollapseNode->IsA()) { if (!ProcessedGraphs.Contains(CollapseNode->GetContainedGraph())) { bFoundUnprocessedReference = true; break; } } } } if (!bFoundUnprocessedReference) { ProcessedGraphs.Add(Graph); } } } // Dump python commands for each graph for (URigVMGraph* Graph : ProcessedGraphs) { if (Graph->IsA()) { continue; } URigVMController* Controller = GetController(Graph); if (Graph->GetParentGraph()) { // Add them all as functions (even collapsed graphs) // The controller will deal with deleting collapsed graph function when it creates the collapse node { // Add Function InternalCommands.Add(FString::Printf(TEXT("function_%s = library_controller.add_function_to_library('%s', mutable=%s)\ngraph = function_%s.get_contained_graph()"), *RigVMPythonUtils::PythonizeName(Graph->GetGraphName()), *Graph->GetGraphName(), Graph->GetEntryNode()->IsMutable() ? TEXT("True") : TEXT("False"), *RigVMPythonUtils::PythonizeName(Graph->GetGraphName()))); URigVMLibraryNode* LibraryNode = Cast(Graph->GetOuter()); InternalCommands.Add(FString::Printf(TEXT("library_controller.set_node_category_by_name('%s', '%s')"), *Graph->GetGraphName(), *LibraryNode->GetNodeCategory())); InternalCommands.Add(FString::Printf(TEXT("library_controller.set_node_keywords_by_name('%s', '%s')"), *Graph->GetGraphName(), *LibraryNode->GetNodeKeywords() )); InternalCommands.Add(FString::Printf(TEXT("library_controller.set_node_description_by_name('%s', '%s')"), *Graph->GetGraphName(), *LibraryNode->GetNodeDescription())); InternalCommands.Add(FString::Printf(TEXT("library_controller.set_node_color_by_name('%s', %s)"), *Graph->GetGraphName(), *RigVMPythonUtils::LinearColorToPythonString(LibraryNode->GetNodeColor()) )); URigVMFunctionEntryNode* EntryNode = Graph->GetEntryNode(); URigVMFunctionReturnNode* ReturnNode = Graph->GetReturnNode(); // Set Entry and Return nodes in the correct position { //bool SetNodePositionByName(const FName& InNodeName, const FVector2D& InPosition, bool bSetupUndoRedo = true, bool bMergeUndoAction = false); InternalCommands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_position_by_name('Entry', unreal.Vector2D(%f, %f))"), *Graph->GetGraphName(), EntryNode->GetPosition().X, EntryNode->GetPosition().Y)); InternalCommands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_position_by_name('Return', unreal.Vector2D(%f, %f))"), *Graph->GetGraphName(), ReturnNode->GetPosition().X, ReturnNode->GetPosition().Y)); } // Add Exposed Pins { bool bHitFirstExecute = false; bool bRenamedExecute = false; for (auto Pin : EntryNode->GetPins()) { if (Pin->GetDirection() != ERigVMPinDirection::Output) { continue; } if(Pin->IsExecuteContext()) { if(!bHitFirstExecute) { bHitFirstExecute = true; if (Pin->GetName() != FRigVMStruct::ExecuteContextName.ToString()) { bRenamedExecute = true; InternalCommands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').rename_exposed_pin('%s', '%s')"), *Graph->GetGraphName(), *FRigVMStruct::ExecuteContextName.ToString(), *Pin->GetName())); } continue; } } // FName AddExposedPin(const FName& InPinName, ERigVMPinDirection InDirection, const FString& InCPPType, const FName& InCPPTypeObjectPath, const FString& InDefaultValue, bool bSetupUndoRedo = true); InternalCommands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_exposed_pin('%s', %s, '%s', '%s', '%s')"), *Graph->GetGraphName(), *Pin->GetName(), *RigVMPythonUtils::EnumValueToPythonString((int64)ERigVMPinDirection::Input), *Pin->GetCPPType(), Pin->GetCPPTypeObject() ? *Pin->GetCPPTypeObject()->GetPathName() : TEXT(""), *Pin->GetDefaultValue())); } bHitFirstExecute = false; for (auto Pin : ReturnNode->GetPins()) { if (Pin->GetDirection() != ERigVMPinDirection::Input) { continue; } if(Pin->IsExecuteContext()) { if(!bHitFirstExecute) { bHitFirstExecute = true; if (!bRenamedExecute && Pin->GetName() != FRigVMStruct::ExecuteContextName.ToString()) { bRenamedExecute = true; InternalCommands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').rename_exposed_pin('%s', '%s')"), *Graph->GetGraphName(), *FRigVMStruct::ExecuteContextName.ToString(), *Pin->GetName())); } continue; } } // FName AddExposedPin(const FName& InPinName, ERigVMPinDirection InDirection, const FString& InCPPType, const FName& InCPPTypeObjectPath, const FString& InDefaultValue, bool bSetupUndoRedo = true); InternalCommands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_exposed_pin('%s', %s, '%s', '%s', '%s')"), *Graph->GetGraphName(), *Pin->GetName(), *RigVMPythonUtils::EnumValueToPythonString((int64)ERigVMPinDirection::Output), *Pin->GetCPPType(), Pin->GetCPPTypeObject() ? *Pin->GetCPPTypeObject()->GetPathName() : TEXT(""), *Pin->GetDefaultValue())); } } } } else if(Graph != GetDefaultModel()) { InternalCommands.Add(FString::Printf(TEXT("blueprint.add_model('%s')"), *Graph->GetName())); } InternalCommands.Append(Controller->GeneratePythonCommands()); } } InternalCommands.Add(TEXT("blueprint.set_auto_vm_recompile(True)")); // Split multiple commands into different array elements TArray InnerFunctionCmds; for (FString Cmd : InternalCommands) { FString Left, Right=Cmd; while (Right.Split(TEXT("\n"), &Left, &Right)) { InnerFunctionCmds.Add(Left); } InnerFunctionCmds.Add(Right); } // Define a function and insert all the commands // We do not want to pollute the global state with our definitions TArray Commands; Commands.Add(FString::Printf(TEXT("import unreal\n" "def create_asset():\n"))); for (const FString& InnerCmd : InnerFunctionCmds) { Commands.Add(FString::Printf(TEXT("\t%s"), *InnerCmd)); } Commands.Add(TEXT("create_asset()\n")); return Commands; } TArray IRigVMAssetInterface::GetExternalDependenciesForCategory(const FName& InCategory) const { TArray Dependencies; if(const FRigVMClient* Client = GetRigVMClient()) { CollectExternalDependencies(Dependencies, InCategory, Client); } if(const TScriptInterface FunctionHost = GetRigVMClientHost()->GetRigVMGraphFunctionHost()) { if(const FRigVMGraphFunctionStore* FunctionStore = FunctionHost->GetRigVMGraphFunctionStore()) { CollectExternalDependencies(Dependencies, InCategory, FunctionStore); } } #if WITH_EDITOR const TArray MemberVariables = GetAssetVariables(); for(const FRigVMGraphVariableDescription& MemberVariable : MemberVariables) { CollectExternalDependenciesForCPPTypeObject(Dependencies, InCategory, MemberVariable.CPPTypeObject.Get()); } #endif return Dependencies; } #if WITH_EDITOR void IRigVMAssetInterface::AddVariableSearchMetaDataInfo(const FName InVariableName, TArray& OutTaggedMetaData) const { for (const FRigVMExternalVariable& Variable : GetExternalVariables(true)) { if (Variable.Name.IsEqual(InVariableName)) { OutTaggedMetaData.Emplace(FRigVMSearchTags::FiB_Name, FText::FromName(InVariableName)); const FEdGraphPinType& Type = RigVMTypeUtils::PinTypeFromExternalVariable(Variable); OutTaggedMetaData.Emplace(FRigVMSearchTags::FiB_PinCategory, FText::FromName(Type.PinCategory)); OutTaggedMetaData.Emplace(FRigVMSearchTags::FiB_PinSubCategory, FText::FromName(Type.PinSubCategory)); if (Type.PinSubCategoryObject.IsValid()) { OutTaggedMetaData.Emplace(FRigVMSearchTags::FiB_ObjectClass, FText::FromString(Type.PinSubCategoryObject->GetPathName())); } OutTaggedMetaData.Emplace(FRigVMSearchTags::FiB_IsArray, FText::Format(LOCTEXT("RigVMNodePinIsArray", "{0}"), Type.IsArray() ? 1 : 0)); } } } #endif URigVMGraph* IRigVMAssetInterface::GetTemplateModel(bool bIsFunctionLibrary) { #if WITH_EDITORONLY_DATA if (TemplateModel == nullptr) { if (bIsFunctionLibrary) { TemplateModel = NewObject(GetObject(), TEXT("TemplateFunctionLibrary")); } else { TemplateModel = NewObject(GetObject(), TEXT("TemplateModel")); } TemplateModel->SetFlags(RF_Transient); TemplateModel->SetExecuteContextStruct(GetRigVMClient()->GetDefaultExecuteContextStruct()); } return TemplateModel; #else return nullptr; #endif } URigVMController* IRigVMAssetInterface::GetTemplateController(bool bIsFunctionLibrary) { #if WITH_EDITORONLY_DATA if (TemplateController == nullptr) { TemplateController = NewObject(GetObject(), TEXT("TemplateController")); TemplateController->SetGraph(GetTemplateModel(bIsFunctionLibrary)); TemplateController->EnableReporting(false); TemplateController->SetFlags(RF_Transient); TemplateController->SetSchemaClass(GetRigVMClient()->GetDefaultSchemaClass()); } return TemplateController; #else return nullptr; #endif } UEdGraph* IRigVMAssetInterface::GetEdGraph(const URigVMGraph* InModel) const { return Cast(GetEditorObjectForRigVMGraph(InModel)); } UEdGraph* IRigVMAssetInterface::GetEdGraph(const FString& InNodePath) const { if (URigVMGraph* ModelForNodePath = GetModel(InNodePath)) { return GetEdGraph(ModelForNodePath); } return nullptr; } bool IRigVMAssetInterface::IsFunctionPublic(const FName& InFunctionName) const { return GetLocalFunctionLibrary()->IsFunctionPublic(InFunctionName); } void IRigVMAssetInterface::MarkFunctionPublic(const FName& InFunctionName, bool bIsPublic) { if(IsFunctionPublic(InFunctionName) == bIsPublic) { return; } URigVMController* Controller = GetRigVMClient()->GetOrCreateController(GetLocalFunctionLibrary()); Controller->MarkFunctionAsPublic(InFunctionName, bIsPublic); } TArray IRigVMAssetInterface::GetDependencies(bool bRecursive) const { TArray Dependencies; TArray Graphs = GetAllModels(); for(URigVMGraph* Graph : Graphs) { for(URigVMNode* Node : Graph->GetNodes()) { if(URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(Node)) { if(const URigVMLibraryNode* LibraryNode = FunctionReferenceNode->LoadReferencedNode()) { if(IRigVMAssetInterface* DependencyBlueprint = LibraryNode->GetImplementingOuter()) { if(DependencyBlueprint != this) { if(!Dependencies.Contains(DependencyBlueprint)) { Dependencies.Add(DependencyBlueprint); if(bRecursive) { TArray ChildDependencies = DependencyBlueprint->GetDependencies(true); for(IRigVMAssetInterface* ChildDependency : ChildDependencies) { Dependencies.AddUnique(ChildDependency); } } } } } } } } } return Dependencies; } TArray IRigVMAssetInterface::GetDependentAssets() const { TArray Dependents; TArray AssetPaths; if(URigVMFunctionLibrary* FunctionLibrary = GetRigVMClient()->GetFunctionLibrary()) { IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked(); TArray Functions = FunctionLibrary->GetFunctions(); for(URigVMLibraryNode* Function : Functions) { const FName FunctionName = Function->GetFName(); if(IsFunctionPublic(FunctionName)) { TArray> References = FunctionLibrary->GetReferencesForFunction(FunctionName); for(const TSoftObjectPtr& Reference : References) { if (const URigVMFunctionReferenceNode* ReferencePtr = Reference.Get()) { if (const FRigVMAssetInterfacePtr ControlRigBlueprint = IRigVMAssetInterface::GetInterfaceOuter(ReferencePtr)) { const TSoftObjectPtr Blueprint = ControlRigBlueprint.GetObject(); const FSoftObjectPath AssetPath = Blueprint.ToSoftObjectPath(); if(AssetPath.GetLongPackageName().StartsWith(TEXT("/Engine/Transient"))) { continue; } if(!AssetPaths.Contains(AssetPath)) { AssetPaths.Add(AssetPath); const FAssetData AssetData = AssetRegistry.GetAssetByObjectPath(AssetPath); if(AssetData.IsValid()) { Dependents.Add(AssetData); } } } } } } } } return Dependents; } TArray IRigVMAssetInterface::GetDependentResolvedAssets(bool bRecursive, bool bOnlyLoaded) const { TArray Assets = GetDependentAssets(); TArray Dependents; for(const FAssetData& Asset : Assets) { if (!bOnlyLoaded || Asset.IsAssetLoaded()) { if(FRigVMAssetInterfacePtr Dependent = Asset.GetAsset()) { if(!Dependents.Contains(Dependent.GetInterface())) { Dependents.Add(Dependent.GetInterface()); if(bRecursive && Dependent != this) { TArray ParentDependents = Dependent->GetDependentResolvedAssets(true); for(IRigVMAssetInterface* ParentDependent : ParentDependents) { Dependents.AddUnique(ParentDependent); } } } } } } return Dependents; } void IRigVMAssetInterface::BroadcastRefreshEditor() { return RefreshEditorEvent.Broadcast(GetObject()); } FOnRigVMRequestInspectMemoryStorage& IRigVMAssetInterface::OnRequestInspectMemoryStorage() { return OnRequestInspectMemoryStorageEvent; } void IRigVMAssetInterface::RequestInspectMemoryStorage(const TArray& InMemoryStorageStructs) const { OnRequestInspectMemoryStorageEvent.Broadcast(InMemoryStorageStructs); } void IRigVMAssetInterface::SetObjectBeingDebugged(UObject* NewObject) { URigVMHost* PreviousRigBeingDebugged = Cast(GetObjectBeingDebugged()); if (PreviousRigBeingDebugged && PreviousRigBeingDebugged != NewObject) { PreviousRigBeingDebugged->DrawInterface.Reset(); PreviousRigBeingDebugged->RigVMLog = nullptr; #if WITH_EDITOR PreviousRigBeingDebugged->bIsBeingDebugged = false; PreviousRigBeingDebugged->GetDebugInfo().Reset(); #endif } SetObjectBeingDebuggedSuper(NewObject); #if WITH_EDITOR if(URigVMHost* NewRigBeingDebugged = Cast(NewObject)) { NewRigBeingDebugged->bIsBeingDebugged = true; } ResetEarlyExitInstruction(true); #endif } void IRigVMAssetInterface::PostTransacted(const FTransactionObjectEvent& TransactionEvent) { DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC() if (TransactionEvent.GetEventType() == ETransactionObjectEventType::UndoRedo) { // The action stack undo/redo transaction should always execute first // It already knows whether or not it has already executed or not GetRigVMClient()->GetOrCreateActionStack()->PostTransacted(TransactionEvent); } PostTransactedSuper(TransactionEvent); if (TransactionEvent.GetEventType() == ETransactionObjectEventType::UndoRedo) { TArray PropertiesChanged = TransactionEvent.GetChangedProperties(); if (PropertiesChanged.Contains(TEXT("VMRuntimeSettings"))) { PropagateRuntimeSettingsFromBPToInstances(); } if (PropertiesChanged.Contains(TEXT("NewVariables"))) { if (RefreshEditorEvent.IsBound()) { RefreshEditorEvent.Broadcast(GetObject()); } (void)MarkPackageDirty(); } if (PropertiesChanged.Contains(TEXT("RigVMClient")) || PropertiesChanged.Contains(TEXT("UbergraphPages"))) { GetUberGraphs().RemoveAll([](const UEdGraph* UberGraph) -> bool { return UberGraph == nullptr || !IsValid(UberGraph); }); GetRigVMClient()->PostTransacted(TransactionEvent); RecompileVM(); (void)MarkPackageDirty(); } } } void IRigVMAssetInterface::ReplaceDeprecatedNodes() { TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* EdGraph : EdGraphs) { EdGraph->Schema = GetRigVMClientHost()->GetRigVMEdGraphSchemaClass(); } ReplaceDeprecatedNodesSuper(); } void IRigVMAssetInterface::PreDuplicate(FObjectDuplicationParameters& DupParams) { PreDuplicateSuper(DupParams); PreDuplicateAssetPath = GetObject()->GetPathName(); PreDuplicateHostPath = GetRigVMClientHost()->GetRigVMGraphFunctionHost().GetObject(); // look for graphs which are no longer reachable and remove them. this can happen in case // one of the default sub objects got renamed. FRigVMClient* RigVMClient = GetRigVMClient(); TArray AllModels = RigVMClient->GetAllModels(true, true); for (TObjectIterator RigVMIt; RigVMIt; ++RigVMIt) { URigVMGraph* Graph = *RigVMIt; if (Graph->IsInOuter(GetObject())) { if (!AllModels.Contains(Graph)) { if (URigVMController* Controller = RigVMClient->GetOrCreateController(RigVMClient->GetDefaultModel())) { Controller->DestroyObject(Graph); } } } } } void IRigVMAssetInterface::ReplaceFunctionIdentifiers(const FString& InOldAssetPath, const FString& InNewAssetPath) { if (!InOldAssetPath.Equals(GetObject()->GetPathName())) { const FString OldLibraryPath = InOldAssetPath + TEXT(":"); const FString NewLibraryPath = InNewAssetPath + TEXT(":"); const FString OldHostPath = InOldAssetPath + TEXT("_C"); const FString NewHostPath = InNewAssetPath + TEXT("_C"); auto ReplaceIdentifier = [OldLibraryPath, NewLibraryPath, OldHostPath, NewHostPath](FRigVMGraphFunctionIdentifier& Identifier) { FString& LibraryNodePath = Identifier.GetLibraryNodePath(); FSoftObjectPath& HostPath = Identifier.HostObject; FString HostPathStr = HostPath.ToString(); if(LibraryNodePath.StartsWith(OldLibraryPath, ESearchCase::CaseSensitive)) { LibraryNodePath = NewLibraryPath + LibraryNodePath.Mid(OldLibraryPath.Len()); } if(HostPathStr.StartsWith(OldHostPath, ESearchCase::CaseSensitive)) { HostPathStr = NewHostPath + HostPathStr.Mid(OldHostPath.Len()); HostPath = HostPathStr; } }; // Replace identifiers in store if(TScriptInterface CRGeneratedClass = GetRigVMClientHost()->GetRigVMGraphFunctionHost()) { FRigVMGraphFunctionStore* Store = CRGeneratedClass->GetRigVMGraphFunctionStore(); for (int32 i=0; i<2; ++i) { TArray& Functions = (i == 0) ? Store->PublicFunctions : Store->PrivateFunctions; for (FRigVMGraphFunctionData& Data : Functions) { ReplaceIdentifier(Data.Header.LibraryPointer); for (TPair& Pair : Data.Header.Dependencies) { ReplaceIdentifier(Pair.Key); } } } } // Replace identifiers in function references TArray AllModels = GetRigVMClient()->GetAllModels(true, true); for(URigVMGraph* Model : AllModels) { for (URigVMNode* Node : Model->GetNodes()) { if (URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(Node)) { ReplaceIdentifier(FunctionReferenceNode->ReferencedFunctionHeader.LibraryPointer); } } } } } void IRigVMAssetInterface::PostDuplicate(bool bDuplicateForPIE) { // assuming PostDuplicate is always followed by a PostLoad: // so theoretically, PostDuplicate just makes corrections to the serialized data and does nothing more, // while PostLoad looks at whatever is serialized and load it into memory according to the version of the editor used // note: how to know if we have corrected everything? // ans: check the reference viewer for the duplicated BP and make sure that the original BP does not appear in there { // pause compilation because we need to patch some stuff first TGuardValue CompilingGuard(bIsCompiling, true); // this will create the new EMPTY generated class to be used as the function store for this BP // it will be filled during PostLoad based on the graph model PostDuplicateSuper(bDuplicateForPIE); } const FString OldAssetPath = PreDuplicateAssetPath.ToString(); const FString NewAssetPath = GetObject()->GetPathName(); ReplaceFunctionIdentifiers(OldAssetPath, NewAssetPath); PreDuplicateAssetPath.Reset(); PreDuplicateHostPath.Reset(); SplitAssetVariant(); TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* EdGraph : EdGraphs) { if (URigVMEdGraph* RigVMEdGraph = Cast(EdGraph)) { RigVMEdGraph->CachedModelGraph.Reset(); } } MarkAssetAsStructurallyModified(); } void IRigVMAssetInterface::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const { if (CachedAssetTags.IsEmpty()) { GetAssetRegistryTagsSuper(Context); CachedAssetTags.Reset(Context.GetNumTags()); Context.EnumerateTags([this](const UObject::FAssetRegistryTag& Tag) { CachedAssetTags.Add(Tag); }); } else { for (const UObject::FAssetRegistryTag& Tag : CachedAssetTags) { Context.AddTag(Tag); } } } FRigVMGraphModifiedEvent& IRigVMAssetInterface::OnModified() { return ModifiedEvent; } FOnRigVMCompiledEvent& IRigVMAssetInterface::OnVMCompiled() { return VMCompiledEvent; } URigVMHost* IRigVMAssetInterface::CreateRigVMHostImpl() { RecompileVMIfRequired(); URigVMHost* Host = CreateRigVMHostSuper(GetObject()); Host->Initialize(true); return Host; } TArray IRigVMAssetInterface::GetAvailableRigVMStructs() const { TArray Structs; UStruct* BaseStruct = FRigVMStruct::StaticStruct(); for (const FRigVMFunction& Function : FRigVMRegistry::Get().GetFunctions()) { if (Function.Struct) { if (Function.Struct->IsChildOf(BaseStruct)) { Structs.Add(Function.Struct); // todo: filter by available types // todo: filter by execute context } } } return Structs; } #if WITH_EDITOR FRigVMVariantRef IRigVMAssetInterface::GetAssetVariantRefImpl() const { return FRigVMVariantRef(FSoftObjectPath(GetObject()), GetAssetVariant()); } bool IRigVMAssetInterface::SplitAssetVariantImpl() { if(GetMatchingVariants().IsEmpty()) { return false; } FScopedTransaction Transaction(LOCTEXT("SplitAssetVariant", "Split Asset Variant")); GetObject()->Modify(); // prefer the path based (deterministic) guid - and fall back on random. const FGuid PathBasedGuid = FRigVMVariant::GenerateGUID(GetObject()->GetPathName()); if(PathBasedGuid != GetAssetVariant().Guid) { GetAssetVariant().Guid = PathBasedGuid; } else { GetAssetVariant().Guid = FRigVMVariant::GenerateGUID(); } return true; } bool IRigVMAssetInterface::JoinAssetVariantImpl(const FGuid& InGuid) { if(GetAssetVariant().Guid != InGuid) { FScopedTransaction Transaction(LOCTEXT("JoinAssetVariant", "Join Asset Variant")); GetObject()->Modify(); GetAssetVariant().Guid = InGuid; return true; } return false; } TArray IRigVMAssetInterface::GetMatchingVariantsImpl() const { if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { TArray Variants = BuildData->FindAssetVariantRefs(GetAssetVariant().Guid); const FRigVMVariantRef MyVariantRef = FRigVMVariantRef(GetObject()->GetPathName(), GetAssetVariant()); Variants.RemoveAll([MyVariantRef](const FRigVMVariantRef& VariantRef) -> bool { return VariantRef == MyVariantRef; }); return Variants; } return TArray(); } #endif void IRigVMAssetInterface::RebuildGraphFromModel() { DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC() TGuardValue SelfGuard(bSuspendModelNotificationsForSelf, true); TGuardValue ClientIgnoreModificationsGuard(GetRigVMClient()->bIgnoreModelNotifications, true); verify(GetOrCreateController()); TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* Graph : EdGraphs) { TArray Nodes = Graph->Nodes; for (UEdGraphNode* Node : Nodes) { Graph->RemoveNode(Node); } if (URigVMEdGraph* RigGraph = Cast(Graph)) { if (RigGraph->bIsFunctionDefinition) { GetFunctionGraphs().Remove(RigGraph); } } } if(GetFunctionLibraryEdGraph() && GetRigVMClient()->GetFunctionLibrary()) { GetFunctionLibraryEdGraph()->ModelNodePath = GetRigVMClient()->GetFunctionLibrary()->GetNodePath(); } TArray RigGraphs = GetRigVMClient()->GetAllModels(true, true); for (int32 RigGraphIndex = 0; RigGraphIndex < RigGraphs.Num(); RigGraphIndex++) { GetOrCreateController(RigGraphs[RigGraphIndex])->ResendAllNotifications(); } for (int32 RigGraphIndex = 0; RigGraphIndex < RigGraphs.Num(); RigGraphIndex++) { URigVMGraph* RigGraph = RigGraphs[RigGraphIndex]; for (URigVMNode* RigNode : RigGraph->GetNodes()) { if (URigVMCollapseNode* CollapseNode = Cast(RigNode)) { CreateEdGraphForCollapseNodeIfNeeded(CollapseNode, true); } } } } void IRigVMAssetInterface::Notify(ERigVMGraphNotifType InNotifType, UObject* InSubject) { GetOrCreateController()->Notify(InNotifType, InSubject); } void IRigVMAssetInterface::HandleModifiedEvent(ERigVMGraphNotifType InNotifType, URigVMGraph* InGraph, UObject* InSubject) { DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC() #if WITH_EDITOR if (bSuspendAllNotifications) { return; } // since it's possible that a notification will be already sent / forwarded to the // listening objects within the switch statement below - we keep a flag to mark // the notify for still pending (or already sent) bool bNotifForOthersPending = true; auto MarkBlueprintAsStructurallyModified = [this]() { if(VMRecompilationBracket == 0) { if(bMarkBlueprintAsStructurallyModifiedPending) { bMarkBlueprintAsStructurallyModifiedPending = false; MarkAssetAsStructurallyModified(bSkipDirtyBlueprintStatus); } } else { bMarkBlueprintAsStructurallyModifiedPending = true; } }; if (!bSuspendModelNotificationsForSelf) { switch (InNotifType) { case ERigVMGraphNotifType::InteractionBracketOpened: { IncrementVMRecompileBracket(); break; } case ERigVMGraphNotifType::InteractionBracketClosed: case ERigVMGraphNotifType::InteractionBracketCanceled: { DecrementVMRecompileBracket(); MarkBlueprintAsStructurallyModified(); break; } case ERigVMGraphNotifType::PinDefaultValueChanged: { if (URigVMPin* Pin = Cast(InSubject)) { bool bRequiresRecompile = false; URigVMPin* RootPin = Pin->GetRootPin(); static const FString ConstSuffix = TEXT(":Const"); const FString PinHash = RootPin->GetPinPath(true) + ConstSuffix; if (const FRigVMOperand* Operand = PinToOperandMap.Find(PinHash)) { FRigVMASTProxy RootPinProxy = FRigVMASTProxy::MakeFromUObject(RootPin); if(const FRigVMExprAST* Expression = InGraph->GetRuntimeAST()->GetExprForSubject(RootPinProxy)) { bRequiresRecompile = Expression->NumParents() > 1; } else { bRequiresRecompile = true; } // If we are only changing a pin's default value, we need to // check if there is a connection to a sub-pin of the root pin // that has its value is directly stored in the root pin due to optimization, if so, // we want to recompile to make sure the pin's new default value and values from other connections // are both applied to the root pin because GetDefaultValue() alone cannot account for values // from other connections. if(!bRequiresRecompile) { TArray SourcePins = RootPin->GetLinkedSourcePins(true); for (const URigVMPin* SourcePin : SourcePins) { // check if the source node is optimized out, if so, only a recompile will allows us // to re-query its value. FRigVMASTProxy SourceNodeProxy = FRigVMASTProxy::MakeFromUObject(SourcePin->GetNode()); if (InGraph->GetRuntimeAST()->GetExprForSubject(SourceNodeProxy) == nullptr) { bRequiresRecompile = true; break; } } } if(!bRequiresRecompile) { const FString DefaultValue = RootPin->GetDefaultValue(); URigVM* VM = GetVM(true); FRigVMExtendedExecuteContext* Context = GetRigVMExtendedExecuteContext(); if (VM != nullptr && Context != nullptr) { VM->SetPropertyValueFromString(*Context, *Operand, DefaultValue); } TArray ArchetypeInstances = GetArchetypeInstances(false, false); for (UObject* ArchetypeInstance : ArchetypeInstances) { URigVMHost* InstancedHost = Cast(ArchetypeInstance); if (!URigVMHost::IsGarbageOrDestroyed(InstancedHost)) { if (!InstancedHost->HasAllFlags(RF_ClassDefaultObject)) { if (InstancedHost->GetVM()) { InstancedHost->VM->SetPropertyValueFromString(InstancedHost->GetRigVMExtendedExecuteContext(), *Operand, DefaultValue); } } } } if (Pin->IsDefinedAsConstant() || Pin->GetRootPin()->IsDefinedAsConstant()) { // re-init the rigs GetRigVMClientHost()->RequestRigVMInit(); bRequiresRecompile = true; } } } else { bRequiresRecompile = true; } if(bRequiresRecompile) { RequestAutoVMRecompilation(); } } (void)MarkPackageDirty(); break; } case ERigVMGraphNotifType::NodeAdded: case ERigVMGraphNotifType::NodeRemoved: { const bool bAdded = InNotifType == ERigVMGraphNotifType::NodeAdded; if (URigVMHost::IsGarbageOrDestroyed(InSubject)) { break; } if (URigVMCollapseNode* CollapseNode = Cast(InSubject)) { if (bAdded) { // If the controller for this graph already exist, make sure it is referencing the correct graph if (URigVMController* Controller = GetRigVMClient()->GetController(CollapseNode->GetContainedGraph())) { Controller->SetGraph(CollapseNode->GetContainedGraph()); } CreateEdGraphForCollapseNodeIfNeeded(CollapseNode); } else { bNotifForOthersPending = !RemoveEdGraphForCollapseNode(CollapseNode, true); // Cannot remove from the Controllers array because we would lose the action stack on that graph // Controllers.Remove(CollapseNode->GetContainedGraph(); } RequestAutoVMRecompilation(); ResetEarlyExitInstruction(true); (void)MarkPackageDirty(); MarkBlueprintAsStructurallyModified(); break; } if (URigVMNode* RigVMNode = Cast(InSubject)) { if(RigVMNode->IsEvent() && RigVMNode->GetGraph()->IsRootGraph()) { // let the UI know the title for the graph may have changed. GetRigVMClient()->NotifyOuterOfPropertyChange(); if(URigVMEdGraph* EdGraph = Cast(GetEdGraph(RigVMNode->GetGraph()))) { // decide if this graph should be renameable const int32 NumberOfEvents = static_cast(Algo::CountIf(RigVMNode->GetGraph()->GetNodes(), [](const URigVMNode* NodeToCount) -> bool { return NodeToCount->IsEvent() && NodeToCount->CanOnlyExistOnce(); })); EdGraph->bAllowRenaming = NumberOfEvents != 1; } } } // fall through to the next case } case ERigVMGraphNotifType::LinkAdded: case ERigVMGraphNotifType::LinkRemoved: case ERigVMGraphNotifType::PinArraySizeChanged: case ERigVMGraphNotifType::PinDirectionChanged: { RequestAutoVMRecompilation(); ResetEarlyExitInstruction(true); (void)MarkPackageDirty(); // we don't need to mark the blueprint as modified since we only // need to recompile the VM here - unless we don't auto recompile. if(!bAutoRecompileVM) { MarkBlueprintAsStructurallyModified(); } break; } case ERigVMGraphNotifType::PinWatchedChanged: { if (URigVMHost* DebuggedHost = Cast(GetObjectBeingDebugged())) { URigVMPin* Pin = CastChecked(InSubject)->GetRootPin(); URigVMCompiler* Compiler = URigVMCompiler::StaticClass()->GetDefaultObject(); TSharedPtr RuntimeAST = GetDefaultModel()->GetRuntimeAST(); if(Pin->RequiresWatch()) { // check if the node is optimized out - in that case we need to recompile if(DebuggedHost->GetVM()->GetByteCode().GetFirstInstructionIndexForSubject(Pin->GetNode()) == INDEX_NONE) { RequestAutoVMRecompilation(); (void)MarkPackageDirty(); } else { if(DebuggedHost->GetDebugMemory()->Num() == 0) { RequestAutoVMRecompilation(); (void)MarkPackageDirty(); } else { Compiler->MarkDebugWatch(GetVMCompileSettings(), true, Pin, DebuggedHost->GetVM(), &PinToOperandMap, RuntimeAST); } } } else { Compiler->MarkDebugWatch(GetVMCompileSettings(), false, Pin, DebuggedHost->GetVM(), &PinToOperandMap, RuntimeAST); } } // break; fall through } case ERigVMGraphNotifType::PinTypeChanged: case ERigVMGraphNotifType::PinIndexChanged: { if (URigVMPin* ModelPin = Cast(InSubject)) { if (UEdGraph* EdGraph = GetEdGraph(InGraph)) { if (URigVMEdGraph* Graph = Cast(EdGraph)) { if (UEdGraphNode* EdNode = Graph->FindNodeForModelNodeName(ModelPin->GetNode()->GetFName())) { if (UEdGraphPin* EdPin = EdNode->FindPin(*ModelPin->GetPinPath())) { if (ModelPin->RequiresWatch()) { AddPinWatch(EdPin); } else { RemovePinWatch(EdPin); } if(InNotifType == ERigVMGraphNotifType::PinWatchedChanged) { return; } RequestAutoVMRecompilation(); (void)MarkPackageDirty(); } } } } } // fall through another time } case ERigVMGraphNotifType::PinAdded: case ERigVMGraphNotifType::PinRemoved: case ERigVMGraphNotifType::PinRenamed: { if (URigVMHost::IsGarbageOrDestroyed(InSubject)) { break; } // exposed pin changes like this (as well as type change etc) // require to mark the blueprint as structurally modified, // so that the instance actions work out. if (URigVMPin* ModelPin = Cast(InSubject)) { if(URigVMCollapseNode* CollapseNode = Cast(ModelPin->GetNode())) { if(Cast(CollapseNode->GetOuter())) { MarkBlueprintAsStructurallyModified(); } } } break; } case ERigVMGraphNotifType::PinBoundVariableChanged: case ERigVMGraphNotifType::VariableRemappingChanged: { RequestAutoVMRecompilation(); (void)MarkPackageDirty(); break; } case ERigVMGraphNotifType::NodeRenamed: { if (URigVMCollapseNode* CollapseNode = Cast(InSubject)) { FString NewNodePath = CollapseNode->GetNodePath(true /* recursive */); FString Left, Right = NewNodePath; URigVMNode::SplitNodePathAtEnd(NewNodePath, Left, Right); FString OldNodePath = CollapseNode->GetPreviousFName().ToString(); if (!Left.IsEmpty()) { OldNodePath = URigVMNode::JoinNodePath(Left, OldNodePath); } HandleRigVMGraphRenamed(GetRigVMClient(), OldNodePath, NewNodePath); if (UEdGraph* ContainedEdGraph = GetEdGraph(CollapseNode->GetContainedGraph())) { ContainedEdGraph->Rename(*CollapseNode->GetEditorSubGraphName(), nullptr); } MarkBlueprintAsStructurallyModified(); } break; } case ERigVMGraphNotifType::NodeCategoryChanged: case ERigVMGraphNotifType::NodeKeywordsChanged: case ERigVMGraphNotifType::NodeDescriptionChanged: { MarkBlueprintAsStructurallyModified(); break; } default: { break; } } } // if the notification still has to be sent... if (bNotifForOthersPending && !GetRigVMClient()->bSuspendModelNotificationsForOthers) { if (ModifiedEvent.IsBound()) { ModifiedEvent.Broadcast(InNotifType, InGraph, InSubject); } } #endif } void IRigVMAssetInterface::SuspendNotifications(bool bSuspendNotifs) { if (bSuspendAllNotifications == bSuspendNotifs) { return; } bSuspendAllNotifications = bSuspendNotifs; if (!bSuspendNotifs) { RebuildGraphFromModel(); RefreshEditorEvent.Broadcast(GetObject()); RequestAutoVMRecompilation(); } } void IRigVMAssetInterface::CreateMemberVariablesOnLoad() { #if WITH_EDITOR AddedMemberVariableMap.Reset(); TArray Variables = GetExternalVariables(true); for (int32 VariableIndex = 0; VariableIndex < Variables.Num(); VariableIndex++) { AddedMemberVariableMap.Add(Variables[VariableIndex].Name, VariableIndex); } if (GetRigVMClient()->Num() == 0) { return; } #endif } void IRigVMAssetInterface::PatchVariableNodesOnLoad() { #if WITH_EDITOR AddedMemberVariableMap.Reset(); #endif } void IRigVMAssetInterface::PatchBoundVariables() { } void IRigVMAssetInterface::PatchVariableNodesWithIncorrectType() { TGuardValue GuardNotifsSelf(bSuspendModelNotificationsForSelf, true); struct Local { static bool RefreshIfNeeded(URigVMController* Controller, URigVMVariableNode* VariableNode, const FString& CPPType, UObject* CPPTypeObject) { if (URigVMPin* ValuePin = VariableNode->GetValuePin()) { if (ValuePin->GetCPPType() != CPPType || ValuePin->GetCPPTypeObject() != CPPTypeObject) { Controller->RefreshVariableNode(VariableNode->GetFName(), VariableNode->GetVariableName(), CPPType, CPPTypeObject, false); if (RigVMTypeUtils::AreCompatible(*ValuePin->GetCPPType(), ValuePin->GetCPPTypeObject(), *CPPType, CPPTypeObject)) { return false; } return true; } } return false; } }; for (URigVMGraph* Graph : GetAllModels()) { URigVMController* Controller = GetOrCreateController(Graph); TArray Nodes = Graph->GetNodes(); for (URigVMNode* Node : Nodes) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->IsInputArgument()) { continue; } FRigVMGraphVariableDescription Description = VariableNode->GetVariableDescription(); // Check for local variables if (VariableNode->IsLocalVariable()) { TArray LocalVariables = Graph->GetLocalVariables(false); for (FRigVMGraphVariableDescription Variable : LocalVariables) { if (Variable.Name == Description.Name) { if (Local::RefreshIfNeeded(Controller, VariableNode, Variable.CPPType, Variable.CPPTypeObject)) { bDirtyDuringLoad = true; } break; } } } else { for (FRigVMGraphVariableDescription& Variable : GetAssetVariables()) { if (Variable.Name == Description.Name) { if (Local::RefreshIfNeeded(Controller, VariableNode, Variable.CPPType, Variable.CPPTypeObject)) { bDirtyDuringLoad = true; } } } } } } } } void IRigVMAssetInterface::PatchLinksWithCast() { #if WITH_EDITOR { TGuardValue DisableAutoCompile(bAutoRecompileVM, false); // find all links containing a cast TArray,FString,FString>> LinksWithCast; for (URigVMGraph* Graph : GetAllModels()) { for(URigVMLink* Link : Graph->GetLinks()) { const URigVMPin* SourcePin = Link->GetSourcePin(); const URigVMPin* TargetPin = Link->GetTargetPin(); if (SourcePin && TargetPin) { const TRigVMTypeIndex SourceTypeIndex = SourcePin->GetTypeIndex(); const TRigVMTypeIndex TargetTypeIndex = TargetPin->GetTypeIndex(); if(SourceTypeIndex != TargetTypeIndex) { if(!FRigVMRegistry::Get().CanMatchTypes(SourceTypeIndex, TargetTypeIndex, true)) { LinksWithCast.Emplace(Graph, TWeakObjectPtr(Link), SourcePin->GetPinPath(), TargetPin->GetPinPath()); } } } } } // remove all of those links for(const auto& Tuple : LinksWithCast) { URigVMController* Controller = GetController(Tuple.Get<0>()); if(URigVMLink* Link = Tuple.Get<1>().Get()) { // the link may be detached, attach it first so that removal works. const URigVMPin* SourcePin = Link->GetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); if(!SourcePin->IsLinkedTo(TargetPin)) { const TArray LinkedPaths = Controller->GetLinkedPaths({Link}); Controller->RestoreLinkedPaths(LinkedPaths); } } Controller->BreakLink(Tuple.Get<2>(), Tuple.Get<3>(), false); // notify the user that the link has been broken. UE_LOG(LogRigVMDeveloper, Warning, TEXT("A link was removed in %s (%s) - it contained different types on source and target pin (former cast link?)."), *Controller->GetGraph()->GetNodePath(), *URigVMLink::GetPinPathRepresentation(Tuple.Get<2>(), Tuple.Get<3>()) ); } } #endif } void IRigVMAssetInterface::GetBackwardsCompatibilityPublicFunctions(TArray& BackwardsCompatiblePublicFunctions, TMap& OldHeaders) { TScriptInterface CRGeneratedClass = GetRigVMClientHost()->GetRigVMGraphFunctionHost(); FRigVMGraphFunctionStore* Store = CRGeneratedClass->GetRigVMGraphFunctionStore(); if (GetObject()->GetLinkerCustomVersion(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::RigVMSaveFunctionAccessInModel) { for (const FRigVMGraphFunctionData& FunctionData : Store->PublicFunctions) { BackwardsCompatiblePublicFunctions.Add(FunctionData.Header.Name); URigVMLibraryNode* LibraryNode = Cast(FunctionData.Header.LibraryPointer.GetNodeSoftPath().ResolveObject()); OldHeaders.Add(LibraryNode, FunctionData.Header); } } // Addressing issue where PublicGraphFunctions is populated, but the model PublicFunctionNames is not URigVMFunctionLibrary* FunctionLibrary = GetLocalFunctionLibrary(); if (FunctionLibrary) { if (GetPublicGraphFunctions().Num() > FunctionLibrary->PublicFunctionNames.Num()) { for (const FRigVMGraphFunctionHeader& PublicHeader : GetPublicGraphFunctions()) { BackwardsCompatiblePublicFunctions.Add(PublicHeader.Name); } } } } void IRigVMAssetInterface::PropagateRuntimeSettingsFromBPToInstances() { TArray ArchetypeInstances = GetArchetypeInstances(true, false); for (UObject* ArchetypeInstance : ArchetypeInstances) { URigVMHost* InstanceHost = Cast(ArchetypeInstance); if (!URigVMHost::IsGarbageOrDestroyed(InstanceHost)) { InstanceHost->VMRuntimeSettings = GetVMRuntimeSettings(); } } TArray EdGraphs; GetAllEdGraphs(EdGraphs); for (UEdGraph* Graph : EdGraphs) { TArray Nodes = Graph->Nodes; for (UEdGraphNode* Node : Nodes) { if(URigVMEdGraphNode* RigNode = Cast(Node)) { RigNode->ReconstructNode_Internal(true); } } } } void IRigVMAssetInterface::InitializeArchetypeInstances() { if (URigVM* VM = GetVM(true)) { TArray ArchetypeInstances = GetArchetypeInstances(false, false); for (UObject* Instance : ArchetypeInstances) { URigVMHost* InstanceHost = Cast(Instance); if (URigVMHost::IsGarbageOrDestroyed(InstanceHost)) { continue; } if (InstanceHost->HasAllFlags(RF_ClassDefaultObject)) { continue; } // No objects should be created during load, so PostInitInstanceIfRequired, which creates a new VM and // DynamicHierarchy, should not be called during load if (!InstanceHost->HasAllFlags(RF_NeedPostLoad)) { InstanceHost->PostInitInstanceIfRequired(); } InstanceHost->InstantiateVMFromCDO(); InstanceHost->CopyExternalVariableDefaultValuesFromCDO(); } } } #if WITH_EDITOR void IRigVMAssetInterface::OnVariableAdded(const FName& InVarName) { FRigVMGraphVariableDescription Variable; for (FRigVMGraphVariableDescription& NewVariable : GetAssetVariables()) { if (NewVariable.Name == InVarName) { Variable = NewVariable; break; } } const FRigVMExternalVariable ExternalVariable = RigVMTypeUtils::ExternalVariableFromRigVMVariableDescription(Variable); FString CPPType; UObject* CPPTypeObject = nullptr; RigVMTypeUtils::CPPTypeFromExternalVariable(ExternalVariable, CPPType, &CPPTypeObject); if (CPPTypeObject) { if (ExternalVariable.bIsArray) { CPPType = RigVMTypeUtils::ArrayTypeFromBaseType(CPPTypeObject->GetPathName()); } else { CPPType = CPPTypeObject->GetPathName(); } } // register the type in the registry FRigVMRegistry::Get().FindOrAddType(FRigVMTemplateArgumentType(*CPPType, CPPTypeObject)); RigVMPythonUtils::Print(GetObject()->GetFName().ToString(), FString::Printf(TEXT("blueprint.add_member_variable('%s', '%s', %s, %s, '%s')"), *InVarName.ToString(), *CPPType, (ExternalVariable.bIsPublic) ? TEXT("False") : TEXT("True"), (ExternalVariable.bIsReadOnly) ? TEXT("True") : TEXT("False"), *Variable.DefaultValue)); BroadcastExternalVariablesChangedEvent(); } void IRigVMAssetInterface::OnVariableRemoved(const FName& InVarName) { TArray AllGraphs = GetAllModels(); for (URigVMGraph* Graph : AllGraphs) { if (URigVMController* Controller = GetOrCreateController(Graph)) { #if WITH_EDITOR const bool bSetupUndoRedo = !GIsTransacting; #else const bool bSetupUndoRedo = false; #endif Controller->OnExternalVariableRemoved(InVarName, bSetupUndoRedo); } } RigVMPythonUtils::Print(GetObject()->GetFName().ToString(), FString::Printf(TEXT("blueprint.remove_member_variable('%s')"), *InVarName.ToString())); BroadcastExternalVariablesChangedEvent(); } void IRigVMAssetInterface::OnVariableRenamed(const FName& InOldVarName, const FName& InNewVarName) { TArray AllGraphs = GetAllModels(); for (URigVMGraph* Graph : AllGraphs) { if (URigVMController* Controller = GetOrCreateController(Graph)) { #if WITH_EDITOR const bool bSetupUndoRedo = !GIsTransacting; #else const bool bSetupUndoRedo = false; #endif Controller->OnExternalVariableRenamed(InOldVarName, InNewVarName, bSetupUndoRedo); } } RigVMPythonUtils::Print(GetObject()->GetFName().ToString(), FString::Printf(TEXT("blueprint.rename_member_variable('%s', '%s')"), *InOldVarName.ToString(), *InNewVarName.ToString())); BroadcastExternalVariablesChangedEvent(); } void IRigVMAssetInterface::OnVariableTypeChanged(const FName& InVarName, FEdGraphPinType InOldPinType, FEdGraphPinType InNewPinType) { FString CPPType; UObject* CPPTypeObject = nullptr; RigVMTypeUtils::CPPTypeFromPinType(InNewPinType, CPPType, &CPPTypeObject); TArray AllGraphs = GetAllModels(); for (URigVMGraph* Graph : AllGraphs) { if (URigVMController* Controller = GetOrCreateController(Graph)) { #if WITH_EDITOR const bool bSetupUndoRedo = !GIsTransacting; #else const bool bSetupUndoRedo = false; #endif if (!CPPType.IsEmpty()) { Controller->OnExternalVariableTypeChanged(InVarName, CPPType, CPPTypeObject, bSetupUndoRedo); } else { Controller->OnExternalVariableRemoved(InVarName, bSetupUndoRedo); } } } if(UScriptStruct* ScriptStruct = Cast(CPPTypeObject)) { for (auto Var : GetAssetVariables()) { if (Var.Name == InVarName) { CPPType = ScriptStruct->GetName(); } } } else if (UEnum* Enum = Cast(CPPTypeObject)) { for (auto Var : GetAssetVariables()) { if (Var.Name == InVarName) { CPPType = Enum->GetName(); } } } // register the type in the registry FRigVMRegistry::Get().FindOrAddType(FRigVMTemplateArgumentType(*CPPType, CPPTypeObject)); RigVMPythonUtils::Print(GetObject()->GetFName().ToString(), FString::Printf(TEXT("blueprint.change_member_variable_type('%s', '%s')"), *InVarName.ToString(), *CPPType)); BroadcastExternalVariablesChangedEvent(); } FName IRigVMAssetInterface::AddAssetVariableFromPinType(const FName& InName, const FEdGraphPinType& InType, bool bIsPublic, bool bIsReadOnly, FString InDefaultValue) { FString CPPType; UObject* CPPTypeObject = nullptr; RigVMTypeUtils::CPPTypeFromPinType(InType, CPPType, &CPPTypeObject); return AddMemberVariable(InName, CPPType, bIsPublic, bIsReadOnly, InDefaultValue); } void IRigVMAssetInterface::BroadcastExternalVariablesChangedEvent() { ExternalVariablesChangedEvent.Broadcast(GetExternalVariables(false)); } void IRigVMAssetInterface::BroadcastNodeDoubleClicked(URigVMNode* InNode) { NodeDoubleClickedEvent.Broadcast(GetObject(), InNode); } void IRigVMAssetInterface::BroadcastGraphImported(UEdGraph* InGraph) { GraphImportedEvent.Broadcast(InGraph); } void IRigVMAssetInterface::BroadcastPostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedChainEvent) { PostEditChangeChainPropertyEvent.Broadcast(PropertyChangedChainEvent); } void IRigVMAssetInterface::SetProfilingEnabled(const bool bEnabled) { GetVMRuntimeSettings().bEnableProfiling = bEnabled; PropagateRuntimeSettingsFromBPToInstances(); RequestAutoVMRecompilation(); } void IRigVMAssetInterface::RefreshAllNodes() { DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC() #if WITH_EDITORONLY_DATA // Avoid refreshing EdGraph nodes during cook if (GIsCookerLoadingPackage) { return; } // Avoid refreshing EdGraph if PostLoad() hasn't been called, since reconstruct node later // can access model data that hasn't been fully loaded. And it is ok to skip here because // the EdGraph will be reconstructed later when the CR editor // initializes, as that is when the EdGraph is actually used. if (GetObject()->HasAnyFlags(RF_NeedPostLoad)) { return; } if (GetDefaultModel() == nullptr) { return; } TArray AllNodes; TArray AllGraphs; GetAllEdGraphs(AllGraphs); for(int32 i=0; i GraphNodes; AllGraphs[i]->GetNodesOfClass(GraphNodes); AllNodes.Append(GraphNodes); } for (URigVMEdGraphNode* Node : AllNodes) { Node->SetFlags(RF_Transient); } for(URigVMEdGraphNode* Node : AllNodes) { Node->ReconstructNode(); } for (URigVMEdGraphNode* Node : AllNodes) { Node->ClearFlags(RF_Transient); } #endif } void IRigVMAssetInterface::BroadcastRequestLocalizeFunctionDialog(FRigVMGraphFunctionIdentifier InFunction, bool bForce) { RequestLocalizeFunctionDialog.Broadcast(InFunction, GetController(GetDefaultModel()), GetRigVMClientHost()->GetRigVMGraphFunctionHost().GetInterface(), bForce); } const FCompilerResultsLog& IRigVMAssetInterface::GetCompileLog() const { return CompileLog; } FCompilerResultsLog& IRigVMAssetInterface::GetCompileLog() { return CompileLog; } void IRigVMAssetInterface::BroadCastReportCompilerMessage(EMessageSeverity::Type InSeverity, UObject* InSubject, const FString& InMessage) { ReportCompilerMessageEvent.Broadcast(InSeverity, InSubject, InMessage); } #endif UEdGraph* IRigVMAssetInterface::CreateEdGraph(URigVMGraph* InModel, bool bForce) { check(InModel); #if WITH_EDITORONLY_DATA if(InModel->IsA()) { return GetFunctionLibraryEdGraph(); } #endif if(bForce) { RemoveEdGraph(InModel); } FString GraphName = InModel->GetName(); GraphName.RemoveFromStart(FRigVMClient::RigVMModelPrefix); GraphName.TrimStartAndEndInline(); if(GraphName.IsEmpty()) { GraphName = URigVMEdGraphSchema::GraphName_RigVM.ToString(); } GraphName = GetRigVMClient()->GetUniqueName(*GraphName).ToString(); URigVMEdGraph* RigVMEdGraph = NewObject(GetObject(), GetRigVMClientHost()->GetRigVMEdGraphClass(), *GraphName, RF_Transactional); RigVMEdGraph->Schema = GetRigVMClientHost()->GetRigVMEdGraphSchemaClass(); RigVMEdGraph->bAllowDeletion = true; RigVMEdGraph->ModelNodePath = InModel->GetNodePath(); RigVMEdGraph->InitializeFromAsset(GetObject()); AddUbergraphPage(RigVMEdGraph); AddLastEditedDocument(RigVMEdGraph); return RigVMEdGraph; } bool IRigVMAssetInterface::RemoveEdGraph(URigVMGraph* InModel) { if(URigVMEdGraph* RigGraph = Cast(GetEdGraph(InModel))) { TArray>& UbergraphPages = GetUberGraphs(); if(UbergraphPages.Contains(RigGraph)) { GetObject()->Modify(); UbergraphPages.Remove(RigGraph); } DestroyObject(RigGraph); return true; } return false; } void IRigVMAssetInterface::DestroyObject(UObject* InObject) { GetRigVMClient()->DestroyObject(InObject); } void IRigVMAssetInterface::RenameGraph(const FString& InNodePath, const FName& InNewName) { FName OldName = NAME_None; UEdGraph* EdGraph = GetEdGraph(InNodePath); if(EdGraph) { OldName = EdGraph->GetFName(); } GetRigVMClient()->RenameModel(InNodePath, InNewName, true); if(EdGraph) { NotifyGraphRenamedSuper(EdGraph, OldName, EdGraph->GetFName()); } } void IRigVMAssetInterface::CreateEdGraphForCollapseNodeIfNeeded(URigVMCollapseNode* InNode, bool bForce) { check(InNode); if (bForce) { RemoveEdGraphForCollapseNode(InNode, false); } if (InNode->GetGraph()->IsA()) { if (URigVMGraph* ContainedGraph = InNode->GetContainedGraph()) { bool bFunctionGraphExists = false; for (UEdGraph* FunctionGraph : GetFunctionGraphs()) { if (URigVMEdGraph* RigFunctionGraph = Cast(FunctionGraph)) { if (RigFunctionGraph->ModelNodePath == ContainedGraph->GetNodePath()) { bFunctionGraphExists = true; break; } } } if (!bFunctionGraphExists) { // create a sub graph URigVMEdGraph* RigFunctionGraph = NewObject(GetObject(), GetRigVMClientHost()->GetRigVMEdGraphClass(), *InNode->GetName(), RF_Transactional); RigFunctionGraph->Schema = GetRigVMClientHost()->GetRigVMEdGraphSchemaClass(); RigFunctionGraph->bAllowRenaming = 1; RigFunctionGraph->bEditable = 1; RigFunctionGraph->bAllowDeletion = 1; RigFunctionGraph->ModelNodePath = ContainedGraph->GetNodePath(); RigFunctionGraph->bIsFunctionDefinition = true; GetFunctionGraphs().Add(RigFunctionGraph); RigFunctionGraph->InitializeFromAsset(GetObject()); GetOrCreateController(ContainedGraph)->ResendAllNotifications(); } } } else if (URigVMEdGraph* RigGraph = Cast(GetEdGraph(InNode->GetGraph()))) { if (URigVMGraph* ContainedGraph = InNode->GetContainedGraph()) { bool bSubGraphExists = false; for (UEdGraph* SubGraph : RigGraph->SubGraphs) { if (URigVMEdGraph* SubRigGraph = Cast(SubGraph)) { if (SubRigGraph->ModelNodePath == ContainedGraph->GetNodePath()) { bSubGraphExists = true; break; } } } if (!bSubGraphExists) { bool bEditable = true; if (InNode->IsA()) { bEditable = false; } // create a sub graph URigVMEdGraph* SubRigGraph = NewObject(RigGraph, GetRigVMClientHost()->GetRigVMEdGraphClass(), *InNode->GetEditorSubGraphName(), RF_Transactional); SubRigGraph->Schema = GetRigVMClientHost()->GetRigVMEdGraphSchemaClass(); SubRigGraph->bAllowRenaming = 1; SubRigGraph->bEditable = bEditable; SubRigGraph->bAllowDeletion = 1; SubRigGraph->ModelNodePath = ContainedGraph->GetNodePath(); SubRigGraph->bIsFunctionDefinition = false; RigGraph->SubGraphs.Add(SubRigGraph); SubRigGraph->InitializeFromAsset(GetObject()); GetOrCreateController(ContainedGraph)->ResendAllNotifications(); } } } } bool IRigVMAssetInterface::RemoveEdGraphForCollapseNode(URigVMCollapseNode* InNode, bool bNotify) { check(InNode); if (InNode->GetGraph()->IsA()) { if (URigVMGraph* ContainedGraph = InNode->GetContainedGraph()) { for (UEdGraph* FunctionGraph : GetFunctionGraphs()) { if (URigVMEdGraph* RigFunctionGraph = Cast(FunctionGraph)) { if (RigFunctionGraph->ModelNodePath == ContainedGraph->GetNodePath()) { if (URigVMController* SubController = GetController(ContainedGraph)) { SubController->OnModified().RemoveAll(RigFunctionGraph); } if (ModifiedEvent.IsBound() && bNotify) { ModifiedEvent.Broadcast(ERigVMGraphNotifType::NodeRemoved, InNode->GetGraph(), InNode); } GetFunctionGraphs().Remove(RigFunctionGraph); RigFunctionGraph->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors); if(RigFunctionGraph->IsRooted()) { RigFunctionGraph->RemoveFromRoot(); } RigFunctionGraph->MarkAsGarbage(); return bNotify; } } } } } else if (URigVMEdGraph* RigGraph = Cast(GetEdGraph(InNode->GetGraph()))) { if (URigVMGraph* ContainedGraph = InNode->GetContainedGraph()) { for (UEdGraph* SubGraph : RigGraph->SubGraphs) { if (URigVMEdGraph* SubRigGraph = Cast(SubGraph)) { if (SubRigGraph->ModelNodePath == ContainedGraph->GetNodePath()) { if (URigVMController* SubController = GetController(ContainedGraph)) { SubController->OnModified().RemoveAll(SubRigGraph); } if (ModifiedEvent.IsBound() && bNotify) { ModifiedEvent.Broadcast(ERigVMGraphNotifType::NodeRemoved, InNode->GetGraph(), InNode); } RigGraph->SubGraphs.Remove(SubRigGraph); SubRigGraph->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors); if(SubRigGraph->IsRooted()) { SubRigGraph->RemoveFromRoot(); } SubRigGraph->MarkAsGarbage(); return bNotify; } } } } } return false; } #if WITH_EDITOR void IRigVMAssetInterface::QueueCompilerMessageDelegate(const FOnRigVMReportCompilerMessage::FDelegate& InDelegate) { FScopeLock Lock(&QueuedCompilerMessageDelegatesMutex); QueuedCompilerMessageDelegates.Add(InDelegate); } void IRigVMAssetInterface::ClearQueuedCompilerMessageDelegates() { FScopeLock Lock(&QueuedCompilerMessageDelegatesMutex); QueuedCompilerMessageDelegates.Reset(); } #endif FRigVMBlueprintCompileScope::FRigVMBlueprintCompileScope(FRigVMAssetInterfacePtr InBlueprint): Blueprint(InBlueprint) { check(Blueprint); Blueprint->IncrementVMRecompileBracket(); } FRigVMBlueprintCompileScope::~FRigVMBlueprintCompileScope() { Blueprint->DecrementVMRecompileBracket(); } #undef LOCTEXT_NAMESPACE