diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..23b32c1 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.57.0" diff --git a/game/Cargo.toml b/game/Cargo.toml index 2582788..a25e253 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -18,7 +18,8 @@ rust-embed = "6.2.0" raylib = "3.5" puffin = "0.9" puffin_http = "0.6" -dirty-fsm = "0.2" +dirty-fsm = "^0.2.1" +num-traits = "0.2" [dev-dependencies] puffin_viewer = "0.6" diff --git a/game/src/gfx/mod.rs b/game/src/gfx/mod.rs deleted file mode 100644 index 74e1944..0000000 --- a/game/src/gfx/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod util; -pub mod render_layer; diff --git a/game/src/gfx/util/mod.rs b/game/src/gfx/util/mod.rs deleted file mode 100644 index ccbeeae..0000000 --- a/game/src/gfx/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod scene; diff --git a/game/src/gfx/util/scene.rs b/game/src/gfx/util/scene.rs deleted file mode 100644 index 9c89177..0000000 --- a/game/src/gfx/util/scene.rs +++ /dev/null @@ -1,20 +0,0 @@ -use raylib::prelude::RaylibDrawHandle; - -/// Defines any renderable scene -pub trait Scene { - /// Render the hud layer (screen-space rendering) - fn render_hud( - &mut self, - gfx: &mut RaylibDrawHandle, - delta_seconds: f64, - ctx: &Context, - ) -> Result<(), Error>; - - /// Render the world layer (world-space rendering) - fn render_world( - &mut self, - gfx: &mut RaylibDrawHandle, - delta_seconds: f64, - ctx: &Context, - ) -> Result<(), Error>; -} diff --git a/game/src/lib.rs b/game/src/lib.rs index ab5b562..324e33e 100644 --- a/game/src/lib.rs +++ b/game/src/lib.rs @@ -1,12 +1,71 @@ #![feature(derive_default_enum)] +#![deny(unsafe_code)] +#![warn( + clippy::all, + clippy::await_holding_lock, + clippy::char_lit_as_u8, + clippy::checked_conversions, + clippy::dbg_macro, + clippy::debug_assert_with_mut_call, + clippy::doc_markdown, + clippy::empty_enum, + clippy::enum_glob_use, + clippy::exit, + clippy::expl_impl_clone_on_copy, + clippy::explicit_deref_methods, + clippy::explicit_into_iter_loop, + clippy::fallible_impl_from, + clippy::filter_map_next, + clippy::float_cmp_const, + clippy::fn_params_excessive_bools, + clippy::if_let_mutex, + clippy::implicit_clone, + clippy::imprecise_flops, + clippy::inefficient_to_string, + clippy::invalid_upcast_comparisons, + clippy::large_types_passed_by_value, + clippy::let_unit_value, + clippy::linkedlist, + clippy::lossy_float_literal, + clippy::macro_use_imports, + clippy::manual_ok_or, + clippy::map_err_ignore, + clippy::map_flatten, + clippy::map_unwrap_or, + clippy::match_on_vec_items, + clippy::match_same_arms, + clippy::match_wildcard_for_single_variants, + clippy::mem_forget, + clippy::mismatched_target_os, + clippy::mut_mut, + clippy::mutex_integer, + clippy::needless_borrow, + clippy::needless_continue, + clippy::option_option, + clippy::path_buf_push_overwrite, + clippy::ptr_as_ptr, + clippy::ref_option_ref, + clippy::rest_pat_in_fully_bound_structs, + clippy::same_functions_in_if_condition, + clippy::semicolon_if_nothing_returned, + clippy::string_add_assign, + clippy::string_add, + clippy::string_lit_as_bytes, + clippy::string_to_string, + clippy::todo, + clippy::trait_duplication_in_bounds, + clippy::unimplemented, + clippy::unnested_or_patterns, + clippy::unused_self, + clippy::useless_transmute, + clippy::verbose_file_reads, + clippy::zero_sized_map_values, + future_incompatible, + nonstandard_style, + rust_2018_idioms +)] -use std::{ - borrow::BorrowMut, - cell::{Cell, RefCell}, - ops::Deref, - rc::Rc, - sync::Arc, -}; +use std::cell::RefCell; use discord_sdk::activity::ActivityBuilder; use raylib::prelude::*; @@ -15,18 +74,14 @@ use utilities::{ datastore::StaticGameData, discord::{DiscordConfig, DiscordRpcClient}, game_config::GameConfig, - math::rotate_vector, }; use crate::{ context::GameContext, scenes::build_screen_state_machine, - utilities::{ - non_ref_raylib::HackedRaylibHandle, - shaders::{ - shader::ShaderWrapper, - util::{dynamic_screen_texture::DynScreenTexture, render_texture::render_to_texture}, - }, + utilities::shaders::{ + shader::ShaderWrapper, + util::{dynamic_screen_texture::DynScreenTexture, render_texture::render_to_texture}, }, }; @@ -36,29 +91,26 @@ extern crate thiserror; extern crate serde; mod context; -mod gfx; mod scenes; mod utilities; /// The game entrypoint -pub async fn game_begin() { +pub async fn game_begin() -> Result<(), Box> { // Load the general config for the game let game_config = GameConfig::load( StaticGameData::get("configs/application.json").expect("Failed to load application.json"), - ) - .expect("Could not load general game config data"); + )?; // Set up profiling #[cfg(debug_assertions)] let _puffin_server = - puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT)).unwrap(); + puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT))?; puffin::set_scopes_on(true); // Attempt to connect to a locally running Discord instance for rich presence access let discord_config = DiscordConfig::load( StaticGameData::get("configs/discord.json").expect("Failed to load discord.json"), - ) - .expect("Could not load Discord config data"); + )?; let discord_rpc = match DiscordRpcClient::new(discord_config.app_id, discord_sdk::Subscriptions::ACTIVITY) .await @@ -73,16 +125,14 @@ pub async fn game_begin() { if let Some(rpc) = discord_rpc { rpc.set_rich_presence(ActivityBuilder::default().details("Testing...")) - .await - .unwrap(); + .await?; } // Get the main state machine - let mut game_state_machine = - build_screen_state_machine().expect("Could not init state main state machine"); + let mut game_state_machine = build_screen_state_machine()?; - let mut context; - let mut raylib_thread; + let context; + let raylib_thread; { // Set up FFI access to raylib let (mut rl, thread) = raylib::init() @@ -102,8 +152,7 @@ pub async fn game_begin() { // Create a dynamic texture to draw to for processing by shaders info!("Allocating a resizable texture for the screen"); let mut dynamic_texture = - DynScreenTexture::new(&mut context.renderer.borrow_mut(), &raylib_thread) - .expect("Failed to allocate a screen texture"); + DynScreenTexture::new(&mut context.renderer.borrow_mut(), &raylib_thread)?; // Load the pixel art shader info!("Loading the pixel art shader"); @@ -113,8 +162,7 @@ pub async fn game_begin() { vec!["viewport"], &mut context.renderer.borrow_mut(), &raylib_thread, - ) - .unwrap(); + )?; info!("Starting the render loop"); while !context.renderer.borrow().window_should_close() { @@ -123,15 +171,13 @@ pub async fn game_begin() { puffin::GlobalProfiler::lock().new_frame(); // Update the GPU texture that we draw to. This handles screen resizing and some other stuff - dynamic_texture - .update(&mut context.renderer.borrow_mut(), &raylib_thread) - .unwrap(); + dynamic_texture.update(&mut context.renderer.borrow_mut(), &raylib_thread)?; - // Switch into draw mode (using unsafe code here to avoid borrow checker hell) + // Switch into draw mode the unsafe way (using unsafe code here to avoid borrow checker hell) + #[allow(unsafe_code)] unsafe { raylib::ffi::BeginDrawing(); } - // let mut d = rl.begin_drawing(&thread); // Fetch the screen size once to work with in render code let screen_size = Vector2::new( @@ -140,7 +186,7 @@ pub async fn game_begin() { ); // Update the pixel shader to correctly handle the screen size - pixel_shader.set_variable("viewport", screen_size).unwrap(); + pixel_shader.set_variable("viewport", screen_size)?; // Render the game via the pixel shader render_to_texture(&mut dynamic_texture, || { @@ -148,7 +194,6 @@ pub async fn game_begin() { puffin::profile_scope!("internal_shaded_render"); // Run a state machine iteration - // let x = (context.renderer, context); let result = game_state_machine.run(&context); if let Err(err) = result { @@ -167,8 +212,10 @@ pub async fn game_begin() { ); // We MUST end draw mode + #[allow(unsafe_code)] unsafe { raylib::ffi::EndDrawing(); } } + Ok(()) } diff --git a/game/src/scenes/fsm_error_screen.rs b/game/src/scenes/fsm_error_screen.rs index 1e80672..dc79b45 100644 --- a/game/src/scenes/fsm_error_screen.rs +++ b/game/src/scenes/fsm_error_screen.rs @@ -4,7 +4,10 @@ use dirty_fsm::{Action, ActionFlag}; use raylib::{color::Color, prelude::RaylibDraw, RaylibHandle}; use tracing::{debug, error, info, trace}; -use crate::{context::GameContext, gfx::render_layer::ScreenSpaceRender, utilities::non_ref_raylib::HackedRaylibHandle}; +use crate::{ + context::GameContext, + utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::ScreenSpaceRender}, +}; use super::{Scenes, ScreenError}; diff --git a/game/src/scenes/loading_screen.rs b/game/src/scenes/loading_screen.rs index 692ef62..fa29d3a 100644 --- a/game/src/scenes/loading_screen.rs +++ b/game/src/scenes/loading_screen.rs @@ -1,21 +1,24 @@ use chrono::{DateTime, Utc}; use dirty_fsm::{Action, ActionFlag}; -use crate::{context::GameContext, gfx::render_layer::ScreenSpaceRender}; +use crate::{context::GameContext, utilities::render_layer::ScreenSpaceRender}; use super::{Scenes, ScreenError}; use tracing::{debug, error, info, trace}; +/// Defines how long the loading screen should be displayed. +const LOADING_SCREEN_DURATION_SECONDS: u8 = 3; + #[derive(Debug)] pub struct LoadingScreen { - start_timestamp: Option> + start_timestamp: Option>, } impl LoadingScreen { /// Construct a new LoadingScreen pub fn new() -> Self { Self { - start_timestamp: None + start_timestamp: None, } } } @@ -42,7 +45,19 @@ impl Action for LoadingScreen { ) -> Result, ScreenError> { trace!("execute() called on LoadingScreen"); self.render_screen_space(&mut context.renderer.borrow_mut()); - Ok(ActionFlag::Continue) + + // Keep rendering until we pass the loading screen duration + if let Some(start_timestamp) = self.start_timestamp { + let duration = Utc::now().signed_duration_since(start_timestamp); + if duration.num_seconds() >= LOADING_SCREEN_DURATION_SECONDS as i64 { + info!("LoadingScreen duration reached, moving to next screen"); + Ok(ActionFlag::SwitchState(Scenes::FsmErrorScreen)) + } else { + Ok(ActionFlag::Continue) + } + } else { + Ok(ActionFlag::Continue) + } } fn on_finish(&mut self, interrupted: bool) -> Result<(), ScreenError> { @@ -56,7 +71,15 @@ impl Action for LoadingScreen { } impl ScreenSpaceRender for LoadingScreen { - fn render_screen_space(&self, raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle) { - todo!() + fn render_screen_space( + &self, + raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle, + ) { + + // Calculate the loading screen fade in/out value + // This makes the loading screen fade in/out over the duration of the loading screen + + + } } diff --git a/game/src/utilities/math.rs b/game/src/utilities/math.rs index b1a1fde..dfda08d 100644 --- a/game/src/utilities/math.rs +++ b/game/src/utilities/math.rs @@ -1,3 +1,6 @@ +use std::ops::Range; + +use num_traits::{real::Real, Float}; use raylib::math::Vector2; /// Rotate a vector by an angle @@ -7,3 +10,35 @@ pub fn rotate_vector(vector: Vector2, angle_rad: f32) -> Vector2 { y: (vector.y * angle_rad.cos()) + (vector.x * angle_rad.sin()), }; } + +/// Interpolate a value from an input range to an output range while being modified by an exponential curve. **Input value is not checked** +pub fn interpolate_exp_unchecked( + value: T, + input_range: Range, + output_range: Range, + exp: T, +) -> T +where + T: Float, +{ + // Normalize the value as a percentage of the input range + let normalized_value = (value - input_range.start) / (input_range.end - input_range.start); + + // Map the value along an exponential curve as defined by the exponent + let mapped_value = ((normalized_value - T::one()).powf(exp) * -T::one()) + T::one(); + + // Return the value mapped to the output range + (mapped_value * (output_range.end - output_range.start)) + output_range.start +} + +/// Interpolate a value from an input range to an output range while being modified by an exponential curve. **Input value is clamped** +pub fn interpolate_exp(value: T, input_range: Range, output_range: Range, exp: T) -> T +where + T: Float, +{ + // Clamp the value to the input range + let clamped_value = value.max(input_range.start).min(input_range.end); + + // Interpolate the value + interpolate_exp_unchecked(clamped_value, input_range, output_range, exp) +} diff --git a/game/src/utilities/mod.rs b/game/src/utilities/mod.rs index 9a6ab4e..8b856fc 100644 --- a/game/src/utilities/mod.rs +++ b/game/src/utilities/mod.rs @@ -4,3 +4,4 @@ pub mod game_config; pub mod math; pub mod shaders; pub mod non_ref_raylib; +pub mod render_layer; diff --git a/game/src/gfx/render_layer.rs b/game/src/utilities/render_layer.rs similarity index 100% rename from game/src/gfx/render_layer.rs rename to game/src/utilities/render_layer.rs diff --git a/game/src/utilities/shaders/shader.rs b/game/src/utilities/shaders/shader.rs index f938463..d7d7e51 100644 --- a/game/src/utilities/shaders/shader.rs +++ b/game/src/utilities/shaders/shader.rs @@ -69,6 +69,9 @@ impl ShaderWrapper { // Create connections between CPU and GPU let mut variables = HashMap::new(); for variable_name in variable_names { + + // I know what I'm doing here. We can skip this error + #[allow(unsafe_code)] variables.insert(variable_name.to_string(), unsafe { raylib::ffi::GetShaderLocation(*shader, CString::new(variable_name)?.as_ptr()) }); diff --git a/game/src/utilities/shaders/util/render_texture.rs b/game/src/utilities/shaders/util/render_texture.rs index 05bc55e..d92bb25 100644 --- a/game/src/utilities/shaders/util/render_texture.rs +++ b/game/src/utilities/shaders/util/render_texture.rs @@ -1,6 +1,7 @@ use raylib::ffi::RenderTexture; /// Renders everything in the draw function to a texture +#[allow(unsafe_code)] pub fn render_to_texture(texture: &mut RenderTexture, draw_fn: Func) where Func: FnOnce() { puffin::profile_function!(); unsafe { diff --git a/wrapper/src/main.rs b/wrapper/src/main.rs index 0251307..3c5985f 100644 --- a/wrapper/src/main.rs +++ b/wrapper/src/main.rs @@ -6,5 +6,5 @@ async fn main() { tracing_subscriber::fmt::init(); // Start the game - game_begin().await; + game_begin().await.unwrap(); }