Skip to content
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

Sync initial input state of PassThroughInputManager from parent #6398

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

frenzibyte
Copy link
Member

The double-schedule is ugly, but mandatory to ensure the syncing process does not apply until the entire drawable hierarchy contained by the input manager is loaded / ready to handle input via event handlers.

While it's somewhat obvious, note that scheduling twice inside a LoadComplete means to execute in the next update frame rather than the current one, since LoadComplete comes before updating the scheduler in the current update frame (see this).

@bdach
Copy link
Collaborator

bdach commented Oct 25, 2024

I don't have the mental willpower to face this head on right now but this is like the fifth change touching this class in a super specific manner and when I read "double schedule" I just recoil.

There has to be a better way to do this. I refuse to accept that this is the best that can be done.

@smoogipoo smoogipoo self-requested a review October 25, 2024 05:51
@frenzibyte
Copy link
Member Author

Really it's just being adjusted inline with user expectations. I'm pretty sure I said this before, but this class's first-and-foremost usage is gameplay, it's barely used anywhere else.

...and when I read "double schedule" I just recoil.

What do you generally expect when you use schedule basically anywhere? It schedules to next frame, right? This is not the case in LoadComplete, it'll still execute within the same frame. I have to schedule twice.

There has to be a better way to do this. I refuse to accept that this is the best that can be done.

If this refers to touching the class in general, you can't really expect me to define any other place to store the content of syncInputState anywhere other than in here, it would look out of place and disconnected from its partial-counterpart syncReleasedInputs. If this refers to the double-schedule, I can't imagine anything working without it. It can't become a single schedule because of the misfortune of me having to use it in LoadComplete.

Unless you want me to look at the entire issue from a very different perspective where I can make this somehow work without doing this "initial input state sync" logic, at which point I don't know.

@bdach
Copy link
Collaborator

bdach commented Oct 25, 2024

I'm pretty sure I said this before, but this class's first-and-foremost usage is gameplay, it's barely used anywhere else.

Shouldn't be in framework then should it? Why are we justifying framework changes by specific game usage scenarios? Bit backwards isn't it?

I dunno. Rather than making the hyperspecific scenarios work the framework component should provide usages with the tools to implement desired behaviours themselves, if possible.

@smoogipoo
Copy link
Contributor

smoogipoo commented Oct 25, 2024

Is there a reason we haven't just made KeyBindingContainer into some sort of InputManager? It sounds like we're adding a bunch of workarounds for it essentially being one in all but name.

Or expose some sort of InputManager.Resync() and call this.GetContainingInputManager().Resync() in KBC?

@frenzibyte
Copy link
Member Author

Signaling a parent input manager to resync input when a key binding container is loaded feels very backwards to me. I'm personally quite anxious from attempting to KeyBindingContainer : InputManager as I'm sure it'll be a refactor too much for me to handle.

Going by what @bdach stated above, I can go for exposing a general Sync method and move the call to RulesetInputManager (still requiring the double-schedule but at a very local level now). Something like:

diff --git a/osu.Framework/Input/PassThroughInputManager.cs b/osu.Framework/Input/PassThroughInputManager.cs
index 3112b4045..e2cbb6f1c 100644
--- a/osu.Framework/Input/PassThroughInputManager.cs
+++ b/osu.Framework/Input/PassThroughInputManager.cs
@@ -42,10 +42,7 @@ public virtual bool UseParentInput
                 useParentInput = value;
 
                 if (UseParentInput)
-                {
-                    syncReleasedInputs();
-                    syncJoystickAxes();
-                }
+                    Sync(SyncBehaviour.ReleasedInput);
             }
         }
 
@@ -56,10 +53,6 @@ protected override void LoadComplete()
             base.LoadComplete();
 
             parentInputManager = GetContainingInputManager();
-
-            // allow a frame for children to be prepared before passing input from parent.
-            // this is especially necessary if our child is a KeyBindingContainer since the key bindings are not prepared until LoadComplete is called on it.
-            Schedule(() => Schedule(syncInitialState));
         }
 
         public override bool HandleHoverEvents => parentInputManager != null && UseParentInput ? parentInputManager.HandleHoverEvents : base.HandleHoverEvents;
@@ -184,16 +177,23 @@ protected override void Update()
 
             // There are scenarios wherein we cannot receive the release events of pressed inputs. For simplicity, sync every frame.
             if (UseParentInput)
-            {
-                syncReleasedInputs();
-                syncJoystickAxes();
-            }
+                Sync(SyncBehaviour.ReleasedInput);
         }
 
         /// <summary>
-        /// Syncs initial state of this input manager to the current state of the parent input manager.
+        /// Syncs the state of this input manager to the current state of the parent input manager, according to the given <paramref name="behaviour"/>.
         /// </summary>
-        private void syncInitialState()
+        protected void Sync(SyncBehaviour behaviour)
+        {
+            if (behaviour == SyncBehaviour.PressedInput)
+                syncPressedInputs();
+            else
+                syncReleasedInputs();
+
+            syncJoystickAxes();
+        }
+
+        private void syncPressedInputs()
         {
             if (parentInputManager == null)
                 return;
@@ -277,5 +277,11 @@ private void syncJoystickAxes()
                 }
             }
         }
+
+        protected enum SyncBehaviour
+        {
+            PressedInput,
+            ReleasedInput,
+        }
     }
 }
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index 31c7c34572..adec23b097 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -75,6 +75,15 @@ private void load(OsuConfigManager config)
             tapsDisabled = config.GetBindable<bool>(OsuSetting.TouchDisableGameplayTaps);
         }
 
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            // allow a frame for children to be prepared before passing input from parent.
+            // this is especially necessary if our child is a KeyBindingContainer since the key bindings are not prepared until LoadComplete is called on it.
+            Schedule(() => Schedule(() => Sync(SyncBehaviour.PressedInput)));
+        }
+
         #region Action mapping (for replays)
 
         public override void HandleInputStateChange(InputStateChangeEvent inputStateChange)

Does that sound more fathomable?

@smoogipoo
Copy link
Contributor

smoogipoo commented Oct 25, 2024

Can you explain how it's better to let user code arbitrarily handle this themselves with two schedules? This looks like a workaround - if another o!f user does the same thing what do we say "yeah just schedule your thing 2 frames into the future because reasons".

@bdach
Copy link
Collaborator

bdach commented Oct 25, 2024

Can you explain how it's better to let user code arbitrarily handle this themselves with two schedules?

If that question is directed at me then I agree it's not.

I can't give a counterproposal at this stage because I'd basically have to go revisit every single passthrough input manager change from the past year and rethink the entire thing from scratch. The diffs keep getting more and more cursed but issues still continue to pop up. From a high level view that does not bode well for this code as far as I'm concerned.

@smoogipoo
Copy link
Contributor

smoogipoo commented Oct 25, 2024

Was directed at frenzi's latest diff. At least the PR's code is contained within o!f, but with that diff it's slipped to being handled partially at osu!'s end.

Both are cursed, I absolutely agree with @bdach . For me, the part that really sucks is that the schedules are arbitrary done because children are loaded at some later point in time, so this PR will immediately break as soon as that becomes not the case. Oh, what's that -- a LoadComponentAsync()? Whoops! Shall we schedule another 10 frames in the future to potentially cover that case?

That's why I suggested Resync() that would be called from a child IM (or KBC as it would be in this case) when it's appropriate to do so. It inverts the topology.

@frenzibyte
Copy link
Member Author

It's not really some later point in time, and this is not a 2 frame schedule. If the child is asynchronously loaded then I don't think it's the input manager's concern to try and make sure input is synced after that time, neither do we have actual use cases for that?

Again, this is a single frame schedule, but because it's done in LoadComplete, the schedule has to be applied twice.

If everyone agrees to the idea of triggering a parent input manager resync from a child key binding container, then...sure, I guess.


// allow a frame for children to be prepared before passing input from parent.
// this is especially necessary if our child is a KeyBindingContainer since the key bindings are not prepared until LoadComplete is called on it.
Schedule(() => Schedule(syncInitialState));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand the part where you're saying the "second schedule is only required due to being called in LoadComplete" – can you elaborate on that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Issue with osu!catch not holding onto your dashes/movement keys upon restarting
4 participants