193 lines
7.3 KiB
C#
193 lines
7.3 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using Unity.Mathematics;
|
||
|
|
||
|
namespace UnityEngine.Rendering
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// ContextContainer is a Dictionary like storage where the key is a generic parameter and the value is of the same type.
|
||
|
/// </summary>
|
||
|
public class ContextContainer : IDisposable
|
||
|
{
|
||
|
Item[] m_Items = new Item[64];
|
||
|
List<uint> m_ActiveItemIndices = new();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Retrives a T of class <c>ContextContainerItem</c> if it was previously created without it being disposed.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
|
||
|
/// <returns>The value created previously using <![CDATA[Create<T>]]> .</returns>
|
||
|
/// <exception cref="InvalidOperationException">This is thown if the value isn't previously created.</exception>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public T Get<T>()
|
||
|
where T : ContextItem, new()
|
||
|
{
|
||
|
var typeId = TypeId<T>.value;
|
||
|
if (!Contains(typeId))
|
||
|
{
|
||
|
throw new InvalidOperationException($"Type {typeof(T).FullName} has not been created yet.");
|
||
|
}
|
||
|
|
||
|
return (T) m_Items[typeId].storage;
|
||
|
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates the value of type T.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
|
||
|
/// <returns>The value of type T created inside the <c>ContextContainer</c>.</returns>
|
||
|
/// <exception cref="InvalidOperationException">Thown if you try to create the value of type T agian after it is already created.</exception>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
|
||
|
public T Create<T>([CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "")
|
||
|
#else
|
||
|
public T Create<T>()
|
||
|
#endif
|
||
|
where T : ContextItem, new()
|
||
|
{
|
||
|
var typeId = TypeId<T>.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<T>(typeId, lineNumber, memberName, filePath);
|
||
|
#else
|
||
|
return CreateAndGetData<T>(typeId);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates the value of type T if the value is not previously created otherwise try to get the value of type T.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
|
||
|
/// <returns>Returns the value of type T which is created or retrived.</returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
|
||
|
public T GetOrCreate<T>([CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "")
|
||
|
#else
|
||
|
public T GetOrCreate<T>()
|
||
|
#endif
|
||
|
where T : ContextItem, new()
|
||
|
{
|
||
|
var typeId = TypeId<T>.value;
|
||
|
if (Contains(typeId))
|
||
|
{
|
||
|
return (T) m_Items[typeId].storage;
|
||
|
}
|
||
|
|
||
|
|
||
|
#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
|
||
|
return CreateAndGetData<T>(typeId, lineNumber, memberName, filePath);
|
||
|
#else
|
||
|
return CreateAndGetData<T>(typeId);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Check if the value of type T has previously been created.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
|
||
|
/// <returns>Returns true if the value exists and false otherwise.</returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public bool Contains<T>()
|
||
|
where T : ContextItem, new()
|
||
|
{
|
||
|
var typeId = TypeId<T>.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<T>(uint typeId, int lineNumber, string memberName, string filePath)
|
||
|
#else
|
||
|
T CreateAndGetData<T>(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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Call Dispose to remove the created values.
|
||
|
/// </summary>
|
||
|
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<T>
|
||
|
{
|
||
|
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
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This is needed to add the data to <c>ContextContainer</c> and will control how the data are removed when calling Dispose on the <c>ContextContainer</c>.
|
||
|
/// </summary>
|
||
|
public abstract class ContextItem
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
public abstract void Reset();
|
||
|
}
|
||
|
}
|