From b7575f113a4ddc17c6789eb5f45e55d57fd160b2 Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Sun, 23 Jul 2023 01:20:26 +0100
Subject: [PATCH] Pull configs into hot-reload assets

---
 Cargo.lock                        |  1 +
 Cargo.toml                        |  1 +
 assets/config/config.phys         |  5 ++
 game_core/Cargo.toml              |  3 +-
 game_core/src/assets/configs.rs   | 81 +++++++++++++++++++++++++++++++
 game_core/src/assets/mod.rs       |  2 +
 game_core/src/debug/mod.rs        | 11 +++--
 game_core/src/entities/physics.rs | 22 ++++++++-
 game_core/src/entities/player.rs  | 16 ++++--
 game_core/src/main.rs             |  3 +-
 game_core/src/system/resources.rs |  2 +-
 11 files changed, 133 insertions(+), 14 deletions(-)
 create mode 100644 assets/config/config.phys
 create mode 100644 game_core/src/assets/configs.rs

diff --git a/Cargo.lock b/Cargo.lock
index 5c5bf6d..c327bb8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1639,6 +1639,7 @@ dependencies = [
 name = "game_core"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "bevy",
  "bevy_embedded_assets",
  "bevy_rapier2d",
diff --git a/Cargo.toml b/Cargo.toml
index a9df19c..38dbbc6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,6 +22,7 @@ serde = "1.0.164"
 serde_json = "1.0.96"
 num-traits = "0.2.15"
 paste = "1.0.12"
+anyhow = "1.0.71"
 
 bevy_rapier2d = { version = "0.22.0", features = ["simd-stable", "wasm-bindgen"]}
 bevy_embedded_assets = "0.8.0"
diff --git a/assets/config/config.phys b/assets/config/config.phys
new file mode 100644
index 0000000..727ab38
--- /dev/null
+++ b/assets/config/config.phys
@@ -0,0 +1,5 @@
+{
+    "gravity": -123.0,
+    "jump": 200.0,
+    "speed": 150.0
+}
\ No newline at end of file
diff --git a/game_core/Cargo.toml b/game_core/Cargo.toml
index 5d89f4f..9b857ad 100644
--- a/game_core/Cargo.toml
+++ b/game_core/Cargo.toml
@@ -21,4 +21,5 @@ bevy_rapier2d.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 num-traits.workspace = true
-paste.workspace = true
\ No newline at end of file
+paste.workspace = true
+anyhow.workspace = true
\ No newline at end of file
diff --git a/game_core/src/assets/configs.rs b/game_core/src/assets/configs.rs
new file mode 100644
index 0000000..bcbb54e
--- /dev/null
+++ b/game_core/src/assets/configs.rs
@@ -0,0 +1,81 @@
+use crate::system::AppState;
+use bevy::asset::{AddAsset, AssetLoader, AssetServer, BoxedFuture, LoadContext, LoadedAsset};
+use bevy::ecs::system::SystemParam;
+use bevy::prelude::*;
+use bevy::reflect::{TypePath, TypeUuid};
+use paste::paste;
+use serde::{Deserialize, Serialize};
+
+macro_rules! impl_config_lifecycle {
+    ($(($struct: ident, $ext: literal)),*) => {
+		paste! {
+			#[allow(dead_code)]
+			mod _inner {
+				use super::*;
+				$(
+					struct [<$struct Loader>];
+					impl AssetLoader for [<$struct Loader>] {
+						fn load<'a>(
+							&'a self,
+							bytes: &'a [u8],
+							load_context: &'a mut LoadContext,
+						) -> BoxedFuture<'a, anyhow::Result<()>> {
+							Box::pin(async move {
+								let value: $struct = serde_json::from_slice(bytes)?;
+								load_context.set_default_asset(LoadedAsset::new(value));
+								Ok(())
+							})
+						}
+
+						fn extensions(&self) -> &[&str] {
+							&[$ext]
+						}
+					}
+					#[derive(Resource)]
+					struct [<$struct Wrapper>] {
+						handle: Handle<$struct>,
+					}
+
+					#[derive(SystemParam)]
+					pub struct [<Fetch $struct>]<'w> {
+						wrapper: Res<'w, [<$struct Wrapper>]>,
+						asset: Res<'w, Assets<$struct>>,
+					}
+
+					impl<'w> [<Fetch $struct>]<'w> {
+						pub fn get(&self) -> Option<&$struct> {
+							self.asset.get(&self.wrapper.handle)
+						}
+					}
+
+					fn [<init_ $struct:snake>](mut commands: Commands, assets: Res<AssetServer>) {
+						let handle = assets.load(format!("config/config.{}", $ext));
+						commands.insert_resource([<$struct Wrapper>] { handle });
+					}
+				)*
+
+				pub struct ConfigsPlugin;
+				impl Plugin for ConfigsPlugin {
+					fn build(&self, app: &mut App) {$(
+						app.add_asset::<$struct>()
+							.add_asset_loader([<$struct Loader>])
+							.add_systems(OnEnter(AppState::Preload), [<init_ $struct:snake>]);
+					)*}
+				}
+			}
+
+			pub use _inner::ConfigsPlugin;
+			pub use _inner::{$([<Fetch $struct>]),*};
+		}
+	};
+}
+
+#[derive(Copy, Clone, TypeUuid, TypePath, Debug, Default, Serialize, Deserialize)]
+#[uuid = "f7d88160-28e4-11ee-bcad-b34d9ff949be"]
+pub struct PhysicsConfig {
+	pub gravity: f32,
+	pub jump: f32,
+	pub speed: f32,
+}
+
+impl_config_lifecycle!((PhysicsConfig, "phys"));
diff --git a/game_core/src/assets/mod.rs b/game_core/src/assets/mod.rs
index bfdb073..8edb7a0 100644
--- a/game_core/src/assets/mod.rs
+++ b/game_core/src/assets/mod.rs
@@ -1,3 +1,4 @@
+mod configs;
 mod loader;
 mod resources;
 mod startup;
