NiklasEi / bevy_common_assets

Collection of generic asset loaders for common file formats
Apache License 2.0
198 stars 24 forks source link

Asset type for arbitrary json/toml/etc documents #27

Open devnev opened 10 months ago

devnev commented 10 months ago

TL;DR

Could this repository include a common asset type and loader for loading generic serde_json::Value, toml_edit::Document, etc. types from files with the generic format extension like .json/.toml rather than a specific extension?

Background

I built something closely related to this for loading plugin settings from specific configuration files: https://github.com/devnev/bevy_settings_loader

But in my plugin, the settings are resource singletons and not identified by file extensions but by the full path to the file. So I add a generic .json / .toml loader and then deserialise from the loaded value into the destination type for the asset matching the settings path.

For that I've added generic JsonAsset and TomlAsset types that wrap serde_json::Value and toml_edit::Document loaded from arbitrary files with the corresponding extension. However, my repository/package/plugin is too specific for something that generic, whereas this repository seems more suitable and has most of the code for it already.

The main missing pieces is a wrapper implementing Asset for serde_json::Value etc, which would allow other plugins to avoid duplicate registration of loaders for the corresponding extension.

NiklasEi commented 9 months ago

I guess that would fit here, yes. But in my opinion, typed assets should be the way to go in most cases.

With Bevy 0.13, asset loaders no longer are picked for loading a certain asset purely on their extensions. You could register the loader without an extension and then load like so:

let toml_doc: Handle<TomlAsset> = asset_server.load("my_toml_settings.toml");

That would at least prevent any conflicts of the generic loader with other asset loaders.

eatenpancreas commented 1 week ago

I will add to this that untyped assets are on rare occasions needed. I've just hand written one for toml for myself, but i think it might be useful for others. In my case, combining untyped values with serde-toml-merge is great for modding in games where mods might need to override files based on load order. I'm sure there's other usages too.

This was my code largely based on this crate's: ```rs use std::ops::{Deref, DerefMut}; use std::str::from_utf8; use bevy::app::Plugin; use bevy::asset::AssetApp; use bevy::{ asset::{Asset, AssetLoader, AsyncReadExt}, reflect::Reflect, }; use thiserror::Error; pub type TomlValue = toml::Value; /// Representation of any Toml asset #[derive(Asset, Reflect)] pub struct AnyToml( // Wrapped with option due to need for default implementation #[reflect(ignore)] Option, ); impl AnyToml { pub fn into_inner(self) -> TomlValue { self.0.unwrap() } } impl Deref for AnyToml { type Target = TomlValue; fn deref(&self) -> &Self::Target { self.0.as_ref().unwrap() } } impl DerefMut for AnyToml { fn deref_mut(&mut self) -> &mut Self::Target { self.0.as_mut().unwrap() } } #[derive(Debug, Error)] pub enum TomlLoaderError { /// An [IO Error](std::io::Error) #[error("Could not read the file: {0}")] Io(#[from] std::io::Error), /// A [conversion Error](std::str::Utf8Error) #[error("Could not interpret as UTF-8: {0}")] FormatError(#[from] std::str::Utf8Error), /// A [TOML Error](serde_toml::de::Error) #[error("Could not parse TOML: {0}")] TomlError(#[from] toml::de::Error), } pub struct AnyTomlPlugin; impl Plugin for AnyTomlPlugin { fn build(&self, app: &mut bevy::prelude::App) { app.init_asset::() .register_asset_loader(AnyTomlLoader); } } #[derive(Default)] struct AnyTomlLoader; impl AssetLoader for AnyTomlLoader { type Asset = AnyToml; type Settings = (); type Error = TomlLoaderError; async fn load<'a>( &'a self, reader: &'a mut bevy::asset::io::Reader<'_>, _settings: &'a Self::Settings, _load_context: &'a mut bevy::asset::LoadContext<'_>, ) -> Result { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let asset = toml::from_str(from_utf8(&bytes)?)?; Ok(AnyToml(Some(asset))) } fn extensions(&self) -> &[&str] { &["toml"] } } ```