#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
using System;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.Steam
{
///
/// Adds support for Steam controllers.
///
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
public
#else
internal
#endif
static class SteamSupport
{
///
/// Wrapper around the Steam controller API.
///
///
/// This must be set by user code for Steam controller support to become functional.
///
public static ISteamControllerAPI api
{
get { return s_API; }
set
{
s_API = value;
InstallHooks(s_API != null);
}
}
internal static ISteamControllerAPI GetAPIAndRequireItToBeSet()
{
if (s_API == null)
throw new InvalidOperationException("ISteamControllerAPI implementation has not been set on SteamSupport");
return s_API;
}
internal static SteamHandle[] s_ConnectedControllers;
internal static SteamController[] s_InputDevices;
internal static int s_InputDeviceCount;
internal static bool s_HooksInstalled;
internal static ISteamControllerAPI s_API;
private const int STEAM_CONTROLLER_MAX_COUNT = 16;
///
/// Enable support for the Steam controller API.
///
public static void Initialize()
{
// We use this as a base layout.
InputSystem.RegisterLayout();
if (api != null)
InstallHooks(true);
}
private static void InstallHooks(bool state)
{
Debug.Assert(api != null);
if (state && !s_HooksInstalled)
{
InputSystem.onBeforeUpdate += OnUpdate;
InputSystem.onActionChange += OnActionChange;
}
else if (!state && s_HooksInstalled)
{
InputSystem.onBeforeUpdate -= OnUpdate;
InputSystem.onActionChange -= OnActionChange;
}
}
private static void OnActionChange(object mapOrAction, InputActionChange change)
{
// We only care about action map activations. Steam has no support for enabling or disabling
// individual actions and also has no support disabling sets once enabled (can only switch
// to different set).
if (change != InputActionChange.ActionMapEnabled)
return;
// See if the map has any bindings to SteamControllers.
// NOTE: We only support a single SteamController on any action map here. The first SteamController
// we find is the one we're doing all the work on.
var actionMap = (InputActionMap)mapOrAction;
foreach (var action in actionMap.actions)
{
foreach (var control in action.controls)
{
var steamController = control.device as SteamController;
if (steamController == null)
continue;
// Yes, there's active bindings to a SteamController on the map. Look through the Steam action
// sets on the controller for a name match on the action map. If we have one, sync the enable/
// disable status of the set.
var actionMapName = actionMap.name;
foreach (var set in steamController.steamActionSets)
{
if (string.Compare(set.name, actionMapName, StringComparison.InvariantCultureIgnoreCase) != 0)
continue;
// Nothing to do if the Steam controller has auto-syncing disabled.
if (!steamController.autoActivateSets)
return;
// Sync status.
steamController.ActivateSteamActionSet(set.handle);
// Done.
return;
}
}
}
}
private static void OnUpdate()
{
if (api == null)
return;
// Update controller state.
api.RunFrame();
// Check if we have any new controllers have appeared.
if (s_ConnectedControllers == null)
s_ConnectedControllers = new SteamHandle[STEAM_CONTROLLER_MAX_COUNT];
var numConnectedControllers = api.GetConnectedControllers(s_ConnectedControllers);
for (var i = 0; i < numConnectedControllers; ++i)
{
var handle = s_ConnectedControllers[i];
// See if we already have a device for this one.
if (s_InputDevices != null)
{
SteamController existingDevice = null;
for (var n = 0; n < s_InputDeviceCount; ++n)
{
if (s_InputDevices[n].steamControllerHandle == handle)
{
existingDevice = s_InputDevices[n];
break;
}
}
// Yes, we do.
if (existingDevice != null)
continue;
}
////FIXME: this should not create garbage
// No, so create a new device.
var controllerLayouts = InputSystem.ListLayoutsBasedOn("SteamController");
foreach (var layout in controllerLayouts)
{
// Rather than directly creating a device with the layout, let it go through
// the usual matching process.
var device = InputSystem.AddDevice(new InputDeviceDescription
{
interfaceName = SteamController.kSteamInterface,
product = layout
});
// Make sure it's a SteamController we got.
var steamDevice = device as SteamController;
if (steamDevice == null)
{
Debug.LogError(string.Format(
"InputDevice created from layout '{0}' based on the 'SteamController' layout is not a SteamController",
device.layout));
continue;
}
// Resolve the controller's actions.
steamDevice.InvokeResolveSteamActions();
// Assign it the Steam controller handle.
steamDevice.steamControllerHandle = handle;
ArrayHelpers.AppendWithCapacity(ref s_InputDevices, ref s_InputDeviceCount, steamDevice);
}
}
// Update all controllers we have.
for (var i = 0; i < s_InputDeviceCount; ++i)
{
var device = s_InputDevices[i];
var handle = device.steamControllerHandle;
// Check if the device still exists.
var stillExists = false;
for (var n = 0; n < numConnectedControllers; ++n)
if (s_ConnectedControllers[n] == handle)
{
stillExists = true;
break;
}
// If not, remove it.
if (!stillExists)
{
ArrayHelpers.EraseAtByMovingTail(s_InputDevices, ref s_InputDeviceCount, i);
////REVIEW: should this rather queue a device removal event?
InputSystem.RemoveDevice(device);
--i;
continue;
}
////TODO: support polling Steam controllers on an async polling thread adhering to InputSystem.pollingFrequency
// Otherwise, update it.
device.InvokeUpdate();
}
}
}
}
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT