Files
UnrealEngine/Engine/Plugins/Mutable/Source/MutableRuntime/Internal/MuR/SystemPrivate.h
2025-05-18 13:04:45 +08:00

1998 lines
52 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "MuR/System.h"
#include "MuR/Settings.h"
#include "MuR/Operations.h"
#include "MuR/Image.h"
#include "MuR/MutableString.h"
#include "MuR/ModelPrivate.h"
#include "MuR/Mesh.h"
#include "MuR/Instance.h"
#include "MuR/ParametersPrivate.h"
#include "MuR/MutableTrace.h"
#include "Templates/UnrealTypeTraits.h"
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
#include "HAL/Thread.h"
#include "HAL/PlatformTLS.h"
#endif
#ifndef MUTABLE_DEBUG_CODERUNNER_TASK_SCHEDULE_CALLSTACK
#define MUTABLE_DEBUG_CODERUNNER_TASK_SCHEDULE_CALLSTACK 0
#endif
namespace mu::MemoryCounters
{
struct FInternalMemoryCounter
{
static MUTABLERUNTIME_API std::atomic<SSIZE_T>& Get();
};
}
namespace mu
{
inline uint32 GetResourceIDRoot(FResourceID Id)
{
return uint32(Id >> 32);
}
class FExtensionDataStreamer;
// Call the tick of the LLM system (we do this to simulate a frame since the LLM system is not entirelly designed to run over a program)
inline void UpdateLLMStats()
{
// This code will only be compiled (and ran) if the global definition to enable LLM tracking is set to 1 for the host program
// Ex : GlobalDefinitions.Add("LLM_ENABLED_IN_CONFIG=1");
#if ENABLE_LOW_LEVEL_MEM_TRACKER && IS_PROGRAM
FLowLevelMemTracker& MemTracker = FLowLevelMemTracker::Get();
if (MemTracker.IsEnabled())
{
MemTracker.UpdateStatsPerFrame();
}
#endif
}
constexpr uint64 AllParametersMask = TNumericLimits<uint64>::Max();
/** ExecutinIndex stores the location inside all ranges of the execution of a specific
* operation. The first integer on each pair is the dimension/range index in the program
* array of ranges, and the second integer is the value inside that range.
* The vector order is undefined.
*/
class ExecutionIndex : public TArray<TPair<int32, int32>>
{
public:
//! Set or add a value to the index
void SetFromModelRangeIndex(uint16 FRangeIndex, int32 RangeValue)
{
SizeType Index = IndexOfByPredicate([=](const ElementType& v) { return v.Key >= FRangeIndex; });
if (Index != INDEX_NONE && (*this)[Index].Key == FRangeIndex)
{
// Update
(*this)[Index].Value = RangeValue;
}
else
{
// Add new
Push(ElementType(FRangeIndex, RangeValue));
}
}
//! Get the value of the index from the range index in the model.
int32 GetFromModelRangeIndex(int32 ModelRangeIndex) const
{
for (const TPair<int32, int32>& e : *this)
{
if (e.Key == ModelRangeIndex)
{
return e.Value;
}
}
return 0;
}
};
/** This structure stores the data about an ongoing mutable operation that needs to be executed. */
struct FScheduledOp
{
inline FScheduledOp()
{
Stage = 0;
Type = EType::Full;
}
inline FScheduledOp(OP::ADDRESS InAt, const FScheduledOp& InOpTemplate, uint8 InStage = 0, uint32 InCustomState = 0)
{
check(InStage < 120);
At = InAt;
ExecutionOptions = InOpTemplate.ExecutionOptions;
ExecutionIndex = InOpTemplate.ExecutionIndex;
Stage = InStage;
CustomState = InCustomState;
Type = InOpTemplate.Type;
}
static inline FScheduledOp FromOpAndOptions(OP::ADDRESS InAt, const FScheduledOp& InOpTemplate, uint8 InExecutionOptions)
{
FScheduledOp r;
r.At = InAt;
r.ExecutionOptions = InExecutionOptions;
r.ExecutionIndex = InOpTemplate.ExecutionIndex;
r.Stage = 0;
r.CustomState = InOpTemplate.CustomState;
r.Type = InOpTemplate.Type;
return r;
}
//! Address of the operation
OP::ADDRESS At = 0;
//! Additional custom state data that the operation can store. This is usually used to pass information
//! between execution stages of an operation.
uint32 CustomState = 0;
//! Index of the operation execution: This is used for iteration of different ranges.
//! It is an index into the CodeRunner::GetMemory()::m_rangeIndex vector.
//! executionIndex 0 is always used for empty ExecutionIndex, which is the most common
//! one.
uint16 ExecutionIndex = 0;
//! Additional execution options. Set externally to this op, it usually alters the result.
//! For example, this is used to keep track of the mipmaps to skip in image operations.
uint8 ExecutionOptions = 0;
//! Internal stage of the operation.
//! Stage 0 is usually scheduling of children, and 1 is execution. Some instructions
//! may have more steges to schedule children that are optional for execution, etc.
uint8 Stage : 7;
//! Type of calculation we are requesting for this operation.
enum class EType : uint8
{
//! Execute the operation to calculate the full result
Full,
//! Execute the operation to obtain the descriptor of an image.
ImageDesc
};
EType Type : 1;
#if MUTABLE_DEBUG_CODERUNNER_TASK_SCHEDULE_CALLSTACK
static constexpr uint64 CallstackMaxDepth = 16;
uint64 StackDepth = 0;
uint64 ScheduleCallstack[CallstackMaxDepth];
#endif
};
inline uint32 GetTypeHash(const FScheduledOp& Op)
{
return HashCombineFast(::GetTypeHash(Op.At), HashCombineFast((uint32)Op.Stage), (uint32)Op.ExecutionIndex);
}
/** A cache address is the operation plus the context of execution(iteration indices, etc...). */
struct FCacheAddress
{
/** The meaning of all these fields is the same than the FScheduledOp struct. */
OP::ADDRESS At = 0;
uint16 ExecutionIndex = 0;
uint8 ExecutionOptions = 0;
FScheduledOp::EType Type = FScheduledOp::EType::Full;
FCacheAddress()
{
}
FCacheAddress(OP::ADDRESS InAt, uint16 InExecutionIndex, uint8 InExecutionOptions, FScheduledOp::EType InType = FScheduledOp::EType::Full)
: At(InAt), ExecutionIndex(InExecutionIndex), ExecutionOptions(InExecutionOptions), Type(InType)
{
}
FCacheAddress(OP::ADDRESS InAt, const FScheduledOp& Item)
: At(InAt), ExecutionIndex(Item.ExecutionIndex), ExecutionOptions(Item.ExecutionOptions), Type(Item.Type)
{
}
FCacheAddress(const FScheduledOp& Item)
: At(Item.At), ExecutionIndex(Item.ExecutionIndex), ExecutionOptions(Item.ExecutionOptions), Type(Item.Type)
{
}
};
inline uint32 GetTypeHash(const FCacheAddress& Address)
{
return HashCombineFast(::GetTypeHash(Address.At), (uint32)Address.ExecutionIndex);
}
inline uint32 GetTypeHash(const Ptr<const FResource>& V)
{
return ::GetTypeHash(V.get());
}
//! Container that stores data per executable code operation (indexed by address and execution
//! index).
template<class DATA>
class CodeContainer
{
using MemoryCounter = MemoryCounters::FInternalMemoryCounter;
using ArrayDataContainerType = TArray<DATA, FDefaultMemoryTrackingAllocator<MemoryCounter>>;
using MapDataContainerType = TMap<FCacheAddress, DATA, FDefaultMemoryTrackingSetAllocator<MemoryCounter>>;
public:
void resize(size_t s)
{
m_index0.SetNumZeroed(s);
}
uint32 size_code() const
{
return uint32(m_index0.Num());
}
void clear()
{
m_index0.Empty();
m_otherIndex.Empty();
}
inline void erase(const FCacheAddress& at)
{
if (at.ExecutionIndex == 0 && at.ExecutionOptions == 0)
{
m_index0[at.At] = nullptr;
}
else
{
m_otherIndex.Remove(at);
}
}
inline DATA get(const FCacheAddress& at) const
{
if (at.ExecutionIndex == 0 && at.ExecutionOptions == 0)
{
if (at.At < uint32(m_index0.Num()))
{
return m_index0[at.At];
}
else
{
return 0;
}
}
else
{
const DATA* it = m_otherIndex.Find(at);
if (it)
{
return *it;
}
}
return 0;
}
inline DATA* get_ptr(const FCacheAddress& at)
{
if (at.ExecutionIndex == 0 && at.ExecutionOptions == 0)
{
if (at.At < uint32(m_index0.Num()))
{
return &m_index0[at.At];
}
else
{
return nullptr;
}
}
else
{
DATA* it = m_otherIndex.Find(at);
if (it)
{
return it;
}
}
return nullptr;
}
inline const DATA* get_ptr(const FCacheAddress& at) const
{
if (at.ExecutionIndex == 0 && at.ExecutionOptions == 0)
{
if (at.At < uint32(m_index0.Num()))
{
return &m_index0[at.At];
}
else
{
return nullptr;
}
}
else
{
const DATA* it = m_otherIndex.Find(at);
if (it)
{
return it;
}
}
return nullptr;
}
inline DATA& operator[](const FCacheAddress& at)
{
if (at.ExecutionIndex == 0 && at.ExecutionOptions == 0)
{
return m_index0[at.At];
}
else
{
return m_otherIndex.FindOrAdd(at);
}
}
inline const DATA& operator[](const FCacheAddress& at) const
{
if (at.ExecutionIndex == 0 && at.ExecutionOptions == 0)
{
return m_index0[at.At];
}
else
{
return m_otherIndex[at];
}
}
struct iterator
{
private:
friend class CodeContainer<DATA>;
const CodeContainer<DATA>* container;
typename CodeContainer<DATA>::ArrayDataContainerType::TIterator it0;
typename CodeContainer<DATA>::MapDataContainerType::TIterator it1;
iterator(CodeContainer<DATA>* InContainer)
: container(InContainer)
, it0(InContainer->m_index0.CreateIterator())
, it1(InContainer->m_otherIndex.CreateIterator())
{
}
public:
inline void operator++(int)
{
if (it0)
{
++it0;
}
else
{
++it1;
}
}
iterator operator++()
{
if (it0)
{
++it0;
}
else
{
++it1;
}
return *this;
}
inline bool operator!=(const iterator& o) const
{
return it0 != o.it0 || it1 != o.it1;
}
inline DATA& operator*()
{
if (it0)
{
return *it0;
}
else
{
return it1->Value;
}
}
inline FCacheAddress get_address() const
{
if (it0)
{
return { uint32_t(it0.GetIndex()), 0, 0};
}
else
{
return it1->Key;
}
}
inline bool IsValid() const
{
return bool(it0) || bool(it1);
}
};
inline iterator begin()
{
iterator it(this);
return it;
}
inline int32 GetAllocatedSize() const
{
return m_index0.GetAllocatedSize()
+ m_otherIndex.GetAllocatedSize();
}
private:
// For index 0
ArrayDataContainerType m_index0;
// For index>0
MapDataContainerType m_otherIndex;
};
/** Interface for storage of data while Mutable code is being executed. */
class FProgramCache
{
public:
using AllocType = FDefaultMemoryTrackingAllocator<MemoryCounters::FInternalMemoryCounter>;
template<class Type, class Alloc = AllocType >
using TMemoryTrackedArray = TArray<Type, Alloc>;
TMemoryTrackedArray<ExecutionIndex, TInlineAllocator<4, AllocType>> m_usedRangeIndices;
/** Runtime data for each program op. */
struct FOpExecutionData
{
uint16 OpHitCount;
/** Enabled if the op descriptor has been calculated. */
uint8 IsDescCacheValid : 1;
uint8 IsValueValid : 1;
uint8 IsCacheLocked : 1;
/** The operation DATA_TYPE. */
EDataType DataType;
/** The position in the type-specific array where the result data is stored.
* 0 means index not valid.
* For small types like bool, int and float, the actual value is the index, instead of having their own array.
*/
union
{
int32 DataTypeIndex;
float ScalarResult;
};
};
/** Cached resources while the program is executing.
* first value of the pair:
* 0 : value not valid (not set).
* 1 : valid, not worth freeing for memory
* 2 : valid, worth freeing
*/
CodeContainer<FOpExecutionData> OpExecutionData;
template<class ResourceType>
struct TResourceResult
{
static_assert(TIsDerivedFrom<ResourceType, FResource>::Value);
FCacheAddress OpAddress;
TSharedPtr<const ResourceType> Value = nullptr;
};
static_assert(sizeof(TResourceResult<FImage>) == 24);
static_assert(sizeof(TResourceResult<FMesh>) == 24);
/** */
TMemoryTrackedArray<FVector4f> ColorResults;
TMemoryTrackedArray<TResourceResult<FImage>> ImageResults;
TMemoryTrackedArray<TResourceResult<FMesh>> MeshResults;
TMemoryTrackedArray<TSharedPtr<const FLayout>> LayoutResults;
TMemoryTrackedArray<TSharedPtr<const FInstance>> InstanceResults;
TMemoryTrackedArray<FProjector> ProjectorResults;
TMemoryTrackedArray<TSharedPtr<const String>> StringResults;
TMemoryTrackedArray<TSharedPtr<const FExtensionData>> ExtensionDataResults;
TMemoryTrackedArray<FMatrix44f> MatrixResults;
/** */
inline const ExecutionIndex& GetRangeIndex(uint32_t i)
{
// Make sure we have the default element.
if (m_usedRangeIndices.IsEmpty())
{
m_usedRangeIndices.Push(ExecutionIndex());
}
check(i < uint32_t(m_usedRangeIndices.Num()));
return m_usedRangeIndices[i];
}
//!
inline uint32 GetRangeIndexIndex(const ExecutionIndex& rangeIndex)
{
if (rangeIndex.IsEmpty())
{
return 0;
}
// Make sure we have the default element.
if (m_usedRangeIndices.IsEmpty())
{
m_usedRangeIndices.Push(ExecutionIndex());
}
// Look for or add the new element.
int32 ElemIndex = m_usedRangeIndices.Find(rangeIndex);
if (ElemIndex != INDEX_NONE)
{
return ElemIndex;
}
m_usedRangeIndices.Push(rangeIndex);
return uint32_t(m_usedRangeIndices.Num()) - 1;
}
void Init(uint32 Size)
{
// This clear would prevent live update cache reusal
//OpExecutionData.clear();
OpExecutionData.resize(Size);
if (ColorResults.IsEmpty())
{
// Insert dafault/null values
ColorResults.Add(FVector4f());
ImageResults.Emplace();
LayoutResults.Add(nullptr);
MeshResults.Emplace();
InstanceResults.Add(nullptr);
ProjectorResults.Add(FProjector());
StringResults.Add(nullptr);
MatrixResults.Add(FMatrix44f());
ExtensionDataResults.Add(nullptr);
}
}
void SetUnused(FOpExecutionData& Data)
{
// Only clear datatypes that have results that use relevant amounts of memory
uint32 DataTypeIndex = Data.DataTypeIndex;
Data.IsValueValid = false;
if (DataTypeIndex)
{
check(uint8(Data.DataType) < uint8(EDataType::Count));
switch (Data.DataType)
{
case EDataType::Image:
ImageResults[DataTypeIndex].Value = nullptr;
break;
case EDataType::Mesh:
MeshResults[DataTypeIndex].Value = nullptr;
break;
case EDataType::Instance:
InstanceResults[DataTypeIndex] = nullptr;
break;
case EDataType::ExtensionData:
ExtensionDataResults[DataTypeIndex] = nullptr;
break;
default:
break;
}
}
}
bool IsValid(FCacheAddress at) const
{
if (at.At == 0 || at.At>=OpExecutionData.size_code() )
{
return false;
}
const FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data)
{
return false;
}
// Is it a desc data query?
if (at.Type == FScheduledOp::EType::ImageDesc)
{
return Data->IsDescCacheValid;
}
// It's a full data query.
return Data->IsValueValid != 0;
}
/** */
void CheckHitCountsCleared()
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
//MUTABLE_CPUPROFILER_SCOPE(CheckHitCountsCleared);
//int32 IncorrectCount = 0;
//CodeContainer<int>::iterator it = m_opHitCount.begin();
//for (; it.IsValid(); ++it)
//{
// int32 Count = *it;
// if (Count>0 && Count < UE_MUTABLE_CACHE_COUNT_LIMIT)
// {
// // We don't manage the hitcounts of small types that don't use much memory
// if (m_resources[it.get_address()].Key==2)
// {
// // Op hitcount should have reached 0, otherwise, it means we requested an operation but never read
// // the result.
// ++IncorrectCount;
// }
// }
//}
//if (IncorrectCount > 0)
//{
// UE_LOG(LogMutableCore, Log, TEXT("The op-hit-count didn't hit 0 for %5d operations. This may mean that too much memory is cached."), IncorrectCount);
// //check(false);
//}
#endif
}
void Clear()
{
MUTABLE_CPUPROFILER_SCOPE(ProgramCacheClear);
uint32 CodeSize = OpExecutionData.size_code();
OpExecutionData.clear();
OpExecutionData.resize(CodeSize);
}
void ClearDescCache()
{
MUTABLE_CPUPROFILER_SCOPE(ProgramDescCacheClear);
CodeContainer<FProgramCache::FOpExecutionData>::iterator it = OpExecutionData.begin();
for (; it.IsValid(); ++it)
{
(*it).IsDescCacheValid = false;
}
}
bool GetBool(FCacheAddress at)
{
if (!at.At) return false;
const FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return false;
check(Data->DataType==EDataType::Bool);
return Data->DataTypeIndex!=0;
}
float GetScalar(FCacheAddress at)
{
if (!at.At) return 0.0f;
const FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return 0.0f;
check(Data->DataType == EDataType::Scalar);
return Data->ScalarResult;
}
int32 GetInt(FCacheAddress at)
{
if (!at.At) return 0;
const FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return 0;
check(Data->DataType == EDataType::Int);
return Data->DataTypeIndex;
}
FVector4f GetColour(FCacheAddress at)
{
if (!at.At) return FVector4f();
const FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return FVector4f();
check(Data->DataType == EDataType::Color);
return ColorResults[Data->DataTypeIndex];
}
FMatrix44f GetMatrix(FCacheAddress at)
{
if (!at.At) return FMatrix44f::Identity;
const FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return FMatrix44f::Identity;
check(Data->DataType == EDataType::Matrix);
return MatrixResults[Data->DataTypeIndex];
}
FProjector GetProjector(FCacheAddress at)
{
if (!at.At) return FProjector();
const FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return FProjector();
check(Data->DataType == EDataType::Projector);
return ProjectorResults[Data->DataTypeIndex];
}
TSharedPtr<const FInstance> GetInstance(FCacheAddress at)
{
if (!at.At) return nullptr;
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return nullptr;
check(Data->DataType == EDataType::Instance);
TSharedPtr<const FInstance> Result = InstanceResults[Data->DataTypeIndex];
// We need to decrease the hit-count even if the result is null.
check(Data->OpHitCount > 0);
--Data->OpHitCount;
if (Data->OpHitCount == 0 && !Data->IsCacheLocked)
{
SetUnused(*Data);
}
return Result;
}
TSharedPtr<const FImage> GetImage(FCacheAddress at, bool& bIsLastReference)
{
bIsLastReference = false;
if (!at.At) return nullptr;
if (at.At >= OpExecutionData.size_code()) return nullptr;
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return nullptr;
check(Data->DataType == EDataType::Image);
TSharedPtr<const FImage> Result = ImageResults[Data->DataTypeIndex].Value;
// We need to decrease the hit-count even if the result is null.
check(Data->OpHitCount > 0);
--Data->OpHitCount;
if (Data->OpHitCount == 0 && !Data->IsCacheLocked)
{
SetUnused(*Data);
bIsLastReference = true;
}
return Result;
}
TSharedPtr<const FMesh> GetMesh(FCacheAddress at, bool& bIsLastReference)
{
bIsLastReference = false;
if (!at.At) return nullptr;
if (at.At >= OpExecutionData.size_code()) return nullptr;
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return nullptr;
check(Data->DataType == EDataType::Mesh);
TSharedPtr<const FMesh> Result = MeshResults[Data->DataTypeIndex].Value;
// We need to decrease the hit-count even if the result is null.
check(Data->OpHitCount > 0);
--Data->OpHitCount;
if (Data->OpHitCount == 0 && !Data->IsCacheLocked)
{
SetUnused(*Data);
bIsLastReference = true;
}
return Result;
}
TSharedPtr<const FLayout> GetLayout(FCacheAddress at)
{
if (!at.At) return nullptr;
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return nullptr;
check(Data->DataType == EDataType::Layout);
return LayoutResults[Data->DataTypeIndex];
}
TSharedPtr<const String> GetString(FCacheAddress at)
{
if (!at.At) return nullptr;
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return nullptr;
check(Data->DataType == EDataType::String);
return StringResults[Data->DataTypeIndex];
}
TSharedPtr<const FExtensionData> GetExtensionData(FCacheAddress at)
{
if (!at.At) return nullptr;
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
if (!Data) return nullptr;
check(Data->DataType == EDataType::ExtensionData);
return ExtensionDataResults[Data->DataTypeIndex];
}
void SetValidDesc(FCacheAddress at)
{
check(at.Type == FScheduledOp::EType::ImageDesc);
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
Data->IsDescCacheValid = true;
}
void SetBool(FCacheAddress at, bool v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Bool || Data->DataType == EDataType::None);
Data->DataType = EDataType::Bool;
Data->DataTypeIndex = v;
Data->IsValueValid = true;
}
void SetInt(FCacheAddress at, int32 v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Int || Data->DataType == EDataType::None);
Data->DataType = EDataType::Int;
Data->DataTypeIndex = v;
Data->IsValueValid = true;
}
void SetScalar(FCacheAddress at, float v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Scalar || Data->DataType == EDataType::None);
Data->DataType = EDataType::Scalar;
Data->ScalarResult = v;
Data->IsValueValid = true;
}
void SetColour(FCacheAddress at, const FVector4f& v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Color || Data->DataType == EDataType::None);
Data->DataType = EDataType::Color;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = ColorResults.Num();
ColorResults.Add(v);
}
else
{
ColorResults[Data->DataTypeIndex] = v;
}
check(Data->DataTypeIndex != 0);
}
void SetMatrix(FCacheAddress at, const FMatrix44f& v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Matrix || Data->DataType == EDataType::None);
Data->DataType = EDataType::Matrix;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = MatrixResults.Num();
MatrixResults.Add(v);
}
else
{
MatrixResults[Data->DataTypeIndex] = v;
}
check(Data->DataTypeIndex != 0);
}
void SetProjector(FCacheAddress at, const FProjector& v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Projector || Data->DataType == EDataType::None);
Data->DataType = EDataType::Projector;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = ProjectorResults.Num();
ProjectorResults.Add(v);
}
else
{
ProjectorResults[Data->DataTypeIndex] = v;
}
check(Data->DataTypeIndex != 0);
}
void SetInstance(FCacheAddress at, TSharedPtr<const FInstance> v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Instance || Data->DataType == EDataType::None);
Data->DataType = EDataType::Instance;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = InstanceResults.Num();
InstanceResults.Add(v);
}
else
{
InstanceResults[Data->DataTypeIndex] = v;
}
check(Data->DataTypeIndex != 0);
}
void SetExtensionData(FCacheAddress at, TSharedPtr<const FExtensionData> v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::ExtensionData || Data->DataType == EDataType::None);
Data->DataType = EDataType::ExtensionData;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = ExtensionDataResults.Num();
ExtensionDataResults.Add(v);
}
else
{
ExtensionDataResults[Data->DataTypeIndex] = v;
}
check(Data->DataTypeIndex != 0);
}
void SetImage(FCacheAddress At, TSharedPtr<const FImage> Value)
{
check(At.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(At);
check(Data->DataType == EDataType::Image || Data->DataType == EDataType::None);
Data->DataType = EDataType::Image;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = ImageResults.Num();
ImageResults.Add(TResourceResult<FImage>{At, Value});
}
else
{
ImageResults[Data->DataTypeIndex].Value = Value;
}
check(Data->DataTypeIndex != 0);
mu::UpdateLLMStats();
}
void SetMesh(FCacheAddress At, TSharedPtr<const FMesh> Value)
{
check(At.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(At);
check(Data->DataType == EDataType::Mesh || Data->DataType == EDataType::None);
Data->DataType = EDataType::Mesh;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = MeshResults.Num();
MeshResults.Add(TResourceResult<FMesh>{At, Value});
}
else
{
MeshResults[Data->DataTypeIndex].Value = Value;
}
check(Data->DataTypeIndex != 0);
mu::UpdateLLMStats();
}
void SetLayout(FCacheAddress at, TSharedPtr<const FLayout> v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::Layout || Data->DataType == EDataType::None);
Data->DataType = EDataType::Layout;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = LayoutResults.Num();
LayoutResults.Add(v);
}
else
{
LayoutResults[Data->DataTypeIndex] = v;
}
check(Data->DataTypeIndex != 0);
mu::UpdateLLMStats();
}
void SetString(FCacheAddress at, TSharedPtr<const String> v)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData* Data = OpExecutionData.get_ptr(at);
check(Data->DataType == EDataType::String || Data->DataType == EDataType::None);
Data->DataType = EDataType::String;
Data->IsValueValid = true;
if (!Data->DataTypeIndex)
{
Data->DataTypeIndex = StringResults.Num();
StringResults.Add(v);
}
else
{
StringResults[Data->DataTypeIndex] = v;
}
check(Data->DataTypeIndex!=0);
}
inline void IncreaseHitCount(FCacheAddress at)
{
// Don't count hits for instruction 0, which is always null. It is usually already
// check that At is not 0, and then it is not requested, generating a stray non-zero count
// at its position.
if (at.At)
{
check(at.At < OpExecutionData.size_code());
FOpExecutionData& Data = OpExecutionData[at];
Data.OpHitCount++;
}
}
inline void SetForceCached(OP::ADDRESS at)
{
// \TODO: It only locks at,0,0
if (at)
{
check(at < OpExecutionData.size_code());
FOpExecutionData& Data = OpExecutionData[FCacheAddress(at, 0, 0)];
Data.IsCacheLocked = true;
}
}
};
inline bool operator==(const FCacheAddress& a, const FCacheAddress& b)
{
return a.At == b.At
&&
a.ExecutionIndex == b.ExecutionIndex
&&
a.ExecutionOptions == b.ExecutionOptions
&&
a.Type == b.Type;
}
inline bool operator<(const FCacheAddress& a, const FCacheAddress& b)
{
if (a.At < b.At) return true;
if (a.At > b.At) return false;
if (a.ExecutionIndex < b.ExecutionIndex) return true;
if (a.ExecutionIndex > b.ExecutionIndex) return false;
if (a.ExecutionOptions < b.ExecutionOptions) return true;
if (a.ExecutionOptions > b.ExecutionOptions) return false;
return a.Type < b.Type;
}
/** Data for an instance that is currently being processed in the mutable system. This means it is
* between a BeginUpdate and EndUpdate, or during an "atomic" operation (like generate a single resource).
*/
struct FLiveInstance
{
FInstance::FID InstanceID;
int32 State = 0;
TSharedPtr<const FInstance> Instance;
TSharedPtr<const FModel> Model;
TSharedPtr<FParameters> OldParameters;
/** Mask of the parameters that have changed since the last update.
* Every bit represents a state parameter.
*/
uint64 UpdatedParameters = 0;
/** Cached data for the generation of this instance. */
TSharedPtr<FProgramCache> Cache;
~FLiveInstance()
{
// Manually done to trace mem deallocations
MUTABLE_CPUPROFILER_SCOPE(LiveInstanceDestructor);
Cache = nullptr;
OldParameters = nullptr;
Instance = nullptr;
Model = nullptr;
}
};
/** Struct to manage all the memory allocated for resources used during mutable operation. */
struct FWorkingMemoryManager
{
using MemoryCounter = MemoryCounters::FInternalMemoryCounter;
template<class Type>
using TMemoryTrackedArray = TArray<Type, FDefaultMemoryTrackingAllocator<MemoryCounter>>;
template<class KeyType, class ValueType>
using TMemoryTrackedMap = TMap<KeyType, ValueType, FDefaultMemoryTrackingSetAllocator<MemoryCounter>>;
/** Cached traking for streamed model data for one model. */
struct FModelCacheEntry
{
/** Model who's data is being tracked. */
TWeakPtr<const FModel> Model;
/** For each model rom, the last time its streamed data was used. */
TMemoryTrackedArray<TPair<uint64, uint64>> RomWeights;
/** Count of pending operations for every rom index. */
TMemoryTrackedArray<uint16> PendingOpsPerRom;
};
//! Management of generated resources
//! @{
//! This is used to uniquely identify a generated resource like meshes or images.
struct FGeneratedResourceData
{
/** Model for this resource. */
TWeakPtr<const FModel> Model;
//! The id assigned to the generated resource.
FResourceID Id;
//! The last request operation for this resource
uint32 LastRequestId;
//! An opaque blob with the values of the relevant parameters
TMemoryTrackedArray<uint8> ParameterValuesBlob;
};
//! The last id generated for a resource
uint32 LastResourceKeyId = 0;
//! The last id generated for a resource request. This is used to check the
//! relevancy of the resources when flushing the cache
uint32 LastResourceResquestId = 0;
//! Cached ids for returned assets
//! This is non-persistent runtime data
TMemoryTrackedArray<FGeneratedResourceData> GeneratedResources;
/** */
FResourceID GetResourceKey(const TSharedPtr<const FModel>&, const FParameters*, uint32 ParamListIndex, OP::ADDRESS RootAt);
//! @}
/** Maximum working memory that mutable should be using. */
int64 BudgetBytes = 0;
/** Maximum excess memory reached suring the current operation. */
int64 BudgetExcessBytes = 0;
/** Maximum number of resource keys that will be stored for resource reusal. */
int32 MaxGeneratedResourceCacheSize = 1024;
/** This value is used to track the order of loading of roms. */
uint64 RomTick = 0;
/** Control info for the per-model cache of streamed data. */
TMemoryTrackedArray<FModelCacheEntry> CachePerModel;
/** Data for each mutable instance that is being updated. */
TMemoryTrackedArray<FLiveInstance> LiveInstances;
/** Temporary reference to the memory of the current instance being updated. Only valid during a mutable "atomic" operation, like a BeginUpdate or a GetImage. */
TSharedPtr<FProgramCache> CurrentInstanceCache;
/** Resources that have been used in the past, but haven't been deallocated because they still fitted the memory budget and they could be reused. */
TMemoryTrackedArray<TSharedPtr<FImage>> PooledImages;
/** List of intermediate resources that are not soterd anywhere yet. They are still locally referenced by code. */
TMemoryTrackedArray<TSharedPtr<const FImage>> TempImages;
TMemoryTrackedArray<TSharedPtr<const FMesh>> TempMeshes;
/** List of resources that are currently in any cache position, and the number of positions they are in. */
TMemoryTrackedMap<TSharedPtr<const FResource>, int32> CacheResources;
/** Given a mutable model, find or create its rom cache. */
FModelCacheEntry* FindModelCache(const FModel*);
FModelCacheEntry& FindOrAddModelCache(const TSharedPtr<const FModel>&);
/** Make sure the working memory is below the internal budget, even counting with the passed additional memory.
* An optional function can be passed to "block" the unload of certain roms of data.
* Return true if it succeeded, false otherwise.
*/
bool EnsureBudgetBelow(uint64 AdditionalMemory);
/** Return true if the memory budget is 90% full. */
bool IsMemoryBudgetFull() const;
/** Calculate the current usage of memory as used to calculate the budget. */
int64 GetCurrentMemoryBytes() const;
/** Register that a specific rom has been requested and update the heuristics to keep it in memory. */
void MarkRomUsed( int32 RomIndex, const TSharedPtr<const FModel>& );
/** */
[[nodiscard]] TSharedPtr<FImage> CreateImage(uint32 SizeX, uint32 SizeY, uint32 Lods, EImageFormat Format, EInitializationType Init)
{
CheckRunnerThread();
uint32 DataSize = FImage::CalculateDataSize(SizeX, SizeY, Lods, Format);
// Look for an unused image in the pool that can be reused
int32 PooledImageCount = PooledImages.Num();
for (int Index = 0; DataSize>0 && Index<PooledImageCount; ++Index)
{
TSharedPtr<FImage>& Candidate = PooledImages[Index];
if (Candidate->GetFormat() == Format
&& Candidate->GetSizeX() == SizeX
&& Candidate->GetSizeY() == SizeY
&& Candidate->GetLODCount() == Lods
)
{
TSharedPtr<FImage> Result = Candidate;
PooledImages.RemoveAtSwap(Index);
if (Init == EInitializationType::Black)
{
Result->InitToBlack();
}
else
{
Result->Flags = 0;
Result->RelevancyMinY = 0;
Result->RelevancyMaxY = 0;
}
return Result;
}
}
// Make room in the budget
EnsureBudgetBelow(DataSize);
// Create it
TSharedPtr<FImage> Result = MakeShared<FImage>(SizeX, SizeY, Lods, Format, Init);
TempImages.Add(Result);
return Result;
}
/** Ref will be nulled and relesed in any case. */
[[nodiscard]] TSharedPtr<FImage> CloneOrTakeOver(TSharedPtr<const FImage>& Resource)
{
CheckRunnerThread();
TempImages.RemoveSingle(Resource);
check(!TempImages.Contains(Resource));
check(!PooledImages.Contains(Resource));
TSharedPtr<FImage> Result;
if (!Resource.IsUnique())
{
// TODO: try to grab from the pool
uint32 DataSize = Resource->GetDataSize();
EnsureBudgetBelow(DataSize);
Result = Resource->Clone();
Release(Resource);
}
else
{
Result = ConstCastSharedPtr<FImage>(Resource);
Resource = nullptr;
}
return Result;
}
/** */
void Release(TSharedPtr<const FImage>& Resource)
{
CheckRunnerThread();
if (!Resource)
{
return;
}
const int32 ResourceDataSize = Resource->GetDataSize();
TempImages.RemoveSingle(Resource);
check(!TempImages.Contains(Resource));
check(!PooledImages.Contains(Resource));
if (IsBudgetTemp(Resource))
{
// Check if we are exceeding the budget
bool bInBudget = EnsureBudgetBelow(ResourceDataSize);
if (bInBudget)
{
PooledImages.Add(ConstCastSharedPtr<FImage>(Resource));
}
}
else
{
// Check if we are exceeding the budget
EnsureBudgetBelow(0);
}
Resource = nullptr;
}
/** */
void Release(TSharedPtr<FImage>& Resource)
{
CheckRunnerThread();
if (!Resource)
{
return;
}
const int32 ResourceDataSize = Resource->GetDataSize();
TempImages.RemoveSingle(Resource);
check(!TempImages.Contains(Resource));
check(!PooledImages.Contains(Resource));
if (IsBudgetTemp(Resource))
{
// Check if we are exceeding the budget
bool bInBudget = EnsureBudgetBelow(ResourceDataSize);
if (bInBudget)
{
PooledImages.Add(Resource);
}
}
else
{
// Check if we are exceeding the budget
EnsureBudgetBelow(0);
}
Resource = nullptr;
}
[[nodiscard]] TSharedPtr<FMesh> CreateMesh(int32 BudgetReserveSize)
{
CheckRunnerThread();
EnsureBudgetBelow(BudgetReserveSize);
TSharedPtr<FMesh> Result = MakeShared<FMesh>();
TempMeshes.Add(Result);
return Result;
}
[[nodiscard]] TSharedPtr<FMesh> CloneOrTakeOver(TSharedPtr<const FMesh>& Resource)
{
CheckRunnerThread();
const int32 ResourceDataSize = Resource->GetDataSize();
TempMeshes.RemoveSingle(Resource);
TSharedPtr<FMesh> Result;
if (!Resource.IsUnique())
{
Result = CreateMesh(ResourceDataSize);
Result->CopyFrom(*Resource);
Release(Resource);
}
else
{
Result = TSharedPtr<FMesh>(ConstCastSharedPtr<FMesh>(Resource));
Resource = nullptr;
}
return Result;
}
void Release(TSharedPtr<const FMesh>& Resource)
{
CheckRunnerThread();
if (!Resource)
{
return;
}
TempMeshes.RemoveSingle(Resource);
check(!TempMeshes.Contains(Resource));
EnsureBudgetBelow(0);
Resource = nullptr;
}
void Release(TSharedPtr<FMesh>& Resource)
{
TSharedPtr<const FMesh> ConstPtr = Resource;
Release(ConstPtr);
Resource = nullptr;
}
/** */
[[nodiscard]] TSharedPtr<const FMesh> LoadMesh(const FCacheAddress& From, bool bTakeOwnership = false)
{
bool bIsLastReference = false;
TSharedPtr<const FMesh> Result = CurrentInstanceCache->GetMesh(From, bIsLastReference);
if (!Result)
{
return nullptr;
}
// If we retrieved the last reference to this resource in "From" cache position (it could still be in other cache positions as well)
if (bIsLastReference)
{
int32* CountPtr = CacheResources.Find(Result);
check(CountPtr);
*CountPtr = (*CountPtr) - 1;
if (!*CountPtr)
{
CacheResources.FindAndRemoveChecked(Result);
}
}
if (!bTakeOwnership && Result.IsUnique())
{
TempMeshes.Add(Result);
}
return Result;
}
/** */
[[nodiscard]] TSharedPtr<const FImage> LoadImage(const FCacheAddress& From, bool bTakeOwnership = false)
{
bool bIsLastReference = false;
TSharedPtr<const FImage> Result = CurrentInstanceCache->GetImage(From, bIsLastReference);
if (!Result)
{
return nullptr;
}
// If we retrieved the last reference to this resource in "From" cache position (it could still be in other cache positions as well)
if (bIsLastReference)
{
int32* CountPtr = CacheResources.Find(Result);
check(CountPtr);
*CountPtr = (*CountPtr) - 1;
if (!*CountPtr)
{
CacheResources.FindAndRemoveChecked(Result);
}
}
if (!bTakeOwnership && Result.IsUnique())
{
TempImages.Add(Result);
}
return Result;
}
/** */
void StoreImage(const FCacheAddress& To, TSharedPtr<const FImage> Resource)
{
if (Resource)
{
int32 ResourceDataSize = Resource->GetDataSize();
TempImages.RemoveSingle(Resource);
check(!TempImages.Contains(Resource));
int32& Count = CacheResources.FindOrAdd(Resource, 0);
++Count;
}
CurrentInstanceCache->SetImage(To, Resource);
}
void StoreMesh(const FCacheAddress& To, TSharedPtr<const FMesh> Resource)
{
if (Resource)
{
//Resource->CheckIntegrity();
TempMeshes.RemoveSingle(Resource);
int32& Count = CacheResources.FindOrAdd(Resource, 0);
++Count;
}
CurrentInstanceCache->SetMesh(To, Resource);
}
/** Return true if the resource is not in any cache (0,1,rom). */
bool IsBudgetTemp(const TSharedPtr<const FResource>& Resource)
{
if (!Resource)
{
return false;
}
bool bIsTemp = Resource.IsUnique();
return bIsTemp;
}
int32 GetPooledBytes() const
{
int32 Result = 0;
for (const TSharedPtr<FImage>& Value : PooledImages)
{
Result += Value->GetDataSize();
}
return Result;
}
int32 GetTempBytes() const
{
int32 Result = 0;
for (const TSharedPtr<const FImage>& Value : TempImages)
{
Result += Value->GetDataSize();
}
for (const TSharedPtr<const FMesh>& Value : TempMeshes)
{
Result += Value->GetDataSize();
}
return Result;
}
int32 GetRomBytes() const
{
int32 Result = 0;
TArray<const FModel*> Models;
for (const FLiveInstance& Instance : LiveInstances)
{
Models.AddUnique(Instance.Model.Get());
}
// Data stored per-model, but related to instance construction
for (const FModel* Model : Models)
{
// Count streamable and currently-loaded resources
const FProgram& Program = Model->GetPrivate()->Program;
for (const TPair<uint32,TSharedPtr<const FImage>>& ImageLOD : Program.ConstantImageLODsStreamed)
{
if (ImageLOD.Value)
{
Result += ImageLOD.Value->GetDataSize();
}
}
for (const TPair<uint32, TSharedPtr<const FMesh>>& Rom : Program.ConstantMeshesStreamed)
{
if (Rom.Value)
{
Result += Rom.Value->GetDataSize();
}
}
}
return Result;
}
int32 GetTrackedCacheBytes() const
{
int32 Result = 0;
for (const TTuple<TSharedPtr<const FResource>,int32>& It : CacheResources)
{
Result += It.Key->GetDataSize();
}
return Result;
}
/** Calculate the amount of bytes in data cached in the level 0 and 1 cache in all live instances. */
int32 GetCacheBytes() const
{
int32 Result = 0;
TSet<const FResource*> Cache0Unique;
TSet<const FResource*> Cache1Unique;
for (const FLiveInstance& Instance : LiveInstances)
{
CodeContainer<FProgramCache::FOpExecutionData>::iterator it = Instance.Cache->OpExecutionData.begin();
for (; it.IsValid(); ++it)
{
if (!(*it).DataTypeIndex)
{
continue;
}
TSet<const FResource*>* TargetSet = &Cache0Unique;
if ((*it).IsCacheLocked)
{
TargetSet = &Cache1Unique;
}
const FResource* Value = nullptr;
switch ((*it).DataType)
{
case EDataType::Image:
Value = Instance.Cache->ImageResults[(*it).DataTypeIndex].Value.Get();
if (Value)
{
TargetSet->Add(Value);
}
break;
case EDataType::Mesh:
Value = Instance.Cache->MeshResults[(*it).DataTypeIndex].Value.Get();
if (Value)
{
TargetSet->Add(Value);
}
break;
default:
break;
}
}
}
// Merge
Cache0Unique.Append(Cache1Unique);
// Count
for (const FResource* Value : Cache0Unique)
{
Result += Value->GetDataSize();
}
return Result;
}
/** Remove all intermediate data (big and small) from the memory except for the one that has been explicitely
* marked as state cache.
*/
void ClearCacheLayer0()
{
check(CurrentInstanceCache);
MUTABLE_CPUPROFILER_SCOPE(ClearLayer0);
CodeContainer<FProgramCache::FOpExecutionData>::iterator it = CurrentInstanceCache->OpExecutionData.begin();
for (; it.IsValid(); ++it)
{
FProgramCache::FOpExecutionData& Data = *it;
if (!Data.DataTypeIndex
||
Data.IsCacheLocked)
{
continue;
}
switch (Data.DataType)
{
case EDataType::Image:
{
TSharedPtr<const FImage> Value = CurrentInstanceCache->ImageResults[Data.DataTypeIndex].Value;
if (Value)
{
CacheResources.Remove(Value);
CurrentInstanceCache->ImageResults[Data.DataTypeIndex].Value = nullptr;
}
break;
}
case EDataType::Mesh:
{
TSharedPtr<const FMesh> Value = CurrentInstanceCache->MeshResults[Data.DataTypeIndex].Value;
if (Value)
{
CacheResources.Remove(Value);
CurrentInstanceCache->MeshResults[Data.DataTypeIndex].Value = nullptr;
}
break;
}
case EDataType::Layout:
{
TSharedPtr<const FLayout> Value = CurrentInstanceCache->LayoutResults[Data.DataTypeIndex];
CurrentInstanceCache->LayoutResults[Data.DataTypeIndex] = nullptr;
break;
}
case EDataType::Instance:
{
TSharedPtr<const FInstance> Value = CurrentInstanceCache->InstanceResults[Data.DataTypeIndex];
CurrentInstanceCache->InstanceResults[Data.DataTypeIndex] = nullptr;
break;
}
default:
break;
}
Data.OpHitCount = 0;
Data.IsValueValid = false;
}
}
/** Remove all intermediate data (big and small) from the memory including the one that has been explicitely
* marked as state cache.
*/
void ClearCacheLayer1()
{
MUTABLE_CPUPROFILER_SCOPE(ClearLayer1);
CodeContainer<FProgramCache::FOpExecutionData>::iterator it = CurrentInstanceCache->OpExecutionData.begin();
for (; it.IsValid(); ++it)
{
FProgramCache::FOpExecutionData& Data = *it;
if (!Data.DataTypeIndex)
{
continue;
}
switch (Data.DataType)
{
case EDataType::Image:
{
TSharedPtr<const FImage> Value = CurrentInstanceCache->ImageResults[Data.DataTypeIndex].Value;
CacheResources.Remove(Value);
CurrentInstanceCache->ImageResults[Data.DataTypeIndex].Value = nullptr;
break;
}
case EDataType::Mesh:
{
TSharedPtr<const FMesh> Value = CurrentInstanceCache->MeshResults[Data.DataTypeIndex].Value;
CacheResources.Remove(Value);
CurrentInstanceCache->MeshResults[Data.DataTypeIndex].Value = nullptr;
break;
}
case EDataType::Layout:
{
TSharedPtr<const FLayout> Value = CurrentInstanceCache->LayoutResults[Data.DataTypeIndex];
CurrentInstanceCache->LayoutResults[Data.DataTypeIndex] = nullptr;
break;
}
case EDataType::Instance:
{
TSharedPtr<const FInstance> Value = CurrentInstanceCache->InstanceResults[Data.DataTypeIndex];
CurrentInstanceCache->InstanceResults[Data.DataTypeIndex] = nullptr;
break;
}
default:
break;
}
Data.OpHitCount = 0;
Data.IsValueValid = false;
}
}
void LogWorkingMemory(const class CodeRunner* CurrentRunner) const;
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
static constexpr uint64 InvalidRunnerId = 0;
std::atomic<uint64> DebugCurrentRunnerId {1};
/** Temp variable with the ID of the thread running code, for debugging. */
uint32 DebugRunnerThreadID = FThread::InvalidThreadId;
uint64 DebugRunnerID = InvalidRunnerId;
#endif
/** This is a development-only check to make sure calls to resource management happen in the correct thread. */
FORCEINLINE void BeginRunnerThread()
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// If this check fails it means have not set up correctly all the paths to debug threading for resource management.
check(DebugRunnerThreadID == FThread::InvalidThreadId);
check(DebugRunnerID == InvalidRunnerId)
DebugRunnerThreadID = FPlatformTLS::GetCurrentThreadId();
DebugRunnerID = ++DebugCurrentRunnerId;
#endif
}
/** This is a development-only check to make sure calls to resource management happen in the correct thread. */
FORCEINLINE void ResetRunnerThread()
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// If this check fails it means have not set up correctly all the paths to debug threading for resource management.
check(DebugRunnerThreadID == FThread::InvalidThreadId);
check(DebugRunnerID != InvalidRunnerId)
DebugRunnerThreadID = FPlatformTLS::GetCurrentThreadId();
#endif
}
/** This is a development-only check to make sure calls to resource management happen in the correct thread. */
FORCEINLINE void InvalidateRunnerThread()
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// If this check fails it means have not set up correctly all the paths to debug threading for resource management.
check(DebugRunnerThreadID != FThread::InvalidThreadId);
check(DebugRunnerID != InvalidRunnerId)
DebugRunnerThreadID = FThread::InvalidThreadId;
#endif
}
/** This is a development-only check to make sure calls to resource management happen in the correct thread. */
FORCEINLINE void CheckRunnerThread()
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// If this check fails it means have not set up correctly all the paths to debug threading for resource management.
check(DebugRunnerThreadID != FThread::InvalidThreadId);
check(DebugRunnerID != InvalidRunnerId);
// If this check fails it means we are doing resource management from a thread that is not the CodeRunner::RunCode
// thread, and this is not allowed.
check(DebugRunnerThreadID == FPlatformTLS::GetCurrentThreadId());
check(DebugRunnerID == DebugCurrentRunnerId);
#endif
}
/** This is a development-only check to make sure calls to resource management happen in the correct thread. */
FORCEINLINE void EndRunnerThread()
{
CurrentInstanceCache->CheckHitCountsCleared();
// If this check fails it means some operation is not correctly handling resource management and didn't release
// a resource it created.
// This should be reported and reviewed, but it is not fatal. Some unnecessary memory may be used temporarily.
ensure(TempImages.Num() == 0);
ensure(TempMeshes.Num() == 0);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// If this check fails it means have not set up correctly all the paths to debug threading for resource management.
check(DebugRunnerThreadID != FThread::InvalidThreadId);
check(DebugRunnerID != InvalidRunnerId);
DebugRunnerThreadID = FThread::InvalidThreadId;
DebugRunnerID = InvalidRunnerId;
#endif
}
};
/** */
class FSystem::Private
{
friend class System;
public:
Private(const FSettings&);
~Private();
//-----------------------------------------------------------------------------------------
//! Own interface
//-----------------------------------------------------------------------------------------
/** This method can be used to internally prepare for code execution. */
MUTABLERUNTIME_API void BeginBuild(const TSharedPtr<const FModel>&);
MUTABLERUNTIME_API void EndBuild();
MUTABLERUNTIME_API bool BuildBool(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS) ;
MUTABLERUNTIME_API int32 BuildInt(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS) ;
MUTABLERUNTIME_API float BuildScalar(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS) ;
MUTABLERUNTIME_API FVector4f BuildColour(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS) ;
MUTABLERUNTIME_API TSharedPtr<const String> BuildString(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS) ;
MUTABLERUNTIME_API TSharedPtr<const FImage> BuildImage(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS, int32 MipsToSkip, int32 LOD);
MUTABLERUNTIME_API TSharedPtr<const FMesh> BuildMesh(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS, EMeshContentFlags MeshContentFilter);
MUTABLERUNTIME_API TSharedPtr<const FInstance> BuildInstance(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS);
MUTABLERUNTIME_API TSharedPtr<const FLayout> BuildLayout(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS) ;
MUTABLERUNTIME_API FProjector BuildProjector(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS) ;
//!
FSettings Settings;
//! Data streaming interface, if any.
TSharedPtr<FModelReader> StreamInterface;
TSharedPtr<FExternalResourceProvider> ExternalResourceProvider;
/** Counter used to generate unique IDs for every new instance created in the system. */
FInstance::FID LastInstanceID = 0;
/** This flag is turned on when a streaming error or similar happens. Results are not usable.
* This should only happen in-editor.
*/
bool bUnrecoverableError = false;
/** If this is set, it will be tried first instead of the internal formatting function. */
FImageOperator::FImagePixelFormatFunc ImagePixelFormatOverride;
/** */
FWorkingMemoryManager WorkingMemoryManager;
/** The pointer returned by this function is only valid for the duration of the current mutable operation. */
inline FLiveInstance* FindLiveInstance(FInstance::FID);
//!
bool CheckUpdatedParameters( const FLiveInstance*, const TSharedPtr<const FParameters>&, uint64& OutUpdatedParameters );
void RunCode(const TSharedPtr<const FModel>&, const FParameters*, OP::ADDRESS, uint32 LODs = FSystem::AllLODs, uint8 ExecutionOptions = 0, int32 LOD = 0);
//!
void PrepareCache(const FModel*, int32 State);
//! Update some mutable core unreal stats.
void UpdateStats();
};
}