use crate::{context::Context, event::timer_update, log::init_log, GAME_FRAME, LOGO_FRAME};
use log::info;
use std::{
io,
time::{Duration, Instant},
};
pub trait Model {
fn init(&mut self, ctx: &mut Context);
fn update(&mut self, ctx: &mut Context, dt: f32) {
if ctx.stage <= LOGO_FRAME {
return;
}
timer_update();
self.handle_event(ctx, dt);
self.handle_timer(ctx, dt);
self.handle_input(ctx, dt);
self.handle_auto(ctx, dt);
}
fn handle_timer(&mut self, ctx: &mut Context, dt: f32);
fn handle_event(&mut self, ctx: &mut Context, dt: f32);
fn handle_input(&mut self, ctx: &mut Context, dt: f32);
fn handle_auto(&mut self, ctx: &mut Context, dt: f32);
}
pub trait Render {
type Model: Model;
fn init(&mut self, ctx: &mut Context, m: &mut Self::Model);
fn update(&mut self, ctx: &mut Context, m: &mut Self::Model, dt: f32) {
self.handle_event(ctx, m, dt);
self.handle_timer(ctx, m, dt);
self.draw(ctx, m, dt);
}
fn handle_event(&mut self, ctx: &mut Context, model: &mut Self::Model, dt: f32);
fn handle_timer(&mut self, ctx: &mut Context, model: &mut Self::Model, dt: f32);
fn draw(&mut self, ctx: &mut Context, model: &mut Self::Model, dt: f32);
}
pub struct Game<M, R>
where
M: Model,
R: Render<Model = M>,
{
pub context: Context,
pub model: M,
pub render: R,
}
impl<M, R> Game<M, R>
where
M: Model,
R: Render<Model = M>,
{
pub fn new(m: M, r: R) -> Self {
let config = crate::get_game_config();
let name = &config.game_name;
let project_path = &config.project_path;
let ctx = Context::new();
init_log(
log::LevelFilter::Info,
&format!("log{}{}.log", std::path::MAIN_SEPARATOR, name),
);
info!("{}(rust_pixel) start...{:?}", name, project_path);
Self {
context: ctx,
model: m,
render: r,
}
}
pub fn run(&mut self) -> io::Result<()> {
info!("Begin run...");
let mut last_tick = Instant::now();
let tick_rate = Duration::from_nanos(1_000_000_000 / GAME_FRAME as u64);
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_nanos(100));
if self
.context
.adapter
.poll_event(timeout, &mut self.context.input_events)
{
return Ok(());
}
let et = last_tick.elapsed();
if et >= tick_rate {
let dt = et.as_secs() as f32 + et.subsec_nanos() as f32 / 1_000_000_000.0;
self.on_tick(dt);
last_tick = Instant::now();
}
}
}
pub fn on_tick(&mut self, dt: f32) {
self.context.stage += 1;
self.model.update(&mut self.context, dt);
self.render.update(&mut self.context, &mut self.model, dt);
}
pub fn init(&mut self) {
info!("Init game...");
#[cfg(target_arch = "wasm32")]
web_sys::console::log_1(&"[game.init] before model.init()".into());
self.model.init(&mut self.context);
#[cfg(target_arch = "wasm32")]
web_sys::console::log_1(&"[game.init] after model.init(), before render.init()".into());
self.render.init(&mut self.context, &mut self.model);
#[cfg(target_arch = "wasm32")]
web_sys::console::log_1(&"[game.init] after render.init() - done!".into());
}
}
#[macro_export]
macro_rules! only_terminal_mode {
() => {
#[cfg(graphics_mode)]
{
println!("Run in terminal only...");
std::process::exit(0);
}
};
}
#[macro_export]
macro_rules! only_graphics_mode {
() => {
#[cfg(not(graphics_mode))]
{
println!("Run in graphics only...");
std::process::exit(0);
}
};
}