core: Clean up tick/render loop

Don't call `render` from `Player::tick`; instead, require the
frontends to explicitly call `render` when they wish to redraw.
The frontend can query `Player::needs_render` to see if the stage
is dirty and needs a redraw. Update desktop and web to use this
new method.

This fits better with the newer winit event loop model, which
requires explicitly calling `request_redraw`, and should avoid
spurious renders.
This commit is contained in:
Mike Welsh 2020-05-02 04:25:21 -07:00
parent 6339c74d67
commit f09bd8c079
3 changed files with 54 additions and 28 deletions

View File

@ -103,6 +103,7 @@ pub struct Player {
swf: Arc<SwfMovie>, swf: Arc<SwfMovie>,
is_playing: bool, is_playing: bool,
needs_render: bool,
audio: Audio, audio: Audio,
renderer: Renderer, renderer: Renderer,
@ -168,6 +169,7 @@ impl Player {
swf: movie.clone(), swf: movie.clone(),
is_playing: false, is_playing: false,
needs_render: true,
background_color: Color { background_color: Color {
r: 255, r: 255,
@ -264,8 +266,6 @@ impl Player {
self.global_time += dt as u64; self.global_time += dt as u64;
let frame_time = 1000.0 / self.frame_rate; let frame_time = 1000.0 / self.frame_rate;
let needs_render = self.frame_accumulator >= frame_time;
const MAX_FRAMES_PER_TICK: u32 = 5; // Sanity cap on frame tick. const MAX_FRAMES_PER_TICK: u32 = 5; // Sanity cap on frame tick.
let mut frame = 0; let mut frame = 0;
while frame < MAX_FRAMES_PER_TICK && self.frame_accumulator >= frame_time { while frame < MAX_FRAMES_PER_TICK && self.frame_accumulator >= frame_time {
@ -280,10 +280,6 @@ impl Player {
self.frame_accumulator = 0.0; self.frame_accumulator = 0.0;
} }
if needs_render {
self.render();
}
self.audio.tick(); self.audio.tick();
} }
} }
@ -314,6 +310,10 @@ impl Player {
self.is_playing = v; self.is_playing = v;
} }
pub fn needs_render(&self) -> bool {
self.needs_render
}
pub fn movie_width(&self) -> u32 { pub fn movie_width(&self) -> u32 {
self.movie_width self.movie_width
} }
@ -333,7 +333,7 @@ impl Player {
} }
pub fn handle_event(&mut self, event: PlayerEvent) { pub fn handle_event(&mut self, event: PlayerEvent) {
let mut needs_render = false; let mut needs_render = self.needs_render;
// Update mouse position from mouse events. // Update mouse position from mouse events.
if let PlayerEvent::MouseMove { x, y } if let PlayerEvent::MouseMove { x, y }
@ -442,10 +442,7 @@ impl Player {
Self::run_actions(avm, context); Self::run_actions(avm, context);
}); });
self.is_mouse_down = is_mouse_down; self.is_mouse_down = is_mouse_down;
if needs_render { self.needs_render = needs_render;
// Update display after mouse events.
self.render();
}
} }
/// Update dragged object, if any. /// Update dragged object, if any.
@ -568,7 +565,8 @@ impl Player {
for mut level in levels { for mut level in levels {
level.run_frame(avm, update_context); level.run_frame(avm, update_context);
} }
}) });
self.needs_render = true;
} }
pub fn render(&mut self) { pub fn render(&mut self) {
@ -608,6 +606,7 @@ impl Player {
self.renderer.draw_letterbox(self.letterbox); self.renderer.draw_letterbox(self.letterbox);
self.renderer.end_frame(); self.renderer.end_frame();
self.needs_render = false;
} }
pub fn audio(&self) -> &Audio { pub fn audio(&self) -> &Audio {

View File

@ -98,12 +98,31 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let mut mouse_pos = PhysicalPosition::new(0.0, 0.0); let mut mouse_pos = PhysicalPosition::new(0.0, 0.0);
let mut time = Instant::now(); let mut time = Instant::now();
let mut next_frame_time = Instant::now();
loop { loop {
// Poll UI events // Poll UI events
event_loop.run(move |event, _window_target, control_flow| { event_loop.run(move |event, _window_target, control_flow| {
*control_flow = ControlFlow::Wait;
match event { match event {
winit::event::Event::LoopDestroyed => return, winit::event::Event::LoopDestroyed => return,
// Core loop
winit::event::Event::MainEventsCleared => {
let new_time = Instant::now();
let dt = new_time.duration_since(time).as_micros();
if dt > 0 {
time = new_time;
let mut player_lock = player.lock().unwrap();
player_lock.tick(dt as f64 / 1000.0);
next_frame_time = new_time + player_lock.time_til_next_frame();
if player_lock.needs_render() {
window.request_redraw();
}
}
}
// Render
winit::event::Event::RedrawRequested(_) => player.lock().unwrap().render(),
winit::event::Event::WindowEvent { event, .. } => match event { winit::event::Event::WindowEvent { event, .. } => match event {
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
let mut player_lock = player.lock().unwrap(); let mut player_lock = player.lock().unwrap();
@ -111,6 +130,7 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
player_lock player_lock
.renderer_mut() .renderer_mut()
.set_viewport_dimensions(size.width, size.height); .set_viewport_dimensions(size.width, size.height);
window.request_redraw();
} }
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
let mut player_lock = player.lock().unwrap(); let mut player_lock = player.lock().unwrap();
@ -120,6 +140,9 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
y: position.y, y: position.y,
}; };
player_lock.handle_event(event); player_lock.handle_event(event);
if player_lock.needs_render() {
window.request_redraw();
}
} }
WindowEvent::MouseInput { WindowEvent::MouseInput {
button: MouseButton::Left, button: MouseButton::Left,
@ -139,10 +162,16 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
} }
}; };
player_lock.handle_event(event); player_lock.handle_event(event);
if player_lock.needs_render() {
window.request_redraw();
}
} }
WindowEvent::CursorLeft { .. } => { WindowEvent::CursorLeft { .. } => {
let mut player_lock = player.lock().unwrap(); let mut player_lock = player.lock().unwrap();
player_lock.handle_event(ruffle_core::PlayerEvent::MouseLeft) player_lock.handle_event(ruffle_core::PlayerEvent::MouseLeft);
if player_lock.needs_render() {
window.request_redraw();
}
} }
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput { .. } | WindowEvent::ReceivedCharacter(_) => { WindowEvent::KeyboardInput { .. } | WindowEvent::ReceivedCharacter(_) => {
@ -154,6 +183,9 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
.handle_event(event) .handle_event(event)
{ {
player_lock.handle_event(event); player_lock.handle_event(event);
if player_lock.needs_render() {
window.request_redraw();
}
} }
} }
_ => (), _ => (),
@ -166,16 +198,8 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
} }
// After polling events, sleep the event loop until the next event or the next frame. // After polling events, sleep the event loop until the next event or the next frame.
if *control_flow == ControlFlow::Wait { if *control_flow != ControlFlow::Exit {
let new_time = Instant::now(); *control_flow = ControlFlow::WaitUntil(next_frame_time);
let dt = new_time.duration_since(time).as_micros();
if dt > 0 {
time = new_time;
player.lock().unwrap().tick(dt as f64 / 1000.0);
}
*control_flow =
ControlFlow::WaitUntil(new_time + player.lock().unwrap().time_til_next_frame());
} }
}); });
} }

View File

@ -414,7 +414,9 @@ impl Ruffle {
0.0 0.0
}; };
instance.core.lock().unwrap().tick(dt); let mut core_lock = instance.core.lock().unwrap();
core_lock.tick(dt);
let mut needs_render = core_lock.needs_render();
// Check for canvas resize. // Check for canvas resize.
let canvas_width = instance.canvas.client_width(); let canvas_width = instance.canvas.client_width();
@ -439,16 +441,17 @@ impl Ruffle {
instance.canvas.set_width(viewport_width); instance.canvas.set_width(viewport_width);
instance.canvas.set_height(viewport_height); instance.canvas.set_height(viewport_height);
let mut core_lock = instance.core.lock().unwrap();
core_lock.set_viewport_dimensions(viewport_width, viewport_height); core_lock.set_viewport_dimensions(viewport_width, viewport_height);
core_lock core_lock
.renderer_mut() .renderer_mut()
.set_viewport_dimensions(viewport_width, viewport_height); .set_viewport_dimensions(viewport_width, viewport_height);
// Force a re-render if we resize. // Force a re-render if we resize.
core_lock.render(); needs_render = true;
}
drop(core_lock); if needs_render {
core_lock.render();
} }
// Request next animation frame. // Request next animation frame.