bevy_asset_loader/loading_state/
config.rs

1use crate::asset_collection::AssetCollection;
2use crate::dynamic_asset::{DynamicAssetCollection, DynamicAssetCollections};
3use crate::loading_state::dynamic_asset_systems::{
4    check_dynamic_asset_collections, load_dynamic_asset_collections,
5};
6use crate::loading_state::systems::{
7    check_loading_collection, finally_init_resource, start_loading_collection,
8};
9use crate::loading_state::{
10    InternalLoadingState, InternalLoadingStateSet, LoadingStateSchedule,
11    OnEnterInternalLoadingState,
12};
13use bevy::app::App;
14use bevy::asset::Asset;
15use bevy::ecs::schedule::ScheduleConfigs;
16use bevy::platform::collections::HashMap;
17use bevy::prelude::{BevyError, FromWorld, IntoScheduleConfigs, Resource, default};
18use bevy::state::state::FreelyMutableState;
19use std::any::TypeId;
20
21/// Methods to configure a loading state
22pub trait ConfigureLoadingState {
23    /// Add the given collection to the loading state.
24    ///
25    /// Its loading progress will be tracked. Only when all included handles are fully loaded, the
26    /// collection will be inserted to the ECS as a resource.
27    ///
28    /// See the `two_collections` example
29    #[must_use = "The configuration will only be applied when passed to App::configure_loading_state"]
30    fn load_collection<A: AssetCollection>(self) -> Self;
31
32    /// The resource will be initialized at the end of the loading state using its [`FromWorld`] implementation.
33    /// All asset collections will be available at that point and fully loaded.
34    ///
35    /// See the `finally_init_resource` example
36    #[must_use = "The configuration will only be applied when passed to App::configure_loading_state"]
37    fn finally_init_resource<R: Resource + FromWorld>(self) -> Self;
38
39    /// Register a custom dynamic asset collection type
40    ///
41    /// See the `custom_dynamic_assets` example
42    #[must_use = "The configuration will only be applied when passed to App::configure_loading_state"]
43    fn register_dynamic_asset_collection<C: DynamicAssetCollection + Asset>(self) -> Self;
44
45    /// Add a file containing dynamic assets to the loading state. Keys contained in the file, will
46    /// be available for asset collections.
47    ///
48    /// See the `dynamic_asset` example
49    #[must_use = "The configuration will only be applied when passed to App::configure_loading_state"]
50    fn with_dynamic_assets_file<C: DynamicAssetCollection + Asset>(self, file: &str) -> Self;
51
52    /// The resource will be initialized at the end of the loading state using its [`FromWorld`] implementation.
53    /// All asset collections will be available at that point and fully loaded.
54    ///
55    /// See the `finally_init_resource` example
56    #[must_use = "The configuration will only be applied when passed to App::configure_loading_state"]
57    #[deprecated(
58        since = "0.22.1",
59        note = "Method has been renamed to `finally_init_resource`"
60    )]
61    fn init_resource<R: Resource + FromWorld>(self) -> Self;
62}
63
64type SchedulConfig = ScheduleConfigs<
65    Box<(dyn bevy::prelude::System<In = (), Out = Result<(), BevyError>> + 'static)>,
66>;
67
68/// Can be used to add new asset collections or similar configuration to a loading state.
69/// ```edition2021
70/// # use bevy_asset_loader::prelude::*;
71/// # use bevy::prelude::*;
72/// # use bevy::asset::AssetPlugin;
73/// # use bevy::state::app::StatesPlugin;
74/// # fn main() {
75/// App::new()
76/// # /*
77///         .add_plugins(DefaultPlugins)
78/// # */
79/// #       .add_plugins((MinimalPlugins, AssetPlugin::default(), StatesPlugin))
80///         .init_state::<GameState>()
81///         .add_loading_state(
82///           LoadingState::new(GameState::Loading)
83///             .continue_to_state(GameState::Menu)
84///         )
85///         .configure_loading_state(LoadingStateConfig::new(GameState::Loading).load_collection::<AudioAssets>())
86/// #       .set_runner(|mut app| {app.update(); AppExit::Success})
87///         .run();
88/// # }
89///
90/// # #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
91/// # enum GameState {
92/// #     #[default]
93/// #     Loading,
94/// #     Menu
95/// # }
96/// # #[derive(AssetCollection, Resource)]
97/// # struct AudioAssets {
98/// #     #[asset(path = "audio/background.ogg")]
99/// #     background: Handle<AudioSource>,
100/// #     #[asset(path = "audio/plop.ogg")]
101/// #     plop: Handle<AudioSource>
102/// # }
103/// ```
104pub struct LoadingStateConfig<S: FreelyMutableState> {
105    state: S,
106
107    on_enter_loading_assets: Vec<SchedulConfig>,
108    on_enter_loading_dynamic_asset_collections: Vec<SchedulConfig>,
109    on_update: Vec<SchedulConfig>,
110    on_enter_finalize: Vec<SchedulConfig>,
111
112    dynamic_assets: HashMap<TypeId, Vec<String>>,
113}
114
115impl<S: FreelyMutableState> LoadingStateConfig<S> {
116    /// Create a new configuration for the given loading state
117    pub fn new(state: S) -> Self {
118        Self {
119            state,
120            on_enter_loading_assets: vec![],
121            on_enter_loading_dynamic_asset_collections: vec![],
122            on_update: vec![],
123            on_enter_finalize: vec![],
124            dynamic_assets: default(),
125        }
126    }
127
128    pub(crate) fn with_dynamic_assets_type_id(&mut self, file: &str, type_id: TypeId) {
129        let mut dynamic_files = self.dynamic_assets.remove(&type_id).unwrap_or_default();
130        dynamic_files.push(file.to_owned());
131        self.dynamic_assets.insert(type_id, dynamic_files);
132    }
133
134    pub(crate) fn build(mut self, app: &mut App) {
135        for config in self.on_enter_loading_assets {
136            app.add_systems(
137                OnEnterInternalLoadingState(
138                    self.state.clone(),
139                    InternalLoadingState::LoadingAssets,
140                ),
141                config,
142            );
143        }
144        for config in self.on_update {
145            app.add_systems(LoadingStateSchedule(self.state.clone()), config);
146        }
147        for config in self.on_enter_finalize {
148            app.add_systems(
149                OnEnterInternalLoadingState(self.state.clone(), InternalLoadingState::Finalize),
150                config,
151            );
152        }
153        for config in self.on_enter_loading_dynamic_asset_collections.drain(..) {
154            app.add_systems(
155                OnEnterInternalLoadingState(
156                    self.state.clone(),
157                    InternalLoadingState::LoadingDynamicAssetCollections,
158                ),
159                config,
160            );
161        }
162        let mut dynamic_assets = app
163            .world_mut()
164            .get_resource_mut::<DynamicAssetCollections<S>>()
165            .unwrap_or_else(|| {
166                panic!("Failed to get the DynamicAssetCollections resource for the loading state. Are you trying to configure a loading state before it was added to the bevy App?")
167            });
168        for (id, files) in self.dynamic_assets.drain() {
169            dynamic_assets.register_files_by_type_id(self.state.clone(), files, id);
170        }
171    }
172}
173
174impl<S: FreelyMutableState> ConfigureLoadingState for LoadingStateConfig<S> {
175    fn load_collection<A: AssetCollection>(mut self) -> Self {
176        self.on_enter_loading_assets
177            .push(start_loading_collection::<S, A>.into_configs());
178        self.on_update.push(
179            check_loading_collection::<S, A>
180                .in_set(InternalLoadingStateSet::CheckAssets)
181                .into_configs(),
182        );
183
184        self
185    }
186
187    fn finally_init_resource<R: Resource + FromWorld>(mut self) -> Self {
188        self.on_enter_finalize
189            .push(finally_init_resource::<R>.into_configs());
190
191        self
192    }
193
194    fn register_dynamic_asset_collection<C: DynamicAssetCollection + Asset>(mut self) -> Self {
195        self.on_enter_loading_dynamic_asset_collections
196            .push(load_dynamic_asset_collections::<S, C>.into_configs());
197        self.on_update.push(
198            check_dynamic_asset_collections::<S, C>
199                .in_set(InternalLoadingStateSet::CheckDynamicAssetCollections),
200        );
201
202        self
203    }
204
205    fn with_dynamic_assets_file<C: DynamicAssetCollection + Asset>(mut self, file: &str) -> Self {
206        self.with_dynamic_assets_type_id(file, TypeId::of::<C>());
207
208        self
209    }
210
211    fn init_resource<R: Resource + FromWorld>(self) -> Self {
212        self.finally_init_resource::<R>()
213    }
214}