#if UNITY_ANALYTICS || UNITY_EDITOR using System; using UnityEngine.InputSystem.Layouts; #if UNITY_EDITOR using UnityEngine.InputSystem.Editor; #endif // UNITY_EDITOR ////FIXME: apparently shutdown events are not coming through in the analytics backend namespace UnityEngine.InputSystem { internal static class InputAnalytics { public const string kVendorKey = "unity.input"; // Struct similar to AnalyticInfo for simplifying usage. public struct InputAnalyticInfo { public InputAnalyticInfo(string name, int maxEventsPerHour, int maxNumberOfElements) { Name = name; MaxEventsPerHour = maxEventsPerHour; MaxNumberOfElements = maxNumberOfElements; } public readonly string Name; public readonly int MaxEventsPerHour; public readonly int MaxNumberOfElements; } // Note: Needs to be externalized from interface depending on C# version. public interface IInputAnalyticData #if UNITY_EDITOR && UNITY_2023_2_OR_NEWER : UnityEngine.Analytics.IAnalytic.IData #endif {} // Unity 2023.2+ deprecates legacy interfaces for registering and sending editor analytics and // replaces them with attribute annotations and required interface implementations. // The IInputAnalytic interface have been introduced here to support both variants // of analytics reporting. Notice that a difference is that data is collected lazily as part // of sending the analytics via the framework. public interface IInputAnalytic #if UNITY_EDITOR && UNITY_2023_2_OR_NEWER : UnityEngine.Analytics.IAnalytic #endif // UNITY_EDITOR && UNITY_2023_2_OR_NEWER { InputAnalyticInfo info { get; } // May be removed when only supporting 2023.2+ versions #if !UNITY_2023_2_OR_NEWER // Conditionally mimic UnityEngine.Analytics.IAnalytic bool TryGatherData(out IInputAnalyticData data, out Exception error); #endif // !UNITY_2023_2_OR_NEWER } public static void Initialize(InputManager manager) { Debug.Assert(manager.m_Runtime != null); } public static void OnStartup(InputManager manager) { manager.m_Runtime.SendAnalytic(new StartupEventAnalytic(manager)); } public static void OnShutdown(InputManager manager) { manager.m_Runtime.SendAnalytic(new ShutdownEventDataAnalytic(manager)); } /// /// Data about what configuration we start up with. /// /// /// Has data about the devices present at startup so that we can know what's being /// used out there. Also has data about devices we couldn't recognize. /// /// Note that we exclude devices that are always present (e.g. keyboard and mouse /// on desktops or touchscreen on phones). /// [Serializable] public struct StartupEventData : IInputAnalyticData { public string version; public DeviceInfo[] devices; public DeviceInfo[] unrecognized_devices; ////REVIEW: ATM we have no way of retrieving these in the player #if UNITY_EDITOR public bool new_enabled; public bool old_enabled; #endif [Serializable] public struct DeviceInfo { public string layout; public string @interface; public string product; public bool native; public static DeviceInfo FromDescription(InputDeviceDescription description, bool native = false, string layout = null) { string product; if (!string.IsNullOrEmpty(description.product) && !string.IsNullOrEmpty(description.manufacturer)) product = $"{description.manufacturer} {description.product}"; else if (!string.IsNullOrEmpty(description.product)) product = description.product; else product = description.manufacturer; if (string.IsNullOrEmpty(layout)) layout = description.deviceClass; return new DeviceInfo { layout = layout, @interface = description.interfaceName, product = product, native = native }; } } } #if UNITY_EDITOR && UNITY_2023_2_OR_NEWER [UnityEngine.Analytics.AnalyticInfo(eventName: kEventName, maxEventsPerHour: kMaxEventsPerHour, maxNumberOfElements: kMaxNumberOfElements, vendorKey: kVendorKey)] #endif // UNITY_EDITOR && UNITY_2023_2_OR_NEWER public struct StartupEventAnalytic : IInputAnalytic { public const string kEventName = "input_startup"; public const int kMaxEventsPerHour = 100; public const int kMaxNumberOfElements = 100; private InputManager m_InputManager; public StartupEventAnalytic(InputManager manager) { m_InputManager = manager; } public InputAnalyticInfo info => new InputAnalyticInfo(kEventName, kMaxEventsPerHour, kMaxNumberOfElements); #if UNITY_EDITOR && UNITY_2023_2_OR_NEWER public bool TryGatherData(out UnityEngine.Analytics.IAnalytic.IData data, out Exception error) #else public bool TryGatherData(out IInputAnalyticData data, out Exception error) #endif { try { data = new StartupEventData { version = InputSystem.version.ToString(), devices = CollectRecognizedDevices(m_InputManager), unrecognized_devices = CollectUnrecognizedDevices(m_InputManager), #if UNITY_EDITOR new_enabled = EditorPlayerSettingHelpers.newSystemBackendsEnabled, old_enabled = EditorPlayerSettingHelpers.oldSystemBackendsEnabled, #endif // UNITY_EDITOR }; error = null; return true; } catch (Exception e) { data = null; error = e; return false; } } private static StartupEventData.DeviceInfo[] CollectRecognizedDevices(InputManager manager) { var deviceInfo = new StartupEventData.DeviceInfo[manager.devices.Count]; for (var i = 0; i < manager.devices.Count; ++i) { deviceInfo[i] = StartupEventData.DeviceInfo.FromDescription( manager.devices[i].description, manager.devices[i].native, manager.devices[i].layout); } return deviceInfo; } private static StartupEventData.DeviceInfo[] CollectUnrecognizedDevices(InputManager manager) { var n = 0; var deviceInfo = new StartupEventData.DeviceInfo[manager.m_AvailableDeviceCount]; for (var i = 0; i < deviceInfo.Length; ++i) { var deviceId = manager.m_AvailableDevices[i].deviceId; if (manager.TryGetDeviceById(deviceId) != null) continue; deviceInfo[n++] = StartupEventData.DeviceInfo.FromDescription( manager.m_AvailableDevices[i].description, manager.m_AvailableDevices[i].isNative); } if (deviceInfo.Length > n) Array.Resize(ref deviceInfo, n); return deviceInfo; } } /// /// Data about when after startup the user first interacted with the application. /// [Serializable] public struct FirstUserInteractionEventData : IInputAnalyticData { } /// /// Data about what level of data we pumped through the system throughout its lifetime. /// [Serializable] public struct ShutdownEventData : IInputAnalyticData { public int max_num_devices; public int max_state_size_in_bytes; public int total_event_bytes; public int total_event_count; public int total_frame_count; public float total_event_processing_time; } #if (UNITY_EDITOR && UNITY_2023_2_OR_NEWER) [UnityEngine.Analytics.AnalyticInfo(eventName: kEventName, maxEventsPerHour: kMaxEventsPerHour, maxNumberOfElements: kMaxNumberOfElements, vendorKey: kVendorKey)] #endif // (UNITY_EDITOR && UNITY_2023_2_OR_NEWER) public readonly struct ShutdownEventDataAnalytic : IInputAnalytic { public const string kEventName = "input_shutdown"; public const int kMaxEventsPerHour = 100; public const int kMaxNumberOfElements = 100; private readonly InputManager m_InputManager; public ShutdownEventDataAnalytic(InputManager manager) { m_InputManager = manager; } public InputAnalyticInfo info => new InputAnalyticInfo(kEventName, kMaxEventsPerHour, kMaxNumberOfElements); #if UNITY_EDITOR && UNITY_2023_2_OR_NEWER public bool TryGatherData(out UnityEngine.Analytics.IAnalytic.IData data, out Exception error) #else public bool TryGatherData(out IInputAnalyticData data, out Exception error) #endif { try { var metrics = m_InputManager.metrics; data = new ShutdownEventData { max_num_devices = metrics.maxNumDevices, max_state_size_in_bytes = metrics.maxStateSizeInBytes, total_event_bytes = metrics.totalEventBytes, total_event_count = metrics.totalEventCount, total_frame_count = metrics.totalUpdateCount, total_event_processing_time = (float)metrics.totalEventProcessingTime, }; error = null; return true; } catch (Exception e) { data = null; error = e; return false; } } } } internal static class AnalyticExtensions { internal static void Send(this TSource analytic) where TSource : InputAnalytics.IInputAnalytic { InputSystem.s_Manager?.m_Runtime?.SendAnalytic(analytic); } } } #endif // UNITY_ANALYTICS || UNITY_EDITOR