use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use platform_core::{
EventHandler, MultiSurfacePlatform, Platform, PlatformError, SurfaceId, WindowConfig,
};
use crate::window::HeadlessWindow;
const FRAME_BUDGET: Duration = Duration::from_nanos(1_000_000_000 / 60);
pub type FrameSink = Arc<Mutex<Option<Vec<u8>>>>;
pub type SurfaceFrameSink = Arc<Mutex<HashMap<SurfaceId, Vec<u8>>>>;
pub struct HeadlessPlatform {
width: u32,
height: u32,
frames: u32,
sink: Option<FrameSink>,
surface_sink: Option<SurfaceFrameSink>,
}
impl HeadlessPlatform {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
frames: 1,
sink: None,
surface_sink: None,
}
}
pub fn with_frames(mut self, frames: u32) -> Self {
self.frames = frames;
self
}
pub fn capture_into(mut self, sink: FrameSink) -> Self {
self.sink = Some(sink);
self
}
pub fn capture_surfaces_into(mut self, sink: SurfaceFrameSink) -> Self {
self.surface_sink = Some(sink);
self
}
}
impl Platform for HeadlessPlatform {
type Window = HeadlessWindow;
fn run<H: EventHandler<HeadlessWindow>>(
self,
_config: WindowConfig,
mut handler: H,
) -> Result<(), PlatformError> {
let window = HeadlessWindow::with_options(self.width, self.height, 1.0, None);
handler.new_events();
let resumed = handler.on_resume(&window);
handler.about_to_wait();
if !resumed {
return Err(PlatformError(
"headless on_resume returned false (renderer initialization failed)".to_string(),
));
}
for _ in 0..self.frames.max(1) {
handler.new_events();
std::thread::sleep(FRAME_BUDGET);
handler.on_redraw(&window);
handler.about_to_wait();
}
if let Some(sink) = &self.sink
&& let Some(pixels) = handler.last_frame_rgba()
{
*sink.lock().unwrap() = Some(pixels);
}
handler.on_suspend();
Ok(())
}
}
impl MultiSurfacePlatform for HeadlessPlatform {
type Window = HeadlessWindow;
fn run_surfaces<H, F>(
self,
surfaces: Vec<(SurfaceId, WindowConfig)>,
factory: F,
) -> Result<(), PlatformError>
where
H: EventHandler<HeadlessWindow> + 'static,
F: Fn(SurfaceId) -> H + 'static,
{
let frames = self.frames.max(1);
let sink = self.surface_sink.clone();
let mut states: Vec<(SurfaceId, HeadlessWindow, H)> = Vec::with_capacity(surfaces.len());
for (id, config) in surfaces {
let window = HeadlessWindow::new(config.width, config.height);
states.push((id, window, factory(id)));
}
states.retain_mut(|(id, window, handler)| {
handler.new_events();
let resumed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handler.on_resume(window)
}));
let _ =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| handler.about_to_wait()));
if resumed.is_err() {
eprintln!("surface {} panicked during build; unmounting it", id.0);
}
matches!(resumed, Ok(true))
});
for _ in 0..frames {
std::thread::sleep(FRAME_BUDGET);
states.retain_mut(|(id, window, handler)| {
handler.new_events();
let drawn = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handler.on_redraw(window)
}));
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handler.about_to_wait()
}));
if drawn.is_err() {
eprintln!("surface {} panicked during redraw; unmounting it", id.0);
}
drawn.is_ok()
});
}
if let Some(sink) = &sink {
for (id, _, handler) in &mut states {
if let Some(pixels) = handler.last_frame_rgba() {
sink.lock().unwrap().insert(*id, pixels);
}
}
}
for (_, _, handler) in &mut states {
handler.on_suspend();
}
Ok(())
}
}