Repository: NiklasEi/bevy_asset_loader (rust)

Issue:
For empty LoaderState it should immidiatly transfer the state
I discovered this bug by commenting and uncommenting code to debug an issue. If `LoaderState` is empty, the transition to new state will never happen instead of happening immidiatly.

Pull request description:
Continue to next state without collections
resolves #82

Patch:
diff --git a/bevy_asset_loader/src/loading_state.rs b/bevy_asset_loader/src/loading_state.rs
index 0872876..3df58eb 100644
--- a/bevy_asset_loader/src/loading_state.rs
+++ b/bevy_asset_loader/src/loading_state.rs
@@ -28,12 +28,12 @@ use crate::dynamic_asset::{DynamicAssetCollection, DynamicAssetCollections};
 #[cfg(feature = "stageless")]
 use stageless::systems::{
     check_loading_collection, finish_loading_state, init_resource, initialize_loading_state,
-    reset_loading_state, run_loading_state, start_loading_collection,
+    reset_loading_state, resume_to_finalize, run_loading_state, start_loading_collection,
 };
 #[cfg(not(feature = "stageless"))]
 use systems::{
     check_loading_collection, finish_loading_state, init_resource, initialize_loading_state,
-    reset_loading_state, run_loading_state, start_loading_collection,
+    reset_loading_state, resume_to_finalize, run_loading_state, start_loading_collection,
 };
 
 #[cfg(not(feature = "stageless"))]
@@ -699,14 +699,14 @@ where
                 .get_resource_mut::<AssetLoaderConfiguration<S>>()
                 .unwrap();
             let mut loading_config = asset_loader_configuration
-                .configuration
+                .state_configurations
                 .remove(&self.loading_state)
                 .unwrap_or(LoadingConfiguration::new(self.next_state.clone()));
             if self.next_state.is_some() {
                 loading_config.next = self.next_state.clone();
             }
             asset_loader_configuration
-                .configuration
+                .state_configurations
                 .insert(self.loading_state.clone(), loading_config);
         }
 
@@ -773,6 +773,9 @@ where
                 .with_system(initialize_loading_state),
         );
         update.add_system_set(self.start_loading_collections);
+        self.check_loading_collections = self
+            .check_loading_collections
+            .with_system(resume_to_finalize::<S>);
         update.add_system_set(self.check_loading_collections);
         update.add_system_set(self.initialize_resources);
         update.add_system_set(
@@ -860,14 +863,14 @@ where
                 .get_resource_mut::<AssetLoaderConfiguration<S>>()
                 .unwrap();
             let mut loading_config = asset_loader_configuration
-                .configuration
+                .state_configurations
                 .remove(&self.loading_state)
                 .unwrap_or_else(|| LoadingConfiguration::new(self.next_state.clone()));
             if self.next_state.is_some() {
                 loading_config.next = self.next_state.clone();
             }
             asset_loader_configuration
-                .configuration
+                .state_configurations
                 .insert(self.loading_state.clone(), loading_config);
         }
 
@@ -935,6 +938,9 @@ where
             .check_loading_dynamic_collections
             .with_system(resume_to_loading_asset_collections::<S>);
         update.add_system_set(self.check_loading_dynamic_collections);
+        self.check_loading_collections = self
+            .check_loading_collections
+            .with_system(resume_to_finalize::<S>);
         update.add_system_set(self.check_loading_collections);
         update.add_system_set(
             ConditionSet::new()
@@ -957,6 +963,7 @@ where
             InternalLoadingState::LoadingDynamicAssetCollections,
             self.start_loading_dynamic_collections,
         );
+
         loading_schedule.add_enter_system_set(
             InternalLoadingState::LoadingAssets,
             self.start_loading_collections,
@@ -1012,13 +1019,13 @@ impl<T> Default for LoadingAssetHandles<T> {
 }
 
 pub(crate) struct AssetLoaderConfiguration<State: StateData> {
-    configuration: HashMap<State, LoadingConfiguration<State>>,
+    state_configurations: HashMap<State, LoadingConfiguration<State>>,
 }
 
 impl<State: StateData> Default for AssetLoaderConfiguration<State> {
     fn default() -> Self {
         AssetLoaderConfiguration {
-            configuration: HashMap::default(),
+            state_configurations: HashMap::default(),
         }
     }
 }
diff --git a/bevy_asset_loader/src/loading_state/dynamic_asset_systems.rs b/bevy_asset_loader/src/loading_state/dynamic_asset_systems.rs
index c6bdff1..4b4ba2c 100644
--- a/bevy_asset_loader/src/loading_state/dynamic_asset_systems.rs
+++ b/bevy_asset_loader/src/loading_state/dynamic_asset_systems.rs
@@ -35,7 +35,10 @@ pub(crate) fn load_dynamic_asset_collections<S: StateData, C: DynamicAssetCollec
             .handles
             .push(asset_server.load_untyped(&file));
     }
