diff --git a/Cargo.lock b/Cargo.lock index 3691e9b..b503c59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -947,6 +947,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fixed-timestep-rotating-model" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "fps_counter", + "lyra-engine", + "rand 0.8.5", + "tracing", +] + [[package]] name = "flate2" version = "1.0.28" diff --git a/Cargo.toml b/Cargo.toml index f285537..5a25f9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ "lyra-ecs", "lyra-reflect", "lyra-scripting", - "lyra-game", "lyra-math", "lyra-scene", "examples/many-lights"] + "lyra-game", "lyra-math", "lyra-scene", "examples/many-lights", "examples/fixed-timestep-rotating-model"] [features] scripting = ["dep:lyra-scripting"] diff --git a/examples/fixed-timestep-rotating-model/Cargo.toml b/examples/fixed-timestep-rotating-model/Cargo.toml new file mode 100644 index 0000000..8d7fa61 --- /dev/null +++ b/examples/fixed-timestep-rotating-model/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "fixed-timestep-rotating-model" +version = "0.1.0" +edition = "2021" + +[dependencies] +lyra-engine = { path = "../../", features = ["tracy"] } +anyhow = "1.0.75" +async-std = "1.12.0" +tracing = "0.1.37" +rand = "0.8.5" +fps_counter = "3.0.0" + +[target.x86_64-unknown-linux-gnu] +linker = "/usr/bin/clang" +rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"] + +[profile.dev] +opt-level = 1 + +[profile.release] +debug = true \ No newline at end of file diff --git a/examples/fixed-timestep-rotating-model/src/main.rs b/examples/fixed-timestep-rotating-model/src/main.rs new file mode 100644 index 0000000..cadcd51 --- /dev/null +++ b/examples/fixed-timestep-rotating-model/src/main.rs @@ -0,0 +1,241 @@ +use std::ptr::NonNull; + +use lyra_engine::{ + assets::{gltf::Gltf, ResourceManager}, + ecs::{ + query::{Res, ResMut, View}, + system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem}, + World, + }, + game::Game, + input::{ + Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, + InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, + }, + math::{self, Transform, Vec3}, + render::light::directional::DirectionalLight, + scene::{ + CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, + ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, + ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, + }, + DeltaTime, +}; +use tracing::info; + +#[async_std::main] +async fn main() { + let action_handler_plugin = |game: &mut Game| { + let action_handler = ActionHandler::builder() + .add_layout(LayoutId::from(0)) + .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis)) + .add_action("Debug", Action::new(ActionKind::Button)) + .add_mapping( + ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) + .bind( + ACTLBL_MOVE_FORWARD_BACKWARD, + &[ + ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_MOVE_LEFT_RIGHT, + &[ + ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_MOVE_UP_DOWN, + &[ + ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_LOOK_LEFT_RIGHT, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(), + ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_LOOK_UP_DOWN, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(), + ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_LOOK_ROLL, + &[ + ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0), + ], + ) + .bind( + "Debug", + &[ActionSource::Keyboard(KeyCode::B).into_binding()], + ) + .finish(), + ) + .finish(); + + let world = game.world_mut(); + world.add_resource(action_handler); + game.with_plugin(InputActionPlugin); + }; + + Game::initialize() + .await + .with_plugin(lyra_engine::DefaultPlugins) + .with_plugin(setup_scene_plugin) + .with_plugin(action_handler_plugin) + //.with_plugin(camera_debug_plugin) + .with_plugin(FreeFlyCameraPlugin) + .run() + .await; +} + +fn setup_scene_plugin(game: &mut Game) { + let world = game.world_mut(); + let resman = world.get_resource_mut::(); + let camera_gltf = resman + .request::("../assets/AntiqueCamera.glb") + .unwrap(); + + camera_gltf.wait_recurse_dependencies_load(); + let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0]; + drop(resman); + + world.spawn(( + camera_mesh.clone(), + WorldTransform::default(), + Transform::from_xyz(0.0, -5.0, 0.0), + )); + + { + let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0); + light_tran.scale = Vec3::new(0.5, 0.5, 0.5); + light_tran.rotate_x(math::Angle::Degrees(-45.0)); + light_tran.rotate_y(math::Angle::Degrees(25.0)); + world.spawn(( + DirectionalLight { + enabled: true, + color: Vec3::ONE, + intensity: 0.15, //..Default::default() + }, + light_tran, + )); + } + + let mut camera = CameraComponent::new_3d(); + camera.transform.translation += math::Vec3::new(0.0, 0.0, 1.5); + world.spawn((camera, FreeFlyCamera::default())); + + let fps_counter = |mut counter: ResMut, + delta: Res| -> anyhow::Result<()> { + let tick = counter.tick(); + + info!("FPS: {}, frame time: {}", tick, **delta); + + Ok(()) + }; + + world.add_resource(fps_counter::FPSCounter::new()); + + let rotate_system = |dt: Res, view: View<&mut Transform>| -> anyhow::Result<()> { + const SPEED: f32 = 4.0; + let dt = **dt; + + for mut transform in view.iter() { + info!("rotation: {:?}", transform.rotation); + transform.rotate_y(math::Angle::Degrees(SPEED * dt)); + } + + Ok(()) + }; + + let mut sys = BatchedSystem::new(); + sys.with_criteria(FixedTimestep::new(60)); + sys.with_system(rotate_system.into_system()); + sys.with_system(fps_counter.into_system()); + + game.with_system("fixed_timestep", sys, &[]); +} + +struct FixedTimestep { + max_tps: u32, + fixed_time: f32, + accumulator: f32, + old_dt: Option, +} + +#[allow(dead_code)] +impl FixedTimestep { + pub fn new(max_tps: u32) -> Self { + Self { + max_tps, + fixed_time: Self::calc_fixed_time(max_tps), + accumulator: 0.0, + old_dt: None, + } + } + + fn calc_fixed_time(max_tps: u32) -> f32 { + 1.0 / max_tps as f32 + } + + fn set_tps(&mut self, tps: u32) { + self.max_tps = tps; + self.fixed_time = Self::calc_fixed_time(tps); + } + + fn tps(&self) -> u32 { + self.max_tps + } + + fn fixed_time(&self) -> f32 { + self.fixed_time + } +} + +impl Criteria for FixedTimestep { + fn can_run(&mut self, mut world: NonNull, check_count: u32) -> CriteriaSchedule { + let world = unsafe { world.as_mut() }; + if check_count == 0 { + let delta_time = world.get_resource::(); + self.accumulator += **delta_time; + } + + if self.accumulator >= self.fixed_time { + self.accumulator -= self.fixed_time; + return CriteriaSchedule::YesAndLoop; + } + + CriteriaSchedule::No + } + + fn modify_world(&mut self, mut world: NonNull) { + let world = unsafe { world.as_mut() }; + self.old_dt = world.try_get_resource().map(|r| *r); + + world.add_resource(DeltaTime::from(self.fixed_time)); + } + + fn undo_world_modifications(&mut self, mut world: NonNull) { + let world = unsafe { world.as_mut() }; + world.add_resource( + self.old_dt + .expect("DeltaTime resource was somehow never got from the world"), + ); + } +} \ No newline at end of file diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 22fe0d5..d5057c5 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -7,6 +7,7 @@ struct FixedTimestep { max_tps: u32, fixed_time: f32, accumulator: f32, + old_dt: Option, } #[allow(dead_code)] @@ -16,6 +17,7 @@ impl FixedTimestep { max_tps, fixed_time: Self::calc_fixed_time(max_tps), accumulator: 0.0, + old_dt: None, } } @@ -52,6 +54,21 @@ impl Criteria for FixedTimestep { CriteriaSchedule::No } + + fn modify_world(&mut self, mut world: NonNull) { + let world = unsafe { world.as_mut() }; + self.old_dt = world.try_get_resource().map(|r| *r); + + world.add_resource(DeltaTime::from(self.fixed_time)); + } + + fn undo_world_modifications(&mut self, mut world: NonNull) { + let world = unsafe { world.as_mut() }; + world.add_resource( + self.old_dt + .expect("DeltaTime resource was somehow never got from the world"), + ); + } } #[derive(Clone)] diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 45e85cd..47baab5 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,4 +1,4 @@ -use std::{any::{Any, TypeId}, collections::HashMap, ptr::NonNull}; +use std::{any::TypeId, collections::HashMap, ptr::NonNull}; use atomic_refcell::{AtomicRef, AtomicRefMut}; diff --git a/lyra-game/src/delta_time.rs b/lyra-game/src/delta_time.rs index 2c19944..4d9a31e 100644 --- a/lyra-game/src/delta_time.rs +++ b/lyra-game/src/delta_time.rs @@ -4,9 +4,15 @@ use lyra_reflect::Reflect; use crate::{plugin::Plugin, game::GameStages}; -#[derive(Clone, Component, Default, Reflect)] +#[derive(Clone, Copy, Component, Default, Reflect)] pub struct DeltaTime(f32, #[reflect(skip)] Option); +impl From for DeltaTime { + fn from(value: f32) -> Self { + DeltaTime(value, None) + } +} + impl std::ops::Deref for DeltaTime { type Target = f32;