A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Runtime.CompilerServices;
4using Unity.Mathematics;
5
6namespace UnityEngine.Rendering
7{
8 /// <summary>
9 /// ContextContainer is a Dictionary like storage where the key is a generic parameter and the value is of the same type.
10 /// </summary>
11 public class ContextContainer : IDisposable
12 {
13 Item[] m_Items = new Item[64];
14 List<uint> m_ActiveItemIndices = new();
15
16 /// <summary>
17 /// Retrives a T of class <c>ContextContainerItem</c> if it was previously created without it being disposed.
18 /// </summary>
19 /// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
20 /// <returns>The value created previously using <![CDATA[Create<T>]]> .</returns>
21 /// <exception cref="InvalidOperationException">This is thown if the value isn't previously created.</exception>
22 [MethodImpl(MethodImplOptions.AggressiveInlining)]
23 public T Get<T>()
24 where T : ContextItem, new()
25 {
26 var typeId = TypeId<T>.value;
27 if (!Contains(typeId))
28 {
29 throw new InvalidOperationException($"Type {typeof(T).FullName} has not been created yet.");
30 }
31
32 return (T) m_Items[typeId].storage;
33
34 }
35
36 /// <summary>
37 /// Creates the value of type T.
38 /// </summary>
39 /// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
40 /// <returns>The value of type T created inside the <c>ContextContainer</c>.</returns>
41 /// <exception cref="InvalidOperationException">Thown if you try to create the value of type T agian after it is already created.</exception>
42 [MethodImpl(MethodImplOptions.AggressiveInlining)]
43#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
44 public T Create<T>([CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "")
45#else
46 public T Create<T>()
47#endif
48 where T : ContextItem, new()
49 {
50 var typeId = TypeId<T>.value;
51 if (Contains(typeId))
52 {
53#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
54 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}.");
55#else
56 throw new InvalidOperationException($"Type {typeof(T).FullName} has already been created.");
57#endif
58 }
59
60#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
61 return CreateAndGetData<T>(typeId, lineNumber, memberName, filePath);
62#else
63 return CreateAndGetData<T>(typeId);
64#endif
65 }
66
67 /// <summary>
68 /// Creates the value of type T if the value is not previously created otherwise try to get the value of type T.
69 /// </summary>
70 /// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
71 /// <returns>Returns the value of type T which is created or retrived.</returns>
72 [MethodImpl(MethodImplOptions.AggressiveInlining)]
73#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
74 public T GetOrCreate<T>([CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "")
75#else
76 public T GetOrCreate<T>()
77#endif
78 where T : ContextItem, new()
79 {
80 var typeId = TypeId<T>.value;
81 if (Contains(typeId))
82 {
83 return (T) m_Items[typeId].storage;
84 }
85
86
87#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
88 return CreateAndGetData<T>(typeId, lineNumber, memberName, filePath);
89#else
90 return CreateAndGetData<T>(typeId);
91#endif
92 }
93
94 /// <summary>
95 /// Check if the value of type T has previously been created.
96 /// </summary>
97 /// <typeparam name="T">Is the class which you are trying to fetch. T has to inherit from <c>ContextContainerItem</c></typeparam>
98 /// <returns>Returns true if the value exists and false otherwise.</returns>
99 [MethodImpl(MethodImplOptions.AggressiveInlining)]
100 public bool Contains<T>()
101 where T : ContextItem, new()
102 {
103 var typeId = TypeId<T>.value;
104 return Contains(typeId);
105 }
106
107 [MethodImpl(MethodImplOptions.AggressiveInlining)]
108 bool Contains(uint typeId) => typeId < m_Items.Length && m_Items[typeId].isSet;
109
110#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
111 T CreateAndGetData<T>(uint typeId, int lineNumber, string memberName, string filePath)
112#else
113 T CreateAndGetData<T>(uint typeId)
114#endif
115 where T : ContextItem, new()
116 {
117 if (m_Items.Length <= typeId)
118 {
119 var items = new Item[math.max(math.ceilpow2(s_TypeCount), m_Items.Length * 2)];
120 for (var i = 0; i < m_Items.Length; i++)
121 {
122 items[i] = m_Items[i];
123 }
124 m_Items = items;
125 }
126
127 m_ActiveItemIndices.Add(typeId);
128 ref var item = ref m_Items[typeId];
129 item.storage ??= new T();
130 item.isSet = true;
131#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
132 item.lineNumber = lineNumber;
133 item.memberName = memberName;
134 item.filePath = filePath;
135#endif
136
137 return (T)item.storage;
138 }
139
140 /// <summary>
141 /// Call Dispose to remove the created values.
142 /// </summary>
143 public void Dispose()
144 {
145 foreach (var index in m_ActiveItemIndices)
146 {
147 ref var item = ref m_Items[index];
148 item.storage.Reset();
149 item.isSet = false;
150 }
151
152 m_ActiveItemIndices.Clear();
153 }
154
155 static uint s_TypeCount;
156
157 static class TypeId<T>
158 {
159 public static uint value = s_TypeCount++;
160 }
161
162 struct Item
163 {
164 public ContextItem storage;
165 public bool isSet;
166#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
167 public int lineNumber;
168 public string memberName;
169 public string filePath;
170#endif
171 }
172
173 }
174
175 /// <summary>
176 /// 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>.
177 /// </summary>
178 public abstract class ContextItem
179 {
180 /// <summary>
181 /// Resets the object so it can be used as a new instance next time it is created.
182 /// To avoid memory allocations and generating garbage, the system reuses objects.
183 /// This function should clear the object so it can be reused without leaking any
184 /// information (e.g. pointers to objects that will no longer be valid to access).
185 /// So it is important the implementation carefully clears all relevant members.
186 /// Note that this is different from a Dispose or Destructor as the object in not
187 /// freed but reset. This can be useful when havin large sub-allocated objects like
188 /// arrays or lists which can be cleared and reused without re-allocating.
189 /// </summary>
190 public abstract void Reset();
191 }
192}