-
Notifications
You must be signed in to change notification settings - Fork 335
FIX: Re-inject player loop hook after user-driven loop reset (UUM-140343) #2419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
c6cf4f9
12a5921
209331a
e1b096b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -148,33 +148,63 @@ | |
| // 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); | ||
| } | ||
| } | ||
| EnsurePlayerLoopInjection(); | ||
|
|
||
| // Watchdog for UUM-140343: user code calling | ||
| // PlayerLoop.SetPlayerLoop(GetDefaultPlayerLoop()) wipes our injection, | ||
| // and Unity provides no callback for SetPlayerLoop, no engine API to add to | ||
| // the default loop, and no per-frame hook inside the player loop that we | ||
| // could anchor to (anything in the loop can be wiped the same way). | ||
| // EditorApplication.update is invoked by SceneTracker, not by the player loop | ||
| // (see Editor/Src/Selection/SceneInspector.cpp), so it survives a user reset | ||
| // and lets us re-inject on the next editor tick. The cost is one cheap | ||
| // PlayerLoopSystem-array scan per editor tick while playing -- effectively zero | ||
| // when nothing's wrong. Outside play mode the early-return below skips it. | ||
| UnityEditor.EditorApplication.update -= EnsurePlayerLoopInjectionIfPlaying; | ||
| UnityEditor.EditorApplication.update += EnsurePlayerLoopInjectionIfPlaying; | ||
| } | ||
| else | ||
| { | ||
| UnityEditor.EditorApplication.update -= EnsurePlayerLoopInjectionIfPlaying; | ||
| } | ||
|
|
||
| m_PlayerLoopInitialization = value; | ||
| } | ||
| } | ||
|
|
||
| // Idempotent: inserts InputSystemPlayerLoopRunnerInitializationSystem at the top of | ||
| // PlayerLoop.Initialization if it's not already there. | ||
| private void EnsurePlayerLoopInjection() | ||
| { | ||
| var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop(); | ||
| var initStepIndex = playerLoop.subSystemList.IndexOf(x => x.type == typeof(PlayerLoop.Initialization)); | ||
| if (initStepIndex < 0) | ||
| return; | ||
|
|
||
| var systems = playerLoop.subSystemList[initStepIndex].subSystemList; | ||
| if (systems.Select(x => x.type) | ||
| .Contains(typeof(InputSystemPlayerLoopRunnerInitializationSystem))) | ||
| return; // Already injected. | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| // Watchdog tick. Fires from EditorApplication.update (independent of the player loop) and | ||
| // re-injects only when actually needed. In edit mode we don't care; OnPlayerLoopInitialization | ||
| // is gated on gameIsPlaying anyway, so a wiped injection while not playing is harmless. | ||
| private void EnsurePlayerLoopInjectionIfPlaying() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a lot of work to do on every tick and currently also triggering LINQ it seems - another idea - let me know what you think, do an active watchdog check instead, e.g. on each editor tick where the player hook is actually called we store the current timestamp
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Completely agree, I don't think this PR should move forward, it was just to illustrate the issue and a (bad) fix. I don't think we should invest on a watchdog at all, unless there is something I am missing, we should just aim to make GetDefaultPlayerLoop to contain all the hooks we need always. |
||
| { | ||
| if (!UnityEditor.EditorApplication.isPlaying) | ||
| return; | ||
| EnsurePlayerLoopInjection(); | ||
| } | ||
| #endif | ||
|
|
||
| public Action<int, string> onDeviceDiscovered | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this only affecting the editor hook? We do a number of player loop hooks currently here: https://github.cds.internal.unity3d.com/unity/unity/blob/trunk/Modules/Input/Private/InputModuleRegistration.cpp#L26-L39
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discussed this offline, but adding here just for context.
GetDefaultPlayerLoop only contains systems registered in Native, the problem is that we have one(?) system today that is registered in managed, so if users SetPlayerLoop using GetDefaultPlayerLoop, those managed registered systems gets removed.
If we were to somehow move all (one? InputSystemPlayerLoopRunnerInitializationSystem) systems registrations to native it would solve the problem.