@@ -23,6 +24,7 @@ mod _plugin {
 }
 
 pub use _plugin::AssetLoadingPlugin;
+pub use configs::{ConfigsPlugin, FetchPhysicsConfig, PhysicsConfig};
 pub(self) use loader::AssetTypeLoader;
 pub use resources::AssetHandles;
 pub(self) use resources::{AssetNameMapping, FixedAssetNameMapping, SpriteSheetConfig};
diff --git a/game_core/src/debug/mod.rs b/game_core/src/debug/mod.rs
index 1d663d5..84cc351 100644
--- a/game_core/src/debug/mod.rs
+++ b/game_core/src/debug/mod.rs
@@ -15,11 +15,12 @@ mod _plugin {
 					Collider::cuboid(200.0, 50.0),
 				));
 			})
-			.add_systems(
-				Update,
-				(|gamepads: Res<Gamepads>| info!("{:?}", gamepads))
-					.run_if(on_fixed_timer(Duration::from_secs(5))),
-			);
+			// .add_systems(
+			// 	Update,
+			// 	(|gamepads: Res<Gamepads>| info!("{:?}", gamepads))
+			// 		.run_if(on_fixed_timer(Duration::from_secs(5))),
+			// )
+			;
 		}
 	}
 }
diff --git a/game_core/src/entities/physics.rs b/game_core/src/entities/physics.rs
index f97b936..57392d7 100644
--- a/game_core/src/entities/physics.rs
+++ b/game_core/src/entities/physics.rs
@@ -1,4 +1,6 @@
+use crate::assets::FetchPhysicsConfig;
 use crate::system::run_in_game;
+use bevy::ecs::query::Has;
 use bevy::prelude::*;
 use bevy_rapier2d::prelude::KinematicCharacterController;
 
@@ -8,6 +10,9 @@ pub const GRAVITY_VEC: Vec2 = Vec2::new(0.0, GRAVITY_ACC);
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, SystemSet)]
 pub struct PhysicsSet;
 
+#[derive(Component, Clone, Copy, Debug)]
+pub struct HasGravity;
+
 #[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut)]
 pub struct Velocity(Vec2);
 
