UnityGame/Library/PackageCache/com.unity.render-pipelines.core/Runtime/Common/ContextContainer.cs

193 lines
7.3 KiB
C#
Raw Normal View History

2024-10-27 10:53:47 +03:00
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();
}
}