A game about forced loneliness, made by TACStudios
1#if UNITY_ANALYTICS || UNITY_EDITOR
2using System;
3using UnityEngine.InputSystem.Layouts;
4#if UNITY_EDITOR
5using UnityEngine.InputSystem.Editor;
6#endif // UNITY_EDITOR
7
8////FIXME: apparently shutdown events are not coming through in the analytics backend
9
10namespace UnityEngine.InputSystem
11{
12 internal static class InputAnalytics
13 {
14 public const string kVendorKey = "unity.input";
15
16 // Struct similar to AnalyticInfo for simplifying usage.
17 public struct InputAnalyticInfo
18 {
19 public InputAnalyticInfo(string name, int maxEventsPerHour, int maxNumberOfElements)
20 {
21 Name = name;
22 MaxEventsPerHour = maxEventsPerHour;
23 MaxNumberOfElements = maxNumberOfElements;
24 }
25
26 public readonly string Name;
27 public readonly int MaxEventsPerHour;
28 public readonly int MaxNumberOfElements;
29 }
30
31 // Note: Needs to be externalized from interface depending on C# version.
32 public interface IInputAnalyticData
33#if UNITY_EDITOR && UNITY_2023_2_OR_NEWER
34 : UnityEngine.Analytics.IAnalytic.IData
35#endif
36 {}
37
38 // Unity 2023.2+ deprecates legacy interfaces for registering and sending editor analytics and
39 // replaces them with attribute annotations and required interface implementations.
40 // The IInputAnalytic interface have been introduced here to support both variants
41 // of analytics reporting. Notice that a difference is that data is collected lazily as part
42 // of sending the analytics via the framework.
43 public interface IInputAnalytic
44#if UNITY_EDITOR && UNITY_2023_2_OR_NEWER
45 : UnityEngine.Analytics.IAnalytic
46#endif // UNITY_EDITOR && UNITY_2023_2_OR_NEWER
47 {
48 InputAnalyticInfo info { get; } // May be removed when only supporting 2023.2+ versions
49
50#if !UNITY_2023_2_OR_NEWER
51 // Conditionally mimic UnityEngine.Analytics.IAnalytic
52 bool TryGatherData(out IInputAnalyticData data, out Exception error);
53#endif // !UNITY_2023_2_OR_NEWER
54 }
55
56 public static void Initialize(InputManager manager)
57 {
58 Debug.Assert(manager.m_Runtime != null);
59 }
60
61 public static void OnStartup(InputManager manager)
62 {
63 manager.m_Runtime.SendAnalytic(new StartupEventAnalytic(manager));
64 }
65
66 public static void OnShutdown(InputManager manager)
67 {
68 manager.m_Runtime.SendAnalytic(new ShutdownEventDataAnalytic(manager));
69 }
70
71 /// <summary>
72 /// Data about what configuration we start up with.
73 /// </summary>
74 /// <remarks>
75 /// Has data about the devices present at startup so that we can know what's being
76 /// used out there. Also has data about devices we couldn't recognize.
77 ///
78 /// Note that we exclude devices that are always present (e.g. keyboard and mouse
79 /// on desktops or touchscreen on phones).
80 /// </remarks>
81 [Serializable]
82 public struct StartupEventData : IInputAnalyticData
83 {
84 public string version;
85 public DeviceInfo[] devices;
86 public DeviceInfo[] unrecognized_devices;
87
88 ////REVIEW: ATM we have no way of retrieving these in the player
89 #if UNITY_EDITOR
90 public bool new_enabled;
91 public bool old_enabled;
92 #endif
93
94 [Serializable]
95 public struct DeviceInfo
96 {
97 public string layout;
98 public string @interface;
99 public string product;
100 public bool native;
101
102 public static DeviceInfo FromDescription(InputDeviceDescription description, bool native = false, string layout = null)
103 {
104 string product;
105 if (!string.IsNullOrEmpty(description.product) && !string.IsNullOrEmpty(description.manufacturer))
106 product = $"{description.manufacturer} {description.product}";
107 else if (!string.IsNullOrEmpty(description.product))
108 product = description.product;
109 else
110 product = description.manufacturer;
111
112 if (string.IsNullOrEmpty(layout))
113 layout = description.deviceClass;
114
115 return new DeviceInfo
116 {
117 layout = layout,
118 @interface = description.interfaceName,
119 product = product,
120 native = native
121 };
122 }
123 }
124 }
125
126#if UNITY_EDITOR && UNITY_2023_2_OR_NEWER
127 [UnityEngine.Analytics.AnalyticInfo(eventName: kEventName, maxEventsPerHour: kMaxEventsPerHour, maxNumberOfElements: kMaxNumberOfElements, vendorKey: kVendorKey)]
128#endif // UNITY_EDITOR && UNITY_2023_2_OR_NEWER
129 public struct StartupEventAnalytic : IInputAnalytic
130 {
131 public const string kEventName = "input_startup";
132 public const int kMaxEventsPerHour = 100;
133 public const int kMaxNumberOfElements = 100;
134
135 private InputManager m_InputManager;
136
137 public StartupEventAnalytic(InputManager manager)
138 {
139 m_InputManager = manager;
140 }
141
142 public InputAnalyticInfo info => new InputAnalyticInfo(kEventName, kMaxEventsPerHour, kMaxNumberOfElements);
143
144#if UNITY_EDITOR && UNITY_2023_2_OR_NEWER
145 public bool TryGatherData(out UnityEngine.Analytics.IAnalytic.IData data, out Exception error)
146#else
147 public bool TryGatherData(out IInputAnalyticData data, out Exception error)
148#endif
149 {
150 try
151 {
152 data = new StartupEventData
153 {
154 version = InputSystem.version.ToString(),
155 devices = CollectRecognizedDevices(m_InputManager),
156 unrecognized_devices = CollectUnrecognizedDevices(m_InputManager),
157#if UNITY_EDITOR
158 new_enabled = EditorPlayerSettingHelpers.newSystemBackendsEnabled,
159 old_enabled = EditorPlayerSettingHelpers.oldSystemBackendsEnabled,
160#endif // UNITY_EDITOR
161 };
162 error = null;
163 return true;
164 }
165 catch (Exception e)
166 {
167 data = null;
168 error = e;
169 return false;
170 }
171 }
172
173 private static StartupEventData.DeviceInfo[] CollectRecognizedDevices(InputManager manager)
174 {
175 var deviceInfo = new StartupEventData.DeviceInfo[manager.devices.Count];
176 for (var i = 0; i < manager.devices.Count; ++i)
177 {
178 deviceInfo[i] = StartupEventData.DeviceInfo.FromDescription(
179 manager.devices[i].description, manager.devices[i].native, manager.devices[i].layout);
180 }
181 return deviceInfo;
182 }
183
184 private static StartupEventData.DeviceInfo[] CollectUnrecognizedDevices(InputManager manager)
185 {
186 var n = 0;
187 var deviceInfo = new StartupEventData.DeviceInfo[manager.m_AvailableDeviceCount];
188 for (var i = 0; i < deviceInfo.Length; ++i)
189 {
190 var deviceId = manager.m_AvailableDevices[i].deviceId;
191 if (manager.TryGetDeviceById(deviceId) != null)
192 continue;
193
194 deviceInfo[n++] = StartupEventData.DeviceInfo.FromDescription(
195 manager.m_AvailableDevices[i].description, manager.m_AvailableDevices[i].isNative);
196 }
197
198 if (deviceInfo.Length > n)
199 Array.Resize(ref deviceInfo, n);
200
201 return deviceInfo;
202 }
203 }
204
205 /// <summary>
206 /// Data about when after startup the user first interacted with the application.
207 /// </summary>
208 [Serializable]
209 public struct FirstUserInteractionEventData : IInputAnalyticData
210 {
211 }
212
213 /// <summary>
214 /// Data about what level of data we pumped through the system throughout its lifetime.
215 /// </summary>
216 [Serializable]
217 public struct ShutdownEventData : IInputAnalyticData
218 {
219 public int max_num_devices;
220 public int max_state_size_in_bytes;
221 public int total_event_bytes;
222 public int total_event_count;
223 public int total_frame_count;
224 public float total_event_processing_time;
225 }
226
227#if (UNITY_EDITOR && UNITY_2023_2_OR_NEWER)
228 [UnityEngine.Analytics.AnalyticInfo(eventName: kEventName, maxEventsPerHour: kMaxEventsPerHour,
229 maxNumberOfElements: kMaxNumberOfElements, vendorKey: kVendorKey)]
230#endif // (UNITY_EDITOR && UNITY_2023_2_OR_NEWER)
231 public readonly struct ShutdownEventDataAnalytic : IInputAnalytic
232 {
233 public const string kEventName = "input_shutdown";
234 public const int kMaxEventsPerHour = 100;
235 public const int kMaxNumberOfElements = 100;
236
237 private readonly InputManager m_InputManager;
238
239 public ShutdownEventDataAnalytic(InputManager manager)
240 {
241 m_InputManager = manager;
242 }
243
244 public InputAnalyticInfo info => new InputAnalyticInfo(kEventName, kMaxEventsPerHour, kMaxNumberOfElements);
245
246#if UNITY_EDITOR && UNITY_2023_2_OR_NEWER
247 public bool TryGatherData(out UnityEngine.Analytics.IAnalytic.IData data, out Exception error)
248#else
249 public bool TryGatherData(out IInputAnalyticData data, out Exception error)
250#endif
251 {
252 try
253 {
254 var metrics = m_InputManager.metrics;
255 data = new ShutdownEventData
256 {
257 max_num_devices = metrics.maxNumDevices,
258 max_state_size_in_bytes = metrics.maxStateSizeInBytes,
259 total_event_bytes = metrics.totalEventBytes,
260 total_event_count = metrics.totalEventCount,
261 total_frame_count = metrics.totalUpdateCount,
262 total_event_processing_time = (float)metrics.totalEventProcessingTime,
263 };
264 error = null;
265 return true;
266 }
267 catch (Exception e)
268 {
269 data = null;
270 error = e;
271 return false;
272 }
273 }
274 }
275 }
276
277 internal static class AnalyticExtensions
278 {
279 internal static void Send<TSource>(this TSource analytic) where TSource : InputAnalytics.IInputAnalytic
280 {
281 InputSystem.s_Manager?.m_Runtime?.SendAnalytic(analytic);
282 }
283 }
284}
285
286#endif // UNITY_ANALYTICS || UNITY_EDITOR