// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraScriptMergeManager.h" #include "NiagaraEmitter.h" #include "NiagaraScript.h" #include "NiagaraScriptSource.h" #include "NiagaraSimulationStageBase.h" #include "NiagaraRendererProperties.h" #include "EdGraphSchema_Niagara.h" #include "NiagaraGraph.h" #include "NiagaraDataInterface.h" #include "NiagaraMessages.h" #include "NiagaraNodeInput.h" #include "NiagaraNodeOutput.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraNodeCustomHlsl.h" #include "NiagaraNodeAssignment.h" #include "NiagaraNodeParameterMapGet.h" #include "NiagaraNodeParameterMapSet.h" #include "NiagaraEditorUtilities.h" #include "ViewModels/Stack/NiagaraStackGraphUtilities.h" #include "ViewModels/NiagaraSystemViewModel.h" #include "NiagaraClipboard.h" #include "NiagaraEditorModule.h" #include "NiagaraEmitterEditorData.h" #include "NiagaraStackEditorData.h" #include "UObject/PropertyPortFlags.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "Modules/ModuleManager.h" #include "NiagaraConstants.h" #include "NiagaraScriptVariable.h" #include "NiagaraSystemEditorData.h" #include "NiagaraSystemFactoryNew.h" #include "ViewModels/NiagaraEmitterHandleViewModel.h" #include "ViewModels/Stack/NiagaraStackModuleItem.h" #include "ViewModels/Stack/NiagaraStackViewModel.h" #define LOCTEXT_NAMESPACE "NiagaraScriptMergeManager" DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptMergeManager - DiffEmitters"), STAT_NiagaraEditor_ScriptMergeManager_DiffEmitters, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptMergeManager - MergeEmitter"), STAT_NiagaraEditor_ScriptMergeManager_MergeEmitter, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptMergeManager - IsModuleInputDifferentFromBase"), STAT_NiagaraEditor_ScriptMergeManager_IsModuleInputDifferentFromBase, STATGROUP_NiagaraEditor); DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptMergeManager - DoesSummaryItemExistInBase"), STAT_NiagaraEditor_ScriptMergeManager_DoesSummaryItemExistInBase, STATGROUP_NiagaraEditor) int32 GNiagaraForceFailIfPreviouslyNotSetOnMerge = 0; static FAutoConsoleVariableRef CVarForceErrorIfMissingDefaultOnMergeh( TEXT("fx.ForceFailIfPreviouslyNotSetOnMerge"), GNiagaraForceFailIfPreviouslyNotSetOnMerge, TEXT("If > 0, when merging in from parent emitters swap linked variables in the stack to be \"Fail If Previously Not Set\" for their default type. \n"), ECVF_Default ); FNiagaraStackFunctionInputOverrideMergeAdapter::FNiagaraStackFunctionInputOverrideMergeAdapter( const FVersionedNiagaraEmitter& InOwningEmitter, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InOwningFunctionCallNode, UEdGraphPin& InOverridePin ) : OwningScript(&InOwningScript) , OwningFunctionCallNode(&InOwningFunctionCallNode) , OverridePin(&InOverridePin) { InputName = FNiagaraParameterHandle(OverridePin->PinName).GetName().ToString(); OverrideNode = CastChecked(OverridePin->GetOwningNode()); const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); Type = NiagaraSchema->PinToTypeDefinition(OverridePin); if (OverridePin->LinkedTo.Num() == 0) { LocalValueString = OverridePin->DefaultValue; } else if (OverridePin->LinkedTo.Num() == 1) { OverrideValueNodePersistentId = OverridePin->LinkedTo[0]->GetOwningNode()->NodeGuid; if (OverridePin->LinkedTo[0]->GetOwningNode()->IsA()) { LinkedValueData = FNiagaraStackLinkedValueData(); LinkedValueData->LinkedParameterValue = NiagaraSchema->PinToNiagaraVariable(OverridePin->LinkedTo[0]); FNiagaraParameterHandle LinkedValueHandle = FNiagaraParameterHandle(LinkedValueData->LinkedParameterValue.GetName()); if (LinkedValueHandle.IsOutputHandle() || LinkedValueHandle.IsStackContextHandle() || LinkedValueHandle.IsEmitterHandle() || LinkedValueHandle.IsParticleAttributeHandle()) { // If the linked handle is a module output or module data set attribute, record the node id so that we can check for renamed nodes // when applying the diff. TArray HandleParts = LinkedValueHandle.GetHandleParts(); if (HandleParts.Num() > 2) { FString FunctionName = HandleParts[1].ToString(); TObjectPtr* ReferencedFunctionCallNodePtr = OwningFunctionCallNode->GetGraph()->Nodes.FindByPredicate( [FunctionName](UEdGraphNode* Node) { return Node->IsA() && CastChecked(Node)->GetFunctionName() == FunctionName; }); if (ReferencedFunctionCallNodePtr != nullptr) { LinkedValueData->LinkedFunctionNodeId = (*ReferencedFunctionCallNodePtr)->NodeGuid; } } } } else if (OverridePin->LinkedTo[0]->GetOwningNode()->IsA()) { UNiagaraNodeInput* DataInputNode = CastChecked(OverridePin->LinkedTo[0]->GetOwningNode()); if ( DataInputNode->IsDataInterface() ) { DataInterfaceValueInputName = DataInputNode->Input.GetName(); DataInterfaceValue = DataInputNode->GetDataInterface(); } else { ObjectAssetInputVariable = DataInputNode->Input; ObjectAssetValue = DataInputNode->GetObjectAsset(); } } else if (OverridePin->LinkedTo[0]->GetOwningNode()->IsA()) { DynamicValueFunction = MakeShared(InOwningEmitter, *OwningScript.Get(), *CastChecked(OverridePin->LinkedTo[0]->GetOwningNode()), INDEX_NONE); } else { UE_LOG(LogNiagaraEditor, Error, TEXT("Invalid Stack Graph - Unsupported input node connection. Owning Node: %s"), *OverrideNode->GetPathName()); } } else { UE_LOG(LogNiagaraEditor, Error, TEXT("Invalid Stack Graph - Input had multiple connections. Owning Node: %s"), *OverrideNode->GetPathName()); } } FNiagaraStackFunctionInputOverrideMergeAdapter::FNiagaraStackFunctionInputOverrideMergeAdapter( UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InOwningFunctionCallNode, FStringView InInputName, FNiagaraVariable InRapidIterationParameter ) : OwningScript(&InOwningScript) , OwningFunctionCallNode(&InOwningFunctionCallNode) , InputName(InInputName) , Type(InRapidIterationParameter.GetType()) , LocalValueRapidIterationParameter(InRapidIterationParameter) { } FNiagaraStackFunctionInputOverrideMergeAdapter::FNiagaraStackFunctionInputOverrideMergeAdapter(UEdGraphPin* InStaticSwitchPin) : OwningFunctionCallNode(CastChecked(InStaticSwitchPin->GetOwningNode())) , InputName(InStaticSwitchPin->PinName.ToString()) , StaticSwitchValue(InStaticSwitchPin->DefaultValue) { const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); Type = NiagaraSchema->PinToTypeDefinition(InStaticSwitchPin); } UNiagaraScript* FNiagaraStackFunctionInputOverrideMergeAdapter::GetOwningScript() const { return OwningScript.Get(); } UNiagaraNodeFunctionCall* FNiagaraStackFunctionInputOverrideMergeAdapter::GetOwningFunctionCall() const { return OwningFunctionCallNode.Get(); } FString FNiagaraStackFunctionInputOverrideMergeAdapter::GetInputName() const { return InputName; } UNiagaraNodeParameterMapSet* FNiagaraStackFunctionInputOverrideMergeAdapter::GetOverrideNode() const { return OverrideNode.Get(); } const FNiagaraTypeDefinition& FNiagaraStackFunctionInputOverrideMergeAdapter::GetType() const { return Type; } UEdGraphPin* FNiagaraStackFunctionInputOverrideMergeAdapter::GetOverridePin() const { return OverridePin; } const FGuid& FNiagaraStackFunctionInputOverrideMergeAdapter::GetOverrideNodeId() const { return OverrideValueNodePersistentId; } TOptional FNiagaraStackFunctionInputOverrideMergeAdapter::GetLocalValueString() const { return LocalValueString; } TOptional FNiagaraStackFunctionInputOverrideMergeAdapter::GetLocalValueRapidIterationParameter() const { return LocalValueRapidIterationParameter; } TOptional FNiagaraStackFunctionInputOverrideMergeAdapter::GetLinkedValueData() const { return LinkedValueData; } TOptional FNiagaraStackFunctionInputOverrideMergeAdapter::GetDataInterfaceValueInputName() const { return DataInterfaceValueInputName; } TOptional FNiagaraStackFunctionInputOverrideMergeAdapter::GetObjectAssetInputVariable() const { return ObjectAssetInputVariable; } UObject* FNiagaraStackFunctionInputOverrideMergeAdapter::GetObjectAssetValue() const { return ObjectAssetValue; } UNiagaraDataInterface* FNiagaraStackFunctionInputOverrideMergeAdapter::GetDataInterfaceValue() const { return DataInterfaceValue; } TSharedPtr FNiagaraStackFunctionInputOverrideMergeAdapter::GetDynamicValueFunction() const { return DynamicValueFunction; } TOptional FNiagaraStackFunctionInputOverrideMergeAdapter::GetStaticSwitchValue() const { return StaticSwitchValue; } FNiagaraStackFunctionMergeAdapter::FNiagaraStackFunctionMergeAdapter(const FVersionedNiagaraEmitter& InOwningEmitter, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InFunctionCallNode, int32 InStackIndex) { OwningScript = &InOwningScript; FunctionCallNode = &InFunctionCallNode; StackIndex = InStackIndex; FVersionedNiagaraEmitterData* EmitterData = InOwningEmitter.GetEmitterData(); bUsesScratchPadScript = EmitterData->ScratchPads->Scripts.Contains(FunctionCallNode->FunctionScript) || EmitterData->ParentScratchPads->Scripts.Contains(FunctionCallNode->FunctionScript); FString UniqueEmitterName = InOwningEmitter.Emitter->GetUniqueEmitterName(); FCompileConstantResolver ConstantResolver(InOwningEmitter, FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(*FunctionCallNode)->GetUsage()); // Collect explicit overrides set via parameter map set nodes. TSet AliasedInputsAdded; UNiagaraNodeParameterMapSet* OverrideNode = FNiagaraStackGraphUtilities::GetStackFunctionOverrideNode(*FunctionCallNode); if (OverrideNode != nullptr) { for(UEdGraphPin* OverridePin : FNiagaraStackGraphUtilities::GetOverridePinsForFunction(*OverrideNode, *FunctionCallNode)) { InputOverrides.Add(MakeShared(InOwningEmitter, *OwningScript.Get(), *FunctionCallNode.Get(), *OverridePin)); AliasedInputsAdded.Add(*OverridePin->PinName.ToString()); } } // If we have a valid function script collect up the default values of the rapid iteration parameters so that default values in the parameter store // can be ignored since they're not actually overrides. This is usually not an issue due to the PreparateRapidIterationParameters call in the emitter // editor, but modifications to modules can cause inconsistency here in the emitter. TArray RapidIterationInputDefaultValues; if (InFunctionCallNode.FunctionScript != nullptr) { TArray FunctionInputVariables; TArray FunctionInputVariableNames; { FCompileConstantResolver Resolver(InOwningEmitter, InOwningScript.GetUsage()); TArray AllFunctionInputVariables; GetStackFunctionInputs(*FunctionCallNode, AllFunctionInputVariables, Resolver, FNiagaraStackGraphUtilities::ENiagaraGetStackFunctionInputPinsOptions::ModuleInputsOnly, false); const int32 AllFunctionInputVariableCount = AllFunctionInputVariables.Num(); FunctionInputVariables.Reserve(AllFunctionInputVariableCount); FunctionInputVariableNames.Reserve(AllFunctionInputVariableCount); for (const FNiagaraVariable& FunctionInputVariable : AllFunctionInputVariables) { if (FunctionInputVariable.IsValid() && FNiagaraStackGraphUtilities::IsRapidIterationType(FunctionInputVariable.GetType())) { FunctionInputVariables.Add(FunctionInputVariable); FunctionInputVariableNames.Add(FunctionInputVariable.GetName()); } } } TArray FunctionInputDefaultValuePins; FunctionInputDefaultValuePins.AddZeroed(FunctionInputVariables.Num()); FunctionCallNode->FindParameterMapDefaultValuePins(FunctionInputVariableNames, InOwningScript.GetUsage(), ConstantResolver, FunctionInputDefaultValuePins); for (int32 i = 0; i < FunctionInputVariables.Num(); i++) { FNiagaraVariable FunctionInputVariable = FunctionInputVariables[i]; UEdGraphPin* FunctionInputDefaultPin = FunctionInputDefaultValuePins[i]; if (FunctionInputDefaultPin != nullptr) { // Try to get the default value from the default pin. FNiagaraVariable FunctionInputDefaultVariable = UEdGraphSchema_Niagara::PinToNiagaraVariable(FunctionInputDefaultPin); if (FunctionInputDefaultVariable.GetData() != nullptr) { FunctionInputVariable.SetData(FunctionInputDefaultVariable.GetData()); } } if (FunctionInputVariable.GetData() == nullptr) { // If the pin didn't have a default value then use the type default. FNiagaraEditorUtilities::ResetVariableToDefaultValue(FunctionInputVariable); } if (FunctionInputVariable.GetData() != nullptr) { FNiagaraParameterHandle AliasedFunctionInputHandle = FNiagaraParameterHandle::CreateAliasedModuleParameterHandle(FNiagaraParameterHandle(FunctionInputVariable.GetName()), &InFunctionCallNode); FNiagaraVariable FunctionInputRapidIterationParameter = FNiagaraStackGraphUtilities::CreateRapidIterationParameter(UniqueEmitterName, OwningScript->GetUsage(), AliasedFunctionInputHandle.GetParameterHandleString(), FunctionInputVariable.GetType()); FunctionInputRapidIterationParameter.SetData(FunctionInputVariable.GetData()); RapidIterationInputDefaultValues.Add(FunctionInputRapidIterationParameter); } } } // Collect rapid iteration parameters which aren't at the function default values. FString RapidIterationParameterNamePrefix = TEXT("Constants." + UniqueEmitterName + "."); TArray RapidIterationParameters; OwningScript->RapidIterationParameters.GetParameters(RapidIterationParameters); for (const FNiagaraVariable& RapidIterationParameter : RapidIterationParameters) { FNiagaraParameterHandle AliasedInputHandle(*RapidIterationParameter.GetName().ToString().RightChop(RapidIterationParameterNamePrefix.Len())); if (AliasedInputHandle.GetNamespace().ToString() == FunctionCallNode->GetFunctionName()) { // Currently rapid iteration parameters for assignment nodes in emitter scripts get double aliased which prevents their inputs from // being diffed correctly, so we need to un-mangle the names here so that the diffs are correct. if (FunctionCallNode->IsA() && (OwningScript->GetUsage() == ENiagaraScriptUsage::EmitterSpawnScript || OwningScript->GetUsage() == ENiagaraScriptUsage::EmitterUpdateScript)) { FString InputName = AliasedInputHandle.GetName().ToString(); if (InputName.StartsWith(UniqueEmitterName + TEXT("."))) { FString UnaliasedInputName = TEXT("Emitter") + InputName.RightChop(UniqueEmitterName.Len()); AliasedInputHandle = FNiagaraParameterHandle(AliasedInputHandle.GetNamespace(), *UnaliasedInputName); } } if (AliasedInputsAdded.Contains(AliasedInputHandle.GetParameterHandleString()) == false) { // Check if the input is at the current default and if so it can be skipped. bool bMatchesDefault = false; FNiagaraVariable* RapidIterationInputDefaultValue = RapidIterationInputDefaultValues.FindByPredicate([RapidIterationParameter](const FNiagaraVariable& DefaultValue) { return DefaultValue.GetName() == RapidIterationParameter.GetName() && DefaultValue.GetType() == RapidIterationParameter.GetType(); }); if (RapidIterationInputDefaultValue != nullptr) { const uint8* CurrentValueData = OwningScript->RapidIterationParameters.GetParameterData(RapidIterationParameter); if (CurrentValueData != nullptr) { bMatchesDefault = FMemory::Memcmp(CurrentValueData, RapidIterationInputDefaultValue->GetData(), RapidIterationParameter.GetSizeInBytes()) == 0; } } if (bMatchesDefault == false) { InputOverrides.Add(MakeShared(*OwningScript.Get(), *FunctionCallNode.Get(), AliasedInputHandle.GetName().ToString(), RapidIterationParameter)); } } } } TArray StaticSwitchPins; TSet StaticSwitchPinsHidden; FNiagaraStackGraphUtilities::GetStackFunctionStaticSwitchPins(*FunctionCallNode.Get(), StaticSwitchPins, StaticSwitchPinsHidden, ConstantResolver); for (UEdGraphPin* StaticSwitchPin : StaticSwitchPins) { if (StaticSwitchPin->AutogeneratedDefaultValue.IsEmpty() || StaticSwitchPin->DoesDefaultValueMatchAutogenerated() == false) { InputOverrides.Add(MakeShared(StaticSwitchPin)); } } } UNiagaraNodeFunctionCall* FNiagaraStackFunctionMergeAdapter::GetFunctionCallNode() const { return FunctionCallNode.Get(); } int32 FNiagaraStackFunctionMergeAdapter::GetStackIndex() const { return StackIndex; } bool FNiagaraStackFunctionMergeAdapter::GetUsesScratchPadScript() const { return bUsesScratchPadScript; } const TArray>& FNiagaraStackFunctionMergeAdapter::GetInputOverrides() const { return InputOverrides; } TSharedPtr FNiagaraStackFunctionMergeAdapter::GetInputOverrideByInputNameAndType(const FString& InputName, const FNiagaraTypeDefinition& InputType) const { for (TSharedPtr InputOverride : InputOverrides) { if (InputOverride->GetInputName() == InputName && InputOverride->GetType() == InputType) { return InputOverride; } } return TSharedPtr(); } void FNiagaraStackFunctionMergeAdapter::GatherFunctionCallNodes(TArray& OutFunctionCallNodes) const { if (FunctionCallNode.IsValid()) { OutFunctionCallNodes.Add(FunctionCallNode.Get()); } for (TSharedRef InputOverride : InputOverrides) { if (InputOverride->GetDynamicValueFunction().IsValid()) { InputOverride->GetDynamicValueFunction()->GatherFunctionCallNodes(OutFunctionCallNodes); } } } FNiagaraScriptStackMergeAdapter::FNiagaraScriptStackMergeAdapter(const FVersionedNiagaraEmitter& InOwningEmitter, UNiagaraNodeOutput& InOutputNode, UNiagaraScript& InScript) { OutputNode = &InOutputNode; InputNode.Reset(); Script = &InScript; UniqueEmitterName = InOwningEmitter.Emitter->GetUniqueEmitterName(); TArray StackGroups; GetStackNodeGroups(*OutputNode, StackGroups); if (StackGroups.Num() >= 2 && StackGroups[0].EndNode->IsA()) { InputNode = Cast(StackGroups[0].EndNode); } if (StackGroups.Num() > 2 && StackGroups[0].EndNode->IsA() && StackGroups.Last().EndNode->IsA()) { for (int i = 1; i < StackGroups.Num() - 1; i++) { UNiagaraNodeFunctionCall* ModuleFunctionCallNode = Cast(StackGroups[i].EndNode); if (ModuleFunctionCallNode != nullptr) { // The first stack node group is the input node, so we subtract one to get the index of the module. int32 StackIndex = i - 1; ModuleFunctions.Add(MakeShared(InOwningEmitter, *Script.Get(), *ModuleFunctionCallNode, StackIndex)); } } } } UNiagaraNodeInput* FNiagaraScriptStackMergeAdapter::GetInputNode() const { return InputNode.Get(); } UNiagaraNodeOutput* FNiagaraScriptStackMergeAdapter::GetOutputNode() const { return OutputNode.Get(); } UNiagaraScript* FNiagaraScriptStackMergeAdapter::GetScript() const { return Script.Get(); } FString FNiagaraScriptStackMergeAdapter::GetUniqueEmitterName() const { return UniqueEmitterName; } const TArray>& FNiagaraScriptStackMergeAdapter::GetModuleFunctions() const { return ModuleFunctions; } TSharedPtr FNiagaraScriptStackMergeAdapter::GetModuleFunctionById(FGuid FunctionCallNodeId) const { for (TSharedRef Modulefunction : ModuleFunctions) { if (Modulefunction->GetFunctionCallNode()->NodeGuid == FunctionCallNodeId) { return Modulefunction; } } return TSharedPtr(); } void FNiagaraScriptStackMergeAdapter::GatherFunctionCallNodes(TArray& OutFunctionCallNodes) const { for (TSharedRef ModuleFunction : ModuleFunctions) { ModuleFunction->GatherFunctionCallNodes(OutFunctionCallNodes); } } FNiagaraEventHandlerMergeAdapter::FNiagaraEventHandlerMergeAdapter(const FVersionedNiagaraEmitter& InEmitter, const FNiagaraEventScriptProperties* InEventScriptProperties, UNiagaraNodeOutput* InOutputNode) { Initialize(InEmitter, InEventScriptProperties, nullptr, InOutputNode); } FNiagaraEventHandlerMergeAdapter::FNiagaraEventHandlerMergeAdapter(const FVersionedNiagaraEmitter& InEmitter, FNiagaraEventScriptProperties* InEventScriptProperties, UNiagaraNodeOutput* InOutputNode) { Initialize(InEmitter, InEventScriptProperties, InEventScriptProperties, InOutputNode); } FNiagaraEventHandlerMergeAdapter::FNiagaraEventHandlerMergeAdapter(const FVersionedNiagaraEmitter& InEmitter, UNiagaraNodeOutput* InOutputNode) { Initialize(InEmitter, nullptr, nullptr, InOutputNode); } void FNiagaraEventHandlerMergeAdapter::Initialize(const FVersionedNiagaraEmitter& InEmitter, const FNiagaraEventScriptProperties* InEventScriptProperties, FNiagaraEventScriptProperties* InEditableEventScriptProperties, UNiagaraNodeOutput* InOutputNode) { Emitter =InEmitter.ToWeakPtr(); EventScriptProperties = InEventScriptProperties; EditableEventScriptProperties = InEditableEventScriptProperties; OutputNode = InOutputNode; if (EventScriptProperties != nullptr && OutputNode != nullptr) { EventStack = MakeShared(InEmitter, *OutputNode.Get(), *EventScriptProperties->Script); InputNode = EventStack->GetInputNode(); } } FVersionedNiagaraEmitter FNiagaraEventHandlerMergeAdapter::GetEmitter() const { return Emitter.ResolveWeakPtr(); } FGuid FNiagaraEventHandlerMergeAdapter::GetUsageId() const { if (EventScriptProperties != nullptr) { return EventScriptProperties->Script->GetUsageId(); } else { return OutputNode->GetUsageId(); } } const FNiagaraEventScriptProperties* FNiagaraEventHandlerMergeAdapter::GetEventScriptProperties() const { return EventScriptProperties; } FNiagaraEventScriptProperties* FNiagaraEventHandlerMergeAdapter::GetEditableEventScriptProperties() const { return EditableEventScriptProperties; } UNiagaraNodeOutput* FNiagaraEventHandlerMergeAdapter::GetOutputNode() const { return OutputNode.Get(); } UNiagaraNodeInput* FNiagaraEventHandlerMergeAdapter::GetInputNode() const { return InputNode.Get(); } TSharedPtr FNiagaraEventHandlerMergeAdapter::GetEventStack() const { return EventStack; } FNiagaraSimulationStageMergeAdapter::FNiagaraSimulationStageMergeAdapter(const FVersionedNiagaraEmitter& InEmitter, const UNiagaraSimulationStageBase* InSimulationStage, int32 InSimulationStageIndex, UNiagaraNodeOutput* InOutputNode) { Initialize(InEmitter, InSimulationStage, nullptr, InSimulationStageIndex, InOutputNode); } FNiagaraSimulationStageMergeAdapter::FNiagaraSimulationStageMergeAdapter(const FVersionedNiagaraEmitter& InEmitter, UNiagaraSimulationStageBase* InSimulationStage, int32 InSimulationStageIndex, UNiagaraNodeOutput* InOutputNode) { Initialize(InEmitter, InSimulationStage, InSimulationStage, InSimulationStageIndex, InOutputNode); } FNiagaraSimulationStageMergeAdapter::FNiagaraSimulationStageMergeAdapter(const FVersionedNiagaraEmitter& InEmitter, UNiagaraNodeOutput* InOutputNode) { Initialize(InEmitter, nullptr, nullptr, INDEX_NONE, InOutputNode); } void FNiagaraSimulationStageMergeAdapter::Initialize(const FVersionedNiagaraEmitter& InEmitter, const UNiagaraSimulationStageBase* InSimulationStage, UNiagaraSimulationStageBase* InEditableSimulationStage, int32 InSimulationStageIndex, UNiagaraNodeOutput* InOutputNode) { Emitter = InEmitter.ToWeakPtr(); SimulationStage = InSimulationStage; EditableSimulationStage = InEditableSimulationStage; OutputNode = InOutputNode; SimulationStageIndex = InSimulationStageIndex; if (SimulationStage != nullptr && OutputNode != nullptr) { SimulationStageStack = MakeShared(InEmitter, *OutputNode.Get(), *SimulationStage->Script); InputNode = SimulationStageStack->GetInputNode(); } } FVersionedNiagaraEmitter FNiagaraSimulationStageMergeAdapter::GetEmitter() const { return Emitter.ResolveWeakPtr(); } FGuid FNiagaraSimulationStageMergeAdapter::GetUsageId() const { if (SimulationStage != nullptr) { return SimulationStage->Script->GetUsageId(); } else { return OutputNode->GetUsageId(); } } const UNiagaraSimulationStageBase* FNiagaraSimulationStageMergeAdapter::GetSimulationStage() const { return SimulationStage; } UNiagaraSimulationStageBase* FNiagaraSimulationStageMergeAdapter::GetEditableSimulationStage() const { return EditableSimulationStage; } UNiagaraNodeOutput* FNiagaraSimulationStageMergeAdapter::GetOutputNode() const { return OutputNode.Get(); } UNiagaraNodeInput* FNiagaraSimulationStageMergeAdapter::GetInputNode() const { return InputNode.Get(); } int32 FNiagaraSimulationStageMergeAdapter::GetSimulationStageIndex() const { return SimulationStageIndex; } TSharedPtr FNiagaraSimulationStageMergeAdapter::GetSimulationStageStack() const { return SimulationStageStack; } FNiagaraRendererMergeAdapter::FNiagaraRendererMergeAdapter(UNiagaraRendererProperties& InRenderer) { Renderer = &InRenderer; } UNiagaraRendererProperties* FNiagaraRendererMergeAdapter::GetRenderer() { return Renderer.Get(); } FNiagaraInputSummaryMergeAdapter::FNiagaraInputSummaryMergeAdapter(const FFunctionInputSummaryViewKey& Key, const FFunctionInputSummaryViewMetadata& Value) : Key(Key) , Value(Value) { } const FFunctionInputSummaryViewKey& FNiagaraInputSummaryMergeAdapter::GetKey() const { return Key; } const FFunctionInputSummaryViewMetadata& FNiagaraInputSummaryMergeAdapter::GetValue() const { return Value; } FNiagaraScratchPadMergeAdapter::FNiagaraScratchPadMergeAdapter() : bIsInitialized(false) { } FNiagaraScratchPadMergeAdapter::FNiagaraScratchPadMergeAdapter(const FVersionedNiagaraEmitter InTargetEmitter, const FVersionedNiagaraEmitter& InInstanceEmitter, const FVersionedNiagaraEmitter& InParentEmitter) : TargetEmitter(InTargetEmitter) , InstanceEmitter(InInstanceEmitter) , ParentEmitter(InParentEmitter) , bIsInitialized(false) { } UNiagaraScript* FNiagaraScratchPadMergeAdapter::GetScratchPadScriptForFunctionId(FGuid FunctionId) { if (bIsInitialized == false) { Initialize(); } UNiagaraScript** ParentScratchPadScriptPtr = FunctionIdToScratchPadScript.Find(FunctionId); return ParentScratchPadScriptPtr != nullptr ? *ParentScratchPadScriptPtr : nullptr; } TArray< FNiagaraScratchPadMergeAdapter::FMergeRecord > FNiagaraScratchPadMergeAdapter::GetMergedEmitterRecords() const { TArray< FNiagaraScratchPadMergeAdapter::FMergeRecord > FoundArray; // Logic needs to be kept in sync with FNiagaraScratchPadMergeAdapter::Initialize() TArray ParentEmitters; FVersionedNiagaraEmitter CurrentParent = ParentEmitter; while (CurrentParent.Emitter != nullptr) { ParentEmitters.Insert(CurrentParent, 0); CurrentParent = CurrentParent.GetEmitterData()->GetParent(); } for (FVersionedNiagaraEmitter CurrentParentEmitter : ParentEmitters) { FVersionedNiagaraEmitterData* CurrentParentEmitterData = CurrentParentEmitter.GetEmitterData(); if (CurrentParentEmitterData->GetParent().Emitter == nullptr) { for (UNiagaraScript* Script : CurrentParentEmitterData->ParentScratchPads->Scripts) { // Right now we don't version scratch scripts. If we end up doing that, we'll need to account for that here. FoundArray.Emplace(CurrentParentEmitter, Script, FGuid()); } } for (UNiagaraScript* Script : CurrentParentEmitterData->ScratchPads->Scripts) { // Right now we don't version scratch scripts. If we end up doing that, we'll need to account for that here. FoundArray.Emplace(CurrentParentEmitter, Script, FGuid()); } } return FoundArray; } void FNiagaraScratchPadMergeAdapter::Initialize() { FVersionedNiagaraEmitterData* TargetEmitterData = TargetEmitter.GetEmitterData(); if (TargetEmitterData->ScratchPads->Scripts.Num() > 0 || TargetEmitterData->ParentScratchPads->Scripts.Num() > 0) { // Collect the parent emitters in order TArray ParentEmitters; FVersionedNiagaraEmitter CurrentParent = ParentEmitter; while (CurrentParent.Emitter != nullptr) { ParentEmitters.Insert(CurrentParent, 0); CurrentParent = CurrentParent.GetEmitterData()->GetParent(); } // Create a mapping from the actual parent scratch pad scripts to their copies in the target emitter. TMap SourceScratchPadScriptToTargetCopyScratchPadScript; int32 TargetCopyIndex = 0; for (FVersionedNiagaraEmitter CurrentParentEmitter : ParentEmitters) { TArray ParentScratchPadScripts; FVersionedNiagaraEmitterData* CurrentParentEmitterData = CurrentParentEmitter.GetEmitterData(); if (CurrentParentEmitterData->GetParent().Emitter == nullptr) { ParentScratchPadScripts.Append(CurrentParentEmitterData->ParentScratchPads->Scripts); } ParentScratchPadScripts.Append(CurrentParentEmitterData->ScratchPads->Scripts); for (UNiagaraScript* ParentScratchPadScript : ParentScratchPadScripts) { UNiagaraScript* TargetCopy; if (ensureMsgf(TargetCopyIndex < TargetEmitterData->ParentScratchPads->Scripts.Num(), TEXT("Parent scratch pad script was missing from the Target's copies. Emitter %s"), *GetPathNameSafe(InstanceEmitter.Emitter))) { TargetCopy = TargetEmitterData->ParentScratchPads->Scripts[TargetCopyIndex]; } else { TargetCopy = nullptr; } SourceScratchPadScriptToTargetCopyScratchPadScript.Add(ParentScratchPadScript, TargetCopy); TargetCopyIndex++; } } // Create a mapping from the instance scratch pad scripts to their copies in the target emitter. if (FVersionedNiagaraEmitterData* InstanceEmitterData = InstanceEmitter.GetEmitterData()) { for (int32 InstanceScratchPadScriptIndex = 0; InstanceScratchPadScriptIndex < InstanceEmitterData->ScratchPads->Scripts.Num(); InstanceScratchPadScriptIndex++) { UNiagaraScript* TargetCopy; if (ensureMsgf(InstanceScratchPadScriptIndex < TargetEmitterData->ScratchPads->Scripts.Num(), TEXT("Instance scratch pad script was missing from the Target's copies. Emitter %s"), *GetPathNameSafe(InstanceEmitter.Emitter))) { TargetCopy = TargetEmitterData->ScratchPads->Scripts[InstanceScratchPadScriptIndex]; } else { TargetCopy = nullptr; } SourceScratchPadScriptToTargetCopyScratchPadScript.Add(InstanceEmitterData->ScratchPads->Scripts[InstanceScratchPadScriptIndex], TargetCopy); } } // For each source emitter collect up the traversed function ids that use the source scratch pad scripts and cache the corresponding target copies so that // any usages encountered when applying the diff can be hooked up correctly. TArray SourceEmitters; SourceEmitters.Append(ParentEmitters); if (InstanceEmitter.Emitter != nullptr) { SourceEmitters.Add(InstanceEmitter); } for (FVersionedNiagaraEmitter SourceEmitter : SourceEmitters) { FVersionedNiagaraEmitterData* SourceEmitterData = SourceEmitter.GetEmitterData(); if (SourceEmitterData->ScratchPads->Scripts.Num() > 0 || (SourceEmitterData->GetParent().Emitter == nullptr && SourceEmitterData->ParentScratchPads->Scripts.Num() > 0)) { UNiagaraScriptSource* ScriptSource = Cast(SourceEmitterData->GraphSource); if (ScriptSource != nullptr) { TArray OutputNodes; ScriptSource->NodeGraph->GetNodesOfClass(OutputNodes); for (UNiagaraNodeOutput* OutputNode : OutputNodes) { TArray TraversedNodes; UNiagaraGraph::BuildTraversal(TraversedNodes, OutputNode, false); for (UNiagaraNode* TraversedNode : TraversedNodes) { UNiagaraNodeFunctionCall* TraversedFunctionNode = Cast(TraversedNode); if (TraversedFunctionNode != nullptr && TraversedFunctionNode->FunctionScript != nullptr && TraversedFunctionNode->FunctionScript->IsAsset() == false && TraversedFunctionNode->GetClass() == UNiagaraNodeFunctionCall::StaticClass()) { UNiagaraScript* ScratchPadScript = TraversedFunctionNode->FunctionScript; if (SourceEmitterData->ScratchPads->Scripts.Contains(ScratchPadScript) || (SourceEmitterData->GetParent().Emitter == nullptr && SourceEmitterData->ParentScratchPads->Scripts.Contains(ScratchPadScript))) { FunctionIdToScratchPadScript.Add(TraversedFunctionNode->NodeGuid, SourceScratchPadScriptToTargetCopyScratchPadScript[TraversedFunctionNode->FunctionScript]); } } } } } } } } bIsInitialized = true; } FNiagaraEmitterMergeAdapter::FNiagaraEmitterMergeAdapter(const FVersionedNiagaraEmitter& InEmitter) { Initialize(InEmitter, InEmitter); } void FNiagaraEmitterMergeAdapter::Initialize(const FVersionedNiagaraEmitter& InEmitter, const FVersionedNiagaraEmitter& InEditableEmitter) { Emitter = InEmitter.ToWeakPtr(); EditableEmitter = InEditableEmitter.ToWeakPtr(); FVersionedNiagaraEmitterData* EmitterData = Emitter.GetEmitterData(); if (EmitterData == nullptr) { return; } UNiagaraScriptSource* EmitterScriptSource = Cast(EmitterData->GraphSource); UNiagaraGraph* Graph = EmitterScriptSource->NodeGraph; TArray OutputNodes; Graph->GetNodesOfClass(OutputNodes); TArray EventOutputNodes; TArray SimulationStageOutputNodes; for (UNiagaraNodeOutput* OutputNode : OutputNodes) { if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::EmitterSpawnScript)) { EmitterSpawnStack = MakeShared(InEmitter, *OutputNode, *EmitterData->EmitterSpawnScriptProps.Script); } else if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::EmitterUpdateScript)) { EmitterUpdateStack = MakeShared(InEmitter, *OutputNode, *EmitterData->EmitterUpdateScriptProps.Script); } else if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::ParticleSpawnScript)) { ParticleSpawnStack = MakeShared(InEmitter, *OutputNode, *EmitterData->SpawnScriptProps.Script); } else if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::ParticleUpdateScript)) { ParticleUpdateStack = MakeShared(InEmitter, *OutputNode, *EmitterData->UpdateScriptProps.Script); } else if(UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::ParticleEventScript)) { EventOutputNodes.Add(OutputNode); } else if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::ParticleSimulationStageScript)) { SimulationStageOutputNodes.Add(OutputNode); } } // Create an event handler adapter for each usage id even if it's missing an event script properties struct or an output node. These // incomplete adapters will be caught if they are diffed. for (const FNiagaraEventScriptProperties& EventScriptProperties : EmitterData->GetEventHandlers()) { UNiagaraNodeOutput** MatchingOutputNodePtr = EventOutputNodes.FindByPredicate( [=](UNiagaraNodeOutput* EventOutputNode) { return EventOutputNode->GetUsageId() == EventScriptProperties.Script->GetUsageId(); }); UNiagaraNodeOutput* MatchingOutputNode = MatchingOutputNodePtr != nullptr ? *MatchingOutputNodePtr : nullptr; if (EditableEmitter.Emitter == nullptr) { EventHandlers.Add(MakeShared(InEmitter, &EventScriptProperties, MatchingOutputNode)); } else { FNiagaraEventScriptProperties* EditableEventScriptProperties = EditableEmitter.GetEmitterData()->GetEventHandlerByIdUnsafe(EventScriptProperties.Script->GetUsageId()); EventHandlers.Add(MakeShared(InEmitter, EditableEventScriptProperties, MatchingOutputNode)); } if (MatchingOutputNode != nullptr) { EventOutputNodes.Remove(MatchingOutputNode); } } for (UNiagaraNodeOutput* EventOutputNode : EventOutputNodes) { EventHandlers.Add(MakeShared(InEmitter, EventOutputNode)); } // Create an shader stage adapter for each usage id even if it's missing a shader stage object or an output node. These // incomplete adapters will be caught if they are diffed. for (int32 SimulationStageIndex = 0; SimulationStageIndex < EmitterData->GetSimulationStages().Num(); SimulationStageIndex++) { const UNiagaraSimulationStageBase* SimulationStage = EmitterData->GetSimulationStages()[SimulationStageIndex]; UNiagaraNodeOutput** MatchingOutputNodePtr = SimulationStageOutputNodes.FindByPredicate( [=](UNiagaraNodeOutput* SimulationStageOutputNode) { return SimulationStageOutputNode->GetUsageId() == SimulationStage->Script->GetUsageId(); }); UNiagaraNodeOutput* MatchingOutputNode = MatchingOutputNodePtr != nullptr ? *MatchingOutputNodePtr : nullptr; if (EditableEmitter.Emitter == nullptr) { SimulationStages.Add(MakeShared(InEmitter, SimulationStage, SimulationStageIndex, MatchingOutputNode)); } else { UNiagaraSimulationStageBase* EditableSimulationStage = EditableEmitter.GetEmitterData()->GetSimulationStageById(SimulationStage->Script->GetUsageId()); SimulationStages.Add(MakeShared(InEmitter, EditableSimulationStage, SimulationStageIndex, MatchingOutputNode)); } if (MatchingOutputNode != nullptr) { SimulationStageOutputNodes.Remove(MatchingOutputNode); } } for (UNiagaraNodeOutput* SimulationStageOutputNode : SimulationStageOutputNodes) { SimulationStages.Add(MakeShared(InEmitter, SimulationStageOutputNode)); } // Renderers for (UNiagaraRendererProperties* RendererProperties : EmitterData->GetRenderers()) { Renderers.Add(MakeShared(*RendererProperties)); } EditorData = Cast(EmitterData->GetEditorData()); } FVersionedNiagaraEmitter FNiagaraEmitterMergeAdapter::GetEditableEmitter() const { return EditableEmitter.ResolveWeakPtr(); } TSharedPtr FNiagaraEmitterMergeAdapter::GetEmitterSpawnStack() const { return EmitterSpawnStack; } TSharedPtr FNiagaraEmitterMergeAdapter::GetEmitterUpdateStack() const { return EmitterUpdateStack; } TSharedPtr FNiagaraEmitterMergeAdapter::GetParticleSpawnStack() const { return ParticleSpawnStack; } TSharedPtr FNiagaraEmitterMergeAdapter::GetParticleUpdateStack() const { return ParticleUpdateStack; } const TArray> FNiagaraEmitterMergeAdapter::GetEventHandlers() const { return EventHandlers; } const TArray> FNiagaraEmitterMergeAdapter::GetSimulationStages() const { return SimulationStages; } const TArray> FNiagaraEmitterMergeAdapter::GetRenderers() const { return Renderers; } const UNiagaraEmitterEditorData* FNiagaraEmitterMergeAdapter::GetEditorData() const { return EditorData.Get(); } TSharedPtr FNiagaraEmitterMergeAdapter::GetScriptStack(ENiagaraScriptUsage Usage, FGuid ScriptUsageId) { switch (Usage) { case ENiagaraScriptUsage::EmitterSpawnScript: return EmitterSpawnStack; case ENiagaraScriptUsage::EmitterUpdateScript: return EmitterUpdateStack; case ENiagaraScriptUsage::ParticleSpawnScript: return ParticleSpawnStack; case ENiagaraScriptUsage::ParticleUpdateScript: return ParticleUpdateStack; case ENiagaraScriptUsage::ParticleEventScript: for (TSharedPtr EventHandler : EventHandlers) { if (EventHandler->GetUsageId() == ScriptUsageId) { return EventHandler->GetEventStack(); } } break; case ENiagaraScriptUsage::ParticleSimulationStageScript: for (TSharedPtr SimulationStage : SimulationStages) { if (SimulationStage->GetUsageId() == ScriptUsageId) { return SimulationStage->GetSimulationStageStack(); } } break; default: checkf(false, TEXT("Unsupported usage")); } return TSharedPtr(); } TSharedPtr FNiagaraEmitterMergeAdapter::GetEventHandler(FGuid EventScriptUsageId) { for (TSharedRef EventHandler : EventHandlers) { if (EventHandler->GetUsageId() == EventScriptUsageId) { return EventHandler; } } return TSharedPtr(); } TSharedPtr FNiagaraEmitterMergeAdapter::GetSimulationStage(FGuid SimulationStageUsageId) { for (TSharedRef SimulationStage : SimulationStages) { if (SimulationStage->GetUsageId() == SimulationStageUsageId) { return SimulationStage; } } return TSharedPtr(); } TSharedPtr FNiagaraEmitterMergeAdapter::GetRenderer(FGuid RendererMergeId) { for (TSharedRef Renderer : Renderers) { if (Renderer->GetRenderer()->GetMergeId() == RendererMergeId) { return Renderer; } } return TSharedPtr(); } void FNiagaraEmitterMergeAdapter::GatherFunctionCallNodes(TArray& OutFunctionCallNodes) const { EmitterSpawnStack->GatherFunctionCallNodes(OutFunctionCallNodes); EmitterUpdateStack->GatherFunctionCallNodes(OutFunctionCallNodes); ParticleSpawnStack->GatherFunctionCallNodes(OutFunctionCallNodes); ParticleUpdateStack->GatherFunctionCallNodes(OutFunctionCallNodes); for (TSharedRef EventHandler : EventHandlers) { if (TSharedPtr EventStack = EventHandler->GetEventStack()) { EventStack->GatherFunctionCallNodes(OutFunctionCallNodes); } } for (TSharedRef SimulationStage : SimulationStages) { SimulationStage->GetSimulationStageStack()->GatherFunctionCallNodes(OutFunctionCallNodes); } } FNiagaraScriptStackDiffResults::FNiagaraScriptStackDiffResults() : bIsValid(true) { } bool FNiagaraScriptStackDiffResults::IsEmpty() const { return RemovedBaseModules.Num() == 0 && AddedOtherModules.Num() == 0 && MovedBaseModules.Num() == 0 && MovedOtherModules.Num() == 0 && ChangedVersionBaseModules.Num() == 0 && ChangedVersionOtherModules.Num() == 0 && EnabledChangedBaseModules.Num() == 0 && EnabledChangedOtherModules.Num() == 0 && RemovedBaseInputOverrides.Num() == 0 && AddedOtherInputOverrides.Num() == 0 && ModifiedOtherInputOverrides.Num() == 0 && ChangedBaseUsage.IsSet() == false && ChangedOtherUsage.IsSet() == false; } bool FNiagaraScriptStackDiffResults::IsValid() const { return bIsValid; } void FNiagaraScriptStackDiffResults::AddError(FText ErrorMessage) { ErrorMessages.Add(ErrorMessage); bIsValid = false; } const TArray& FNiagaraScriptStackDiffResults::GetErrorMessages() const { return ErrorMessages; } FNiagaraEmitterDiffResults::FNiagaraEmitterDiffResults() : bScratchPadModified(false) , bIsValid(true) { } bool FNiagaraEmitterDiffResults::IsValid() const { bool bEventHandlerDiffsAreValid = true; for (const FNiagaraModifiedEventHandlerDiffResults& EventHandlerDiffResults : ModifiedEventHandlers) { if (EventHandlerDiffResults.ScriptDiffResults.IsValid() == false) { bEventHandlerDiffsAreValid = false; break; } } bool bSimulationStageDiffsAreValid = true; for (const FNiagaraModifiedSimulationStageDiffResults& SimulationStageDiffResults : ModifiedSimulationStages) { if (SimulationStageDiffResults.ScriptDiffResults.IsValid() == false) { bSimulationStageDiffsAreValid = false; break; } } return bIsValid && bEventHandlerDiffsAreValid && bSimulationStageDiffsAreValid && EmitterSpawnDiffResults.IsValid() && EmitterUpdateDiffResults.IsValid() && ParticleSpawnDiffResults.IsValid() && ParticleUpdateDiffResults.IsValid(); } bool FNiagaraEmitterDiffResults::IsEmpty() const { return DifferentEmitterProperties.Num() == 0 && EmitterSpawnDiffResults.IsEmpty() && EmitterUpdateDiffResults.IsEmpty() && ParticleSpawnDiffResults.IsEmpty() && ParticleUpdateDiffResults.IsEmpty() && RemovedBaseEventHandlers.Num() == 0 && AddedOtherEventHandlers.Num() == 0 && ModifiedEventHandlers.Num() == 0 && RemovedBaseSimulationStages.Num() == 0 && AddedOtherSimulationStages.Num() == 0 && ModifiedSimulationStages.Num() == 0 && RemovedBaseRenderers.Num() == 0 && AddedOtherRenderers.Num() == 0 && ModifiedBaseRenderers.Num() == 0 && ModifiedOtherRenderers.Num() == 0 && AddedSummaryEntriesInOther.Num() == 0 && AddedSummarySectionsInOther.Num() == 0 && ModifiedStackEntryDisplayNames.Num() == 0 && AddedOrModifiedStackNotesInOther.Num() == 0 && bScratchPadModified == false && NewShouldShowSummaryViewValue.IsSet() == false; } void FNiagaraEmitterDiffResults::AddError(FText ErrorMessage) { ErrorMessages.Add(ErrorMessage); bIsValid = false; } const TArray& FNiagaraEmitterDiffResults::GetErrorMessages() const { return ErrorMessages; } FString FNiagaraEmitterDiffResults::GetErrorMessagesString() const { TArray ErrorMessageStrings; for (FText ErrorMessage : ErrorMessages) { ErrorMessageStrings.Add(ErrorMessage.ToString()); } return FString::Join(ErrorMessageStrings, TEXT("\n")); } bool FNiagaraEmitterDiffResults::HasVersionChanges() const { for (auto& EventHandlerDiff : ModifiedEventHandlers) { if (EventHandlerDiff.ScriptDiffResults.ChangedVersionOtherModules.Num() > 0) { return true; } } for (auto& SimStageDiff : ModifiedSimulationStages) { if (SimStageDiff.ScriptDiffResults.ChangedVersionOtherModules.Num() > 0) { return true; } } return EmitterSpawnDiffResults.ChangedVersionOtherModules.Num() > 0 || EmitterUpdateDiffResults.ChangedVersionOtherModules.Num() > 0 || ParticleSpawnDiffResults.ChangedVersionOtherModules.Num() > 0 || ParticleUpdateDiffResults.ChangedVersionOtherModules.Num() > 0; } TSharedRef FNiagaraScriptMergeManager::Get() { FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked("NiagaraEditor"); return NiagaraEditorModule.GetScriptMergeManager(); } void FNiagaraScriptMergeManager::GetForcedChangeIds( const TMap& InParentFunctionIdToNodeMap, const TMap& InParentAtLastMergeFunctionIdToNodeMap, const TMap& InInstanceFunctionIdToNodeMap, TMap& OutFunctionIdToForcedChangeId) const { for(const TPair& InstanceFunctionIdNodePair : InInstanceFunctionIdToNodeMap) { const FGuid& InstanceFunctionId = InstanceFunctionIdNodePair.Key; UNiagaraNodeFunctionCall* InstanceFunctionNode = InstanceFunctionIdNodePair.Value; UNiagaraNodeFunctionCall*const* MatchingParentFunctionNodePtr = InParentFunctionIdToNodeMap.Find(InstanceFunctionId); UNiagaraNodeFunctionCall*const* MatchingParentAtLastMergeFunctionNodePtr = InParentAtLastMergeFunctionIdToNodeMap.Find(InstanceFunctionId); if (MatchingParentFunctionNodePtr == nullptr && MatchingParentAtLastMergeFunctionNodePtr == nullptr) { // If neither the current parent or parent at last merge had a function node with this id, force the current change id of the node // since it only exists in the instance. OutFunctionIdToForcedChangeId.Add(InstanceFunctionId, InstanceFunctionNode->GetChangeId()); } else if (MatchingParentFunctionNodePtr != nullptr && MatchingParentAtLastMergeFunctionNodePtr != nullptr) { if ((*MatchingParentFunctionNodePtr)->GetChangeId() == (*MatchingParentAtLastMergeFunctionNodePtr)->GetChangeId()) { // If both the parent and parent at last merge agree on the change id, then the most recent changes will have happened in // the instance so we can force the instance change id. OutFunctionIdToForcedChangeId.Add(InstanceFunctionId, InstanceFunctionNode->GetChangeId()); } else { if (InstanceFunctionNode->GetChangeId() == (*MatchingParentAtLastMergeFunctionNodePtr)->GetChangeId()) { // If the parent and the parent at last merge have different change ids, and the instances change id matches // the parent at last merge change id, then the parent has changed, and the instance has not, so we can // force the id from the parent. OutFunctionIdToForcedChangeId.Add(InstanceFunctionId, (*MatchingParentFunctionNodePtr)->GetChangeId()); } } } else if (MatchingParentAtLastMergeFunctionNodePtr != nullptr) { // If the parent did not have this function id but the parent at the last merge did have the id, it was deleted in the parent // so only the instance id is still relevant. OutFunctionIdToForcedChangeId.Add(InstanceFunctionId, InstanceFunctionNode->GetChangeId()); } else { ensureMsgf(MatchingParentFunctionNodePtr == nullptr, TEXT("Error while merging changes for emitter instance function node: %s. A node with this change id was not found in the last merge parent, but it was found on parent node: %s"), *InstanceFunctionNode->GetPathName(), *(*MatchingParentFunctionNodePtr)->GetPathName()); } } } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ForceInstanceChangeIds(TSharedRef MergedInstanceAdapter, const FVersionedNiagaraEmitter& OriginalEmitterInstance, const TMap& FunctionIdToForcedChangedId) const { FApplyDiffResults DiffResults; if (FunctionIdToForcedChangedId.Num() != 0) { auto It = FunctionIdToForcedChangedId.CreateConstIterator(); FVersionedNiagaraEmitter Emitter = MergedInstanceAdapter->GetEditableEmitter(); TArray Graphs; TArray Scripts; Emitter.GetEmitterData()->GetScripts(Scripts); TArray OriginalScripts; OriginalEmitterInstance.GetEmitterData()->GetScripts(OriginalScripts); // First gather all the graphs used by this emitter.. for (UNiagaraScript* Script : Scripts) { if (Script != nullptr && Script->GetLatestSource() != nullptr) { UNiagaraScriptSource* ScriptSource = Cast(Script->GetLatestSource()); if (ScriptSource != nullptr) { Graphs.AddUnique(ScriptSource->NodeGraph); } } } // Now gather up all the nodes TArray Nodes; for (UNiagaraGraph* Graph : Graphs) { Graph->GetNodesOfClass(Nodes); } // Now go through all the nodes and set the persistent change ids if we encounter a node that needs it's change id kept. bool bAnySet = false; while (It) { for (UNiagaraNode* Node : Nodes) { if (Node->NodeGuid == It.Key()) { Node->ForceChangeId(It.Value(), false); bAnySet = true; break; } } ++It; } if (bAnySet) { for (UNiagaraGraph* Graph : Graphs) { Graph->MarkGraphRequiresSynchronization(TEXT("Overwrote change id's within graph.")); } } DiffResults.bModifiedGraph = bAnySet; if (bAnySet) { bool bAnyUpdated = false; TMap RenameMap; RenameMap.Add(TEXT("Emitter"), TEXT("Emitter")); for (UNiagaraScript* Script : Scripts) { for (UNiagaraScript* OriginalScript : OriginalScripts) { if (Script->Usage == OriginalScript->Usage && Script->GetUsageId() == OriginalScript->GetUsageId()) { bAnyUpdated |= Script->SynchronizeExecutablesWithCompilation(OriginalScript, RenameMap); } } } if (bAnyUpdated) { //Emitter->OnPostCompile(); } } } DiffResults.bSucceeded = true; return DiffResults; } void FNiagaraScriptMergeManager::UpdateModuleVersions(const FVersionedNiagaraEmitter& Instance, const FNiagaraEmitterDiffResults& DiffResults) const { // gather all the changes TArray> ChangedVersionBaseModules; ChangedVersionBaseModules.Append(DiffResults.EmitterSpawnDiffResults.ChangedVersionBaseModules); ChangedVersionBaseModules.Append(DiffResults.EmitterUpdateDiffResults.ChangedVersionBaseModules); ChangedVersionBaseModules.Append(DiffResults.ParticleSpawnDiffResults.ChangedVersionBaseModules); ChangedVersionBaseModules.Append(DiffResults.ParticleUpdateDiffResults.ChangedVersionBaseModules); for (auto& EventHandlerDiff : DiffResults.ModifiedEventHandlers) { ChangedVersionBaseModules.Append(EventHandlerDiff.ScriptDiffResults.ChangedVersionBaseModules); } for (auto& SimStageDiff : DiffResults.ModifiedSimulationStages) { ChangedVersionBaseModules.Append(SimStageDiff.ScriptDiffResults.ChangedVersionBaseModules); } TArray> ChangedVersionOtherModules; ChangedVersionOtherModules.Append(DiffResults.EmitterSpawnDiffResults.ChangedVersionOtherModules); ChangedVersionOtherModules.Append(DiffResults.EmitterUpdateDiffResults.ChangedVersionOtherModules); ChangedVersionOtherModules.Append(DiffResults.ParticleSpawnDiffResults.ChangedVersionOtherModules); ChangedVersionOtherModules.Append(DiffResults.ParticleUpdateDiffResults.ChangedVersionOtherModules); for (auto& EventHandlerDiff : DiffResults.ModifiedEventHandlers) { ChangedVersionOtherModules.Append(EventHandlerDiff.ScriptDiffResults.ChangedVersionOtherModules); } for (auto& SimStageDiff : DiffResults.ModifiedSimulationStages) { ChangedVersionOtherModules.Append(SimStageDiff.ScriptDiffResults.ChangedVersionOtherModules); } // the python update script needs a view model, so we need to create a temporary system here to instantiate the view models UNiagaraSystem* System = nullptr; if (ChangedVersionOtherModules.Num() > 0) { TSharedPtr SystemViewModel; System = NewObject(GetTransientPackage(), NAME_None, RF_Transient); UNiagaraSystemFactoryNew::InitializeSystem(System, true); SystemViewModel = MakeShared(); FNiagaraSystemViewModelOptions SystemOptions; SystemOptions.bCanModifyEmittersFromTimeline = false; SystemOptions.bCanSimulate = false; SystemOptions.bCanAutoCompile = false; SystemOptions.bIsForDataProcessingOnly = true; SystemOptions.EditMode = ENiagaraSystemViewModelEditMode::EmitterDuringMerge; SystemOptions.MessageLogGuid = System->GetAssetGuid(); SystemViewModel->Initialize(*System, SystemOptions); SystemViewModel->GetEditorData().SetOwningSystemIsPlaceholder(true, *System); SystemViewModel->AddEmitter(Instance); // apply version changes for (int i = 0; i < ChangedVersionOtherModules.Num(); i++) { TSharedRef BaseModule = ChangedVersionBaseModules[i]; TSharedRef ChangedModule = ChangedVersionOtherModules[i]; FGuid NewScriptVersion = BaseModule->GetFunctionCallNode()->SelectedScriptVersion; // search for the stack item of the module node we are merging UNiagaraStackModuleItem* ModuleItem = nullptr; UNiagaraStackViewModel* EmitterStackViewModel = SystemViewModel->GetEmitterHandleViewModels()[0]->GetEmitterStackViewModel(); TArray EntriesToCheck; EmitterStackViewModel->GetRootEntry()->GetUnfilteredChildren(EntriesToCheck); while (EntriesToCheck.Num() > 0) { UNiagaraStackEntry* Entry = EntriesToCheck.Pop(); UNiagaraStackModuleItem* ModuleItemToCheck = Cast(Entry); if (ModuleItemToCheck && ModuleItemToCheck->GetModuleNode().NodeGuid == ChangedModule->GetFunctionCallNode()->NodeGuid) { ModuleItem = ModuleItemToCheck; break; } Entry->GetUnfilteredChildren(EntriesToCheck); } FNiagaraScriptVersionUpgradeContext UpgradeContext; UpgradeContext.ConstantResolver = FCompileConstantResolver(Instance, FNiagaraStackGraphUtilities::GetOutputNodeUsage(*ChangedModule->GetFunctionCallNode())); if (ModuleItem) { UpgradeContext.CreateClipboardCallback = [ModuleItem](UNiagaraClipboardContent* ClipboardContent) { ModuleItem->RefreshChildren(); ModuleItem->Copy(ClipboardContent); if (ClipboardContent->Functions.Num() > 0) { ClipboardContent->FunctionInputs = ClipboardContent->Functions[0]->Inputs; ClipboardContent->Functions.Empty(); } }; UpgradeContext.ApplyClipboardCallback = [ModuleItem](UNiagaraClipboardContent* ClipboardContent, FText& OutWarning) { ModuleItem->Paste(ClipboardContent, OutWarning); }; } ChangedModule->GetFunctionCallNode()->ChangeScriptVersion(NewScriptVersion, UpgradeContext, true /*bShowNotesInStack*/, true /*bDeferOverridePinUpdate*/); ChangedModule->GetFunctionCallNode()->RefreshFromExternalChanges(); } // now that we've applied the changes to all of the changed modules we go back through them and ensure their override nodes are up to date // this is done after the fact to avoid constantly regenerating the static variables that may be at play for (TSharedRef& ChangedModule : ChangedVersionOtherModules) { FNiagaraScriptVersionUpgradeContext UpgradeContext; UpgradeContext.ConstantResolver = FCompileConstantResolver(Instance, FNiagaraStackGraphUtilities::GetOutputNodeUsage(*ChangedModule->GetFunctionCallNode())); ChangedModule->GetFunctionCallNode()->UpdateOverridePins(UpgradeContext); } } // now that we're done with our transient System we need to reset it so that if it does get pulled in (via TObjectIterator as an example) // it won't conflict with legitimate systems holding onto the supplied emitter if (System) { System->ResetToEmptySystem(); System->MarkAsGarbage(); } } enum class EmitterMergeState { Failed, Unchanged, DuplicateParent, CopyProperties }; FNiagaraScriptMergeManager::EmitterMergeState FNiagaraScriptMergeManager::GetEmitterMergeState(const FNiagaraEmitterDiffResults& DiffResults, const FVersionedNiagaraEmitter& Parent, const FVersionedNiagaraEmitter& Instance) const { EmitterMergeState State = EmitterMergeState::CopyProperties; if (DiffResults.IsValid() == false) { State = EmitterMergeState::Failed; } else if (DiffResults.IsEmpty()) { // If there were no changes made on the instance, check if the instance matches the parent. FNiagaraEmitterDiffResults DiffResultsFromParent = DiffEmitters(Parent, Instance); if (DiffResultsFromParent.IsValid() && DiffResultsFromParent.IsEmpty()) { State = EmitterMergeState::Unchanged; } else if (Instance.Emitter->IsVersioningEnabled()) { // if this emitter is versioned we cannot just duplicate the parent, as we would lose version data State = EmitterMergeState::CopyProperties; } else { State = EmitterMergeState::DuplicateParent; } } return State; } INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmitter(const FVersionedNiagaraEmitter& Parent, const FVersionedNiagaraEmitter& ParentAtLastMerge, const FVersionedNiagaraEmitter& Instance) const { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptMergeManager_MergeEmitter); FMergeEmitterResults MergeResults; const bool bNoParentAtLastMerge = (ParentAtLastMerge.Emitter == nullptr); FVersionedNiagaraEmitter FirstEmitterToDiffAgainst = bNoParentAtLastMerge ? Parent : ParentAtLastMerge; FNiagaraEmitterDiffResults DiffResults = DiffEmitters(FirstEmitterToDiffAgainst, Instance); // depending on the diff result and versioning state of the emitters, we decide what to do next EmitterMergeState State = GetEmitterMergeState(DiffResults, Parent, Instance); if (State == EmitterMergeState::Unchanged) { MergeResults.MergeResult = EMergeEmitterResult::SucceededNoDifferences; } else if (State == EmitterMergeState::DuplicateParent) { TRACE_CPUPROFILER_EVENT_SCOPE(EmitterMergeState::DuplicateParent); // If there were differences from the parent or the parent diff failed we can just return a copy of the parent as the merged instance since there // were no changes in the instance which need to be applied. MergeResults.MergeResult = EMergeEmitterResult::SucceededDifferencesApplied; MergeResults.MergedInstance = Parent.Emitter->DuplicateWithoutMerging(GetTransientPackage()); MergeResults.MergedInstance->DisableVersioning(Parent.Version); FVersionedNiagaraEmitterData* MergedEmitterData = MergeResults.MergedInstance->GetLatestEmitterData(); MergedEmitterData->ParentScratchPads->AppendScripts(MergedEmitterData->ScratchPads); } else if (State == EmitterMergeState::Failed) { TRACE_CPUPROFILER_EVENT_SCOPE(EmitterMergeState::Failed); MergeResults.MergeResult = EMergeEmitterResult::FailedToDiff; MergeResults.ErrorMessages = DiffResults.GetErrorMessages(); auto ReportScriptStackDiffErrors = [](FMergeEmitterResults& EmitterMergeResults, const FNiagaraScriptStackDiffResults& ScriptStackDiffResults, FText ScriptName) { FText ScriptStackDiffInvalidFormat = LOCTEXT("ScriptStackDiffInvalidFormat", "Failed to diff {0} script stack. {1} Errors:"); if (ScriptStackDiffResults.IsValid() == false) { EmitterMergeResults.ErrorMessages.Add(FText::Format(ScriptStackDiffInvalidFormat, ScriptName, ScriptStackDiffResults.GetErrorMessages().Num())); for (const FText& ErrorMessage : ScriptStackDiffResults.GetErrorMessages()) { EmitterMergeResults.ErrorMessages.Add(ErrorMessage); } } }; ReportScriptStackDiffErrors(MergeResults, DiffResults.EmitterSpawnDiffResults, LOCTEXT("EmitterSpawnScriptName", "Emitter Spawn")); ReportScriptStackDiffErrors(MergeResults, DiffResults.EmitterUpdateDiffResults, LOCTEXT("EmitterUpdateScriptName", "Emitter Update")); ReportScriptStackDiffErrors(MergeResults, DiffResults.ParticleSpawnDiffResults, LOCTEXT("ParticleSpawnScriptName", "Particle Spawn")); ReportScriptStackDiffErrors(MergeResults, DiffResults.ParticleUpdateDiffResults, LOCTEXT("ParticleUpdateScriptName", "Particle Update")); for (const FNiagaraModifiedEventHandlerDiffResults& EventHandlerDiffResults : DiffResults.ModifiedEventHandlers) { FText EventHandlerName = FText::Format(LOCTEXT("EventHandlerScriptNameFormat", "Event Handler - {0}"), FText::FromName(EventHandlerDiffResults.BaseAdapter->GetEventScriptProperties()->SourceEventName)); ReportScriptStackDiffErrors(MergeResults, EventHandlerDiffResults.ScriptDiffResults, EventHandlerName); } } else if (State == EmitterMergeState::CopyProperties) //-V547 { TRACE_CPUPROFILER_EVENT_SCOPE(EmitterMergeState::CopyProperties); UNiagaraEmitter* MergedInstance = Parent.Emitter->DuplicateWithoutMerging(GetTransientPackage()); MergedInstance->DisableVersioning(Parent.Version); FVersionedNiagaraEmitter VersionedMergedInstance = FVersionedNiagaraEmitter(MergedInstance, Parent.Version); TSharedRef MergedInstanceAdapter = MakeShared(VersionedMergedInstance); // diff for version changes and apply them first. We need to upgrade both the current emitter and the parent at last merge // to the newest version and then diff them again. Otherwise we won't know which changes were made by the version upgrade // and which changes were made to the parent emitter. FNiagaraEmitterDiffResults VersionChangeDiffResults = DiffEmitters(Parent, Instance); if (VersionChangeDiffResults.HasVersionChanges()) { // apply version upgrade to the merged emitter instance UpdateModuleVersions(Instance, VersionChangeDiffResults); // apply version upgrade to the parent at last merge UNiagaraEmitter* ParentAtLastMergeCopy = Cast(StaticDuplicateObject(FirstEmitterToDiffAgainst.Emitter, GetTransientPackage())); ParentAtLastMergeCopy->ClearFlags(RF_Standalone | RF_Public); FVersionedNiagaraEmitter VersionedParentCopy = FVersionedNiagaraEmitter(ParentAtLastMergeCopy, FirstEmitterToDiffAgainst.Version); FNiagaraEmitterDiffResults ParentsDiffResults = DiffEmitters(Parent, VersionedParentCopy); UpdateModuleVersions(VersionedParentCopy, ParentsDiffResults); // now diff again DiffResults = DiffEmitters(VersionedParentCopy, Instance); ensureMsgf(DiffResults.HasVersionChanges() == false, TEXT("Emitter still has pending version changes after merge! Asset %s"), *Instance.Emitter->GetPathName()); } TMap ParentFunctionIdToNodeMap; TMap LastMergedParentFunctionIdToNodeMap; TMap InstanceFunctionIdToNodeMap; auto PopulateIdToNodeMap = [](TSharedRef EmitterAdapter, TMap& Map) { TArray EmitterFunctionCalls; EmitterAdapter->GatherFunctionCallNodes(EmitterFunctionCalls); for (UNiagaraNodeFunctionCall* EmitterFunctionCall : EmitterFunctionCalls) { Map.Add(EmitterFunctionCall->NodeGuid, EmitterFunctionCall); } }; { TRACE_CPUPROFILER_EVENT_SCOPE(Chunk1); PopulateIdToNodeMap(MakeShared(Parent), ParentFunctionIdToNodeMap); PopulateIdToNodeMap(DiffResults.BaseEmitterAdapter.ToSharedRef(), LastMergedParentFunctionIdToNodeMap); PopulateIdToNodeMap(DiffResults.OtherEmitterAdapter.ToSharedRef(), InstanceFunctionIdToNodeMap); } TMap FunctionIdToForcedChangeId; { TRACE_CPUPROFILER_EVENT_SCOPE(Chunk2); GetForcedChangeIds(ParentFunctionIdToNodeMap, LastMergedParentFunctionIdToNodeMap, InstanceFunctionIdToNodeMap, FunctionIdToForcedChangeId); } FVersionedNiagaraEmitterData* MergedEmitterData = VersionedMergedInstance.GetEmitterData(); MergedEmitterData->ParentScratchPads->AppendScripts(MergedEmitterData->ScratchPads); CopyInstanceScratchPadScripts(VersionedMergedInstance, Instance.GetEmitterData()); TSharedRef ScratchPadAdapter = MakeShared(VersionedMergedInstance, Instance, Parent); MergeResults.MergeResult = EMergeEmitterResult::SucceededDifferencesApplied; FApplyDiffResults EmitterSpawnResults = ApplyScriptStackDiff(MergedInstanceAdapter, MergedInstanceAdapter->GetEmitterSpawnStack().ToSharedRef(), ScratchPadAdapter, DiffResults.EmitterSpawnDiffResults, bNoParentAtLastMerge); if (EmitterSpawnResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= EmitterSpawnResults.bModifiedGraph; MergeResults.ErrorMessages.Append(EmitterSpawnResults.ErrorMessages); FApplyDiffResults EmitterUpdateResults = ApplyScriptStackDiff(MergedInstanceAdapter, MergedInstanceAdapter->GetEmitterUpdateStack().ToSharedRef(), ScratchPadAdapter, DiffResults.EmitterUpdateDiffResults, bNoParentAtLastMerge); if (EmitterUpdateResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= EmitterUpdateResults.bModifiedGraph; MergeResults.ErrorMessages.Append(EmitterUpdateResults.ErrorMessages); FApplyDiffResults ParticleSpawnResults = ApplyScriptStackDiff(MergedInstanceAdapter, MergedInstanceAdapter->GetParticleSpawnStack().ToSharedRef(), ScratchPadAdapter, DiffResults.ParticleSpawnDiffResults, bNoParentAtLastMerge); if (ParticleSpawnResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= ParticleSpawnResults.bModifiedGraph; MergeResults.ErrorMessages.Append(ParticleSpawnResults.ErrorMessages); FApplyDiffResults ParticleUpdateResults = ApplyScriptStackDiff(MergedInstanceAdapter, MergedInstanceAdapter->GetParticleUpdateStack().ToSharedRef(), ScratchPadAdapter, DiffResults.ParticleUpdateDiffResults, bNoParentAtLastMerge); if (ParticleUpdateResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= ParticleUpdateResults.bModifiedGraph; MergeResults.ErrorMessages.Append(ParticleUpdateResults.ErrorMessages); FApplyDiffResults EventHandlerResults = ApplyEventHandlerDiff(MergedInstanceAdapter, ScratchPadAdapter, DiffResults, bNoParentAtLastMerge); if (EventHandlerResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= EventHandlerResults.bModifiedGraph; MergeResults.ErrorMessages.Append(EventHandlerResults.ErrorMessages); FApplyDiffResults SimulationStageResults = ApplySimulationStageDiff(MergedInstanceAdapter, ScratchPadAdapter, DiffResults, bNoParentAtLastMerge); if (SimulationStageResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= SimulationStageResults.bModifiedGraph; MergeResults.ErrorMessages.Append(SimulationStageResults.ErrorMessages); FApplyDiffResults RendererResults = ApplyRendererDiff(VersionedMergedInstance, DiffResults, bNoParentAtLastMerge); if (RendererResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= RendererResults.bModifiedGraph; MergeResults.ErrorMessages.Append(RendererResults.ErrorMessages); FApplyDiffResults InputSummaryResults = ApplyEmitterSummaryDiff(VersionedMergedInstance, DiffResults); if (InputSummaryResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= InputSummaryResults.bModifiedGraph; MergeResults.ErrorMessages.Append(InputSummaryResults.ErrorMessages); CopyPropertiesToBase(VersionedMergedInstance.GetEmitterData(), Instance.GetEmitterData(), DiffResults.DifferentEmitterProperties); FApplyDiffResults StackEntryDisplayNameDiffs = ApplyStackEntryDisplayNameDiffs(VersionedMergedInstance, DiffResults); if (StackEntryDisplayNameDiffs.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= StackEntryDisplayNameDiffs.bModifiedGraph; MergeResults.ErrorMessages.Append(StackEntryDisplayNameDiffs.ErrorMessages); FApplyDiffResults StackNotesDiffs = ApplyStackNoteDiffs(VersionedMergedInstance, DiffResults); if (StackNotesDiffs.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= StackNotesDiffs.bModifiedGraph; MergeResults.ErrorMessages.Append(StackNotesDiffs.ErrorMessages); #if 0 UE_LOG(LogNiagaraEditor, Log, TEXT("A")); //for (FNiagaraEmitterHandle& EmitterHandle : EmitterHandles) { //UE_LOG(LogNiagara, Log, TEXT("Emitter Handle: %s"), *EmitterHandle.GetUniqueInstanceName()); UNiagaraScript* UpdateScript = MergedInstance->GetScript(ENiagaraScriptUsage::ParticleUpdateScript, FGuid()); UNiagaraScript* SpawnScript = MergedInstance->GetScript(ENiagaraScriptUsage::ParticleSpawnScript, FGuid()); UE_LOG(LogNiagaraEditor, Log, TEXT("Spawn Parameters")); SpawnScript->GetVMExecutableData().Parameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Spawn RI Parameters")); SpawnScript->RapidIterationParameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Update Parameters")); UpdateScript->GetVMExecutableData().Parameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Update RI Parameters")); UpdateScript->RapidIterationParameters.DumpParameters(); } #endif FApplyDiffResults ChangeIdResults = ForceInstanceChangeIds(MergedInstanceAdapter, Instance, FunctionIdToForcedChangeId); if (ChangeIdResults.bSucceeded == false) { MergeResults.MergeResult = EMergeEmitterResult::FailedToMerge; } MergeResults.bModifiedGraph |= ChangeIdResults.bModifiedGraph; MergeResults.ErrorMessages.Append(ChangeIdResults.ErrorMessages); #if 0 UE_LOG(LogNiagaraEditor, Log, TEXT("B")); //for (FNiagaraEmitterHandle& EmitterHandle : EmitterHandles) { //UE_LOG(LogNiagara, Log, TEXT("Emitter Handle: %s"), *EmitterHandle.GetUniqueInstanceName()); UNiagaraScript* UpdateScript = MergedInstance->GetScript(ENiagaraScriptUsage::ParticleUpdateScript, FGuid()); UNiagaraScript* SpawnScript = MergedInstance->GetScript(ENiagaraScriptUsage::ParticleSpawnScript, FGuid()); UE_LOG(LogNiagaraEditor, Log, TEXT("Spawn Parameters")); SpawnScript->GetVMExecutableData().Parameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Spawn RI Parameters")); SpawnScript->RapidIterationParameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Update Parameters")); UpdateScript->GetVMExecutableData().Parameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Update RI Parameters")); UpdateScript->RapidIterationParameters.DumpParameters(); } #endif FNiagaraStackGraphUtilities::CleanUpStaleRapidIterationParameters(VersionedMergedInstance); #if 0 UE_LOG(LogNiagaraEditor, Log, TEXT("C")); //for (FNiagaraEmitterHandle& EmitterHandle : EmitterHandles) { //UE_LOG(LogNiagara, Log, TEXT("Emitter Handle: %s"), *EmitterHandle.GetUniqueInstanceName()); UNiagaraScript* UpdateScript = MergedInstance->GetScript(ENiagaraScriptUsage::ParticleUpdateScript, FGuid()); UNiagaraScript* SpawnScript = MergedInstance->GetScript(ENiagaraScriptUsage::ParticleSpawnScript, FGuid()); UE_LOG(LogNiagaraEditor, Log, TEXT("Spawn Parameters")); SpawnScript->GetVMExecutableData().Parameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Spawn RI Parameters")); SpawnScript->RapidIterationParameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Update Parameters")); UpdateScript->GetVMExecutableData().Parameters.DumpParameters(); UE_LOG(LogNiagaraEditor, Log, TEXT("Update RI Parameters")); UpdateScript->RapidIterationParameters.DumpParameters(); } #endif if (MergeResults.MergeResult == EMergeEmitterResult::SucceededDifferencesApplied) { UNiagaraScriptSource* ScriptSource = Cast(MergedEmitterData->GraphSource); FNiagaraStackGraphUtilities::RelayoutGraph(*ScriptSource->NodeGraph); MergeResults.MergedInstance = MergedInstance; } TMap FinalChangeIds; FNiagaraEditorUtilities::GatherChangeIds(*MergedInstance, FinalChangeIds, TEXT("Final")); } else { checkf(false, TEXT("Unknown merge state!")); } if (MergeResults.MergeResult != INiagaraMergeManager::EMergeEmitterResult::SucceededNoDifferences && MergeResults.MergeResult != INiagaraMergeManager::EMergeEmitterResult::SucceededDifferencesApplied) { TArray Errors; Errors.Add(FText::Format(LOCTEXT("MergeFailedFormat", "Failed to merge changes from parent emitter {0}"), FText::FromString(Parent.Emitter->GetName()))); Errors.Append(MergeResults.ErrorMessages); UNiagaraMessageDataText* Message = NewObject(GetTransientPackage()); Message->Init( FText::Join(FText::FromString("\n"), Errors), LOCTEXT("MergeFailedFormatShort", "Failed to merge changes from parent emitter"), ENiagaraMessageSeverity::Error, "Emitter Merge"); Message->SetAllowDismissal(false); MergeResults.MergeNiagaraMessage = Message; } return MergeResults; } bool FNiagaraScriptMergeManager::IsMergeableScriptUsage(ENiagaraScriptUsage ScriptUsage) const { return ScriptUsage == ENiagaraScriptUsage::EmitterSpawnScript || ScriptUsage == ENiagaraScriptUsage::EmitterUpdateScript || ScriptUsage == ENiagaraScriptUsage::ParticleSpawnScript || ScriptUsage == ENiagaraScriptUsage::ParticleUpdateScript || ScriptUsage == ENiagaraScriptUsage::ParticleEventScript || ScriptUsage == ENiagaraScriptUsage::ParticleSimulationStageScript; } bool FNiagaraScriptMergeManager::HasBaseModule(const FVersionedNiagaraEmitter& BaseEmitter, ENiagaraScriptUsage ScriptUsage, FGuid ScriptUsageId, FGuid ModuleId) { TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); TSharedPtr BaseScriptStackAdapter = BaseEmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId); return BaseScriptStackAdapter.IsValid() && BaseScriptStackAdapter->GetModuleFunctionById(ModuleId).IsValid(); } bool FNiagaraScriptMergeManager::FindBaseModule(const FVersionedNiagaraEmitter& BaseEmitter, ENiagaraScriptUsage ScriptUsage, FGuid ScriptUsageId, FGuid ModuleId, class UNiagaraScript*& OutActualScript, FGuid& OutScriptVersionGuid, FVersionedNiagaraEmitter& OutBaseEmitter) { // Search through the existing scratch pads local to this versioned emitter and the ones inherited from parents. OutScriptVersionGuid = FGuid(); OutActualScript = nullptr; OutBaseEmitter = FVersionedNiagaraEmitter(); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); TSharedRef ScratchPadMergeAdapter = GetScratchPadMergeAdapterUsingCache(BaseEmitter); TSharedPtr BaseScriptStackAdapter = BaseEmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId); if (BaseScriptStackAdapter.IsValid()) { TSharedPtr AdapterFound = BaseScriptStackAdapter->GetModuleFunctionById(ModuleId); if (AdapterFound.IsValid()) { UNiagaraNodeFunctionCall* CallNode = AdapterFound->GetFunctionCallNode(); if (CallNode) { UNiagaraScript* Script = CallNode->FunctionScript; if (Script) { int32 IndexToMatch = INDEX_NONE; bool bParents = false; FVersionedNiagaraEmitterData* EmitterData = BaseEmitter.Emitter->GetEmitterData(BaseEmitter.Version); if (EmitterData) { if (EmitterData->ScratchPads) { IndexToMatch = EmitterData->ScratchPads->FindIndexForScript(Script); } if (INDEX_NONE == IndexToMatch && EmitterData->ParentScratchPads) { IndexToMatch = EmitterData->ParentScratchPads->FindIndexForScript(Script); bParents = true; } } if (bParents && IndexToMatch != INDEX_NONE) { TArray< FNiagaraScratchPadMergeAdapter::FMergeRecord > MergeRecordsInOrder = ScratchPadMergeAdapter->GetMergedEmitterRecords(); if (MergeRecordsInOrder.IsValidIndex(IndexToMatch)) { OutScriptVersionGuid = MergeRecordsInOrder[IndexToMatch].OriginalScriptVersion; OutActualScript = MergeRecordsInOrder[IndexToMatch].OriginalScript; OutBaseEmitter = MergeRecordsInOrder[IndexToMatch].OriginalEmitter; return true; } } else if (!bParents && IndexToMatch != INDEX_NONE) { OutScriptVersionGuid = CallNode->SelectedScriptVersion; OutActualScript = Script; OutBaseEmitter = BaseEmitter; return true; } } return false; } } } return false; } bool FNiagaraScriptMergeManager::IsModuleInputDifferentFromBase(const FVersionedNiagaraEmitter& Emitter, const FVersionedNiagaraEmitter& BaseEmitter, ENiagaraScriptUsage ScriptUsage, FGuid ScriptUsageId, FGuid ModuleId, FNiagaraVariableBase Variable) { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptMergeManager_IsModuleInputDifferentFromBase); TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(Emitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); TSharedRef ScriptStackAdapter = EmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId).ToSharedRef(); TSharedPtr BaseScriptStackAdapter = BaseEmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId); if (BaseScriptStackAdapter.IsValid() == false) { return false; } FNiagaraScriptStackDiffResults ScriptStackDiffResults; DiffScriptStacks(BaseScriptStackAdapter.ToSharedRef(), ScriptStackAdapter, ScriptStackDiffResults); if (ScriptStackDiffResults.IsValid() == false) { return true; } if (ScriptStackDiffResults.IsEmpty()) { return false; } auto FindInputOverrideByInputName = [=](TSharedRef InputOverride) { FNiagaraVariableBase CandidateVariable(InputOverride->GetType(), FName(InputOverride->GetInputName())); return InputOverride->GetOwningFunctionCall()->NodeGuid == ModuleId && CandidateVariable == Variable; }; return ScriptStackDiffResults.RemovedBaseInputOverrides.FindByPredicate(FindInputOverrideByInputName) != nullptr || ScriptStackDiffResults.AddedOtherInputOverrides.FindByPredicate(FindInputOverrideByInputName) != nullptr || ScriptStackDiffResults.ModifiedOtherInputOverrides.FindByPredicate(FindInputOverrideByInputName) != nullptr; } bool FNiagaraScriptMergeManager::DoesSummaryItemExistInBase(const FVersionedNiagaraEmitter& Emitter, FHierarchyElementIdentity Identity) { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptMergeManager_DoesSummaryItemExistInBase); FVersionedNiagaraEmitter BaseEmitter = Emitter.GetEmitterData()->GetParent(); if(BaseEmitter.GetEmitterData() != nullptr) { TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); return BaseEmitterAdapter->GetEditorData()->GetSummaryRoot()->FindChildWithIdentity(Identity, true) != nullptr; } return false; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ResetModuleInputToBase(const FVersionedNiagaraEmitter& VersionedEmitter, const FVersionedNiagaraEmitter& VersionedBaseEmitter, ENiagaraScriptUsage ScriptUsage, FGuid ScriptUsageId, FGuid ModuleId, FString InputName) { TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(VersionedEmitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(VersionedBaseEmitter); TSharedRef ScratchPadMergeAdapter = GetScratchPadMergeAdapterUsingCache(VersionedEmitter); // Diff from the emitter to the base to create a diff which will reset the emitter back to the base. FNiagaraScriptStackDiffResults ResetDiffResults; DiffScriptStacks(EmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId).ToSharedRef(), BaseEmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId).ToSharedRef(), ResetDiffResults); FText EmitterPath = FText::FromString(VersionedEmitter.Emitter->GetPathName()); if (ResetDiffResults.IsValid() == false) { FApplyDiffResults Results; Results.bSucceeded = false; Results.bModifiedGraph = false; Results.ErrorMessages.Add(FText::Format(LOCTEXT("ResetFailedBecauseOfDiffMessage", "Failed to reset input back to it's base value. It couldn't be diffed successfully. Emitter: {0} Input:{1}"), EmitterPath, FText::FromString(InputName))); return Results; } if (ResetDiffResults.IsEmpty()) { FApplyDiffResults Results; Results.bSucceeded = false; Results.bModifiedGraph = false; Results.ErrorMessages.Add(FText::Format(LOCTEXT("ResetFailedBecauseOfEmptyDiffMessage", "Failed to reset input back to it's base value. It wasn't different from the base. Emitter: {0} Input:{1}"), EmitterPath, FText::FromString(InputName))); return Results; } FVersionedNiagaraEmitterData* EmitterData = VersionedEmitter.GetEmitterData(); FVersionedNiagaraEmitterData* BaseEmitterData = VersionedBaseEmitter.GetEmitterData(); if (EmitterData->ParentScratchPads->Scripts.Num() != (BaseEmitterData->ParentScratchPads->Scripts.Num() + BaseEmitterData->ScratchPads->Scripts.Num())) { FApplyDiffResults Results; Results.bSucceeded = false; Results.bModifiedGraph = false; Results.ErrorMessages.Add(FText::Format(LOCTEXT("ResetFailedBecauseOfScratchPadScripts", "Failed to reset input back to it's base value. Its scratch pad scripts were out of sync. Emitter: {0} Input:{1}"), EmitterPath, FText::FromString(InputName))); return Results; } // Remove items from the diff which are not relevant to this input. ResetDiffResults.RemovedBaseModules.Empty(); ResetDiffResults.AddedOtherModules.Empty(); auto FindUnrelatedInputOverrides = [=](TSharedRef InputOverride) { return InputOverride->GetOwningFunctionCall()->NodeGuid != ModuleId || InputOverride->GetInputName() != InputName; }; ResetDiffResults.RemovedBaseInputOverrides.RemoveAll(FindUnrelatedInputOverrides); ResetDiffResults.AddedOtherInputOverrides.RemoveAll(FindUnrelatedInputOverrides); ResetDiffResults.ModifiedBaseInputOverrides.RemoveAll(FindUnrelatedInputOverrides); ResetDiffResults.ModifiedOtherInputOverrides.RemoveAll(FindUnrelatedInputOverrides); return ApplyScriptStackDiff(EmitterAdapter, EmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId).ToSharedRef(), ScratchPadMergeAdapter, ResetDiffResults, false); } bool FNiagaraScriptMergeManager::HasBaseEventHandler(const FVersionedNiagaraEmitter& BaseEmitter, FGuid EventScriptUsageId) { TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); return BaseEmitterAdapter->GetEventHandler(EventScriptUsageId).IsValid(); } bool FNiagaraScriptMergeManager::IsEventHandlerPropertySetDifferentFromBase(const FVersionedNiagaraEmitter& Emitter, const FVersionedNiagaraEmitter& BaseEmitter, FGuid EventScriptUsageId) { TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(Emitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); TSharedPtr EventHandlerAdapter = EmitterAdapter->GetEventHandler(EventScriptUsageId); TSharedPtr BaseEventHandlerAdapter = BaseEmitterAdapter->GetEventHandler(EventScriptUsageId); if (EventHandlerAdapter->GetEditableEventScriptProperties() == nullptr || BaseEventHandlerAdapter->GetEventScriptProperties() == nullptr) { return true; } TArray DifferentProperties; DiffEditableProperties(BaseEventHandlerAdapter->GetEventScriptProperties(), EventHandlerAdapter->GetEventScriptProperties(), *FNiagaraEventScriptProperties::StaticStruct(), DifferentProperties); return DifferentProperties.Num() > 0; } void FNiagaraScriptMergeManager::ResetEventHandlerPropertySetToBase(const FVersionedNiagaraEmitter& VersionedEmitter, const FVersionedNiagaraEmitter& BaseEmitter, FGuid EventScriptUsageId) { TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(VersionedEmitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); TSharedPtr EventHandlerAdapter = EmitterAdapter->GetEventHandler(EventScriptUsageId); TSharedPtr BaseEventHandlerAdapter = BaseEmitterAdapter->GetEventHandler(EventScriptUsageId); if (EventHandlerAdapter->GetEditableEventScriptProperties() == nullptr || BaseEventHandlerAdapter->GetEventScriptProperties() == nullptr) { // TODO: Display an error to the user. return; } TArray DifferentProperties; DiffEditableProperties(BaseEventHandlerAdapter->GetEventScriptProperties(), EventHandlerAdapter->GetEventScriptProperties(), *FNiagaraEventScriptProperties::StaticStruct(), DifferentProperties); CopyPropertiesToBase(EventHandlerAdapter->GetEditableEventScriptProperties(), BaseEventHandlerAdapter->GetEventScriptProperties(), DifferentProperties); VersionedEmitter.Emitter->PostEditChange(); } bool FNiagaraScriptMergeManager::HasBaseSimulationStage(const FVersionedNiagaraEmitter& BaseEmitter, FGuid SimulationStageScriptUsageId) { TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); return BaseEmitterAdapter->GetSimulationStage(SimulationStageScriptUsageId).IsValid(); } bool FNiagaraScriptMergeManager::IsSimulationStagePropertySetDifferentFromBase(const FVersionedNiagaraEmitter& Emitter, const FVersionedNiagaraEmitter& BaseEmitter, FGuid SimulationStageScriptUsageId) { TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(Emitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); TSharedPtr SimulationStageAdapter = EmitterAdapter->GetSimulationStage(SimulationStageScriptUsageId); TSharedPtr BaseSimulationStageAdapter = BaseEmitterAdapter->GetSimulationStage(SimulationStageScriptUsageId); if (SimulationStageAdapter->GetEditableSimulationStage() == nullptr || BaseSimulationStageAdapter->GetSimulationStage() == nullptr) { return true; } TArray DifferentProperties; DiffEditableProperties(BaseSimulationStageAdapter->GetSimulationStage(), SimulationStageAdapter->GetSimulationStage(), *BaseSimulationStageAdapter->GetSimulationStage()->GetClass(), DifferentProperties); return DifferentProperties.Num() > 0; } void FNiagaraScriptMergeManager::ResetSimulationStagePropertySetToBase(const FVersionedNiagaraEmitter& VersionedEmitter, const FVersionedNiagaraEmitter& BaseEmitter, FGuid SimulationStageScriptUsageId) { TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(VersionedEmitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); TSharedPtr SimulationStageAdapter = EmitterAdapter->GetSimulationStage(SimulationStageScriptUsageId); TSharedPtr BaseSimulationStageAdapter = BaseEmitterAdapter->GetSimulationStage(SimulationStageScriptUsageId); if (SimulationStageAdapter->GetEditableSimulationStage() == nullptr || BaseSimulationStageAdapter->GetSimulationStage() == nullptr) { // TODO: Display an error to the user. return; } TArray DifferentProperties; DiffEditableProperties(BaseSimulationStageAdapter->GetSimulationStage(), SimulationStageAdapter->GetSimulationStage(), *BaseSimulationStageAdapter->GetSimulationStage()->GetClass(), DifferentProperties); CopyPropertiesToBase(SimulationStageAdapter->GetEditableSimulationStage(), BaseSimulationStageAdapter->GetSimulationStage(), DifferentProperties); VersionedEmitter.Emitter->PostEditChange(); } bool FNiagaraScriptMergeManager::HasBaseRenderer(const FVersionedNiagaraEmitter& BaseEmitter, FGuid RendererMergeId) { TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); return BaseEmitterAdapter->GetRenderer(RendererMergeId).IsValid(); } bool FNiagaraScriptMergeManager::IsRendererDifferentFromBase(const FVersionedNiagaraEmitter& Emitter, const FVersionedNiagaraEmitter& BaseEmitter, FGuid RendererMergeId) { TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(Emitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); FNiagaraEmitterDiffResults DiffResults; DiffRenderers(BaseEmitterAdapter->GetRenderers(), EmitterAdapter->GetRenderers(), DiffResults); if (DiffResults.IsValid() == false) { return true; } if (DiffResults.ModifiedOtherRenderers.Num() == 0) { return false; } auto FindRendererByMergeId = [=](TSharedRef Renderer) { return Renderer->GetRenderer()->GetMergeId() == RendererMergeId; }; return DiffResults.ModifiedOtherRenderers.FindByPredicate(FindRendererByMergeId) != nullptr; } void FNiagaraScriptMergeManager::ResetRendererToBase(FVersionedNiagaraEmitter Emitter, const FVersionedNiagaraEmitter& BaseEmitter, FGuid RendererMergeId) { TSharedRef EmitterAdapter = GetEmitterMergeAdapterUsingCache(Emitter); TSharedRef BaseEmitterAdapter = GetEmitterMergeAdapterUsingCache(BaseEmitter); // Diff from the current emitter to the base emitter to create a diff which will reset the emitter back to the base. FNiagaraEmitterDiffResults ResetDiffResults; DiffRenderers(EmitterAdapter->GetRenderers(), BaseEmitterAdapter->GetRenderers(), ResetDiffResults); auto FindUnrelatedRenderers = [=](TSharedRef Renderer) { return Renderer->GetRenderer()->GetMergeId() != RendererMergeId; }; // Removed added and removed renderers, as well as changes to renderers with different ids from the one being reset. ResetDiffResults.RemovedBaseRenderers.Empty(); ResetDiffResults.AddedOtherRenderers.Empty(); ResetDiffResults.ModifiedBaseRenderers.RemoveAll(FindUnrelatedRenderers); ResetDiffResults.ModifiedOtherRenderers.RemoveAll(FindUnrelatedRenderers); ApplyRendererDiff(Emitter, ResetDiffResults, false); } bool FNiagaraScriptMergeManager::IsEmitterEditablePropertySetDifferentFromBase(const FVersionedNiagaraEmitter& Emitter, const FVersionedNiagaraEmitter& BaseEmitter) { TArray DifferentProperties; DiffEditableProperties(BaseEmitter.GetEmitterData(), Emitter.GetEmitterData(), *FVersionedNiagaraEmitterData::StaticStruct(), DifferentProperties); return DifferentProperties.Num() > 0; } void FNiagaraScriptMergeManager::ResetEmitterEditablePropertySetToBase(const FVersionedNiagaraEmitter& VersionedEmitter, const FVersionedNiagaraEmitter& BaseEmitter) { TArray DifferentProperties; DiffEditableProperties(BaseEmitter.GetEmitterData(), VersionedEmitter.GetEmitterData(), *FVersionedNiagaraEmitterData::StaticStruct(), DifferentProperties); CopyPropertiesToBase(VersionedEmitter.GetEmitterData(), BaseEmitter.GetEmitterData(), DifferentProperties); VersionedEmitter.Emitter->PostEditChange(); } FNiagaraEmitterDiffResults FNiagaraScriptMergeManager::DiffEmitters(const FVersionedNiagaraEmitter& BaseEmitter, const FVersionedNiagaraEmitter& OtherEmitter) const { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptMergeManager_DiffEmitters); TSharedRef BaseEmitterAdapter = MakeShared(BaseEmitter); TSharedRef OtherEmitterAdapter = MakeShared(OtherEmitter); FNiagaraEmitterDiffResults EmitterDiffResults; if (BaseEmitterAdapter->GetEmitterSpawnStack().IsValid() && OtherEmitterAdapter->GetEmitterSpawnStack().IsValid()) { DiffScriptStacks(BaseEmitterAdapter->GetEmitterSpawnStack().ToSharedRef(), OtherEmitterAdapter->GetEmitterSpawnStack().ToSharedRef(), EmitterDiffResults.EmitterSpawnDiffResults); } else { EmitterDiffResults.AddError(LOCTEXT("EmitterSpawnStacksInvalidMessage", "One of the emitter spawn script stacks was invalid.")); } if (BaseEmitterAdapter->GetEmitterUpdateStack().IsValid() && OtherEmitterAdapter->GetEmitterUpdateStack().IsValid()) { DiffScriptStacks(BaseEmitterAdapter->GetEmitterUpdateStack().ToSharedRef(), OtherEmitterAdapter->GetEmitterUpdateStack().ToSharedRef(), EmitterDiffResults.EmitterUpdateDiffResults); } else { EmitterDiffResults.AddError(LOCTEXT("EmitterUpdateStacksInvalidMessage", "One of the emitter update script stacks was invalid.")); } if (BaseEmitterAdapter->GetParticleSpawnStack().IsValid() && OtherEmitterAdapter->GetParticleSpawnStack().IsValid()) { DiffScriptStacks(BaseEmitterAdapter->GetParticleSpawnStack().ToSharedRef(), OtherEmitterAdapter->GetParticleSpawnStack().ToSharedRef(), EmitterDiffResults.ParticleSpawnDiffResults); } else { EmitterDiffResults.AddError(LOCTEXT("ParticleSpawnStacksInvalidMessage", "One of the particle spawn script stacks was invalid.")); } if (BaseEmitterAdapter->GetParticleUpdateStack().IsValid() && OtherEmitterAdapter->GetParticleUpdateStack().IsValid()) { DiffScriptStacks(BaseEmitterAdapter->GetParticleUpdateStack().ToSharedRef(), OtherEmitterAdapter->GetParticleUpdateStack().ToSharedRef(), EmitterDiffResults.ParticleUpdateDiffResults); } else { EmitterDiffResults.AddError(LOCTEXT("ParticleUpdateStacksInvalidMessage", "One of the particle update script stacks was invalid.")); } FVersionedNiagaraEmitterData* BaseEmitterData = BaseEmitter.GetEmitterData(); FVersionedNiagaraEmitterData* OtherEmitterData = OtherEmitter.GetEmitterData(); DiffEventHandlers(BaseEmitterAdapter->GetEventHandlers(), OtherEmitterAdapter->GetEventHandlers(), EmitterDiffResults); DiffSimulationStages(BaseEmitterAdapter->GetSimulationStages(), OtherEmitterAdapter->GetSimulationStages(), EmitterDiffResults); DiffRenderers(BaseEmitterAdapter->GetRenderers(), OtherEmitterAdapter->GetRenderers(), EmitterDiffResults); DiffEmitterSummary(BaseEmitterAdapter->GetEditorData(), OtherEmitterAdapter->GetEditorData(), EmitterDiffResults); DiffEditableProperties(BaseEmitterData, OtherEmitterData, *FVersionedNiagaraEmitterData::StaticStruct(), EmitterDiffResults.DifferentEmitterProperties); DiffStackEntryDisplayNames(BaseEmitterAdapter->GetEditorData(), OtherEmitterAdapter->GetEditorData(), EmitterDiffResults.ModifiedStackEntryDisplayNames); DiffStackNotes(BaseEmitterAdapter->GetEditorData(), OtherEmitterAdapter->GetEditorData(), EmitterDiffResults); TArray BaseScratchPadScripts; BaseScratchPadScripts.Append(BaseEmitterData->ParentScratchPads->Scripts); BaseScratchPadScripts.Append(BaseEmitterData->ScratchPads->Scripts); TArray OtherScratchPadScripts; OtherScratchPadScripts.Append(OtherEmitterData->ParentScratchPads->Scripts); OtherScratchPadScripts.Append(OtherEmitterData->ScratchPads->Scripts); DiffScratchPadScripts(BaseScratchPadScripts, OtherScratchPadScripts, EmitterDiffResults); EmitterDiffResults.BaseEmitterAdapter = BaseEmitterAdapter; EmitterDiffResults.OtherEmitterAdapter = OtherEmitterAdapter; return EmitterDiffResults; } template struct FCommonValuePair { FCommonValuePair(ValueType InBaseValue, ValueType InOtherValue) : BaseValue(InBaseValue) , OtherValue(InOtherValue) { } ValueType BaseValue; ValueType OtherValue; }; template struct FListDiffResults { TArray RemovedBaseValues; TArray AddedOtherValues; TArray> CommonValuePairs; }; template FListDiffResults DiffLists(const TArray& BaseList, const TArray OtherList, KeyFromValueDelegate KeyFromValue) { FListDiffResults DiffResults; TMap BaseKeyToValueMap; TSet BaseKeys; for (ValueType BaseValue : BaseList) { KeyType BaseKey = KeyFromValue(BaseValue); BaseKeyToValueMap.Add(BaseKey, BaseValue); BaseKeys.Add(BaseKey); } TMap OtherKeyToValueMap; TSet OtherKeys; for (ValueType OtherValue : OtherList) { KeyType OtherKey = KeyFromValue(OtherValue); OtherKeyToValueMap.Add(OtherKey, OtherValue); OtherKeys.Add(OtherKey); } for (KeyType RemovedKey : BaseKeys.Difference(OtherKeys)) { DiffResults.RemovedBaseValues.Add(BaseKeyToValueMap[RemovedKey]); } for (KeyType AddedKey : OtherKeys.Difference(BaseKeys)) { DiffResults.AddedOtherValues.Add(OtherKeyToValueMap[AddedKey]); } for (KeyType CommonKey : BaseKeys.Intersect(OtherKeys)) { DiffResults.CommonValuePairs.Add(FCommonValuePair(BaseKeyToValueMap[CommonKey], OtherKeyToValueMap[CommonKey])); } return DiffResults; } void FNiagaraScriptMergeManager::DiffEventHandlers(const TArray>& BaseEventHandlers, const TArray>& OtherEventHandlers, FNiagaraEmitterDiffResults& DiffResults) const { FListDiffResults> EventHandlerListDiffResults = DiffLists, FGuid>( BaseEventHandlers, OtherEventHandlers, [](TSharedRef EventHandler) { return EventHandler->GetUsageId(); }); DiffResults.RemovedBaseEventHandlers.Append(EventHandlerListDiffResults.RemovedBaseValues); DiffResults.AddedOtherEventHandlers.Append(EventHandlerListDiffResults.AddedOtherValues); for (const FCommonValuePair>& CommonValuePair : EventHandlerListDiffResults.CommonValuePairs) { if (CommonValuePair.BaseValue->GetEventScriptProperties() == nullptr || CommonValuePair.BaseValue->GetOutputNode() == nullptr) { DiffResults.AddError(FText::Format(LOCTEXT("InvalidBaseEventHandlerDiffFailedFormat", "Failed to diff event handlers, the base event handler was invalid. Script Usage Id: {0}"), FText::FromString(CommonValuePair.BaseValue->GetUsageId().ToString()))); } else if(CommonValuePair.OtherValue->GetEventScriptProperties() == nullptr || CommonValuePair.OtherValue->GetOutputNode() == nullptr) { DiffResults.AddError(FText::Format(LOCTEXT("InvalidOtherEventHandlerDiffFailedFormat", "Failed to diff event handlers, the other event handler was invalid. Script Usage Id: {0}"), FText::FromString(CommonValuePair.OtherValue->GetUsageId().ToString()))); } else { TArray DifferentProperties; DiffEditableProperties(CommonValuePair.BaseValue->GetEventScriptProperties(), CommonValuePair.OtherValue->GetEventScriptProperties(), *FNiagaraEventScriptProperties::StaticStruct(), DifferentProperties); FNiagaraScriptStackDiffResults EventHandlerScriptStackDiffResults; DiffScriptStacks(CommonValuePair.BaseValue->GetEventStack().ToSharedRef(), CommonValuePair.OtherValue->GetEventStack().ToSharedRef(), EventHandlerScriptStackDiffResults); if (DifferentProperties.Num() > 0 || EventHandlerScriptStackDiffResults.IsValid() == false || EventHandlerScriptStackDiffResults.IsEmpty() == false) { FNiagaraModifiedEventHandlerDiffResults ModifiedEventHandlerResults; ModifiedEventHandlerResults.BaseAdapter = CommonValuePair.BaseValue; ModifiedEventHandlerResults.OtherAdapter = CommonValuePair.OtherValue; ModifiedEventHandlerResults.ChangedProperties.Append(DifferentProperties); ModifiedEventHandlerResults.ScriptDiffResults = EventHandlerScriptStackDiffResults; DiffResults.ModifiedEventHandlers.Add(ModifiedEventHandlerResults); } if (EventHandlerScriptStackDiffResults.IsValid() == false) { for (const FText& EventHandlerScriptStackDiffErrorMessage : EventHandlerScriptStackDiffResults.GetErrorMessages()) { DiffResults.AddError(EventHandlerScriptStackDiffErrorMessage); } } } } } void FNiagaraScriptMergeManager::DiffSimulationStages(const TArray>& BaseSimulationStages, const TArray>& OtherSimulationStages, FNiagaraEmitterDiffResults& DiffResults) const { FListDiffResults> SimulationStageListDiffResults = DiffLists, FGuid>( BaseSimulationStages, OtherSimulationStages, [](TSharedRef SimulationStage) { return SimulationStage->GetUsageId(); }); // Sort the diff results for easier diff applying and testing. auto OrderSimulationStageByIndex = [](TSharedRef SimulationStageA, TSharedRef SimulationStageB) { return SimulationStageA->GetSimulationStageIndex() < SimulationStageB->GetSimulationStageIndex(); }; SimulationStageListDiffResults.RemovedBaseValues.Sort(OrderSimulationStageByIndex); SimulationStageListDiffResults.AddedOtherValues.Sort(OrderSimulationStageByIndex); auto OrderCommonSimulationStagePairByBaseIndex = []( const FCommonValuePair>& CommonValuesA, const FCommonValuePair>& CommonValuesB) { return CommonValuesA.BaseValue->GetSimulationStageIndex() < CommonValuesB.BaseValue->GetSimulationStageIndex(); }; SimulationStageListDiffResults.CommonValuePairs.Sort(OrderCommonSimulationStagePairByBaseIndex); // Populate results from the sorted diff. DiffResults.RemovedBaseSimulationStages.Append(SimulationStageListDiffResults.RemovedBaseValues); DiffResults.AddedOtherSimulationStages.Append(SimulationStageListDiffResults.AddedOtherValues); for (const FCommonValuePair>& CommonValuePair : SimulationStageListDiffResults.CommonValuePairs) { if (CommonValuePair.BaseValue->GetSimulationStage() == nullptr || CommonValuePair.BaseValue->GetOutputNode() == nullptr) { DiffResults.AddError(FText::Format(LOCTEXT("InvalidBaseSimulationStageDiffFailedFormat", "Failed to diff shader stages, the base shader stage was invalid. Script Usage Id: {0}"), FText::FromString(CommonValuePair.BaseValue->GetUsageId().ToString()))); } else if (CommonValuePair.OtherValue->GetSimulationStage() == nullptr || CommonValuePair.OtherValue->GetOutputNode() == nullptr) { DiffResults.AddError(FText::Format(LOCTEXT("InvalidOtherSimulationStageDiffFailedFormat", "Failed to diff shader stage, the other shader stage was invalid. Script Usage Id: {0}"), FText::FromString(CommonValuePair.OtherValue->GetUsageId().ToString()))); } else { TArray DifferentProperties; DiffEditableProperties(CommonValuePair.BaseValue->GetSimulationStage(), CommonValuePair.OtherValue->GetSimulationStage(), *CommonValuePair.BaseValue->GetSimulationStage()->GetClass(), DifferentProperties); FNiagaraScriptStackDiffResults SimulationStageScriptStackDiffResults; DiffScriptStacks(CommonValuePair.BaseValue->GetSimulationStageStack().ToSharedRef(), CommonValuePair.OtherValue->GetSimulationStageStack().ToSharedRef(), SimulationStageScriptStackDiffResults); if (DifferentProperties.Num() > 0 || SimulationStageScriptStackDiffResults.IsValid() == false || SimulationStageScriptStackDiffResults.IsEmpty() == false) { FNiagaraModifiedSimulationStageDiffResults ModifiedSimulationStageResults; ModifiedSimulationStageResults.BaseAdapter = CommonValuePair.BaseValue; ModifiedSimulationStageResults.OtherAdapter = CommonValuePair.OtherValue; ModifiedSimulationStageResults.ChangedProperties.Append(DifferentProperties); ModifiedSimulationStageResults.ScriptDiffResults = SimulationStageScriptStackDiffResults; DiffResults.ModifiedSimulationStages.Add(ModifiedSimulationStageResults); } if (SimulationStageScriptStackDiffResults.IsValid() == false) { for (const FText& SimulationStageScriptStackDiffErrorMessage : SimulationStageScriptStackDiffResults.GetErrorMessages()) { DiffResults.AddError(SimulationStageScriptStackDiffErrorMessage); } } } } } void FNiagaraScriptMergeManager::DiffRenderers(const TArray>& BaseRenderers, const TArray>& OtherRenderers, FNiagaraEmitterDiffResults& DiffResults) const { FListDiffResults> RendererListDiffResults = DiffLists, FGuid>( BaseRenderers, OtherRenderers, [](TSharedRef Renderer) { return Renderer->GetRenderer()->GetMergeId(); }); DiffResults.RemovedBaseRenderers.Append(RendererListDiffResults.RemovedBaseValues); DiffResults.AddedOtherRenderers.Append(RendererListDiffResults.AddedOtherValues); for (const FCommonValuePair>& CommonValuePair : RendererListDiffResults.CommonValuePairs) { if (CommonValuePair.BaseValue->GetRenderer()->Equals(CommonValuePair.OtherValue->GetRenderer()) == false) { DiffResults.ModifiedBaseRenderers.Add(CommonValuePair.BaseValue); DiffResults.ModifiedOtherRenderers.Add(CommonValuePair.OtherValue); } } } void FNiagaraScriptMergeManager::DiffEmitterSummary(const UNiagaraEmitterEditorData* BaseEditorData, const UNiagaraEmitterEditorData* OtherEditorData, FNiagaraEmitterDiffResults& DiffResults) const { if(BaseEditorData == nullptr || OtherEditorData == nullptr) { return; } DiffResults.NewShouldShowSummaryViewValue = OtherEditorData->ShouldShowSummaryView(); TArray AddedItemsInOther; TArray RemovedItemsInBase; TArray AddedSections; UHierarchyRoot* BaseRoot = BaseEditorData->GetSummaryRoot(); UHierarchyRoot* OtherRoot = OtherEditorData->GetSummaryRoot(); TArray BaseItems; BaseRoot->GetChildrenOfType(BaseItems, true); TArray OtherItems; OtherRoot->GetChildrenOfType(OtherItems, true); FListDiffResults SummaryItemDiff = DiffLists( BaseItems, OtherItems, [](UHierarchyElement* HierarchyItem) { return HierarchyItem->GetPersistentIdentity(); }); DiffResults.AddedSummaryEntriesInOther.Append(SummaryItemDiff.AddedOtherValues); TConstArrayView> BaseSections = BaseRoot->GetSectionData(); TConstArrayView> OtherSections = OtherRoot->GetSectionData(); for (const UHierarchySection* OtherSection : OtherSections) { FHierarchyElementIdentity OtherSectionIdentity = OtherSection->GetPersistentIdentity(); if(!BaseSections.ContainsByPredicate([OtherSectionIdentity](const UHierarchySection* BaseSection) { return BaseSection->GetPersistentIdentity() == OtherSectionIdentity; })) { AddedSections.Add(OtherSection); } } DiffResults.AddedSummarySectionsInOther = AddedSections; } void FNiagaraScriptMergeManager::DiffScriptStacks(TSharedRef BaseScriptStackAdapter, TSharedRef OtherScriptStackAdapter, FNiagaraScriptStackDiffResults& DiffResults) const { // Diff the module lists. FListDiffResults> ModuleListDiffResults = DiffLists, FGuid>( BaseScriptStackAdapter->GetModuleFunctions(), OtherScriptStackAdapter->GetModuleFunctions(), [](TSharedRef FunctionAdapter) { return FunctionAdapter->GetFunctionCallNode()->NodeGuid; }); // Sort the diff results for easier diff applying and testing. auto OrderModuleByStackIndex = [](TSharedRef ModuleA, TSharedRef ModuleB) { return ModuleA->GetStackIndex() < ModuleB->GetStackIndex(); }; ModuleListDiffResults.RemovedBaseValues.Sort(OrderModuleByStackIndex); ModuleListDiffResults.AddedOtherValues.Sort(OrderModuleByStackIndex); auto OrderCommonModulePairByBaseStackIndex = []( const FCommonValuePair>& CommonValuesA, const FCommonValuePair>& CommonValuesB) { return CommonValuesA.BaseValue->GetStackIndex() < CommonValuesB.BaseValue->GetStackIndex(); }; ModuleListDiffResults.CommonValuePairs.Sort(OrderCommonModulePairByBaseStackIndex); // Populate results from the sorted diff. DiffResults.RemovedBaseModules.Append(ModuleListDiffResults.RemovedBaseValues); DiffResults.AddedOtherModules.Append(ModuleListDiffResults.AddedOtherValues); for (const FCommonValuePair>& CommonValuePair : ModuleListDiffResults.CommonValuePairs) { if (CommonValuePair.BaseValue->GetStackIndex() != CommonValuePair.OtherValue->GetStackIndex()) { DiffResults.MovedBaseModules.Add(CommonValuePair.BaseValue); DiffResults.MovedOtherModules.Add(CommonValuePair.OtherValue); } if (CommonValuePair.BaseValue->GetFunctionCallNode()->IsNodeEnabled() != CommonValuePair.OtherValue->GetFunctionCallNode()->IsNodeEnabled()) { DiffResults.EnabledChangedBaseModules.Add(CommonValuePair.BaseValue); DiffResults.EnabledChangedOtherModules.Add(CommonValuePair.OtherValue); } if (CommonValuePair.BaseValue->GetFunctionCallNode()->SelectedScriptVersion != CommonValuePair.OtherValue->GetFunctionCallNode()->SelectedScriptVersion) { DiffResults.ChangedVersionBaseModules.Add(CommonValuePair.BaseValue); DiffResults.ChangedVersionOtherModules.Add(CommonValuePair.OtherValue); } UNiagaraScript* BaseFunctionScript = CommonValuePair.BaseValue->GetFunctionCallNode()->FunctionScript; UNiagaraScript* OtherFunctionScript = CommonValuePair.OtherValue->GetFunctionCallNode()->FunctionScript; bool bFunctionScriptsMatch = BaseFunctionScript == OtherFunctionScript; bool bFunctionScriptsAreNotAssets = BaseFunctionScript != nullptr && BaseFunctionScript->IsAsset() == false && OtherFunctionScript != nullptr && OtherFunctionScript->IsAsset() == false; if (bFunctionScriptsMatch || bFunctionScriptsAreNotAssets) { DiffFunctionInputs(CommonValuePair.BaseValue, CommonValuePair.OtherValue, DiffResults); } else { FText ErrorMessage = FText::Format(LOCTEXT("FunctionScriptMismatchFormat", "Function scripts for function {0} did not match. Parent: {1} Child: {2}. This can be fixed by removing the module from the parent, merging the removal to the child, then removing it from the child, and then re-adding it to the parent and merging again."), FText::FromString(CommonValuePair.BaseValue->GetFunctionCallNode()->GetFunctionName()), FText::FromString(CommonValuePair.BaseValue->GetFunctionCallNode()->FunctionScript != nullptr ? CommonValuePair.BaseValue->GetFunctionCallNode()->FunctionScript->GetPathName() : TEXT("(null)")), FText::FromString(CommonValuePair.OtherValue->GetFunctionCallNode()->FunctionScript != nullptr ? CommonValuePair.OtherValue->GetFunctionCallNode()->FunctionScript->GetPathName() : TEXT("(null)"))); DiffResults.AddError(ErrorMessage); } } if (BaseScriptStackAdapter->GetScript()->GetUsage() != OtherScriptStackAdapter->GetScript()->GetUsage()) { DiffResults.ChangedBaseUsage = BaseScriptStackAdapter->GetScript()->GetUsage(); DiffResults.ChangedOtherUsage = OtherScriptStackAdapter->GetScript()->GetUsage(); } } void FNiagaraScriptMergeManager::DiffFunctionInputs(TSharedRef BaseFunctionAdapter, TSharedRef OtherFunctionAdapter, FNiagaraScriptStackDiffResults& DiffResults) const { FListDiffResults> ListDiffResults = DiffLists, FNiagaraVariableBase>( BaseFunctionAdapter->GetInputOverrides(), OtherFunctionAdapter->GetInputOverrides(), [](TSharedRef InputOverrideAdapter) { return FNiagaraVariableBase(InputOverrideAdapter->GetType(), FName(InputOverrideAdapter->GetInputName())); }); DiffResults.RemovedBaseInputOverrides.Append(ListDiffResults.RemovedBaseValues); DiffResults.AddedOtherInputOverrides.Append(ListDiffResults.AddedOtherValues); for (const FCommonValuePair>& CommonValuePair : ListDiffResults.CommonValuePairs) { TOptional FunctionMatch = DoFunctionInputOverridesMatch(CommonValuePair.BaseValue, CommonValuePair.OtherValue); if (FunctionMatch.IsSet()) { if (FunctionMatch.GetValue() == false) { DiffResults.ModifiedBaseInputOverrides.Add(CommonValuePair.BaseValue); DiffResults.ModifiedOtherInputOverrides.Add(CommonValuePair.OtherValue); } } else { DiffResults.AddError(FText::Format(LOCTEXT("FunctionInputDiffFailedFormat", "Failed to diff function inputs. Function name: {0} Input Name: {1}"), FText::FromString(BaseFunctionAdapter->GetFunctionCallNode()->GetFunctionName()), FText::FromString(CommonValuePair.BaseValue->GetInputName()))); } } } void FNiagaraScriptMergeManager::DiffEditableProperties(const void* BaseDataAddress, const void* OtherDataAddress, UStruct& Struct, TArray& OutDifferentProperties) const { for (TFieldIterator PropertyIterator(&Struct); PropertyIterator; ++PropertyIterator) { if ((PropertyIterator->HasAllPropertyFlags(CPF_Edit) && PropertyIterator->HasMetaData("NiagaraNoMerge") == false)) { if (PropertyIterator->Identical( PropertyIterator->ContainerPtrToValuePtr(BaseDataAddress), PropertyIterator->ContainerPtrToValuePtr(OtherDataAddress), PPF_DeepComparison) == false) { OutDifferentProperties.Add(*PropertyIterator); } } } } void FNiagaraScriptMergeManager::DiffStackEntryDisplayNames(const UNiagaraEmitterEditorData* BaseEditorData, const UNiagaraEmitterEditorData* OtherEditorData, TMap& OutModifiedStackEntryDisplayNames) const { if (BaseEditorData != nullptr && OtherEditorData != nullptr) { // find display names that have been added or changed in the instance const TMap& OtherRenames = OtherEditorData->GetStackEditorData().GetAllStackEntryDisplayNames(); for (auto& Pair : OtherRenames) { const FText* BaseDisplayName = BaseEditorData->GetStackEditorData().GetStackEntryDisplayName(Pair.Key); if (BaseDisplayName == nullptr || !BaseDisplayName->EqualTo(Pair.Value)) { OutModifiedStackEntryDisplayNames.Add(Pair.Key, Pair.Value); } } } } void FNiagaraScriptMergeManager::DiffStackNotes(const UNiagaraEmitterEditorData* BaseEditorData, const UNiagaraEmitterEditorData* OtherEditorData, FNiagaraEmitterDiffResults& DiffResults) const { if (BaseEditorData != nullptr && OtherEditorData != nullptr) { const TMap& OtherStackNotes = OtherEditorData->GetStackEditorData().GetAllStackNotes(); for(const auto& Pair : OtherStackNotes) { // if the other stack notes differ in any way, we will overwrite base with other TOptional BaseStackNote = BaseEditorData->GetStackEditorData().GetStackNote(Pair.Key); if(BaseStackNote.Get(FNiagaraStackNoteData()) != Pair.Value) { DiffResults.AddedOrModifiedStackNotesInOther.Add(Pair.Key, Pair.Value); } } } } void FNiagaraScriptMergeManager::DiffScratchPadScripts(const TArray& BaseScratchPadScripts, const TArray& OtherEmitterScratchPadScripts, FNiagaraEmitterDiffResults& DiffResults) const { if (BaseScratchPadScripts.Num() != OtherEmitterScratchPadScripts.Num()) { DiffResults.bScratchPadModified = true; return; } for (int32 ScratchPadScriptIndex = 0; ScratchPadScriptIndex < BaseScratchPadScripts.Num(); ScratchPadScriptIndex++) { UNiagaraScript* BaseScratchPadScript = BaseScratchPadScripts[ScratchPadScriptIndex]; UNiagaraScript* OtherScratchPadScript = OtherEmitterScratchPadScripts[ScratchPadScriptIndex]; if (!BaseScratchPadScript->GetFName().IsEqual(OtherScratchPadScript->GetFName(), ENameCase::IgnoreCase, false) || BaseScratchPadScript->GetBaseChangeID() != OtherScratchPadScript->GetBaseChangeID()) { DiffResults.bScratchPadModified = true; return; } } } TOptional FNiagaraScriptMergeManager::DoFunctionInputOverridesMatch(TSharedRef BaseFunctionInputAdapter, TSharedRef OtherFunctionInputAdapter) const { // Local String Value. if ((BaseFunctionInputAdapter->GetLocalValueString().IsSet() && OtherFunctionInputAdapter->GetLocalValueString().IsSet() == false) || (BaseFunctionInputAdapter->GetLocalValueString().IsSet() == false && OtherFunctionInputAdapter->GetLocalValueString().IsSet())) { return false; } if (BaseFunctionInputAdapter->GetLocalValueString().IsSet() && OtherFunctionInputAdapter->GetLocalValueString().IsSet()) { return BaseFunctionInputAdapter->GetLocalValueString().GetValue() == OtherFunctionInputAdapter->GetLocalValueString().GetValue(); } // Local rapid iteration parameter value. if ((BaseFunctionInputAdapter->GetLocalValueRapidIterationParameter().IsSet() && OtherFunctionInputAdapter->GetLocalValueRapidIterationParameter().IsSet() == false) || (BaseFunctionInputAdapter->GetLocalValueRapidIterationParameter().IsSet() == false && OtherFunctionInputAdapter->GetLocalValueRapidIterationParameter().IsSet())) { return false; } if (BaseFunctionInputAdapter->GetLocalValueRapidIterationParameter().IsSet() && OtherFunctionInputAdapter->GetLocalValueRapidIterationParameter().IsSet()) { const uint8* BaseRapidIterationParameterValue = BaseFunctionInputAdapter->GetOwningScript()->RapidIterationParameters .GetParameterData(BaseFunctionInputAdapter->GetLocalValueRapidIterationParameter().GetValue()); const uint8* OtherRapidIterationParameterValue = OtherFunctionInputAdapter->GetOwningScript()->RapidIterationParameters .GetParameterData(OtherFunctionInputAdapter->GetLocalValueRapidIterationParameter().GetValue()); if (BaseRapidIterationParameterValue == nullptr || OtherRapidIterationParameterValue == nullptr) { return TOptional(); } return FMemory::Memcmp( BaseRapidIterationParameterValue, OtherRapidIterationParameterValue, BaseFunctionInputAdapter->GetLocalValueRapidIterationParameter().GetValue().GetSizeInBytes()) == 0; } // Linked value if ((BaseFunctionInputAdapter->GetLinkedValueData().IsSet() && OtherFunctionInputAdapter->GetLinkedValueData().IsSet() == false) || (BaseFunctionInputAdapter->GetLinkedValueData().IsSet() == false && OtherFunctionInputAdapter->GetLinkedValueData().IsSet())) { return false; } if (BaseFunctionInputAdapter->GetLinkedValueData().IsSet() && OtherFunctionInputAdapter->GetLinkedValueData().IsSet()) { return BaseFunctionInputAdapter->GetLinkedValueData().GetValue() == OtherFunctionInputAdapter->GetLinkedValueData().GetValue(); } // Data value if ((BaseFunctionInputAdapter->GetDataInterfaceValueInputName().IsSet() && OtherFunctionInputAdapter->GetDataInterfaceValueInputName().IsSet() == false) || (BaseFunctionInputAdapter->GetDataInterfaceValueInputName().IsSet() == false && OtherFunctionInputAdapter->GetDataInterfaceValueInputName().IsSet()) || (BaseFunctionInputAdapter->GetDataInterfaceValue() != nullptr && OtherFunctionInputAdapter->GetDataInterfaceValue() == nullptr) || (BaseFunctionInputAdapter->GetDataInterfaceValue() == nullptr && OtherFunctionInputAdapter->GetDataInterfaceValue() != nullptr)) { return false; } if (BaseFunctionInputAdapter->GetDataInterfaceValueInputName().IsSet() && OtherFunctionInputAdapter->GetDataInterfaceValueInputName().IsSet() && BaseFunctionInputAdapter->GetDataInterfaceValue() != nullptr && OtherFunctionInputAdapter->GetDataInterfaceValue() != nullptr) { return BaseFunctionInputAdapter->GetDataInterfaceValueInputName().GetValue() == OtherFunctionInputAdapter->GetDataInterfaceValueInputName().GetValue() && BaseFunctionInputAdapter->GetDataInterfaceValue()->Equals(OtherFunctionInputAdapter->GetDataInterfaceValue()); } // Object Reference if (BaseFunctionInputAdapter->GetObjectAssetInputVariable().IsSet() != OtherFunctionInputAdapter->GetObjectAssetInputVariable().IsSet()) { return false; } if (BaseFunctionInputAdapter->GetObjectAssetInputVariable().IsSet() && OtherFunctionInputAdapter->GetObjectAssetInputVariable().IsSet()) { return BaseFunctionInputAdapter->GetObjectAssetInputVariable().GetValue() == OtherFunctionInputAdapter->GetObjectAssetInputVariable().GetValue() && BaseFunctionInputAdapter->GetObjectAssetValue() == OtherFunctionInputAdapter->GetObjectAssetValue(); } // Dynamic value if ((BaseFunctionInputAdapter->GetDynamicValueFunction().IsValid() && OtherFunctionInputAdapter->GetDynamicValueFunction().IsValid() == false) || (BaseFunctionInputAdapter->GetDynamicValueFunction().IsValid() == false && OtherFunctionInputAdapter->GetDynamicValueFunction().IsValid())) { return false; } if (BaseFunctionInputAdapter->GetDynamicValueFunction().IsValid() && OtherFunctionInputAdapter->GetDynamicValueFunction().IsValid()) { TSharedRef BaseDynamicValueFunction = BaseFunctionInputAdapter->GetDynamicValueFunction().ToSharedRef(); TSharedRef OtherDynamicValueFunction = OtherFunctionInputAdapter->GetDynamicValueFunction().ToSharedRef(); UNiagaraNodeCustomHlsl* BaseCustomHlsl = Cast(BaseDynamicValueFunction->GetFunctionCallNode()); UNiagaraNodeCustomHlsl* OtherCustomHlsl = Cast(OtherDynamicValueFunction->GetFunctionCallNode()); if (BaseCustomHlsl != nullptr || OtherCustomHlsl != nullptr) { if ((BaseCustomHlsl != nullptr && OtherCustomHlsl == nullptr) || (BaseCustomHlsl == nullptr && OtherCustomHlsl != nullptr)) { return false; } if (BaseCustomHlsl->GetCustomHlsl() != OtherCustomHlsl->GetCustomHlsl() || BaseCustomHlsl->ScriptUsage != OtherCustomHlsl->ScriptUsage) { return false; } } else if (BaseDynamicValueFunction->GetUsesScratchPadScript() || OtherDynamicValueFunction->GetUsesScratchPadScript()) { if ((BaseDynamicValueFunction->GetUsesScratchPadScript() && OtherDynamicValueFunction->GetUsesScratchPadScript() == false) || (BaseDynamicValueFunction->GetUsesScratchPadScript() == false && OtherDynamicValueFunction->GetUsesScratchPadScript())) { return false; } if (BaseDynamicValueFunction->GetFunctionCallNode()->NodeGuid != OtherDynamicValueFunction->GetFunctionCallNode()->NodeGuid) { return false; } } else if (BaseDynamicValueFunction->GetFunctionCallNode()->FunctionScript != OtherDynamicValueFunction->GetFunctionCallNode()->FunctionScript) { return false; } FNiagaraScriptStackDiffResults FunctionDiffResults; DiffFunctionInputs(BaseDynamicValueFunction, OtherDynamicValueFunction, FunctionDiffResults); return FunctionDiffResults.RemovedBaseInputOverrides.Num() == 0 && FunctionDiffResults.AddedOtherInputOverrides.Num() == 0 && FunctionDiffResults.ModifiedOtherInputOverrides.Num() == 0; } // Static switch if (BaseFunctionInputAdapter->GetStaticSwitchValue().IsSet() && OtherFunctionInputAdapter->GetStaticSwitchValue().IsSet()) { return BaseFunctionInputAdapter->GetStaticSwitchValue().GetValue() == OtherFunctionInputAdapter->GetStaticSwitchValue().GetValue(); } return TOptional(); } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddModule( TSharedRef BaseEmitterAdapter, TSharedRef ScratchPadAdapter, UNiagaraScript& OwningScript, UNiagaraNodeOutput& TargetOutputNode, TSharedRef AddModule) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::AddModule); FApplyDiffResults Results; UNiagaraNodeFunctionCall* AddedModuleNode = nullptr; if (AddModule->GetFunctionCallNode()->IsA()) { UNiagaraNodeAssignment* AssignmentNode = CastChecked(AddModule->GetFunctionCallNode()); const TArray& Targets = AssignmentNode->GetAssignmentTargets(); const TArray& Defaults = AssignmentNode->GetAssignmentDefaults(); AddedModuleNode = FNiagaraStackGraphUtilities::AddParameterModuleToStack(Targets, TargetOutputNode, AddModule->GetStackIndex(),Defaults); AddedModuleNode->NodeGuid = AddModule->GetFunctionCallNode()->NodeGuid; AddedModuleNode->RefreshFromExternalChanges(); Results.bModifiedGraph = true; } else { UNiagaraScript* FunctionScript = nullptr; FGuid VersionGuid; if (AddModule->GetUsesScratchPadScript()) { FunctionScript = ScratchPadAdapter->GetScratchPadScriptForFunctionId(AddModule->GetFunctionCallNode()->NodeGuid); } else { FunctionScript = AddModule->GetFunctionCallNode()->FunctionScript; VersionGuid = AddModule->GetFunctionCallNode()->SelectedScriptVersion; } AddedModuleNode = FNiagaraStackGraphUtilities::AddScriptModuleToStack(FunctionScript, TargetOutputNode, AddModule->GetStackIndex(), AddModule->GetFunctionCallNode()->GetFunctionName(), VersionGuid); AddedModuleNode->NodeGuid = AddModule->GetFunctionCallNode()->NodeGuid; // Synchronize the node Guid across runs so that the compile id's sync up. Results.bModifiedGraph = true; } if (AddedModuleNode != nullptr) { AddedModuleNode->NodeGuid = AddModule->GetFunctionCallNode()->NodeGuid; // Synchronize the node Guid across runs so that the compile id's sync up. AddedModuleNode->SetEnabledState(AddModule->GetFunctionCallNode()->GetDesiredEnabledState(), AddModule->GetFunctionCallNode()->HasUserSetTheEnabledState()); for (TSharedRef InputOverride : AddModule->GetInputOverrides()) { FApplyDiffResults AddInputResults = AddInputOverride(BaseEmitterAdapter, ScratchPadAdapter, OwningScript, *AddedModuleNode, InputOverride); Results.bSucceeded &= AddInputResults.bSucceeded; Results.bModifiedGraph |= AddInputResults.bModifiedGraph; Results.ErrorMessages.Append(AddInputResults.ErrorMessages); } } else { Results.bSucceeded = false; Results.ErrorMessages.Add(LOCTEXT("AddModuleFailed", "Failed to add module from diff.")); } return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::RemoveInputOverride(UNiagaraScript& OwningScript, TSharedRef OverrideToRemove) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::RemoveInputOverride); FApplyDiffResults Results; Results.bSucceeded = true; Results.bModifiedGraph = false; // If there is a dynamic input we need to call remove recursively to make sure that all rapid iteration parameters are removed. if (OverrideToRemove->GetDynamicValueFunction().IsValid()) { for (TSharedRef DynamicValueInputOverrideToRemove : OverrideToRemove->GetDynamicValueFunction()->GetInputOverrides()) { FApplyDiffResults DynamicValueInputResults = RemoveInputOverride(OwningScript, DynamicValueInputOverrideToRemove); Results.bSucceeded &= DynamicValueInputResults.bSucceeded; Results.bModifiedGraph |= DynamicValueInputResults.bModifiedGraph; Results.ErrorMessages.Append(DynamicValueInputResults.ErrorMessages); } } if (OverrideToRemove->GetOverridePin() != nullptr && OverrideToRemove->GetOverrideNode() != nullptr) { FNiagaraStackGraphUtilities::RemoveNodesForStackFunctionInputOverridePin(*OverrideToRemove->GetOverridePin()); OverrideToRemove->GetOverrideNode()->RemovePin(OverrideToRemove->GetOverridePin()); Results.bModifiedGraph = true; } else if (OverrideToRemove->GetLocalValueRapidIterationParameter().IsSet()) { OwningScript.Modify(); OwningScript.RapidIterationParameters.RemoveParameter(OverrideToRemove->GetLocalValueRapidIterationParameter().GetValue()); } else if (OverrideToRemove->GetStaticSwitchValue().IsSet()) { const UEdGraphSchema_Niagara* Schema = GetDefault(); UEdGraphPin** StaticSwitchPinPtr = OverrideToRemove->GetOwningFunctionCall()->Pins.FindByPredicate([&OverrideToRemove, &Schema](UEdGraphPin* Pin) { if (Pin->Direction != EGPD_Input) { return false; } FNiagaraVariable PinVariable = Schema->PinToNiagaraVariable(Pin); return PinVariable.GetName() == *OverrideToRemove->GetInputName() && PinVariable.GetType() == OverrideToRemove->GetType(); }); if (StaticSwitchPinPtr != nullptr && (*StaticSwitchPinPtr)->AutogeneratedDefaultValue.IsEmpty() == false) { (*StaticSwitchPinPtr)->DefaultValue = (*StaticSwitchPinPtr)->AutogeneratedDefaultValue; Results.bModifiedGraph = true; } } else { Results.bSucceeded = false; Results.ErrorMessages.Add(LOCTEXT("RemoveInputOverrideFailed", "Failed to remove input override because it was invalid.")); } return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInputOverride( TSharedRef BaseEmitterAdapter, TSharedRef ScratchPadAdapter, UNiagaraScript& OwningScript, UNiagaraNodeFunctionCall& TargetFunctionCall, TSharedRef OverrideToAdd) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::AddInputOverride); FApplyDiffResults Results; // If an assignment node, make sure that we have an assignment target for the input override. UNiagaraNodeAssignment* AssignmentNode = Cast(&TargetFunctionCall); if (AssignmentNode) { FNiagaraParameterHandle FunctionInputHandle(FNiagaraConstants::ModuleNamespace, *OverrideToAdd->GetInputName()); UNiagaraNodeAssignment* PreviousVersionAssignmentNode = Cast(OverrideToAdd->GetOwningFunctionCall()); bool bAnyAdded = false; for (int32 i = 0; i < PreviousVersionAssignmentNode->NumTargets(); i++) { const FNiagaraVariable& Var = PreviousVersionAssignmentNode->GetAssignmentTarget(i); int32 FoundVarIdx = AssignmentNode->FindAssignmentTarget(Var.GetName()); if (FoundVarIdx == INDEX_NONE) { AssignmentNode->AddAssignmentTarget(Var, &PreviousVersionAssignmentNode->GetAssignmentDefaults()[i]); bAnyAdded = true; } } if (bAnyAdded) { AssignmentNode->RefreshFromExternalChanges(); } } FNiagaraParameterHandle FunctionInputHandle(FNiagaraConstants::ModuleNamespace, *OverrideToAdd->GetInputName()); FNiagaraParameterHandle AliasedFunctionInputHandle = FNiagaraParameterHandle::CreateAliasedModuleParameterHandle(FunctionInputHandle, &TargetFunctionCall); if (OverrideToAdd->GetOverridePin() != nullptr) { const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); FNiagaraTypeDefinition InputType = NiagaraSchema->PinToTypeDefinition(OverrideToAdd->GetOverridePin()); FNiagaraVariable InputVariable(InputType, FunctionInputHandle.GetParameterHandleString()); TOptional InputMetaData = TargetFunctionCall.GetNiagaraGraph()->GetMetaData(InputVariable); FGuid InputVariableGuid = InputMetaData.IsSet() ? InputMetaData->GetVariableGuid() : FGuid(); UEdGraphPin& InputOverridePin = FNiagaraStackGraphUtilities::GetOrCreateStackFunctionInputOverridePin(TargetFunctionCall, AliasedFunctionInputHandle, InputType, InputVariableGuid, OverrideToAdd->GetOverrideNode()->NodeGuid); if (InputOverridePin.LinkedTo.Num() != 0) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("AddPinBasedInputOverrideFailedOverridePinStillLinkedFormat", "Failed to add input override because the target override pin was still linked to other nodes. Target Script Usage: {0} Target Script Usage Id: {1} Target Node: {2} Target Input Handle: {3} Linked Node: {4} Linked Pin: {5}"), FNiagaraTypeDefinition::GetScriptUsageEnum()->GetDisplayNameTextByValue((int64)OwningScript.GetUsage()), FText::FromString(OwningScript.GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)), FText::FromString(TargetFunctionCall.GetFunctionName()), FText::FromName(AliasedFunctionInputHandle.GetParameterHandleString()), InputOverridePin.LinkedTo[0] != nullptr && InputOverridePin.LinkedTo[0]->GetOwningNode() != nullptr ? InputOverridePin.LinkedTo[0]->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView) : FText::FromString(TEXT("(null)")), InputOverridePin.LinkedTo[0] != nullptr ? FText::FromName(InputOverridePin.LinkedTo[0]->PinName) : FText::FromString(TEXT("(null)")))); } else { if (OverrideToAdd->GetLocalValueString().IsSet()) { InputOverridePin.DefaultValue = OverrideToAdd->GetLocalValueString().GetValue(); Results.bSucceeded = true; } else if (OverrideToAdd->GetLinkedValueData().IsSet()) { check(OverrideToAdd->GetOverrideNode() && OverrideToAdd->GetOverrideNode()->GetNiagaraGraph()); FNiagaraVariableBase OldLinkedParameter = OverrideToAdd->GetLinkedValueData()->LinkedParameterValue; FNiagaraParameterHandle OldLinkedParameterHandle = FNiagaraParameterHandle(OldLinkedParameter.GetName()); FGuid LinkedFunctionCallNodeId = OverrideToAdd->GetLinkedValueData()->LinkedFunctionNodeId; UNiagaraScriptVariable* ScriptVar = OverrideToAdd->GetOverrideNode()->GetNiagaraGraph()->GetScriptVariable(OldLinkedParameter); ENiagaraDefaultMode DesiredMode = ENiagaraDefaultMode::Value; if (ScriptVar) DesiredMode = ScriptVar->DefaultMode; if (GNiagaraForceFailIfPreviouslyNotSetOnMerge > 0) DesiredMode = ENiagaraDefaultMode::FailIfPreviouslyNotSet; // If the linked value handle has a valid function call node id, then we need to check to see if that function call node has been // renamed due to the merge, and if so we need to update the handle. FNiagaraVariableBase NewLinkedParameter; if (LinkedFunctionCallNodeId.IsValid() && OldLinkedParameterHandle.GetHandleParts().Num() >= 3 && ( OldLinkedParameterHandle.IsOutputHandle() || OldLinkedParameterHandle.IsStackContextHandle() || OldLinkedParameterHandle.IsEmitterHandle() || OldLinkedParameterHandle.IsParticleAttributeHandle())) { TObjectPtr* ReferencedFunctionCallNodePtr = TargetFunctionCall.GetGraph()->Nodes.FindByPredicate( [&LinkedFunctionCallNodeId](UEdGraphNode* Node) { return Node->IsA() && Node->NodeGuid == LinkedFunctionCallNodeId; }); if (ReferencedFunctionCallNodePtr != nullptr) { UNiagaraNodeFunctionCall* ReferencedFunctionCallNode = CastChecked(*ReferencedFunctionCallNodePtr); FString FunctionName = OldLinkedParameterHandle.GetHandleParts()[1].ToString(); if (ReferencedFunctionCallNode->GetFunctionName() != FunctionName) { // The function node has been renamed so we need to update the handle. The handle is in the format [Namespace].[Function Call Name].[NameParts] TArray OldHandleParts = OldLinkedParameterHandle.GetHandleParts(); TArray NewHandleNameParts; NewHandleNameParts.Add(OldHandleParts[0].ToString()); NewHandleNameParts.Add(ReferencedFunctionCallNode->GetFunctionName()); for (int32 i = 2; i < OldHandleParts.Num(); i++) { NewHandleNameParts.Add(OldHandleParts[i].ToString()); } NewLinkedParameter = FNiagaraVariableBase(OldLinkedParameter.GetType(), *FString::Join(NewHandleNameParts, TEXT("."))); } } } if (NewLinkedParameter.IsValid() == false) { NewLinkedParameter = OldLinkedParameter; } TSet KnownParameters = { NewLinkedParameter }; FNiagaraStackGraphUtilities::SetLinkedParameterValueForFunctionInput(InputOverridePin, NewLinkedParameter, KnownParameters, DesiredMode, OverrideToAdd->GetOverrideNodeId()); FGuid LinkedOutputId = FNiagaraStackGraphUtilities::GetScriptVariableIdForLinkedModuleParameter(NewLinkedParameter, *TargetFunctionCall.GetNiagaraGraph()); if (LinkedOutputId.IsValid()) { TargetFunctionCall.UpdateInputNameBinding(LinkedOutputId, NewLinkedParameter.GetName()); } Results.bSucceeded = true; } else if (OverrideToAdd->GetDataInterfaceValueInputName().IsSet() && OverrideToAdd->GetDataInterfaceValue() != nullptr) { FName OverrideValueInputName = OverrideToAdd->GetDataInterfaceValueInputName().GetValue(); UNiagaraDataInterface* OverrideValueObject = OverrideToAdd->GetDataInterfaceValue(); UNiagaraDataInterface* NewOverrideValueObject; FNiagaraStackGraphUtilities::SetDataInterfaceValueForFunctionInput(InputOverridePin, OverrideToAdd->GetDataInterfaceValue()->GetClass(), OverrideValueInputName.ToString(), NewOverrideValueObject, OverrideToAdd->GetOverrideNodeId()); OverrideValueObject->CopyTo(NewOverrideValueObject); Results.bSucceeded = true; } else if (OverrideToAdd->GetObjectAssetInputVariable().IsSet()) { FNiagaraVariableBase OverrideVariable = OverrideToAdd->GetObjectAssetInputVariable().GetValue(); UObject* OverrideObjectValue = OverrideToAdd->GetObjectAssetValue(); FNiagaraStackGraphUtilities::SetObjectAssetValueForFunctionInput(InputOverridePin, OverrideVariable.GetType().GetClass(), OverrideVariable.GetName().ToString(), OverrideObjectValue); Results.bSucceeded = true; } else if (OverrideToAdd->GetDynamicValueFunction().IsValid()) { UNiagaraNodeCustomHlsl* CustomHlslFunction = Cast(OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()); if (CustomHlslFunction != nullptr) { UNiagaraNodeCustomHlsl* DynamicInputFunctionCall; FNiagaraStackGraphUtilities::SetCustomExpressionForFunctionInput(InputOverridePin, CustomHlslFunction->GetCustomHlsl(), DynamicInputFunctionCall, OverrideToAdd->GetOverrideNodeId()); for (TSharedRef DynamicInputInputOverride : OverrideToAdd->GetDynamicValueFunction()->GetInputOverrides()) { FApplyDiffResults AddResults = AddInputOverride(BaseEmitterAdapter, ScratchPadAdapter, OwningScript, *((UNiagaraNodeFunctionCall*)DynamicInputFunctionCall), DynamicInputInputOverride); Results.bSucceeded &= AddResults.bSucceeded; Results.bModifiedGraph |= AddResults.bModifiedGraph; Results.ErrorMessages.Append(AddResults.ErrorMessages); } } else if (OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()) { UNiagaraScript* FunctionScript = nullptr; if (OverrideToAdd->GetDynamicValueFunction()->GetUsesScratchPadScript()) { FunctionScript = ScratchPadAdapter->GetScratchPadScriptForFunctionId(OverrideToAdd->GetOverrideNodeId()); } else { FunctionScript = OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->FunctionScript; } UNiagaraNodeFunctionCall* DynamicInputFunctionCall; FNiagaraStackGraphUtilities::SetDynamicInputForFunctionInput(InputOverridePin, FunctionScript, DynamicInputFunctionCall, OverrideToAdd->GetOverrideNodeId(), OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->GetFunctionName(), OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->SelectedScriptVersion); for (TSharedRef DynamicInputInputOverride : OverrideToAdd->GetDynamicValueFunction()->GetInputOverrides()) { FApplyDiffResults AddResults = AddInputOverride(BaseEmitterAdapter, ScratchPadAdapter, OwningScript, *DynamicInputFunctionCall, DynamicInputInputOverride); Results.bSucceeded &= AddResults.bSucceeded; Results.bModifiedGraph |= AddResults.bModifiedGraph; Results.ErrorMessages.Append(AddResults.ErrorMessages); } } } else { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format(LOCTEXT("AddPinBasedInputOverrideFailed", "Failed to add input override {0} to function {1} because it was invalid."), FText::FromString(OverrideToAdd->GetInputName()), FText::FromString(TargetFunctionCall.GetFunctionName()))); } } Results.bModifiedGraph = true; } else { if (OverrideToAdd->GetLocalValueRapidIterationParameter().IsSet()) { FNiagaraVariable RapidIterationParameter = FNiagaraStackGraphUtilities::CreateRapidIterationParameter( BaseEmitterAdapter->GetEditableEmitter().Emitter->GetUniqueEmitterName(), OwningScript.GetUsage(), AliasedFunctionInputHandle.GetParameterHandleString(), OverrideToAdd->GetLocalValueRapidIterationParameter().GetValue().GetType()); const uint8* SourceData = OverrideToAdd->GetOwningScript()->RapidIterationParameters.GetParameterData(OverrideToAdd->GetLocalValueRapidIterationParameter().GetValue()); OwningScript.Modify(); bool bAddParameterIfMissing = true; OwningScript.RapidIterationParameters.SetParameterData(SourceData, RapidIterationParameter, bAddParameterIfMissing); Results.bSucceeded = true; } else if (OverrideToAdd->GetStaticSwitchValue().IsSet()) { TArray StaticSwitchPins; TSet StaticSwitchPinsHidden; FCompileConstantResolver ConstantResolver(BaseEmitterAdapter->GetEditableEmitter(), OwningScript.GetUsage()); FNiagaraStackGraphUtilities::GetStackFunctionStaticSwitchPins(TargetFunctionCall, StaticSwitchPins, StaticSwitchPinsHidden, ConstantResolver); UEdGraphPin** MatchingStaticSwitchPinPtr = StaticSwitchPins.FindByPredicate([&OverrideToAdd](UEdGraphPin* StaticSwitchPin) { return StaticSwitchPin->PinName == *OverrideToAdd->GetInputName(); }); if (MatchingStaticSwitchPinPtr != nullptr) { UEdGraphPin* MatchingStaticSwitchPin = *MatchingStaticSwitchPinPtr; const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); FNiagaraTypeDefinition SwitchType = NiagaraSchema->PinToTypeDefinition(MatchingStaticSwitchPin); if (SwitchType == OverrideToAdd->GetType()) { MatchingStaticSwitchPin->DefaultValue = OverrideToAdd->GetStaticSwitchValue().GetValue(); TargetFunctionCall.MarkNodeRequiresSynchronization(TEXT("Static Switch Value Changed"), true); Results.bModifiedGraph = true; Results.bSucceeded = true; } else { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format(LOCTEXT("AddStaticInputOverrideFailedWrongTypeFormat", "Failed to add static switch input override {0} to function {1} because a the type of the pin matched by name did not match."), FText::FromString(OverrideToAdd->GetInputName()), FText::FromString(TargetFunctionCall.GetFunctionName()))); } } else { FNiagaraVariableBase StaticSwitchVariable(OverrideToAdd->GetType(), *OverrideToAdd->GetInputName()); TargetFunctionCall.AddOrphanedStaticSwitchPinForDataRetention(StaticSwitchVariable, OverrideToAdd->GetStaticSwitchValue().GetValue()); Results.bSucceeded = true; } } else { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format(LOCTEXT("AddParameterBasedInputOverrideFailedFormat", "Failed to add input override {0} to function {1} because it was invalid."), FText::FromString(OverrideToAdd->GetInputName()), FText::FromString(TargetFunctionCall.GetFunctionName()))); } Results.bModifiedGraph = false; } return Results; } void FNiagaraScriptMergeManager::CopyPropertiesToBase(void* BaseDataAddress, const void* OtherDataAddress, TArray PropertiesToCopy) const { for (FProperty* PropertyToCopy : PropertiesToCopy) { PropertyToCopy->CopyCompleteValue(PropertyToCopy->ContainerPtrToValuePtr(BaseDataAddress), PropertyToCopy->ContainerPtrToValuePtr(OtherDataAddress)); } } void FNiagaraScriptMergeManager::CopyInstanceScratchPadScripts(const FVersionedNiagaraEmitter& MergedInstance, FVersionedNiagaraEmitterData* SourceInstance) const { TObjectPtr ScratchPadContainer = MergedInstance.GetEmitterData()->ScratchPads; for (UNiagaraScript* SourceScratchPadScript : SourceInstance->ScratchPads->Scripts) { FName UniqueObjectName = FNiagaraEditorUtilities::GetUniqueObjectName(ScratchPadContainer, SourceScratchPadScript->GetName()); UNiagaraScript* MergedInstanceScratchPadScript = CastChecked(StaticDuplicateObject(SourceScratchPadScript, ScratchPadContainer, UniqueObjectName)); ScratchPadContainer->Scripts.Add(MergedInstanceScratchPadScript); } } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyScriptStackDiff( TSharedRef BaseEmitterAdapter, TSharedRef BaseScriptStackAdapter, TSharedRef ScratchPadAdapter, const FNiagaraScriptStackDiffResults& DiffResults, const bool bNoParentAtLastMerge) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::ApplyScriptStackDiff); FApplyDiffResults Results; if (DiffResults.IsEmpty()) { Results.bSucceeded = true; Results.bModifiedGraph = false; return Results; } struct FAddInputOverrideActionData { UNiagaraNodeFunctionCall* TargetFunctionCall; TSharedPtr OverrideToAdd; }; // Collect the graph actions from the adapter and diff first. TArray> RemoveModules; TArray> AddModules; TArray> RemoveInputOverrides; TArray AddInputOverrideActionDatas; TArray> EnableModules; TArray> DisableModules; for (TSharedRef RemovedModule : DiffResults.RemovedBaseModules) { TSharedPtr MatchingModuleAdapter = BaseScriptStackAdapter->GetModuleFunctionById(RemovedModule->GetFunctionCallNode()->NodeGuid); if (MatchingModuleAdapter.IsValid()) { if (bNoParentAtLastMerge) { // If there is no last known parent we don't know if the module was removed in the child, or added in the parent, so // instead of removing the parent module we disable it in this case, since removing modules in child emitters isn't // supported through the UI. DisableModules.Add(MatchingModuleAdapter.ToSharedRef()); } else { RemoveModules.Add(MatchingModuleAdapter.ToSharedRef()); } } } AddModules.Append(DiffResults.AddedOtherModules); for (TSharedRef RemovedInputOverrideAdapter : DiffResults.RemovedBaseInputOverrides) { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraStackGraphUtilities::RemovedInputOverrideAdapter); TSharedPtr MatchingModuleAdapter = BaseScriptStackAdapter->GetModuleFunctionById(RemovedInputOverrideAdapter->GetOwningFunctionCall()->NodeGuid); if (MatchingModuleAdapter.IsValid()) { TSharedPtr MatchingInputOverrideAdapter = MatchingModuleAdapter->GetInputOverrideByInputNameAndType(RemovedInputOverrideAdapter->GetInputName(), RemovedInputOverrideAdapter->GetType()); if (MatchingInputOverrideAdapter.IsValid()) { RemoveInputOverrides.Add(MatchingInputOverrideAdapter.ToSharedRef()); } } } for (TSharedRef AddedInputOverrideAdapter : DiffResults.AddedOtherInputOverrides) { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraStackGraphUtilities::AddedInputOverrideAdapter); TSharedPtr MatchingModuleAdapter = BaseScriptStackAdapter->GetModuleFunctionById(AddedInputOverrideAdapter->GetOwningFunctionCall()->NodeGuid); if (MatchingModuleAdapter.IsValid()) { TSharedPtr MatchingInputOverrideAdapter = MatchingModuleAdapter->GetInputOverrideByInputNameAndType(AddedInputOverrideAdapter->GetInputName(), AddedInputOverrideAdapter->GetType()); if (MatchingInputOverrideAdapter.IsValid()) { RemoveInputOverrides.AddUnique(MatchingInputOverrideAdapter.ToSharedRef()); } FAddInputOverrideActionData AddInputOverrideActionData; AddInputOverrideActionData.TargetFunctionCall = MatchingModuleAdapter->GetFunctionCallNode(); AddInputOverrideActionData.OverrideToAdd = AddedInputOverrideAdapter; AddInputOverrideActionDatas.Add(AddInputOverrideActionData); } } for (TSharedRef ModifiedInputOverrideAdapter : DiffResults.ModifiedOtherInputOverrides) { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraStackGraphUtilities::ModifiedInputOverrideAdapter); TSharedPtr MatchingModuleAdapter = BaseScriptStackAdapter->GetModuleFunctionById(ModifiedInputOverrideAdapter->GetOwningFunctionCall()->NodeGuid); if (MatchingModuleAdapter.IsValid()) { TSharedPtr MatchingInputOverrideAdapter = MatchingModuleAdapter->GetInputOverrideByInputNameAndType(ModifiedInputOverrideAdapter->GetInputName(), ModifiedInputOverrideAdapter->GetType()); if (MatchingInputOverrideAdapter.IsValid()) { RemoveInputOverrides.AddUnique(MatchingInputOverrideAdapter.ToSharedRef()); } FAddInputOverrideActionData AddInputOverrideActionData; AddInputOverrideActionData.TargetFunctionCall = MatchingModuleAdapter->GetFunctionCallNode(); AddInputOverrideActionData.OverrideToAdd = ModifiedInputOverrideAdapter; AddInputOverrideActionDatas.Add(AddInputOverrideActionData); } } for (TSharedRef EnabledChangedModule : DiffResults.EnabledChangedOtherModules) { TSharedPtr MatchingModuleAdapter = BaseScriptStackAdapter->GetModuleFunctionById(EnabledChangedModule->GetFunctionCallNode()->NodeGuid); if (MatchingModuleAdapter.IsValid()) { if (EnabledChangedModule->GetFunctionCallNode()->IsNodeEnabled()) { EnableModules.Add(MatchingModuleAdapter.ToSharedRef()); } else { DisableModules.Add(MatchingModuleAdapter.ToSharedRef()); } } } // Update the usage if different if (DiffResults.ChangedOtherUsage.IsSet()) { BaseScriptStackAdapter->GetScript()->SetUsage(DiffResults.ChangedOtherUsage.GetValue()); BaseScriptStackAdapter->GetOutputNode()->SetUsage(DiffResults.ChangedOtherUsage.GetValue()); } // Apply the graph actions. for (TSharedRef RemoveModule : RemoveModules) { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraStackGraphUtilities::RemoveModuleFromStack); bool bRemoveResults = FNiagaraStackGraphUtilities::RemoveModuleFromStack(*BaseScriptStackAdapter->GetScript(), *RemoveModule->GetFunctionCallNode()); if (bRemoveResults == false) { Results.bSucceeded = false; Results.ErrorMessages.Add(LOCTEXT("RemoveModuleFailedMessage", "Failed to remove module while applying diff")); } else { Results.bModifiedGraph = true; } } for (TSharedRef AddModuleAdapter : AddModules) { FApplyDiffResults AddModuleResults = AddModule(BaseEmitterAdapter, ScratchPadAdapter, *BaseScriptStackAdapter->GetScript(), *BaseScriptStackAdapter->GetOutputNode(), AddModuleAdapter); Results.bSucceeded &= AddModuleResults.bSucceeded; Results.bModifiedGraph |= AddModuleResults.bModifiedGraph; Results.ErrorMessages.Append(AddModuleResults.ErrorMessages); } for (TSharedRef RemoveInputOverrideItem : RemoveInputOverrides) { FApplyDiffResults RemoveInputOverrideResults = RemoveInputOverride(*BaseScriptStackAdapter->GetScript(), RemoveInputOverrideItem); Results.bSucceeded &= RemoveInputOverrideResults.bSucceeded; Results.bModifiedGraph |= RemoveInputOverrideResults.bModifiedGraph; Results.ErrorMessages.Append(RemoveInputOverrideResults.ErrorMessages); } for (const FAddInputOverrideActionData& AddInputOverrideActionData : AddInputOverrideActionDatas) { FApplyDiffResults AddInputOverrideResults = AddInputOverride(BaseEmitterAdapter, ScratchPadAdapter, *BaseScriptStackAdapter->GetScript(), *AddInputOverrideActionData.TargetFunctionCall, AddInputOverrideActionData.OverrideToAdd.ToSharedRef()); Results.bSucceeded &= AddInputOverrideResults.bSucceeded; Results.bModifiedGraph |= AddInputOverrideResults.bModifiedGraph; Results.ErrorMessages.Append(AddInputOverrideResults.ErrorMessages); } // Apply enabled state last so that it applies to function calls added from input overrides; TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::SetModuleIsEnabled); for (TSharedRef EnableModule : EnableModules) { FNiagaraStackGraphUtilities::SetModuleIsEnabled(*EnableModule->GetFunctionCallNode(), true); } for (TSharedRef DisableModule : DisableModules) { FNiagaraStackGraphUtilities::SetModuleIsEnabled(*DisableModule->GetFunctionCallNode(), false); } return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyEventHandlerDiff( TSharedRef BaseEmitterAdapter, TSharedRef ScratchPadAdapter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::ApplyEventHandlerDiff); FApplyDiffResults Results; if (DiffResults.RemovedBaseEventHandlers.Num() > 0) { // If this becomes supported, it needs to handle the bNoParentAtLastMerge case Results.bSucceeded = false; Results.bModifiedGraph = false; Results.ErrorMessages.Add(LOCTEXT("RemovedEventHandlersUnsupported", "Apply diff failed, removed event handlers are currently unsupported.")); return Results; } // Apply the modifications first since adding new event handlers may invalidate the adapter. for (const FNiagaraModifiedEventHandlerDiffResults& ModifiedEventHandler : DiffResults.ModifiedEventHandlers) { if (ModifiedEventHandler.OtherAdapter->GetEventScriptProperties() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingModifiedEventPropertiesFormat", "Apply diff failed. The modified event handler with id: {0} was missing it's event properties."), FText::FromString(ModifiedEventHandler.OtherAdapter->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else if (ModifiedEventHandler.OtherAdapter->GetOutputNode() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingModifiedEventOutputNodeFormat", "Apply diff failed. The modified event handler with id: {0} was missing it's output node."), FText::FromString(ModifiedEventHandler.OtherAdapter->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else { TSharedPtr MatchingBaseEventHandlerAdapter = BaseEmitterAdapter->GetEventHandler(ModifiedEventHandler.OtherAdapter->GetUsageId()); if (MatchingBaseEventHandlerAdapter.IsValid()) { if (ModifiedEventHandler.ChangedProperties.Num() > 0) { CopyPropertiesToBase(MatchingBaseEventHandlerAdapter->GetEditableEventScriptProperties(), ModifiedEventHandler.OtherAdapter->GetEditableEventScriptProperties(), ModifiedEventHandler.ChangedProperties); } if (ModifiedEventHandler.ScriptDiffResults.IsEmpty() == false) { FApplyDiffResults ApplyEventHandlerStackDiffResults = ApplyScriptStackDiff( BaseEmitterAdapter, MatchingBaseEventHandlerAdapter->GetEventStack().ToSharedRef(), ScratchPadAdapter, ModifiedEventHandler.ScriptDiffResults, bNoParentAtLastMerge); Results.bSucceeded &= ApplyEventHandlerStackDiffResults.bSucceeded; Results.bModifiedGraph |= ApplyEventHandlerStackDiffResults.bModifiedGraph; Results.ErrorMessages.Append(ApplyEventHandlerStackDiffResults.ErrorMessages); } } } } UNiagaraScriptSource* EmitterSource = CastChecked(BaseEmitterAdapter->GetEditableEmitter().GetEmitterData()->GraphSource); UNiagaraGraph* EmitterGraph = EmitterSource->NodeGraph; for (TSharedRef AddedEventHandler : DiffResults.AddedOtherEventHandlers) { if (AddedEventHandler->GetEventScriptProperties() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingAddedEventPropertiesFormat", "Apply diff failed. The added event handler with id: {0} was missing it's event properties."), FText::FromString(AddedEventHandler->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else if (AddedEventHandler->GetOutputNode() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingAddedEventOutputNodeFormat", "Apply diff failed. The added event handler with id: {0} was missing it's output node."), FText::FromString(AddedEventHandler->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else { FVersionedNiagaraEmitter BaseEmitter = BaseEmitterAdapter->GetEditableEmitter(); FNiagaraEventScriptProperties AddedEventScriptProperties = *AddedEventHandler->GetEventScriptProperties(); AddedEventScriptProperties.Script = NewObject(BaseEmitter.Emitter, MakeUniqueObjectName(BaseEmitter.Emitter, UNiagaraScript::StaticClass(), "EventScript"), RF_Transactional); AddedEventScriptProperties.Script->SetUsage(ENiagaraScriptUsage::ParticleEventScript); AddedEventScriptProperties.Script->SetUsageId(AddedEventHandler->GetUsageId()); AddedEventScriptProperties.Script->SetLatestSource(EmitterSource); BaseEmitter.Emitter->AddEventHandler(AddedEventScriptProperties, BaseEmitter.Version); FGuid PreferredOutputNodeGuid = AddedEventHandler->GetOutputNode()->NodeGuid; FGuid PreferredInputNodeGuid = AddedEventHandler->GetInputNode()->NodeGuid; UNiagaraNodeOutput* EventOutputNode = FNiagaraStackGraphUtilities::ResetGraphForOutput(*EmitterGraph, ENiagaraScriptUsage::ParticleEventScript, AddedEventScriptProperties.Script->GetUsageId(), PreferredOutputNodeGuid, PreferredInputNodeGuid); for (TSharedRef ModuleAdapter : AddedEventHandler->GetEventStack()->GetModuleFunctions()) { FApplyDiffResults AddModuleResults = AddModule(BaseEmitterAdapter, ScratchPadAdapter, *AddedEventScriptProperties.Script, *EventOutputNode, ModuleAdapter); Results.bSucceeded &= AddModuleResults.bSucceeded; Results.ErrorMessages.Append(AddModuleResults.ErrorMessages); } // Force the base compile id of the new event handler to match the added instance event handler. UNiagaraScriptSource* AddedEventScriptSourceFromDiff = Cast(AddedEventHandler->GetEventScriptProperties()->Script->GetLatestSource()); UNiagaraGraph* AddedEventScriptGraphFromDiff = AddedEventScriptSourceFromDiff->NodeGraph; FGuid ScriptBaseIdFromDiff = AddedEventScriptGraphFromDiff->GetBaseId(ENiagaraScriptUsage::ParticleEventScript, AddedEventHandler->GetUsageId()); UNiagaraScriptSource* AddedEventScriptSource = Cast(AddedEventScriptProperties.Script->GetLatestSource()); UNiagaraGraph* AddedEventScriptGraph = AddedEventScriptSource->NodeGraph; AddedEventScriptGraph->ForceBaseId(ENiagaraScriptUsage::ParticleEventScript, AddedEventHandler->GetUsageId(), ScriptBaseIdFromDiff); Results.bModifiedGraph = true; } } return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplySimulationStageDiff( TSharedRef BaseEmitterAdapter, TSharedRef ScratchPadAdapter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::ApplySimulationStageDiff); FApplyDiffResults Results; if (DiffResults.RemovedBaseSimulationStages.Num() > 0) { Results.bSucceeded = false; Results.bModifiedGraph = false; // If this becomes supported, it needs to handle the bNoParentAtLastMerge case Results.ErrorMessages.Add(LOCTEXT("RemovedSimulationStagesUnsupported", "Apply diff failed, removed shader stages are currently unsupported.")); return Results; } for (const FNiagaraModifiedSimulationStageDiffResults& ModifiedSimulationStage : DiffResults.ModifiedSimulationStages) { if (ModifiedSimulationStage.OtherAdapter->GetSimulationStage() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingModifiedSimulationStageObjectFormat", "Apply diff failed. The modified shader stage with id: {0} was missing it's shader stage object."), FText::FromString(ModifiedSimulationStage.OtherAdapter->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else if (ModifiedSimulationStage.OtherAdapter->GetOutputNode() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingModifiedSimulationStageOutputNodeFormat", "Apply diff failed. The modified shader stage with id: {0} was missing it's output node."), FText::FromString(ModifiedSimulationStage.OtherAdapter->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else { TSharedPtr MatchingBaseSimulationStageAdapter = BaseEmitterAdapter->GetSimulationStage(ModifiedSimulationStage.OtherAdapter->GetUsageId()); if (MatchingBaseSimulationStageAdapter.IsValid()) { if (ModifiedSimulationStage.ChangedProperties.Num() > 0) { CopyPropertiesToBase(MatchingBaseSimulationStageAdapter->GetEditableSimulationStage(), ModifiedSimulationStage.OtherAdapter->GetEditableSimulationStage(), ModifiedSimulationStage.ChangedProperties); } if (ModifiedSimulationStage.ScriptDiffResults.IsEmpty() == false) { FApplyDiffResults ApplySimulationStageStackDiffResults = ApplyScriptStackDiff( BaseEmitterAdapter, MatchingBaseSimulationStageAdapter->GetSimulationStageStack().ToSharedRef(), ScratchPadAdapter, ModifiedSimulationStage.ScriptDiffResults, bNoParentAtLastMerge); Results.bSucceeded &= ApplySimulationStageStackDiffResults.bSucceeded; Results.bModifiedGraph |= ApplySimulationStageStackDiffResults.bModifiedGraph; Results.ErrorMessages.Append(ApplySimulationStageStackDiffResults.ErrorMessages); } } } } FVersionedNiagaraEmitterData* BaseEmitterData = BaseEmitterAdapter->GetEditableEmitter().GetEmitterData(); UNiagaraScriptSource* EmitterSource = CastChecked(BaseEmitterData->GraphSource); UNiagaraGraph* EmitterGraph = EmitterSource->NodeGraph; for (TSharedRef AddedOtherSimulationStage : DiffResults.AddedOtherSimulationStages) { if (AddedOtherSimulationStage->GetSimulationStage() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingAddedSimulationStageObjectFormat", "Apply diff failed. The added shader stage with id: {0} was missing it's shader stage object."), FText::FromString(AddedOtherSimulationStage->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else if (AddedOtherSimulationStage->GetOutputNode() == nullptr) { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( LOCTEXT("MissingAddedSimulationStageOutputNodeFormat", "Apply diff failed. The added shader stage with id: {0} was missing it's output node."), FText::FromString(AddedOtherSimulationStage->GetUsageId().ToString(EGuidFormats::DigitsWithHyphens)))); } else { UNiagaraEmitter* BaseEmitter = BaseEmitterAdapter->GetEditableEmitter().Emitter; UNiagaraSimulationStageBase* AddedSimulationStage = CastChecked(StaticDuplicateObject(AddedOtherSimulationStage->GetSimulationStage(), BaseEmitter)); AddedSimulationStage->Script = NewObject(AddedSimulationStage, MakeUniqueObjectName(AddedSimulationStage, UNiagaraScript::StaticClass(), "SimulationStage"), RF_Transactional); AddedSimulationStage->Script->SetUsage(ENiagaraScriptUsage::ParticleSimulationStageScript); AddedSimulationStage->Script->SetUsageId(AddedOtherSimulationStage->GetUsageId()); AddedSimulationStage->Script->SetLatestSource(EmitterSource); BaseEmitter->AddSimulationStage(AddedSimulationStage, BaseEmitterAdapter->GetEditableEmitter().Version); int32 TargetIndex = FMath::Min(AddedOtherSimulationStage->GetSimulationStageIndex(), BaseEmitterData->GetSimulationStages().Num() - 1); BaseEmitter->MoveSimulationStageToIndex(AddedSimulationStage, TargetIndex, BaseEmitterAdapter->GetEditableEmitter().Version); FGuid PreferredOutputNodeGuid = AddedOtherSimulationStage->GetOutputNode()->NodeGuid; FGuid PreferredInputNodeGuid = AddedOtherSimulationStage->GetInputNode()->NodeGuid; UNiagaraNodeOutput* SimulationStageOutputNode = FNiagaraStackGraphUtilities::ResetGraphForOutput(*EmitterGraph, ENiagaraScriptUsage::ParticleSimulationStageScript, AddedSimulationStage->Script->GetUsageId(), PreferredOutputNodeGuid, PreferredInputNodeGuid); for (TSharedRef ModuleAdapter : AddedOtherSimulationStage->GetSimulationStageStack()->GetModuleFunctions()) { FApplyDiffResults AddModuleResults = AddModule(BaseEmitterAdapter, ScratchPadAdapter, *AddedSimulationStage->Script, *SimulationStageOutputNode, ModuleAdapter); Results.bSucceeded &= AddModuleResults.bSucceeded; Results.ErrorMessages.Append(AddModuleResults.ErrorMessages); } // Force the base compile id of the new shader stage to match the added instance shader stage. UNiagaraScriptSource* AddedSimulationStageSourceFromDiff = Cast(AddedOtherSimulationStage->GetSimulationStage()->Script->GetLatestSource()); UNiagaraGraph* AddedSimulationStageGraphFromDiff = AddedSimulationStageSourceFromDiff->NodeGraph; FGuid ScriptBaseIdFromDiff = AddedSimulationStageGraphFromDiff->GetBaseId(ENiagaraScriptUsage::ParticleSimulationStageScript, AddedOtherSimulationStage->GetUsageId()); UNiagaraScriptSource* AddedSimulationStageSource = Cast(AddedSimulationStage->Script->GetLatestSource()); UNiagaraGraph* AddedSimulationStageGraph = AddedSimulationStageSource->NodeGraph; AddedSimulationStageGraph->ForceBaseId(ENiagaraScriptUsage::ParticleSimulationStageScript, AddedOtherSimulationStage->GetUsageId(), ScriptBaseIdFromDiff); Results.bModifiedGraph = true; } } return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyRendererDiff(const FVersionedNiagaraEmitter& BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::ApplyRendererDiff); TArray RenderersToRemove; TArray RenderersToAdd; TArray RenderersToDisable; FVersionedNiagaraEmitterData* BaseEmitterData = BaseEmitter.GetEmitterData(); for (TSharedRef RemovedRenderer : DiffResults.RemovedBaseRenderers) { auto FindRendererByMergeId = [=](UNiagaraRendererProperties* Renderer) { return Renderer->GetMergeId() == RemovedRenderer->GetRenderer()->GetMergeId(); }; UNiagaraRendererProperties* const* MatchingRendererPtr = BaseEmitterData->GetRenderers().FindByPredicate(FindRendererByMergeId); if (MatchingRendererPtr != nullptr) { if (bNoParentAtLastMerge) { // If there is no last known parent we don't know if the renderer was removed in the child, or added in the parent, so // instead of removing the parent renderer we disable it in this case, since removing renderers in child emitters isn't // supported through the UI, and instead the user is expected to disable it. RenderersToDisable.Add(*MatchingRendererPtr); } else { RenderersToRemove.Add(*MatchingRendererPtr); } } } for (TSharedRef AddedRenderer : DiffResults.AddedOtherRenderers) { RenderersToAdd.Add(Cast(StaticDuplicateObject(AddedRenderer->GetRenderer(), BaseEmitter.Emitter))); } for (TSharedRef ModifiedRenderer : DiffResults.ModifiedOtherRenderers) { auto FindRendererByMergeId = [=](UNiagaraRendererProperties* Renderer) { return Renderer->GetMergeId() == ModifiedRenderer->GetRenderer()->GetMergeId(); }; UNiagaraRendererProperties*const* MatchingRendererPtr = BaseEmitterData->GetRenderers().FindByPredicate(FindRendererByMergeId); if (MatchingRendererPtr != nullptr) { RenderersToRemove.Add(*MatchingRendererPtr); RenderersToAdd.Add(Cast(StaticDuplicateObject(ModifiedRenderer->GetRenderer(), BaseEmitter.Emitter))); } } for (UNiagaraRendererProperties* RendererToRemove : RenderersToRemove) { BaseEmitter.Emitter->RemoveRenderer(RendererToRemove, BaseEmitter.Version); } for (UNiagaraRendererProperties* RendererToAdd : RenderersToAdd) { BaseEmitter.Emitter->AddRenderer(RendererToAdd, BaseEmitter.Version); } for (UNiagaraRendererProperties* RendererToDisable : RenderersToDisable) { RendererToDisable->bIsEnabled = false; } FApplyDiffResults Results; Results.bSucceeded = true; Results.bModifiedGraph = false; return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyEmitterSummaryDiff(const FVersionedNiagaraEmitter& BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::ApplyEmitterSummaryDiff); FVersionedNiagaraEmitterData* BaseEmitterData = BaseEmitter.GetEmitterData(); UNiagaraEmitterEditorData* EditorData = Cast(BaseEmitterData->GetEditorData()); //-TEMP: Temporary code until we figure out why we don't have valid editor data if (EditorData == nullptr) { FApplyDiffResults Results; Results.bSucceeded = false; Results.bModifiedGraph = false; return Results; } check(EditorData != GetDefault()); check(EditorData->GetOuter() == BaseEmitter.Emitter); EditorData->Modify(); UHierarchyRoot* BaseRoot = EditorData->GetSummaryRoot(); // this will copy over the section without any child elements for (const UHierarchySection* AddedOtherSection : DiffResults.AddedSummarySectionsInOther) { BaseRoot->DuplicateSectionFromOtherRoot(*AddedOtherSection); } for (const UHierarchyElement* AddedInput : DiffResults.AddedSummaryEntriesInOther) { if (UHierarchyElement* SummaryItemParent = AddedInput->GetTypedOuter()) { FHierarchyElementIdentity ParentIdentity = SummaryItemParent->GetPersistentIdentity(); if(ParentIdentity.IsValid()) { const bool bParentIsRoot = SummaryItemParent->IsA(); // if the parent is the root, we can't use the parent identity to copy over the child as the two roots are supposed to be different, so we add it directly if(bParentIsRoot) { /** Core principal of adding new items: * Any added item should come without children, as the parent could have added the same child somewhere else * Otherwise we'd need to identify not just new items vs. old items, but also new items with old children. * The children we removed but actually belong will be added back during another iteration of the loop */ if(BaseRoot->FindChildWithIdentity(AddedInput->GetPersistentIdentity(), false) == nullptr) { UHierarchyElement* NewChild = BaseRoot->CopyAndAddItemAsChild(*AddedInput); NewChild->GetChildrenMutable().Empty(); if(FDataHierarchyElementMetaData_SectionAssociation* SectionAssociation = NewChild->FindOrAddMetaDataOfType()) { if(SectionAssociation->Section.IsValid()) { const TObjectPtr* NewSection = BaseRoot->GetSectionData().FindByPredicate([OriginalSection = SectionAssociation->Section](UHierarchySection* SectionCandidate) { return SectionCandidate->GetSectionName().IsEqual(OriginalSection->GetSectionName()); }); if(NewSection) { SectionAssociation->Section = (*NewSection); } } } } } else { // it's possible the child was already added via ownership link // (i.e. if categoryA owns inputA and categoryA is copied first, inputA will be included too) // we only want to add that child if it hasn't been added already if(BaseRoot->FindChildWithIdentity(AddedInput->GetPersistentIdentity(), true) == nullptr) { UHierarchyElement* NewChild = BaseRoot->CopyAndAddItemUnderParentIdentity(*AddedInput, ParentIdentity); //if(!ensure(NewChild != nullptr)) if(NewChild == nullptr) { UE_LOG(LogNiagaraEditor, Log, TEXT("Item %s could not be added during merge process"), *AddedInput->ToString()); } else { // see above NewChild->GetChildrenMutable().Empty(); } } } } } } if (DiffResults.NewShouldShowSummaryViewValue.IsSet()) { EditorData->SetShowSummaryView(DiffResults.NewShouldShowSummaryViewValue.GetValue()); } FApplyDiffResults Results; Results.bSucceeded = true; Results.bModifiedGraph = false; return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyStackEntryDisplayNameDiffs(FVersionedNiagaraEmitter BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::ApplyStackEntryDisplayNameDiffs); if (DiffResults.ModifiedStackEntryDisplayNames.Num() > 0) { UNiagaraEmitterEditorData* EditorData = Cast(BaseEmitter.GetEmitterData()->GetEditorData()); for (auto& Pair : DiffResults.ModifiedStackEntryDisplayNames) { EditorData->GetStackEditorData().SetStackEntryDisplayName(Pair.Key, Pair.Value); } } FApplyDiffResults Results; Results.bSucceeded = true; Results.bModifiedGraph = false; return Results; } FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyStackNoteDiffs(FVersionedNiagaraEmitter BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults) const { TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraScriptMergeManager::ApplyStackNoteDiffs); if(DiffResults.AddedOrModifiedStackNotesInOther.Num() > 0) { if(UNiagaraEmitterEditorData* EmitterEditorData = Cast(BaseEmitter.GetEmitterData()->GetEditorData())) { for(const auto& Pair : DiffResults.AddedOrModifiedStackNotesInOther) { EmitterEditorData->GetStackEditorData().AddOrReplaceStackNote(Pair.Key, Pair.Value, false); } } } FApplyDiffResults Results; Results.bSucceeded = true; Results.bModifiedGraph = false; return Results; } FNiagaraScriptMergeManager::FCachedMergeAdapter* FNiagaraScriptMergeManager::FindOrAddMergeAdapterCacheForEmitter(const FVersionedNiagaraEmitter& VersionedEmitter) { FCachedMergeAdapter* CachedMergeAdapter = CachedMergeAdapters.Find(VersionedEmitter); if (CachedMergeAdapter == nullptr) { CachedMergeAdapter = &CachedMergeAdapters.Add(VersionedEmitter); } else { if (CachedMergeAdapter->ChangeId != VersionedEmitter.Emitter->GetChangeId()) { CachedMergeAdapter->ChangeId = VersionedEmitter.Emitter->GetChangeId(); CachedMergeAdapter->EmitterMergeAdapter.Reset(); CachedMergeAdapter->ScratchPadMergeAdapter.Reset(); } } return CachedMergeAdapter; } void FNiagaraScriptMergeManager::ClearMergeAdapterCache() { CachedMergeAdapters.Empty(); } TSharedRef FNiagaraScriptMergeManager::GetEmitterMergeAdapterUsingCache(const FVersionedNiagaraEmitter& Emitter) { FCachedMergeAdapter* CachedMergeAdapter = FindOrAddMergeAdapterCacheForEmitter(Emitter); if (CachedMergeAdapter->EmitterMergeAdapter.IsValid() == false || CachedMergeAdapter->EmitterMergeAdapter->GetEditableEmitter().Emitter == nullptr) { CachedMergeAdapter->EmitterMergeAdapter = MakeShared(Emitter); } return CachedMergeAdapter->EmitterMergeAdapter.ToSharedRef(); } TSharedRef FNiagaraScriptMergeManager::GetScratchPadMergeAdapterUsingCache(const FVersionedNiagaraEmitter& VersionedEmitter) { FCachedMergeAdapter* CachedMergeAdapter = FindOrAddMergeAdapterCacheForEmitter(VersionedEmitter); if (CachedMergeAdapter->ScratchPadMergeAdapter.IsValid() == false) { CachedMergeAdapter->ScratchPadMergeAdapter = MakeShared(VersionedEmitter, FVersionedNiagaraEmitter(), VersionedEmitter.GetEmitterData()->GetParent()); } return CachedMergeAdapter->ScratchPadMergeAdapter.ToSharedRef(); } #undef LOCTEXT_NAMESPACE