@@ -43,15 +48,28 @@ impl Acceleration {
 	pub fn gravity() -> Self {
 		Acceleration(GRAVITY_VEC)
 	}
+	pub fn new(val: Vec2) -> Self {
+		Acceleration(val)
+	}
 }
 
 fn apply_acceleration(
 	time: Res<Time>,
-	mut query: Query<(&mut Velocity, &Acceleration, Option<&PhysicsLimits>)>,
+	mut query: Query<(
+		&mut Velocity,
+		&Acceleration,
+		Option<&PhysicsLimits>,
+		Has<HasGravity>,
+	)>,
+	phys_config: FetchPhysicsConfig,
 ) {
+	let gravity = phys_config.get().map(|p| p.gravity).unwrap_or_default();
 	let dt = time.delta_seconds();
-	for (mut velocity, acceleration, limits) in &mut query {
+	for (mut velocity, acceleration, limits, has_gravity) in &mut query {
 		**velocity += **acceleration * dt;
+		if has_gravity {
+			**velocity += Vec2::new(0.0, gravity) * dt;
+		}
 		**velocity = limits
 			.copied()
 			.unwrap_or_default()
diff --git a/game_core/src/entities/player.rs b/game_core/src/entities/player.rs
index c6a6fda..5da2d04 100644
--- a/game_core/src/entities/player.rs
+++ b/game_core/src/entities/player.rs
@@ -1,7 +1,8 @@
-use crate::assets::AssetHandles;
+use crate::assets::{AssetHandles, FetchPhysicsConfig};
 use crate::system::{AppState, ChaseCam, PolyInputManager};
 use bevy::prelude::*;
 
+use crate::entities::physics::HasGravity;
 use crate::entities::{
 	Acceleration, PhysicsLimits, PhysicsSet, SimpleAnimation, SimpleAnimationBundle, Velocity,
 	GRAVITY_ACC,
@@ -27,8 +28,9 @@ pub fn spawn_player(mut commands: Commands, assets: Res<AssetHandles>) {
 			offset: CharacterLength::Absolute(0.03),
 			..Default::default()
 		},
-		Acceleration::gravity(),
+		Acceleration::default(),
 		Velocity::default(),
+		HasGravity,
 		PhysicsLimits {
 			velocity: (
 				Some(Vec2::new(f32::NEG_INFINITY, GRAVITY_ACC)),
@@ -53,7 +55,13 @@ pub fn spawn_player(mut commands: Commands, assets: Res<AssetHandles>) {
 pub fn handle_input(
 	input: PolyInputManager,
 	mut query: Query<(&mut Velocity, &KinematicCharacterControllerOutput), With<Player>>,
+	phys_config: FetchPhysicsConfig,
 ) {
+	let speed = phys_config
+		.get()
+		.map(|phys_config| phys_config.speed)
+		.unwrap_or_default();
+
 	for (mut velocity, controller) in &mut query {
 		let delta_y = if controller.grounded && input.is_jump_just_pressed() {
 			Some(40.0)
@@ -62,9 +70,9 @@ pub fn handle_input(
 		};
 
 		let delta_x = if input.is_right_pressed_or_active() {
-			200.0
+			speed
 		} else if input.is_left_pressed_or_active() {
-			-200.0
+			-speed
 		} else {
 			0.0
 		};
diff --git a/game_core/src/main.rs b/game_core/src/main.rs
index 9eb1379..8f74f60 100644
--- a/game_core/src/main.rs
+++ b/game_core/src/main.rs
@@ -2,8 +2,9 @@ use bevy::prelude::App;
 
 fn main() {
 	App::new()
-		.add_plugins(game_core::assets::AssetLoadingPlugin)
 		.add_plugins(game_core::system::SystemPluginSet)
+		.add_plugins(game_core::assets::AssetLoadingPlugin)
+		.add_plugins(game_core::assets::ConfigsPlugin)
 		.add_plugins(game_core::entities::EntityPluginSet)
 		.add_plugins(game_core::debug::DebugPlugin)
 		.add_plugins(bevy_rapier2d::plugin::RapierPhysicsPlugin::<()>::pixels_per_meter(18.0))
diff --git a/game_core/src/system/resources.rs b/game_core/src/system/resources.rs
index 36c5482..f643b4f 100644
--- a/game_core/src/system/resources.rs
+++ b/game_core/src/system/resources.rs
@@ -31,7 +31,7 @@ pub fn configure_default_plugins() -> PluginGroupBuilder {
 		})
 		.set(AssetPlugin {
 			asset_folder: get_asset_path_string(),
-			watch_for_changes: ChangeWatcher::with_delay(Duration::from_secs(1)),
+			watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(25)),
 		})
 		.set(ImagePlugin::default_nearest())
 		.set(LogPlugin {
-- 
GitLab