这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@jkelleyrtp
Copy link
Member

@jkelleyrtp jkelleyrtp commented Jun 30, 2022

Objective

This PR removes the ability for nodes to have multiple parents. Fixes #191

This simplifies some of the codebase, but mostly is intended to bring Taffy closer to spec for HTML.

It also removes some of the bespoke swap_remove code which seemed difficult to maintain.

The next logical step is to use hecs instead of slotmap, but I found that integrating hecs was becoming a lot of work given the code is designed mostly around a struct of arrays. Slotmap seemed to fit in better.

@alice-i-cecile alice-i-cecile added bug Something isn't working code quality Make the code cleaner or prettier. performance Layout go brr and removed bug Something isn't working labels Jul 1, 2022
@alice-i-cecile
Copy link
Collaborator

Definitely curious to see before / after benchmarks for these changes :)

@jkelleyrtp
Copy link
Member Author

Here's the results:

  • Deep Layout Build: -60.944%
  • Deep Layout Single: -13.944%
  • Deep Layout Relayout: +2.0090%
  • Generated Benchmarks: -30.319%

It looks like overall a big win for building trees, but a negligible win for relayouts. Which, is what you'd expect since there was already an internal arena system. The connection between user code and internal code will be faster, at the very least.

Old:

deep hierarchy - build  time:   [1.8723 us 1.8736 us 1.8751 us]                                    
                        change: [-1.3236% -0.9099% -0.5534%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 11 outliers among 100 measurements (11.00%)
  6 (6.00%) high mild
  5 (5.00%) high severe

deep hierarchy - single time:   [8.2531 us 8.2627 us 8.2743 us]                                     
                        change: [-0.6573% -0.3675% -0.0925%] (p = 0.01 < 0.05)
                        Change within noise threshold.
Found 13 outliers among 100 measurements (13.00%)
  1 (1.00%) low mild
  6 (6.00%) high mild
  6 (6.00%) high severe

deep hierarchy - relayout                                                                             
                        time:   [4.2865 us 4.2923 us 4.2985 us]
                        change: [-0.7002% -0.4260% -0.1599%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 6 outliers among 100 measurements (6.00%)
  2 (2.00%) high mild
  4 (4.00%) high severe

generated benchmarks    
                        time:   [304.68 us 305.43 us 306.18 us]                                 
                        change: [+0.0310% +0.4128% +0.7712%] (p = 0.03 < 0.05)
                        Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high mild

New:

deep hierarchy - build  time:   [730.92 ns 732.54 ns 734.13 ns]                                    
                        change: [-61.059% -60.944% -60.815%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  6 (6.00%) high mild

deep hierarchy - single time:   [7.1005 us 7.1179 us 7.1371 us]                                     
                        change: [-14.192% -13.944% -13.690%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 14 outliers among 100 measurements (14.00%)
  10 (10.00%) high mild
  4 (4.00%) high severe

deep hierarchy - relayout                                                                             
                        time:   [4.3750 us 4.3803 us 4.3858 us]
                        change: [+1.7462% +2.0090% +2.2782%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 9 outliers among 100 measurements (9.00%)
  1 (1.00%) high mild
  8 (8.00%) high severe

generated benchmarks    
                        time:   [212.28 us 212.66 us 213.05 us]                                 
                        change: [-30.531% -30.319% -30.090%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  3 (3.00%) high mild
  3 (3.00%) high severe

@jkelleyrtp
Copy link
Member Author

jkelleyrtp commented Jul 1, 2022

@alice-i-cecile Should we use stable keys or unstable keys for the tree? The current implementation uses stable, I believe. This means that a key for a node that was removed because of a parent removal will throw a hard error when used later. Unstable keys mean you could be accidentally manipulating new nodes using old keys if you weren't careful.

We could either adopt a generational arena, which I think slows down performance a tad, or just clearly document that Taffy uses a slab and that it is incorrect usage for outdated keys to be used once removed.


Also

We have methods that catch errors. However, a lot of the flexbox methods rely on panic-y indexing that don't trap errors.

Should we continue to trap errors on indexing operations, or just panic like we would if flexbox was relayouting?

@alice-i-cecile
Copy link
Collaborator

I would prefer to use stable keys for now as the conservative choice. Once that's in place we can experiment with swapping to unstable keys, and see if the benchmarks justify it.

WRT error handling, I would like to try and catch + propagate errors as aggressively as we can for now. This code base is a pain to debug and maintain, and moving away from panicking index operations will make fixing bugs much easier. Furthermore panics in the layout often means that an entire game or GUI is crashing, which is not an acceptable failure mode for a layouting library because of how inconsequential mistakes here are compared to things like app state.

@jkelleyrtp jkelleyrtp force-pushed the jk/single-parent-only branch from 3890f55 to 7eb30f5 Compare July 1, 2022 18:27
@jkelleyrtp jkelleyrtp changed the title Use slab instead of bespoke arena, limiting to one parent only Use slotmap instead of bespoke arena, limiting to one parent only Jul 1, 2022
@jkelleyrtp jkelleyrtp marked this pull request as ready for review July 1, 2022 19:21
@jkelleyrtp
Copy link
Member Author

I got a bit overzealous but then toned back my changes.

I've noticed a lot of methods that return results but don't actually create errors. Pretty much every public method except for two on our Taffy type return Results with no errors crafted.

Some methods should not be fallible. Creating a new leaf, for example.

We can figure out what to do about this later.

Personally, I prefer the approach where taffy panics since it'll be faster, but it'll obviously be less robust.

Perhaps we could have "try_" methods, though the primary method - compute_layout - will need a lot of work for it to actually produce real errors.

#[cfg(feature = "std")]
impl std::error::Error for InvalidNode {}
/// The error Taffy generates on invalid operations
pub type TaffyResult<T> = core::result::Result<T, TaffyError>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this type alias makes things clearer.

/// The error Taffy generates on invalid operations
pub type TaffyResult<T> = core::result::Result<T, TaffyError>;

/// An error that occurs while trying to access or modify a [`Node`]'s children by index.
Copy link
Collaborator

Choose a reason for hiding this comment

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

These docs need updating.

Copy link
Collaborator

@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.

Awesome! General thoughts:

  1. Can you add #[forbid(unsafe_code)] to the crate now that indexmap is gone?
  2. I'd like to see some benchmarks, but won't block on it. I don't care if this regresses performance given the removal of unsafe + code quality improvements, but it's good marketing.
  3. I like the use of Node much more than NodeId.
  4. Getting rid of all that code in Forest feels great.
  5. Only one parent is a nice correctness win.

@jkelleyrtp
Copy link
Member Author

Awesome! General thoughts:

  1. Can you add #[forbid(unsafe_code)] to the crate now that indexmap is gone?
  2. I'd like to see some benchmarks, but won't block on it. I don't care if this regresses performance given the removal of unsafe + code quality improvements, but it's good marketing.
  3. I like the use of Node much more than NodeId.
  4. Getting rid of all that code in Forest feels great.
  5. Only one parent is a nice correctness win.

Sure - I also need to fix some of the documentation surrounding "forests" as you've noted.

Per 2 - I posted above #196 (comment). Roughly a 30-90% win from average to best case. Worst cases hover around 0-15%. I tried out slab but went to slotmap and saw no performance difference, and Slotmap keys are the same sizes as slab keys. They also benefit from nonzero optimizations, so the Option<Node> for parents is the same size as before. Essentially we get stable keys for free.

@jkelleyrtp jkelleyrtp requested a review from alice-i-cecile July 1, 2022 19:42
Copy link
Collaborator

@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.

This is good to go!

@alice-i-cecile alice-i-cecile enabled auto-merge (squash) July 1, 2022 20:01
@alice-i-cecile alice-i-cecile added the breaking-change A change that breaks our public interface label Jul 1, 2022
/// Creates and adds a new unattached leaf node to the tree, and returns the [`NodeId`] of the new node
pub fn new_leaf(&mut self, layout: FlexboxLayout) -> TaffyResult<Node> {
let id = self.nodes.insert(NodeData::new(layout));
let _ = self.children.insert(new_vec_with_capacity(0));
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason for the dropped binding here?

Copy link
Member Author

Choose a reason for hiding this comment

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

The key for each slotmap ends up the same but .insert is labeled must_use so either we do a drop binding or we call drop manually.

I turned on clippy pedantic just to poke around and it suggested an explicit drop... in any case all of the keys are the same.

@alice-i-cecile alice-i-cecile disabled auto-merge July 1, 2022 20:14
@alice-i-cecile alice-i-cecile enabled auto-merge (squash) July 1, 2022 20:31
@alice-i-cecile alice-i-cecile merged commit b553bbd into main Jul 1, 2022
@alice-i-cecile alice-i-cecile deleted the jk/single-parent-only branch July 1, 2022 20:35
jkelleyrtp added a commit that referenced this pull request Oct 10, 2022
* feat: use slab instead of bespoke arena

* fix: all but one test

* chore: rip out forest and merge it into taffy

* fix: final tests

* polish: fix up some warnings

* chore: clean up forest and data fails

* chore: make clippy happier

* fix: compilation for no-std targets

* chore: clean up some more clippy stuff and renable more tests

* chore: remove outdated indexmap

* fix: update benches to not unwrap new elements

* fix: revert back to errors everywhere

* fix: revert complex bench too

* chore: remove unused import

* fix: removing parents

* chore: clean up and remove references to forest

* chore: remove final reference to forest

* chore: update release log

* Update error name in RELEASES

* Add single parent fix to RELEASES

* Remove extra whitespace

* chore: add warning to mark dirty function

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change A change that breaks our public interface code quality Make the code cleaner or prettier. performance Layout go brr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Request: remove the Node->NodeId abstraction Nodes can have multiple parents Consider migrating to a lightweight ECS framework

4 participants