use std::cell::Cell;
use std::rc::Rc;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use slate_platform::{
DefaultPlatform, DefaultWindow, Event, Platform, Window, WindowOptions, WindowRenderDelegate,
wake_run_loop,
};
use crate::app::AppContext;
use crate::app_state::window_state::WindowState;
use crate::app_state::{AppSignal, AppState, RecoveryState};
use crate::element::AnyElement;
use crate::elements::Div;
use crate::erased_view::ErasedView;
use crate::executor::{Executor, RedrawRequester};
use crate::view::{IntoAny, View};
const TICK_INTERVAL: Duration = Duration::from_millis(10);
pub struct NoopView;
impl View for NoopView {
fn render(&mut self, _cx: &mut crate::RenderCx) -> AnyElement {
Div::new().into_any()
}
}
#[derive(Debug, Clone)]
pub struct RecoveryProbe {
pub initial_generation: u64,
pub final_generation: u64,
pub returned_to_not_lost: bool,
pub gave_up: bool,
pub timed_out_before_trigger: bool,
pub timed_out_in_recovery: bool,
pub request_quit: bool,
pub elapsed: Duration,
pub final_state: RecoveryState,
}
pub struct RecoveryHarness {
platform: DefaultPlatform,
#[allow(dead_code)]
window: Arc<DefaultWindow>,
state: Rc<AppState>,
cx: AppContext,
window_id: slate_platform::WindowId,
}
impl RecoveryHarness {
pub fn new() -> Self {
let platform = DefaultPlatform::new();
let window = platform.create_window(WindowOptions {
title: "slate-recovery-harness".into(),
size: (1, 1),
min_size: None,
resizable: false,
visible: false,
position: Some((-32000, -32000)),
});
let redraw_requester = RedrawRequester::new(wake_run_loop);
let executor = Executor::new(redraw_requester.clone());
let runtime = slate_reactive::Runtime::new();
let cx = AppContext::new_for_test(runtime.clone(), executor.background.clone());
let state = Rc::new(AppState::new(
executor,
redraw_requester.clone(),
runtime.clone(),
));
let window_id = window.id();
{
let win_state = WindowState::new(window.clone(), runtime);
state.windows.borrow_mut().insert(window_id, win_state);
}
state.register_redraw_requester(window_id, redraw_requester);
let dyn_strong: Rc<dyn WindowRenderDelegate> = state.clone();
let dyn_weak = Rc::downgrade(&dyn_strong);
window.set_render_delegate(dyn_weak);
drop(dyn_strong);
Self {
platform,
window,
state,
cx,
window_id,
}
}
pub fn probe_force_device_lost(self, timeout: Duration) -> RecoveryProbe {
let start = Instant::now();
let triggered = Cell::new(false);
let initial_gen = Cell::new(0u64);
let request_quit = Cell::new(false);
let window_id = self.window_id;
let mut erased_factory = |_cx: &AppContext| -> Box<dyn ErasedView> { Box::new(NoopView) };
let RecoveryHarness {
platform,
window: _window,
state,
cx,
window_id: _,
} = self;
platform.run(|event| {
if start.elapsed() > timeout {
platform.quit();
return;
}
if state.pending_quit() {
request_quit.set(true);
platform.quit();
return;
}
let should_tick = match event {
Event::Resumed => {
if state
.init_surfaces(window_id, &mut erased_factory, &cx, &platform)
.is_err()
{
request_quit.set(true);
platform.quit();
return;
}
initial_gen.set(state.renderer_generation().unwrap_or(0));
true
}
Event::Wake => true,
Event::WindowRedrawRequested { .. } => true,
Event::WindowResized {
window,
physical_size,
..
} => {
state.handle_window_resized(window, physical_size);
false
}
Event::WindowCloseRequested { .. } => {
request_quit.set(true);
platform.quit();
return;
}
Event::WindowDestroyed { window, .. } => {
if matches!(
state.handle_window_destroyed(window),
AppSignal::RequestQuit
) {
request_quit.set(true);
platform.quit();
}
return;
}
_ => false,
};
if !should_tick {
return;
}
let sig = state.dispatch_redraw(window_id);
if matches!(sig, AppSignal::RequestQuit) {
request_quit.set(true);
platform.quit();
return;
}
let recovery = state.current_recovery_state();
let gen_now = state.renderer_generation().unwrap_or(0);
if !triggered.get()
&& matches!(recovery, RecoveryState::NotLost)
&& gen_now >= initial_gen.get()
&& !state.renderer_is_device_lost()
{
#[cfg(all(target_os = "windows", feature = "test-hooks"))]
if state.force_renderer_device_lost(window_id) {
triggered.set(true);
}
#[cfg(not(all(target_os = "windows", feature = "test-hooks")))]
{
triggered.set(true);
}
schedule_next_tick();
} else if (triggered.get()
&& matches!(recovery, RecoveryState::NotLost)
&& gen_now > initial_gen.get())
|| matches!(recovery, RecoveryState::GiveUp { .. })
{
platform.quit();
} else {
schedule_next_tick();
}
});
let final_gen = state.renderer_generation().unwrap_or(0);
let final_state = state.current_recovery_state();
let elapsed = start.elapsed();
let triggered_b = triggered.get();
let returned_to_not_lost = triggered_b
&& matches!(final_state, RecoveryState::NotLost)
&& final_gen > initial_gen.get();
let gave_up = matches!(final_state, RecoveryState::GiveUp { .. });
let timed_out_before_trigger = !triggered_b && elapsed >= timeout;
let timed_out_in_recovery =
triggered_b && elapsed >= timeout && !returned_to_not_lost && !gave_up;
RecoveryProbe {
initial_generation: initial_gen.get(),
final_generation: final_gen,
returned_to_not_lost,
gave_up,
timed_out_before_trigger,
timed_out_in_recovery,
request_quit: request_quit.get(),
elapsed,
final_state,
}
}
}
impl Default for RecoveryHarness {
fn default() -> Self {
Self::new()
}
}
fn schedule_next_tick() {
thread::sleep(TICK_INTERVAL);
wake_run_loop();
}