1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
/// You can use the `fps_gameloop!` macro to avoid writing a lot of boilerplate code. Take this block of code from a program written with gemini
/// ```rust,no_run
/// # use gemini_engine::{elements::view::{View, ColChar, Wrapping, Vec2D}, elements3d::{Viewport, Mesh3D, Transform3D, DisplayMode}, gameloop};
/// # let mut view = View::new(0, 0, ColChar::BACKGROUND);
/// # let viewport = Viewport::new(Transform3D::default(), 0.0, Vec2D::ZERO);
/// let mut cube = Mesh3D::default_cube();
///
/// let FPS = 30.0;
/// let mut frame_skip = false;
/// loop {
/// let now = gameloop::Instant::now();
///
/// // Logic
/// cube.transform.rotation.y += 0.1;
///
/// if frame_skip {
/// frame_skip = false;
/// } else {
/// view.clear();
/// // Rendering
/// view.blit(&viewport.render(vec![&cube], DisplayMode::Solid), Wrapping::Ignore);
/// view.display_render().unwrap();
/// }
/// let elapsed = now.elapsed();
/// frame_skip = gameloop::sleep_fps(FPS, Some(elapsed));
/// }
/// ```
/// There's a lot of very repetitive code here. That's where this macro comes in. Here is the same block of code, rewritten with `fps_gameloop!`:
/// ```rust,no_run
/// # use gemini_engine::{elements::view::{View, ColChar, Wrapping, Vec2D}, elements3d::{Viewport, Mesh3D, Transform3D, DisplayMode}, fps_gameloop};
/// # let mut view = View::new(0, 0, ColChar::BACKGROUND);
/// # let viewport = Viewport::new(Transform3D::default(), 0.0, Vec2D::ZERO);
/// let mut cube = Mesh3D::default_cube();
///
/// let FPS = 30.0;
/// fps_gameloop!(
/// {
/// cube.transform.rotation.y += 0.1;
/// },
/// {
/// view.clear();
/// view.blit(&viewport.render(vec![&cube], DisplayMode::Solid), Wrapping::Ignore);
/// view.display_render().unwrap();
/// },
/// FPS
/// );
/// ```
/// The code is now a lot less cluttered. This macro accepts three fragments (and an optional fourth fragment). A logic block fragment (contained inside `{}` brackets) for code that should run every single frame, a render block fragment for code related to displaying to the terminal (all plots, blits and renders) and a `f32` fragment representing the desired frames per second. The fourth optional fragment is to be a function that accepts a [`Duration`](std::time::Duration) parameter representing the time taken to render everything and a `bool` parameter representing whether the last frame was skipped or not. It can be used to, say, print debug info. Here's an example:
/// ```rust,no_run
/// # use gemini_engine::{fps_gameloop, gameloop};
/// fps_gameloop!(
/// // -- other f ields --
/// # {}, {}, 0.0,
/// |elapsed: gameloop::Duration, frame_skip: bool| {
/// println!(
/// "Elapsed: {:.2?}µs | Frame skip: {}",
/// elapsed.as_micros(),
/// frame_skip
/// );
/// }
/// );
#[macro_export]
macro_rules! fps_gameloop {
($logic:block, $render:block, $fps:expr) => {
fps_gameloop!($logic, $render, $fps, |_, _| ());
};
($logic:block, $render:block, $fps:expr, $handle_elapsed:expr) => {
use gemini_engine::gameloop::{sleep_fps, Instant};
let mut frame_skip = false;
loop {
let now = Instant::now();
$logic; // Logic
match frame_skip {
true => frame_skip = false,
false => {
$render; // Rendering
}
}
// Debug info and such
($handle_elapsed)(now.elapsed(), frame_skip);
let elapsed = now.elapsed();
frame_skip = sleep_fps($fps, Some(elapsed));
}
};
}