File size: 15,070 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
using System;
using System.Linq;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Utilities;
using UnityEngineInternal.Input;

#if UNITY_EDITOR
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;

#endif

// This should be the only file referencing the API at UnityEngineInternal.Input.

namespace UnityEngine.InputSystem.LowLevel
{
    /// <summary>
    /// Implements <see cref="IInputRuntime"/> based on <see cref="NativeInputSystem"/>.
    /// </summary>
    internal class NativeInputRuntime : IInputRuntime
    {
        public static readonly NativeInputRuntime instance = new NativeInputRuntime();

        public int AllocateDeviceId()
        {
            return NativeInputSystem.AllocateDeviceId();
        }

        public void Update(InputUpdateType updateType)
        {
            NativeInputSystem.Update((NativeInputUpdateType)updateType);
        }

        public unsafe void QueueEvent(InputEvent* ptr)
        {
            NativeInputSystem.QueueInputEvent((IntPtr)ptr);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "False positive.")]
        public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr)
        {
            if (commandPtr == null)
                throw new ArgumentNullException(nameof(commandPtr));

            return NativeInputSystem.IOCTL(deviceId, commandPtr->type, new IntPtr(commandPtr->payloadPtr), commandPtr->payloadSizeInBytes);
        }

        public unsafe InputUpdateDelegate onUpdate
        {
            get => m_OnUpdate;
            set
            {
                if (value != null)
                    NativeInputSystem.onUpdate =
                        (updateType, eventBufferPtr) =>
                    {
                        var buffer = new InputEventBuffer((InputEvent*)eventBufferPtr->eventBuffer,
                            eventBufferPtr->eventCount,
                            sizeInBytes: eventBufferPtr->sizeInBytes,
                            capacityInBytes: eventBufferPtr->capacityInBytes);

                        try
                        {
                            value((InputUpdateType)updateType, ref buffer);
                        }
                        catch (Exception e)
                        {
                            // Always report the original exception first to confuse users less about what it the actual failure.
                            Debug.LogException(e);
                            Debug.LogError($"{e.GetType().Name} during event processing of {updateType} update; resetting event buffer");
                            buffer.Reset();
                        }

                        if (buffer.eventCount > 0)
                        {
                            eventBufferPtr->eventCount = buffer.eventCount;
                            eventBufferPtr->sizeInBytes = (int)buffer.sizeInBytes;
                            eventBufferPtr->capacityInBytes = (int)buffer.capacityInBytes;
                            eventBufferPtr->eventBuffer =
                                NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer.data);
                        }
                        else
                        {
                            eventBufferPtr->eventCount = 0;
                            eventBufferPtr->sizeInBytes = 0;
                        }
                    };
                else
                    NativeInputSystem.onUpdate = null;
                m_OnUpdate = value;
            }
        }

        public Action<InputUpdateType> onBeforeUpdate
        {
            get => m_OnBeforeUpdate;
            set
            {
                // This is stupid but the enum prevents us from jacking the delegate in directly.
                // This means we get a double dispatch here :(
                if (value != null)
                    NativeInputSystem.onBeforeUpdate = updateType => value((InputUpdateType)updateType);
                else
                    NativeInputSystem.onBeforeUpdate = null;
                m_OnBeforeUpdate = value;
            }
        }

        public Func<InputUpdateType, bool> onShouldRunUpdate
        {
            get => m_OnShouldRunUpdate;
            set
            {
                // This is stupid but the enum prevents us from jacking the delegate in directly.
                // This means we get a double dispatch here :(
                if (value != null)
                    NativeInputSystem.onShouldRunUpdate = updateType => value((InputUpdateType)updateType);
                else
                    NativeInputSystem.onShouldRunUpdate = null;
                m_OnShouldRunUpdate = value;
            }
        }

        #if UNITY_EDITOR
        private struct InputSystemPlayerLoopRunnerInitializationSystem {};
        public Action onPlayerLoopInitialization
        {
            get => m_PlayerLoopInitialization;
            set
            {
                // This is a hot-fix for a critical problem in input system, case 1368559, case 1367556, case 1372830
                // TODO move it to a proper native callback instead
                if (value != null)
                {
                    // Inject ourselves directly to PlayerLoop.Initialization as first subsystem to run,
                    // Use InputSystemPlayerLoopRunnerInitializationSystem as system type
                    var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop();
                    var initStepIndex = playerLoop.subSystemList.IndexOf(x => x.type == typeof(PlayerLoop.Initialization));
                    if (initStepIndex >= 0)
                    {
                        var systems = playerLoop.subSystemList[initStepIndex].subSystemList;

                        // Check if we're not already injected
                        if (!systems.Select(x => x.type)
                            .Contains(typeof(InputSystemPlayerLoopRunnerInitializationSystem)))
                        {
                            ArrayHelpers.InsertAt(ref systems, 0, new UnityEngine.LowLevel.PlayerLoopSystem
                            {
                                type = typeof(InputSystemPlayerLoopRunnerInitializationSystem),
                                updateDelegate = () => m_PlayerLoopInitialization?.Invoke()
                            });

                            playerLoop.subSystemList[initStepIndex].subSystemList = systems;
                            UnityEngine.LowLevel.PlayerLoop.SetPlayerLoop(playerLoop);
                        }
                    }
                }

                m_PlayerLoopInitialization = value;
            }
        }
        #endif

        public Action<int, string> onDeviceDiscovered
        {
            get => NativeInputSystem.onDeviceDiscovered;
            set => NativeInputSystem.onDeviceDiscovered = value;
        }

        public Action onShutdown
        {
            get => m_ShutdownMethod;
            set
            {
                if (value == null)
                {
                    #if UNITY_EDITOR
                    EditorApplication.wantsToQuit -= OnWantsToShutdown;
                    #else
                    Application.quitting -= OnShutdown;
                    #endif
                }
                else if (m_ShutdownMethod == null)
                {
                    #if UNITY_EDITOR
                    EditorApplication.wantsToQuit += OnWantsToShutdown;
                    #else
                    Application.quitting += OnShutdown;
                    #endif
                }

                m_ShutdownMethod = value;
            }
        }

        public Action<bool> onPlayerFocusChanged
        {
            get => m_FocusChangedMethod;
            set
            {
                if (value == null)
                    Application.focusChanged -= OnFocusChanged;
                else if (m_FocusChangedMethod == null)
                    Application.focusChanged += OnFocusChanged;
                m_FocusChangedMethod = value;
            }
        }

        public bool isPlayerFocused => Application.isFocused;

        public float pollingFrequency
        {
            get => m_PollingFrequency;
            set
            {
                m_PollingFrequency = value;
                NativeInputSystem.SetPollingFrequency(value);
            }
        }

        public double currentTime => NativeInputSystem.currentTime;

        ////REVIEW: this applies the offset, currentTime doesn't
        public double currentTimeForFixedUpdate => Time.fixedUnscaledTime + currentTimeOffsetToRealtimeSinceStartup;

        public double currentTimeOffsetToRealtimeSinceStartup => NativeInputSystem.currentTimeOffsetToRealtimeSinceStartup;
        public float unscaledGameTime => Time.unscaledTime;

        public bool runInBackground
        {
            get =>
                Application.runInBackground ||
                // certain platforms ignore the runInBackground flag and always run. Make sure we're
                // not running on one of those and set the values when running on specific platforms.
                m_RunInBackground;
            set => m_RunInBackground = value;
        }

        bool m_RunInBackground;

        private Action m_ShutdownMethod;
        private InputUpdateDelegate m_OnUpdate;
        private Action<InputUpdateType> m_OnBeforeUpdate;
        private Func<InputUpdateType, bool> m_OnShouldRunUpdate;
        #if UNITY_EDITOR
        private Action m_PlayerLoopInitialization;
        #endif
        private float m_PollingFrequency = 60.0f;
        private bool m_DidCallOnShutdown = false;
        private void OnShutdown()
        {
            m_ShutdownMethod();
        }

        private bool OnWantsToShutdown()
        {
            if (!m_DidCallOnShutdown)
            {
                // we should use `EditorApplication.quitting`, but that is too late
                // to send an analytics event, because Analytics is already shut down
                // at that point. So we use `EditorApplication.wantsToQuit`, and make sure
                // to only use the first time. This is currently only used for analytics,
                // and getting analytics before we actually shut downn in some cases is
                // better then never.

                OnShutdown();
                m_DidCallOnShutdown = true;
            }

            return true;
        }

        private Action<bool> m_FocusChangedMethod;

        private void OnFocusChanged(bool focus)
        {
            m_FocusChangedMethod(focus);
        }

        public Vector2 screenSize => new Vector2(Screen.width, Screen.height);
        public ScreenOrientation screenOrientation => Screen.orientation;

        public bool isInBatchMode => Application.isBatchMode;

        #if UNITY_EDITOR

        public bool isInPlayMode => EditorApplication.isPlaying;
        public bool isPaused => EditorApplication.isPaused;
        public bool isEditorActive => InternalEditorUtility.isApplicationActive;

        public Func<IntPtr, bool> onUnityRemoteMessage
        {
            set
            {
                if (m_UnityRemoteMessageHandler == value)
                    return;

                if (m_UnityRemoteMessageHandler != null)
                {
                    var removeMethod = GetUnityRemoteAPIMethod("RemoveMessageHandler");
                    removeMethod?.Invoke(null, new[] { m_UnityRemoteMessageHandler });
                    m_UnityRemoteMessageHandler = null;
                }

                if (value != null)
                {
                    var addMethod = GetUnityRemoteAPIMethod("AddMessageHandler");
                    addMethod?.Invoke(null, new[] { value });
                    m_UnityRemoteMessageHandler = value;
                }
            }
        }

        public void SetUnityRemoteGyroEnabled(bool value)
        {
            var setMethod = GetUnityRemoteAPIMethod("SetGyroEnabled");
            setMethod?.Invoke(null, new object[] { value });
        }

        public void SetUnityRemoteGyroUpdateInterval(float interval)
        {
            var setMethod = GetUnityRemoteAPIMethod("SetGyroUpdateInterval");
            setMethod?.Invoke(null, new object[] { interval });
        }

        private MethodInfo GetUnityRemoteAPIMethod(string methodName)
        {
            var editorAssembly = typeof(EditorApplication).Assembly;
            var genericRemoteClass = editorAssembly.GetType("UnityEditor.Remote.GenericRemote");
            if (genericRemoteClass == null)
                return null;

            return genericRemoteClass.GetMethod(methodName);
        }

        private Func<IntPtr, bool> m_UnityRemoteMessageHandler;
        private Action<PlayModeStateChange> m_OnPlayModeChanged;
        private Action m_OnProjectChanged;

        private void OnPlayModeStateChanged(PlayModeStateChange value)
        {
            m_OnPlayModeChanged(value);
        }

        private void OnProjectChanged()
        {
            m_OnProjectChanged();
        }

        public Action<PlayModeStateChange> onPlayModeChanged
        {
            get => m_OnPlayModeChanged;
            set
            {
                if (value == null)
                    EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
                else if (m_OnPlayModeChanged == null)
                    EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
                m_OnPlayModeChanged = value;
            }
        }

        public Action onProjectChange
        {
            get => m_OnProjectChanged;
            set
            {
                if (value == null)
                    EditorApplication.projectChanged -= OnProjectChanged;
                else if (m_OnProjectChanged == null)
                    EditorApplication.projectChanged += OnProjectChanged;
                m_OnProjectChanged = value;
            }
        }

        #endif // UNITY_EDITOR

        public void RegisterAnalyticsEvent(string name, int maxPerHour, int maxPropertiesPerEvent)
        {
            #if UNITY_ANALYTICS
            const string vendorKey = "unity.input";
            #if UNITY_EDITOR
            EditorAnalytics.RegisterEventWithLimit(name, maxPerHour, maxPropertiesPerEvent, vendorKey);
            #else
            Analytics.Analytics.RegisterEvent(name, maxPerHour, maxPropertiesPerEvent, vendorKey);
            #endif // UNITY_EDITOR
            #endif // UNITY_ANALYTICS
        }

        public void SendAnalyticsEvent(string name, object data)
        {
            #if UNITY_ANALYTICS
            #if UNITY_EDITOR
            EditorAnalytics.SendEventWithLimit(name, data);
            #else
            Analytics.Analytics.SendEvent(name, data);
            #endif // UNITY_EDITOR
            #endif // UNITY_ANALYTICS
        }
    }
}