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

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

Open
devnev opened this issue Jan 29, 2024 · 2 comments
Open

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

devnev opened this issue Jan 29, 2024 · 2 comments

Comments

@devnev
Copy link

devnev commented Jan 29, 2024

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
Copy link
Owner

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

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:
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<TomlValue>,
);

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::<AnyToml>()
            .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<Self::Asset, Self::Error> {
        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"]
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants