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

Cleanup UI surface mappings - Follow up for #12698 #12719

Conversation

StrikeForceZero
Copy link
Contributor

@StrikeForceZero StrikeForceZero commented Mar 25, 2024

Draft PR: I still need to review some potential performance tradeoffs and whether bringing back the RootNodePair structure to collapse it even further is an option or not. See comment #12719 (comment)


Objective

Solution

  • By using a tuple as a key in the EntityHashMap<(Entity, Entity), taffy::node::Node>, we reduce the redundancy and complexity of the internal mappings.

…a_and_root_node_pair

This is to reduce redundancy and simplify the logic
Replaces the 'camera_and_root_node_pair' hashmap with
'camera_root_to_viewport_taffy'. Updating usages to directly reference
'implicit_viewport_node' and 'user_root_node', and removes the
'RootNodePair' struct.
self.taffy.remove(*node).unwrap();
}
}
self.camera_root_to_viewport_taffy.retain(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I originally wrote this earlier, I hadn't realized I was retaining inside a loop. I will need to circle back to one.

pub fn remove_entities(&mut self, entities: impl IntoIterator<Item = Entity>) {
for entity in entities {
self.camera_root_to_viewport_taffy.retain(
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 same goes for this one with the retain inside the loop.

// remove the root node from the previous implicit node's children
self.taffy
.remove_child(previous_parent, user_root_node)
.unwrap();
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 think that moving this outside the original unwrap_or_else means it eagerly removes the child from its previous parent and then immediately applies the new (or same one) right after, regardless of whether it needs to. It might be negligible, but still worth leaving a note.

camera_entity_to_taffy: EntityHashMap<EntityHashMap<taffy::node::Node>>,
camera_roots: EntityHashMap<Vec<RootNodePair>>,
/// `HashMap<(Camera Entity, Root (parentless) Entity), implicit Viewport>`
camera_root_to_viewport_taffy: EntityTupleHashMap<taffy::node::Node>,
Copy link
Contributor Author

@StrikeForceZero StrikeForceZero Mar 25, 2024

Choose a reason for hiding this comment

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

Few things here:

  • (Entity, Entity) as the key makes it harder to work with unless you iterate over the whole thing or already have both values. The benefit is that a viewport node can't be inserted without the camera entity and root node entity being in the map as the key.

  • We could take this a step further and remove entity_to_taffy and restore RootNodePair. But that does affect a few bit more LOC. And not to mention, would make lookups even less optimal.

Conclusion; I think these are interesting approaches, but it might be better to create a management struct that maintains it / while preserving the speed as before. (I see how I can change a few calls to make it more atomic) Gonna explore more here unless someone has a good suggestion that I'm not seeing!

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think EntityTupleHashMap<taffy::node::Node> makes sense as you want to be able to iterate over root nodes given just a camera node. I think either of:

  1. EntityHashMap<EntityHashMap<taffy::node::Node>>
  2. EntityHashMap<Vec<RootNodePair>>

ought to work fine on their own. They're essentially equivalent with slightly different performance characteristics. 2. is likely faster for iterating and lookups with small numbers of root nodes per camera. 1. is likely faster for lookups with large numbers of root nodes per camera. This is unlikely to be a performance hotspot so we probably ought not to worry about it too much.

A third option of EntityHashMap<taffy::node::Node> where the Node stored is the "user root node" and you lookup the "implicit viewport node" using Taffy.parent(user_root_node) would probably also work. Looking up a parent in Taffy should be fast as it's just a Vec lookup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After a massive 3 day deep dive through the layout system. I've boiled it down to this:

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UiNodeMeta {
    pub(super) camera_entity: Option<Entity>,
    pub(super) root_node_pair: RootNodePair,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RootNodePair {
    // The implicit "viewport" node created by Bevy
    pub(super) implicit_viewport_node: taffy::node::Node,
    // The root (parentless) node specified by the user
    pub(super) user_root_node: taffy::node::Node,
}

#[derive(Resource)]
pub struct UiSurface {
    pub(super) entity_to_taffy: EntityHashMap<taffy::node::Node>,
    // I'm not sure I like the name `meta` but can't think of anything else right now. 
    // maybe `relations`?
    pub(super) ui_root_node_meta: EntityHashMap<UiNodeMeta>,
    pub(super) camera_root_nodes: EntityHashMap<EntityHashSet>,
    pub(super) taffy: Taffy,
}

My goal in the next day or so is to break up the work I've been experimenting with and submit several incremental PR's with intentions to:

  • document several areas, including the ones I unknowingly stomped over
  • extract UiSurface into its own file since layout/mod is getting pretty big.
  • to clean some stuff up
  • implement the revised mapping structure
  • add more regression tests for edge cases I found affecting main

@ItsDoot ItsDoot added A-UI Graphical user interfaces, styles, layouts, and widgets C-Code-Quality A section of code that is hard to understand or change labels Mar 26, 2024
@StrikeForceZero
Copy link
Contributor Author

closing in favor of #12804

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.

3 participants