// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraDigestDatabase.h" #include "Misc/LazySingleton.h" #include "NiagaraEditorModule.h" #include "NiagaraGraph.h" #include "NiagaraGraphDigest.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraParameterCollection.h" #include "NiagaraScript.h" #include "NiagaraScriptSource.h" #include "UObject/UObjectGlobals.h" namespace NiagaraGraphDigestDatabaseImpl { static int32 GDigestGraphCacheSize = 512; static FAutoConsoleVariableRef CVarDigestGraphCacheSize( TEXT("fx.Niagara.DigestGraphCacheSize"), GDigestGraphCacheSize, TEXT("Defines the size of the cache for digested Niagara graphs."), ECVF_ReadOnly ); FNiagaraDigestDatabase* GDigestDatabase = nullptr; } // NiagaraGraphDigestDatabaseImpl:: FNiagaraDigestDatabase& FNiagaraDigestDatabase::Get() { if (!NiagaraGraphDigestDatabaseImpl::GDigestDatabase) { NiagaraGraphDigestDatabaseImpl::GDigestDatabase = &TLazySingleton::Get(); } return *NiagaraGraphDigestDatabaseImpl::GDigestDatabase; } void FNiagaraDigestDatabase::Shutdown() { Get().ReleaseDatabase(); } FNiagaraDigestDatabase::FNiagaraDigestDatabase() : CompilationGraphCache(NiagaraGraphDigestDatabaseImpl::GDigestGraphCacheSize) { // we use GC as an opportunity to clear the graph cache, preventing the database from holding onto references and // extending object lifetimes unnecessarily. In particular the digested graphs contain duplicates of the DI // which could be referencing real objects. Note that the NPC cache isn't cleared as it is a duplicate owned by // this database that mirrors the universal data (i.e. the NPC themselves aren't released when running in the editor) FCoreUObjectDelegates::GetPreGarbageCollectDelegate().AddRaw(this, &FNiagaraDigestDatabase::ReleaseGraphCache); } FNiagaraDigestDatabase::~FNiagaraDigestDatabase() { ReleaseDatabase(); FCoreUObjectDelegates::GetPreGarbageCollectDelegate().RemoveAll(this); } void FNiagaraDigestDatabase::ReleaseGraphCache() { FWriteScopeLock WriteScope(DigestCacheLock); CompilationGraphCache.Empty(NiagaraGraphDigestDatabaseImpl::GDigestGraphCacheSize); } void FNiagaraDigestDatabase::ReleaseDatabase() { FWriteScopeLock WriteScope(DigestCacheLock); CompilationGraphCache.Empty(NiagaraGraphDigestDatabaseImpl::GDigestGraphCacheSize); CompilationNPCCache.Empty(); } void FNiagaraDigestDatabase::AddReferencedObjects(FReferenceCollector& Collector) { FReadScopeLock ReadScope(DigestCacheLock); for (FCompilationNPCCache::TIterator It(CompilationNPCCache); It; ++It) { It.Value()->AddReferencedObjects(Collector); } } FString FNiagaraDigestDatabase::GetReferencerName() const { static const FString ReferencerName = TEXT("NiagaraDigestDatabsae"); return ReferencerName; } ////////////////////////////////////////////////////////////////////////// /// Digested graphs FNiagaraDigestedGraphPtr FNiagaraDigestDatabase::CreateGraphDigest(const UNiagaraGraph* Graph, const FNiagaraGraphChangeIdBuilder& Digester) { check(IsInGameThread()); FNiagaraCompilationGraphHandle GraphHash(Graph, Digester); FNiagaraDigestedGraphPtr PendingGraph; { FWriteScopeLock WriteScope(DigestCacheLock); if (const FNiagaraDigestedGraphPtr* CompilationGraph = CompilationGraphCache.FindAndTouch(GraphHash)) { ++GraphCacheHits; return *CompilationGraph; } ++GraphCacheMisses; PendingGraph = MakeShared(); CompilationGraphCache.Add(GraphHash, PendingGraph); } if (PendingGraph) { PendingGraph->Digest(Graph, Digester); } return PendingGraph; } FNiagaraCompilationGraphHandle::FNiagaraCompilationGraphHandle(const UNiagaraGraph* Graph, const FNiagaraGraphChangeIdBuilder& Builder) { AssetKey = Graph; // generate the hash key based on the provided ChangeIdBuilder Hash = Builder.FindChangeId(Graph); } ////////////////////////////////////////////////////////////////////////// /// Digested parameter collection FNiagaraCompilationNPCHandle FNiagaraDigestDatabase::CreateCompilationCopy(const UNiagaraParameterCollection* Collection) { check(IsInGameThread()); FNiagaraCompilationNPCHandle CollectionHash(Collection); FNiagaraCompilationNPC* PendingCollection = nullptr; { FWriteScopeLock WriteScope(DigestCacheLock); if (const FNiagaraCompilationNPCHandle::FDigestPtr* CompilationCollection = CompilationNPCCache.Find(CollectionHash)) { ++CollectionCacheHits; return CollectionHash; } ++CollectionCacheMisses; FNiagaraCompilationNPCHandle::FDigestPtr CompilationCollection = MakeShared(); PendingCollection = CompilationCollection.Get(); CompilationNPCCache.Add(CollectionHash, CompilationCollection); } if (PendingCollection) { PendingCollection->Create(Collection); } return CollectionHash; } FNiagaraCompilationNPCHandle::FDigestPtr FNiagaraDigestDatabase::Resolve(const FNiagaraCompilationNPCHandle& Handle) const { FReadScopeLock ReadScope(DigestCacheLock); return CompilationNPCCache.FindRef(Handle); } void FNiagaraCompilationNPC::Create(const UNiagaraParameterCollection* Collection) { SourceCollection = Collection; Namespace = Collection->GetNamespace(); CollectionPath = FSoftObjectPath(Collection).ToString(); CollectionFullName = GetFullNameSafe(Collection); Variables.Reserve(Collection->GetParameters().Num()); for (const FNiagaraVariable& Parameter : Collection->GetParameters()) { Variables.Emplace(Parameter); } { // we also need to deal with any data interfaces that might be stored in the NPC UPackage* TransientPackage = GetTransientPackage(); const FNiagaraParameterStore& DefaultParamStore = Collection->GetDefaultInstance()->GetParameterStore(); for (const FNiagaraVariableBase& Variable : Variables) { const int32 VariableOffset = DefaultParamStore.IndexOf(Variable); if (VariableOffset != INDEX_NONE) { if (UNiagaraDataInterface* DefaultDataInterface = DefaultParamStore.GetDataInterface(VariableOffset)) { UNiagaraDataInterface* DuplicateDataInterface = DuplicateObject(DefaultDataInterface, TransientPackage); DefaultDataInterfaces.Add(Variable, DuplicateDataInterface); } } } } } void FNiagaraCompilationNPC::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddStableReferenceMap(DefaultDataInterfaces); } UNiagaraDataInterface* FNiagaraCompilationNPC::GetDataInterface(const FNiagaraVariableBase& Variable) const { return DefaultDataInterfaces.FindRef(Variable); } FNiagaraCompilationNPCHandle::FNiagaraCompilationNPCHandle(const UNiagaraParameterCollection* Connection) { if (Connection) { Namespace = Connection->GetNamespace(); AssetKey = Connection; Hash = Connection->GetCompileHash(); } } FNiagaraCompilationNPCHandle::FDigestPtr FNiagaraCompilationNPCHandle::Resolve() const { return FNiagaraDigestDatabase::Get().Resolve(*this); }