use crate::compat::HashMap;
use crate::compat::Mutex;
use crate::compat::MutexGuard;
use alloc::collections::VecDeque;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicU64, Ordering};
use core::time::Duration;
#[cfg(not(feature = "mini"))]
use std::sync::Condvar;
#[cfg(not(feature = "mini"))]
use std::sync::OnceLock;
#[cfg(not(feature = "mini"))]
use std::time::Instant;
const DEFAULT_EMBEDDED_TARGET_FPS: u32 = 60;
const MIN_EMBEDDED_TARGET_FPS: u32 = 1;
const MAX_EMBEDDED_TARGET_FPS: u32 = 240;
fn clamp_embedded_target_fps(fps: u32) -> u32 {
fps.clamp(MIN_EMBEDDED_TARGET_FPS, MAX_EMBEDDED_TARGET_FPS)
}
fn frame_interval_for_fps(fps: u32) -> Duration {
Duration::from_nanos(1_000_000_000 / fps as u64)
}
type EmbeddedTaskFn = Box<dyn FnOnce(u64) + Send + 'static>;
struct EmbeddedTask {
id: u64,
label: String,
action: Option<EmbeddedTaskFn>,
}
impl EmbeddedTask {
fn new(id: u64, label: String, action: EmbeddedTaskFn) -> Self {
Self { id, label, action: Some(action) }
}
fn run(mut self, frame_index: u64) {
let _ = self.id;
let _ = self.label;
if let Some(action) = self.action.take() {
action(frame_index);
}
}
}
#[derive(Default)]
struct EmbeddedRuntimeState {
initialized: bool,
running: bool,
target_fps: u32,
windows: HashMap<u64, EmbeddedWindowRecord>,
buttons: HashMap<u64, EmbeddedButtonRecord>,
pending_tasks: VecDeque<EmbeddedTask>,
}
impl EmbeddedRuntimeState {
fn new() -> Self {
Self {
initialized: false,
running: false,
target_fps: DEFAULT_EMBEDDED_TARGET_FPS,
windows: HashMap::new(),
buttons: HashMap::new(),
pending_tasks: VecDeque::new(),
}
}
}
pub(crate) struct EmbeddedEngineShared {
next_widget_id: AtomicU64,
next_task_id: AtomicU64,
frame_count: AtomicU64,
state: Mutex<EmbeddedRuntimeState>,
wake_signal: Condvar,
}
impl EmbeddedEngineShared {
fn new() -> Self {
Self {
next_widget_id: AtomicU64::new(1),
next_task_id: AtomicU64::new(1),
frame_count: AtomicU64::new(0),
state: Mutex::new(EmbeddedRuntimeState::new()),
wake_signal: Condvar::new(),
}
}
fn lock_state(&self) -> MutexGuard<'_, EmbeddedRuntimeState> {
self.state.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn set_target_fps(&self, fps: u32) -> u32 {
let mut state = self.lock_state();
state.target_fps = clamp_embedded_target_fps(fps);
self.wake_signal.notify_all();
state.target_fps
}
fn target_fps(&self) -> u32 {
self.lock_state().target_fps
}
pub(crate) fn init(&self) {
let mut state = self.lock_state();
if state.initialized {
return;
}
state.initialized = true;
}
pub(crate) fn run_loop(&self) {
{
let mut state = self.lock_state();
if state.running {
return;
}
state.running = true;
}
loop {
let frame_start = Instant::now();
let (tasks, target_fps, still_running) = {
let mut state = self.lock_state();
let still_running = state.running;
let target_fps = state.target_fps;
let tasks = state.pending_tasks.drain(..).collect::<Vec<_>>();
(tasks, target_fps, still_running)
};
if !still_running {
break;
}
let frame_index = self.frame_count.fetch_add(1, Ordering::SeqCst) + 1;
for task in tasks {
task.run(frame_index);
}
let frame_interval = frame_interval_for_fps(clamp_embedded_target_fps(target_fps));
let elapsed = frame_start.elapsed();
if elapsed < frame_interval {
let wait_duration = frame_interval - elapsed;
let state = self.lock_state();
if !state.running {
break;
}
let _ = self
.wake_signal
.wait_timeout(state, wait_duration)
.unwrap_or_else(|poisoned| poisoned.into_inner());
}
}
}
pub(crate) fn quit(&self) {
let mut state = self.lock_state();
state.running = false;
state.pending_tasks.clear();
drop(state);
self.wake_signal.notify_all();
}
fn alloc_widget_id(&self) -> u64 {
self.next_widget_id.fetch_add(1, Ordering::SeqCst)
}
pub(crate) fn register_window(
&self,
title: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> u64 {
let window_id = self.alloc_widget_id();
let mut state = self.lock_state();
state.windows.insert(
window_id,
EmbeddedWindowRecord { id: window_id, title: title.to_string(), x, y, width, height },
);
window_id
}
pub(crate) fn register_button(
&self,
parent: u64,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> u64 {
let button_id = self.alloc_widget_id();
let mut state = self.lock_state();
state.buttons.insert(
button_id,
EmbeddedButtonRecord {
id: button_id,
parent,
text: text.to_string(),
x,
y,
width,
height,
},
);
button_id
}
fn submit_task<F>(&self, label: String, action: F) -> u64
where
F: FnOnce(u64) + Send + 'static,
{
let task_id = self.next_task_id.fetch_add(1, Ordering::SeqCst);
let mut state = self.lock_state();
state.pending_tasks.push_back(EmbeddedTask::new(task_id, label, Box::new(action)));
drop(state);
self.wake_signal.notify_all();
task_id
}
fn stats(&self) -> EmbeddedEngineStats {
let state = self.lock_state();
EmbeddedEngineStats {
initialized: state.initialized,
running: state.running,
frame_count: self.frame_count.load(Ordering::SeqCst),
pending_task_count: state.pending_tasks.len(),
window_count: state.windows.len(),
button_count: state.buttons.len(),
target_fps: state.target_fps,
}
}
}
#[derive(Clone, Debug)]
pub struct EmbeddedWindowRecord {
pub id: u64,
pub title: String,
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug)]
pub struct EmbeddedButtonRecord {
pub id: u64,
pub parent: u64,
pub text: String,
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EmbeddedEngineStats {
pub initialized: bool,
pub running: bool,
pub frame_count: u64,
pub pending_task_count: usize,
pub window_count: usize,
pub button_count: usize,
pub target_fps: u32,
}
pub(crate) fn embedded_engine_shared() -> Arc<EmbeddedEngineShared> {
static SHARED: OnceLock<Arc<EmbeddedEngineShared>> = OnceLock::new();
SHARED.get_or_init(|| Arc::new(EmbeddedEngineShared::new())).clone()
}
pub fn set_embedded_target_fps(fps: u32) -> u32 {
embedded_engine_shared().set_target_fps(fps)
}
pub fn embedded_target_fps() -> u32 {
embedded_engine_shared().target_fps()
}
pub fn submit_embedded_task<F>(label: impl Into<String>, action: F) -> u64
where
F: FnOnce(u64) + Send + 'static,
{
embedded_engine_shared().submit_task(label.into(), action)
}
pub fn embedded_engine_stats() -> EmbeddedEngineStats {
embedded_engine_shared().stats()
}
#[cfg(test)]
mod tests {
use super::*;
fn test_guard() -> crate::compat::MutexGuard<'static, ()> {
static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
GUARD.get_or_init(|| Mutex::new(())).lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[test]
fn embedded_target_fps_clamps() {
let _guard = test_guard();
assert_eq!(set_embedded_target_fps(0), MIN_EMBEDDED_TARGET_FPS);
assert_eq!(set_embedded_target_fps(999), MAX_EMBEDDED_TARGET_FPS);
assert_eq!(set_embedded_target_fps(72), 72);
assert_eq!(embedded_target_fps(), 72);
}
#[test]
fn embedded_resource_registry_tracks_window_and_button() {
let _guard = test_guard();
let before = embedded_engine_stats();
let shared = embedded_engine_shared();
let window_id = shared.register_window("stats", 1, 2, 300, 200);
let _button_id = shared.register_button(window_id, "ok", 10, 10, 80, 24);
let after = embedded_engine_stats();
assert!(after.window_count > before.window_count);
assert!(after.button_count > before.button_count);
}
}