use crate::event::Event;
use crate::image::{Image, SharedImage};
use crate::input::Input;
use crate::key::{KEY_0, KEY_1, KEY_2, KEY_3, KEY_ALT, KEY_RETURN};
use crate::platform::Platform;
use crate::profiler::Profiler;
use crate::resource::Resource;
use crate::settings::{BACKGROUND_COLOR, MAX_ELAPSED_MS, NUM_MEASURE_FRAMES};
use crate::types::Key;
use crate::utils::simplify_string;
use crate::NUM_COLORS;
pub trait PyxelCallback {
fn update(&mut self);
fn draw(&mut self);
}
pub struct System {
one_frame_ms: f64,
next_update_ms: f64,
frame_count: u32,
quit_key: Key,
is_paused: bool,
fps_profiler: Profiler,
update_profiler: Profiler,
draw_profiler: Profiler,
enable_perf_monitor: bool,
}
unsafe_singleton!(System);
impl System {
pub fn init(fps: u32, quit_key: Key) {
Self::set_instance(Self {
one_frame_ms: 1000.0 / fps as f64,
next_update_ms: 0.0,
frame_count: 0,
quit_key,
is_paused: false,
fps_profiler: Profiler::new(NUM_MEASURE_FRAMES),
update_profiler: Profiler::new(NUM_MEASURE_FRAMES),
draw_profiler: Profiler::new(NUM_MEASURE_FRAMES),
enable_perf_monitor: false,
});
}
fn process_frame(&mut self, callback: &mut dyn PyxelCallback) {
let tick_count = Platform::instance().tick_count();
let elapsed_ms = tick_count as f64 - self.next_update_ms;
if elapsed_ms < 0.0 {
return;
}
if self.frame_count == 0 {
self.next_update_ms = tick_count as f64 + self.one_frame_ms;
} else {
self.fps_profiler.end(tick_count);
self.fps_profiler.start(tick_count);
let update_count: u32;
if elapsed_ms > MAX_ELAPSED_MS as f64 {
update_count = 1;
self.next_update_ms = Platform::instance().tick_count() as f64 + self.one_frame_ms;
} else {
update_count = (elapsed_ms / self.one_frame_ms) as u32 + 1;
self.next_update_ms += self.one_frame_ms * update_count as f64;
}
for _ in 1..update_count {
self.update_frame(Some(callback));
self.frame_count += 1;
}
}
self.update_frame(Some(callback));
self.draw_frame(Some(callback));
self.frame_count += 1;
}
#[cfg(not(target_os = "emscripten"))]
fn process_frame_for_flip(&mut self) {
self.update_profiler.end(Platform::instance().tick_count());
self.draw_frame(None);
self.frame_count += 1;
let mut tick_count;
let mut elapsed_ms;
loop {
tick_count = Platform::instance().tick_count();
elapsed_ms = tick_count as f64 - self.next_update_ms;
let wait_ms = self.next_update_ms - Platform::instance().tick_count() as f64;
if wait_ms > 0.0 {
Platform::instance().sleep((wait_ms / 2.0) as u32);
} else {
break;
}
}
self.fps_profiler.end(tick_count);
self.fps_profiler.start(tick_count);
if elapsed_ms > MAX_ELAPSED_MS as f64 {
self.next_update_ms = Platform::instance().tick_count() as f64 + self.one_frame_ms;
} else {
self.next_update_ms += self.one_frame_ms;
}
self.update_frame(None);
}
fn update_frame(&mut self, callback: Option<&mut dyn PyxelCallback>) {
self.update_profiler
.start(Platform::instance().tick_count());
self.process_events();
if self.is_paused {
return;
}
self.check_special_input();
if let Some(callback) = callback {
callback.update();
self.update_profiler.end(Platform::instance().tick_count());
}
}
fn process_events(&mut self) {
Input::instance().reset_input_states();
while let Some(event) = Platform::instance().poll_event() {
match event {
Event::Quit => {
Platform::instance().quit();
}
Event::Shown => {
self.is_paused = false;
Platform::instance().resume_audio();
}
Event::Hidden => {
self.is_paused = true;
Platform::instance().pause_audio();
}
_ => {
if !self.is_paused {
Input::instance().process_input_event(event, self.frame_count);
}
}
}
}
}
fn check_special_input(&mut self) {
if crate::btn(KEY_ALT) {
if crate::btnp(KEY_RETURN, None, None) {
crate::fullscreen(!crate::is_fullscreen());
}
if crate::btnp(KEY_0, None, None) {
self.enable_perf_monitor = !self.enable_perf_monitor;
}
if crate::btnp(KEY_1, None, None) {
crate::screenshot(None);
}
if crate::btnp(KEY_2, None, None) {
crate::reset_capture();
}
if crate::btnp(KEY_3, None, None) {
crate::screencast(None);
}
}
if crate::btnp(self.quit_key, None, None) {
crate::quit();
}
}
fn draw_frame(&mut self, callback: Option<&mut dyn PyxelCallback>) {
if self.is_paused {
return;
}
self.draw_profiler.start(Platform::instance().tick_count());
if let Some(callback) = callback {
callback.draw();
}
self.draw_perf_monitor();
self.draw_cursor();
Platform::instance().render_screen(
&crate::screen().lock().canvas.data,
&*crate::colors().lock(),
BACKGROUND_COLOR,
);
Resource::instance().capture_screen(
&crate::screen().lock().canvas.data,
&crate::colors().lock(),
self.frame_count,
);
self.draw_profiler.end(Platform::instance().tick_count());
}
fn draw_perf_monitor(&mut self) {
if !self.enable_perf_monitor {
return;
}
let screen = crate::screen();
let mut screen = screen.lock();
let clip_rect = screen.canvas.clip_rect;
let camera_x = screen.canvas.camera_x;
let camera_y = screen.canvas.camera_y;
let palette1 = screen.palette[1];
let palette2 = screen.palette[2];
screen.clip0();
screen.camera0();
screen.pal(1, NUM_COLORS as u8 + 1);
screen.pal(2, NUM_COLORS as u8 + 9);
let fps = format!("{:.*}", 2, self.fps_profiler.average_fps());
screen.text(1.0, 0.0, &fps, 1);
screen.text(0.0, 0.0, &fps, 2);
let update_time = format!("{:.*}", 2, self.update_profiler.average_time());
screen.text(1.0, 6.0, &update_time, 1);
screen.text(0.0, 6.0, &update_time, 2);
let draw_time = format!("{:.*}", 2, self.draw_profiler.average_time());
screen.text(1.0, 12.0, &draw_time, 1);
screen.text(0.0, 12.0, &draw_time, 2);
screen.canvas.clip_rect = clip_rect;
screen.canvas.camera_x = camera_x;
screen.canvas.camera_y = camera_y;
screen.pal(1, palette1);
screen.pal(2, palette2);
}
fn draw_cursor(&mut self) {
let x = crate::mouse_x();
let y = crate::mouse_y();
Platform::instance().show_cursor(
x < 0 || x >= crate::width() as i32 || y < 0 || y >= crate::height() as i32,
);
if !Input::instance().is_mouse_visible() {
return;
}
let width = crate::cursor().lock().width() as i32;
let height = crate::cursor().lock().height() as i32;
if x <= -width || x >= crate::width() as i32 || y <= -height || y >= crate::height() as i32
{
return;
}
let screen = crate::screen();
let mut screen = screen.lock();
let clip_rect = screen.canvas.clip_rect;
let camera_x = screen.canvas.camera_x;
let camera_y = screen.canvas.camera_y;
let palette = screen.palette;
screen.clip0();
screen.camera0();
for i in 0..NUM_COLORS {
screen.pal(i as u8, (NUM_COLORS + i) as u8);
}
screen.blt(
x as f64,
y as f64,
crate::cursor(),
0.0,
0.0,
width as f64,
height as f64,
Some(0),
);
screen.canvas.clip_rect = clip_rect;
screen.canvas.camera_x = camera_x;
screen.canvas.camera_y = camera_y;
screen.palette = palette;
}
}
pub fn width() -> u32 {
Platform::instance().screen_width()
}
pub fn height() -> u32 {
Platform::instance().screen_height()
}
pub fn frame_count() -> u32 {
System::instance().frame_count
}
pub fn title(title: &str) {
Platform::instance().set_title(title);
}
pub fn icon(data_str: &[&str], scale: u32) {
let width = simplify_string(data_str[0]).len() as u32;
let height = data_str.len() as u32;
let image = Image::new(width, height);
image.lock().set(0, 0, data_str);
Platform::instance().set_icon(&image.lock().canvas.data, &*crate::colors().lock(), scale);
}
pub fn is_fullscreen() -> bool {
Platform::instance().is_fullscreen()
}
pub fn fullscreen(is_fullscreen: bool) {
Platform::instance().set_fullscreen(is_fullscreen);
}
pub fn run<T: PyxelCallback>(mut callback: T) {
Platform::instance().run(move || {
System::instance().process_frame(&mut callback);
});
}
pub fn show() {
struct App {
image: SharedImage,
}
impl PyxelCallback for App {
fn update(&mut self) {}
fn draw(&mut self) {
crate::screen().lock().blt(
0.0,
0.0,
self.image.clone(),
0.0,
0.0,
crate::width() as f64,
crate::height() as f64,
None,
);
}
}
let image = Image::new(crate::width(), crate::height());
image.lock().blt(
0.0,
0.0,
crate::screen(),
0.0,
0.0,
crate::width() as f64,
crate::height() as f64,
None,
);
crate::run(App { image });
}
pub fn flip() {
#[cfg(target_os = "emscripten")]
panic!("flip is not supported on Web");
#[cfg(not(target_os = "emscripten"))]
System::instance().process_frame_for_flip();
}
pub fn quit() {
Platform::instance().quit();
}