// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraGraph.h" #include "Algo/RemoveIf.h" #include "Algo/StableSort.h" #include "EdGraphSchema_Niagara.h" #include "GraphEditAction.h" #include "INiagaraEditorTypeUtilities.h" #include "NiagaraCommon.h" #include "NiagaraComponent.h" #include "NiagaraConstants.h" #include "NiagaraCustomVersion.h" #include "NiagaraEditorModule.h" #include "NiagaraEditorUtilities.h" #include "NiagaraHlslTranslator.h" #include "NiagaraModule.h" #include "NiagaraNode.h" #include "NiagaraNodeAssignment.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraNodeInput.h" #include "NiagaraNodeOutput.h" #include "NiagaraNodeParameterMapBase.h" #include "NiagaraNodeParameterMapGet.h" #include "NiagaraNodeParameterMapSet.h" #include "NiagaraNodeReroute.h" #include "NiagaraNodeStaticSwitch.h" #include "NiagaraParameterDefinitions.h" #include "NiagaraScript.h" #include "NiagaraScriptSource.h" #include "NiagaraScriptVariable.h" #include "NiagaraSettings.h" #include "HAL/FileManager.h" #include "HAL/LowLevelMemTracker.h" #include "Misc/Paths.h" #include "Misc/SecureHash.h" #include "Modules/ModuleManager.h" #include "String/ParseTokens.h" #include "ViewModels/NiagaraScriptViewModel.h" #include "DataHierarchyViewModelBase.h" #include "ViewModels/HierarchyEditor/NiagaraScriptParametersHierarchyViewModel.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(NiagaraGraph) DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - PostLoad"), STAT_NiagaraEditor_Graph_PostLoad, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - FindInputNodes"), STAT_NiagaraEditor_Graph_FindInputNodes, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - FindInputNodes_NotFilterUsage"), STAT_NiagaraEditor_Graph_FindInputNodes_NotFilterUsage, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - FindInputNodes_FilterUsage"), STAT_NiagaraEditor_Graph_FindInputNodes_FilterUsage, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - FindInputNodes_FilterDupes"), STAT_NiagaraEditor_Graph_FindInputNodes_FilterDupes, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - FindInputNodes_FindInputNodes_Sort"), STAT_NiagaraEditor_Graph_FindInputNodes_Sort, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - FindOutputNode"), STAT_NiagaraEditor_Graph_FindOutputNode, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - BuildTraversal"), STAT_NiagaraEditor_Graph_BuildTraversal, STATGROUP_NiagaraEditor); #define NIAGARA_SCOPE_CYCLE_COUNTER(x) //SCOPE_CYCLE_COUNTER(x) bool bWriteToLog = false; #define LOCTEXT_NAMESPACE "NiagaraGraph" int32 GNiagaraUseGraphHash = 1; static FAutoConsoleVariableRef CVarNiagaraUseGraphHash( TEXT("fx.UseNewGraphHash"), GNiagaraUseGraphHash, TEXT("If > 0 a hash of the graph node state will be used, otherwise will use the older code path. \n"), ECVF_Default ); namespace NiagaraGraphImpl { void FindReferencedVariables(const UNiagaraGraph* Graph, TConstArrayView Nodes, TSet& ReferencedVariables) { // Check all pins on all nodes in the graph to find parameter pins which may have been missed in the parameter map traversal. This // can happen for nodes which are not fully connected for (const UNiagaraNode* Node : Nodes) { if (const UNiagaraNodeStaticSwitch* SwitchNode = Cast(Node)) { if (!SwitchNode->IsSetByCompiler() && !SwitchNode->IsSetByPin()) { ReferencedVariables.Add(FNiagaraVariable(SwitchNode->GetInputType(), SwitchNode->InputParameterName)); } } else if (const UNiagaraNodeFunctionCall* FunctionNode = Cast(Node)) { for (const FNiagaraPropagatedVariable& Propagated : FunctionNode->PropagatedStaticSwitchParameters) { ReferencedVariables.Add(Propagated.ToVariable()); } } else if (const UNiagaraNodeParameterMapBase* ParameterMapNode = Cast(Node)) { const bool IsMapGet = Node->IsA(); const bool IsMapSet = Node->IsA(); for (UEdGraphPin* Pin : Node->Pins) { // we only consider parameter pins, inputs for MapSets and outputs for MapGets if (Pin->PinType.PinSubCategory == UNiagaraNodeParameterMapBase::ParameterPinSubCategory) { const bool bRelevantPin = ((Pin->Direction == EEdGraphPinDirection::EGPD_Input) && IsMapSet) || ((Pin->Direction == EEdGraphPinDirection::EGPD_Output) && IsMapGet); if (!bRelevantPin) { continue; } ReferencedVariables.Add(UEdGraphSchema_Niagara::PinToNiagaraVariable(Pin, false)); } } } } auto GetBindingScriptVariable = [&](const FNiagaraVariable& Variable) -> UNiagaraScriptVariable* { if (UNiagaraScriptVariable* ScriptVariable = Graph->GetScriptVariable(Variable)) { if (ScriptVariable->DefaultMode == ENiagaraDefaultMode::Binding && ScriptVariable->DefaultBinding.IsValid()) { return ScriptVariable; } } return nullptr; }; TArray VariablesToProcess; for (const FNiagaraVariable& ReferencedVariable : ReferencedVariables) { if (UNiagaraScriptVariable* ScriptVariable = GetBindingScriptVariable(ReferencedVariable)) { VariablesToProcess.Add(ScriptVariable); } } while (!VariablesToProcess.IsEmpty()) { UNiagaraScriptVariable* Variable = VariablesToProcess.Pop(EAllowShrinking::No); FNiagaraTypeDefinition LinkedType = Variable->Variable.GetType(); FName BindingName = Variable->DefaultBinding.GetName(); if (FNiagaraConstants::GetOldPositionTypeVariables().Contains(FNiagaraVariable(LinkedType, BindingName))) { // it is not uncommon that old assets have vector inputs that default bind to what is now a position type. If we detect that, we change the type to prevent a compiler error. LinkedType = FNiagaraTypeDefinition::GetPositionDef(); } const FNiagaraVariable BoundVariable(LinkedType, BindingName); bool bAlreadyInSet = false; ReferencedVariables.Add(BoundVariable, &bAlreadyInSet); if (!bAlreadyInSet) { if (UNiagaraScriptVariable* ScriptVariable = GetBindingScriptVariable(BoundVariable)) { VariablesToProcess.Add(ScriptVariable); } } } } } FNiagaraGraphParameterReferenceCollection::FNiagaraGraphParameterReferenceCollection(bool bInCreated) : bCreatedByUser(bInCreated) { } bool FNiagaraGraphParameterReferenceCollection::WasCreatedByUser() const { return bCreatedByUser; } FNiagaraGraphScriptUsageInfo::FNiagaraGraphScriptUsageInfo() : UsageType(ENiagaraScriptUsage::Function) { } void FNiagaraGraphScriptUsageInfo::PostLoad(UObject* Owner) { const int32 NiagaraVer = Owner->GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); if (NiagaraVer < FNiagaraCustomVersion::UseHashesToIdentifyCompileStateOfTopLevelScripts) { if (BaseId.IsValid() == false && GeneratedCompileId_DEPRECATED.IsValid()) { // When loading old data use the last generated compile id as the base id to prevent recompiles on load for existing scripts. BaseId = GeneratedCompileId_DEPRECATED; } if (CompileHash.IsValid() == false && DataHash_DEPRECATED.Num() == FNiagaraCompileHash::HashSize) { CompileHash = FNiagaraCompileHash(DataHash_DEPRECATED); CompileHashFromGraph = FNiagaraCompileHash(DataHash_DEPRECATED); } } } bool FNiagaraGraphScriptUsageInfo::IsValid() const { for (int32 i = 0; i < Traversal.Num(); i++) { if (Traversal[i] == nullptr) return false; } return true; } FDelegateHandle UNiagaraGraph::AddOnGraphNeedsRecompileHandler(const FOnGraphChanged::FDelegate& InHandler) { return OnGraphNeedsRecompile.Add(InHandler); } void UNiagaraGraph::RemoveOnGraphNeedsRecompileHandler(FDelegateHandle Handle) { OnGraphNeedsRecompile.Remove(Handle); } void UNiagaraGraph::NotifyGraphChanged(const FEdGraphEditAction& InAction) { InvalidateCachedParameterData(); if ((InAction.Action & GRAPHACTION_AddNode) != 0 || (InAction.Action & GRAPHACTION_RemoveNode) != 0 || (InAction.Action & GRAPHACTION_GenericNeedsRecompile) != 0) { MarkGraphRequiresSynchronization(TEXT("Graph Changed")); } if ((InAction.Action & GRAPHACTION_GenericNeedsRecompile) != 0) { OnGraphNeedsRecompile.Broadcast(InAction); return; } Super::NotifyGraphChanged(InAction); } void UNiagaraGraph::NotifyGraphChanged() { Super::NotifyGraphChanged(); InvalidateCachedParameterData(); InvalidateNumericCache(); } void UNiagaraGraph::PostLoad_LWCFixup(int32 NiagaraVersion) { // TODO - put behind a CustomVersion TArray NiagaraParameterNodes; GetNodesOfClass(NiagaraParameterNodes); if (NiagaraParameterNodes.IsEmpty()) { return; } bool GraphChanged = false; const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); TArray OldTypes = FNiagaraConstants::GetOldPositionTypeVariables(); const FEdGraphPinType PositionPinType = NiagaraSchema->TypeDefinitionToPinType(FNiagaraTypeDefinition::GetPositionDef()); for (UNiagaraNodeParameterMapBase* NiagaraNode : NiagaraParameterNodes) { for (UEdGraphPin* Pin : NiagaraNode->Pins) { if (NiagaraNode->IsAddPin(Pin)) { continue; } FNiagaraVariable Variable = FNiagaraVariable(NiagaraSchema->PinToTypeDefinition(Pin), Pin->PinName); for (const FNiagaraVariable& OldVarType : OldTypes) { if (Variable == OldVarType) { Pin->PinType = PositionPinType; Pin->PinType.PinSubCategory = UNiagaraNodeParameterMapBase::ParameterPinSubCategory; NiagaraNode->MarkNodeRequiresSynchronization(__FUNCTION__, false); GraphChanged = true; break; } } // fix default pin types if necessary // NOTE - that this code is impacting more than just LWC related nodes, it is touching all MapGet nodes // - Further by assigning the Pin->PinType to the DefaultPin->PinType it is going to assign // UNiagaraNodeParameterMapBase::ParameterPinSubCategory to the SubCategory of the input (default) pin // which currently is reserved for output pins. This seems to be an unnecessary change and does have // an impact when dealing with RefreshParameterReferences' AddParameterReference lambda where the subcategory // is evaluated. This results in DefaultValues not necessarily getting into the compile hash (if the default pin // is the first reference added, then it will be encountered and rejected in AppendCompileHash and then the // default value won't be handled if (Pin->Direction == EGPD_Output) { if (UNiagaraNodeParameterMapGet* GetNode = Cast(NiagaraNode)) { UEdGraphPin* DefaultPin = GetNode->GetDefaultPin(Pin); if (DefaultPin && DefaultPin->PinType != Pin->PinType) { DefaultPin->PinType = Pin->PinType; NiagaraNode->MarkNodeRequiresSynchronization(__FUNCTION__, false); GraphChanged = true; } } } } } TArray ChangedScriptVariables; for (FNiagaraVariable OldVarType : OldTypes) { TObjectPtr* ScriptVariablePtr = VariableToScriptVariable.Find(OldVarType); if (ScriptVariablePtr != nullptr && *ScriptVariablePtr) { const FNiagaraVariable NewVarType = FNiagaraVariable(FNiagaraTypeDefinition::GetPositionDef(), OldVarType.GetName()); UNiagaraScriptVariable* ScriptVar = ScriptVariablePtr->Get(); if (ScriptVar->DefaultMode == ENiagaraDefaultMode::Value) { ChangedScriptVariables.Add(ScriptVar); } ScriptVar->Variable.SetType(FNiagaraTypeDefinition::GetPositionDef()); VariableToScriptVariable.Remove(OldVarType); VariableToScriptVariable.Add(NewVarType, TObjectPtr(ScriptVar)); GraphChanged = true; } } if (!ChangedScriptVariables.IsEmpty()) { TSet MapGetNodes; for (UNiagaraScriptVariable* ChangedScriptVariable : ChangedScriptVariables) { FNiagaraVariable& Variable = ChangedScriptVariable->Variable; FNiagaraEditorModule& EditorModule = FNiagaraEditorModule::Get(); auto TypeUtilityValue = EditorModule.GetTypeUtilities(Variable.GetType()); if (TypeUtilityValue.IsValid() && TypeUtilityValue->CanHandlePinDefaults()) { TArray Pins = FindParameterMapDefaultValuePins(Variable.GetName()); for (UEdGraphPin* Pin : Pins) { check(ChangedScriptVariable->DefaultMode == ENiagaraDefaultMode::Value && !Variable.GetType().IsDataInterface() && !Variable.GetType().IsUObject()); Variable.AllocateData(); FString NewDefaultValue = TypeUtilityValue->GetPinDefaultStringFromValue(Variable); NiagaraSchema->TrySetDefaultValue(*Pin, NewDefaultValue, false); MapGetNodes.Add(CastChecked(Pin->GetOwningNode())); } } } if (!MapGetNodes.IsEmpty()) { GraphChanged = true; for (UNiagaraNodeParameterMapGet* MapGet : MapGetNodes) { MapGet->SynchronizeDefaultPins(); } } } if (GraphChanged) { Modify(); ChangeId = FGuid::NewGuid(); } } // while it would be great to only run this fixup once, it's not clear how we are missing 'local' niagara script variables from our map // and so we'll keep this running all the time. It just running through nodes rather than a parmaeter map traversal is still a huge // improvement, but we need to get to the bottom of where the missing script variables are coming from. void UNiagaraGraph::PostLoad_ManageScriptVariables(int32 NiagaraVersion) { // we only want to manage script variables for modules/functions. Top level scripts system/emitter/particle scripts don't // need to manage their parameters via script variables bool bHasStandaloneScriptUsage = false; { TArray OutputNodes; FindOutputNodes(OutputNodes); auto ScriptUsagePredicate = [](const UNiagaraNodeOutput* OutputNode) -> bool { const ENiagaraScriptUsage Usage = OutputNode->GetUsage(); return (Usage == ENiagaraScriptUsage::Module || Usage == ENiagaraScriptUsage::DynamicInput || Usage == ENiagaraScriptUsage::Function); }; bHasStandaloneScriptUsage = OutputNodes.ContainsByPredicate(ScriptUsagePredicate); } TSet ReferencedVariables; if (bHasStandaloneScriptUsage) { TArray NiagaraNodes; NiagaraNodes.Reserve(Nodes.Num()); for (TObjectPtr& Node : Nodes) { if (Node) { if (UNiagaraNode* NiagaraNode = Cast(Node.Get())) { NiagaraNodes.Add(NiagaraNode); } } } NiagaraGraphImpl::FindReferencedVariables(this, NiagaraNodes, ReferencedVariables); } // clear out any script variables that are not in the set of referenced variables above for (FScriptVariableMap::TIterator ScriptVariableIt = VariableToScriptVariable.CreateIterator(); ScriptVariableIt; ++ScriptVariableIt) { if (!ReferencedVariables.Contains(ScriptVariableIt.Key())) { ScriptVariableIt.RemoveCurrent(); } } // and also ensure that we're not actually missing any script variables for (const FNiagaraVariable& Variable : ReferencedVariables) { if (!VariableToScriptVariable.Contains(Variable)) { CreateScriptVariableInternal(Variable, FNiagaraVariableMetaData(), false); } } } UNiagaraGraph::UNiagaraGraph() : bNeedNumericCacheRebuilt(true) , bIsRenamingParameter(false) , bParameterReferenceRefreshPending(true) { Schema = UEdGraphSchema_Niagara::StaticClass(); ChangeId = FGuid::NewGuid(); ParameterHierarchyRoot = CreateDefaultSubobject("ParameterHierarchyRoot"); } void UNiagaraGraph::PostLoad() { LLM_SCOPE(ELLMTag::Niagara); check(!bIsForCompilationOnly); Super::PostLoad(); ////////////////////////////////////////////////////////////////////////// /// GLOBAL CLEANUP/INVALIDATION /// -has no dependence on other chunks /// -can add/modify UNiagaraScriptVariable ////////////////////////////////////////////////////////////////////////// if (GIsEditor) { SetFlags(RF_Transactional); } const int32 NiagaraVer = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); // If this is from a prior version, enforce a valid Change Id! if (ChangeId.IsValid() == false) { MarkGraphRequiresSynchronization(TEXT("Graph change id was invalid")); } // Since we added the ReferenceHashFromGraph we need to keep it updated but the id won't be out of sync, so we // just do a quick check to see if all are missing then force it. { int32 NumMissing = 0; for (int32 i = 0; i < CachedUsageInfo.Num(); i++) { if (false == CachedUsageInfo[i].ReferenceHashFromGraph.IsValid()) { NumMissing++; } } if (NumMissing == CachedUsageInfo.Num()) { CachedUsageInfo.Empty(); LastBuiltTraversalDataChangeId = FGuid(); } } // There was a bug where the graph traversal could have nullptr entries in it, which causes crashes later. Detect these and just clear out the cache. { bool bAnyInvalid = false; for (FNiagaraGraphScriptUsageInfo& CachedUsageInfoItem : CachedUsageInfo) { CachedUsageInfoItem.PostLoad(this); if (!CachedUsageInfoItem.IsValid()) { bAnyInvalid = true; } } if (bAnyInvalid) { CachedUsageInfo.Empty(); LastBuiltTraversalDataChangeId = FGuid(); } } // Migrate input condition metadata if (NiagaraVer < FNiagaraCustomVersion::MetaDataAndParametersUpdate) { int NumMigrated = 0; // If the version of the asset is older than FNiagaraCustomVersion::MetaDataAndParametersUpdate // we need to migrate the old metadata by looping through VariableToMetaData_DEPRECATED // and create new entries in VariableToScriptVariable for (auto& Pair : VariableToMetaData_DEPRECATED) { FNiagaraVariable Var(Pair.Key.GetType(), Pair.Key.GetName()); SetMetaData(Var, Pair.Value); NumMigrated++; } VariableToMetaData_DEPRECATED.Empty(); } // TODO - put behind a CustomVersion if (!VariableToScriptVariable.IsEmpty()) { const TArray StaticSwitchInputs = FindStaticSwitchInputs(); for (auto It = VariableToScriptVariable.CreateIterator(); It; ++It) { FNiagaraVariable Var = It.Key(); TObjectPtr& ScriptVar = It.Value(); if (ScriptVar == nullptr) { ScriptVar = NewObject(const_cast(this)); ScriptVar->Init(Var, FNiagaraVariableMetaData()); UE_LOG(LogNiagaraEditor, Display, TEXT("Fixed null UNiagaraScriptVariable | variable %s | asset path %s"), *Var.GetName().ToString(), *GetPathName()); } else { if (ScriptVar->GetName().Contains(TEXT(".")) || ScriptVar->GetName().Contains(TEXT(" "))) { ScriptVar->Rename(*MakeUniqueObjectName(this, UNiagaraScriptVariable::StaticClass(), NAME_None).ToString(), nullptr, REN_NonTransactional); } // Conditional postload all ScriptVars to ensure static switch default values are allocated as these are required when postloading all graph nodes later. ScriptVar->ConditionalPostLoad(); } ScriptVar->SetIsStaticSwitch(StaticSwitchInputs.Contains(Var)); } } if (NiagaraVer < FNiagaraCustomVersion::StaticSwitchFunctionPinsUsePersistentGuids) { // Make sure that the variable persistent ids are unique, otherwise pin allocation for static switches will be inconsistent. TArray> ScriptVariables; VariableToScriptVariable.GenerateValueArray(ScriptVariables); TMap VariableGuidToScriptVariable; for (TObjectPtr& ScriptVariable : ScriptVariables) { if (ScriptVariable != nullptr) { UNiagaraScriptVariable** MatchingScriptVariablePtr = VariableGuidToScriptVariable.Find(ScriptVariable->Metadata.GetVariableGuid()); if (MatchingScriptVariablePtr == nullptr) { VariableGuidToScriptVariable.Add(ScriptVariable->Metadata.GetVariableGuid(), ScriptVariable); } else { UNiagaraScriptVariable* MatchingScriptVariable = *MatchingScriptVariablePtr; if (ScriptVariable->GetIsSubscribedToParameterDefinitions() && MatchingScriptVariable->GetIsSubscribedToParameterDefinitions()) { // Both of the script variables with duplicate ids are controlled by parameter definitions so issue a warning because neither will be updated. UE_LOG(LogNiagaraEditor, Log, TEXT("Duplicate ids found for script variables which are both subscribed to parameter definitions.\nScript Variable 1 Name: %s Type: %s Path: %s\nScript Variable 2 Name: %s Type: %s Path: %s"), *MatchingScriptVariable->Variable.GetName().ToString(), *MatchingScriptVariable->Variable.GetType().GetName(), *MatchingScriptVariable->GetPathName(), *ScriptVariable->Variable.GetName().ToString(), *ScriptVariable->Variable.GetType().GetName(), *ScriptVariable->GetPathName()); } else { // Remove the duplicated entry and regenerate the ids for entries not subscribed to parameter definitions. VariableGuidToScriptVariable.Remove(ScriptVariable->Metadata.GetVariableGuid()); if (ScriptVariable->GetIsSubscribedToParameterDefinitions() == false) { ScriptVariable->Metadata.SetVariableGuid(UNiagaraScriptVariable::GenerateStableGuid(ScriptVariable)); if (VariableGuidToScriptVariable.Contains(ScriptVariable->Metadata.GetVariableGuid())) { UE_LOG(LogNiagaraEditor, Log, TEXT("Could not generate a stable unique variable guid for script variable. Name: %s Type: %s Path: %s"), *ScriptVariable->Variable.GetName().ToString(), *ScriptVariable->Variable.GetType().GetName(), *ScriptVariable->GetPathName()); } else { VariableGuidToScriptVariable.Add(ScriptVariable->Metadata.GetVariableGuid(), ScriptVariable); } } if (MatchingScriptVariable->GetIsSubscribedToParameterDefinitions() == false) { MatchingScriptVariable->Metadata.SetVariableGuid(UNiagaraScriptVariable::GenerateStableGuid(MatchingScriptVariable)); if (VariableGuidToScriptVariable.Contains(MatchingScriptVariable->Metadata.GetVariableGuid())) { UE_LOG(LogNiagaraEditor, Log, TEXT("Could not generate a stable unique variable guid for script variable. Name: %s Type: %s Path: %s"), *MatchingScriptVariable->Variable.GetName().ToString(), *MatchingScriptVariable->Variable.GetType().GetName(), *MatchingScriptVariable->GetPathName()); } else { VariableGuidToScriptVariable.Add(MatchingScriptVariable->Metadata.GetVariableGuid(), MatchingScriptVariable); } } } } } } } if (NiagaraVer < FNiagaraCustomVersion::MoveCommonInputMetadataToProperties) { auto MigrateInputCondition = [](TMap& PropertyMetaData, const FName& InputConditionKey, FNiagaraInputConditionMetadata& InOutInputCondition) { FString* InputCondition = PropertyMetaData.Find(InputConditionKey); if (InputCondition != nullptr) { FString InputName; FString TargetValue; int32 EqualsIndex = InputCondition->Find("="); if (EqualsIndex == INDEX_NONE) { InOutInputCondition.InputName = **InputCondition; } else { InOutInputCondition.InputName = *InputCondition->Left(EqualsIndex); InOutInputCondition.TargetValues.Add(InputCondition->RightChop(EqualsIndex + 1)); } PropertyMetaData.Remove(InputConditionKey); } }; for (auto& VariableToScriptVariableItem : VariableToScriptVariable) { TObjectPtr& MetaData = VariableToScriptVariableItem.Value; // Migrate advanced display. if (MetaData->Metadata.PropertyMetaData.Contains("AdvancedDisplay")) { MetaData->Metadata.bAdvancedDisplay = true; MetaData->Metadata.PropertyMetaData.Remove("AdvancedDisplay"); } // Migrate inline edit condition toggle if (MetaData->Metadata.PropertyMetaData.Contains("InlineEditConditionToggle")) { MetaData->Metadata.bInlineEditConditionToggle = true; MetaData->Metadata.PropertyMetaData.Remove("InlineEditConditionToggle"); } // Migrate edit and visible conditions MigrateInputCondition(MetaData->Metadata.PropertyMetaData, TEXT("EditCondition"), MetaData->Metadata.EditCondition); MigrateInputCondition(MetaData->Metadata.PropertyMetaData, TEXT("VisibleCondition"), MetaData->Metadata.VisibleCondition); } } if (NiagaraVer < FNiagaraCustomVersion::StandardizeParameterNames) { StandardizeParameterNames(); } ////////////////////////////////////////////////////////////////////////// /// NODE VALIDATION ////////////////////////////////////////////////////////////////////////// { TArray InputNodes; for (UEdGraphNode* Node : Nodes) { Node->ConditionalPostLoad(); } for (UEdGraphNode* Node : Nodes) { if (UNiagaraNode* NiagaraNode = Cast(Node)) { // Assume that all externally referenced assets have changed, so update to match. They will return true if they have changed. if (UObject* ReferencedAsset = NiagaraNode->GetReferencedAsset()) { ReferencedAsset->ConditionalPostLoad(); NiagaraNode->RefreshFromExternalChanges(); } if (UNiagaraNodeInput* InputNode = Cast(NiagaraNode)) { InputNodes.Add(InputNode); } } } // In the past, we didn't bother setting the CallSortPriority and just used lexicographic ordering. // In the event that we have multiple non-matching nodes with a zero call sort priority, this will // give every node a unique order value. if (!InputNodes.IsEmpty()) { bool bPrioritiesAllZeroes = true; TArray UniqueNames; for (UNiagaraNodeInput* InputNode : InputNodes) { if (InputNode->CallSortPriority != 0) { bPrioritiesAllZeroes = false; } if (InputNode->Usage == ENiagaraInputNodeUsage::Parameter) { UniqueNames.AddUnique(InputNode->Input.GetName()); } if (InputNode->Usage == ENiagaraInputNodeUsage::SystemConstant) { InputNode->Input = FNiagaraConstants::UpdateEngineConstant(InputNode->Input); } } if (bPrioritiesAllZeroes && UniqueNames.Num() > 1) { // Just do the lexicographic sort and assign the call order to their ordered index value. UniqueNames.Sort(FNameLexicalLess()); for (UNiagaraNodeInput* InputNode : InputNodes) { if (InputNode->Usage == ENiagaraInputNodeUsage::Parameter) { int32 FoundIndex = UniqueNames.Find(InputNode->Input.GetName()); check(FoundIndex != INDEX_NONE); InputNode->CallSortPriority = FoundIndex; } } } } } // Fix LWC position type mapping; before LWC, all position were FVector3f, but now that they are a separate type, we // need to upgrade the pins and metadata to match PostLoad_LWCFixup(NiagaraVer); PostLoad_ManageScriptVariables(NiagaraVer); // Fix inconsistencies in the default value declaration between graph and metadata ValidateDefaultPins(); // this will ensure that the parameter maps have been refreshed and our NiagaraScriptVariable are up to date. // There's an argument to be made that we should just call RefreshParameterReferences directly and avoid explicitly // generating the compile ids but there's still code out there that is expecting to have fully generated compile ids // available on load. RebuildCachedCompileIds(); // we want to make sure that nothing above resulted in the reference map from being initialized as it shouldn't be necessary to // do such heavy lifting during PostLoad() ensureMsgf(bParameterReferenceRefreshPending, TEXT("ParameterReferenceMap was built during PostLoad of %s - for load time reasons this should be cleaned up."), *GetFullName()); } #if WITH_EDITORONLY_DATA void UNiagaraGraph::DeclareConstructClasses(TArray& OutConstructClasses, const UClass* SpecificSubclass) { Super::DeclareConstructClasses(OutConstructClasses, SpecificSubclass); OutConstructClasses.Add(FTopLevelAssetPath(UNiagaraScriptVariable::StaticClass())); } #endif void UNiagaraGraph::ChangeParameterType(const TArray& ParametersToChange, const FNiagaraTypeDefinition& NewType, bool bAllowOrphanedPins) { check(!bIsForCompilationOnly); Modify(); const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); TArray NiagaraParameterNodes; GetNodesOfClass(NiagaraParameterNodes); // we will encounter pins that we've already changed, so we cache their previous state here to carry them over to potential new orphaned pins TMap OldTypesCache; TMap OldDefaultValuesCache; TMap OldAutogeneratedDefaultValuesCache; // we keep track of pins that will (but haven't yet) change type to keep connections intact that will have pins of the same type after the op is done TSet ChangingPins; // a subset of changing pins. This only includes parameter pins (i.e. outputs on map gets & inputs on map sets) TSet ChangingParameterPins; TSet ChangingDefaultPins; // since default pins are named 'None' but, if orphaned, can exist independently from their outputs, we cache the parameter pin's name here to override the new orphaned pin's name TMap OrphanedDefaultPinNameOverrides; auto GatherChangingPins = [&](UNiagaraNodeParameterMapBase* Node) { for(UEdGraphPin* Pin : Node->Pins) { if(Node->IsAddPin(Pin) || Pin->bOrphanedPin) { continue; } // we add pins that represent the parameter here FNiagaraVariable Variable(UEdGraphSchema_Niagara::PinToTypeDefinition(Pin), Pin->PinName); if(ParametersToChange.Contains(Variable) && Pin->PinType.PinSubCategory == UNiagaraNodeParameterMapBase::ParameterPinSubCategory) { ChangingPins.Add(Pin); ChangingParameterPins.Add(Pin); OldTypesCache.Add(Pin, Variable.GetType()); OldDefaultValuesCache.Add(Pin, Pin->DefaultValue); OldAutogeneratedDefaultValuesCache.Add(Pin, Pin->AutogeneratedDefaultValue); // parameter map get nodes might have additional default pins that we can't match via name if(UNiagaraNodeParameterMapGet* MapGet = Cast(Node)) { if(UEdGraphPin* DefaultPin = MapGet->GetDefaultPin(Pin)) { ChangingPins.Add(DefaultPin); ChangingDefaultPins.Add(DefaultPin); OrphanedDefaultPinNameOverrides.Add(DefaultPin, Pin->PinName); OldTypesCache.Add(DefaultPin, Variable.GetType()); OldDefaultValuesCache.Add(DefaultPin, DefaultPin->DefaultValue); OldAutogeneratedDefaultValuesCache.Add(DefaultPin, DefaultPin->AutogeneratedDefaultValue); } } } } }; for (UNiagaraNodeParameterMapBase* NiagaraNode : NiagaraParameterNodes) { GatherChangingPins(NiagaraNode); } auto TryCreateOrphanedPin = [&](UNiagaraNode* Node, FName OrphanedPinName, UEdGraphPin* OriginalPin) { if(OriginalPin->LinkedTo.Num() > 0 || (OriginalPin->Direction == EGPD_Input && OriginalPin->LinkedTo.Num() == 0 && OriginalPin->bDefaultValueIsIgnored == false && OldDefaultValuesCache[OriginalPin] != OldAutogeneratedDefaultValuesCache[OriginalPin])) { // We check pin connections for type to determine if we want to keep a connection or orphan it TSet PinConnectionsToKeep; for (UEdGraphPin* ConnectedPin : OriginalPin->LinkedTo) { // we might have already visited and changed the connected pin, so we test against its old type cache if available FNiagaraTypeDefinition ConnectedPinType = UEdGraphSchema_Niagara::PinToTypeDefinition(ConnectedPin); FNiagaraVariable ConnectedVariable(OldTypesCache.Contains(ConnectedPin) ? OldTypesCache[ConnectedPin] : ConnectedPinType, ConnectedPin->PinName); /* We keep a connection in the following cases: // - Default case: if the connected pin already has the type we are changing to // - If the connected pin will have a matching type after type conversion is done // - If the connected pin is a numeric pin and the new type will be compatible with a numeric pin // - If we change types from vec -> position for easier fixup */ if(ConnectedPinType == NewType || (ConnectedPin->PinType.PinSubCategory == UNiagaraNodeParameterMapBase::ParameterPinSubCategory && ChangingPins.Contains(ConnectedPin)) || (ConnectedPinType == FNiagaraTypeDefinition::GetGenericNumericDef() && FNiagaraTypeDefinition::GetNumericTypes().Contains(NewType)) || (GetDefault()->bEnforceStrictStackTypes == false && ConnectedPinType == FNiagaraTypeDefinition::GetVec3Def() && NewType == FNiagaraTypeDefinition::GetPositionDef())) { PinConnectionsToKeep.Add(ConnectedPin); } } // in case we are keeping all connections, all previously existing connections are parameter pins of the same type or will be of the same type after this operation is done // we also check for default values cache as by this point we have already changed the default values of the original pin if((PinConnectionsToKeep.Num() != OriginalPin->LinkedTo.Num()) || (OldDefaultValuesCache[OriginalPin] != OldAutogeneratedDefaultValuesCache[OriginalPin])) { ensure(OldTypesCache.Contains(OriginalPin) && OldDefaultValuesCache.Contains(OriginalPin)); UEdGraphPin* NewOrphanedPin = Node->CreatePin(OriginalPin->Direction, OriginalPin->PinType.PinCategory, OrphanedPinName); NewOrphanedPin->PinType = UEdGraphSchema_Niagara::TypeDefinitionToPinType(OldTypesCache[OriginalPin]); NewOrphanedPin->DefaultValue = OldDefaultValuesCache[OriginalPin]; NewOrphanedPin->PersistentGuid = FGuid::NewGuid(); NewOrphanedPin->bNotConnectable = true; NewOrphanedPin->bOrphanedPin = true; TSet PinsToDisconnect; for (UEdGraphPin* ConnectedPin : OriginalPin->LinkedTo) { ConnectedPin->Modify(); // wire up the new orphaned pin with previous connections if we aren't keeping the connection if(!PinConnectionsToKeep.Contains(ConnectedPin)) { NewOrphanedPin->LinkedTo.Add(ConnectedPin); ConnectedPin->LinkedTo.Add(NewOrphanedPin); PinsToDisconnect.Add(ConnectedPin); } } // disconnect the previous connections from the original pin for (UEdGraphPin* PinToDisconnect : PinsToDisconnect) { PinToDisconnect->LinkedTo.Remove(OriginalPin); OriginalPin->LinkedTo.Remove(PinToDisconnect); } return NewOrphanedPin; } } return static_cast(nullptr); }; for (UNiagaraNodeParameterMapBase* NiagaraNode : NiagaraParameterNodes) { TArray NewOrphanedPins; bool bNodeChanged = false; TMap ValidOrphanedPinCandidates; // we go through all changing pins to change their type, and if applicable, attempt to create an orphaned pin for (UEdGraphPin* Pin : NiagaraNode->Pins) { if (ChangingPins.Contains(Pin)) { Pin->Modify(); Pin->PinType = NiagaraSchema->TypeDefinitionToPinType(NewType); // we need to restore the parameter sub category here, but only for parameter pins if(ChangingParameterPins.Contains(Pin)) { Pin->PinType.PinSubCategory = UNiagaraNodeParameterMapBase::ParameterPinSubCategory; } if(bAllowOrphanedPins) { // the name for most parameter pins will remain the same as before, but not for default pins, which will adapt the parameter's name FName OrphanedPinName = OrphanedDefaultPinNameOverrides.Contains(Pin) ? OrphanedDefaultPinNameOverrides[Pin] : Pin->PinName; ValidOrphanedPinCandidates.Add(Pin, OrphanedPinName); } // since we have now determined if we want to create an orphaned pin based on default values, we can safely reset them. Pin->ResetDefaultValue(); UNiagaraNode::SetPinDefaultToTypeDefaultIfUnset(Pin); bNodeChanged = true; } } TMap OriginalToOrphanedMap; for(auto& OrphanedPinCandidate : ValidOrphanedPinCandidates) { UEdGraphPin* NewOrphanedPin = TryCreateOrphanedPin(NiagaraNode, OrphanedPinCandidate.Value, OrphanedPinCandidate.Key); if(NewOrphanedPin != nullptr) { OriginalToOrphanedMap.Add(OrphanedPinCandidate.Key, NewOrphanedPin); NewOrphanedPins.Add(NewOrphanedPin); } } // we now match up the new orphaned default & output pins on map get nodes and add them to the node's map TSet HandledGuidsOfOrphanedPins; for(auto& Pin : NiagaraNode->Pins) { if(OriginalToOrphanedMap.Contains(Pin) && !HandledGuidsOfOrphanedPins.Contains(Pin)) { UEdGraphPin* OrphanedPin = OriginalToOrphanedMap[Pin]; OrphanedPin->PersistentGuid = FGuid::NewGuid(); HandledGuidsOfOrphanedPins.Add(Pin); if(UNiagaraNodeParameterMapGet* MapGet = Cast(NiagaraNode)) { if(UEdGraphPin* DefaultPin = MapGet->GetDefaultPin(Pin)) { if(OriginalToOrphanedMap.Contains(DefaultPin)) { if(UEdGraphPin* NewOrphanedDefaultPin = OriginalToOrphanedMap[DefaultPin]) { NewOrphanedDefaultPin->PersistentGuid = FGuid::NewGuid(); HandledGuidsOfOrphanedPins.Add(DefaultPin); MapGet->AddOrphanedPinPairGuids(OrphanedPin, NewOrphanedDefaultPin); } } } } } } if(bNodeChanged) { NiagaraNode->MarkNodeRequiresSynchronization(__FUNCTION__, true); } } for(const FNiagaraVariable& CurrentParameter : ParametersToChange) { TObjectPtr* ScriptVariablePtr = VariableToScriptVariable.Find(CurrentParameter); if (ScriptVariablePtr != nullptr && *ScriptVariablePtr) { UNiagaraScriptVariable& ScriptVar = *ScriptVariablePtr->Get(); ScriptVar.Modify(); ScriptVar.Variable.SetType(NewType); VariableToScriptVariable.Remove(CurrentParameter); FNiagaraVariable NewVarType(NewType, CurrentParameter.GetName()); VariableToScriptVariable.Add(NewVarType, TObjectPtr(&ScriptVar)); ScriptVariableChanged(NewVarType); } // Also fix stale parameter reference map entries. This should usually happen in RefreshParameterReferences, but since we just changed VariableToScriptVariable we need to fix that as well. FNiagaraGraphParameterReferenceCollection* ReferenceCollection = ParameterToReferencesMap.Find(CurrentParameter); if (ReferenceCollection != nullptr) { if (ReferenceCollection->ParameterReferences.Num() > 0) { FNiagaraVariable NewVarType(NewType, CurrentParameter.GetName()); FNiagaraGraphParameterReferenceCollection NewReferenceCollection = *ReferenceCollection; ParameterToReferencesMap.Add(NewVarType, NewReferenceCollection); } ParameterToReferencesMap.Remove(CurrentParameter); } } InvalidateCachedParameterData(); OnParametersChangedDelegate.Broadcast(TInstancedStruct::Make()); } void UNiagaraGraph::ReplaceScriptReferences(UNiagaraScript* OldScript, UNiagaraScript* NewScript) { bool bChanged = false; for (UEdGraphNode* Node : Nodes) { UNiagaraNodeFunctionCall* FunctionNode = Cast(Node); if (FunctionNode && FunctionNode->FunctionScript == OldScript) { FunctionNode->Modify(); FunctionNode->FunctionScript = NewScript; bChanged = true; } } if (bChanged) { MarkGraphRequiresSynchronization(TEXT("Script references updated")); } } UNiagaraGraph::FOnParametersChanged& UNiagaraGraph::OnParametersChanged() { return OnParametersChangedDelegate; } TArray UNiagaraGraph::FindParameterMapDefaultValuePins(const FName VariableName) const { TArray OutDefaultPins; TArray MapGetNodes; GetNodesOfClass(MapGetNodes); for(UNiagaraNodeParameterMapGet* MapGet : MapGetNodes) { FPinCollectorArray OutputPins; MapGet->GetOutputPins(OutputPins); for (UEdGraphPin* OutputPin : OutputPins) { if (VariableName != OutputPin->PinName || OutputPin->PinType.PinSubCategory != UNiagaraNodeParameterMapBase::ParameterPinSubCategory) { continue; } if (UEdGraphPin* Pin = MapGet->GetDefaultPin(OutputPin)) { check(Pin->Direction == EEdGraphPinDirection::EGPD_Input); OutDefaultPins.AddUnique(Pin); } } } return OutDefaultPins; } // Impacts of this function include changing the default value of UNiagaraScriptVariable::Variable & DefaultMode (note that this won't impact the // key in the VariableToScriptVariable map. Additionally properties on the pins can also be changed. void UNiagaraGraph::ValidateDefaultPins() { check(!bIsForCompilationOnly); FNiagaraEditorModule& EditorModule = FNiagaraEditorModule::Get(); if (!VariableToScriptVariable.IsEmpty()) { TArray TraversedMapGetNodes; { TArray OutputNodes; GetNodesOfClass(OutputNodes); TArray NodesTraversed; for (UNiagaraNodeOutput* OutputNode : OutputNodes) { NodesTraversed.Reset(); BuildTraversal(NodesTraversed, OutputNode); for (UNiagaraNode* NiagaraNode : NodesTraversed) { if (UNiagaraNodeParameterMapGet* MapGetNode = Cast(NiagaraNode)) { TraversedMapGetNodes.AddUnique(MapGetNode); } } } } for (const FScriptVariableMap::ElementType& MetaData : VariableToScriptVariable) { const FNiagaraVariable& Variable = MetaData.Key; UNiagaraScriptVariable* ScriptVariable = MetaData.Value; if (!ScriptVariable || ScriptVariable->DefaultMode == ENiagaraDefaultMode::Custom || ScriptVariable->DefaultMode == ENiagaraDefaultMode::FailIfPreviouslyNotSet) { // If the user selected custom mode or if previously unset they can basically do whatever they want continue; } if (ScriptVariable->GetIsStaticSwitch()) { // We ignore static switch variables as they handle default values differently continue; } // Determine if the values set in the graph (which are used for the compilation) are consistent and if necessary update the metadata value FPinCollectorArray DefaultPins; { for (UNiagaraNodeParameterMapGet* MapGetNode : TraversedMapGetNodes) { FPinCollectorArray OutputPins; MapGetNode->GetOutputPins(OutputPins); for (UEdGraphPin* OutputPin : OutputPins) { if (Variable.GetName() != OutputPin->PinName || OutputPin->PinType.PinSubCategory != UNiagaraNodeParameterMapBase::ParameterPinSubCategory) { continue; } if (UEdGraphPin* Pin = MapGetNode->GetDefaultPin(OutputPin)) { check(Pin->Direction == EEdGraphPinDirection::EGPD_Input); DefaultPins.AddUnique(Pin); } } } } bool IsCustom = false; bool IsConsistent = true; bool DefaultInitialized = false; FNiagaraVariable DefaultData; auto TypeUtilityValue = EditorModule.GetTypeUtilities(Variable.GetType()); for (UEdGraphPin* Pin : DefaultPins) { if (Pin->LinkedTo.Num() > 0) { IsCustom = true; } else if (!Pin->bHidden && TypeUtilityValue) { FNiagaraVariable ComparisonData = Variable; TypeUtilityValue->SetValueFromPinDefaultString(Pin->DefaultValue, ComparisonData); if (!DefaultInitialized) { DefaultData = ComparisonData; DefaultInitialized = true; } else if (!ComparisonData.HoldsSameData(DefaultData)) { IsConsistent = false; } } } if (!IsCustom && !IsConsistent) { UE_LOG(LogNiagaraEditor, Log, TEXT("Niagara graph %s: The default value declaration for the variable '%s' is not consistent between the graph and the metadata.\n Either change the default mode to 'custom' or check the input pins in the parameter map get node in the graph."), *GetFullName(), *Variable.GetName().ToString()); continue; } if (IsCustom) { ScriptVariable->DefaultMode = ENiagaraDefaultMode::Custom; } else { for (UEdGraphPin* Pin : DefaultPins) { Pin->bNotConnectable = true; Pin->bDefaultValueIsReadOnly = true; if (ScriptVariable->DefaultMode == ENiagaraDefaultMode::Binding || ScriptVariable->DefaultMode == ENiagaraDefaultMode::FailIfPreviouslyNotSet) { Pin->bHidden = true; } } if (DefaultInitialized) { // set default value from pins ScriptVariable->Variable = DefaultData; } } } } } FName UNiagaraGraph::StandardizeName(FName Name, ENiagaraScriptUsage Usage, bool bIsGet, bool bIsSet) { bool bIsScriptUsage = Usage == ENiagaraScriptUsage::Module || Usage == ENiagaraScriptUsage::DynamicInput || Usage == ENiagaraScriptUsage::Function; TArray> NamePartStrings; TStringBuilder<128> NameAsString; Name.AppendString(NameAsString); UE::String::ParseTokens(NameAsString, TEXT("."), [&NamePartStrings](FStringView Token) { NamePartStrings.Add(Token); }); TArray> NameParts; for (FStringView NamePartString : NamePartStrings) { NameParts.Emplace(NamePartString); } if (NameParts.Num() == 0) { NameParts.Add(NAME_None); } TOptional Namespace; if (NameParts[0] == FNiagaraConstants::EngineNamespace || NameParts[0] == FNiagaraConstants::ParameterCollectionNamespace || NameParts[0] == FNiagaraConstants::UserNamespace || NameParts[0] == FNiagaraConstants::ArrayNamespace || NameParts[0] == FNiagaraConstants::DataInstanceNamespace) { // Don't modify engine, parameter collections, or user parameters since they're defined externally and won't have fixed up // names. Also ignore the array and DataInstance namespaces because they're special. return Name; } else if (NameParts[0] == FNiagaraConstants::LocalNamespace) { // Local can only be used in the context of a single module so force it to have a secondary module namespace. static const FName LocalNamespace = *(FNiagaraConstants::LocalNamespace.ToString() + TEXT(".") + FNiagaraConstants::ModuleNamespace.ToString()); Namespace = LocalNamespace; NameParts.RemoveAt(0); } else if (NameParts[0] == FNiagaraConstants::OutputNamespace) { static const FName OutputScriptNamespace = *(FNiagaraConstants::OutputNamespace.ToString() + TEXT(".") + FNiagaraConstants::ModuleNamespace.ToString()); static const FName OutputUnknownNamespace = *(FNiagaraConstants::OutputNamespace.ToString() + TEXT(".") + FName(NAME_None).ToString()); if (bIsScriptUsage) { if (bIsSet || NameParts.Num() <= 2) { // In a script outputs must always be written to the module namespace, and if the previous output didn't specify module // or a sub-namespace assume it was reading from the module namespace. Namespace = OutputScriptNamespace; NameParts.RemoveAt(0); } else { // When reading they can be from the module namespace, or a more specific namespace from a different module, we also need // to handle the case where they are reading from the output of a nested module, so allow an additional namespace too. if (NameParts.Num() > 3) { Namespace = *(NameParts[0].ToString() + TEXT(".") + NameParts[1].ToString() + TEXT(".") + NameParts[2].ToString()); NameParts.RemoveAt(0, 3); } else { Namespace = *(NameParts[0].ToString() + TEXT(".") + NameParts[1].ToString()); NameParts.RemoveAt(0, 2); } } } else { // The only valid usage for output parameters in system and emitter graphs is reading them from an aliased parameter of the form // Output.ModuleName.ValueName. If there are not enough name parts put in 'none' for the module name. if (NameParts.Num() > 2) { Namespace = *(NameParts[0].ToString() + TEXT(".") + NameParts[1].ToString()); NameParts.RemoveAt(0, 2); } else { Namespace = OutputUnknownNamespace; NameParts.RemoveAt(0); } } } else if ( NameParts[0] == FNiagaraConstants::ModuleNamespace || NameParts[0] == FNiagaraConstants::SystemNamespace || NameParts[0] == FNiagaraConstants::EmitterNamespace || NameParts[0] == FNiagaraConstants::ParticleAttributeNamespace) { if (NameParts.Num() == 2) { // Standard module input or dataset attribute. Namespace = NameParts[0]; NameParts.RemoveAt(0); } else { // If there are more than 2 name parts, allow the first 2 for a namespace and sub-namespace. // Sub-namespaces are used for module specific dataset attributes, or for configuring // inputs for nested modules. Namespace = *(NameParts[0].ToString() + TEXT(".") + NameParts[1].ToString()); NameParts.RemoveAt(0, 2); } } else if (NameParts[0] == FNiagaraConstants::TransientNamespace) { // Transient names have a single namespace. Namespace = FNiagaraConstants::TransientNamespace; NameParts.RemoveAt(0); } else { // Namespace was unknown. if (bIsScriptUsage && NameParts.Contains(FNiagaraConstants::ModuleNamespace)) { // If we're in a script check for a misplaced module namespace and if it has one, force it to be a // module output to help with fixing up usages. static const FName OutputScriptNamespace = *(FNiagaraConstants::OutputNamespace.ToString() + TEXT(".") + FNiagaraConstants::ModuleNamespace.ToString()); Namespace = OutputScriptNamespace; } else if(bIsScriptUsage || bIsGet) { // If it's in a get node or it's in a script, force it into the transient namespace. Namespace = FNiagaraConstants::TransientNamespace; } else { // Otherwise it's a set node in a system or emitter script so it must be used to configure a module input. For this situation // we have 2 cases, regular modules and set variables nodes. if (NameParts[0].ToString().StartsWith(TRANSLATOR_SET_VARIABLES_STR)) { // For a set variables node we need to strip the module name and then fully standardize the remainder of the // name using the settings for a map set in a module. We do this because the format of the input parameter will be // Module.NamespaceName.ValueName and the NamespaceName.ValueName portion will have been standardized as part of the // UNiagaraNodeAssignment post load. Namespace = NameParts[0]; NameParts.RemoveAt(0); FString AssignmentTargetString = NameParts[0].ToString(); for (int32 i = 1; i < NameParts.Num(); i++) { AssignmentTargetString += TEXT(".") + NameParts[i].ToString(); } FName StandardizedAssignmentTargetName = StandardizeName(*AssignmentTargetString, ENiagaraScriptUsage::Module, false, true); NameParts.Empty(); NameParts.Add(StandardizedAssignmentTargetName); } else { // For standard inputs we need to replace the module name with the module namespace and then standardize it, and then // remove the module namespace and use that as the name, and the module name as the namespace. Namespace = NameParts[0]; NameParts.RemoveAt(0); FString ModuleInputString = FNiagaraConstants::ModuleNamespace.ToString(); for (int32 i = 0; i < NameParts.Num(); i++) { ModuleInputString += TEXT(".") + NameParts[i].ToString(); } FName StandardizedModuleInput = StandardizeName(*ModuleInputString, ENiagaraScriptUsage::Module, true, false); FString StandardizedModuleInputString = StandardizedModuleInput.ToString(); StandardizedModuleInputString.RemoveFromStart(FNiagaraConstants::ModuleNamespace.ToString() + TEXT(".")); NameParts.Empty(); NameParts.Add(*StandardizedModuleInputString); } } } checkf(Namespace.IsSet(), TEXT("No namespace picked in %s."), *Name.ToString()); NameParts.Remove(FNiagaraConstants::ModuleNamespace); if (NameParts.Num() == 0) { NameParts.Add(NAME_None); } // Form the name by concatenating the remaining parts of the name. FString ParameterName; if (NameParts.Num() == 1) { ParameterName = NameParts[0].ToString(); } else { TArray RemainingNamePartStrings; for (FName NamePart : NameParts) { RemainingNamePartStrings.Add(NamePart.ToString()); } ParameterName = FString::Join(RemainingNamePartStrings, TEXT("")); } // Last, combine it with the namespace(s) chosen above. return *FString::Printf(TEXT("%s.%s"), *Namespace.GetValue().ToString(), *ParameterName); } void UNiagaraGraph::StandardizeParameterNames() { check(!bIsForCompilationOnly); TMap OldNameToStandardizedNameMap; bool bAnyNamesModified = false; const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); TArray OutputNodes; GetNodesOfClass(OutputNodes); auto HandleParameterMapNode = [&bAnyNamesModified](const UEdGraphSchema_Niagara* NiagaraSchema, UNiagaraNodeParameterMapBase* Node, EEdGraphPinDirection PinDirection, ENiagaraScriptUsage Usage, bool bIsGet, bool bIsSet, TMap& OldNameToStandardizedNameMap) { for (UEdGraphPin* Pin : Node->Pins) { if (Pin->Direction == PinDirection) { FNiagaraTypeDefinition PinType = NiagaraSchema->PinToTypeDefinition(Pin); if (PinType.IsValid() && PinType != FNiagaraTypeDefinition::GetParameterMapDef()) { FName* NewNamePtr = OldNameToStandardizedNameMap.Find(Pin->PinName); FName NewName; if (NewNamePtr != nullptr) { NewName = *NewNamePtr; } else { NewName = StandardizeName(Pin->PinName, Usage, bIsGet, bIsSet); OldNameToStandardizedNameMap.Add(Pin->PinName, NewName); } if (Pin->PinName != NewName) { bAnyNamesModified = true; } Pin->PinName = NewName; Pin->PinFriendlyName = FText::AsCultureInvariant(NewName.ToString()); } } } }; TSet AllTraversedNodes; for (UNiagaraNodeOutput* OutputNode : OutputNodes) { TArray TraversedNodes; BuildTraversal(TraversedNodes, OutputNode, false); AllTraversedNodes.Append(TraversedNodes); for (UNiagaraNode* TraversedNode : TraversedNodes) { TraversedNode->ConditionalPostLoad(); if (TraversedNode->IsA()) { HandleParameterMapNode(NiagaraSchema, CastChecked(TraversedNode), EGPD_Output, OutputNode->GetUsage(), true, false, OldNameToStandardizedNameMap); } else if (TraversedNode->IsA()) { HandleParameterMapNode(NiagaraSchema, CastChecked(TraversedNode), EGPD_Input, OutputNode->GetUsage(), false, true, OldNameToStandardizedNameMap); } } } for (UEdGraphNode* Node : Nodes) { UNiagaraNodeParameterMapBase* ParameterMapNode = Cast(Node); if (ParameterMapNode != nullptr && AllTraversedNodes.Contains(ParameterMapNode) == false) { if (ParameterMapNode->IsA()) { HandleParameterMapNode(NiagaraSchema, ParameterMapNode, EGPD_Output, ENiagaraScriptUsage::Module, true, false, OldNameToStandardizedNameMap); } else if (ParameterMapNode->IsA()) { HandleParameterMapNode(NiagaraSchema, ParameterMapNode, EGPD_Input, ENiagaraScriptUsage::Module, false, true, OldNameToStandardizedNameMap); } } } // Since we'll be modifying the keys, make a copy of the map and then clear the original so it cal be // repopulated. FScriptVariableMap OldVariableToScriptVariable = VariableToScriptVariable; VariableToScriptVariable.Empty(); for (TPair> VariableScriptVariablePair : OldVariableToScriptVariable) { FNiagaraVariable Variable = VariableScriptVariablePair.Key; UNiagaraScriptVariable* ScriptVariable = VariableScriptVariablePair.Value; ScriptVariable->ConditionalPostLoad(); if (ScriptVariable->GetIsStaticSwitch() == false) { // We ignore static switches here because they're not in the parameter and so they don't need // their parameter names to be fixed up. FName* NewNamePtr = OldNameToStandardizedNameMap.Find(Variable.GetName()); FName NewName; if (NewNamePtr != nullptr) { NewName = *NewNamePtr; } else { FName OldName = Variable.GetName(); NewName = StandardizeName(OldName, ENiagaraScriptUsage::Module, false, false); OldNameToStandardizedNameMap.Add(OldName, NewName); } if (Variable.GetName() != NewName) { bAnyNamesModified = true; } Variable.SetName(NewName); ScriptVariable->Variable.SetName(NewName); } VariableToScriptVariable.Add(Variable, ScriptVariable); } if (bAnyNamesModified) { MarkGraphRequiresSynchronization(TEXT("Standardized parameter names")); } } bool UNiagaraGraph::VariableLess(const FNiagaraVariable& Lhs, const FNiagaraVariable& Rhs) { return Lhs.GetName().LexicalLess(Rhs.GetName()); } TOptional UNiagaraGraph::GetScriptVariableData(const FNiagaraVariable& Variable) const { // for now this is only supported on the compilation copy if (bIsForCompilationOnly) { const int32 ScriptVariableIndex = Algo::BinarySearchBy(CompilationScriptVariables, Variable, &FNiagaraScriptVariableData::Variable, VariableLess); if (CompilationScriptVariables.IsValidIndex(ScriptVariableIndex)) { return CompilationScriptVariables[ScriptVariableIndex]; } } return TOptional(); } void UNiagaraGraph::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { // if this is a compilation copy we want to avoid sending any notifications (as we're not really tied to anything anymore). // one area that this can happen is through a Reload of the source system while a compilation is in flight. if (bIsForCompilationOnly) { return; } NotifyGraphChanged(); RefreshParameterReferences(); } void UNiagaraGraph::BeginDestroy() { Super::BeginDestroy(); ReleaseCompilationCopy(); } class UNiagaraScriptSource* UNiagaraGraph::GetSource() const { return CastChecked(GetOuter()); } FNiagaraCompileHash UNiagaraGraph::GetCompileReferencedDataHash(ENiagaraScriptUsage InUsage, const FGuid& InUsageId) const { for (int32 i = 0; i < CachedUsageInfo.Num(); i++) { if (UNiagaraScript::IsEquivalentUsage(CachedUsageInfo[i].UsageType, InUsage) && CachedUsageInfo[i].UsageId == InUsageId) { return CachedUsageInfo[i].ReferenceHashFromGraph; } } return FNiagaraCompileHash(); } FNiagaraCompileHash UNiagaraGraph::GetCompileDataHash(ENiagaraScriptUsage InUsage, const FGuid& InUsageId) const { for (int32 i = 0; i < CachedUsageInfo.Num(); i++) { if (UNiagaraScript::IsEquivalentUsage(CachedUsageInfo[i].UsageType, InUsage) && CachedUsageInfo[i].UsageId == InUsageId) { if (GNiagaraUseGraphHash <= 0) { return CachedUsageInfo[i].CompileHash; } else { return CachedUsageInfo[i].CompileHashFromGraph; } } } return FNiagaraCompileHash(); } FGuid UNiagaraGraph::GetBaseId(ENiagaraScriptUsage InUsage, const FGuid& InUsageId) const { for (int32 i = 0; i < CachedUsageInfo.Num(); i++) { if (UNiagaraScript::IsEquivalentUsage(CachedUsageInfo[i].UsageType, InUsage) && CachedUsageInfo[i].UsageId == InUsageId) { return CachedUsageInfo[i].BaseId; } } return FGuid(); } void UNiagaraGraph::ForceBaseId(ENiagaraScriptUsage InUsage, const FGuid& InUsageId, const FGuid InForcedBaseId) { FNiagaraGraphScriptUsageInfo* MatchingCachedUsageInfo = CachedUsageInfo.FindByPredicate([InUsage, InUsageId](const FNiagaraGraphScriptUsageInfo& CachedUsageInfoItem) { return CachedUsageInfoItem.UsageType == InUsage && CachedUsageInfoItem.UsageId == InUsageId; }); if (MatchingCachedUsageInfo == nullptr) { MatchingCachedUsageInfo = &CachedUsageInfo.AddDefaulted_GetRef(); MatchingCachedUsageInfo->UsageType = InUsage; MatchingCachedUsageInfo->UsageId = InUsageId; } MatchingCachedUsageInfo->BaseId = InForcedBaseId; } UEdGraphPin* UNiagaraGraph::FindParameterMapDefaultValuePin(const FName VariableName, ENiagaraScriptUsage InUsage, ENiagaraScriptUsage InParentUsage) const { TArray DefaultInputPin = { nullptr }; MultiFindParameterMapDefaultValuePins({ VariableName }, InUsage, InParentUsage, { DefaultInputPin }); return DefaultInputPin[0]; } void UNiagaraGraph::MultiFindParameterMapDefaultValuePins(TConstArrayView VariableNames, ENiagaraScriptUsage InUsage, ENiagaraScriptUsage InParentUsage, TArrayView DefaultPins) const { TArray MatchingDefaultPins; TArray NodesTraversed; BuildTraversal(NodesTraversed, InUsage, FGuid(), true); FPinCollectorArray OutputPins; const int32 VariableNameCount = VariableNames.Num(); int32 DefaultPinsFound = 0; for (UNiagaraNode* Node : NodesTraversed) { if (UNiagaraNodeParameterMapGet* GetNode = Cast(Node)) { OutputPins.Reset(); GetNode->GetOutputPins(OutputPins); for (UEdGraphPin* OutputPin : OutputPins) { int32 NameIndex = VariableNames.IndexOfByKey(OutputPin->PinName); if ((NameIndex != INDEX_NONE) && (DefaultPins[NameIndex] == nullptr)) { UEdGraphPin* Pin = GetNode->GetDefaultPin(OutputPin); if (Pin) { ++DefaultPinsFound; DefaultPins[NameIndex] = Pin; if (DefaultPinsFound == VariableNameCount) { break; } } } } } } auto TraceInputPin = [&](UEdGraphPin* Pin) { if (Pin && Pin->LinkedTo.Num() != 0 && Pin->LinkedTo[0] != nullptr) { UNiagaraNode* Owner = Cast(Pin->LinkedTo[0]->GetOwningNode()); UEdGraphPin* PreviousInput = Pin; int32 NumIters = 0; while (Owner) { // Check to see if there are any reroute or choose by usage nodes involved in this.. UEdGraphPin* InputPin = Owner->GetPassThroughPin(PreviousInput->LinkedTo[0], InParentUsage); if (InputPin == nullptr) { return PreviousInput; } else if (InputPin->LinkedTo.Num() == 0) { return InputPin; } check(InputPin->LinkedTo[0] != nullptr); Owner = Cast(InputPin->LinkedTo[0]->GetOwningNode()); PreviousInput = InputPin; ++NumIters; check(NumIters < Nodes.Num()); // If you hit this assert then we have a cycle in our graph somewhere. } } return Pin; }; if (DefaultPinsFound > 0) { for (UEdGraphPin*& DefaultInputPin : DefaultPins) { DefaultInputPin = TraceInputPin(DefaultInputPin); } } } void UNiagaraGraph::FindOutputNodes(TArray& OutputNodes) const { for (UEdGraphNode* Node : Nodes) { if (UNiagaraNodeOutput* OutNode = Cast(Node)) { OutputNodes.Add(OutNode); } } } void UNiagaraGraph::FindOutputNodes(ENiagaraScriptUsage TargetUsageType, TArray& OutputNodes) const { TArray NodesFound; for (UEdGraphNode* Node : Nodes) { UNiagaraNodeOutput* OutNode = Cast(Node); if (OutNode && OutNode->GetUsage() == TargetUsageType) { NodesFound.Add(OutNode); } } OutputNodes = NodesFound; } void UNiagaraGraph::FindEquivalentOutputNodes(ENiagaraScriptUsage TargetUsageType, TArray& OutputNodes) const { TArray NodesFound; for (UEdGraphNode* Node : Nodes) { UNiagaraNodeOutput* OutNode = Cast(Node); if (OutNode && UNiagaraScript::IsEquivalentUsage(OutNode->GetUsage(), TargetUsageType)) { NodesFound.Add(OutNode); } } OutputNodes = NodesFound; } void BuildCompleteTraversal(UEdGraphNode* CurrentNode, TArray& AllNodes) { if (CurrentNode == nullptr || AllNodes.Contains(CurrentNode)) { return; } AllNodes.Add(CurrentNode); for (UEdGraphPin* Pin : CurrentNode->GetAllPins()) { if(Pin->Direction == EGPD_Input) { for (UEdGraphPin* LinkedPin : Pin->LinkedTo) { BuildCompleteTraversal(LinkedPin->GetOwningNode(), AllNodes); } } } } UNiagaraGraph* UNiagaraGraph::CreateCompilationCopy(const TArray& CompileUsages) { check(!bIsForCompilationOnly); UNiagaraGraph* Result = NewObject(); FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked("NiagaraEditor"); // create a shallow copy for (TFieldIterator PropertyIt(GetClass(), EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; if (*Property->GetNameCPP() == GET_MEMBER_NAME_CHECKED(UEdGraph, Nodes)) { // The nodes will be handled separately below. continue; } const uint8* SourceAddr = Property->ContainerPtrToValuePtr(this); uint8* DestinationAddr = Property->ContainerPtrToValuePtr(Result); Property->CopyCompleteValue(DestinationAddr, SourceAddr); } Result->bIsForCompilationOnly = true; Result->CompilationScriptVariables.Reserve(Result->VariableToScriptVariable.Num()); for (const auto& It : Result->VariableToScriptVariable) { if (const UNiagaraScriptVariable* ScriptVariable = It.Value) { FNiagaraScriptVariableData& VariableData = Result->CompilationScriptVariables.AddDefaulted_GetRef(); VariableData.InitFrom(*ScriptVariable, false); } } Algo::StableSortBy(Result->CompilationScriptVariables, &FNiagaraScriptVariableData::Variable, VariableLess); Result->VariableToScriptVariable.Empty(); // Only add references to nodes which match a compile usage. TMap DuplicationMapping; TArray OutputNodes; GetNodesOfClass(OutputNodes); for (UNiagaraNodeOutput* OutputNode : OutputNodes) { if(CompileUsages.ContainsByPredicate([OutputNode](ENiagaraScriptUsage CompileUsage) { return UNiagaraScript::IsEquivalentUsage(CompileUsage, OutputNode->GetUsage()); })) { TArray TraversedNodes; BuildCompleteTraversal(OutputNode, TraversedNodes); Result->Nodes.Append(TraversedNodes); } } TSet RerouteNodesToFixup; TArray NewNodes; for (UEdGraphNode* Node : Result->Nodes) { if (const UNiagaraNodeReroute* RerouteNode = Cast(Node)) { RerouteNodesToFixup.Add(RerouteNode); continue; } Node->ClearFlags(RF_Transactional); UEdGraphNode* DupNode = NewNodes.Add_GetRef(DuplicateObject(Node, Result)); Node->SetFlags(RF_Transactional); DuplicationMapping.Add(Node, DupNode); } // Detect any invalidations that might have snuck in on the source graph! for (UEdGraphNode* Node : Result->Nodes) { for (UEdGraphPin* Pin : Node->Pins) { for (int j = 0; j < Pin->LinkedTo.Num(); j++) { if (Pin->LinkedTo[j]) { UEdGraphNode* NodeB = Pin->LinkedTo[j]->GetOwningNode(); if (Node && NodeB && Node->GetGraph() != NodeB->GetGraph()) { UE_LOG(LogNiagaraEditor, Error, TEXT("%s"), *NodeB->GetPathName()); const FText WarningTemplate = LOCTEXT("DuplicationErrorToast", "Duplication Error! {0} was touched when cloning for compilation. Please report this issue to the Niagara team! "); FText MismatchWarning = FText::Format( WarningTemplate, FText::FromString(NodeB->GetPathName())); FNiagaraEditorUtilities::WarnWithToastAndLog(MismatchWarning); } } } } } for (UEdGraphNode* Node : NewNodes) { // fix up linked pins for (UEdGraphPin* Pin : Node->Pins) { ensure(Pin->GetOwningNode()->GetGraph() == Result); for (int i = 0; i < Pin->LinkedTo.Num(); i++) { UEdGraphPin* LinkedPin = Pin->LinkedTo[i]; UEdGraphNode* OwningNode = LinkedPin->GetOwningNode(); // if the owning node is already in the right graph then no need to adjust it (this can // happen if the pin connection is added as a part of removing the reroute nodes) if (OwningNode->GetGraph() == Result) { continue; } UEdGraphNode** NewNodePtr = DuplicationMapping.Find(OwningNode); bool bAddReciprocalLink = false; if (NewNodePtr == nullptr) { // check to see if we're dealing with an input pin that is coming from // a reroute node that was trimmed above if (Pin->Direction == EGPD_Input && RerouteNodesToFixup.Contains(OwningNode)) { check(LinkedPin->Direction == EGPD_Output); LinkedPin = CastChecked(OwningNode)->GetTracedOutputPin(LinkedPin, false); // reroute nodes can return nullptr for the traced output if they are not connected to anything if (LinkedPin != nullptr) { OwningNode = LinkedPin->GetOwningNode(); NewNodePtr = DuplicationMapping.Find(OwningNode); bAddReciprocalLink = true; } } } if (NewNodePtr == nullptr) { // If output pins were connected to another node which wasn't encountered in traversal then it's possible it won't be found here. // In that case set the linked pin to nullptr to be removed later. Pin->LinkedTo[i] = nullptr; } else { UEdGraphPin** NodePin = (*NewNodePtr)->Pins.FindByPredicate([LinkedPin](UEdGraphPin* Pin) { return Pin->PinId == LinkedPin->PinId; }); check(NodePin); Pin->LinkedTo[i] = *NodePin; if (bAddReciprocalLink) { (*NodePin)->LinkedTo.AddUnique(Pin); } } } // Remove any null linked pins. Pin->LinkedTo.RemoveAll([](const UEdGraphPin* Pin) { return Pin == nullptr; }); } } Result->Nodes = NewNodes; // probably not necessary for compilation, but remove references to the original graph Result->ParameterToReferencesMap.Empty(); // by setting this flag to false we make sure that if something attempts to refresh the reference map that we'll // hit the ensure at the top of RefreshParameterReferences() Result->bParameterReferenceRefreshPending = true; return Result; } void UNiagaraGraph::ReleaseCompilationCopy() { if (!bIsForCompilationOnly) { return; } MarkAsGarbage(); } UNiagaraNodeOutput* UNiagaraGraph::FindOutputNode(ENiagaraScriptUsage TargetUsageType, FGuid TargetUsageId) const { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_FindOutputNode); for (UEdGraphNode* Node : Nodes) { if (UNiagaraNodeOutput* OutNode = Cast(Node)) { if (OutNode->GetUsage() == TargetUsageType && OutNode->GetUsageId() == TargetUsageId) { return OutNode; } } } return nullptr; } UNiagaraNodeOutput* UNiagaraGraph::FindEquivalentOutputNode(ENiagaraScriptUsage TargetUsageType, FGuid TargetUsageId) const { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_FindOutputNode); for (UEdGraphNode* Node : Nodes) { if (UNiagaraNodeOutput* OutNode = Cast(Node)) { if (UNiagaraScript::IsEquivalentUsage(OutNode->GetUsage(), TargetUsageType) && OutNode->GetUsageId() == TargetUsageId) { return OutNode; } } } return nullptr; } void BuildTraversalHelper(TArray& OutNodesTraversed, UNiagaraNode* CurrentNode, bool bEvaluateStaticSwitches) { auto VisitPin = [](const UEdGraphPin* Pin, TArray& OutNodesTraversed, bool bEvaluateStaticSwitches) { if (Pin->Direction == EEdGraphPinDirection::EGPD_Input && Pin->LinkedTo.Num() > 0) { for (UEdGraphPin* LinkedPin : Pin->LinkedTo) { bool bFilterForCompilation = true; TArray NodesVisitedDuringTrace; UEdGraphPin* TracedPin = bEvaluateStaticSwitches ? UNiagaraNode::TraceOutputPin(LinkedPin, bFilterForCompilation, &NodesVisitedDuringTrace) : LinkedPin; if (TracedPin != nullptr) { // Deal with static switches driven by a pin... for (const UNiagaraNode* VisitedNode : NodesVisitedDuringTrace) { if (OutNodesTraversed.Contains(VisitedNode)) { continue; } BuildTraversalHelper(OutNodesTraversed, (UNiagaraNode * )VisitedNode, bEvaluateStaticSwitches); } UNiagaraNode* Node = Cast(TracedPin->GetOwningNode()); if (OutNodesTraversed.Contains(Node)) { continue; } BuildTraversalHelper(OutNodesTraversed, Node, bEvaluateStaticSwitches); } } } }; if (CurrentNode == nullptr) { return; } if (bEvaluateStaticSwitches) { UNiagaraNodeStaticSwitch* StaticSwitch = Cast< UNiagaraNodeStaticSwitch>(CurrentNode); if (StaticSwitch && StaticSwitch->IsSetByPin()) { // The selector pin is never traversed directly below by the TracedPin route, so we explicitly visit it here. UEdGraphPin* Pin = StaticSwitch->GetSelectorPin(); if (Pin) VisitPin(Pin, OutNodesTraversed, bEvaluateStaticSwitches); } else if (StaticSwitch) { return; } } for (UEdGraphPin* Pin : CurrentNode->GetAllPins()) { VisitPin(Pin, OutNodesTraversed, bEvaluateStaticSwitches); } OutNodesTraversed.Add(CurrentNode); } void UNiagaraGraph::BuildTraversal(TArray& OutNodesTraversed, ENiagaraScriptUsage TargetUsage, FGuid TargetUsageId, bool bEvaluateStaticSwitches) const { UNiagaraNodeOutput* Output = FindOutputNode(TargetUsage, TargetUsageId); if (Output) { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_BuildTraversal); BuildTraversalHelper(OutNodesTraversed, Output, bEvaluateStaticSwitches); } } void UNiagaraGraph::BuildTraversal(TArray& OutNodesTraversed, UNiagaraNode* FinalNode, bool bEvaluateStaticSwitches) { if (FinalNode) { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_BuildTraversal); BuildTraversalHelper(OutNodesTraversed, FinalNode, bEvaluateStaticSwitches); } } void UNiagaraGraph::FindInputNodes(TArray& OutInputNodes, UNiagaraGraph::FFindInputNodeOptions Options) const { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_FindInputNodes); TArray InputNodes; if (!Options.bFilterByScriptUsage) { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_FindInputNodes_NotFilterUsage); for (UEdGraphNode* Node : Nodes) { UNiagaraNodeInput* NiagaraInputNode = Cast(Node); if (NiagaraInputNode != nullptr && ((NiagaraInputNode->Usage == ENiagaraInputNodeUsage::Parameter && Options.bIncludeParameters) || (NiagaraInputNode->Usage == ENiagaraInputNodeUsage::Attribute && Options.bIncludeAttributes) || (NiagaraInputNode->Usage == ENiagaraInputNodeUsage::SystemConstant && Options.bIncludeSystemConstants) || (NiagaraInputNode->Usage == ENiagaraInputNodeUsage::TranslatorConstant && Options.bIncludeTranslatorConstants))) { InputNodes.Add(NiagaraInputNode); } } } else { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_FindInputNodes_FilterUsage); TArray Traversal; BuildTraversal(Traversal, Options.TargetScriptUsage, Options.TargetScriptUsageId); for (UNiagaraNode* Node : Traversal) { UNiagaraNodeInput* NiagaraInputNode = Cast(Node); if (NiagaraInputNode != nullptr && ((NiagaraInputNode->Usage == ENiagaraInputNodeUsage::Parameter && Options.bIncludeParameters) || (NiagaraInputNode->Usage == ENiagaraInputNodeUsage::Attribute && Options.bIncludeAttributes) || (NiagaraInputNode->Usage == ENiagaraInputNodeUsage::SystemConstant && Options.bIncludeSystemConstants))) { InputNodes.Add(NiagaraInputNode); } } } if (Options.bFilterDuplicates) { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_FindInputNodes_FilterDupes); for (UNiagaraNodeInput* InputNode : InputNodes) { auto NodeMatches = [=](UNiagaraNodeInput* UniqueInputNode) { if (InputNode->Usage == ENiagaraInputNodeUsage::Parameter) { return UniqueInputNode->Input.IsEquivalent(InputNode->Input, false); } else { return UniqueInputNode->Input.IsEquivalent(InputNode->Input); } }; if (OutInputNodes.ContainsByPredicate(NodeMatches) == false) { OutInputNodes.Add(InputNode); } } } else { OutInputNodes.Append(InputNodes); } if (Options.bSort) { NIAGARA_SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Graph_FindInputNodes_Sort); UNiagaraNodeInput::SortNodes(OutInputNodes); } } TArray UNiagaraGraph::FindStaticSwitchInputs(bool bReachableOnly, const TArray& InStaticVars) const { TArray NodesToProcess = bReachableOnly ? FindReachableNodes(InStaticVars) : Nodes; TArray Result; for (UEdGraphNode* Node : NodesToProcess) { UNiagaraNodeStaticSwitch* SwitchNode = Cast(Node); if (SwitchNode && !SwitchNode->IsSetByCompiler() && !SwitchNode->IsSetByPin()) { FNiagaraVariable Variable(SwitchNode->GetInputType(), SwitchNode->InputParameterName); Result.AddUnique(Variable); } if (UNiagaraNodeFunctionCall* FunctionNode = Cast(Node)) { for (const FNiagaraPropagatedVariable& Propagated : FunctionNode->PropagatedStaticSwitchParameters) { Result.AddUnique(Propagated.ToVariable()); } } } Algo::SortBy(Result, &FNiagaraVariable::GetName, FNameLexicalLess()); return Result; } TArray UNiagaraGraph::FindReachableNodes(const TArray& InStaticVars) const { TArray ResultNodes; FNiagaraParameterMapHistoryBuilder Builder; Builder.RegisterExternalStaticVariables(InStaticVars); Builder.SetIgnoreDisabled(false); TArray OutNodes; FindOutputNodes(OutNodes); ResultNodes.Append(OutNodes); for (UNiagaraNodeOutput* OutNode : OutNodes) { Builder.BuildParameterMaps(OutNode, true); { TArray VisitedNodes; Builder.GetContextuallyVisitedNodes(VisitedNodes); for (const UNiagaraNode* Node : VisitedNodes) { if (Node->GetOuter() == this) ResultNodes.AddUnique((UNiagaraNode*)Node); } } } return ResultNodes; } void UNiagaraGraph::GetParameters(TArray& Inputs, TArray& Outputs)const { Inputs.Empty(); Outputs.Empty(); TArray InputsNodes; FFindInputNodeOptions Options; Options.bSort = true; FindInputNodes(InputsNodes, Options); for (UNiagaraNodeInput* Input : InputsNodes) { Inputs.Add(Input->Input); } TArray OutputNodes; FindOutputNodes(OutputNodes); for (UNiagaraNodeOutput* OutputNode : OutputNodes) { for (FNiagaraVariable& Var : OutputNode->Outputs) { Outputs.AddUnique(Var); } } //Do we need to sort outputs? //Should leave them as they're defined in the output node? // auto SortVars = [](const FNiagaraVariable& A, const FNiagaraVariable& B) // { // //Case insensitive lexicographical comparisons of names for sorting. // return A.GetName().ToString() < B.GetName().ToString(); // }; // Outputs.Sort(SortVars); } void UNiagaraGraph::GetAllInputScriptVariables(TArray& OutScriptVariables) const { TArray AllInputScriptVariables; for(const auto& VariablePair : GetAllMetaData()) { if(VariablePair.Key.IsInNameSpace(FNiagaraConstants::ModuleNamespace) || VariablePair.Value->GetIsStaticSwitch()) { AllInputScriptVariables.Add(VariablePair.Value); } } OutScriptVariables.Append(AllInputScriptVariables); } const UNiagaraGraph::FScriptVariableMap& UNiagaraGraph::GetAllMetaData() const { check(!bIsForCompilationOnly); return VariableToScriptVariable; } UNiagaraGraph::FScriptVariableMap& UNiagaraGraph::GetAllMetaData() { check(!bIsForCompilationOnly); return VariableToScriptVariable; } void UNiagaraGraph::ConditionalRefreshParameterReferences() { if (bParameterReferenceRefreshPending) { RefreshParameterReferences(); } } const TMap& UNiagaraGraph::GetParameterReferenceMap() const { if (bParameterReferenceRefreshPending) { RefreshParameterReferences(); } return ParameterToReferencesMap; } UNiagaraScriptVariable* UNiagaraGraph::GetScriptVariable(FNiagaraVariable Parameter) const { check(!bIsForCompilationOnly); if (const TObjectPtr* FoundScriptVariable = VariableToScriptVariable.Find(Parameter)) { return *FoundScriptVariable; } return nullptr; } UNiagaraScriptVariable* UNiagaraGraph::GetScriptVariable(FName ParameterName) const { check(!bIsForCompilationOnly); for (auto& VariableToScriptVariableItem : VariableToScriptVariable) { if (VariableToScriptVariableItem.Key.GetName() == ParameterName) { return VariableToScriptVariableItem.Value; } } return nullptr; } UNiagaraScriptVariable* UNiagaraGraph::GetScriptVariable(FGuid VariableGuid) const { check(!bIsForCompilationOnly); for (auto& VariableToScriptVariableItem : VariableToScriptVariable) { if(VariableToScriptVariableItem.Value->Metadata.GetVariableGuid() == VariableGuid) { return VariableToScriptVariableItem.Value; } } return nullptr; } TArray UNiagaraGraph::GetChildScriptVariablesForInput_Deprecated(FGuid VariableGuid) const { check(!bIsForCompilationOnly); TArray ChildrenVariables; TOptional FoundVariable; for (auto& VariableToScriptVariableItem : VariableToScriptVariable) { if(VariableToScriptVariableItem.Value->Metadata.GetVariableGuid() == VariableGuid) { FoundVariable = VariableToScriptVariableItem.Key; } } if(FoundVariable.IsSet()) { for (auto& VariableToScriptVariableItem : VariableToScriptVariable) { if(VariableToScriptVariableItem.Value->Metadata.GetParentAttribute_DEPRECATED() == FoundVariable->GetName()) { ChildrenVariables.Add(VariableToScriptVariableItem.Value); } } } ChildrenVariables.Sort([&](const UNiagaraScriptVariable& VariableA, const UNiagaraScriptVariable& VariableB) { return VariableA.Metadata.GetEditorSortPriority_DEPRECATED() < VariableB.Metadata.GetEditorSortPriority_DEPRECATED(); }); return ChildrenVariables; } TArray UNiagaraGraph::GetChildScriptVariableGuidsForInput(FGuid VariableGuid) const { check(!bIsForCompilationOnly); TArray ChildrenVariableGuids; TOptional FoundVariable; for (auto& VariableToScriptVariableItem : VariableToScriptVariable) { if(VariableToScriptVariableItem.Value->Metadata.GetVariableGuid() == VariableGuid) { FoundVariable = VariableToScriptVariableItem.Key; } } if(FoundVariable.IsSet()) { TArray Variables; GetAllVariables(Variables); for(FNiagaraVariable& Variable : Variables) { TOptional MetaData = GetMetaData(Variable); if(MetaData.IsSet()) { FHierarchyElementIdentity Identity({MetaData.GetValue().GetVariableGuid()}, {}); if(UHierarchyElement* FoundHierarchyParameter = ParameterHierarchyRoot->FindChildWithIdentity(Identity, true)) { TArray ChildrenScriptParameters; FoundHierarchyParameter->GetChildrenOfType(ChildrenScriptParameters, false); for(UNiagaraHierarchyScriptParameter* ChildrenScriptParameter : ChildrenScriptParameters) { ChildrenVariableGuids.Add(ChildrenScriptParameter->GetScriptVariable()->Metadata.GetVariableGuid()); } } } else { UE_LOG(LogNiagaraEditor, Log, TEXT("Couldn't find metadata for variable")); } } } return ChildrenVariableGuids; } UNiagaraScriptVariable* UNiagaraGraph::AddParameter(const FNiagaraVariable& Parameter, bool bIsStaticSwitch /*= false*/) { check(!bIsForCompilationOnly); // Delay the NotifyGraphChanged() call until the static switch flag is set on the UNiagaraScriptVariable so that ParameterPanel displays correctly. const bool bNotifyChanged = false; UNiagaraScriptVariable* NewScriptVar = AddParameter(Parameter, FNiagaraVariableMetaData(), bIsStaticSwitch, bNotifyChanged); NotifyGraphChanged(); return NewScriptVar; } UNiagaraScriptVariable* UNiagaraGraph::AddParameter(const FNiagaraVariable& Parameter, const FNiagaraVariableMetaData& ParameterMetaData, bool bIsStaticSwitch, bool bNotifyChanged) { check(!bIsForCompilationOnly); FNiagaraGraphParameterReferenceCollection* FoundParameterReferenceCollection = ParameterToReferencesMap.Find(Parameter); if (!FoundParameterReferenceCollection) { const bool bCreatedByUser = !bIsStaticSwitch; ParameterToReferencesMap.Emplace(Parameter, {bCreatedByUser}); } TObjectPtr* FoundScriptVariable = VariableToScriptVariable.Find(Parameter); if (!FoundScriptVariable) { Modify(); UNiagaraScriptVariable* NewScriptVariable = CreateScriptVariableInternal(Parameter, ParameterMetaData, bIsStaticSwitch); if (bNotifyChanged) { NotifyGraphChanged(); } return NewScriptVariable; } return *FoundScriptVariable; } UNiagaraScriptVariable* UNiagaraGraph::AddParameter(const UNiagaraScriptVariable* InScriptVar) { check(!bIsForCompilationOnly); TObjectPtr* FoundScriptVariable = VariableToScriptVariable.Find(InScriptVar->Variable); if (!FoundScriptVariable) { Modify(); UNiagaraScriptVariable* NewScriptVariable = CastChecked(StaticDuplicateObject(InScriptVar, this, FName())); NewScriptVariable->SetFlags(RF_Transactional); // If the incoming script variable is linked to a parameter definition, do not make a new ID. // The parameter ID is associated with the linked definition. // Vice-Versa if the new parameter is not linked to a parameter definition, create a new ID so that it is distinct for this graph. if (NewScriptVariable->GetIsSubscribedToParameterDefinitions() == false) { NewScriptVariable->Metadata.CreateNewGuid(); } ParameterToReferencesMap.Emplace(NewScriptVariable->Variable, {true /*bInCreated*/}); VariableToScriptVariable.Add(NewScriptVariable->Variable, NewScriptVariable); TInstancedStruct ChangeData = TInstancedStruct::Make(NewScriptVariable); OnParametersChangedDelegate.Broadcast(ChangeData); NotifyGraphChanged(); return NewScriptVariable; } ensureMsgf(false, TEXT("Tried to add parameter that already existed! Parameter: %s"), *InScriptVar->Variable.GetName().ToString()); return *FoundScriptVariable; } UNiagaraScriptVariable* UNiagaraGraph::CreateScriptVariableInternal(const FNiagaraVariable& Parameter, const FNiagaraVariableMetaData& ParameterMetaData, bool bIsStaticSwitch) { UNiagaraScriptVariable* NewScriptVariable = NewObject(this, FName(), RF_Transactional); NewScriptVariable->Init(Parameter, ParameterMetaData); NewScriptVariable->SetIsStaticSwitch(bIsStaticSwitch); FNiagaraEditorUtilities::ResetVariableToDefaultValue(NewScriptVariable->Variable); // Inputs in graphs (module namespace) are intrinsically written to, so set the default mode to value instead of fail if not set. if (NewScriptVariable->Variable.IsInNameSpace(FNiagaraConstants::ModuleNamespace)) { NewScriptVariable->DefaultMode = ENiagaraDefaultMode::Value; } else { NewScriptVariable->DefaultMode = ENiagaraDefaultMode::FailIfPreviouslyNotSet; } VariableToScriptVariable.Add(Parameter, NewScriptVariable); TInstancedStruct ChangeData = TInstancedStruct::Make(NewScriptVariable); OnParametersChangedDelegate.Broadcast(ChangeData); return NewScriptVariable; } FName UNiagaraGraph::MakeUniqueParameterName(const FName& InName) { TArray> Graphs; Graphs.Emplace(this); return MakeUniqueParameterNameAcrossGraphs(InName, Graphs); } FName UNiagaraGraph::MakeUniqueParameterNameAcrossGraphs(const FName& InName, TArray>& InGraphs) { TSet Names; for (TWeakObjectPtr Graph : InGraphs) { if (Graph.IsValid()) { for (const auto& ParameterElement : Graph->GetParameterReferenceMap()) { Names.Add(ParameterElement.Key.GetName()); } } } return FNiagaraUtilities::GetUniqueName(InName, Names); } void UNiagaraGraph::AddParameterReference(const FNiagaraVariable& Parameter, FNiagaraGraphParameterReference& NewParameterReference) { FNiagaraGraphParameterReferenceCollection* FoundParameterReferenceCollection = ParameterToReferencesMap.Find(Parameter); if (ensureMsgf(FoundParameterReferenceCollection != nullptr, TEXT("Failed to find parameter reference collection when adding graph parameter reference!"))) { FoundParameterReferenceCollection->ParameterReferences.Add(NewParameterReference); } } void UNiagaraGraph::RemoveParameter(const FNiagaraVariable& Parameter, bool bAllowDeleteStaticSwitch /*= false*/) { check(!bIsForCompilationOnly); // make sure that we have collected parameter references already ConditionalRefreshParameterReferences(); FNiagaraGraphParameterReferenceCollection* ReferenceCollection = ParameterToReferencesMap.Find(Parameter); if (ReferenceCollection) { for (int32 Index = 0; Index < ReferenceCollection->ParameterReferences.Num(); Index++) { const FNiagaraGraphParameterReference& Reference = ReferenceCollection->ParameterReferences[Index]; UNiagaraNode* Node = Cast(Reference.Value.Get()); if (Node && Node->GetGraph() == this) { if (Node->IsA(UNiagaraNodeStaticSwitch::StaticClass()) && bAllowDeleteStaticSwitch == false) { // Static switch parameters are automatically populated from the graph nodes and cannot be manually deleted NotifyGraphChanged(); return; } UEdGraphPin* Pin = Node->GetPinByPersistentGuid(Reference.Key); if (Pin) { Node->RemovePin(Pin); } } } // Remove it from the reference collection directly because it might have been user added and // these aren't removed when the cached data is rebuilt. ParameterToReferencesMap.Remove(Parameter); NotifyGraphChanged(); } TArray VarsToRemove; for (auto It : VariableToScriptVariable) { if (It.Key == Parameter) { VarsToRemove.Add(It.Key); } } for (FNiagaraVariable& Var : VarsToRemove) { VariableToScriptVariable.Remove(Var); OnParametersChangedDelegate.Broadcast(TOptional>()); } } void CopyScriptVariableDataForRename(const UNiagaraScriptVariable& OldScriptVariable, UNiagaraScriptVariable& NewScriptVariable) { NewScriptVariable.Variable = OldScriptVariable.Variable; NewScriptVariable.DefaultMode = OldScriptVariable.DefaultMode; NewScriptVariable.DefaultBinding = OldScriptVariable.DefaultBinding; if(OldScriptVariable.GetDefaultValueData() != nullptr) { NewScriptVariable.SetDefaultValueData(OldScriptVariable.GetDefaultValueData()); } NewScriptVariable.Metadata = OldScriptVariable.Metadata; } bool UNiagaraGraph::RenameParameterFromPin(const FNiagaraVariable& Parameter, FName NewName, UEdGraphPin* InPin) { check(!bIsForCompilationOnly); auto TrySubscribeScriptVarToDefinitionByName = [this](UNiagaraScriptVariable* TargetScriptVar) { UNiagaraScript* OuterScript = GetTypedOuter(); bool bForDataProcessingOnly = true; TSharedPtr ScriptViewModel = MakeShared(FText(), ENiagaraParameterEditMode::EditAll, bForDataProcessingOnly); ScriptViewModel->SetScript(OuterScript); FNiagaraParameterDefinitionsUtilities::TrySubscribeScriptVarToDefinitionByName(TargetScriptVar, ScriptViewModel.Get()); }; FNiagaraVariable NewVariable(Parameter.GetType(), NewName); UNiagaraScriptVariable* ExistingVariable = GetScriptVariable(NewVariable); if (Parameter.GetName() == NewName || (ExistingVariable != nullptr && ExistingVariable->Variable.GetType() == NewVariable.GetType())) { return true; } Modify(); // make sure that we have collected parameter references already ConditionalRefreshParameterReferences(); if (FNiagaraGraphParameterReferenceCollection* ReferenceCollection = ParameterToReferencesMap.Find(Parameter)) { FNiagaraGraphParameterReferenceCollection NewReferences = *ReferenceCollection; if (NewReferences.ParameterReferences.Num() == 1 && NewReferences.ParameterReferences[0].Key == InPin->PersistentGuid) { bool bRenameRequestedFromStaticSwitch = false; bool bMerged = false; if (RenameParameter(Parameter, NewName, bRenameRequestedFromStaticSwitch, &bMerged)) { if (TObjectPtr const* RenamedScriptVarPtr = VariableToScriptVariable.Find(FNiagaraVariable(Parameter.GetType(), NewName))) { TrySubscribeScriptVarToDefinitionByName(*RenamedScriptVarPtr); } // Rename all the bindings that point to the old parameter for (auto It : VariableToScriptVariable) { UNiagaraScriptVariable* Variable = It.Value; if (Variable && Variable->DefaultBinding.GetName() == Parameter.GetName()) { Variable->DefaultBinding.SetName(NewName); } } if (!bMerged) { FNiagaraEditorUtilities::InfoWithToastAndLog(FText::Format( LOCTEXT("RenamedVarInGraphForAll", "\"{0}\" has been fully renamed as it was only used on this node."), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(NewName))); } return true; } return false; } } FNiagaraVariable NewParameter = Parameter; NewParameter.SetName(NewName); TObjectPtr* FoundOldScriptVariablePtr = VariableToScriptVariable.Find(Parameter); TObjectPtr* FoundNewScriptVariablePtr = VariableToScriptVariable.Find(NewParameter); bool bMerged = false; const bool bOldScriptVariableIsStaticSwitch = FoundOldScriptVariablePtr ? (*FoundOldScriptVariablePtr)->GetIsStaticSwitch() : false; const FNiagaraVariableMetaData OldMetaData = FoundOldScriptVariablePtr ? (*FoundOldScriptVariablePtr)->Metadata : FNiagaraVariableMetaData(); if (bIsRenamingParameter) { return false; } //Set metadata on the new parameter and put the new parameter into VariableToScriptVariable if (FoundOldScriptVariablePtr && !FoundNewScriptVariablePtr) { // Only create a new variable if needed. UNiagaraScriptVariable* FoundOldScriptVariable = *FoundOldScriptVariablePtr; UNiagaraScriptVariable* NewScriptVariable = CastChecked(StaticDuplicateObject(FoundOldScriptVariable, this, FName())); NewScriptVariable->SetFlags(RF_Transactional); CopyScriptVariableDataForRename(*FoundOldScriptVariable, *NewScriptVariable); NewScriptVariable->Variable.SetName(NewName); NewScriptVariable->Metadata.CreateNewGuid(); NewScriptVariable->SetIsSubscribedToParameterDefinitions(false); VariableToScriptVariable.Add(NewParameter, NewScriptVariable); TrySubscribeScriptVarToDefinitionByName(NewScriptVariable); const FNiagaraGraphParameterReferenceCollection* ReferenceCollection = GetParameterReferenceMap().Find(Parameter); if (ReferenceCollection && ReferenceCollection->ParameterReferences.Num() < 1) { VariableToScriptVariable.Remove(Parameter); } } if (FoundNewScriptVariablePtr) { bMerged = true; FNiagaraEditorUtilities::InfoWithToastAndLog(FText::Format( LOCTEXT("MergedVar", "\"{0}\" has been merged with parameter \"{1}\".\nAll of \"{1}\"'s meta-data will be used, overwriting \"{0}\"'s meta-data."), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(Parameter.GetName()), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(NewName))); } RefreshParameterReferences(); if (!bMerged) { FNiagaraEditorUtilities::InfoWithToastAndLog(FText::Format( LOCTEXT("RenamedVarInGraphForNode", "\"{0}\" has been duplicated as \"{1}\" as it is used in multiple locations.\nPlease edit the Parameters Panel version to change in all locations."), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(Parameter.GetName()), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(NewName))); } return false; } void UNiagaraGraph::FixupReferenceCollectionsPostRename(const FNiagaraVariable& OrigVariable, const FNiagaraVariable& DestVariable, bool bParameterMerged, bool bSuppressEvents) { // Fixup reference collection and pin names FNiagaraGraphParameterReferenceCollection NewReferences; if (ParameterToReferencesMap.RemoveAndCopyValue(OrigVariable, NewReferences)) { if (!NewReferences.ParameterReferences.IsEmpty()) { const FText NewNameText = FText::FromName(DestVariable.GetName()); for (FNiagaraGraphParameterReference& Reference : NewReferences.ParameterReferences) { UNiagaraNode* Node = Cast(Reference.Value.Get()); if (Node && Node->GetGraph() == this) { Node->Modify(); if (UEdGraphPin* Pin = Node->GetPinByPersistentGuid(Reference.Key)) { Pin->Modify(); Node->CommitEditablePinName(NewNameText, Pin, bSuppressEvents); } } } } if (!bParameterMerged) { ParameterToReferencesMap.Add(DestVariable, NewReferences); } else { RefreshParameterReferences(); } } } bool UNiagaraGraph::RenameParameter(const FNiagaraVariable& Parameter, FName NewName, bool bRenameRequestedFromStaticSwitch, bool* bOutMerged, bool bSuppressEvents) { check(!bIsForCompilationOnly); // Initialize the merger state if requested if (bOutMerged) *bOutMerged = false; if (Parameter.GetName() == NewName) return true; // Block rename when already renaming. This prevents recursion when CommitEditablePinName is called on referenced nodes. if (bIsRenamingParameter) { return false; } bIsRenamingParameter = true; bool bParameterMerged = false; Modify(); // Create the new parameter FNiagaraVariable NewParameter = Parameter; NewParameter.SetName(NewName); TObjectPtr* OldScriptVariablePtr = VariableToScriptVariable.Find(Parameter); TObjectPtr OldScriptVariable = OldScriptVariablePtr ? *OldScriptVariablePtr : nullptr; FNiagaraVariableMetaData OldMetaData; OldMetaData.CreateNewGuid(); if (OldScriptVariable != nullptr) { if (!bRenameRequestedFromStaticSwitch && OldScriptVariable->GetIsStaticSwitch()) { // We current disallow renaming static switch variables in the Parameters panel. bIsRenamingParameter = false; return false; } OldMetaData = OldScriptVariable->Metadata; } TObjectPtr* NewScriptVariablePtr = VariableToScriptVariable.Find(NewParameter); // Swap metadata to the new parameter; put the new parameter into VariableToScriptVariable if (OldScriptVariable != nullptr) { Modify(); // Rename all the bindings that point to the old parameter for (auto It : VariableToScriptVariable) { UNiagaraScriptVariable* Variable = It.Value; if (Variable && Variable->DefaultBinding.GetName() == Parameter.GetName()) { Variable->DefaultBinding.SetName(NewParameter.GetName()); } } // Only create a new variable if needed. if (NewScriptVariablePtr == nullptr) { // Replace the script variable data UNiagaraScriptVariable* NewScriptVariable = CastChecked(StaticDuplicateObject(OldScriptVariable, this, FName())); NewScriptVariable->SetFlags(RF_Transactional); CopyScriptVariableDataForRename(*OldScriptVariable, *NewScriptVariable); NewScriptVariable->Metadata.CreateNewGuid(); NewScriptVariable->Variable.SetName(NewName); VariableToScriptVariable.Add(NewParameter, NewScriptVariable); } if (!bRenameRequestedFromStaticSwitch) { // Static switches take care to remove the last existing parameter themselves, we don't want to remove the parameter if there are still switches with the name around VariableToScriptVariable.Remove(Parameter); } } // Either set the new meta-data or use the existing meta-data. if (NewScriptVariablePtr == nullptr) { SetMetaData(NewParameter, OldMetaData); } else { bParameterMerged = true; FName NewParamName = NewName; FNiagaraEditorUtilities::DecomposeVariableNamespace(NewName, NewParamName); FName OldParamName = Parameter.GetName(); FNiagaraEditorUtilities::DecomposeVariableNamespace(Parameter.GetName(), OldParamName); FNiagaraEditorUtilities::InfoWithToastAndLog(FText::Format( LOCTEXT("MergedVar", "\"{0}\" has been merged with parameter \"{1}\".\nAll of \"{1}\"'s meta-data will be used, overwriting \"{0}\"'s meta-data."), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(Parameter.GetName()), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(NewName))); } // make sure that we have collected parameter references already ConditionalRefreshParameterReferences(); FixupReferenceCollectionsPostRename(Parameter, NewParameter, bParameterMerged, bSuppressEvents); bIsRenamingParameter = false; NotifyGraphChanged(); if (bOutMerged) { *bOutMerged = bParameterMerged; } TInstancedStruct RenameData = TInstancedStruct::Make(); FNiagaraParameterRenamedData& Data = RenameData.GetMutable(); Data.OldScriptVariable = OldScriptVariable; Data.NewScriptVariable = VariableToScriptVariable[NewParameter]; OnParametersChangedDelegate.Broadcast(RenameData); return true; } bool UNiagaraGraph::RenameStaticSwitch(UNiagaraNodeStaticSwitch* SwitchNode, FName NewParameterName) { if (!SwitchNode || SwitchNode->InputParameterName == NewParameterName) { return false; } Modify(); bIsRenamingParameter = true; ON_SCOPE_EXIT{ bIsRenamingParameter = false; }; const FName OrigParameterName = SwitchNode->InputParameterName; const FNiagaraVariableBase OrigVariable(SwitchNode->GetInputType(), OrigParameterName); const FNiagaraVariableBase DestVariable(SwitchNode->GetInputType(), NewParameterName); // see if the old/new names map to existing UNiagaraScriptVariables UNiagaraScriptVariable* OrigScriptVariable = VariableToScriptVariable.FindRef(OrigVariable); UNiagaraScriptVariable* DestScriptVariable = VariableToScriptVariable.FindRef(DestVariable); // assign the new name to the node SwitchNode->InputParameterName = NewParameterName; // if we're just merging to an existing variable then we don't need to create a new one if (DestScriptVariable) { FNiagaraEditorUtilities::InfoWithToastAndLog(FText::Format( LOCTEXT("MergedVar", "\"{0}\" has been merged with parameter \"{1}\".\nAll of \"{1}\"'s meta-data will be used, overwriting \"{0}\"'s meta-data."), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(OrigParameterName), FNiagaraParameterUtilities::FormatParameterNameForTextDisplay(NewParameterName))); } else { TObjectPtr& CreatedParameter = VariableToScriptVariable.Add(DestVariable); if (OrigScriptVariable) { CreatedParameter = DuplicateObject(OrigScriptVariable, this); } else { CreatedParameter = NewObject(this); } CreatedParameter->SetFlags(RF_Transactional); CreatedParameter->Metadata.CreateNewGuid(); CreatedParameter->Variable = DestVariable; } TInstancedStruct RenameData = TInstancedStruct::Make(); FNiagaraParameterRenamedData& Data = RenameData.GetMutable(); Data.OldScriptVariable = OrigScriptVariable; Data.NewScriptVariable = VariableToScriptVariable[DestVariable]; OnParametersChangedDelegate.Broadcast(RenameData); // now see if we need to remove any script variables that are now obsolete because they have been renamed if (OrigScriptVariable) { const TArray StaticSwitchInputs = FindStaticSwitchInputs(false /*bReachableOnly*/); if (!StaticSwitchInputs.Contains(OrigVariable)) { RemoveParameter(OrigVariable, true); } } // Fixup reference collection and pin names FixupReferenceCollectionsPostRename(OrigVariable, DestVariable, DestScriptVariable != nullptr /* bIsMerging */, false /* bSuppressEvents */); NotifyGraphChanged(); // force the graph to refresh the metadata GetParameterReferenceMap(); return true; } void UNiagaraGraph::ScriptVariableChanged(FNiagaraVariable Variable) { check(!bIsForCompilationOnly); UNiagaraScriptVariable* ScriptVariable = GetScriptVariable(Variable); if (!ScriptVariable) { return; } if(ScriptVariable->GetIsStaticSwitch()) { TArray StaticSwitchNodes; GetNodesOfClass(StaticSwitchNodes); for(UNiagaraNodeStaticSwitch* StaticSwitchNode : StaticSwitchNodes) { if(StaticSwitchNode->InputParameterName == ScriptVariable->Variable.GetName()) { StaticSwitchNode->AttemptUpdatePins(); } } return; } TSet MapGetNodes; TArray Pins = FindParameterMapDefaultValuePins(Variable.GetName()); for (UEdGraphPin* Pin : Pins) { if(UNiagaraNodeParameterMapGet* MapGetNode = Cast(Pin->GetOwningNode())) { MapGetNodes.Add(MapGetNode); } if (ScriptVariable->DefaultMode == ENiagaraDefaultMode::Value && !Variable.GetType().IsDataInterface() && !Variable.GetType().IsUObject()) { FNiagaraEditorModule& EditorModule = FNiagaraEditorModule::Get(); auto TypeUtilityValue = EditorModule.GetTypeUtilities(Variable.GetType()); if (TypeUtilityValue.IsValid() && TypeUtilityValue->CanHandlePinDefaults()) { if (!Variable.IsDataAllocated()) { Variable.AllocateData(); } FString NewDefaultValue = TypeUtilityValue->GetPinDefaultStringFromValue(Variable); GetDefault()->TrySetDefaultValue(*Pin, NewDefaultValue, true); } } } for(UNiagaraNodeParameterMapGet* MapGet : MapGetNodes) { MapGet->SynchronizeDefaultPins(); } if(GIsTransacting == false) { ValidateDefaultPins(); } NotifyGraphChanged(); } FVersionedNiagaraEmitter UNiagaraGraph::GetOwningEmitter() const { UNiagaraEmitter* OwningEmitter = GetTypedOuter(); if (OwningEmitter) { for (FNiagaraAssetVersion Version : OwningEmitter->GetAllAvailableVersions()) { FVersionedNiagaraEmitterData* EmitterData = OwningEmitter->GetEmitterData(Version.VersionGuid); if (EmitterData->GraphSource) { UNiagaraGraph* EmitterGraph = Cast(EmitterData->GraphSource)->NodeGraph; if (this == EmitterGraph) { return FVersionedNiagaraEmitter(OwningEmitter, Version.VersionGuid); } } } } return FVersionedNiagaraEmitter(); } bool UNiagaraGraph::SynchronizeScriptVariable(const UNiagaraScriptVariable* SourceScriptVar, UNiagaraScriptVariable* DestScriptVar /*= nullptr*/, bool bIgnoreChangeId /*= false*/) { check(!bIsForCompilationOnly); if (DestScriptVar == nullptr) { const FGuid& SourceScriptVarId = SourceScriptVar->Metadata.GetVariableGuid(); TArray> ScriptVariables; VariableToScriptVariable.GenerateValueArray(ScriptVariables); TObjectPtr* ScriptVarPtr = ScriptVariables.FindByPredicate([&SourceScriptVarId](const UNiagaraScriptVariable* ScriptVar) { return ScriptVar->Metadata.GetVariableGuid() == SourceScriptVarId; }); if(ScriptVarPtr == nullptr) { // Failed to find a DestScriptVar with an Id matching that of SourceScriptVar. return false; } DestScriptVar = *ScriptVarPtr; } // Only synchronize if the dest script var change id is out of sync from the source script var change id. if (bIgnoreChangeId || (DestScriptVar->GetChangeId() != SourceScriptVar->GetChangeId()) ) { // UNiagaraScriptVariable Properties // Only notify that the scriptvariable change needs to be synchronized in the graph if a default value change occurs: if we are only synchronizing metadata changes, do not dirty the graph. bool bRequiresSync = false; if(DestScriptVar->GetIsOverridingParameterDefinitionsDefaultValue() == false && UNiagaraScriptVariable::DefaultsAreEquivalent(SourceScriptVar, DestScriptVar) == false) { DestScriptVar->DefaultMode = SourceScriptVar->DefaultMode; DestScriptVar->DefaultBinding = SourceScriptVar->DefaultBinding; if (SourceScriptVar->GetDefaultValueData() != nullptr) { DestScriptVar->SetDefaultValueData(SourceScriptVar->GetDefaultValueData()); } bRequiresSync = true; } DestScriptVar->SetChangeId(SourceScriptVar->GetChangeId()); // FNiagaraVariable Properties DestScriptVar->Variable.SetData(SourceScriptVar->Variable.GetData()); DestScriptVar->Variable.SetType(SourceScriptVar->Variable.GetType()); // FNiagaraVariableMetadata DestScriptVar->Metadata.Description = SourceScriptVar->Metadata.Description; // Call rename parameter as we need to synchronize the parameter name to all pins. if (DestScriptVar->Variable.GetName() != SourceScriptVar->Variable.GetName()) { bool bRenameRequestedFromStaticSwitch = false; bool* bMerged = nullptr; bool bSuppressEvents = true; RenameParameter(DestScriptVar->Variable, SourceScriptVar->Variable.GetName(), bRenameRequestedFromStaticSwitch, bMerged, bSuppressEvents); } // Notify the script variable has changed to propagate the default value to the graph node. if (bRequiresSync) { ScriptVariableChanged(DestScriptVar->Variable); } return bRequiresSync; } return false; } bool UNiagaraGraph::SynchronizeParameterDefinitionsScriptVariableRemoved(const FGuid RemovedScriptVarId) { check(!bIsForCompilationOnly); TArray> ScriptVariables; VariableToScriptVariable.GenerateValueArray(ScriptVariables); for (UNiagaraScriptVariable* ScriptVar : ScriptVariables) { if (ScriptVar->Metadata.GetVariableGuid() == RemovedScriptVarId) { ScriptVar->SetIsSubscribedToParameterDefinitions(false); MarkGraphRequiresSynchronization(TEXT("Graph Parameter Unlinked From Definition.")); return true; } } return false; } void UNiagaraGraph::SynchronizeParametersWithParameterDefinitions( const TArray TargetDefinitions, const TArray AllDefinitions, const TSet& AllDefinitionsParameterIds, INiagaraParameterDefinitionsSubscriber* Subscriber, FSynchronizeWithParameterDefinitionsArgs Args) { check(!bIsForCompilationOnly); bool bMarkRequiresSync = false; TArray> ScriptVariables; TArray TargetScriptVariables; VariableToScriptVariable.GenerateValueArray(ScriptVariables); // Filter script variables that will be synchronized if specific script variable ids are specified. if (Args.SpecificDestScriptVarIds.Num() > 0) { TargetScriptVariables = ScriptVariables.FilterByPredicate([&Args](const UNiagaraScriptVariable* DestScriptVar){ return Args.SpecificDestScriptVarIds.Contains(DestScriptVar->Metadata.GetVariableGuid()); }); } else { TargetScriptVariables = ScriptVariables; } // Get all script variables from target definitions. TArray TargetLibraryScriptVariables; for (const UNiagaraParameterDefinitions* TargetParameterDefinitionsItr : TargetDefinitions) { TargetLibraryScriptVariables.Append(TargetParameterDefinitionsItr->GetParametersConst()); } auto GetTargetDefinitionScriptVarWithSameId = [&TargetLibraryScriptVariables](const UNiagaraScriptVariable* GraphScriptVar)->const UNiagaraScriptVariable* { const FGuid& GraphScriptVarId = GraphScriptVar->Metadata.GetVariableGuid(); if (const UNiagaraScriptVariable* const* FoundLibraryScriptVarPtr = TargetLibraryScriptVariables.FindByPredicate([GraphScriptVarId](const UNiagaraScriptVariable* LibraryScriptVar) { return LibraryScriptVar->Metadata.GetVariableGuid() == GraphScriptVarId; })) { return *FoundLibraryScriptVarPtr; } return nullptr; }; // If subscribing all name match parameters; // If a destination parameter has the same name as a source parameter, create a subscription for the source parameter definition. // Retain the destination parameter default value if it does not match the source parameters. if (Args.bSubscribeAllNameMatchParameters) { // Get all script variables from all definitions. TArray AllDefinitionsScriptVariables; for (const UNiagaraParameterDefinitions* AllDefinitionsItr : AllDefinitions) { AllDefinitionsScriptVariables.Append(AllDefinitionsItr->GetParametersConst()); } auto GetDefinitionScriptVarWithSameNameAndType = [&AllDefinitionsScriptVariables](const UNiagaraScriptVariable* GraphScriptVar)->const UNiagaraScriptVariable* { if (const UNiagaraScriptVariable* const* FoundLibraryScriptVarPtr = AllDefinitionsScriptVariables.FindByPredicate([&GraphScriptVar](const UNiagaraScriptVariable* LibraryScriptVar) { return LibraryScriptVar->Variable == GraphScriptVar->Variable; })) { return *FoundLibraryScriptVarPtr; } return nullptr; }; for (UNiagaraScriptVariable* TargetScriptVar : TargetScriptVariables) { // Skip parameters that are already subscribed. if (TargetScriptVar->GetIsSubscribedToParameterDefinitions()) { continue; } else if (const UNiagaraScriptVariable* LibraryScriptVar = GetDefinitionScriptVarWithSameNameAndType(TargetScriptVar)) { // Add the found definition script var as a target script var so that it can be synchronized with later. TargetLibraryScriptVariables.Add(LibraryScriptVar); const bool bDoNotAssetIfAlreadySubscribed = true; Subscriber->SubscribeToParameterDefinitions(CastChecked(LibraryScriptVar->GetOuter()), bDoNotAssetIfAlreadySubscribed); TargetScriptVar->SetIsSubscribedToParameterDefinitions(true); TargetScriptVar->Metadata.SetVariableGuid(LibraryScriptVar->Metadata.GetVariableGuid()); if (UNiagaraScriptVariable::DefaultsAreEquivalent(TargetScriptVar, LibraryScriptVar) == false) { // Preserve the TargetScriptVars default value if it is not equivalent to prevent breaking changes from subscribing new parameters. TargetScriptVar->SetIsOverridingParameterDefinitionsDefaultValue(true); } SynchronizeScriptVariable(LibraryScriptVar, TargetScriptVar); } } } for(UNiagaraScriptVariable* TargetScriptVar : TargetScriptVariables) { if (TargetScriptVar->GetIsSubscribedToParameterDefinitions()) { if (const UNiagaraScriptVariable* TargetLibraryScriptVar = GetTargetDefinitionScriptVarWithSameId(TargetScriptVar)) { bMarkRequiresSync |= SynchronizeScriptVariable(TargetLibraryScriptVar, TargetScriptVar, Args.bForceSynchronizeParameters); } else if(AllDefinitionsParameterIds.Contains(TargetScriptVar->Metadata.GetVariableGuid()) == false) { // ScriptVar is marked as being sourced from a parameter definitions but no matching library script variables were found, break the link to the parameter definitions for ScriptVar. TargetScriptVar->SetIsSubscribedToParameterDefinitions(false); bMarkRequiresSync = true; } } } if(bMarkRequiresSync) { NotifyGraphNeedsRecompile(); } } void UNiagaraGraph::RenameAssignmentAndSetNodePins(const FName OldName, const FName NewName) { TArray MapGetNodes; GetNodesOfClass(MapGetNodes); TArray AssignmentNodes; GetNodesOfClass(AssignmentNodes); for (UNiagaraNodeParameterMapGet* MapGetNode : MapGetNodes) { TArray OutputPins; MapGetNode->GetOutputPins(OutputPins); for (UEdGraphPin* OutputPin : OutputPins) { if (OutputPin->PinName == OldName) { MapGetNode->SetPinName(OutputPin, NewName); } } } for (UNiagaraNodeAssignment* AssignmentNode : AssignmentNodes) { bool bMustRefresh = AssignmentNode->RenameAssignmentTarget(OldName, NewName); AssignmentNode->RefreshFromExternalChanges(); } } int32 UNiagaraGraph::GetOutputNodeVariableIndex(const FNiagaraVariable& Variable)const { TArray Variables; GetOutputNodeVariables(Variables); return Variables.Find(Variable); } void UNiagaraGraph::GetOutputNodeVariables(TArray< FNiagaraVariable >& OutVariables)const { TArray OutputNodes; FindOutputNodes(OutputNodes); for (UNiagaraNodeOutput* OutputNode : OutputNodes) { for (FNiagaraVariable& Var : OutputNode->Outputs) { OutVariables.AddUnique(Var); } } } void UNiagaraGraph::GetOutputNodeVariables(ENiagaraScriptUsage InScriptUsage, TArray< FNiagaraVariable >& OutVariables)const { TArray OutputNodes; FindOutputNodes(InScriptUsage, OutputNodes); for (UNiagaraNodeOutput* OutputNode : OutputNodes) { for (FNiagaraVariable& Var : OutputNode->Outputs) { OutVariables.AddUnique(Var); } } } bool UNiagaraGraph::HasParameterMapParameters()const { TArray Inputs; TArray Outputs; GetParameters(Inputs, Outputs); for (FNiagaraVariable& Var : Inputs) { if (Var.GetType() == FNiagaraTypeDefinition::GetParameterMapDef()) { return true; } } for (FNiagaraVariable& Var : Outputs) { if (Var.GetType() == FNiagaraTypeDefinition::GetParameterMapDef()) { return true; } } return false; } bool UNiagaraGraph::GetPropertyMetadata(FName PropertyName, FString& OutValue) const { for (const FNiagaraScriptVariableData& ScriptVariableData : CompilationScriptVariables) { if (const FString* FoundMatch = ScriptVariableData.Metadata.PropertyMetaData.Find(PropertyName)) { OutValue = *FoundMatch; return true; } } for (const FScriptVariableMap::ElementType& Iter : VariableToScriptVariable) { // TODO: This should never be null, but somehow it is in some assets so guard this to prevent crashes // until we have better repro steps. if (const UNiagaraScriptVariable* ScriptVariable = Iter.Value) { if (const FString* PropertyValue = ScriptVariable->Metadata.PropertyMetaData.Find(PropertyName)) { OutValue = *PropertyValue; return true; } } } return false; } bool UNiagaraGraph::HasNumericParameters()const { TArray Inputs; TArray Outputs; GetParameters(Inputs, Outputs); for (FNiagaraVariable& Var : Inputs) { if (Var.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef()) { return true; } } for (FNiagaraVariable& Var : Outputs) { if (Var.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef()) { return true; } } return false; } void UNiagaraGraph::NotifyGraphNeedsRecompile() { FEdGraphEditAction Action; Action.Action = (EEdGraphActionType)GRAPHACTION_GenericNeedsRecompile; NotifyGraphChanged(Action); } void UNiagaraGraph::NotifyGraphDataInterfaceChanged() { OnDataInterfaceChangedDelegate.Broadcast(); } FNiagaraTypeDefinition UNiagaraGraph::GetCachedNumericConversion(const class UEdGraphPin* InPin) { if (bNeedNumericCacheRebuilt) { RebuildNumericCache(); } FNiagaraTypeDefinition ReturnDef; if (InPin && InPin->PinId.IsValid()) { FNiagaraTypeDefinition* FoundDef = CachedNumericConversions.Find(TPair(InPin->PinId, InPin->GetOwningNode())); if (FoundDef) { ReturnDef = *FoundDef; } } return ReturnDef; } bool UNiagaraGraph::AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor, const TArray& InTraversal) const { check(!bIsForCompilationOnly); #if WITH_EDITORONLY_DATA if (FNiagaraCompileHashVisitorDebugInfo* DebugInfo = InVisitor->AddDebugInfo()) { DebugInfo->Object = FString::Printf(TEXT("Class: \"%s\" Name: \"%s\""), *this->GetClass()->GetName(), *this->GetName()); } #endif InVisitor->UpdateString(TEXT("ForceRebuildId"), ForceRebuildId.ToString()); TSet ReferencedVariables; NiagaraGraphImpl::FindReferencedVariables(this, InTraversal, ReferencedVariables); TArray SortedScriptVariables; SortedScriptVariables.Reserve(ReferencedVariables.Num()); for (const FNiagaraVariable& ReferencedVariable : ReferencedVariables) { if (UNiagaraScriptVariable* ScriptVariable = VariableToScriptVariable.FindRef(ReferencedVariable)) { SortedScriptVariables.Add(ScriptVariable); } } Algo::StableSortBy(SortedScriptVariables, [](UNiagaraScriptVariable* ScriptVar) { return ScriptVar->Variable.GetName(); }, FNameLexicalLess()); for (UNiagaraScriptVariable* ScriptVariable : SortedScriptVariables) { #if WITH_EDITORONLY_DATA if (FNiagaraCompileHashVisitorDebugInfo* DebugInfo = InVisitor->AddDebugInfo()) { DebugInfo->Object = FString::Printf(TEXT("Class: \"%s\" Name: \"%s\""), *ScriptVariable->GetClass()->GetName(), *ScriptVariable->Variable.GetName().ToString()); } #endif verify(ScriptVariable->AppendCompileHash(InVisitor)); } // Write all the values of the nodes to the visitor as they could influence compilation. for (const UNiagaraNode* Node : InTraversal) { #if WITH_EDITORONLY_DATA if (FNiagaraCompileHashVisitorDebugInfo* DebugInfo = InVisitor->AddDebugInfo()) { DebugInfo->Object = FString::Printf(TEXT("Class: \"%s\" Title: \"%s\" Name: \"%s\" Guid: %s"), *Node->GetClass()->GetName(), *Node->GetNodeTitle(ENodeTitleType::EditableTitle).ToString(), *Node->GetName(), *LexToString(Node->NodeGuid)); } #endif verify(Node->AppendCompileHash(InVisitor)); } #if WITH_EDITORONLY_DATA // Optionally log out the information for debugging. if (FNiagaraCompileHashVisitor::LogCompileIdGeneration == 2 && InTraversal.Num() > 0) { FString RelativePath; UObject* Package = GetOutermost(); if (Package != nullptr) { RelativePath += Package->GetName() + TEXT("/"); } UObject* Parent = GetOuter(); while (Parent != Package) { bool bSkipName = false; if (Parent->IsA()) // Removing common clutter bSkipName = true; else if (Parent->IsA()) // Removing common clutter bSkipName = true; if (!bSkipName) RelativePath = RelativePath + Parent->GetName() + TEXT("/"); Parent = Parent->GetOuter(); } FString ObjName = GetName(); FString DumpDebugInfoPath = FPaths::ProjectSavedDir() + TEXT("NiagaraHashes/") + RelativePath ; FPaths::NormalizeDirectoryName(DumpDebugInfoPath); DumpDebugInfoPath.ReplaceInline(TEXT("<"), TEXT("(")); DumpDebugInfoPath.ReplaceInline(TEXT(">"), TEXT(")")); DumpDebugInfoPath.ReplaceInline(TEXT("::"), TEXT("==")); DumpDebugInfoPath.ReplaceInline(TEXT("|"), TEXT("_")); DumpDebugInfoPath.ReplaceInline(TEXT("*"), TEXT("-")); DumpDebugInfoPath.ReplaceInline(TEXT("?"), TEXT("!")); DumpDebugInfoPath.ReplaceInline(TEXT("\""), TEXT("\'")); if (!IFileManager::Get().DirectoryExists(*DumpDebugInfoPath)) { if (!IFileManager::Get().MakeDirectory(*DumpDebugInfoPath, true)) UE_LOG(LogNiagaraEditor, Warning, TEXT("Failed to create directory for debug info '%s'"), *DumpDebugInfoPath); } FString ExportText = FString::Printf(TEXT("UNiagaraGraph::AppendCompileHash %s %s\n===========================\n"), *GetFullName(), *InTraversal[InTraversal.Num()- 1]->GetNodeTitle(ENodeTitleType::ListView).ToString()); for (int32 i = 0; i < InVisitor->Values.Num(); i++) { ExportText += FString::Printf(TEXT("Object[%d]: %s\n"), i, *InVisitor->Values[i].Object); ensure(InVisitor->Values[i].PropertyKeys.Num() == InVisitor->Values[i].PropertyValues.Num()); for (int32 j = 0; j < InVisitor->Values[i].PropertyKeys.Num(); j++) { ExportText += FString::Printf(TEXT("\tProperty[%d]: %s = %s\n"), j, *InVisitor->Values[i].PropertyKeys[j], *InVisitor->Values[i].PropertyValues[j]); } } FNiagaraEditorUtilities::WriteTextFileToDisk(DumpDebugInfoPath, ObjName + TEXT(".txt"), ExportText, true); } #endif return true; } void DiffProperties(const FNiagaraCompileHashVisitorDebugInfo& A, const FNiagaraCompileHashVisitorDebugInfo& B) { if (A.PropertyKeys.Num() != B.PropertyKeys.Num()) { UE_LOG(LogNiagaraEditor, Log, TEXT("Hash Difference: Property Count Mismatch %d vs %d on %s"), A.PropertyKeys.Num(), B.PropertyKeys.Num(), *A.Object); } else { bool bFoundMatch = false; for (int32 i = 0; i < A.PropertyKeys.Num(); i++) { if (A.PropertyKeys[i] == B.PropertyKeys[i]) { bFoundMatch = true; if (A.PropertyValues[i] != B.PropertyValues[i]) { UE_LOG(LogNiagaraEditor, Log, TEXT("Hash Difference: Property Value Mismatch %s vs %s on property %s of %s"), *A.PropertyValues[i], *B.PropertyValues[i], *A.PropertyKeys[i], *A.Object); } } } ensure(bFoundMatch); } } void UNiagaraGraph::ConditionalRebuildCompileIdCache() { if (!bHasValidLastBuiltScriptVersionId) { bHasValidLastBuiltScriptVersionId = true; LastBuiltScriptVersionId = FGuid(); } } void UNiagaraGraph::RebuildCachedCompileIds() { check(!bIsForCompilationOnly); const FGuid CurrentScriptVersionId = FNiagaraCustomVersion::GetLatestScriptCompileVersion(); // If the graph hasn't changed since last rebuild, then do nothing. const bool bForceRebuild = (bHasValidLastBuiltScriptVersionId && (LastBuiltScriptVersionId != CurrentScriptVersionId)); if (!bForceRebuild && ChangeId == LastBuiltTraversalDataChangeId && LastBuiltTraversalDataChangeId.IsValid()) { return; } if (!AllowShaderCompiling()) { return; } // First find all the output nodes TArray NiagaraOutputNodes; GetNodesOfClass(NiagaraOutputNodes); // Now build the new cache.. TArray NewUsageCache; NewUsageCache.AddDefaulted(NiagaraOutputNodes.Num()); bool bNeedsAnyNewCompileIds = false; FNiagaraGraphScriptUsageInfo* ParticleSpawnUsageInfo = nullptr; FNiagaraGraphScriptUsageInfo* ParticleUpdateUsageInfo = nullptr; for (int32 i = 0; i < NiagaraOutputNodes.Num(); i++) { UNiagaraNodeOutput* OutputNode = NiagaraOutputNodes[i]; NewUsageCache[i].UsageType = OutputNode->GetUsage(); NewUsageCache[i].UsageId = OutputNode->GetUsageId(); BuildTraversal(MutableView(NewUsageCache[i].Traversal), OutputNode); int32 FoundMatchIdx = INDEX_NONE; for (int32 j = 0; j < CachedUsageInfo.Num(); j++) { if (UNiagaraScript::IsEquivalentUsage(CachedUsageInfo[j].UsageType, NewUsageCache[i].UsageType) && CachedUsageInfo[j].UsageId == NewUsageCache[i].UsageId) { FoundMatchIdx = j; break; } } if (FoundMatchIdx == INDEX_NONE || CachedUsageInfo[FoundMatchIdx].BaseId.IsValid() == false) { NewUsageCache[i].BaseId = FGuid::NewGuid(); } else { //Copy the old base id if available and valid. NewUsageCache[i].BaseId = CachedUsageInfo[FoundMatchIdx].BaseId; } // Now compare the change id's of all the nodes in the traversal by hashing them up and comparing the hash // now with the hash from previous runs. FSHA1 GraphHashState; FNiagaraCompileHashVisitor Visitor(GraphHashState); AppendCompileHash(&Visitor, NewUsageCache[i].Traversal); GraphHashState.Final(); FSHA1 HashState; FSHA1 HashStateStatics; for (UNiagaraNode* Node : NewUsageCache[i].Traversal) { Node->UpdateCompileHashForNode(HashState); UNiagaraNodeFunctionCall* CallNode = Cast(Node); if (CallNode) { CallNode->UpdateReferencedStaticsHashForNode(HashStateStatics); } } HashState.Final(); HashStateStatics.Final(); // We can't store in a FShaHash struct directly because you can't FProperty it. Using a standin of the same size. { TStaticArray DataHash; HashState.GetHash(DataHash.GetData()); NewUsageCache[i].CompileHash = FNiagaraCompileHash(DataHash.GetData(), DataHash.Num()); } { TStaticArray DataHashStatics; HashStateStatics.GetHash(DataHashStatics.GetData()); NewUsageCache[i].ReferenceHashFromGraph = FNiagaraCompileHash(DataHashStatics.GetData(), DataHashStatics.Num()); } { // We can't store in a FShaHash struct directly because you can't UProperty it. Using a standin of the same size. TStaticArray DataHash; GraphHashState.GetHash(DataHash.GetData()); NewUsageCache[i].CompileHashFromGraph = FNiagaraCompileHash(DataHash.GetData(), DataHash.Num()); NewUsageCache[i].CompileLastObjects = Visitor.Values; #if WITH_EDITORONLY_DATA // Log out all the entries that differ! if (FNiagaraCompileHashVisitor::LogCompileIdGeneration != 0 && FoundMatchIdx != -1 && NewUsageCache[i].CompileHashFromGraph != CachedUsageInfo[FoundMatchIdx].CompileHashFromGraph) { TArray OldDebugValues; OldDebugValues = CachedUsageInfo[FoundMatchIdx].CompileLastObjects; TArray bFoundIndices; // Record if we ever found these. bFoundIndices.AddZeroed(OldDebugValues.Num()); for (int32 ObjIdx = 0; ObjIdx < NewUsageCache[i].CompileLastObjects.Num(); ObjIdx++) { bool bFound = false; for (int32 OldObjIdx = 0; OldObjIdx < OldDebugValues.Num(); OldObjIdx++) { if (OldDebugValues[OldObjIdx].Object == NewUsageCache[i].CompileLastObjects[ObjIdx].Object) { bFound = true; // Record that we found a match for this object bFoundIndices[OldObjIdx] = true; // Record that we found an overall match DiffProperties(OldDebugValues[OldObjIdx], NewUsageCache[i].CompileLastObjects[ObjIdx]); } } if (!bFound) { UE_LOG(LogNiagaraEditor, Log, TEXT("Hash Difference: New Object: %s"), *NewUsageCache[i].CompileLastObjects[ObjIdx].Object); } } for (int32 ObjIdx = 0; ObjIdx < OldDebugValues.Num(); ObjIdx++) { if (bFoundIndices[ObjIdx] == false) { UE_LOG(LogNiagaraEditor, Log, TEXT("Hash Difference: Removed Object: %s"), *OldDebugValues[ObjIdx].Object); } } } #endif } if (UNiagaraScript::IsEquivalentUsage(NewUsageCache[i].UsageType, ENiagaraScriptUsage::ParticleSpawnScript) && NewUsageCache[i].UsageId == FGuid()) { ParticleSpawnUsageInfo = &NewUsageCache[i]; } if (UNiagaraScript::IsEquivalentUsage(NewUsageCache[i].UsageType, ENiagaraScriptUsage::ParticleUpdateScript) && NewUsageCache[i].UsageId == FGuid()) { ParticleUpdateUsageInfo = &NewUsageCache[i]; } } if (ParticleSpawnUsageInfo != nullptr && ParticleUpdateUsageInfo != nullptr) { // If we have info for both spawn and update generate the gpu version too. FNiagaraGraphScriptUsageInfo GpuUsageInfo; GpuUsageInfo.UsageType = ENiagaraScriptUsage::ParticleGPUComputeScript; GpuUsageInfo.UsageId = FGuid(); FNiagaraGraphScriptUsageInfo* OldGpuInfo = CachedUsageInfo.FindByPredicate( [](const FNiagaraGraphScriptUsageInfo& OldInfo) { return OldInfo.UsageType == ENiagaraScriptUsage::ParticleGPUComputeScript && OldInfo.UsageId == FGuid(); }); if (OldGpuInfo == nullptr || OldGpuInfo->BaseId.IsValid() == false) { GpuUsageInfo.BaseId = FGuid::NewGuid(); } else { // Copy the old base id if available GpuUsageInfo.BaseId = OldGpuInfo->BaseId; } // The GPU script has no graph representation, but we still need to fill in the hash, because it's used in the shader map ID. // Just copy the hash from the spawn script. GpuUsageInfo.CompileHash = ParticleSpawnUsageInfo->CompileHash; GpuUsageInfo.CompileHashFromGraph = ParticleSpawnUsageInfo->CompileHashFromGraph; NewUsageCache.Add(GpuUsageInfo); } // Debug logic, usually disabled at top of file. if (bNeedsAnyNewCompileIds && bWriteToLog) { TMap ComputeChangeIds; FNiagaraEditorUtilities::GatherChangeIds(*this, ComputeChangeIds, GetName()); } // Now update the cache with the newly computed results. CachedUsageInfo = NewUsageCache; LastBuiltTraversalDataChangeId = ChangeId; LastBuiltScriptVersionId = CurrentScriptVersionId; bHasValidLastBuiltScriptVersionId = true; RebuildNumericCache(); } void UNiagaraGraph::CopyCachedReferencesMap(UNiagaraGraph* TargetGraph) { // make sure that we have collected parameter references already ConditionalRefreshParameterReferences(); TargetGraph->ParameterToReferencesMap = ParameterToReferencesMap; } const class UEdGraphSchema_Niagara* UNiagaraGraph::GetNiagaraSchema() const { return Cast(GetSchema()); } void UNiagaraGraph::RebuildNumericCache() { CachedNumericConversions.Empty(); TMap VisitedNodes; for (UEdGraphNode* Node : Nodes) { ResolveNumerics(VisitedNodes, Node); } bNeedNumericCacheRebuilt = false; } void UNiagaraGraph::InvalidateNumericCache() { bNeedNumericCacheRebuilt = true; CachedNumericConversions.Empty(); } FString UNiagaraGraph::GetFunctionAliasByContext(const FNiagaraGraphFunctionAliasContext& FunctionAliasContext) const { FString FunctionAlias; TSet SkipNodeTypes; for (const UEdGraphNode* Node : Nodes) { const UNiagaraNode* NiagaraNode = Cast(Node); if (NiagaraNode != nullptr) { if (SkipNodeTypes.Contains(NiagaraNode->GetClass())) { continue; } bool OncePerNodeType = false; NiagaraNode->AppendFunctionAliasForContext(FunctionAliasContext, FunctionAlias, OncePerNodeType); if (OncePerNodeType) { SkipNodeTypes.Add(NiagaraNode->GetClass()); } } } for (const UEdGraphPin* Pin : FunctionAliasContext.StaticSwitchValues) { FunctionAlias += TEXT("_") + FNiagaraHlslTranslator::GetSanitizedFunctionNameSuffix(Pin->GetName()) + TEXT("_") + FNiagaraHlslTranslator::GetSanitizedFunctionNameSuffix(Pin->DefaultValue); } return FunctionAlias; } void UNiagaraGraph::ResolveNumerics(TMap& VisitedNodes, UEdGraphNode* Node) { UNiagaraNode* NiagaraNode = Cast(Node); if (NiagaraNode) { FPinCollectorArray InputPins; NiagaraNode->GetInputPins(InputPins); for (int32 i = 0; i < InputPins.Num(); i++) { if (InputPins[i]) { for (int32 j = 0; j < InputPins[i]->LinkedTo.Num(); j++) { if (InputPins[i]->LinkedTo[j]) { UNiagaraNode* FoundNode = Cast(InputPins[i]->LinkedTo[j]->GetOwningNode()); if (!FoundNode || VisitedNodes.Contains(FoundNode)) { continue; } VisitedNodes.Add(FoundNode, true); ResolveNumerics(VisitedNodes, FoundNode); } } } } NiagaraNode->ResolveNumerics(GetNiagaraSchema(), false, &CachedNumericConversions); } } void UNiagaraGraph::ForceGraphToRecompileOnNextCheck() { Modify(); CachedUsageInfo.Empty(); ForceRebuildId = FGuid::NewGuid(); MarkGraphRequiresSynchronization(__FUNCTION__); } void UNiagaraGraph::GatherExternalDependencyData(ENiagaraScriptUsage InUsage, const FGuid& InUsageId, FNiagaraScriptHashCollector& HashCollector) { RebuildCachedCompileIds(); for (int32 i = 0; i < CachedUsageInfo.Num(); i++) { // First add our direct dependency chain... if (UNiagaraScript::IsEquivalentUsage(CachedUsageInfo[i].UsageType, InUsage) && CachedUsageInfo[i].UsageId == InUsageId) { for (UNiagaraNode* Node : CachedUsageInfo[i].Traversal) { Node->GatherExternalDependencyData(InUsage, InUsageId, HashCollector); } } // Now add any other dependency chains that we might have... else if (UNiagaraScript::IsUsageDependentOn(InUsage, CachedUsageInfo[i].UsageType)) { if (GNiagaraUseGraphHash == 1) { HashCollector.AddHash(CachedUsageInfo[i].CompileHashFromGraph, CachedUsageInfo[i].Traversal.Last()->GetPathName()); } else { HashCollector.AddHash(CachedUsageInfo[i].CompileHash, CachedUsageInfo[i].Traversal.Last()->GetPathName()); } for (UNiagaraNode* Node : CachedUsageInfo[i].Traversal) { Node->GatherExternalDependencyData(InUsage, InUsageId, HashCollector); } } } } void UNiagaraGraph::GetAllReferencedGraphs(TArray& Graphs) const { Graphs.AddUnique(this); TArray FunctionCallNodes; GetNodesOfClass(FunctionCallNodes); for (UNiagaraNodeFunctionCall* FunctionCallNode : FunctionCallNodes) { UNiagaraGraph* FunctionGraph = FunctionCallNode->GetCalledGraph(); if (FunctionGraph != nullptr) { if (!Graphs.Contains(FunctionGraph)) { FunctionGraph->GetAllReferencedGraphs(Graphs); } } } } /** Determine if another item has been synchronized with this graph.*/ bool UNiagaraGraph::IsOtherSynchronized(const FGuid& InChangeId) const { if (ChangeId.IsValid() && ChangeId == InChangeId) { return true; } return false; } /** Identify that this graph has undergone changes that will require synchronization with a compiled script.*/ void UNiagaraGraph::MarkGraphRequiresSynchronization(FString Reason) { Modify(); ChangeId = FGuid::NewGuid(); if (GEnableVerboseNiagaraChangeIdLogging) { UE_LOG(LogNiagaraEditor, Verbose, TEXT("Graph %s was marked requires synchronization. Reason: %s"), *GetPathName(), *Reason); } } TOptional UNiagaraGraph::GetMetaData(const FNiagaraVariable& InVar) const { if (TOptional VariableData = GetScriptVariableData(InVar)) { return VariableData->Metadata; } if (const UNiagaraScriptVariable* ScriptVariable = VariableToScriptVariable.FindRef(InVar)) { return ScriptVariable->Metadata; } return TOptional(); } void UNiagaraGraph::SetMetaData(const FNiagaraVariable& InVar, const FNiagaraVariableMetaData& InMetaData) { check(!bIsForCompilationOnly); if (TObjectPtr* FoundMetaData = VariableToScriptVariable.Find(InVar)) { if (*FoundMetaData) { // Replace the old metadata.. UNiagaraScriptVariable* ScriptVariable = (*FoundMetaData); ScriptVariable->Modify(); ScriptVariable->Metadata = InMetaData; if (!ScriptVariable->Metadata.GetVariableGuid().IsValid()) { ScriptVariable->Metadata.CreateNewGuid(); } } } else { Modify(); TObjectPtr& NewScriptVariable = VariableToScriptVariable.Add(InVar, NewObject(this, FName(), RF_Transactional)); NewScriptVariable->Init(InVar, InMetaData); NewScriptVariable->SetIsStaticSwitch(FindStaticSwitchInputs().Contains(InVar)); } } UNiagaraGraph::FOnDataInterfaceChanged& UNiagaraGraph::OnDataInterfaceChanged() { return OnDataInterfaceChangedDelegate; } UNiagaraGraph::FOnSubObjectSelectionChanged& UNiagaraGraph::OnSubObjectSelectionChanged() { return OnSelectedSubObjectChanged; } void UNiagaraGraph::RefreshParameterReferences() const { ensureMsgf(!bIsForCompilationOnly, TEXT("RefreshParameterReferences() shouldn't be called on a graph duplicated for compilation - %s"), *GetFullName()); // A set of variables to track which parameters are used so that unused parameters can be removed after the reference tracking. TSet CandidateUnreferencedParametersToRemove; // The set of pins which has already been handled by add parameters. TSet HandledParameterMapPins; // Purge existing parameter references and collect candidate unreferenced parameters. for (auto& ParameterToReferences : ParameterToReferencesMap) { ParameterToReferences.Value.ParameterReferences.Empty(); if (ParameterToReferences.Value.WasCreatedByUser() == false) { // Collect all parameters not created for the user so that they can be removed later if no references are found for them. CandidateUnreferencedParametersToRemove.Add(ParameterToReferences.Key); } } auto AddParameterReference = [&](const FNiagaraVariable& Parameter, const UEdGraphPin* Pin) { FNiagaraGraphParameterReferenceCollection& ReferenceCollection = ParameterToReferencesMap.FindOrAdd(Parameter, FNiagaraGraphParameterReferenceCollection(false /*bInCreated*/)); ReferenceCollection.ParameterReferences.AddUnique(FNiagaraGraphParameterReference(Pin->PersistentGuid, Cast(Pin->GetOwningNode()))); // If we're adding a parameter reference then it needs to be removed from the list of candidate variables to remove since it's been referenced. CandidateUnreferencedParametersToRemove.Remove(Parameter); }; auto AddStaticParameterReference = [&](const FNiagaraVariable& Variable, UNiagaraNode* Node) { FNiagaraGraphParameterReferenceCollection& ReferenceCollection = ParameterToReferencesMap.FindOrAdd(Variable, FNiagaraGraphParameterReferenceCollection(false /*bInCreated*/)); ReferenceCollection.ParameterReferences.AddUnique(FNiagaraGraphParameterReference(Node->NodeGuid, Node)); CandidateUnreferencedParametersToRemove.Remove(Variable); }; auto AddBindingParameterReference = [&](const FNiagaraVariable& Variable) { // We add an empty reference collection only when no other references exist. // We cannot add an actual reference since those require both a guid and a node, // but neither of these exist for direct bindings. FNiagaraGraphParameterReferenceCollection& ReferenceCollection = ParameterToReferencesMap.FindOrAdd(Variable, FNiagaraGraphParameterReferenceCollection(true /*bInCreated*/)); }; // Add parameter references from parameter map traversals. const TArray Histories = UNiagaraNodeParameterMapBase::GetParameterMaps(this); for (const FNiagaraParameterMapHistory& History : Histories) { for (int32 Index = 0; Index < History.VariablesWithOriginalAliasesIntact.Num(); Index++) { const FNiagaraVariable& Parameter = History.VariablesWithOriginalAliasesIntact[Index]; for (const FNiagaraParameterMapHistory::FModuleScopedPin& WriteEvent : History.PerVariableWriteHistory[Index]) { if (WriteEvent.Pin->PinType.PinSubCategory == UNiagaraNodeParameterMapBase::ParameterPinSubCategory) { bool bAddReference = true; // we exclude default pins from adding references as they shouldn't count. Originally they weren't marked as parameter pins so this wasn't necessary. if(WriteEvent.Pin->Direction == EGPD_Input && WriteEvent.Pin->GetOwningNode()->IsA()) { bAddReference = false; } if(bAddReference) { AddParameterReference(Parameter, WriteEvent.Pin); } HandledParameterMapPins.Add(WriteEvent.Pin); } } for (const FNiagaraParameterMapHistory::FReadHistory& ReadHistory : History.PerVariableReadHistory[Index]) { if (ReadHistory.ReadPin.Pin->PinType.PinSubCategory == UNiagaraNodeParameterMapBase::ParameterPinSubCategory) { AddParameterReference(Parameter, ReadHistory.ReadPin.Pin); HandledParameterMapPins.Add(ReadHistory.ReadPin.Pin); } } } } // Check all pins on all nodes in the graph to find parameter pins which may have been missed in the parameter map traversal. This // can happen for nodes which are not fully connected and therefore don't show up in the traversal. const UEdGraphSchema_Niagara* NiagaraSchema = GetNiagaraSchema(); for (UEdGraphNode* Node : Nodes) { if (UNiagaraNodeStaticSwitch* SwitchNode = Cast(Node)) { if (!SwitchNode->IsSetByCompiler() && !SwitchNode->IsSetByPin()) { FNiagaraVariable Variable(SwitchNode->GetInputType(), SwitchNode->InputParameterName); AddStaticParameterReference(Variable, SwitchNode); } } else if (UNiagaraNodeFunctionCall* FunctionNode = Cast(Node)) { for (const FNiagaraPropagatedVariable& Propagated : FunctionNode->PropagatedStaticSwitchParameters) { AddStaticParameterReference(Propagated.ToVariable(), FunctionNode); } } else if (UNiagaraNodeParameterMapBase* ParameterMapNode = Cast(Node)) { for (UEdGraphPin* Pin : Node->Pins) { // we only consider parameter pins, inputs for MapSets and outputs for MapGets if (Pin->PinType.PinSubCategory == UNiagaraNodeParameterMapBase::ParameterPinSubCategory) { const bool bRelevantPin = ((Pin->Direction == EEdGraphPinDirection::EGPD_Input) && Node->IsA()) || ((Pin->Direction == EEdGraphPinDirection::EGPD_Output) && Node->IsA()); if (!bRelevantPin) { continue; } bool bAlreadyHandledPin = false; HandledParameterMapPins.FindOrAdd(Pin, &bAlreadyHandledPin); if (!bAlreadyHandledPin) { const FNiagaraVariable Parameter = NiagaraSchema->PinToNiagaraVariable(Pin, false); AddParameterReference(Parameter, Pin); } } } } } // Add reference to all variables that are default bound to for (auto It = VariableToScriptVariable.CreateConstIterator(); It; ++It) { UNiagaraScriptVariable* Variable = It.Value(); if (!Variable || (Variable->DefaultMode != ENiagaraDefaultMode::Binding || !Variable->DefaultBinding.IsValid())) { continue; } FNiagaraTypeDefinition LinkedType = Variable->Variable.GetType(); FName BindingName = Variable->DefaultBinding.GetName(); if (FNiagaraConstants::GetOldPositionTypeVariables().Contains(FNiagaraVariable(LinkedType, BindingName))) { // it is not uncommon that old assets have vector inputs that default bind to what is now a position type. If we detect that, we change the type to prevent a compiler error. LinkedType = FNiagaraTypeDefinition::GetPositionDef(); } AddBindingParameterReference(FNiagaraVariable(LinkedType, BindingName)); } // If there were any previous parameters which didn't have any references added, remove them here. for (const FNiagaraVariable& UnreferencedParameterToRemove : CandidateUnreferencedParametersToRemove) { ParameterToReferencesMap.Remove(UnreferencedParameterToRemove); } bParameterReferenceRefreshPending = false; } void UNiagaraGraph::InvalidateCachedParameterData() { bParameterReferenceRefreshPending = true; } const TMap UNiagaraGraph::CollectVarsToInOutPinsMap() const { const UEdGraphSchema_Niagara* NiagaraSchema = GetNiagaraSchema(); TMap VarToPinsMap; // Collect all input and output nodes to inspect the pins and infer usages of variables they reference. TArray MapSetNodes; TArray MapGetNodes; GetNodesOfClass(MapSetNodes); GetNodesOfClass(MapGetNodes); FPinCollectorArray MapGetOutputPins; for (UNiagaraNodeParameterMapGet* MapGetNode : MapGetNodes) { MapGetOutputPins.Reset(); MapGetNode->GetOutputPins(MapGetOutputPins); for (UEdGraphPin* Pin : MapGetOutputPins) { if (Pin->PinName == UNiagaraNodeParameterMapBase::SourcePinName || Pin->PinName == UNiagaraNodeParameterMapBase::AddPinName) { continue; } FNiagaraVariable Var = FNiagaraVariable(NiagaraSchema->PinToTypeDefinition(Pin), Pin->PinName); FInputPinsAndOutputPins& InOutPins = VarToPinsMap.FindOrAdd(Var); InOutPins.OutputPins.Add(Pin); } } FPinCollectorArray MapSetInputPins; for (UNiagaraNodeParameterMapSet* MapSetNode : MapSetNodes) { MapSetInputPins.Reset(); MapSetNode->GetInputPins(MapSetInputPins); for (UEdGraphPin* Pin : MapSetInputPins) { if (Pin->PinName == UNiagaraNodeParameterMapBase::SourcePinName || Pin->PinName == UNiagaraNodeParameterMapBase::AddPinName) { continue; } FNiagaraVariable Var = FNiagaraVariable(NiagaraSchema->PinToTypeDefinition(Pin), Pin->PinName); FInputPinsAndOutputPins& InOutPins = VarToPinsMap.FindOrAdd(Var); InOutPins.InputPins.Add(Pin); } } return VarToPinsMap; } void UNiagaraGraph::GetAllScriptVariableGuids(TArray& VariableGuids) const { if (bIsForCompilationOnly) { VariableGuids.Reserve(CompilationScriptVariables.Num()); Algo::Transform(CompilationScriptVariables, VariableGuids, [&](const FNiagaraScriptVariableData& VariableData) { return VariableData.Metadata.GetVariableGuid(); }); return; } for (auto It = VariableToScriptVariable.CreateConstIterator(); It; ++It) { VariableGuids.Add(It.Value()->Metadata.GetVariableGuid()); } } void UNiagaraGraph::GetAllVariables(TArray& Variables) const { if (bIsForCompilationOnly) { Variables.Reserve(CompilationScriptVariables.Num()); Algo::Transform(CompilationScriptVariables, Variables, [&](const FNiagaraScriptVariableData& VariableData) { return VariableData.Variable; }); return; } VariableToScriptVariable.GetKeys(Variables); } bool UNiagaraGraph::HasVariable(const FNiagaraVariable& Variable) const { if (TOptional VariableData = GetScriptVariableData(Variable)) { return true; } return VariableToScriptVariable.Contains(Variable); } TOptional UNiagaraGraph::IsStaticSwitch(const FNiagaraVariable& Variable) const { if (TOptional VariableData = GetScriptVariableData(Variable)) { return VariableData->GetIsStaticSwitch(); } if (const UNiagaraScriptVariable* FoundScriptVariable = VariableToScriptVariable.FindRef(Variable)) { return FoundScriptVariable->GetIsStaticSwitch(); } return TOptional(); } TOptional UNiagaraGraph::GetStaticSwitchDefaultValue(const FNiagaraVariable& Variable) const { if (TOptional VariableData = GetScriptVariableData(Variable)) { return VariableData->GetStaticSwitchDefaultValue(); } if (const UNiagaraScriptVariable* FoundScriptVariable = VariableToScriptVariable.FindRef(Variable)) { return FoundScriptVariable->GetStaticSwitchDefaultValue(); } return TOptional(); } TOptional UNiagaraGraph::GetDefaultMode(const FNiagaraVariable& Variable, FNiagaraScriptVariableBinding* Binding) const { if (TOptional VariableData = GetScriptVariableData(Variable)) { if (VariableData->DefaultMode == ENiagaraDefaultMode::Binding && VariableData->DefaultBinding.IsValid()) { if (Binding) { *Binding = VariableData->DefaultBinding; } return { ENiagaraDefaultMode::Binding }; } else { return VariableData->DefaultMode; } } if (const UNiagaraScriptVariable* FoundScriptVariable = VariableToScriptVariable.FindRef(Variable)) { if (FoundScriptVariable->DefaultMode == ENiagaraDefaultMode::Binding && FoundScriptVariable->DefaultBinding.IsValid()) { if (Binding) { *Binding = FoundScriptVariable->DefaultBinding; } return {ENiagaraDefaultMode::Binding}; } else { return FoundScriptVariable->DefaultMode; } } return TOptional(); } TOptional UNiagaraGraph::GetScriptVariableGuid(const FNiagaraVariable& Variable) const { if (TOptional VariableData = GetScriptVariableData(Variable)) { return VariableData->Metadata.GetVariableGuid(); } if (const UNiagaraScriptVariable* FoundScriptVariable = VariableToScriptVariable.FindRef(Variable)) { return FoundScriptVariable->Metadata.GetVariableGuid(); } return TOptional(); } TOptional UNiagaraGraph::GetVariable(const FNiagaraVariable& Variable) const { if (TOptional VariableData = GetScriptVariableData(Variable)) { return VariableData->Variable; } if (const UNiagaraScriptVariable* FoundScriptVariable = VariableToScriptVariable.FindRef(Variable)) { return FoundScriptVariable->Variable; } return TOptional(); } void UNiagaraGraph::SetIsStaticSwitch(const FNiagaraVariable& Variable, bool InValue) { check(!bIsForCompilationOnly); if (TObjectPtr* FoundScriptVariable = VariableToScriptVariable.Find(Variable)) { (*FoundScriptVariable)->SetIsStaticSwitch(InValue); } } bool UNiagaraGraph::ReferencesStaticVariable(FNiagaraStaticVariableSearchContext& SearchContext) const { FNiagaraStaticVariableSearchContext::FGraphKey GraphKey(this, ChangeId); if (const bool* bHasReferencePtr = SearchContext.CachedResults.Find(GraphKey)) { return *bHasReferencePtr; } for (const auto& ScriptVariableEntry : VariableToScriptVariable) { if (ScriptVariableEntry.Key.GetType().IsStatic()) { SearchContext.CachedResults.Add(GraphKey, true); return true; } } if (SearchContext.bIncludeReferencedGraphs) { for (const UEdGraphNode* GraphNode : Nodes) { if (const UNiagaraNodeFunctionCall* FunctionCall = Cast(GraphNode)) { if (const UNiagaraGraph* CalledGraph = FunctionCall->GetCalledGraph()) { if (CalledGraph->ReferencesStaticVariable(SearchContext)) { SearchContext.CachedResults.Add(GraphKey, true); return true; } } } } } SearchContext.CachedResults.Add(GraphKey, false); return false; } /** Migrates the deprecated data from the provided OwnerData and populates the ParameterHierarchyRoot */ void UNiagaraGraph::MigrateParameterScriptDataToHierarchyRoot(FVersionedNiagaraScriptData& OwnerData) { UHierarchyRoot* ParameterRoot = GetScriptParameterHierarchyRoot(); TMap HierarchySections; TMap IdentityCategoryMap; TMap SectionCategoryMapping; // First we transfer all sections and create contained categories for(const FNiagaraStackSection& StackSection : OwnerData.InputSections_DEPRECATED) { FText SectionDisplayName = StackSection.SectionDisplayName.IsEmptyOrWhitespace() ? FText::FromName(StackSection.SectionIdentifier) : StackSection.SectionDisplayName; UHierarchySection* HierarchySection = NewObject(ParameterRoot, UHierarchySection::StaticClass()); HierarchySection->SetSectionNameAsText(SectionDisplayName); HierarchySection->SetTooltip(StackSection.Tooltip); FHierarchyElementIdentity SectionIdentity; SectionIdentity.Names.Add(StackSection.SectionIdentifier); HierarchySection->SetIdentity(SectionIdentity); ParameterRoot->GetSectionDataMutable().Insert(HierarchySection, 0); HierarchySections.Add(StackSection.SectionIdentifier, HierarchySection); for(const FText& CategoryIdentity : StackSection.Categories) { // We skip categories that don't have a proper identifier setup if(CategoryIdentity.IsEmptyOrWhitespace()) { continue; } FName CategoryName = FName(CategoryIdentity.ToString()); UNiagaraHierarchyScriptCategory* HierarchyCategory = ParameterRoot->AddChild(); HierarchyCategory->SetCategoryName(CategoryName); FDataHierarchyElementMetaData_SectionAssociation* SectionAssociation = HierarchyCategory->FindOrAddMetaDataOfType(); SectionAssociation->Section = HierarchySection; IdentityCategoryMap.Add(CategoryName, HierarchyCategory); SectionCategoryMapping.Add(CategoryName, HierarchySection->GetSectionName()); } } OwnerData.InputSections_DEPRECATED.Empty(); TMap> ScriptVariablesMap = GetAllMetaData(); TArray ParentLevelInputs; TMap> ParentChildInputMap; // We determine all parent & child level module inputs & static switches for(const auto& ScriptVariablePair : ScriptVariablesMap) { if(ScriptVariablePair.Key.IsInNameSpace(FNiagaraConstants::ModuleNamespace) == false && ScriptVariablePair.Value->GetIsStaticSwitch() == false) { continue; } UNiagaraScriptVariable* ScriptVariable = ScriptVariablePair.Value; if(ScriptVariable->Metadata.GetParentAttribute_DEPRECATED().IsNone()) { ParentLevelInputs.Add(ScriptVariable); ParentChildInputMap.Add(ScriptVariable, GetChildScriptVariablesForInput_Deprecated(ScriptVariable->Metadata.GetVariableGuid())); } } // Now we create the actual hierarchy elements for(const auto& ParentChildPair : ParentChildInputMap) { // By default, we add to the root directly, unless a category was specified UHierarchyElement* Owner = ParameterRoot; FText SpecifiedCategoryName = ParentChildPair.Key->Metadata.GetCategoryName_DEPRECATED(); if(SpecifiedCategoryName.IsEmptyOrWhitespace() == false) { // Categories might already exist due to section info if(IdentityCategoryMap.Contains(FName(SpecifiedCategoryName.ToString()))) { Owner = IdentityCategoryMap[FName(SpecifiedCategoryName.ToString())]; } // But if not we create a new category else { UNiagaraHierarchyScriptCategory* Category = ParameterRoot->AddChild(); Category->SetCategoryName(FName(SpecifiedCategoryName.ToString())); IdentityCategoryMap.Add(Category->GetCategoryName(), Category); Owner = Category; } } // We add the parent input to either the root, or a given category UNiagaraHierarchyScriptParameter* ParentHierarchyInput = Owner->AddChild(); ParentHierarchyInput->Initialize(*ParentChildPair.Key); // Children inputs can be added directly to the parent input and are already sorted for(UNiagaraScriptVariable* ChildInput : ParentChildPair.Value) { UNiagaraHierarchyScriptParameter* ChildHierarchyInput = ParentHierarchyInput->AddChild(); ChildHierarchyInput->Initialize(*ChildInput); } } // now that we have created all inputs, categories and sections, we fix up the order TArray TopLevelCategories; ParameterRoot->GetChildrenOfType(TopLevelCategories, false); // inputs are generally sorted by editor sort priority, but within a given context (within a category etc.) // child inputs also already are sorted when constructed auto SortInputsPredicate = ([&](UHierarchyElement& ItemA, UHierarchyElement& ItemB) { UNiagaraHierarchyScriptParameter* ScriptParameterA = Cast(&ItemA); UNiagaraHierarchyScriptParameter* ScriptParameterB = Cast(&ItemB); if(ScriptParameterA == nullptr || ScriptParameterB == nullptr) { return false; } UNiagaraScriptVariable* ScriptVariableA = ScriptParameterA->GetScriptVariable(); UNiagaraScriptVariable* ScriptVariableB = ScriptParameterB->GetScriptVariable(); if(ScriptVariableA == nullptr || ScriptVariableB == nullptr) { return false; } const FNiagaraVariableMetaData& MetaDataA = ScriptVariableA->Metadata; const FNiagaraVariableMetaData& MetaDataB = ScriptVariableB->Metadata; if(MetaDataA.bAdvancedDisplay && !MetaDataB.bAdvancedDisplay) { return false; } else if(!MetaDataA.bAdvancedDisplay && MetaDataB.bAdvancedDisplay) { return true; } if(MetaDataA.GetEditorSortPriority_DEPRECATED() != MetaDataB.GetEditorSortPriority_DEPRECATED()) { return MetaDataA.GetEditorSortPriority_DEPRECATED() < MetaDataB.GetEditorSortPriority_DEPRECATED(); } return ItemA.ToString().Compare(ItemB.ToString(), ESearchCase::Type::IgnoreCase) < 0; }); // We now sort inputs, and keep track of the minimum sort order per category // This is because the stack determined category order by input sort priority (the category with the parameter of the lowest priority value would come first) TMap CategoryMinimumInputSortOrderMap; for(UHierarchyCategory* Category : TopLevelCategories) { Category->SortChildren(SortInputsPredicate, false); if(Category->GetChildren().Num() > 0) { // since we have sorted the children by sort priority, child 0 has the lowest sort priority number (= highest priority) UNiagaraHierarchyScriptParameter* MinimumItem = CastChecked(Category->GetChildren()[0]); int32 MinimumCategorySortOrder = MinimumItem->GetScriptVariable()->Metadata.GetEditorSortPriority_DEPRECATED(); CategoryMinimumInputSortOrderMap.Add(Category, MinimumCategorySortOrder); } else { CategoryMinimumInputSortOrderMap.Add(Category, 0); } } auto SortRootChildrenPredicate = ([&](UHierarchyElement& ItemA, UHierarchyElement& ItemB) { if(ItemA.IsA() && ItemB.IsA()) { if(CategoryMinimumInputSortOrderMap[&ItemA] != CategoryMinimumInputSortOrderMap[&ItemB]) { return CategoryMinimumInputSortOrderMap[&ItemA] < CategoryMinimumInputSortOrderMap[&ItemB]; } return ItemA.ToString().Compare(ItemB.ToString(), ESearchCase::Type::IgnoreCase) < 0; } // Categories always come after items to ensure consistency with old behavior if(ItemA.IsA() && ItemB.IsA()) { return false; } if(ItemB.IsA() && ItemA.IsA()) { return true; } if(ItemA.IsA() && ItemB.IsA()) { UNiagaraHierarchyScriptParameter* ScriptParameterA = Cast(&ItemA); UNiagaraHierarchyScriptParameter* ScriptParameterB = Cast(&ItemB); if(ScriptParameterA == nullptr || ScriptParameterB == nullptr) { return false; } UNiagaraScriptVariable* ScriptVariableA = ScriptParameterA->GetScriptVariable(); UNiagaraScriptVariable* ScriptVariableB = ScriptParameterB->GetScriptVariable(); if(ScriptVariableA == nullptr || ScriptVariableB == nullptr) { return false; } const FNiagaraVariableMetaData& MetaDataA = ScriptVariableA->Metadata; const FNiagaraVariableMetaData& MetaDataB = ScriptVariableB->Metadata; if(MetaDataA.bAdvancedDisplay && !MetaDataB.bAdvancedDisplay) { return false; } else if(!MetaDataA.bAdvancedDisplay && MetaDataB.bAdvancedDisplay) { return true; } if(MetaDataA.GetEditorSortPriority_DEPRECATED() != MetaDataB.GetEditorSortPriority_DEPRECATED()) { return MetaDataA.GetEditorSortPriority_DEPRECATED() < MetaDataB.GetEditorSortPriority_DEPRECATED(); } return ItemA.ToString().Compare(ItemB.ToString(), ESearchCase::Type::IgnoreCase) < 0; } return false; }); ParameterRoot->SortChildren(SortRootChildrenPredicate, false); } #undef NIAGARA_SCOPE_CYCLE_COUNTER #undef LOCTEXT_NAMESPACE