using System; using UnityEngine.InputSystem.Utilities; ////REVIEW: nuke this and force raw pointers on all code using events? namespace UnityEngine.InputSystem.LowLevel { /// /// Pointer to an . Makes it easier to work with InputEvents and hides /// the unsafe operations necessary to work with them. /// /// /// Note that event pointers generally refer to event buffers that are continually reused. This means /// that event pointers should not be held on to. Instead, to hold onto event data, manually copy /// an event to a buffer. /// public unsafe struct InputEventPtr : IEquatable { // C# does not allow us to have pointers to structs that have managed data members. Since // this can't be guaranteed for generic type parameters, they can't be used with pointers. // This is why we cannot make InputEventPtr generic or have a generic method that returns // a pointer to a specific type of event. private readonly InputEvent* m_EventPtr; /// /// Initialize the pointer to refer to the given event. /// /// Pointer to an event. Can be null. public InputEventPtr(InputEvent* eventPtr) { m_EventPtr = eventPtr; } /// /// Whether the pointer is not null. /// /// True if the struct refers to an event. public bool valid => m_EventPtr != null; /// /// Whether the event is considered "handled" and should not be processed further. /// /// /// This is used in two ways. Setting it from inside will /// cause the event to not be processed further. If it is a or /// , the targeted by the event will /// not receive the state change. /// /// Setting this flag from inside a state change monitor (see ) /// will prevent other monitors on the same not receiving the state change. /// /// The event pointer instance is not . public bool handled { get { if (!valid) return false; return m_EventPtr->handled; } set { if (!valid) throw new InvalidOperationException("The InputEventPtr is not valid."); m_EventPtr->handled = value; } } public int id { get { if (!valid) return 0; return m_EventPtr->eventId; } set { if (!valid) throw new InvalidOperationException("The InputEventPtr is not valid."); m_EventPtr->eventId = value; } } public FourCC type { get { if (!valid) return new FourCC(); return m_EventPtr->type; } } public uint sizeInBytes { get { if (!valid) return 0; return m_EventPtr->sizeInBytes; } } public int deviceId { get { if (!valid) return InputDevice.InvalidDeviceId; return m_EventPtr->deviceId; } set { if (!valid) throw new InvalidOperationException("The InputEventPtr is not valid."); m_EventPtr->deviceId = value; } } public double time { get => valid ? m_EventPtr->time : 0.0; set { if (!valid) throw new InvalidOperationException("The InputEventPtr is not valid."); m_EventPtr->time = value; } } internal double internalTime { get => valid ? m_EventPtr->internalTime : 0.0; set { if (!valid) throw new InvalidOperationException("The InputEventPtr is not valid."); m_EventPtr->internalTime = value; } } public InputEvent* data => m_EventPtr; // The stateFormat, stateSizeInBytes, and stateOffset properties are very // useful for debugging. internal FourCC stateFormat { get { var eventType = type; if (eventType == StateEvent.Type) return StateEvent.FromUnchecked(this)->stateFormat; if (eventType == DeltaStateEvent.Type) return DeltaStateEvent.FromUnchecked(this)->stateFormat; throw new InvalidOperationException("Event must be a StateEvent or DeltaStateEvent but is " + this); } } internal uint stateSizeInBytes { get { if (IsA()) return StateEvent.From(this)->stateSizeInBytes; if (IsA()) return DeltaStateEvent.From(this)->deltaStateSizeInBytes; throw new InvalidOperationException("Event must be a StateEvent or DeltaStateEvent but is " + this); } } internal uint stateOffset { get { if (IsA()) return DeltaStateEvent.From(this)->stateOffset; throw new InvalidOperationException("Event must be a DeltaStateEvent but is " + this); } } public bool IsA() where TOtherEvent : struct, IInputEventTypeInfo { if (m_EventPtr == null) return false; // NOTE: Important to say `default` instead of `new TOtherEvent()` here. The latter will result in a call to // `Activator.CreateInstance` on Mono and thus allocate GC memory. TOtherEvent otherEvent = default; return m_EventPtr->type == otherEvent.typeStatic; } // NOTE: It is your responsibility to know *if* there actually another event following this one in memory. public InputEventPtr Next() { if (!valid) return new InputEventPtr(); return new InputEventPtr(InputEvent.GetNextInMemory(m_EventPtr)); } public override string ToString() { if (!valid) return "null"; // il2cpp has a bug which makes builds fail if this is written as 'return m_EventPtr->ToString()'. // Gives an error about "trying to constrain an invalid type". // Writing it as a two-step operation like here makes it build cleanly. var eventPtr = *m_EventPtr; return eventPtr.ToString(); } /// /// Return the plain pointer wrapped around by the struct. /// /// A plain pointer. Can be null. public InputEvent* ToPointer() { return this; } public bool Equals(InputEventPtr other) { return m_EventPtr == other.m_EventPtr || InputEvent.Equals(m_EventPtr, other.m_EventPtr); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is InputEventPtr ptr && Equals(ptr); } public override int GetHashCode() { return unchecked((int)(long)m_EventPtr); } public static bool operator==(InputEventPtr left, InputEventPtr right) { return left.m_EventPtr == right.m_EventPtr; } public static bool operator!=(InputEventPtr left, InputEventPtr right) { return left.m_EventPtr != right.m_EventPtr; } public static implicit operator InputEventPtr(InputEvent* eventPtr) { return new InputEventPtr(eventPtr); } public static InputEventPtr From(InputEvent* eventPtr) { return new InputEventPtr(eventPtr); } public static implicit operator InputEvent*(InputEventPtr eventPtr) { return eventPtr.data; } // Make annoying Microsoft code analyzer happy. public static InputEvent* FromInputEventPtr(InputEventPtr eventPtr) { return eventPtr.data; } } }