Skip to content

Replace all different load variants in AssetServer with a builder.#23663

Merged
alice-i-cecile merged 13 commits intobevyengine:mainfrom
andriyDev:load-builder
Apr 15, 2026
Merged

Replace all different load variants in AssetServer with a builder.#23663
alice-i-cecile merged 13 commits intobevyengine:mainfrom
andriyDev:load-builder

Conversation

@andriyDev
Copy link
Copy Markdown
Contributor

Objective

  • In 0.18, we had 10 different functions that load assets (I'm not even counting load_folder).
  • In 0.19, we've even added load_erased - but it unfortunately doesn't support all the features that the other variants support.
  • We apparently needed load_acquire_with_settings_override which 1) loads the asset, 2) uses the settings provided, 3) allows reading unapproved asset paths, and 4) drops a guard once the load completes.
    • That's fine if that's necessary. But we needed to create an explicit variant for that.
  • We need fewer load paths!

Solution

  • Create a builder.
  • Store all these options dynamically instead of statically handling each case.
  • Have the caller choose a particular "kind" of load when they are ready: load, load_erased, load_untyped, or load_untyped_async.
    • I intentionally didn't provide a load_async or load_erased_async, since those can be replicated using load/load_erased + AssetServer::wait_for_asset_id to get the exact same effect.

I am also intentionally leaving NestedLoader untouched in this PR, but a followup will duplicate this API for NestedLoader, which should make it easier to understand.

Unlike the NestedLoader API, we aren't doing any type-state craziness, so the docs are much more clear: users don't need to understand how type-state stuff works, they just call the handful of methods on the type. The "cost" here is we now need to be careful about including the cross product of loads between static asset type, runtime asset type, or dynamic asset type, crossed with deferred or async. In theory, if we added more kinds on either side, we would need to expand this cross product a lot. In practice though, it seems unlikely there will be any more variants there. (maybe there could be a blocking variant? I don't think this is a popular opinion though).

A big con here is some somewhat common calls are now more verbose. Specifically, asset_server.load_with_settings() has become asset_server.load_builder().with_settings().load(). I am not really concerned about this though, since it really isn't that painful.

Testing

  • Tests all pass!

Showcase

Now instead of:

asset_server.load_acquire_with_settings_override("some_path", |settings: &mut GltfLoaderSettings| { ... }, my_lock_guard);

You can instead do:

asset_server.load_builder()
    .with_guard(my_lock_guard)
    .with_settings(|settings: &mut GltfLoaderSettings| { ... })
    .override_unapproved()
    .load("some_path");

We also now cover more variants! For example, you can now load an asset untyped with a guard, or with override_unapproved, etc.

@andriyDev andriyDev added A-Assets Load files from disk to use for things like images, models, and sounds C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 4, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Assets Apr 4, 2026
Copy link
Copy Markdown
Contributor

@greeble-dev greeble-dev 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, just got some nitpicky comments.

Also thinking that load_builder could be bikeshedded as it's a teeny bit verbose? LoadContext uses loader. Maybe build is an option? I suspect both of these end up too ambiguous though.

@@ -0,0 +1,21 @@
---
title: Advanced AssetServer load variants are now expose through a builder pattern.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
title: Advanced AssetServer load variants are now expose through a builder pattern.
title: Advanced AssetServer load variants are now exposed through a builder pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines +20 to +21
Every load variant above can be reimplemented using `load_builder`, and each one of these methods
has deprecation messages on them explaining their new equivalent.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
Every load variant above can be reimplemented using `load_builder`, and each one of these methods
has deprecation messages on them explaining their new equivalent.
Every load variant above can be reimplemented using `load_builder`, and each one of these methods
has deprecation messages on them explaining their new equivalent.
For example, `load_with_settings_override` can be replaced by:
```rust
asset_server
.load_builder()
.with_settings(settings)
.override_unapproved()
.load(path)

I thought one example here might be nice so the user gets the flavor of things.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Nice!

Comment thread crates/bevy_asset/src/server/mod.rs
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
pub fn load<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
self.load_with_meta_transform(path, None, (), false)
self.load_builder().load(path.into())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
self.load_builder().load(path.into())
self.load_builder().load(path)

I don't think the into is necessary? Unless there's an optimization reason?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're correct it's not necessary, but I believe it should reduce the number of monomorphizations, since it will always call load where the Into is just AssetPath.

Comment on lines +387 to 388
#[deprecated(note = "Use `asset_server.load_builder().load_erased(type_id, path)` instead")]
pub fn load_erased<'a>(
Copy link
Copy Markdown
Contributor

@greeble-dev greeble-dev Apr 5, 2026

Choose a reason for hiding this comment

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

Maybe load_erased and load_untyped should be kept? They're convenient and the duplication is modest.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Seeing as how load_erased was added this cycle, I don't think it's important. As for load_untyped, this also has a mega ugly API so I don't really want to make it convenient to use - load should be the front-door and all other loads are considered "advanced" IMO.

Comment thread crates/bevy_asset/src/server/mod.rs
@alice-i-cecile alice-i-cecile added X-Contentious There are nontrivial implications that should be thought through S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 6, 2026
@andriyDev andriyDev requested a review from greeble-dev April 15, 2026 06:11
@andriyDev andriyDev added S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Apr 15, 2026
Comment thread _release-content/migration-guides/load_builder.md Outdated
Copy link
Copy Markdown
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.

Thoughts:

  • wow, there are very much too many asset loading APIs
  • I share @cart's distaste for builders
  • this is a large improvement over the status quo
  • this is well-executed! good docs, sensible API, clean migration story

I think we should fix this, and do so in time for 0.19, but instead of a builder pattern we should keep it simple:

  • load(), which takes no settings at all
  • load_with, which takes an LoadOptions struct. This impls Default for nice partial initialization, and wraps all over the other config that you might have.

load_with_settings is the tricky bit here, because it's generic, but I think we should be able to pass in a generic which defaults to () without too big of a hit.

Copy link
Copy Markdown
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.

I've been convinced that my proposal is unworkable due to differing return types.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Apr 15, 2026
@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 15, 2026
@alice-i-cecile alice-i-cecile removed this pull request from the merge queue due to a manual request Apr 15, 2026
@andriyDev
Copy link
Copy Markdown
Contributor Author

andriyDev commented Apr 15, 2026

For any code-archaeologists, here was the discussion: https://discord.com/channels/691052431525675048/749332104487108618/1494030066940903434

TL;DR a descriptor style API would still leave a bunch of extra variants in the AssetServer to support the different return types of the load functions, which is what we're trying to avoid!

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Apr 15, 2026
Merged via the queue into bevyengine:main with commit 61127f6 Apr 15, 2026
38 checks passed
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in Assets Apr 15, 2026
@andriyDev andriyDev deleted the load-builder branch April 16, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Assets Load files from disk to use for things like images, models, and sounds C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Contentious There are nontrivial implications that should be thought through

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants