From 31c76f4f3014ef88d53bed0a107e2f73c8d472b5 Mon Sep 17 00:00:00 2001 From: darkfi Date: Mon, 22 Dec 2025 15:44:13 -0300 Subject: [PATCH] android: add platform config to set `sleep_interval_ms` (an FPS setting) when blocking_event_loop is enabled. Currently in my app I have a task scheduling updates continuously but this introduces latency and uneeded cpu usage. Instead we can make the receiver optionally call .recv_timeout() inside the main event loop. In fact all native platforms in miniquad should support `sleep_interval_ms` by default instead of continuous drawing. --- src/conf.rs | 9 +++++++++ src/native/android.rs | 32 +++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/conf.rs b/src/conf.rs index a7a3d407..13cdc754 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -152,6 +152,14 @@ pub struct Platform { /// [`schedule_update`]: super::window::schedule_update pub blocking_event_loop: bool, + /// When `blocking_event_loop` is enabled, we can set this value to periodically + /// wakeup the `update()` and `draw()` functions. This reduces CPU usage vs + /// continuously drawing without having to continuously schedule updates that can + /// choke the receiver queue. + /// + /// Currently supported only on Android. + pub sleep_interval_ms: Option, + /// If `true`, the framebuffer includes an alpha channel. /// Currently supported only on Android. /// @@ -183,6 +191,7 @@ impl Default for Platform { apple_gfx_api: AppleGfxApi::default(), webgl_version: WebGLVersion::default(), blocking_event_loop: false, + sleep_interval_ms: None, swap_interval: None, framebuffer_alpha: false, wayland_decorations: WaylandDecorations::default(), diff --git a/src/native/android.rs b/src/native/android.rs index b417b522..3fae9bb0 100644 --- a/src/native/android.rs +++ b/src/native/android.rs @@ -6,7 +6,7 @@ use crate::{ }, }; -use std::{cell::RefCell, sync::mpsc, thread}; +use std::{cell::RefCell, sync::mpsc, thread, time::Duration}; pub use crate::native::gl::{self, *}; @@ -480,14 +480,23 @@ where }, }; + let rx_timeout = conf + .platform + .sleep_interval_ms + .map(|sleep| Duration::from_millis(sleep as u64)); + while !s.quit { let block_on_wait = conf.platform.blocking_event_loop && !s.update_requested; if block_on_wait { - let res = rx.recv(); - - if let Ok(msg) = res { - s.process_message(msg); + // We don't need to loop here because the loop above consumes all + // available messages. Instead we are going to block until receiving here. + + match rx_recv(&rx, rx_timeout) { + Ok(msg) => s.process_message(msg), + // Timeout so time to do periodic update() + Err(mpsc::RecvTimeoutError::Timeout) => s.update_requested = true, + Err(mpsc::RecvTimeoutError::Disconnected) => panic!(), } } else { // process all the messages from the main thread @@ -515,6 +524,19 @@ where }); } +/// Adds a call to Receiver as if there was a `.recv_timeout_opt(timeout)` +/// where the `timeout` arg is optional. +fn rx_recv( + rx: &mpsc::Receiver, + timeout: Option, +) -> Result { + match timeout { + Some(timeout) => rx.recv_timeout(timeout), + // No timeout specified so just do a normal blocking recv() + None => rx.recv().map_err(|_| mpsc::RecvTimeoutError::Disconnected), + } +} + #[no_mangle] extern "C" fn jni_on_load(vm: *mut std::ffi::c_void) { unsafe {