using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using Unity.Mathematics; namespace UnityEngine.Rendering { /// /// ContextContainer is a Dictionary like storage where the key is a generic parameter and the value is of the same type. /// public class ContextContainer : IDisposable { Item[] m_Items = new Item[64]; List m_ActiveItemIndices = new(); /// /// Retrives a T of class ContextContainerItem if it was previously created without it being disposed. /// /// Is the class which you are trying to fetch. T has to inherit from ContextContainerItem /// The value created previously using ]]> . /// This is thown if the value isn't previously created. [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Get() where T : ContextItem, new() { var typeId = TypeId.value; if (!Contains(typeId)) { throw new InvalidOperationException($"Type {typeof(T).FullName} has not been created yet."); } return (T) m_Items[typeId].storage; } /// /// Creates the value of type T. /// /// Is the class which you are trying to fetch. T has to inherit from ContextContainerItem /// The value of type T created inside the ContextContainer. /// Thown if you try to create the value of type T agian after it is already created. [MethodImpl(MethodImplOptions.AggressiveInlining)] #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG public T Create([CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "") #else public T Create() #endif where T : ContextItem, new() { var typeId = TypeId.value; if (Contains(typeId)) { #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG throw new InvalidOperationException($"Type {typeof(T).FullName} has already been created. It was previously created in member {m_Items[typeId].memberName} at line {m_Items[typeId].lineNumber} in {m_Items[typeId].filePath}."); #else throw new InvalidOperationException($"Type {typeof(T).FullName} has already been created."); #endif } #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG return CreateAndGetData(typeId, lineNumber, memberName, filePath); #else return CreateAndGetData(typeId); #endif } /// /// Creates the value of type T if the value is not previously created otherwise try to get the value of type T. /// /// Is the class which you are trying to fetch. T has to inherit from ContextContainerItem /// Returns the value of type T which is created or retrived. [MethodImpl(MethodImplOptions.AggressiveInlining)] #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG public T GetOrCreate([CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "") #else public T GetOrCreate() #endif where T : ContextItem, new() { var typeId = TypeId.value; if (Contains(typeId)) { return (T) m_Items[typeId].storage; } #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG return CreateAndGetData(typeId, lineNumber, memberName, filePath); #else return CreateAndGetData(typeId); #endif } /// /// Check if the value of type T has previously been created. /// /// Is the class which you are trying to fetch. T has to inherit from ContextContainerItem /// Returns true if the value exists and false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains() where T : ContextItem, new() { var typeId = TypeId.value; return Contains(typeId); } [MethodImpl(MethodImplOptions.AggressiveInlining)] bool Contains(uint typeId) => typeId < m_Items.Length && m_Items[typeId].isSet; #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG T CreateAndGetData(uint typeId, int lineNumber, string memberName, string filePath) #else T CreateAndGetData(uint typeId) #endif where T : ContextItem, new() { if (m_Items.Length <= typeId) { var items = new Item[math.max(math.ceilpow2(s_TypeCount), m_Items.Length * 2)]; for (var i = 0; i < m_Items.Length; i++) { items[i] = m_Items[i]; } m_Items = items; } m_ActiveItemIndices.Add(typeId); ref var item = ref m_Items[typeId]; item.storage ??= new T(); item.isSet = true; #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG item.lineNumber = lineNumber; item.memberName = memberName; item.filePath = filePath; #endif return (T)item.storage; } /// /// Call Dispose to remove the created values. /// public void Dispose() { foreach (var index in m_ActiveItemIndices) { ref var item = ref m_Items[index]; item.storage.Reset(); item.isSet = false; } m_ActiveItemIndices.Clear(); } static uint s_TypeCount; static class TypeId { public static uint value = s_TypeCount++; } struct Item { public ContextItem storage; public bool isSet; #if CONTEXT_CONTAINER_ALLOCATOR_DEBUG public int lineNumber; public string memberName; public string filePath; #endif } } /// /// This is needed to add the data to ContextContainer and will control how the data are removed when calling Dispose on the ContextContainer. /// public abstract class ContextItem { /// /// Resets the object so it can be used as a new instance next time it is created. /// To avoid memory allocations and generating garbage, the system reuses objects. /// This function should clear the object so it can be reused without leaking any /// information (e.g. pointers to objects that will no longer be valid to access). /// So it is important the implementation carefully clears all relevant members. /// Note that this is different from a Dispose or Destructor as the object in not /// freed but reset. This can be useful when havin large sub-allocated objects like /// arrays or lists which can be cleared and reused without re-allocating. /// public abstract void Reset(); } }