-
Notifications
You must be signed in to change notification settings - Fork 339
Expand file tree
/
Copy pathNativeInputRuntime.cs
More file actions
432 lines (377 loc) · 16.1 KB
/
Copy pathNativeInputRuntime.cs
File metadata and controls
432 lines (377 loc) · 16.1 KB
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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
using System;
using System.Linq;
using Unity.Collections.LowLevel.Unsafe;
#if UNITY_ANALYTICS
using UnityEngine.Analytics;
#endif
using UnityEngine.InputSystem.Utilities;
using UnityEngineInternal.Input;
// 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
{
private static NativeInputRuntime s_Instance;
// Private ctor exists to enforce Singleton pattern
private NativeInputRuntime() {}
/// <summary>
/// Employ the Singleton pattern for this class and initialize a new instance on first use.
/// </summary>
/// <remarks>
/// This property is typically used to initialize InputManager and isn't used afterwards, i.e. there's
/// no perf impact to the null check.
/// </remarks>
public static NativeInputRuntime instance
{
get
{
s_Instance ??= new NativeInputRuntime();
return s_Instance;
}
}
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 so users can easily identify the actual failure.
Debug.LogException(e);
Debug.LogError($"Exception {e.GetType().Name}: {e.Message} 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;
}
// Callbacks set by Editor to handle shutdown subscription
// In Editor, we use EditorApplication.wantsToQuit which expects Func<bool>
internal Action<Func<bool>> m_RegisterWantsToQuit;
internal Action<Func<bool>> m_UnregisterWantsToQuit;
public Action onShutdown
{
get => m_ShutdownMethod;
set
{
if (value == null)
{
if (m_UnregisterWantsToQuit != null)
{
m_UnregisterWantsToQuit(OnWantsToShutdown);
}
else
{
Application.quitting -= OnShutdown;
}
}
else if (m_ShutdownMethod == null)
{
if (m_RegisterWantsToQuit != null)
{
m_RegisterWantsToQuit(OnWantsToShutdown);
}
else
{
Application.quitting += OnShutdown;
}
}
m_ShutdownMethod = value;
}
}
#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
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;
}
}
#endif
private FocusFlags m_FocusState = FocusFlags.None;
public FocusFlags focusState
{
get => m_FocusState;
set => m_FocusState = value;
}
public bool isPlayerFocused => (m_FocusState & FocusFlags.ApplicationFocus) != FocusFlags.None;
public float pollingFrequency
{
#if UNITY_INPUT_SYSTEM_PLATFORM_POLLING_FREQUENCY
get => NativeInputSystem.GetPollingFrequency();
#else
get => m_PollingFrequency;
#endif
set
{
#if !UNITY_INPUT_SYSTEM_PLATFORM_POLLING_FREQUENCY
m_PollingFrequency = value;
#endif
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;
}
private 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
#if !UNITY_INPUT_SYSTEM_PLATFORM_POLLING_FREQUENCY
// From Unity 6000.3.0a2 (TODO Update comment and manifest before landing PR) this is handled by module
// and initial value is suggested by the platform based on its supported device set.
// In older version this is stored here and package override module/platform.
private float m_PollingFrequency = 60.0f;
#endif
private bool m_DidCallOnShutdown;
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 down in some cases is
// better than never.
OnShutdown();
m_DidCallOnShutdown = true;
}
return true;
}
public void InitializeFocusState()
{
m_FocusState = Application.isFocused
? m_FocusState | FocusFlags.ApplicationFocus
: m_FocusState & ~FocusFlags.ApplicationFocus;
}
#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
private Action<bool> m_FocusChangedMethod;
private void OnFocusChanged(bool focus)
{
m_FocusChangedMethod(focus);
// We set this *after* the callback above as InputManager.defaultUpdateType is influenced by the property.
m_FocusState = focus
? m_FocusState | FocusFlags.ApplicationFocus
: m_FocusState & ~FocusFlags.ApplicationFocus;
}
#endif
public Vector2 screenSize => new Vector2(Screen.width, Screen.height);
public ScreenOrientation screenOrientation => Screen.orientation;
#if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA
public bool normalizeScrollWheelDelta
{
get => NativeInputSystem.normalizeScrollWheelDelta;
set => NativeInputSystem.normalizeScrollWheelDelta = value;
}
public float scrollWheelDeltaPerTick
{
get => NativeInputSystem.GetScrollWheelDeltaPerTick();
}
#endif
#if UNITY_EDITOR
// These delegates are set by InputSystemEditorInitializer to avoid direct Editor dependencies
internal Func<bool> m_IsInPlayMode;
internal Func<bool> m_IsEditorActive;
internal Func<bool> m_IsEditorPaused;
public bool isInPlayMode => m_IsInPlayMode?.Invoke() ?? false;
public bool isEditorActive => m_IsEditorActive?.Invoke() ?? true;
public bool isEditorPaused => m_IsEditorPaused?.Invoke() ?? false;
private Action<InputPlayModeChange> m_OnPlayModeChanged;
private Action m_OnProjectChanged;
/// <summary>
/// Callback for play mode state changes.
/// </summary>
public Action<InputPlayModeChange> onPlayModeChanged
{
get => m_OnPlayModeChanged;
set => m_OnPlayModeChanged = value;
}
public Action onProjectChange
{
get => m_OnProjectChanged;
set => m_OnProjectChanged = value;
}
/// <summary>
/// Called by InputSystemEditorInitializer to dispatch play mode changes
/// </summary>
internal void DispatchPlayModeChange(InputPlayModeChange change)
{
m_OnPlayModeChanged?.Invoke(change);
}
/// <summary>
/// Called by InputSystemEditorInitializer to dispatch project changes
/// </summary>
internal void DispatchProjectChange()
{
m_OnProjectChanged?.Invoke();
}
#endif // UNITY_EDITOR
#if UNITY_ANALYTICS || UNITY_EDITOR
// Callback for sending analytics in Editor - set by InputSystemEditorInitializer
internal Action<InputAnalytics.IInputAnalytic> m_SendEditorAnalytic;
public void SendAnalytic(InputAnalytics.IInputAnalytic analytic)
{
#if ENABLE_CLOUD_SERVICES_ANALYTICS
// In Editor, use the callback set by InputSystemEditorInitializer
if (m_SendEditorAnalytic != null)
{
m_SendEditorAnalytic(analytic);
return;
}
#if UNITY_ANALYTICS
// In Player builds, use the regular Analytics API
var info = analytic.info;
Analytics.Analytics.RegisterEvent(info.Name, info.MaxEventsPerHour, info.MaxNumberOfElements, InputAnalytics.kVendorKey);
if (analytic.TryGatherData(out var data, out var error))
Analytics.Analytics.SendEvent(info.Name, data);
else
Debug.Log(error); // Non fatal
#endif
#endif //ENABLE_CLOUD_SERVICES_ANALYTICS
}
#endif // UNITY_ANALYTICS || UNITY_EDITOR
}
}