UnityGame/Library/PackageCache/com.unity.inputsystem/InputSystem/InputAnalytics.cs
2024-10-27 10:53:47 +03:00

287 lines
11 KiB
C#

#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));
}
/// <summary>
/// Data about what configuration we start up with.
/// </summary>
/// <remarks>
/// 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).
/// </remarks>
[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;
}
}
/// <summary>
/// Data about when after startup the user first interacted with the application.
/// </summary>
[Serializable]
public struct FirstUserInteractionEventData : IInputAnalyticData
{
}
/// <summary>
/// Data about what level of data we pumped through the system throughout its lifetime.
/// </summary>
[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<TSource>(this TSource analytic) where TSource : InputAnalytics.IInputAnalytic
{
InputSystem.s_Manager?.m_Runtime?.SendAnalytic(analytic);
}
}
}
#endif // UNITY_ANALYTICS || UNITY_EDITOR