summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2024-07-30 21:52:11 +1200
committerBen Bridle <ben@derelict.engineering>2024-07-30 21:52:11 +1200
commit820666fbbb10e4f8a288c738fa97af402eea9c06 (patch)
treee15e453fa899a47dbc03b2946eba9933e3a2b3a5
parente55907ff1c3067ed3326091a477698ffc8db0026 (diff)
downloadbedrock-pc-820666fbbb10e4f8a288c738fa97af402eea9c06.zip
Rework main loop timings
The program is vastly more efficient to run, consuming only 1.5% CPU instead of 13% during testing on Windows 10. Input events are handled four times as frequently now, which makes for smoother lines when drawing in Cobalt.
-rw-r--r--src/emulator.rs81
1 files changed, 51 insertions, 30 deletions
diff --git a/src/emulator.rs b/src/emulator.rs
index 47d4bd6..606704d 100644
--- a/src/emulator.rs
+++ b/src/emulator.rs
@@ -7,7 +7,8 @@ use std::cmp::{min, max};
use std::time::*;
use std::thread::sleep;
-const FRAME: Duration = Duration::from_micros(16666);
+const FRAME: Duration = Duration::from_micros(4_000);
+const RENDER_WAIT: Duration = Duration::from_micros(500_000);
const LINE_HEIGHT: f64 = 20.0;
macro_rules! u16 { ($u32:expr) => { $u32.try_into().unwrap_or(u16::MAX) }; }
@@ -18,8 +19,9 @@ pub struct BedrockEmulator {
initialising: bool,
sleeping: bool,
pixel_scale: u32,
- process_mark: Instant,
- frame_mark: Instant,
+ start_of_process: Instant,
+ end_of_process: Instant,
+ end_of_render: Instant,
debug_mark: Instant,
cycles_elapsed: usize,
}
@@ -36,8 +38,9 @@ impl BedrockEmulator {
initialising: true,
sleeping: false,
pixel_scale: 3,
- process_mark: Instant::now(),
- frame_mark: Instant::now(),
+ start_of_process: Instant::now(),
+ end_of_process: Instant::now(),
+ end_of_render: Instant::now(),
debug_mark: Instant::now(),
cycles_elapsed: 0,
}
@@ -187,37 +190,55 @@ impl WindowController for BedrockEmulator {
}
fn on_process(&mut self) {
- // The duration remaining until the next frame is due to be rendered
- let frame_remaining = match self.frame_mark.elapsed() < FRAME {
- true => FRAME.saturating_sub(self.frame_mark.elapsed()),
- false => FRAME,
- };
- // Wait for the initial resize event to come through
+ // Prevent program starting before the window receives an initial size.
if self.initialising {
- sleep(FRAME / 10);
- self.process_mark = Instant::now();
+ sleep(FRAME);
return;
}
- // Sleep for the remainder of the frame, or until a timer expires
- if self.sleeping && !self.vm.dev.can_wake() {
- let sleep_duration = match self.vm.dev.clock.time_to_next_wake() {
- Some(ms) => min(ms, frame_remaining),
- None => frame_remaining,
- };
- sleep(sleep_duration);
- self.process_mark = Instant::now();
- return;
+
+ if self.sleeping {
+ // Pause the processor until the current frame is rendered. This
+ // prevents the current frame from being overdrawn before rendering.
+ if self.vm.dev.screen.dirty {
+ sleep(FRAME);
+ self.end_of_process = Instant::now();
+ return;
+ }
+ // Ensure a minimum delay of FRAME between the start of consecutive
+ // process frames when asleep, unless a timer has expired.
+ let frame_start = self.start_of_process + FRAME;
+ let time_to_frame_start = frame_start.duration_since(Instant::now());
+ if !time_to_frame_start.is_zero() {
+ let time_to_sleep = match self.vm.dev.clock.time_to_next_wake() {
+ Some(ms) => min(ms, time_to_frame_start),
+ None => time_to_frame_start,
+ };
+ sleep(time_to_sleep);
+ self.end_of_process = Instant::now();
+ return;
+ }
+ // Stay asleep if there are no pending wake events.
+ if !self.vm.dev.can_wake() {
+ sleep(FRAME);
+ self.end_of_process = Instant::now();
+ return;
+ }
}
- // Wake from sleep and evaluate the program for the remainder of the frame
+
+ // Run the processor for the remainder of the frame.
+ self.start_of_process = Instant::now();
self.sleeping = false;
- while self.process_mark.elapsed() < frame_remaining {
+ let frame_end = self.end_of_process + FRAME;
+
+ while Instant::now() < frame_end {
if let Some(signal) = self.vm.evaluate(1000) {
match signal {
Signal::Debug(var) => self.debug(var),
Signal::Sleep => {
self.sleeping = true;
- let frame_elapsed = self.process_mark.elapsed();
- sleep(frame_remaining.saturating_sub(frame_elapsed));
+ // Sleep for the remainer of the frame.
+ sleep(frame_end.duration_since(Instant::now()));
+ self.end_of_process = Instant::now();
break;
},
Signal::Halt => {
@@ -230,26 +251,26 @@ impl WindowController for BedrockEmulator {
}
self.vm.dev.stream.flush_local();
self.vm.dev.file.flush_entry();
- self.process_mark = Instant::now();
+ self.end_of_process = Instant::now();
}
fn render_request(&mut self) -> RenderRequest {
if self.vm.dev.screen.dirty {
match self.sleeping {
true => RenderRequest::UPDATE,
- false => match self.frame_mark.elapsed() >= 6 * FRAME {
+ false => match self.end_of_render.elapsed() >= RENDER_WAIT {
true => RenderRequest::UPDATE,
false => RenderRequest::NONE,
}
}
} else {
- self.frame_mark = Instant::now();
+ self.end_of_render = Instant::now();
RenderRequest::NONE
}
}
fn on_render(&mut self, buffer: &mut Buffer, _hint: RenderHint) {
self.vm.dev.screen.render(buffer);
- self.frame_mark = Instant::now();
+ self.end_of_render = Instant::now();
}
}