// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraMessageManager.h" #include "NiagaraScriptSource.h" #include "Modules/ModuleManager.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "NiagaraMessages.h" #include "Toolkits/NiagaraScriptToolkit.h" #include "NiagaraEditorModule.h" #include "Subsystems/AssetEditorSubsystem.h" #include "NiagaraEditorUtilities.h" #include "ViewModels/NiagaraSystemViewModel.h" #include "ViewModels/NiagaraScratchPadScriptViewModel.h" #include "ViewModels/NiagaraScratchPadViewModel.h" #include "NiagaraMessages.h" #define LOCTEXT_NAMESPACE "NiagaraMessageManager" FNiagaraMessageManager* FNiagaraMessageManager::Singleton = nullptr; uint32 FNiagaraMessageManager::NextTopicBitflag = 1; bool FNiagaraMessageManager::bNeedFlushMessages = false; bool FNiagaraMessageManager::bRefreshTimeElapsed = true; FNiagaraMessageManager::FNiagaraMessageManager() { } FNiagaraMessageManager* FNiagaraMessageManager::Get() { if (Singleton == nullptr) { Singleton = new FNiagaraMessageManager(); } return Singleton; } void FNiagaraMessageManager::AddMessage(const TSharedRef& InMessage, const FGuid& InMessageAssetKey) { bNeedFlushMessages = true; FAssetMessageInfo& AssetMessageInfo = AssetToMessageInfoMap.FindOrAdd(InMessageAssetKey); AssetMessageInfo.Messages.Add(InMessage); AssetMessageInfo.bDirty = true; AssetMessageInfo.DirtyTopicBitfield |= InMessage->GetMessageTopicBitflag(); } void FNiagaraMessageManager::AddMessageJob(TUniquePtr&& InMessageJob, const FGuid& InMessageJobAssetKey) { //MessageJobs.Emplace(FMessageJobAndAssetKey(InMessageJob, InMessageJobAssetKey)); MessageJobs.Emplace(FMessageJobAndAssetKey(InMessageJobAssetKey)); //@todo(ng) touchup MessageJobs.Last().MessageJob = MoveTemp(InMessageJob); } void FNiagaraMessageManager::ClearAssetMessages(const FGuid& AssetKey) { AssetToMessageInfoMap.Remove(AssetKey); } void FNiagaraMessageManager::ClearAssetMessagesForTopic(const FGuid& AssetKey, const FName& Topic) { FAssetMessageInfo* AssetMessageInfo = AssetToMessageInfoMap.Find(AssetKey); if (AssetMessageInfo != nullptr) { const uint32 TopicBitflag = GetMessageTopicBitflag(Topic); for(int i = AssetMessageInfo->Messages.Num() - 1; i > -1; --i) { if (TopicBitflag & AssetMessageInfo->Messages[i]->GetMessageTopicBitflag()) { AssetMessageInfo->DirtyTopicBitfield |= AssetMessageInfo->Messages[i]->GetMessageTopicBitflag(); AssetMessageInfo->Messages.RemoveAt(i, EAllowShrinking::No); AssetMessageInfo->bDirty = true; bNeedFlushMessages = true; } } } } void FNiagaraMessageManager::ClearAssetMessagesForObject(const FGuid& AssetKey, const FObjectKey& ObjectKeys) { FAssetMessageInfo* AssetMessageInfo = AssetToMessageInfoMap.Find(AssetKey); if (AssetMessageInfo != nullptr) { for (int i = AssetMessageInfo->Messages.Num() - 1; i > -1; --i) { const TArray& MessageObjectKeys = AssetMessageInfo->Messages[i]->GetAssociatedObjectKeys(); if (MessageObjectKeys.Contains(ObjectKeys)) { AssetMessageInfo->DirtyTopicBitfield |= AssetMessageInfo->Messages[i]->GetMessageTopicBitflag(); AssetMessageInfo->Messages.RemoveAt(i, EAllowShrinking::No); AssetMessageInfo->bDirty = true; bNeedFlushMessages = true; } } } } const TOptional FNiagaraMessageManager::GetStringForScriptUsageInStack(const ENiagaraScriptUsage InScriptUsage) { switch (InScriptUsage) { case ENiagaraScriptUsage::ParticleSpawnScript: return TOptional(FString("Particle Spawn Script")); case ENiagaraScriptUsage::ParticleSpawnScriptInterpolated: return TOptional(TEXT("Particle Spawn Script Interpolated")); case ENiagaraScriptUsage::ParticleGPUComputeScript: return TOptional(TEXT("Particle GPU Compute Script")); case ENiagaraScriptUsage::ParticleUpdateScript: return TOptional(TEXT("Particle Update Script")); case ENiagaraScriptUsage::ParticleEventScript: return TOptional(TEXT("Particle Event Script")); case ENiagaraScriptUsage::ParticleSimulationStageScript: return TOptional(FString("Particle Simulation Stage Script")); case ENiagaraScriptUsage::EmitterSpawnScript: return TOptional(TEXT("Emitter Spawn Script")); case ENiagaraScriptUsage::EmitterUpdateScript: return TOptional(TEXT("Emitter Update Script")); case ENiagaraScriptUsage::SystemSpawnScript: return TOptional(TEXT("System Spawn Script")); case ENiagaraScriptUsage::SystemUpdateScript: return TOptional(TEXT("System Update Script")); //We don't expect to see these usages in the stack so do not set the toptional case ENiagaraScriptUsage::DynamicInput: case ENiagaraScriptUsage::Function: case ENiagaraScriptUsage::Module: return TOptional(); //unhandled cases default: ensureMsgf(false, TEXT("Tried to get script usage text for usage that is not handled!")); return TOptional(); } } void FNiagaraMessageManager::RegisterMessageTopic(FName TopicName) { const uint32* TopicBitflag = RegisteredTopicToBitflagsMap.Find(TopicName); checkf( TopicBitflag == nullptr, TEXT("Tried to register topic '%s' but it has already been registered!"), *TopicName.ToString()); RegisteredTopicToBitflagsMap.Add(TopicName, NextTopicBitflag); // binary increment the topic bitflag for the next topic to be registered. NextTopicBitflag <<= 1; } void FNiagaraMessageManager::RegisterAdditionalMessageLogTopic(FName MessageLogTopicName) { AdditionalMessageLogTopics.AddUnique(MessageLogTopicName); } uint32 FNiagaraMessageManager::GetMessageTopicBitflag(FName TopicName) { const uint32* TopicBitflag = RegisteredTopicToBitflagsMap.Find(TopicName); if (TopicBitflag == nullptr) { // It is possible the message topic has not been registered yet. If so, register now. RegisterMessageTopic(TopicName); return *(RegisteredTopicToBitflagsMap.Find(TopicName)); } return *TopicBitflag; } void FNiagaraMessageManager::Tick(float DeltaSeconds) { DoMessageJobsTick(); TryFlushMessagesTick(); } void FNiagaraMessageManager::DoMessageJobsTick() { if (MessageJobs.Num() > 0) { double WorkStartTime = FPlatformTime::Seconds(); double CurrentWorkLoopTime = WorkStartTime; while(MessageJobs.Num() > 0) { FMessageJobAndAssetKey CurrentMessageJobAndAssetKey = MessageJobs.Pop(EAllowShrinking::No); TSharedRef GeneratedMessage = CurrentMessageJobAndAssetKey.MessageJob->GenerateNiagaraMessage(); AddMessage(GeneratedMessage, CurrentMessageJobAndAssetKey.AssetKey); CurrentWorkLoopTime = FPlatformTime::Seconds(); if (CurrentWorkLoopTime - WorkStartTime > MaxJobWorkTime) { // Max job work time has been reached, early exit so as to not stall the UI. return; } } } } void FNiagaraMessageManager::TryFlushMessagesTick() { if (bNeedFlushMessages && bRefreshTimeElapsed) { FlushMessages(); } } void FNiagaraMessageManager::FlushMessages() { bNeedFlushMessages = false; bRefreshTimeElapsed = false; FTimerDelegate SetRefreshTimerElapsedDelegate; SetRefreshTimerElapsedDelegate.BindStatic(&SetRefreshTimerElapsed); GEditor->GetTimerManager()->SetTimer( RefreshTimerHandle , SetRefreshTimerElapsedDelegate , RefreshHysterisisTime , false); for (auto AssetIt = AssetToMessageInfoMap.CreateIterator(); AssetIt; ++AssetIt) { FAssetMessageInfo& AssetMessageInfo = AssetIt.Value(); if (AssetMessageInfo.bDirty) { for (auto RegistrationIt = AssetMessageInfo.RegistrationKey_To_RegistrationHandleMap.CreateIterator(); RegistrationIt; ++RegistrationIt) { INiagaraMessageRegistrationHandle* RegistrationHandle = RegistrationIt.Value().Get(); // Only push messages that have a topic that is dirty AND in the registration handle topics. const uint32 DesiredTopicBitfield = AssetMessageInfo.DirtyTopicBitfield & RegistrationHandle->GetTopicBitfield(); if (DesiredTopicBitfield != 0) { // Note, filtering is not using desired topic bitfield here as that requires the registered view to recycle non-dirty messages const TArray>& TopicalMessages = RegistrationHandle->FilterMessages(AssetMessageInfo.Messages, RegistrationHandle->GetTopicBitfield()); RegistrationHandle->GetOnRequestRefresh().Execute(TopicalMessages); } } } AssetMessageInfo.bDirty = false; AssetMessageInfo.DirtyTopicBitfield = 0; } } void FNiagaraMessageManager::SetRefreshTimerElapsed() { bRefreshTimeElapsed = true; } uint32 FNiagaraMessageManager::MakeBitfieldForMessageTopics(const FText& DebugNameText, const TArray& MessageTopics) { uint32 TopicBitfield = 0; for (const FName& TopicName : MessageTopics) { const uint32 TopicBitflag = GetMessageTopicBitflag(TopicName); TopicBitfield |= TopicBitflag; } return TopicBitfield; } FNiagaraMessageTopicRegistrationHandle::FOnRequestRefresh& FNiagaraMessageManager::SubscribeToAssetMessagesByTopic( const FText& DebugNameText , const FGuid& MessageAssetKey , const TArray& MessageTopics , FGuid& OutMessageManagerRegistrationKey) { checkf(MessageAssetKey != FGuid(), TEXT("Tried to subscribe to an asset without a set asset key!")); uint32 TopicBitfield = MakeBitfieldForMessageTopics(DebugNameText, MessageTopics); TSharedPtr RegistrationHandle = MakeShared(TopicBitfield); const FGuid RegistrationKey = FGuid::NewGuid(); OutMessageManagerRegistrationKey = RegistrationKey; FAssetMessageInfo& AssetMessageInfo = AssetToMessageInfoMap.FindOrAdd(MessageAssetKey); TSharedPtr& RegistrationHandleRef = AssetMessageInfo.RegistrationKey_To_RegistrationHandleMap.Emplace(RegistrationKey, RegistrationHandle); return RegistrationHandleRef->GetOnRequestRefresh(); } FNiagaraMessageTopicRegistrationHandle::FOnRequestRefresh& FNiagaraMessageManager::SubscribeToAssetMessagesByObject( const FText& DebugNameText , const FGuid& MessageAssetKey , const FObjectKey& ObjectKey , FGuid& OutMessageManagerRegistrationKey) { checkf(MessageAssetKey != FGuid(), TEXT("Tried to subscribe to an asset without a set asset key!")); TSharedPtr RegistrationHandle = MakeShared(ObjectKey); const FGuid RegistrationKey = FGuid::NewGuid(); OutMessageManagerRegistrationKey = RegistrationKey; FAssetMessageInfo& AssetMessageInfo = AssetToMessageInfoMap.FindOrAdd(MessageAssetKey); TSharedPtr& RegistrationHandleRef = AssetMessageInfo.RegistrationKey_To_RegistrationHandleMap.Emplace(RegistrationKey, RegistrationHandle); return RegistrationHandleRef->GetOnRequestRefresh(); } void FNiagaraMessageManager::Unsubscribe(const FText& DebugNameText, const FGuid& MessageAssetKey, FGuid& MessageManagerRegistrationKey) { ensureMsgf(MessageAssetKey != FGuid(), TEXT("Tried to unsubscribe from an asset without a set asset key!")); FAssetMessageInfo* AssetMessageInfo = AssetToMessageInfoMap.Find(MessageAssetKey); if (ensureMsgf(AssetMessageInfo != nullptr, TEXT("Tried to unbind message subscriber '%s' but failed to find the associated asset message info!"), *DebugNameText.ToString()) ) { AssetMessageInfo->RegistrationKey_To_RegistrationHandleMap.FindAndRemoveChecked(MessageManagerRegistrationKey); } MessageManagerRegistrationKey = FGuid(); } FNiagaraCompileEventToken::FNiagaraCompileEventToken( const FString& InScriptAssetPath , const FText& InMessage , const TOptional& InNodeGUID , const TOptional& InPinGUID) : ScriptAssetPath(InScriptAssetPath) , NodeGUID(InNodeGUID) , PinGUID(InPinGUID) { if (!InMessage.IsEmpty()) { CachedText = InMessage; } else { CachedText = FText::FromString(InScriptAssetPath); } MessageTokenActivated = FOnMessageTokenActivated::CreateStatic(&FNiagaraCompileEventToken::OpenScriptAssetByPathAndFocusNodeOrPinIfSet, ScriptAssetPath, NodeGUID, PinGUID); } void FNiagaraCompileEventToken::OpenScriptAssetByPathAndFocusNodeOrPinIfSet( const TSharedRef& Token , FString InScriptAssetPath , const TOptional InNodeGUID , const TOptional InPinGUID) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); FAssetData AssetData = AssetRegistry.GetAssetByObjectPath(FSoftObjectPath(InScriptAssetPath)); if (AssetData.IsValid()) { UNiagaraScript* ScriptAsset = Cast(AssetData.GetAsset()); if (ScriptAsset != nullptr && ScriptAsset->IsAsset()) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(ScriptAsset, EToolkitMode::Standalone); FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::LoadModuleChecked("NiagaraEditor"); if (InPinGUID.IsSet()) { TSharedRef PinToFocusInfo = MakeShared(InPinGUID.GetValue()); FNiagaraScriptIDAndGraphFocusInfo PinToFocusAndScriptID = FNiagaraScriptIDAndGraphFocusInfo(ScriptAsset->GetUniqueID(), PinToFocusInfo); NiagaraEditorModule.GetOnScriptToolkitsShouldFocusGraphElement().Broadcast(&PinToFocusAndScriptID); } else if (InNodeGUID.IsSet()) { TSharedRef NodeToFocusInfo = MakeShared(InNodeGUID.GetValue()); FNiagaraScriptIDAndGraphFocusInfo NodeToFocusAndScriptID = FNiagaraScriptIDAndGraphFocusInfo(ScriptAsset->GetUniqueID(), NodeToFocusInfo); NiagaraEditorModule.GetOnScriptToolkitsShouldFocusGraphElement().Broadcast(&NodeToFocusAndScriptID); } } else if (ScriptAsset != nullptr) { UNiagaraSystem* System = ScriptAsset->GetTypedOuter(); UNiagaraEmitter* Emitter = ScriptAsset->GetTypedOuter(); if (System) { if (System->IsAsset()) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(System, EToolkitMode::Standalone); } else if (Emitter && Emitter->IsAsset()) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(Emitter, EToolkitMode::Standalone); } TArray> ReferencingSystemViewModels; FNiagaraSystemViewModel::GetAllViewModelsForObject(System, ReferencingSystemViewModels); for (TSharedPtr ReferencingSystemViewModel : ReferencingSystemViewModels) { TSharedPtr ScratchModuleViewModel = ReferencingSystemViewModel->GetScriptScratchPadViewModel()->GetViewModelForScript(ScriptAsset); if (ScratchModuleViewModel.IsValid()) { ReferencingSystemViewModel->GetScriptScratchPadViewModel()->FocusScratchPadScriptViewModel(ScratchModuleViewModel.ToSharedRef()); if (InPinGUID.IsSet()) { TSharedRef PinToFocusInfo = MakeShared(InPinGUID.GetValue()); FNiagaraScriptIDAndGraphFocusInfo PinToFocusAndScriptID = FNiagaraScriptIDAndGraphFocusInfo(ScriptAsset->GetUniqueID(), PinToFocusInfo); ScratchModuleViewModel->RaisePinFocusRequested(&PinToFocusAndScriptID); } else if (InNodeGUID.IsSet()) { TSharedRef NodeToFocusInfo = MakeShared(InNodeGUID.GetValue()); FNiagaraScriptIDAndGraphFocusInfo NodeToFocusAndScriptID = FNiagaraScriptIDAndGraphFocusInfo(ScriptAsset->GetUniqueID(), NodeToFocusInfo); ScratchModuleViewModel->RaiseNodeFocusRequested(&NodeToFocusAndScriptID); } } } } else if (Emitter && Emitter->IsAsset()) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(Emitter, EToolkitMode::Standalone); } } else { FNiagaraEditorUtilities::WarnWithToastAndLog(FText::Format( LOCTEXT("CantNavigateWarning", "Could not navigate to script {0}\nIt was either was not a valid script, or it is not an asset which can be opened directly."), FText::FromString(InScriptAssetPath))); } } } TSharedRef FNiagaraCompileEventToken::Create( const FString& InScriptAssetPath , const FText& InMessage , const TOptional& InNodeGUID /*= TOptional() */ , const TOptional& InPinGUID /*= TOptional()*/ ) { return MakeShareable(new FNiagaraCompileEventToken(InScriptAssetPath, InMessage, InNodeGUID, InPinGUID)); } TArray> FNiagaraMessageTopicRegistrationHandle::FilterMessages(const TArray>& Messages, const uint32& DesiredTopicBitfield) const { return Messages.FilterByPredicate([DesiredTopicBitfield](const TSharedRef& Message) {return Message->GetMessageTopicBitflag() & DesiredTopicBitfield; }); } TArray> FNiagaraMessageObjectRegistrationHandle::FilterMessages(const TArray>& Messages, const uint32& DesiredTopicBitfield) const { return Messages.FilterByPredicate([this](const TSharedRef& Message) { const TArray& MessageObjectKeys = Message->GetAssociatedObjectKeys(); return MessageObjectKeys.Contains(ObjectKey); }); } #undef LOCTEXT_NAMESPACE //NiagaraMessageManager