Fix initialization race conditions in WebAssembly

WebGL is still broken, but oh well... Time to move on.
This commit is contained in:
Héctor Ramón Jiménez 2024-06-19 19:03:07 +02:00
parent 5f25943449
commit 65c8e08b44
No known key found for this signature in database
GPG key ID: 7CC46565708259A7

View file

@ -200,20 +200,22 @@ where
Runtime::new(executor, proxy.clone()) Runtime::new(executor, proxy.clone())
}; };
let (application, task) = runtime.enter(|| P::new(flags)); let (program, task) = runtime.enter(|| P::new(flags));
if let Some(stream) = task.into_stream() { if let Some(stream) = task.into_stream() {
runtime.run(stream); runtime.run(stream);
} }
runtime.track(program.subscription().map(Action::Output).into_recipes());
let (boot_sender, boot_receiver) = oneshot::channel(); let (boot_sender, boot_receiver) = oneshot::channel();
let (event_sender, event_receiver) = mpsc::unbounded(); let (event_sender, event_receiver) = mpsc::unbounded();
let (control_sender, control_receiver) = mpsc::unbounded(); let (control_sender, control_receiver) = mpsc::unbounded();
let instance = Box::pin(run_instance::<P, C>( let instance = Box::pin(run_instance::<P, C>(
application, program,
runtime, runtime,
proxy, proxy.clone(),
debug, debug,
boot_receiver, boot_receiver,
event_receiver, event_receiver,
@ -226,18 +228,19 @@ where
instance: std::pin::Pin<Box<F>>, instance: std::pin::Pin<Box<F>>,
context: task::Context<'static>, context: task::Context<'static>,
id: Option<String>, id: Option<String>,
boot: Option<BootConfig<C>>, boot: Option<BootConfig<Message, C>>,
sender: mpsc::UnboundedSender<Event<Message>>, sender: mpsc::UnboundedSender<Event<Action<Message>>>,
receiver: mpsc::UnboundedReceiver<Control>, receiver: mpsc::UnboundedReceiver<Control>,
error: Option<Error>, error: Option<Error>,
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
is_booted: std::rc::Rc<std::cell::RefCell<bool>>, is_booted: std::rc::Rc<std::cell::RefCell<bool>>,
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
queued_events: Vec<Event<Message>>, queued_events: Vec<Event<Action<Message>>>,
} }
struct BootConfig<C> { struct BootConfig<Message: 'static, C> {
proxy: Proxy<Message>,
sender: oneshot::Sender<Boot<C>>, sender: oneshot::Sender<Boot<C>>,
window_settings: Option<window::Settings>, window_settings: Option<window::Settings>,
graphics_settings: graphics::Settings, graphics_settings: graphics::Settings,
@ -248,6 +251,7 @@ where
context, context,
id: settings.id, id: settings.id,
boot: Some(BootConfig { boot: Some(BootConfig {
proxy,
sender: boot_sender, sender: boot_sender,
window_settings, window_settings,
graphics_settings, graphics_settings,
@ -262,14 +266,16 @@ where
queued_events: Vec::new(), queued_events: Vec::new(),
}; };
impl<Message, F, C> winit::application::ApplicationHandler<Message> impl<Message, F, C> winit::application::ApplicationHandler<Action<Message>>
for Runner<Message, F, C> for Runner<Message, F, C>
where where
Message: std::fmt::Debug,
F: Future<Output = ()>, F: Future<Output = ()>,
C: Compositor + 'static, C: Compositor + 'static,
{ {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let Some(BootConfig { let Some(BootConfig {
mut proxy,
sender, sender,
window_settings, window_settings,
graphics_settings, graphics_settings,
@ -299,11 +305,23 @@ where
.send(Boot { .send(Boot {
compositor, compositor,
clipboard, clipboard,
window_settings, is_daemon: window_settings.is_none(),
}) })
.ok() .ok()
.expect("Send boot event"); .expect("Send boot event");
if let Some(window_settings) = window_settings {
let (sender, _receiver) = oneshot::channel();
proxy.send_action(Action::Window(
runtime::window::Action::Open(
window::Id::unique(),
window_settings,
sender,
),
));
}
Ok::<_, graphics::Error>(()) Ok::<_, graphics::Error>(())
}; };
@ -383,12 +401,12 @@ where
fn user_event( fn user_event(
&mut self, &mut self,
event_loop: &winit::event_loop::ActiveEventLoop, event_loop: &winit::event_loop::ActiveEventLoop,
message: Message, action: Action<Message>,
) { ) {
self.process_event( self.process_event(
event_loop, event_loop,
Event::EventLoopAwakened(winit::event::Event::UserEvent( Event::EventLoopAwakened(winit::event::Event::UserEvent(
message, action,
)), )),
); );
} }
@ -412,7 +430,7 @@ where
fn process_event( fn process_event(
&mut self, &mut self,
event_loop: &winit::event_loop::ActiveEventLoop, event_loop: &winit::event_loop::ActiveEventLoop,
event: Event<Message>, event: Event<Action<Message>>,
) { ) {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
if !*self.is_booted.borrow() { if !*self.is_booted.borrow() {
@ -574,7 +592,7 @@ where
struct Boot<C> { struct Boot<C> {
compositor: C, compositor: C,
clipboard: Clipboard, clipboard: Clipboard,
window_settings: Option<window::Settings>, is_daemon: bool,
} }
enum Event<Message: 'static> { enum Event<Message: 'static> {
@ -616,7 +634,7 @@ async fn run_instance<P, C>(
let Boot { let Boot {
mut compositor, mut compositor,
mut clipboard, mut clipboard,
window_settings, is_daemon,
} = boot.try_recv().ok().flatten().expect("Receive boot"); } = boot.try_recv().ok().flatten().expect("Receive boot");
let mut window_manager = WindowManager::new(); let mut window_manager = WindowManager::new();
@ -626,29 +644,7 @@ async fn run_instance<P, C>(
let mut actions = 0; let mut actions = 0;
let mut ui_caches = FxHashMap::default(); let mut ui_caches = FxHashMap::default();
let mut user_interfaces = ManuallyDrop::new(build_user_interfaces( let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
&program,
&mut debug,
&mut window_manager,
FxHashMap::from_iter([(
window::Id::MAIN,
user_interface::Cache::default(),
)]),
));
runtime.track(program.subscription().map(Action::Output).into_recipes());
let is_daemon = window_settings.is_none();
if let Some(window_settings) = window_settings {
let (sender, _receiver) = oneshot::channel();
proxy.send_action(Action::Window(runtime::window::Action::Open(
window::Id::unique(),
window_settings,
sender,
)));
}
debug.startup_finished(); debug.startup_finished();
@ -697,8 +693,6 @@ async fn run_instance<P, C>(
| event::StartCause::ResumeTimeReached { .. }, | event::StartCause::ResumeTimeReached { .. },
) => { ) => {
for (_id, window) in window_manager.iter_mut() { for (_id, window) in window_manager.iter_mut() {
// TODO once widgets can request to be redrawn, we can avoid always requesting a
// redraw
window.raw.request_redraw(); window.raw.request_redraw();
} }
} }
@ -878,9 +872,6 @@ async fn run_instance<P, C>(
) { ) {
Ok(()) => { Ok(()) => {
debug.render_finished(); debug.render_finished();
// TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this.
} }
Err(error) => match error { Err(error) => match error {
// This is an unrecoverable error. // This is an unrecoverable error.
@ -1024,7 +1015,6 @@ async fn run_instance<P, C>(
debug.event_processing_finished(); debug.event_processing_finished();
// TODO mw application update returns which window IDs to update
if !messages.is_empty() || uis_stale { if !messages.is_empty() || uis_stale {
let cached_interfaces: FxHashMap< let cached_interfaces: FxHashMap<
window::Id, window::Id,
@ -1034,7 +1024,6 @@ async fn run_instance<P, C>(
.map(|(id, ui)| (id, ui.into_cache())) .map(|(id, ui)| (id, ui.into_cache()))
.collect(); .collect();
// Update application
update( update(
&mut program, &mut program,
&mut runtime, &mut runtime,
@ -1042,8 +1031,6 @@ async fn run_instance<P, C>(
&mut messages, &mut messages,
); );
// we must synchronize all window states with application state after an
// application update since we don't know what changed
for (id, window) in window_manager.iter_mut() { for (id, window) in window_manager.iter_mut() {
window.state.synchronize( window.state.synchronize(
&program, &program,
@ -1051,12 +1038,9 @@ async fn run_instance<P, C>(
&window.raw, &window.raw,
); );
// TODO once widgets can request to be redrawn, we can avoid always requesting a
// redraw
window.raw.request_redraw(); window.raw.request_redraw();
} }
// rebuild UIs with the synchronized states
user_interfaces = user_interfaces =
ManuallyDrop::new(build_user_interfaces( ManuallyDrop::new(build_user_interfaces(
&program, &program,
@ -1082,7 +1066,7 @@ async fn run_instance<P, C>(
/// Builds a window's [`UserInterface`] for the [`Program`]. /// Builds a window's [`UserInterface`] for the [`Program`].
fn build_user_interface<'a, P: Program>( fn build_user_interface<'a, P: Program>(
application: &'a P, program: &'a P,
cache: user_interface::Cache, cache: user_interface::Cache,
renderer: &mut P::Renderer, renderer: &mut P::Renderer,
size: Size, size: Size,
@ -1093,7 +1077,7 @@ where
P::Theme: DefaultStyle, P::Theme: DefaultStyle,
{ {
debug.view_started(); debug.view_started();
let view = application.view(id); let view = program.view(id);
debug.view_finished(); debug.view_finished();
debug.layout_started(); debug.layout_started();
@ -1104,7 +1088,7 @@ where
} }
fn update<P: Program, E: Executor>( fn update<P: Program, E: Executor>(
application: &mut P, program: &mut P,
runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>, runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
debug: &mut Debug, debug: &mut Debug,
messages: &mut Vec<P::Message>, messages: &mut Vec<P::Message>,
@ -1115,7 +1099,7 @@ fn update<P: Program, E: Executor>(
debug.log_message(&message); debug.log_message(&message);
debug.update_started(); debug.update_started();
let task = runtime.enter(|| application.update(message)); let task = runtime.enter(|| program.update(message));
debug.update_finished(); debug.update_finished();
if let Some(stream) = task.into_stream() { if let Some(stream) = task.into_stream() {
@ -1123,13 +1107,13 @@ fn update<P: Program, E: Executor>(
} }
} }
let subscription = application.subscription(); let subscription = program.subscription();
runtime.track(subscription.map(Action::Output).into_recipes()); runtime.track(subscription.map(Action::Output).into_recipes());
} }
fn run_action<P, C>( fn run_action<P, C>(
action: Action<P::Message>, action: Action<P::Message>,
application: &P, program: &P,
compositor: &mut C, compositor: &mut C,
messages: &mut Vec<P::Message>, messages: &mut Vec<P::Message>,
clipboard: &mut Clipboard, clipboard: &mut Clipboard,
@ -1170,7 +1154,7 @@ fn run_action<P, C>(
.start_send(Control::CreateWindow { .start_send(Control::CreateWindow {
id, id,
settings, settings,
title: application.title(id), title: program.title(id),
monitor, monitor,
}) })
.expect("Send control action"); .expect("Send control action");
@ -1403,7 +1387,7 @@ fn run_action<P, C>(
/// Build the user interface for every window. /// Build the user interface for every window.
pub fn build_user_interfaces<'a, P: Program, C>( pub fn build_user_interfaces<'a, P: Program, C>(
application: &'a P, program: &'a P,
debug: &mut Debug, debug: &mut Debug,
window_manager: &mut WindowManager<P, C>, window_manager: &mut WindowManager<P, C>,
mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>, mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>,
@ -1420,7 +1404,7 @@ where
Some(( Some((
id, id,
build_user_interface( build_user_interface(
application, program,
cache, cache,
&mut window.renderer, &mut window.renderer,
window.state.logical_size(), window.state.logical_size(),