#if ENABLE_INPUT_SYSTEM
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using ConjureOS.Input.LowLevel;
using UnityEngine.InputSystem.Users;
using System.Collections.Generic;
using ArrayHelpers = ConjureOS.Input.Utilities.ArrayHelpers;
namespace ConjureOS.Input.Utilities
{
///
/// A collection of utility functions for working with arrays.
///
///
/// The goal of this collection is to make it easy to use arrays directly rather than resorting to
/// .
///
internal static class ArrayHelpers
{
public static int IndexOfReference(this TFirst[] array, TSecond value, int count = -1)
where TSecond : class
where TFirst : TSecond
{
return IndexOfReference(array, value, 0, count);
}
public static int IndexOfReference(this TFirst[] array, TSecond value, int startIndex, int count)
where TSecond : class
where TFirst : TSecond
{
if (array == null)
return -1;
if (count < 0)
count = array.Length - startIndex;
for (var i = startIndex; i < startIndex + count; ++i)
if (ReferenceEquals(array[i], value))
return i;
return -1;
}
public static int AppendWithCapacity(ref TValue[] array, ref int count, TValue value, int capacityIncrement = 10)
{
if (array == null)
{
array = new TValue[capacityIncrement];
array[0] = value;
++count;
return 0;
}
var capacity = array.Length;
if (capacity == count)
{
capacity += capacityIncrement;
Array.Resize(ref array, capacity);
}
var index = count;
array[index] = value;
++count;
return index;
}
public static void EraseAtWithCapacity(this TValue[] array, ref int count, int index)
{
Debug.Assert(array != null);
Debug.Assert(count <= array.Length);
Debug.Assert(index >= 0 && index < count);
// If we're erasing from the beginning or somewhere in the middle, move
// the array contents down from after the index.
if (index < count - 1)
{
Array.Copy(array, index + 1, array, index, count - index - 1);
}
array[count - 1] = default; // Tail has been moved down by one.
--count;
}
}
}
namespace ConjureOS.Input.LowLevel
{
///
/// Default state layout for Conjure Arcade controllers.
///
///
[StructLayout(LayoutKind.Explicit, Size = 28)]
public struct ConjureArcadeControllerState : IInputStateTypeInfo
{
public static FourCC Format => new FourCC('C', 'N', 'J', 'A');
///
/// Button bit mask.
///
/// Button bit mask.
///
///
///
///
///
///
///
///
///
[InputControl(name = "buttonA", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonA, displayName = "Button A", shortDisplayName = "A")]
[InputControl(name = "buttonB", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonB, displayName = "Button B", shortDisplayName = "B")]
[InputControl(name = "buttonC", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonC, displayName = "Button C", shortDisplayName = "C")]
[InputControl(name = "button1", layout = "Button", bit = (uint)ConjureArcadeControllerButton.Button1, displayName = "Button 1", shortDisplayName = "1")]
[InputControl(name = "button2", layout = "Button", bit = (uint)ConjureArcadeControllerButton.Button2, displayName = "Button 2", shortDisplayName = "2")]
[InputControl(name = "button3", layout = "Button", bit = (uint)ConjureArcadeControllerButton.Button3, displayName = "Button 3", shortDisplayName = "3")]
[InputControl(name = "buttonStart", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonStart, usages = new[] { "Join", "Pause" }, displayName = "Button Start", shortDisplayName = "Start")]
[InputControl(name = "buttonPower", layout = "Button", bit = (uint)ConjureArcadeControllerButton.ButtonPower, usages = new[] { "Power", "Exit" }, displayName = "Button Power", shortDisplayName = "Power")]
[FieldOffset(0)]
public uint buttons;
///
/// Stick position. Each axis goes from -1 to 1 with
/// 0 being center position.
///
/// Left stick position.
///
[InputControl(layout = "Stick", usage = "Primary2DMotion", processors = "stickDeadzone", displayName = "Stick", shortDisplayName = "S")]
[FieldOffset(4)]
public Vector2 stick;
///
/// State format tag for GamepadState.
///
/// Returns "CNJA".
public FourCC format => Format;
///
/// Create a gamepad state with the given buttons being pressed.
///
/// Buttons to put into pressed state.
/// is null.
public ConjureArcadeControllerState(params GamepadButton[] buttons)
: this()
{
if (buttons == null)
throw new ArgumentNullException(nameof(buttons));
foreach (var button in buttons)
{
Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask");
var bit = 1U << (int)button;
this.buttons |= bit;
}
}
///
/// Set the specific buttons to be pressed or unpressed.
///
/// A gamepad button.
/// Whether to set to be pressed or not pressed in
/// .
/// GamepadState with a modified mask.
public ConjureArcadeControllerState WithButton(GamepadButton button, bool value = true)
{
Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask");
var bit = 1U << (int)button;
if (value)
buttons |= bit;
else
buttons &= ~bit;
return this;
}
}
public enum ConjureArcadeControllerButton
{
ButtonA = 0,
ButtonB = 1,
ButtonC = 2,
Button1 = 3,
Button2 = 4,
Button3 = 5,
ButtonStart = 6,
ButtonPower = 7,
}
}
namespace ConjureOS.Input
{
[InputControlLayout(stateType = typeof(ConjureArcadeControllerState), displayName = "Conjure Arcade Controller")]
public class ConjureArcadeController : InputDevice
{
public ButtonControl buttonA { get; protected set; }
public ButtonControl buttonB { get; protected set; }
public ButtonControl buttonC { get; protected set; }
public ButtonControl button1 { get; protected set; }
public ButtonControl button2 { get; protected set; }
public ButtonControl button3 { get; protected set; }
public ButtonControl buttonStart { get; protected set; }
public ButtonControl buttonPower { get; protected set; }
public StickControl stick { get; protected set; }
///
/// Retrieve a gamepad button by its enumeration
/// constant.
///
/// Button to retrieve.
/// is not a valid gamepad
/// button value.
public ButtonControl this[ConjureArcadeControllerButton button]
{
get
{
switch (button)
{
case ConjureArcadeControllerButton.ButtonA: return buttonA;
case ConjureArcadeControllerButton.ButtonB: return buttonB;
case ConjureArcadeControllerButton.ButtonC: return buttonC;
case ConjureArcadeControllerButton.Button1: return button1;
case ConjureArcadeControllerButton.Button2: return button2;
case ConjureArcadeControllerButton.Button3: return button3;
case ConjureArcadeControllerButton.ButtonStart: return buttonStart;
case ConjureArcadeControllerButton.ButtonPower: return buttonPower;
default:
throw new InvalidEnumArgumentException(nameof(button), (int)button, typeof(GamepadButton));
}
}
}
///
/// The gamepad last used/connected by the player or null if there is no gamepad connected
/// to the system.
///
///
/// When added, a device is automatically made current (see ), so
/// when connecting a gamepad, it will also become current. After that, it will only become current again
/// when input change on non-noisy controls (see ) is received.
///
/// For local multiplayer scenarios (or whenever there are multiple gamepads that need to be usable
/// in a concurrent fashion), it is not recommended to rely on this property. Instead, it is recommended
/// to use or .
///
///
///
public static ConjureArcadeController current { get; private set; }
///
/// A list of gamepads currently connected to the system.
///
/// All currently connected gamepads.
///
/// Does not cause GC allocation.
///
/// Do not hold on to the value returned by this getter but rather query it whenever
/// you need it. Whenever the gamepad setup changes, the value returned by this getter
/// is invalidated.
///
///
public new static ReadOnlyArray all => new(s_Gamepads, 0, s_GamepadCount);
///
protected override void FinishSetup()
{
buttonA = GetChildControl("buttonA");
buttonB = GetChildControl("buttonB");
buttonC = GetChildControl("buttonC");
button1 = GetChildControl("button1");
button2 = GetChildControl("button2");
button3 = GetChildControl("button3");
buttonStart = GetChildControl("buttonStart");
buttonPower = GetChildControl("buttonPower");
stick = GetChildControl("stick");
base.FinishSetup();
}
///
/// Make the gamepad the gamepad.
///
///
/// This is called automatically by the system when there is input on a gamepad.
///
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
///
/// Called when the gamepad is added to the system.
///
protected override void OnAdded()
{
ArrayHelpers.AppendWithCapacity(ref s_Gamepads, ref s_GamepadCount, this);
}
///
/// Called when the gamepad is removed from the system.
///
protected override void OnRemoved()
{
if (current == this)
current = null;
// Remove from `all`.
var index = ArrayHelpers.IndexOfReference(s_Gamepads, this, s_GamepadCount);
if (index != -1)
ArrayHelpers.EraseAtWithCapacity(s_Gamepads, ref s_GamepadCount, index);
else
{
Debug.Assert(false,
$"Gamepad {this} seems to not have been added but is being removed (gamepad list: {string.Join(", ", all)})"); // Put in else to not allocate on normal path.
}
}
private static int s_GamepadCount;
private static ConjureArcadeController[] s_Gamepads;
}
}
#endif