using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.InputSystem.LowLevel
{
///
/// The input event stream is a combination of the input event buffer passed from native code and an
/// append buffer that is owned by the managed side. Events queued during update are added to the
/// append buffer. To calling code, the two buffers look like a single coherent stream of events.
/// Calling Advance will first step through the events from the native side, followed by any events
/// that have been appended.
///
internal unsafe struct InputEventStream
{
public bool isOpen => m_IsOpen;
public int remainingEventCount => m_RemainingNativeEventCount + m_RemainingAppendEventCount;
///
/// How many events were left in the native buffer during reading.
///
public int numEventsRetainedInBuffer => m_NumEventsRetainedInBuffer;
public InputEvent* currentEventPtr => m_RemainingNativeEventCount > 0
? m_CurrentNativeEventReadPtr
: (m_RemainingAppendEventCount > 0 ? m_CurrentAppendEventReadPtr : null);
public uint numBytesRetainedInBuffer =>
(uint)((byte*)m_CurrentNativeEventWritePtr -
(byte*)NativeArrayUnsafeUtility
.GetUnsafeBufferPointerWithoutChecks(m_NativeBuffer.data));
public InputEventStream(ref InputEventBuffer eventBuffer, int maxAppendedEvents)
{
m_CurrentNativeEventWritePtr = m_CurrentNativeEventReadPtr =
(InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(eventBuffer.data);
m_NativeBuffer = eventBuffer;
m_RemainingNativeEventCount = m_NativeBuffer.eventCount;
m_NumEventsRetainedInBuffer = 0;
m_CurrentAppendEventReadPtr = m_CurrentAppendEventWritePtr = default;
m_AppendBuffer = default;
m_RemainingAppendEventCount = 0;
m_MaxAppendedEvents = maxAppendedEvents;
m_IsOpen = true;
}
public void Close(ref InputEventBuffer eventBuffer)
{
// If we have retained events, update event count and buffer size. If not, just reset.
if (m_NumEventsRetainedInBuffer > 0)
{
var bufferPtr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_NativeBuffer.data);
Debug.Assert((byte*)m_CurrentNativeEventWritePtr > (byte*)bufferPtr);
var newBufferSize = (byte*)m_CurrentNativeEventWritePtr - (byte*)bufferPtr;
m_NativeBuffer = new InputEventBuffer((InputEvent*)bufferPtr, m_NumEventsRetainedInBuffer, (int)newBufferSize,
(int)m_NativeBuffer.capacityInBytes);
}
else
{
m_NativeBuffer.Reset();
}
if (m_AppendBuffer.data.IsCreated)
m_AppendBuffer.Dispose();
eventBuffer = m_NativeBuffer;
m_IsOpen = false;
}
public void CleanUpAfterException()
{
if (!isOpen)
return;
m_NativeBuffer.Reset();
if (m_AppendBuffer.data.IsCreated)
m_AppendBuffer.Dispose();
m_IsOpen = false;
}
public void Write(InputEvent* eventPtr)
{
if (m_AppendBuffer.eventCount >= m_MaxAppendedEvents)
{
Debug.LogError($"Maximum number of queued events exceeded. Set the '{nameof(InputSettings.maxQueuedEventsPerUpdate)}' " +
$"setting to a higher value if you need to queue more events than this. " +
$"Current limit is '{m_MaxAppendedEvents}'.");
return;
}
var wasAlreadyCreated = m_AppendBuffer.data.IsCreated;
var oldBufferPtr = (byte*)m_AppendBuffer.bufferPtr.data;
m_AppendBuffer.AppendEvent(eventPtr, allocator: Allocator.Temp);
if (!wasAlreadyCreated)
{
m_CurrentAppendEventWritePtr = m_CurrentAppendEventReadPtr =
(InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_AppendBuffer.data);
}
else
{
// AppendEvent can reallocate the buffer if it needs more space, so make sure the read and write pointers
// point to the equivalent places in the new buffer.
var newBufferPtr = (byte*)m_AppendBuffer.bufferPtr.data;
if (oldBufferPtr != newBufferPtr)
{
var currentWriteOffset = (byte*)m_CurrentAppendEventWritePtr - oldBufferPtr;
var currentReadOffset = (byte*)m_CurrentAppendEventReadPtr - oldBufferPtr;
m_CurrentAppendEventWritePtr = (InputEvent*)(newBufferPtr + currentWriteOffset);
m_CurrentAppendEventReadPtr = (InputEvent*)(newBufferPtr + currentReadOffset);
}
}
m_RemainingAppendEventCount++;
}
public InputEvent* Advance(bool leaveEventInBuffer)
{
if (m_RemainingNativeEventCount > 0)
{
m_NativeBuffer.AdvanceToNextEvent(ref m_CurrentNativeEventReadPtr, ref m_CurrentNativeEventWritePtr,
ref m_NumEventsRetainedInBuffer, ref m_RemainingNativeEventCount, leaveEventInBuffer);
}
else if (m_RemainingAppendEventCount > 0)
{
var numEventRetained = 0;
m_AppendBuffer.AdvanceToNextEvent(ref m_CurrentAppendEventReadPtr, ref m_CurrentAppendEventWritePtr,
ref numEventRetained, ref m_RemainingAppendEventCount, false);
}
return currentEventPtr;
}
///
/// Peeks next event in the stream
///
public InputEvent* Peek()
{
// Advance will go to next event in m_NativeBuffer
if (m_RemainingNativeEventCount > 1)
return InputEvent.GetNextInMemory(m_CurrentNativeEventReadPtr);
// Advance will decrement m_RemainingNativeEventCount to 0
// and currentEventPtr will point to m_CurrentAppendEventReadPtr if any
if (m_RemainingNativeEventCount == 1)
return m_RemainingAppendEventCount > 0 ? m_CurrentAppendEventReadPtr : null;
// Advance will go to next event in m_AppendBuffer
if (m_RemainingAppendEventCount > 1)
return InputEvent.GetNextInMemory(m_CurrentAppendEventReadPtr);
// No next event
return null;
}
private InputEventBuffer m_NativeBuffer;
private InputEvent* m_CurrentNativeEventReadPtr;
private InputEvent* m_CurrentNativeEventWritePtr;
private int m_RemainingNativeEventCount;
private readonly int m_MaxAppendedEvents;
// During Update, new events that are queued will be added to the append buffer
private InputEventBuffer m_AppendBuffer;
private InputEvent* m_CurrentAppendEventReadPtr;
private InputEvent* m_CurrentAppendEventWritePtr;
private int m_RemainingAppendEventCount;
// When timeslicing events or in before-render updates, we may be leaving events in the buffer
// for later processing. We do this by compacting the event buffer and moving events down such
// that the events we leave in the buffer form one contiguous chunk of memory at the beginning
// of the buffer.
private int m_NumEventsRetainedInBuffer;
private bool m_IsOpen;
}
}