From 4c4fba3acc4a1a2f4a284e3ba6ac896b7ca9b894 Mon Sep 17 00:00:00 2001
From: Putta Khunchalee <ultimaweapon@outlook.com>
Date: Sun, 15 Dec 2024 22:48:20 +0700
Subject: [PATCH] Moves error display to App trait (#1183)

---
 .github/workflows/ci-windows.yml | 12 +++++++
 gui/src/main.rs                  | 61 +++++++++++++++++---------------
 gui/src/rt/app.rs                |  6 ++++
 gui/src/rt/mod.rs                | 26 +++++++++++---
 4 files changed, 72 insertions(+), 33 deletions(-)
 create mode 100644 gui/src/rt/app.rs

diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml
index 1c7b5ae08..36b6c31dd 100644
--- a/.github/workflows/ci-windows.yml
+++ b/.github/workflows/ci-windows.yml
@@ -10,6 +10,7 @@ jobs:
       uses: actions/checkout@v4
     - name: Generate cache keys
       run: |
+        echo "target=${{ runner.os }}-target" >> $env:GITHUB_OUTPUT
         echo "vulkan=${{ runner.os }}-vulkan-1.3.290.0" >> $env:GITHUB_OUTPUT
       id: cache-keys
     - name: Restore Vulkan SDK
@@ -27,6 +28,11 @@ jobs:
       if: ${{ steps.restore-vulkan.outputs.cache-hit != 'true' }}
     - name: Set Vulkan SDK path
       run: echo "VULKAN_SDK=C:\VulkanSDK" >> $env:GITHUB_ENV
+    - name: Restore target directory
+      uses: actions/cache/restore@v4
+      with:
+        path: target
+        key: ${{ steps.cache-keys.outputs.target }}
     - name: Update Rust
       run: rustup update stable
     - name: Add additional Rust targets
@@ -40,6 +46,12 @@ jobs:
       with:
         name: obliteration-win-x64
         path: dist
+    - name: Cache target directory
+      uses: actions/cache/save@v4
+      with:
+        path: target
+        key: ${{ steps.cache-keys.outputs.target }}-${{ github.run_id }}
+      if: startsWith(github.ref, 'refs/heads/')
     - name: Cache Vulkan SDK
       uses: actions/cache/save@v4
       with:
diff --git a/gui/src/main.rs b/gui/src/main.rs
index 5c83736b3..1d42522ee 100644
--- a/gui/src/main.rs
+++ b/gui/src/main.rs
@@ -10,6 +10,7 @@ use debug::DebugServer;
 use erdp::ErrorDisplay;
 use slint::{ComponentHandle, ModelRc, SharedString, VecModel};
 use std::cell::Cell;
+use std::error::Error;
 use std::net::SocketAddrV4;
 use std::path::PathBuf;
 use std::process::ExitCode;
@@ -74,30 +75,9 @@ fn main() -> ExitCode {
     }
 
     // Run.
-    let run = async move {
-        // Setup Slint back-end. This need to be done before using any Slint API.
-        slint::platform::set_platform(Box::new(SlintBackend::new())).unwrap();
-
-        // Run.
-        let e = match run(args, exe).await {
-            Ok(_) => return,
-            Err(e) => e,
-        };
-
-        // Show error window.
-        let win = ErrorWindow::new().unwrap();
-
-        win.set_message(format!("An unexpected error has occurred: {}.", e.display()).into());
-        win.on_close({
-            let win = win.as_weak();
+    let app = Rc::new(App { args, exe });
 
-            move || win.unwrap().hide().unwrap()
-        });
-
-        win.exec().await.unwrap();
-    };
-
-    match self::rt::block_on(run) {
+    match self::rt::run(app.clone(), run(app)) {
         Ok(_) => ExitCode::SUCCESS,
         Err(e) => {
             error(format!(
@@ -110,7 +90,10 @@ fn main() -> ExitCode {
     }
 }
 
-async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> {
+async fn run(app: Rc<App>) -> Result<(), ProgramError> {
+    // Setup Slint back-end. This need to be done before using any Slint API.
+    slint::platform::set_platform(Box::new(SlintBackend::new())).unwrap();
+
     #[cfg(unix)]
     rlim::set_rlimit_nofile().map_err(ProgramError::FdLimit)?;
 
@@ -125,9 +108,9 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> {
     };
 
     // Get kernel path.
-    let kernel = args.kernel.as_ref().cloned().unwrap_or_else(|| {
+    let kernel = app.args.kernel.as_ref().cloned().unwrap_or_else(|| {
         // Get kernel directory.
-        let mut path = exe.parent().unwrap().to_owned();
+        let mut path = app.exe.parent().unwrap().to_owned();
 
         #[cfg(target_os = "windows")]
         path.push("share");
@@ -175,7 +158,7 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> {
     }
 
     // Get profile to use.
-    let (profile, debug) = if let Some(v) = args.debug {
+    let (profile, debug) = if let Some(v) = app.args.debug {
         // TODO: Select last used profile.
         (profiles.pop().unwrap(), Some(v))
     } else {
@@ -352,6 +335,28 @@ enum ProgramMode {
     PanicHandler,
 }
 
+/// Implementation of [`self::rt::App`] for main program mode.
+struct App {
+    args: ProgramArgs,
+    exe: PathBuf,
+}
+
+impl self::rt::App for App {
+    async fn error(&self, e: impl Error) {
+        // Show error window.
+        let win = ErrorWindow::new().unwrap();
+
+        win.set_message(format!("An unexpected error has occurred: {}.", e.display()).into());
+        win.on_close({
+            let win = win.as_weak();
+
+            move || win.unwrap().hide().unwrap()
+        });
+
+        win.exec().await.unwrap();
+    }
+}
+
 /// Represents an error when our program fails.
 #[derive(Debug, Error)]
 enum ProgramError {
@@ -393,5 +398,5 @@ enum ProgramError {
     RunMainWindow(#[source] slint::PlatformError),
 
     #[error("couldn't create VMM screen")]
-    CreateScreen(#[source] Box<dyn std::error::Error>),
+    CreateScreen(#[source] Box<dyn Error>),
 }
diff --git a/gui/src/rt/app.rs b/gui/src/rt/app.rs
new file mode 100644
index 000000000..75a4cf4a0
--- /dev/null
+++ b/gui/src/rt/app.rs
@@ -0,0 +1,6 @@
+use std::error::Error;
+
+/// Provides application-specific methods for runtime to use.
+pub trait App: 'static {
+    async fn error(&self, e: impl Error);
+}
diff --git a/gui/src/rt/mod.rs b/gui/src/rt/mod.rs
index bbd64ccfb..88c2b40df 100644
--- a/gui/src/rt/mod.rs
+++ b/gui/src/rt/mod.rs
@@ -1,3 +1,4 @@
+pub use self::app::*;
 pub use self::context::*;
 pub use self::window::*;
 
@@ -7,7 +8,7 @@ use self::waker::Waker;
 use std::collections::HashMap;
 use std::error::Error;
 use std::future::Future;
-use std::rc::Weak;
+use std::rc::{Rc, Weak};
 use std::sync::Arc;
 use thiserror::Error;
 use winit::application::ApplicationHandler;
@@ -15,19 +16,34 @@ use winit::error::{EventLoopError, OsError};
 use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
 use winit::window::WindowId;
 
+mod app;
 mod context;
 mod event;
 mod task;
 mod waker;
 mod window;
 
-pub fn block_on(main: impl Future<Output = ()> + 'static) -> Result<(), RuntimeError> {
+pub fn run<E, A>(
+    app: Rc<A>,
+    main: impl Future<Output = Result<(), E>> + 'static,
+) -> Result<(), RuntimeError>
+where
+    E: Error,
+    A: App,
+{
     // Setup winit event loop.
     let mut el = EventLoop::<Event>::with_user_event();
     let el = el.build().map_err(RuntimeError::CreateEventLoop)?;
-    let main = async move {
-        main.await;
-        RuntimeContext::with(|cx| cx.el.exit());
+    let main = {
+        let app = app.clone();
+
+        async move {
+            if let Err(e) = main.await {
+                app.error(e).await;
+            }
+
+            RuntimeContext::with(|cx| cx.el.exit());
+        }
     };
 
     // Run event loop.