-    if let Some(mut config) = asset_loader_config.configuration.get_mut(state.current()) {
+    if let Some(mut config) = asset_loader_config
+        .state_configurations
+        .get_mut(state.current())
+    {
         config.loading_dynamic_collections += 1;
     }
 }
@@ -78,7 +81,7 @@ pub(crate) fn check_dynamic_asset_collections<S: StateData, C: DynamicAssetColle
             collection.register(&mut asset_keys);
         }
         let mut config = asset_loader_config
-            .configuration
+            .state_configurations
             .get_mut(state.current())
             .expect("No asset loader configuration for current state");
         config.loading_dynamic_collections -= 1;
@@ -92,7 +95,7 @@ pub(crate) fn resume_to_loading_asset_collections<S: StateData>(
     asset_loader_config: Res<AssetLoaderConfiguration<S>>,
 ) {
     let config = asset_loader_config
-        .configuration
+        .state_configurations
         .get(state.current())
         .expect("No asset loader configuration for current state");
     if config.loading_dynamic_collections == 0 {
diff --git a/bevy_asset_loader/src/loading_state/stageless/dynamic_asset_systems.rs b/bevy_asset_loader/src/loading_state/stageless/dynamic_asset_systems.rs
index bd2afd8..7ccba36 100644
--- a/bevy_asset_loader/src/loading_state/stageless/dynamic_asset_systems.rs
+++ b/bevy_asset_loader/src/loading_state/stageless/dynamic_asset_systems.rs
@@ -27,7 +27,7 @@ pub(crate) fn load_dynamic_asset_collections<S: StateData, C: DynamicAssetCollec
     }
 
     let mut config = asset_loader_config
-        .configuration
+        .state_configurations
         .get_mut(&state.0)
         .expect("No asset loader configuration for current state");
     config.loading_dynamic_collections += 1;
@@ -76,7 +76,7 @@ pub(crate) fn check_dynamic_asset_collections<S: StateData, C: DynamicAssetColle
             collection.register(&mut asset_keys);
         }
         let mut config = asset_loader_config
-            .configuration
+            .state_configurations
             .get_mut(&state.0)
             .expect("No asset loader configuration for current state");
         config.loading_dynamic_collections -= 1;
@@ -94,7 +94,7 @@ pub(crate) fn resume_to_loading_asset_collections<S: StateData>(
         return;
     }
     let config = asset_loader_config
-        .configuration
+        .state_configurations
         .get(&state.0)
         .expect("No asset loader configuration for current state");
     if config.loading_dynamic_collections == 0 {
diff --git a/bevy_asset_loader/src/loading_state/stageless/systems.rs b/bevy_asset_loader/src/loading_state/stageless/systems.rs
index a8934d4..250fdfe 100644
--- a/bevy_asset_loader/src/loading_state/stageless/systems.rs
+++ b/bevy_asset_loader/src/loading_state/stageless/systems.rs
@@ -1,11 +1,11 @@
 use bevy::asset::{AssetServer, LoadState};
 use bevy::ecs::prelude::Commands;
 
+use bevy::ecs::change_detection::{Mut, ResMut};
 use bevy::ecs::prelude::{FromWorld, World};
-use bevy::ecs::schedule::StateData;
-use bevy::ecs::system::SystemState;
+use bevy::ecs::schedule::{Stage, StateData};
+use bevy::ecs::system::{Res, SystemState};
 
-use bevy::prelude::{Mut, Res, ResMut, Stage};
 use std::marker::PhantomData;
 
 #[cfg(feature = "progress_tracking")]
@@ -32,7 +32,7 @@ pub(crate) fn start_loading_collection<S: StateData, Assets: AssetCollection>(wo
     let (mut asset_loader_configuration, state) = system_state.get_mut(world);
 
     let mut config = asset_loader_configuration
-        .configuration
+        .state_configurations
         .get_mut(&state.0)
         .unwrap_or_else(|| {
             panic!(
@@ -54,15 +54,7 @@ pub(crate) fn check_loading_collection<S: StateData, Assets: AssetCollection>(wo
             return;
         }
     }
-    if let Some((done, total, option_loading_collections)) =
-        count_loaded_handles::<S, Assets>(world)
-    {
-        if let Some(loading_collections) = option_loading_collections {
-            if loading_collections == 0 {
-                world.insert_resource(NextState(InternalLoadingState::Finalize))
-            }
-        }
-
+    if let Some((done, total)) = count_loaded_handles::<S, Assets>(world) {
         if total == done {
             let asset_collection = Assets::create(world);
             world.insert_resource(asset_collection);
@@ -83,7 +75,7 @@ pub(crate) fn check_loading_collection<S: StateData, Assets: AssetCollection>(wo
 
 fn count_loaded_handles<S: StateData, Assets: AssetCollection>(
     world: &mut World,
-) -> Option<(u32, u32, Option<usize>)> {
+) -> Option<(u32, u32)> {
     let cell = world.cell();
     let loading_asset_handles = cell.get_resource::<LoadingAssetHandles<Assets>>()?;
     let total = loading_asset_handles.handles.len();
@@ -99,23 +91,39 @@ fn count_loaded_handles<S: StateData, Assets: AssetCollection>(
         .filter(|state| state == &LoadState::Loaded)
         .count();
     if done < total {
-        return Some((done as u32, total as u32, None));
+        return Some((done as u32, total as u32));
     }
 
-    let mut loading_collections: Option<usize> = None;
-
     let state = cell
         .get_resource::<CurrentState<S>>()
         .expect("Cannot get State resource");
     let mut asset_loader_configuration = cell
         .get_resource_mut::<AssetLoaderConfiguration<S>>()
         .expect("Cannot get AssetLoaderConfiguration resource");
-    if let Some(mut config) = asset_loader_configuration.configuration.get_mut(&state.0) {
+    if let Some(mut config) = asset_loader_configuration
+        .state_configurations
+        .get_mut(&state.0)
+    {
         config.loading_collections -= 1;
-        loading_collections = Some(config.loading_collections)
     }
 
-    Some((done as u32, total as u32, loading_collections))
+    Some((done as u32, total as u32))
+}
+
+pub(crate) fn resume_to_finalize<S: StateData>(
+    mut commands: Commands,
+    loader_configuration: Res<AssetLoaderConfiguration<S>>,
+    state: Res<CurrentState<S>>,
+    internal_state: Res<CurrentState<InternalLoadingState>>,
+) {
+    if internal_state.0 != InternalLoadingState::LoadingAssets {
+        return;
+    }
+    if let Some(configuration) = loader_configuration.state_configurations.get(&state.0) {
+        if configuration.loading_collections == 0 {
+            commands.insert_resource(NextState(InternalLoadingState::Finalize))
+        }
+    }
 }
 
 pub(crate) fn initialize_loading_state(
@@ -135,7 +143,10 @@ pub(crate) fn finish_loading_state<S: StateData>(
     state: Res<CurrentState<S>>,
     asset_loader_configuration: Res<AssetLoaderConfiguration<S>>,
 ) {
-    if let Some(config) = asset_loader_configuration.configuration.get(&state.0) {
+    if let Some(config) = asset_loader_configuration
+        .state_configurations
+        .get(&state.0)
+    {
         if let Some(next) = config.next.as_ref() {
             commands.insert_resource(NextState(next.clone()));
             return;
diff --git a/bevy_asset_loader/src/loading_state/systems.rs b/bevy_asset_loader/src/loading_state/systems.rs
index e2afc76..ee04190 100644
--- a/bevy_asset_loader/src/loading_state/systems.rs
+++ b/bevy_asset_loader/src/loading_state/systems.rs
@@ -29,7 +29,7 @@ pub(crate) fn start_loading_collection<S: StateData, Assets: AssetCollection>(wo
     let (mut asset_loader_configuration, state) = system_state.get_mut(world);
 
     let mut config = asset_loader_configuration
-        .configuration
+        .state_configurations
         .get_mut(state.current())
         .unwrap_or_else(|| {
             panic!(
@@ -88,27 +88,36 @@ fn count_loaded_handles<S: StateData, Assets: AssetCollection>(
     let state = cell
         .get_resource::<State<S>>()
         .expect("Cannot get State resource");
-    let mut loading_state = cell
-        .get_resource_mut::<State<InternalLoadingState>>()
-        .expect("Cannot get LoadingStatePhase");
     let mut asset_loader_configuration = cell
         .get_resource_mut::<AssetLoaderConfiguration<S>>()
         .expect("Cannot get AssetLoaderConfiguration resource");
     if let Some(mut config) = asset_loader_configuration
-        .configuration
+        .state_configurations
         .get_mut(state.current())
     {
         config.loading_collections -= 1;
-        if config.loading_collections == 0 {
-            loading_state
-                .set(InternalLoadingState::Finalize)
-                .expect("Failed to set loading State");
-        }
     }
 
     Some((done as u32, total as u32))
 }
 
+pub(crate) fn resume_to_finalize<S: StateData>(
+    loader_configuration: Res<AssetLoaderConfiguration<S>>,
+    mut internal_state: ResMut<State<InternalLoadingState>>,
+    user_state: Res<State<S>>,
+) {
+    if let Some(configuration) = loader_configuration
+        .state_configurations
+        .get(user_state.current())
+    {
+        if configuration.loading_collections == 0 {
+            internal_state
+                .overwrite_set(InternalLoadingState::Finalize)
+                .expect("Failed to set internal state to Finalize")
+        }
+    }
+}
+
 pub(crate) fn initialize_loading_state(mut loading_state: ResMut<State<InternalLoadingState>>) {
     loading_state
         .overwrite_set(InternalLoadingState::LoadingDynamicAssetCollections)
@@ -121,7 +130,7 @@ pub(crate) fn finish_loading_state<S: StateData>(
     asset_loader_configuration: Res<AssetLoaderConfiguration<S>>,
 ) {
     if let Some(config) = asset_loader_configuration
-        .configuration
+        .state_configurations
         .get(state.current())
     {
         if let Some(next) = config.next.as_ref() {


Test patch:
diff --git a/bevy_asset_loader/tests/continues_without_collection.rs b/bevy_asset_loader/tests/continues_without_collection.rs
new file mode 100644
index 0000000..c8b51b3
--- /dev/null
+++ b/bevy_asset_loader/tests/continues_without_collection.rs
@@ -0,0 +1,52 @@
+#![allow(dead_code, unused_imports)]
+
+use bevy::app::AppExit;
+use bevy::asset::AssetPlugin;
+use bevy::prelude::*;
+use bevy_asset_loader::loading_state::{LoadingState, LoadingStateAppExt};
+
+#[cfg(all(
+    not(feature = "2d"),
+    not(feature = "3d"),
+    not(feature = "progress_tracking"),
+    not(feature = "stageless")
+))]
+#[test]
+fn continues_without_collection() {
+    App::new()
+        .add_state(MyStates::Load)
+        .add_plugins(MinimalPlugins)
+        .add_plugin(AssetPlugin::default())
+        .add_loading_state(LoadingState::new(MyStates::Load).continue_to_state(MyStates::Next))
+        .init_resource::<TestState>()
+        .add_system_set(SystemSet::on_update(MyStates::Load).with_system(expect))
+        .add_system_set(SystemSet::on_enter(MyStates::Next).with_system(exit))
+        .run();
+}
+
+fn expect(mut test_state: ResMut<TestState>) {
+    if test_state.wait_frames == 0 {
+        panic!("The asset loader did not continue to the next state");
+    }
+    test_state.wait_frames -= 1;
+}
+
+fn exit(mut exit: EventWriter<AppExit>) {
+    exit.send(AppExit);
+}
+
+struct TestState {
+    wait_frames: usize,
+}
+
+impl Default for TestState {
+    fn default() -> Self {
+        TestState { wait_frames: 1 }
+    }
+}
+
+#[derive(Clone, Eq, PartialEq, Debug, Hash)]
+enum MyStates {
+    Load,
+    Next,
+}
diff --git a/bevy_asset_loader/tests/stageless.rs b/bevy_asset_loader/tests/stageless.rs
index 98e76c1..6b47e1a 100644
--- a/bevy_asset_loader/tests/stageless.rs
+++ b/bevy_asset_loader/tests/stageless.rs
@@ -6,6 +6,7 @@
 ))]
 mod stageless {
     mod can_run_without_next_state;
+    mod continues_without_collection;
     mod init_resource;
     mod multiple_asset_collections;
     mod multiple_loading_states;
diff --git a/bevy_asset_loader/tests/stageless/can_run_without_next_state.rs b/bevy_asset_loader/tests/stageless/can_run_without_next_state.rs
index 1f4db7c..5c91655 100644
--- a/bevy_asset_loader/tests/stageless/can_run_without_next_state.rs
+++ b/bevy_asset_loader/tests/stageless/can_run_without_next_state.rs
@@ -1,5 +1,3 @@
-#![allow(dead_code)]
-
 use bevy::app::AppExit;
 use bevy::asset::AssetPlugin;
 use bevy::audio::AudioPlugin;
diff --git a/bevy_asset_loader/tests/stageless/continues_without_collection.rs b/bevy_asset_loader/tests/stageless/continues_without_collection.rs
new file mode 100644
index 0000000..ca981d8
--- /dev/null
+++ b/bevy_asset_loader/tests/stageless/continues_without_collection.rs
@@ -0,0 +1,50 @@
+use bevy::app::AppExit;
+use bevy::asset::AssetPlugin;
+use bevy::prelude::*;
+use bevy_asset_loader::loading_state::{LoadingState, LoadingStateAppExt};
+use iyes_loopless::prelude::*;
+
+#[test]
+fn continues_without_collection() {
+    App::new()
+        .add_loopless_state(MyStates::Load)
+        .add_plugins(MinimalPlugins)
+        .add_plugin(AssetPlugin::default())
+        .add_loading_state(LoadingState::new(MyStates::Load).continue_to_state(MyStates::Next))
+        .init_resource::<TestState>()
+        .add_enter_system(MyStates::Next, exit)
+        .add_system_set(
+            ConditionSet::new()
+                .run_in_state(MyStates::Load)
+                .with_system(expect)
+                .into(),
+        )
+        .run();
+}
+
+fn expect(mut test_state: ResMut<TestState>) {
+    if test_state.wait_frames == 0 {
+        panic!("The asset loader did not continue to the next state");
+    }
+    test_state.wait_frames -= 1;
+}
+
+fn exit(mut exit: EventWriter<AppExit>) {
+    exit.send(AppExit);
+}
+
+struct TestState {
+    wait_frames: usize,
+}
+
+impl Default for TestState {
+    fn default() -> Self {
+        TestState { wait_frames: 4 }
+    }
+}
+
+#[derive(Clone, Eq, PartialEq, Debug, Hash)]
+enum MyStates {
+    Load,
+    Next,
+}
diff --git a/bevy_asset_loader/tests/stageless/init_resource.rs b/bevy_asset_loader/tests/stageless/init_resource.rs
index 6e06006..818826a 100644
--- a/bevy_asset_loader/tests/stageless/init_resource.rs
+++ b/bevy_asset_loader/tests/stageless/init_resource.rs
@@ -1,5 +1,3 @@
-#![allow(dead_code)]
-
 use bevy::app::AppExit;
 use bevy::asset::AssetPlugin;
 use bevy::audio::AudioPlugin;
diff --git a/bevy_asset_loader/tests/stageless/multiple_asset_collections.rs b/bevy_asset_loader/tests/stageless/multiple_asset_collections.rs
index 9f40030..9045702 100644
--- a/bevy_asset_loader/tests/stageless/multiple_asset_collections.rs
+++ b/bevy_asset_loader/tests/stageless/multiple_asset_collections.rs
@@ -1,5 +1,3 @@
-#![allow(dead_code)]
-
 use bevy::app::AppExit;
 use bevy::asset::AssetPlugin;
 use bevy::audio::AudioPlugin;
diff --git a/bevy_asset_loader/tests/stageless/multiple_loading_states.rs b/bevy_asset_loader/tests/stageless/multiple_loading_states.rs
index 465b2ab..3b91ebd 100644
--- a/bevy_asset_loader/tests/stageless/multiple_loading_states.rs
+++ b/bevy_asset_loader/tests/stageless/multiple_loading_states.rs
@@ -1,5 +1,3 @@
-#![allow(dead_code)]
-
 use bevy::app::AppExit;
 use bevy::asset::AssetPlugin;
 use bevy::audio::AudioPlugin;