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}