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}