Fixed bugs when running two sequential headless apps in the same thread.

This commit is contained in:
Samuel Guerra 2021-04-15 20:08:12 -03:00
parent cdccd899a7
commit 4173c13f66
4 changed files with 81 additions and 46 deletions

View File

@ -5,38 +5,51 @@ use zero_ui::core::window::{HeadlessAppOpenWindowExt, WindowId};
use zero_ui::prelude::*;
#[test]
pub fn window_tab_cycle() {
pub fn window_tab_cycle_index_auto() {
// default window! cycles TAB navigation
t(|_| TabIndex::AUTO);
t(TabIndex);
t(|i| TabIndex(TabIndex::AUTO.0 - i - 1));
let buttons = widgets![
button! { content = text("Button 0") },
button! { content = text("Button 1") },
button! { content = text("Button 2") },
];
let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect();
fn t(make_index: impl FnMut(u32) -> TabIndex) {
// all TAB navigation must respect the `tab_index` value
// that by default is AUTO, but can be not in the same order
// as the widgets are declared.
let tab_ids: Vec<_> = (0..3).map(make_index).collect();
let mut app = TestApp::new(v_stack(buttons));
let buttons = widgets![
button! { content = text("Button 0"); tab_index = tab_ids[0] },
button! { content = text("Button 1"); tab_index = tab_ids[1] },
button! { content = text("Button 2"); tab_index = tab_ids[2] },
];
// we collect the widget_id values in the TAB navigation order.
let mut ids: Vec<_> = (0..3).map(|i| (buttons.widget_id(i), tab_ids[i])).collect();
ids.sort_by_key(|(_, ti)| *ti);
let ids: Vec<_> = ids.into_iter().map(|(id, _)| id).collect();
// advance normally..
assert_eq!(Some(ids[0]), app.focused());
app.press_tab();
assert_eq!(Some(ids[1]), app.focused());
app.press_tab();
assert_eq!(Some(ids[2]), app.focused());
// then cycles back.
app.press_tab();
assert_eq!(Some(ids[0]), app.focused());
let mut app = TestApp::new(v_stack(buttons));
// same backwards.
app.press_shift_tab();
assert_eq!(Some(ids[2]), app.focused());
app.press_shift_tab();
assert_eq!(Some(ids[1]), app.focused());
app.press_shift_tab();
assert_eq!(Some(ids[0]), app.focused());
// cycles back.
app.press_shift_tab();
assert_eq!(Some(ids[2]), app.focused());
// advance normally..
assert_eq!(Some(ids[0]), app.focused());
app.press_tab();
assert_eq!(Some(ids[1]), app.focused());
app.press_tab();
assert_eq!(Some(ids[2]), app.focused());
// then cycles back.
app.press_tab();
assert_eq!(Some(ids[0]), app.focused());
// same backwards.
app.press_shift_tab();
assert_eq!(Some(ids[2]), app.focused());
app.press_shift_tab();
assert_eq!(Some(ids[1]), app.focused());
app.press_shift_tab();
assert_eq!(Some(ids[0]), app.focused());
// cycles back.
app.press_shift_tab();
assert_eq!(Some(ids[2]), app.focused());
}
}
#[test]

View File

@ -241,7 +241,6 @@ state_key! {
/// The index is zero based, zero first.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct TabIndex(pub u32);
impl TabIndex {
/// Widget is skipped during tab navigation.
///

View File

@ -75,21 +75,24 @@ mod profiler_impl {
}
fn register_thread(&mut self) {
let id = ThreadId(self.threads.len());
let name = match thread::current().name() {
Some(s) => s.to_string(),
None => format!("<unnamed-{}>", id.0),
};
let registered_name = THREAD_PROFILER.with(|profiler| {
if profiler.borrow().is_none() {
let id = ThreadId(self.threads.len());
self.threads.push(ThreadInfo { name });
let thread_profiler = ThreadProfiler { id, tx: self.tx.clone() };
*profiler.borrow_mut() = Some(thread_profiler);
THREAD_PROFILER.with(|profiler| {
assert!(profiler.borrow().is_none());
let thread_profiler = ThreadProfiler { id, tx: self.tx.clone() };
*profiler.borrow_mut() = Some(thread_profiler);
Some(match thread::current().name() {
Some(s) => s.to_string(),
None => format!("<unnamed-{}>", id.0),
})
} else {
None
}
});
if let Some(name) = registered_name {
self.threads.push(ThreadInfo { name });
}
}
fn write_profile(&self, filename: &str, ignore_0ms: bool) {
@ -189,6 +192,8 @@ mod profiler_impl {
}
/// Registers the current thread with the global profiler.
///
/// Does nothing if the thread is already registered.
#[inline]
pub fn register_thread_with_profiler() {
GLOBAL_PROFILER.lock().unwrap().register_thread();

View File

@ -33,7 +33,11 @@ impl AppServicesInit {
let mut service = Box::new(service);
let prev = S::thread_local_entry().init(service.as_mut() as _);
if prev.is_null() {
self.m.services.push(service);
let deiniter = Box::new(|| S::thread_local_entry().deinit());
self.m.services.push(ServiceEntry {
_instance: service,
deiniter,
});
Ok(())
} else {
S::thread_local_entry().init(prev);
@ -50,7 +54,7 @@ impl AppServicesInit {
/// Panics if another instance of the service is already registered.
#[track_caller]
pub fn register<S: AppService + Sized>(&mut self, service: S) {
self.try_register(service).unwrap()
self.try_register(service).expect("service already registered")
}
/// Reference the [`AppServices`].
@ -59,9 +63,19 @@ impl AppServicesInit {
}
}
struct ServiceEntry {
_instance: Box<dyn AppService>,
deiniter: Box<dyn Fn()>,
}
impl Drop for ServiceEntry {
fn drop(&mut self) {
(self.deiniter)();
}
}
/// Access to application services.
pub struct AppServices {
services: Vec<Box<dyn AppService>>,
services: Vec<ServiceEntry>,
}
impl AppServices {
/// Gets a service reference if the service is registered in the application.
@ -83,7 +97,7 @@ impl AppServices {
/// If the service is not registered in the application.
pub fn req<S: AppService>(&mut self) -> &mut S {
self.get::<S>()
.unwrap_or_else(|| panic!("service `{}` is required", type_name::<S>()))
.unwrap_or_else(|| panic!("app service `{}` is required", type_name::<S>()))
}
/// Gets multiple service references if all services are registered in the application.
@ -146,7 +160,7 @@ impl WindowServicesInit {
let _ = S::thread_local_entry().init(service_ptr);
});
let unloader = Box::new(|| {
let _ = S::thread_local_entry().init(ptr::null_mut());
let _ = S::thread_local_entry().deinit();
});
(service, loader, unloader)
}));
@ -166,7 +180,7 @@ impl WindowServicesInit {
/// Panics if the window service type is already registered.
#[track_caller]
pub fn register<S: WindowService>(&mut self, new: impl Fn(&WindowContext) -> S + 'static) {
self.try_register(new).unwrap()
self.try_register(new).expect("window service type already registered")
}
/// Schedules a visitor that is called once for each open window.
@ -451,6 +465,10 @@ macro_rules! service_types {
self.local.with(move |l| l.value.replace(service))
}
fn deinit(&self) {
self.init(ptr::null_mut());
}
fn get(&self) -> *mut S {
self.local.with(|l| l.value.get())
}