A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using Unity.Collections;
5using Unity.Collections.LowLevel.Unsafe;
6using UnityEngine.InputSystem.Utilities;
7
8////TODO: batch append method
9
10////TODO: switch to NativeArray long length (think we have it in Unity 2018.3)
11
12////REVIEW: can we get rid of kBufferSizeUnknown and force size to always be known? (think this would have to wait until
13//// the native changes have landed in 2018.3)
14
15namespace UnityEngine.InputSystem.LowLevel
16{
17 /// <summary>
18 /// A buffer of raw memory holding a sequence of <see cref="InputEvent">input events</see>.
19 /// </summary>
20 /// <remarks>
21 /// Note that event buffers are not thread-safe. It is not safe to write events to the buffer
22 /// concurrently from multiple threads. It is, however, safe to traverse the contents of an
23 /// existing buffer from multiple threads as long as it is not mutated at the same time.
24 /// </remarks>
25 public unsafe struct InputEventBuffer : IEnumerable<InputEventPtr>, IDisposable, ICloneable
26 {
27 public const long BufferSizeUnknown = -1;
28
29 /// <summary>
30 /// Total number of events in the buffer.
31 /// </summary>
32 /// <value>Number of events currently in the buffer.</value>
33 public int eventCount => m_EventCount;
34
35 /// <summary>
36 /// Size of the used portion of the buffer in bytes. Use <see cref="capacityInBytes"/> to
37 /// get the total allocated size.
38 /// </summary>
39 /// <value>Used size of buffer in bytes.</value>
40 /// <remarks>
41 /// If the size is not known, returns <see cref="BufferSizeUnknown"/>.
42 ///
43 /// Note that the size does not usually correspond to <see cref="eventCount"/> times <c>sizeof(InputEvent)</c>.
44 /// as <see cref="InputEvent"/> instances are variable in size.
45 /// </remarks>
46 public long sizeInBytes => m_SizeInBytes;
47
48 /// <summary>
49 /// Total size of allocated memory in bytes. This value minus <see cref="sizeInBytes"/> is the
50 /// spare capacity of the buffer. Will never be less than <see cref="sizeInBytes"/>.
51 /// </summary>
52 /// <value>Size of allocated memory in bytes.</value>
53 /// <remarks>
54 /// A buffer's capacity determines how much event data can be written to the buffer before it has to be
55 /// reallocated.
56 /// </remarks>
57 public long capacityInBytes
58 {
59 get
60 {
61 if (!m_Buffer.IsCreated)
62 return 0;
63
64 return m_Buffer.Length;
65 }
66 }
67
68 /// <summary>
69 /// The raw underlying memory buffer.
70 /// </summary>
71 /// <value>Underlying buffer of unmanaged memory.</value>
72 public NativeArray<byte> data => m_Buffer;
73
74 /// <summary>
75 /// Pointer to the first event in the buffer.
76 /// </summary>
77 /// <value>Pointer to first event in buffer.</value>
78 public InputEventPtr bufferPtr
79 {
80 // When using ConvertExistingDataToNativeArray, the NativeArray isn't getting a "safety handle" (seems like a bug)
81 // and calling GetUnsafeReadOnlyPtr() will result in a NullReferenceException. Get the pointer without checks here.
82 get { return (InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer); }
83 }
84
85 /// <summary>
86 /// Construct an event buffer using the given memory block containing <see cref="InputEvent"/>s.
87 /// </summary>
88 /// <param name="eventPtr">A buffer containing <paramref name="eventCount"/> number of input events. The
89 /// individual events in the buffer are variable-sized (depending on the type of each event).</param>
90 /// <param name="eventCount">The number of events in <paramref name="eventPtr"/>. Can be zero.</param>
91 /// <param name="sizeInBytes">Total number of bytes of event data in the memory block pointed to by <paramref name="eventPtr"/>.
92 /// If -1 (default), the size of the actual event data in the buffer is considered unknown and has to be determined by walking
93 /// <paramref name="eventCount"/> number of events (due to the variable size of each event).</param>
94 /// <param name="capacityInBytes">The total size of the memory block allocated at <paramref name="eventPtr"/>. If this
95 /// is larger than <paramref name="sizeInBytes"/>, additional events can be appended to the buffer until the capacity
96 /// is exhausted. If this is -1 (default), the capacity is considered unknown and no additional events can be
97 /// appended to the buffer.</param>
98 /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is <c>null</c> and <paramref name="eventCount"/> is not zero
99 /// -or- <paramref name="capacityInBytes"/> is less than <paramref name="sizeInBytes"/>.</exception>
100 public InputEventBuffer(InputEvent* eventPtr, int eventCount, int sizeInBytes = -1, int capacityInBytes = -1)
101 : this()
102 {
103 if (eventPtr == null && eventCount != 0)
104 throw new ArgumentException("eventPtr is NULL but eventCount is != 0", nameof(eventCount));
105 if (capacityInBytes != 0 && capacityInBytes < sizeInBytes)
106 throw new ArgumentException($"capacity({capacityInBytes}) cannot be smaller than size({sizeInBytes})",
107 nameof(capacityInBytes));
108
109 if (eventPtr != null)
110 {
111 if (capacityInBytes < 0)
112 capacityInBytes = sizeInBytes;
113
114 m_Buffer = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(eventPtr,
115 capacityInBytes > 0 ? capacityInBytes : 0, Allocator.None);
116 m_SizeInBytes = sizeInBytes >= 0 ? sizeInBytes : BufferSizeUnknown;
117 m_EventCount = eventCount;
118 m_WeOwnTheBuffer = false;
119 }
120 }
121
122 /// <summary>
123 /// Construct an event buffer using the array containing <see cref="InputEvent"/>s.
124 /// </summary>
125 /// <param name="buffer">A native array containing <paramref name="eventCount"/> number of input events. The
126 /// individual events in the buffer are variable-sized (depending on the type of each event).</param>
127 /// <param name="eventCount">The number of events in <paramref name="buffer"/>. Can be zero.</param>
128 /// <param name="sizeInBytes">Total number of bytes of event data in the <paramref cref="buffer"/>.
129 /// If -1 (default), the size of the actual event data in <paramref name="buffer"/> is considered unknown and has to be determined by walking
130 /// <paramref name="eventCount"/> number of events (due to the variable size of each event).</param>
131 /// <param name="transferNativeArrayOwnership">If true, ownership of the <c>NativeArray</c> given by <paramref name="buffer"/> is
132 /// transferred to the <c>InputEventBuffer</c>. Calling <see cref="Dispose"/> will deallocate the array. Also, <see cref="AllocateEvent"/>
133 /// may re-allocate the array.</param>
134 /// <exception cref="ArgumentException"><paramref name="buffer"/> has no memory allocated but <paramref name="eventCount"/> is not zero.</exception>
135 /// <exception cref="ArgumentOutOfRangeException"><paramref name="sizeInBytes"/> is greater than the total length allocated for
136 /// <paramref name="buffer"/>.</exception>
137 public InputEventBuffer(NativeArray<byte> buffer, int eventCount, int sizeInBytes = -1, bool transferNativeArrayOwnership = false)
138 {
139 if (eventCount > 0 && !buffer.IsCreated)
140 throw new ArgumentException("buffer has no data but eventCount is > 0", nameof(eventCount));
141 if (sizeInBytes > buffer.Length)
142 throw new ArgumentOutOfRangeException(nameof(sizeInBytes));
143
144 m_Buffer = buffer;
145 m_WeOwnTheBuffer = transferNativeArrayOwnership;
146 m_SizeInBytes = sizeInBytes >= 0 ? sizeInBytes : buffer.Length;
147 m_EventCount = eventCount;
148 }
149
150 /// <summary>
151 /// Append a new event to the end of the buffer by copying the event from <paramref name="eventPtr"/>.
152 /// </summary>
153 /// <param name="eventPtr">Data of the event to store in the buffer. This will be copied in full as
154 /// per <see cref="InputEvent.sizeInBytes"/> found in the event's header.</param>
155 /// <param name="capacityIncrementInBytes">If the buffer needs to be reallocated to accommodate the event, number of
156 /// bytes to grow the buffer by.</param>
157 /// <param name="allocator">If the buffer needs to be reallocated to accommodate the event, the type of allocation to
158 /// use.</param>
159 /// <exception cref="ArgumentNullException"><paramref name="eventPtr"/> is <c>null</c>.</exception>
160 /// <remarks>
161 /// If the buffer's current capacity (see <see cref="capacityInBytes"/>) is smaller than <see cref="InputEvent.sizeInBytes"/>
162 /// of the given event, the buffer will be reallocated.
163 /// </remarks>
164 public void AppendEvent(InputEvent* eventPtr, int capacityIncrementInBytes = 2048, Allocator allocator = Allocator.Persistent)
165 {
166 if (eventPtr == null)
167 throw new ArgumentNullException(nameof(eventPtr));
168
169 // Allocate space.
170 var eventSizeInBytes = eventPtr->sizeInBytes;
171 var destinationPtr = AllocateEvent((int)eventSizeInBytes, capacityIncrementInBytes, allocator);
172
173 // Copy event.
174 UnsafeUtility.MemCpy(destinationPtr, eventPtr, eventSizeInBytes);
175 }
176
177 /// <summary>
178 /// Make space for an event of <paramref name="sizeInBytes"/> bytes and return a pointer to
179 /// the memory for the event.
180 /// </summary>
181 /// <param name="sizeInBytes">Number of bytes to make available for the event including the event header (see <see cref="InputEvent"/>).</param>
182 /// <param name="capacityIncrementInBytes">If the buffer needs to be reallocated to accommodate the event, number of
183 /// bytes to grow the buffer by.</param>
184 /// <param name="allocator">If the buffer needs to be reallocated to accommodate the event, the type of allocation to
185 /// use.</param>
186 /// <returns>A pointer to a block of memory in <see cref="bufferPtr"/>. Store the event data here.</returns>
187 /// <exception cref="ArgumentException"><paramref name="sizeInBytes"/> is less than the size needed for the
188 /// header of an <see cref="InputEvent"/>. Will automatically be aligned to a multiple of 4.</exception>
189 /// <remarks>
190 /// Only <see cref="InputEvent.sizeInBytes"/> is initialized by this method. No other fields from the event's
191 /// header are touched.
192 ///
193 /// The event will be appended to the buffer after the last event currently in the buffer (if any).
194 /// </remarks>
195 public InputEvent* AllocateEvent(int sizeInBytes, int capacityIncrementInBytes = 2048, Allocator allocator = Allocator.Persistent)
196 {
197 if (sizeInBytes < InputEvent.kBaseEventSize)
198 throw new ArgumentException(
199 $"sizeInBytes must be >= sizeof(InputEvent) == {InputEvent.kBaseEventSize} (was {sizeInBytes})",
200 nameof(sizeInBytes));
201
202 var alignedSizeInBytes = sizeInBytes.AlignToMultipleOf(InputEvent.kAlignment);
203
204 // See if we need to enlarge our buffer.
205 var necessaryCapacity = m_SizeInBytes + alignedSizeInBytes;
206 var currentCapacity = capacityInBytes;
207 if (currentCapacity < necessaryCapacity)
208 {
209 // Yes, so reallocate.
210
211 var newCapacity = necessaryCapacity.AlignToMultipleOf(capacityIncrementInBytes);
212 if (newCapacity > int.MaxValue)
213 throw new NotImplementedException("NativeArray long support");
214 var newBuffer =
215 new NativeArray<byte>((int)newCapacity, allocator, NativeArrayOptions.ClearMemory);
216
217 if (m_Buffer.IsCreated)
218 {
219 UnsafeUtility.MemCpy(newBuffer.GetUnsafePtr(),
220 NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer),
221 this.sizeInBytes);
222
223 if (m_WeOwnTheBuffer)
224 m_Buffer.Dispose();
225 }
226
227 m_Buffer = newBuffer;
228 m_WeOwnTheBuffer = true;
229 }
230
231 var eventPtr = (InputEvent*)((byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer) + m_SizeInBytes);
232 eventPtr->sizeInBytes = (uint)sizeInBytes;
233 m_SizeInBytes += alignedSizeInBytes;
234 ++m_EventCount;
235
236 return eventPtr;
237 }
238
239 /// <summary>
240 /// Whether the given event pointer refers to data within the event buffer.
241 /// </summary>
242 /// <param name="eventPtr"></param>
243 /// <returns></returns>
244 /// <remarks>
245 /// Note that this method does NOT check whether the given pointer points to an actual
246 /// event in the buffer. It solely performs a pointer out-of-bounds check.
247 ///
248 /// Also note that if the size of the memory buffer is unknown (<see cref="BufferSizeUnknown"/>,
249 /// only a lower-bounds check is performed.
250 /// </remarks>
251 public bool Contains(InputEvent* eventPtr)
252 {
253 if (eventPtr == null)
254 return false;
255
256 if (sizeInBytes == 0)
257 return false;
258
259 var bufferPtr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(data);
260 if (eventPtr < bufferPtr)
261 return false;
262
263 if (sizeInBytes != BufferSizeUnknown && eventPtr >= (byte*)bufferPtr + sizeInBytes)
264 return false;
265
266 return true;
267 }
268
269 public void Reset()
270 {
271 m_EventCount = 0;
272 if (m_SizeInBytes != BufferSizeUnknown)
273 m_SizeInBytes = 0;
274 }
275
276 /// <summary>
277 /// Advance the read position to the next event in the buffer, preserving or not preserving the
278 /// current event depending on <paramref name="leaveEventInBuffer"/>.
279 /// </summary>
280 /// <param name="currentReadPos"></param>
281 /// <param name="currentWritePos"></param>
282 /// <param name="numEventsRetainedInBuffer"></param>
283 /// <param name="numRemainingEvents"></param>
284 /// <param name="leaveEventInBuffer"></param>
285 /// <remarks>
286 /// This method MUST ONLY BE CALLED if the current event has been fully processed. If the at <paramref name="currentWritePos"/>
287 /// is smaller than the current event, then this method will OVERWRITE parts or all of the current event.
288 /// </remarks>
289 internal void AdvanceToNextEvent(ref InputEvent* currentReadPos,
290 ref InputEvent* currentWritePos, ref int numEventsRetainedInBuffer,
291 ref int numRemainingEvents, bool leaveEventInBuffer)
292 {
293 Debug.Assert(currentReadPos >= currentWritePos, "Current write position is beyond read position");
294
295 // Get new read position *before* potentially moving the current event so that we don't
296 // end up overwriting the data we need to find the next event in memory.
297 var newReadPos = currentReadPos;
298 if (numRemainingEvents > 1)
299 {
300 // Don't perform safety check in non-debug builds.
301 #if UNITY_EDITOR || DEVELOPMENT_BUILD
302 newReadPos = InputEvent.GetNextInMemoryChecked(currentReadPos, ref this);
303 #else
304 newReadPos = InputEvent.GetNextInMemory(currentReadPos);
305 #endif
306 }
307
308 // If the current event should be left in the buffer, advance write position.
309 if (leaveEventInBuffer)
310 {
311 Debug.Assert(Contains(currentWritePos), "Current write position should be contained in buffer");
312
313 // Move down in buffer if read and write pos have deviated from each other.
314 var numBytes = currentReadPos->sizeInBytes;
315 if (currentReadPos != currentWritePos)
316 UnsafeUtility.MemMove(currentWritePos, currentReadPos, numBytes);
317 currentWritePos = (InputEvent*)((byte*)currentWritePos + numBytes.AlignToMultipleOf(4));
318 ++numEventsRetainedInBuffer;
319 }
320
321 currentReadPos = newReadPos;
322 --numRemainingEvents;
323 }
324
325 public IEnumerator<InputEventPtr> GetEnumerator()
326 {
327 return new Enumerator(this);
328 }
329
330 IEnumerator IEnumerable.GetEnumerator()
331 {
332 return GetEnumerator();
333 }
334
335 public void Dispose()
336 {
337 // Nothing to do if we don't actually own the memory.
338 if (!m_WeOwnTheBuffer)
339 return;
340
341 Debug.Assert(m_Buffer.IsCreated, "Buffer has not been created");
342
343 m_Buffer.Dispose();
344 m_WeOwnTheBuffer = false;
345 m_SizeInBytes = 0;
346 m_EventCount = 0;
347 }
348
349 public InputEventBuffer Clone()
350 {
351 var clone = new InputEventBuffer();
352 if (m_Buffer.IsCreated)
353 {
354 clone.m_Buffer = new NativeArray<byte>(m_Buffer.Length, Allocator.Persistent);
355 clone.m_Buffer.CopyFrom(m_Buffer);
356 clone.m_WeOwnTheBuffer = true;
357 }
358 clone.m_SizeInBytes = m_SizeInBytes;
359 clone.m_EventCount = m_EventCount;
360 return clone;
361 }
362
363 object ICloneable.Clone()
364 {
365 return Clone();
366 }
367
368 private NativeArray<byte> m_Buffer;
369 private long m_SizeInBytes;
370 private int m_EventCount;
371 private bool m_WeOwnTheBuffer; ////FIXME: what we really want is access to NativeArray's allocator label
372
373 private struct Enumerator : IEnumerator<InputEventPtr>
374 {
375 private readonly InputEvent* m_Buffer;
376 private readonly int m_EventCount;
377 private InputEvent* m_CurrentEvent;
378 private int m_CurrentIndex;
379
380 public Enumerator(InputEventBuffer buffer)
381 {
382 m_Buffer = buffer.bufferPtr;
383 m_EventCount = buffer.m_EventCount;
384 m_CurrentEvent = null;
385 m_CurrentIndex = 0;
386 }
387
388 public bool MoveNext()
389 {
390 if (m_CurrentIndex == m_EventCount)
391 return false;
392
393 if (m_CurrentEvent == null)
394 {
395 m_CurrentEvent = m_Buffer;
396 return m_CurrentEvent != null;
397 }
398
399 Debug.Assert(m_CurrentEvent != null, "Current event must not be null");
400
401 ++m_CurrentIndex;
402 if (m_CurrentIndex == m_EventCount)
403 return false;
404
405 m_CurrentEvent = InputEvent.GetNextInMemory(m_CurrentEvent);
406 return true;
407 }
408
409 public void Reset()
410 {
411 m_CurrentEvent = null;
412 m_CurrentIndex = 0;
413 }
414
415 public void Dispose()
416 {
417 }
418
419 public InputEventPtr Current => m_CurrentEvent;
420
421 object IEnumerator.Current => Current;
422 }
423 }
424}