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

Allow adding assets to states from plugins #81

Closed
uzytkownik opened this issue Oct 12, 2022 · 5 comments · Fixed by #86
Closed

Allow adding assets to states from plugins #81

uzytkownik opened this issue Oct 12, 2022 · 5 comments · Fixed by #86

Comments

@uzytkownik
Copy link

Currently the only (official) way to describe the loading transitions is from fn main function. For bigger projects, from what I understand, plugins are often used.

I wrote very quick'n'dirty proof-of-concept:

use bevy::{prelude::*, utils::*, ecs::schedule::StateData, asset::Asset};
use bevy_asset_loader::prelude::*;

enum AssetLoader<T> {
    BeingBuilt {
        hash: HashMap<T, Option<LoadingState<T>>>,
    },
    Completed
}

impl<T> Default for AssetLoader<T> {
    fn default() -> Self {
        Self::BeingBuilt {
            hash: Default::default()
        }
    }
}

pub trait AssetLoaderExt {
    fn continue_to_state<T: StateData>(&mut self, in_state: T, next: T) -> &mut Self;
    fn init_resource_in_state<T: StateData, R: FromWorld + Send + Sync + 'static>(&mut self, in_state: T) -> &mut Self;
    fn with_collection<T: StateData, A: AssetCollection>(&mut self, in_state: T) -> &mut Self;
    fn with_dynamic_collection<T: StateData, C: DynamicAssetCollection + Asset>(&mut self, in_state: T, files: Vec<&str>) -> &mut Self;
    fn with_loading_state<T: StateData>(&mut self, in_state: T, run: impl FnOnce(LoadingState<T>) -> LoadingState<T>) -> &mut Self;
    fn complete_loading<T: StateData>(&mut self) -> &mut Self;
}

impl AssetLoaderExt for App {

    fn continue_to_state<T: StateData>(&mut self, in_state: T, next: T) -> &mut Self {
        self.with_loading_state(in_state, |ls| {
            ls.continue_to_state(next)
        })
    }

    fn init_resource_in_state<T: StateData, R: FromWorld + Send + Sync + 'static>(&mut self, in_state: T) -> &mut Self {
        self.with_loading_state(in_state, |ls| {
            ls.init_resource::<R>()
        })
    }

    fn with_collection<T: StateData, A: AssetCollection>(&mut self, in_state: T) -> &mut Self {
        self.with_loading_state(in_state, |ls| {
            ls.with_collection::<A>()
        })
    }

    fn with_dynamic_collection<T: StateData, C: DynamicAssetCollection + Asset>(&mut self, in_state: T, files: Vec<&str>) -> &mut Self {
        self.with_loading_state(in_state, |ls| {
            ls.with_dynamic_collections::<C>(files)
        })
    }

    fn with_loading_state<T: StateData>(&mut self, in_state: T, run: impl FnOnce(LoadingState<T>) -> LoadingState<T>) -> &mut Self {
        let mut al = self.world.get_resource_or_insert_with(|| AssetLoader::<T>::default());
        match &mut *al {
            AssetLoader::BeingBuilt { hash } => {
                let ols = hash.entry(in_state).or_insert(Some(LoadingState::new(in_state)));
                *ols = Some(run(ols.take().unwrap()));
            },
            AssetLoader::Completed => unreachable!()
        }
        self
    }

    fn complete_loading<T: StateData>(&mut self) -> &mut Self {
        if let Some(al) = self.world.remove_resource::<AssetLoader<T>>() {
            match al {
                AssetLoader::BeingBuilt { mut hash } => {
                    for (_state, ls) in hash.drain() {
                        self.add_loading_state(ls.unwrap());
                    }
                },
                AssetLoader::Completed => unreachable!()
            }
        }
        self.world.insert_resource(AssetLoader::<T>::Completed);
        self
    }
}

(It would probably be able to expose simpler interface if LoadingState changed from fn foo(self) -> Self to fn foo(&mut self) -> &mut Self).

This way the loading can be constructed as:

// ... 
impl Plugin for PlayerPlugin {
    fn build(&self, app: &mut App) {
        // ...
        app.with_collection::<_, PlayerImages>(&State::Game);
    }
}

// ...
fn main() {
    let mut app = App::new();
    // ...
    app.continue_to_state(State::Init, State::Game);
    // ...
    app.complete_loading::<State>();
    app.run();
}

(I suspect that complete_loading can be avoided with 'native' loading)

@NiklasEi
Copy link
Owner

I have not seen a use case for this so far. Can you explain what you want the possibility to configure a loading state across multiple plugins for? In my own code bases, I would like them to be configured in one plugin to make it easier to follow.

The biggest pain point I would have with this kind of API is that the user needs to remember to call complete_loading and that the actual content of the loading state then depends on the order of user plugins (since they can all try to add new collections to a state).

@uzytkownik
Copy link
Author

I divided my game (toy but I'm trying to do it properly) into files and treat each component as plugin. So player is a plugin, mob is a plugin, board is a plugin etc.

@NiklasEi
Copy link
Owner

So you want to add the collections concerning the player in the player plugin, the board in the board plugin and so on? This should already be possible. You can add identical loading states with different collections and they will get combined.

@uzytkownik
Copy link
Author

I missed this from documentation. Can it be treated as documentation bug then?

@NiklasEi
Copy link
Owner

Yes, I can add that to the readme and consider this issue closed as soon as the functionality is documented.

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

Successfully merging a pull request may close this issue.

2 participants