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

Make Classes cheap to clone #3021

Merged
merged 8 commits into from
Dec 10, 2022

Conversation

cecton
Copy link
Member

@cecton cecton commented Dec 6, 2022

Description

The goal of this PR is to make Classes cheap to clone. My motivation is to
improve the use of Classes in UI toolkits like
yewprint. In Yewprint, the user can pass
classes to a component like this:

use yewprint::Button;

// ...

<Button class={classes!("boo!")}>{"Click here to get scared"}</Button>

The next thing Yew will do is to render the actual button to a child component
as you can see here:

        <button
            class={classes!(
                "bp3-button",
                props.fill.then_some("bp3-fill"),
                props.minimal.then_some("bp3-minimal"),
                props.small.then_some("bp3-small"),
                props.outlined.then_some("bp3-outlined"),
                props.loading.then_some("bp3-loading"),
                props.large.then_some("bp3-large"),
                (props.active && !props.disabled).then_some("bp3-active"),
                props.disabled.then_some("bp3-disabled"),
                props.intent,
                props.class.clone(),             // <----- here
            )}
            style={props.style.clone()}
            onclick={(!props.disabled).then_some(props.onclick.clone())}
        >

This is inefficient at the moment because all the class values (can be
Cow::Owned) are cloned unnecessarily.

To make it more efficient, I'm proposing 2 major changes to the Classes
object:

  1. use AttrValue instead of Cow in Classes

    Cow is cheap to clone only when the value is a &'static str. Using
    AttrValue here (aka IString) will allow owned string to be cloned
    efficiently, thanks to the use of AttrValue::Rc.

  2. wrap indexset into Rc

    In most cases, classes will be added to the user classes before being
    passed to the child component. But there are cases where classes are passed
    as is to the child component. In that case, it is unnecessary to clone the
    internal IndexSet itself.

Checklist

  • I have reviewed my own code
  • I have added tests [edit: I have not added test because the API does not
    change at all and the current tests are already covering all the use cases.]
  • Benchmark proves these changes are reasonable.

github-actions[bot]
github-actions bot previously approved these changes Dec 6, 2022
Comment on lines 36 to 40
// NOTE: this can be improved once Iterator::intersperse() becomes stable
for class in rest {
s.push_str(" ");
s.push_str(class.as_str());
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I had to change this because class.as_str() borrows on class and the compiler refused to yield the borrow value 😞 not a big deal

@@ -60,7 +67,7 @@ impl Classes {
if self.is_empty() {
*self = classes_to_add
} else {
self.set.extend(classes_to_add.set)
Rc::make_mut(&mut self.set).extend(classes_to_add.set.iter().cloned())
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 use of Rc::make_mut() is important here as it will avoid cloning the set if it's not necessary

@github-actions
Copy link

github-actions bot commented Dec 6, 2022

Visit the preview URL for this PR (updated for commit 4275574):

https://yew-rs-api--pr3021-cecton-make-classes-1inatn7g.web.app

(expires Wed, 14 Dec 2022 00:50:47 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Comment on lines 104 to 112
fn into_prop_value(self) -> AttrValue {
let mut classes = self.set.iter();
let mut classes = self.set.iter().cloned();

match classes.next() {
None => AttrValue::Static(""),
Some(class) if classes.len() == 0 => match *class {
Cow::Borrowed(class) => AttrValue::Static(class),
Cow::Owned(ref class) => AttrValue::Rc(Rc::from(class.as_str())),
},
Some(first) => AttrValue::Rc(Rc::from(build_string(first, classes.map(Cow::borrow)))),
Some(class) if classes.len() == 0 => class,
Some(first) => build_attr_value(first, classes),
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Some of the code has become simpler thanks to the cloning

Comment on lines 152 to 157
fn into_iter(self) -> Self::IntoIter {
self.set.into_iter()
// NOTE: replace this by Rc::unwrap_or_clone() when it becomes stable
Rc::try_unwrap(self.set)
.unwrap_or_else(|rc| (*rc).clone())
.into_iter()
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Shamelessly copy-pasted from std. I will take no question

@github-actions
Copy link

github-actions bot commented Dec 7, 2022

Benchmark - SSR

Yew Master

Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 335.865 338.347 336.997 0.842
Hello World 10 625.380 629.829 626.879 1.530
Function Router 10 2183.713 2209.820 2193.753 7.421
Concurrent Task 10 1008.172 1010.190 1009.380 0.720

Pull Request

Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 335.709 337.033 336.359 0.473
Hello World 10 637.694 639.343 638.025 0.515
Function Router 10 2269.200 2279.552 2274.481 2.804
Concurrent Task 10 1007.830 1010.056 1009.510 0.635

@github-actions
Copy link

github-actions bot commented Dec 7, 2022

Size Comparison

examples master (KB) pull request (KB) diff (KB) diff (%)
async_clock 108.294 108.294 0 0.000%
boids 172.371 172.365 -0.006 -0.003%
communication_child_to_parent 92.446 92.446 0 0.000%
communication_grandchild_with_grandparent 106.919 106.918 -0.001 -0.001%
communication_grandparent_to_grandchild 102.770 102.780 +0.011 +0.010%
communication_parent_to_child 89.534 89.536 +0.002 +0.002%
contexts 109.482 109.484 +0.002 +0.002%
counter 87.450 87.450 0 0.000%
counter_functional 87.865 87.864 -0.001 -0.001%
dyn_create_destroy_apps 90.300 90.302 +0.002 +0.002%
file_upload 101.769 101.769 0 0.000%
function_memory_game 165.375 166.646 +1.271 +0.769%
function_router 350.312 350.631 +0.318 +0.091%
function_todomvc 160.078 161.571 +1.493 +0.933%
futures 224.488 226.598 +2.109 +0.940%
game_of_life 107.059 108.182 +1.123 +1.049%
immutable 184.155 184.154 -0.001 -0.001%
inner_html 83.870 83.869 -0.001 -0.001%
js_callback 113.158 113.153 -0.005 -0.004%
keyed_list 197.892 197.891 -0.001 -0.000%
mount_point 87.017 87.016 -0.001 -0.001%
nested_list 114.014 115.058 +1.044 +0.916%
node_refs 94.960 94.959 -0.001 -0.001%
password_strength 1553.435 1553.434 -0.001 -0.000%
portals 98.232 98.231 -0.001 -0.001%
router 320.461 320.604 +0.143 +0.044%
simple_ssr 153.133 153.133 0 0.000%
ssr_router 394.648 394.974 +0.325 +0.082%
suspense 110.766 110.753 -0.013 -0.011%
timer 90.319 90.317 -0.002 -0.002%
todomvc 141.357 142.812 +1.454 +1.029%
two_apps 88.073 88.073 0 0.000%
web_worker_fib 154.445 154.444 -0.001 -0.001%
webgl 86.569 86.569 0 0.000%

⚠️ The following examples have changed their size significantly:

examples master (KB) pull request (KB) diff (KB) diff (%)
game_of_life 107.059 108.182 +1.123 +1.049%
todomvc 141.357 142.812 +1.454 +1.029%

github-actions[bot]
github-actions bot previously approved these changes Dec 7, 2022
github-actions[bot]
github-actions bot previously approved these changes Dec 7, 2022
github-actions[bot]
github-actions bot previously approved these changes Dec 7, 2022
Copy link
Member

@ranile ranile left a comment

Choose a reason for hiding this comment

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

Looks good to me

@ranile ranile added the A-yew Area: The main yew crate label Dec 8, 2022
@ranile ranile merged commit 772763c into yewstack:master Dec 10, 2022
@cecton cecton deleted the cecton-make-classes-cheap-to-clone branch December 11, 2022 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-yew Area: The main yew crate breaking change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants