A game about forced loneliness, made by TACStudios
1using System;
2using System.Linq;
3using Unity.Collections.LowLevel.Unsafe;
4using UnityEngine.Analytics;
5using UnityEngine.InputSystem.Utilities;
6using UnityEngineInternal.Input;
7
8#if UNITY_EDITOR
9using System.Reflection;
10using UnityEditor;
11using UnityEditorInternal;
12#endif
13
14// This should be the only file referencing the API at UnityEngineInternal.Input.
15
16namespace UnityEngine.InputSystem.LowLevel
17{
18 /// <summary>
19 /// Implements <see cref="IInputRuntime"/> based on <see cref="NativeInputSystem"/>.
20 /// </summary>
21 internal class NativeInputRuntime : IInputRuntime
22 {
23 public static readonly NativeInputRuntime instance = new NativeInputRuntime();
24
25 public int AllocateDeviceId()
26 {
27 return NativeInputSystem.AllocateDeviceId();
28 }
29
30 public void Update(InputUpdateType updateType)
31 {
32 NativeInputSystem.Update((NativeInputUpdateType)updateType);
33 }
34
35 public unsafe void QueueEvent(InputEvent* ptr)
36 {
37 NativeInputSystem.QueueInputEvent((IntPtr)ptr);
38 }
39
40 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "False positive.")]
41 public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr)
42 {
43 if (commandPtr == null)
44 throw new ArgumentNullException(nameof(commandPtr));
45
46 return NativeInputSystem.IOCTL(deviceId, commandPtr->type, new IntPtr(commandPtr->payloadPtr), commandPtr->payloadSizeInBytes);
47 }
48
49 public unsafe InputUpdateDelegate onUpdate
50 {
51 get => m_OnUpdate;
52 set
53 {
54 if (value != null)
55 NativeInputSystem.onUpdate =
56 (updateType, eventBufferPtr) =>
57 {
58 var buffer = new InputEventBuffer((InputEvent*)eventBufferPtr->eventBuffer,
59 eventBufferPtr->eventCount,
60 sizeInBytes: eventBufferPtr->sizeInBytes,
61 capacityInBytes: eventBufferPtr->capacityInBytes);
62
63 try
64 {
65 value((InputUpdateType)updateType, ref buffer);
66 }
67 catch (Exception e)
68 {
69 // Always report the original exception first to confuse users less about what it the actual failure.
70 Debug.LogException(e);
71 Debug.LogError($"{e.GetType().Name} during event processing of {updateType} update; resetting event buffer");
72 buffer.Reset();
73 }
74
75 if (buffer.eventCount > 0)
76 {
77 eventBufferPtr->eventCount = buffer.eventCount;
78 eventBufferPtr->sizeInBytes = (int)buffer.sizeInBytes;
79 eventBufferPtr->capacityInBytes = (int)buffer.capacityInBytes;
80 eventBufferPtr->eventBuffer =
81 NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer.data);
82 }
83 else
84 {
85 eventBufferPtr->eventCount = 0;
86 eventBufferPtr->sizeInBytes = 0;
87 }
88 };
89 else
90 NativeInputSystem.onUpdate = null;
91 m_OnUpdate = value;
92 }
93 }
94
95 public Action<InputUpdateType> onBeforeUpdate
96 {
97 get => m_OnBeforeUpdate;
98 set
99 {
100 // This is stupid but the enum prevents us from jacking the delegate in directly.
101 // This means we get a double dispatch here :(
102 if (value != null)
103 NativeInputSystem.onBeforeUpdate = updateType => value((InputUpdateType)updateType);
104 else
105 NativeInputSystem.onBeforeUpdate = null;
106 m_OnBeforeUpdate = value;
107 }
108 }
109
110 public Func<InputUpdateType, bool> onShouldRunUpdate
111 {
112 get => m_OnShouldRunUpdate;
113 set
114 {
115 // This is stupid but the enum prevents us from jacking the delegate in directly.
116 // This means we get a double dispatch here :(
117 if (value != null)
118 NativeInputSystem.onShouldRunUpdate = updateType => value((InputUpdateType)updateType);
119 else
120 NativeInputSystem.onShouldRunUpdate = null;
121 m_OnShouldRunUpdate = value;
122 }
123 }
124
125 #if UNITY_EDITOR
126 private struct InputSystemPlayerLoopRunnerInitializationSystem {};
127 public Action onPlayerLoopInitialization
128 {
129 get => m_PlayerLoopInitialization;
130 set
131 {
132 // This is a hot-fix for a critical problem in input system, case 1368559, case 1367556, case 1372830
133 // TODO move it to a proper native callback instead
134 if (value != null)
135 {
136 // Inject ourselves directly to PlayerLoop.Initialization as first subsystem to run,
137 // Use InputSystemPlayerLoopRunnerInitializationSystem as system type
138 var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop();
139 var initStepIndex = playerLoop.subSystemList.IndexOf(x => x.type == typeof(PlayerLoop.Initialization));
140 if (initStepIndex >= 0)
141 {
142 var systems = playerLoop.subSystemList[initStepIndex].subSystemList;
143
144 // Check if we're not already injected
145 if (!systems.Select(x => x.type)
146 .Contains(typeof(InputSystemPlayerLoopRunnerInitializationSystem)))
147 {
148 ArrayHelpers.InsertAt(ref systems, 0, new UnityEngine.LowLevel.PlayerLoopSystem
149 {
150 type = typeof(InputSystemPlayerLoopRunnerInitializationSystem),
151 updateDelegate = () => m_PlayerLoopInitialization?.Invoke()
152 });
153
154 playerLoop.subSystemList[initStepIndex].subSystemList = systems;
155 UnityEngine.LowLevel.PlayerLoop.SetPlayerLoop(playerLoop);
156 }
157 }
158 }
159
160 m_PlayerLoopInitialization = value;
161 }
162 }
163 #endif
164
165 public Action<int, string> onDeviceDiscovered
166 {
167 get => NativeInputSystem.onDeviceDiscovered;
168 set => NativeInputSystem.onDeviceDiscovered = value;
169 }
170
171 public Action onShutdown
172 {
173 get => m_ShutdownMethod;
174 set
175 {
176 if (value == null)
177 {
178 #if UNITY_EDITOR
179 EditorApplication.wantsToQuit -= OnWantsToShutdown;
180 #else
181 Application.quitting -= OnShutdown;
182 #endif
183 }
184 else if (m_ShutdownMethod == null)
185 {
186 #if UNITY_EDITOR
187 EditorApplication.wantsToQuit += OnWantsToShutdown;
188 #else
189 Application.quitting += OnShutdown;
190 #endif
191 }
192
193 m_ShutdownMethod = value;
194 }
195 }
196
197 public Action<bool> onPlayerFocusChanged
198 {
199 get => m_FocusChangedMethod;
200 set
201 {
202 if (value == null)
203 Application.focusChanged -= OnFocusChanged;
204 else if (m_FocusChangedMethod == null)
205 Application.focusChanged += OnFocusChanged;
206 m_FocusChangedMethod = value;
207 }
208 }
209
210 public bool isPlayerFocused => Application.isFocused;
211
212 public float pollingFrequency
213 {
214 get => m_PollingFrequency;
215 set
216 {
217 m_PollingFrequency = value;
218 NativeInputSystem.SetPollingFrequency(value);
219 }
220 }
221
222 public double currentTime => NativeInputSystem.currentTime;
223
224 ////REVIEW: this applies the offset, currentTime doesn't
225 public double currentTimeForFixedUpdate => Time.fixedUnscaledTime + currentTimeOffsetToRealtimeSinceStartup;
226
227 public double currentTimeOffsetToRealtimeSinceStartup => NativeInputSystem.currentTimeOffsetToRealtimeSinceStartup;
228 public float unscaledGameTime => Time.unscaledTime;
229
230 public bool runInBackground
231 {
232 get =>
233 Application.runInBackground ||
234 // certain platforms ignore the runInBackground flag and always run. Make sure we're
235 // not running on one of those and set the values when running on specific platforms.
236 m_RunInBackground;
237 set => m_RunInBackground = value;
238 }
239
240 bool m_RunInBackground;
241
242 private Action m_ShutdownMethod;
243 private InputUpdateDelegate m_OnUpdate;
244 private Action<InputUpdateType> m_OnBeforeUpdate;
245 private Func<InputUpdateType, bool> m_OnShouldRunUpdate;
246 #if UNITY_EDITOR
247 private Action m_PlayerLoopInitialization;
248 #endif
249 private float m_PollingFrequency = 60.0f;
250 private bool m_DidCallOnShutdown = false;
251 private void OnShutdown()
252 {
253 m_ShutdownMethod();
254 }
255
256 private bool OnWantsToShutdown()
257 {
258 if (!m_DidCallOnShutdown)
259 {
260 // we should use `EditorApplication.quitting`, but that is too late
261 // to send an analytics event, because Analytics is already shut down
262 // at that point. So we use `EditorApplication.wantsToQuit`, and make sure
263 // to only use the first time. This is currently only used for analytics,
264 // and getting analytics before we actually shut downn in some cases is
265 // better then never.
266
267 OnShutdown();
268 m_DidCallOnShutdown = true;
269 }
270
271 return true;
272 }
273
274 private Action<bool> m_FocusChangedMethod;
275
276 private void OnFocusChanged(bool focus)
277 {
278 m_FocusChangedMethod(focus);
279 }
280
281 public Vector2 screenSize => new Vector2(Screen.width, Screen.height);
282 public ScreenOrientation screenOrientation => Screen.orientation;
283
284#if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA
285 public bool normalizeScrollWheelDelta
286 {
287 get => NativeInputSystem.normalizeScrollWheelDelta;
288 set => NativeInputSystem.normalizeScrollWheelDelta = value;
289 }
290
291 public float scrollWheelDeltaPerTick
292 {
293 get => NativeInputSystem.GetScrollWheelDeltaPerTick();
294 }
295#endif
296
297 public bool isInBatchMode => Application.isBatchMode;
298
299 #if UNITY_EDITOR
300
301 public bool isInPlayMode => EditorApplication.isPlaying;
302 public bool isPaused => EditorApplication.isPaused;
303 public bool isEditorActive => InternalEditorUtility.isApplicationActive;
304
305 public Func<IntPtr, bool> onUnityRemoteMessage
306 {
307 set
308 {
309 if (m_UnityRemoteMessageHandler == value)
310 return;
311
312 if (m_UnityRemoteMessageHandler != null)
313 {
314 var removeMethod = GetUnityRemoteAPIMethod("RemoveMessageHandler");
315 removeMethod?.Invoke(null, new[] { m_UnityRemoteMessageHandler });
316 m_UnityRemoteMessageHandler = null;
317 }
318
319 if (value != null)
320 {
321 var addMethod = GetUnityRemoteAPIMethod("AddMessageHandler");
322 addMethod?.Invoke(null, new[] { value });
323 m_UnityRemoteMessageHandler = value;
324 }
325 }
326 }
327
328 public void SetUnityRemoteGyroEnabled(bool value)
329 {
330 var setMethod = GetUnityRemoteAPIMethod("SetGyroEnabled");
331 setMethod?.Invoke(null, new object[] { value });
332 }
333
334 public void SetUnityRemoteGyroUpdateInterval(float interval)
335 {
336 var setMethod = GetUnityRemoteAPIMethod("SetGyroUpdateInterval");
337 setMethod?.Invoke(null, new object[] { interval });
338 }
339
340 private MethodInfo GetUnityRemoteAPIMethod(string methodName)
341 {
342 var editorAssembly = typeof(EditorApplication).Assembly;
343 var genericRemoteClass = editorAssembly.GetType("UnityEditor.Remote.GenericRemote");
344 if (genericRemoteClass == null)
345 return null;
346
347 return genericRemoteClass.GetMethod(methodName);
348 }
349
350 private Func<IntPtr, bool> m_UnityRemoteMessageHandler;
351 private Action<PlayModeStateChange> m_OnPlayModeChanged;
352 private Action m_OnProjectChanged;
353
354 private void OnPlayModeStateChanged(PlayModeStateChange value)
355 {
356 m_OnPlayModeChanged(value);
357 }
358
359 private void OnProjectChanged()
360 {
361 m_OnProjectChanged();
362 }
363
364 public Action<PlayModeStateChange> onPlayModeChanged
365 {
366 get => m_OnPlayModeChanged;
367 set
368 {
369 if (value == null)
370 EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
371 else if (m_OnPlayModeChanged == null)
372 EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
373 m_OnPlayModeChanged = value;
374 }
375 }
376
377 public Action onProjectChange
378 {
379 get => m_OnProjectChanged;
380 set
381 {
382 if (value == null)
383 EditorApplication.projectChanged -= OnProjectChanged;
384 else if (m_OnProjectChanged == null)
385 EditorApplication.projectChanged += OnProjectChanged;
386 m_OnProjectChanged = value;
387 }
388 }
389
390 #endif // UNITY_EDITOR
391
392 #if UNITY_ANALYTICS || UNITY_EDITOR
393
394 public void SendAnalytic(InputAnalytics.IInputAnalytic analytic)
395 {
396 #if ENABLE_CLOUD_SERVICES_ANALYTICS
397 #if (UNITY_EDITOR)
398 #if (UNITY_2023_2_OR_NEWER)
399 EditorAnalytics.SendAnalytic(analytic);
400 #else
401 // The preprocessor filtering is a workaround for the fact that the AnalyticsResult enum is not available before 2023.1.0a14 when not using the built-in Unity Analytics module.
402 #if UNITY_INPUT_SYSTEM_ENABLE_ANALYTICS || UNITY_2023_1_OR_NEWER
403 var info = analytic.info;
404 EditorAnalytics.RegisterEventWithLimit(info.Name, info.MaxEventsPerHour, info.MaxNumberOfElements, InputAnalytics.kVendorKey);
405 EditorAnalytics.SendEventWithLimit(info.Name, analytic);
406 #endif // UNITY_INPUT_SYSTEM_ENABLE_ANALYTICS || UNITY_2023_1_OR_NEWER
407 #endif // UNITY_2023_2_OR_NEWER
408 #elif (UNITY_ANALYTICS) // Implicitly: !UNITY_EDITOR
409 var info = analytic.info;
410 Analytics.Analytics.RegisterEvent(info.Name, info.MaxEventsPerHour, info.MaxNumberOfElements, InputAnalytics.kVendorKey);
411 if (analytic.TryGatherData(out var data, out var error))
412 Analytics.Analytics.SendEvent(info.Name, data);
413 else
414 Debug.Log(error); // Non fatal
415 #endif //UNITY_EDITOR
416 #endif //ENABLE_CLOUD_SERVICES_ANALYTICS
417 }
418
419 #endif // UNITY_ANALYTICS || UNITY_EDITOR
420 }
421}