Skip to content

Conversation

@andriyDev
Copy link
Contributor

Objective

  • Allow processors to produce multiple files instead of just one.

Solution

  • Instead of passing a &mut Writer to Process, instead we pass a WriterContext. Users then just call write_single or write_multiple to decide whether they want a single file or multiple files. Once users are done writing, they call SingleWriter::finish or MultipleWriter::finish.
  • I also added a new asset action - Decomposed. This makes it more obvious to callers that they are querying for the wrong asset.

This means if you have an asset path like path/to/my_asset.json, and the Process produces files "part1.thing", "part2.thing", and "dir/mesh.gltf", these assets will be accessible at path/to/my_asset.json/part1.thing, path/to/my_asset.json/part2.thing, and path/to/my_asset.json/dir/mesh.gltf. In essence, the asset in the unprocessed directory becomes a folder in the processed directory.

Testing

  • The asset_processing example works and has been updated to include a "multiple" processor.
  • Updated unit tests to account for "multiple" files.
  • Created new tests too.

@andriyDev andriyDev added C-Feature A new feature, making something new possible A-Assets Load files from disk to use for things like images, models, and sounds M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Nov 20, 2025
@andriyDev andriyDev added this to the 0.18 milestone Nov 20, 2025
@andriyDev andriyDev requested a review from JMS55 November 20, 2025 03:06
@andriyDev andriyDev force-pushed the one-to-many branch 3 times, most recently from 08313c5 to 77091f1 Compare November 20, 2025 03:51
Copy link
Contributor

@JMS55 JMS55 left a comment

Choose a reason for hiding this comment

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

Very exciting! Left some feedback

  • I really don't like the single vs multi naming. I think we need better naming.
  • As someone not very familiar with the asset system, I don't quite get how the multi files get mapped to asset server concepts. Sub assets? Or?
  • Do we support asset savers besides filesystems? How does this work with those?

}
for (i, line) in bytes.lines().map(Result::unwrap).enumerate() {
let mut writer = writer_context
.write_multiple(Path::new(&format!("Line{i}.line")))
Copy link
Contributor

Choose a reason for hiding this comment

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

write_multiple() in the loop, for writing a single item, feels kind of weird to me. Can we call it something else?

write_part, write_subasset, write_new_asset, ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair point! I tried renaming write_single and write_multiple to write_full and write_partial respectively. The naming here really sucks. We shouldn't use subasset because that's already taken and it has a completely different meaning here.

I want to make sure that the "full" vs "partial" are like antonyms (like single vs multiple). Just to give the intuition that these can't be mixed.

}
```

Then if you have an asset like `shakespeare.txt`, you can load these separate files as
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we show some code for that?

How are these represented in terms of bevy's asset system? Are they subassets? I don't remember how things like meshes within gltf files are represented and how to load subparts of it and the different asset_server.load() syntax, and how that all maps together.

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 gave one example.

They are effectively completely separate assets. The Process trait has no idea about subassets. Writing "multiple"/"partial" files just writes a new file that you can point the asset server at.

from scratch within the asset processor!

We plan to use this to break apart large glTF files into smaller, easier-to-load pieces -
particularly for producing meshlets.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
particularly for producing meshlets.
particularly for producing virtual geometry meshes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Process {
processor: String,
settings: ProcessSettings,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

This is essentially saying "once an asset is processed into multiple new assets, you can't load the original asset anymore"? Can we state it more like that if so?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a much more obvious way to put it! I slightly reworded it, but overall the same.


/// Start writing a single output file, which can be loaded with the `load_settings`.
///
/// Returns an error if you have previously started writing.
Copy link
Contributor

Choose a reason for hiding this comment

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

started writing <with either of the two apis>

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 actually changed this to specifically mention "partial" writes - since it's now impossible to call single twice (since it takes an owned self).

Copy link
Contributor Author

@andriyDev andriyDev left a comment

Choose a reason for hiding this comment

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

To respond to your questions:

  • Good point! Discussed in a comment.
  • Multi-files are just treated as individual assets. There is no requirement that subassets map to some multi-file. If the process wants to throw out the bytes of the original asset and write 100 copies of the Bee Movie script, that's totally up to it!
  • The Process trait has no inherent relationship to AssetSaver, both before and after this PR. The AssetSaver is used by the particular Process implementation LoadTransformAndSave, which makes it slightly more convenient to write Process implementations. We could figure out a similar convenience for one-to-many, but I think we should leave that for another PR, since that seems a little tricky to get right, and I don't think we need it for a glTF processor.

Process {
processor: String,
settings: ProcessSettings,
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a much more obvious way to put it! I slightly reworded it, but overall the same.

}
```

Then if you have an asset like `shakespeare.txt`, you can load these separate files as
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 gave one example.

They are effectively completely separate assets. The Process trait has no idea about subassets. Writing "multiple"/"partial" files just writes a new file that you can point the asset server at.


/// Start writing a single output file, which can be loaded with the `load_settings`.
///
/// Returns an error if you have previously started writing.
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 actually changed this to specifically mention "partial" writes - since it's now impossible to call single twice (since it takes an owned self).

from scratch within the asset processor!

We plan to use this to break apart large glTF files into smaller, easier-to-load pieces -
particularly for producing meshlets.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

}
for (i, line) in bytes.lines().map(Result::unwrap).enumerate() {
let mut writer = writer_context
.write_multiple(Path::new(&format!("Line{i}.line")))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair point! I tried renaming write_single and write_multiple to write_full and write_partial respectively. The naming here really sucks. We shouldn't use subasset because that's already taken and it has a completely different meaning here.

I want to make sure that the "full" vs "partial" are like antonyms (like single vs multiple). Just to give the intuition that these can't be mixed.

@andriyDev andriyDev requested a review from JMS55 November 21, 2025 04:00
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-Feature A new feature, making something new possible D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants