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

refactor and test ui_focus_system #4264

Closed
wants to merge 6 commits into from

Conversation

pubrrr
Copy link
Contributor

@pubrrr pubrrr commented Mar 20, 2022

Objective

While digging through the bevy code, I found that ui_focus_system overly complex and quite hard to understand. -> make it easier to understand.

Solution

  • write unit tests
  • refactor
  • I tried to introduce no logical changes

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Mar 20, 2022
@pubrrr
Copy link
Contributor Author

pubrrr commented Mar 20, 2022

Ok, apparently the ci fails:

 = rustc_version v0.2.3
  └── (build) stdweb v0.4.20
      └── gilrs-core v0.3.2
 = rustc_version v0.4.0
  └── (build) rstest v0.12.0
      └── (dev) bevy_ui v0.7.0-dev

It seems this will depend on koute/stdweb#418

@alice-i-cecile alice-i-cecile added A-UI Graphical user interfaces, styles, layouts, and widgets C-Code-Quality A section of code that is hard to understand or change and removed S-Needs-Triage This issue needs to be labelled labels Mar 20, 2022
@alice-i-cecile alice-i-cecile self-requested a review March 20, 2022 17:59
@alice-i-cecile
Copy link
Member

For the CI, the workaround is to whitelist the duplicate dependencies. That said, I'm reluctant to pull a unit testing framework into our dependency tree just for this.

What benefit does it offer here?

*interaction = Interaction::None;
}
}
focus_ui(
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 think it's useful to have a system whose contents are a single function call: that seems like needless layering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to make it testable. Or do you have other suggestions how to mock Windows?

I.e. ui_focus_system is actually meant as a public export of focus_ui::<Windows>

Copy link
Member

Choose a reason for hiding this comment

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

Ah. I think we should try to tackle that directly: I don't want to have to replicate this strategy everywhere.

touches_input: Res<Touches>,
mut node_query: Query<NodeQuery>,
) {
reset_interactions(
Copy link
Member

Choose a reason for hiding this comment

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

I like this living in a separate system at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I extract it? This system currently does two things:

  • resetting Interactions to None
  • checking the input to set some Interactions to Hovered / Clicked

The coupling between those two things is quite hard as they rely on each other. This becomes quite obvious from the Local<State>. Actually I wanted to get rid of it, but I did not have a good idea without changing the logic.


let mouse_clicked =
mouse_button_input.just_pressed(MouseButton::Left) || touches_input.just_released(0);
let cursor_position = match windows.get_cursor_position() {
Copy link
Member

Choose a reason for hiding this comment

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

I think you should be able to use the ? operator here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The compiler says: the ? operator can only be used in a function that returns Result or Option (or another type that implements FromResidual)

Or do you mean I should return an Option here?

Copy link
Member

Choose a reason for hiding this comment

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

Ah my bad; I'd misread what the function does.

)
}

fn contains_cursor(
Copy link
Member

Choose a reason for hiding this comment

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

This feels like it should be a method on Node or something to that effect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also thought that this function looks a bit odd as a standalone. But is Node the right place as Node is actually indifferent to positions?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah 🤔 TBH, this feels like functionality that should be pub that we reuse internally, but I can't immediately figure out the best design for that.

*interaction = Interaction::None;
}

trait CursorResource: Resource {
Copy link
Member

Choose a reason for hiding this comment

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

I really dislike the use of the generic plus trait here.

Copy link
Member

Choose a reason for hiding this comment

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

Ah I see: it's to work around the difficulty mocking Windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See my other comment - how else would I make Windows do what I want in a unit test? Everything I tried ended up with a dependency on hardware.


#[cfg(test)]
mod tests {
use rstest::*;
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 think that this adds enough value to pull in a testing library on a one-off basis. These tests should be feasibel to rewrite with a helper method instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I kinda handcoded it myself now

Copy link
Member

Choose a reason for hiding this comment

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

Excellent, thanks!

}

#[cfg(test)]
mod tests {
Copy link
Member

Choose a reason for hiding this comment

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

These tests look good, and we definitely need tests for this piece of code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there actually a common practice how to test systems? This TestApp approach is something I came up with in one of my own projects from the Cheatbook suggesting Direct World Access. But I didn't see many tests around in bevy itself.

Copy link
Member

Choose a reason for hiding this comment

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

You can see how I did it in one of my external crates here: https://github.com/Leafwing-Studios/leafwing_input_manager/blob/main/tests/integration.rs

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

Overall, I think that this region could use code quality improvements to break up the mega-function, and it definitely needs tests!

I pretty strongly dislike rstest here (I don't think it's clearer, and it pulls in a new dependency). The refactor for the code itself needs a bit more iteration, but it feels like breaking this out into smaller pieces is the right path.

@alice-i-cecile
Copy link
Member

So, WRT the testing strategy: #3835 is the blocker here. IMO we should hold off on this until that's merged, then use the headless windows there to enable us to write proper tests for this code.

@pubrrr pubrrr force-pushed the refactorFocus2 branch 2 times, most recently from 54d4d59 to 99a737e Compare March 20, 2022 21:47
}
*interaction = Interaction::Clicked;
Copy link
Contributor Author

@pubrrr pubrrr Mar 20, 2022

Choose a reason for hiding this comment

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

  • I might need to be careful here concerning change detection. Double check.

@nicopap
Copy link
Contributor

nicopap commented Mar 21, 2022

You might be interested in bevyengine/rfcs#41 and https://github.com/nicopap/ui-navigation. I'm planning to start the bevy PR next week (I still need to update the RFC with the second round of changes). I'm not sold on rtest either, to be perfectly honest, the test code was completely obtuse to me 😀. Looks great now though!

The ui-navigation crate is sorely lacking when it comes to tests. And unlike bevy's focus system, it's fairly well documented.

@pubrrr
Copy link
Contributor Author

pubrrr commented Mar 23, 2022

I guess after adding a test that checks change detection, unfortunately most of my changes won't work.

At least there are a couple more tests. The "Option<Interaction> -> Interaction" change can be kept, and maybe a refactoring of cutting it into smaller functions. But the approach "set everything to None, then check the state" won't work. Or does anyone have an idea how to make it work?

Anyway, I'll leave this PR around. Ping me once headless windows are there. I believe the tests can easily be adapted to work without the extra trait then.

@NthTensor
Copy link
Contributor

This looks like it has gone stale, and we are now planning to deprecate Interaction anyway. Closing as not planned.

@NthTensor NthTensor closed this Sep 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Code-Quality A section of code that is hard to understand or change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants