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

More idiomatic texture atlas builder #13238

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

s-puig
Copy link
Contributor

@s-puig s-puig commented May 4, 2024

Objective

  • TextureAtlasBuilder has some non-idiomatic methods.

Solution

  • Refactor non-idiomatic methods

Changelog

  • Renamed TextureAtlasBuilder.finish() to build()
  • TextureAtlasBuilder.with_texture() now consumes self and returns Self

Migration Guide

- texture_atlas_builder.add_texture(Some(id), texture);
+ texture_atlas_builder = texture_atlas_builder.add_texture(Some(id), texture);

- let (texture_atlas_layout, texture) = texture_atlas_builder.finish().unwrap();
+ let (texture_atlas_layout, texture) = texture_atlas_builder.build().unwrap();

@pablo-lua pablo-lua added A-Rendering Drawing game state to the screen C-Code-Quality A section of code that is hard to understand or change C-Breaking-Change A breaking change to Bevy's public API that needs to be noted in a migration guide labels May 4, 2024
crates/bevy_sprite/src/texture_atlas_builder.rs Outdated Show resolved Hide resolved
@@ -184,7 +185,7 @@ impl<'a> TextureAtlasBuilder<'a> {
///
/// If there is not enough space in the atlas texture, an error will
/// be returned. It is then recommended to make a larger sprite sheet.
pub fn finish(self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> {
pub fn build(self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@matiqo15 matiqo15 added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label May 5, 2024
@@ -86,8 +86,9 @@ impl<'a> TextureAtlasBuilder<'a> {
///
/// Optionally an asset id can be passed that can later be used with the texture layout to retrieve the index of this texture.
/// The insertion order will reflect the index of the added texture in the finished texture atlas.
pub fn add_texture(&mut self, image_id: Option<AssetId<Image>>, texture: &'a Image) {
pub fn add_texture(mut self, image_id: Option<AssetId<Image>>, texture: &'a Image) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

To be idiomatic and not awkward in cases like these, this should return &mut Self:

pub fn add_texture(&mut self, image_id: Option<AssetId<Image>>, texture: &'a Image) -> &mut Self

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'm not so sure about that. It breaks the usual builder pattern (incidentally, this is why i noticed in the first place) i.e.

texture_atlas_builder = texture_atlas_builder.add_texture(Some(id), texture);

Copy link
Contributor

Choose a reason for hiding this comment

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

It is often more ergonomic (and more efficient) to take and return the builder as a mutable reference. The borrow checker makes this work naturally. This approach has the advantage that one can write code like

let mut fb = FooBuilder::new();
fb.a();
fb.b();
let f = fb.build();

as well as the FooBuilder::new().a().b().build() style.

From https://rust-unofficial.github.io/patterns/patterns/creational/builder.html#discussion

Imo, the only way that the builder = builder.method(...); pattern makes sense is if the method were named something less imperative like with_texture(...), but as @IceSentry mentioned, that style of use just makes things easier to mess up with no gain.

Is there a practical case this PR is attempting to address / improve?

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 a practical case this PR is attempting to address / improve?

I noticed when i was working on a project and wanted to quickly make an atlas like so:

let atlas = TextureAtlasBuilder::default().padding(padding.unwrap_or_default()).add_texture(None, &image).add_texture(None, &image2).finish();

Above doesn't work because add_texture returns ().

From https://rust-unofficial.github.io/patterns/patterns/creational/builder.html#discussion [..]

Mutable references builders are great if they are usually one-liners. They require every method to take/return a mutable reference or they become awkward to chain. The downside is that you now have to instantiate the owned type separately or the reference will outlive it:

// Allowed
let mut fb = FooBuilder::new();
fb.a();
fb.build();
// Compiler error
let mut fb = FooBuilder::new().a();
fb.build();

Anyways, i only care about it being consistent, so whatever you guys decide.

@@ -219,10 +219,10 @@ fn create_texture_atlas(
continue;
};

texture_atlas_builder.add_texture(Some(id), texture);
texture_atlas_builder = texture_atlas_builder.add_texture(Some(id), texture);
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't seem like a win and just makes it much easier to mess up if you forget to do the assignment and we can't really warn users easily if they were using the previous code so it will break and be hard to debug.

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 there's anything wrong with using mutation 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.

I think it is hard to mess up. It's a warn by default and they already have to use build() which will make it a compiler error that properly explains it:

error[E0382]: use of moved value: `texture_atlas_builder`
   --> examples/2d/texture_atlas.rs:225:43
    |
210 |     let mut texture_atlas_builder =
    |         ------------------------- move occurs because `texture_atlas_builder` has type `bevy::prelude::TextureAtlasBuilder<'_>`, which does not implement the `Copy` trait
211 |         TextureAtlasBuilder::default().padding(padding.unwrap_or_default());
212 |     for handle in folder.handles.iter() {
    |     ----------------------------------- inside of this loop
...
222 |         texture_atlas_builder.add_texture(Some(id), texture);
    |                               ------------------------------ `texture_atlas_builder` moved due to this method call, in previous iteration of loop
...
225 |     let (texture_atlas_layout, texture) = texture_atlas_builder.build().unwrap();
    |                                           ^^^^^^^^^^^^^^^^^^^^^ value used here after move
    |
note: `bevy::prelude::TextureAtlasBuilder::<'a>::add_texture` takes ownership of the receiver `self`, which moves `texture_atlas_builder`
   --> /home/foo/Programming/bevy/crates/bevy_sprite/src/texture_atlas_builder.rs:89:28
    |
89  |     pub fn add_texture(mut self, image_id: Option<AssetId<Image>>, texture: &'a Image) -> Self {

I don't mind changing it back though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably better to have this method take a mutable reference and return a mutable reference, this is a Pattern quite used for builders IIRC

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it labels May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Breaking-Change A breaking change to Bevy's public API that needs to be noted in a migration guide C-Code-Quality A section of code that is hard to understand or change S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants