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();
}
}