diff --git a/all-is-cubes-desktop/src/bin/all-is-cubes/main.rs b/all-is-cubes-desktop/src/bin/all-is-cubes/main.rs index b79b7c131..66af74893 100644 --- a/all-is-cubes-desktop/src/bin/all-is-cubes/main.rs +++ b/all-is-cubes-desktop/src/bin/all-is-cubes/main.rs @@ -13,8 +13,6 @@ use clap::{CommandFactory as _, Parser as _}; use all_is_cubes::euclid::Size2D; use all_is_cubes::listen::ListenableCell; use all_is_cubes_render::camera::{GraphicsOptions, Viewport}; -use all_is_cubes_ui::notification; -use all_is_cubes_ui::vui::widgets::ProgressBarState; #[cfg(feature = "record")] use all_is_cubes_desktop::record; @@ -26,7 +24,7 @@ use all_is_cubes_desktop::winit::{ self as aic_winit, create_winit_wgpu_desktop_session, winit_main_loop_and_init, }; use all_is_cubes_desktop::{ - inner_main, load_config, logging, DesktopSession, InnerMainParams, Session, + inner_main, load_config, logging, DesktopSession, InnerMainParams, Session, UniverseTask, }; mod command_options; @@ -91,14 +89,7 @@ fn main() -> Result<(), anyhow::Error> { // Done with options; now start creating the session. - // Kick off constructing the universe in the background. - let (universe_future, universe_notif_tx) = { - let (n_tx, n_rx) = tokio::sync::oneshot::channel(); - ( - runtime.spawn(input_source.create_universe(precompute_light, n_rx)), - n_tx, - ) - }; + let mut universe_task = UniverseTask::new(&executor, input_source, precompute_light); // This cell will be moved into the session after (possibly) being reset to the actual // window size. This is a kludge because the `Session`'s `Vui` wants to be able to track @@ -121,12 +112,7 @@ fn main() -> Result<(), anyhow::Error> { ); // TODO: this code should live in the lib session.graphics_options_mut().set(graphics_options); - if let Ok(n) = session.show_notification(notification::NotificationContent::Progress( - ProgressBarState::new(0.0), - )) { - // Ignore send error because the process might have finished and dropped the receiver. - _ = universe_notif_tx.send(n); - } + universe_task.attach_to_session(&mut session); let session_done_time = Instant::now(); log::debug!( "Initialized session ({:.3} s)", @@ -145,7 +131,7 @@ fn main() -> Result<(), anyhow::Error> { application_title: title_and_version(), runtime, before_loop_time: Instant::now(), - universe_future, + universe_task, headless: options.is_headless(), logging: late_logging, #[cfg(feature = "record")] diff --git a/all-is-cubes-desktop/src/startup.rs b/all-is-cubes-desktop/src/startup.rs index eea4e40f8..d232543fd 100644 --- a/all-is-cubes-desktop/src/startup.rs +++ b/all-is-cubes-desktop/src/startup.rs @@ -7,6 +7,8 @@ use anyhow::Context as _; use all_is_cubes::universe::Universe; use all_is_cubes_ui::apps::{ExitMainTask, MainTaskContext}; +use all_is_cubes_ui::notification; +use all_is_cubes_ui::vui::widgets::ProgressBarState; use crate::glue::{Executor, Renderer, Window}; use crate::{logging, record}; @@ -28,7 +30,7 @@ pub fn inner_main( application_title, runtime, before_loop_time, - universe_future, + universe_task, headless, logging, recording, @@ -78,7 +80,7 @@ pub fn inner_main( } dsession.session.set_main_task(|mut ctx| async move { - let universe_result: Result = match universe_future.await { + let universe_result: Result = match universe_task.future.await { Ok(Ok(u)) => Ok(u), Ok(Err(e)) => { Err(e).context("failed to create universe from requested template or file") @@ -185,7 +187,7 @@ pub struct InnerMainParams { pub application_title: String, pub runtime: tokio::runtime::Runtime, pub before_loop_time: Instant, - pub universe_future: tokio::task::JoinHandle>, + pub universe_task: UniverseTask, pub headless: bool, /// Result of calling [`logging::install()`], which should be done as early as feasible. pub logging: logging::LateLogging, @@ -201,6 +203,43 @@ pub struct InnerMainParams { pub task_done_signal: tokio::sync::oneshot::Sender<()>, } +/// An async task that constructs a [`Universe`] that will belong to a [`DesktopSession`], +/// delivered via [`InnerMainParams`]. +#[derive(Debug)] +pub struct UniverseTask { + future: tokio::task::JoinHandle>, + progress_notification_handoff_tx: + Option>, +} + +#[allow(missing_docs)] // sloppy API anyway +impl UniverseTask { + pub fn new(executor: &Executor, source: crate::UniverseSource, precompute_light: bool) -> Self { + // Kick off constructing the universe in the background. + let (n_tx, n_rx) = tokio::sync::oneshot::channel(); + let future = executor + .tokio() + .spawn(source.create_universe(precompute_light, n_rx)); + Self { + future, + progress_notification_handoff_tx: Some(n_tx), + } + } + + pub fn attach_to_session(&mut self, session: &mut crate::Session) { + if let Ok(n) = session.show_notification(notification::NotificationContent::Progress( + ProgressBarState::new(0.0), + )) { + // Ignore send error because the process might have finished and dropped the receiver. + _ = self + .progress_notification_handoff_tx + .take() + .expect("attach_to_session() must be called only once") + .send(n); + } + } +} + #[allow(clippy::needless_pass_by_value)] fn report_error_and_exit(_ctx: &MainTaskContext, error: anyhow::Error) -> ! { // TODO: if we are a GUI-no-terminal session, log this instead of printing and create a dialog