Add Platform trait.
I did some more thinking about the whole trait/boundary stuff and reallized that we don't need the GUI to handle the generic-ness of a trait object since only 1 platform will ever be used in a binary. What do you think of the following:
1. Define a platform trait
2. Each platform defines a zero-sized struct implementing the trait (ex `WebPlatform`).
3. Create an ifdef'd type alias on those structs:
```
// gui/src/platform/mod.rs
#[cfg(feature = "web")]
pub type CurrentPlatform = web::WebPlatform;
#[cfg(feature = "desktop")]
pub type CurrentPlatform = desktop::DesktopPlatform;
#[cfg(feature = "mobile")]
pub type CurrentPlatform = mobile::MobilePlatform;
```
4. Add a compile time assertion that `CurrentPlatform` implements `Platform`.
Pros:
- We don't end up working around async trait objects
- We define what functions are needed for a platform
- We save a little on binary size by avoiding a fully generic solution.
Cons:
- The trait does not really do much other than being a collection of functions.
- In some ways it seems like what we're currently doing but with extra steps.
This commit is contained in:
+147
-3
@@ -1,3 +1,126 @@
|
||||
//! Platform abstraction layer
|
||||
//!
|
||||
//! This module defines traits that each platform (web, desktop, mobile) must implement.
|
||||
//! The traits make the platform boundary explicit and provide compile-time verification.
|
||||
|
||||
use crate::app::Command;
|
||||
use color_eyre::eyre::Error;
|
||||
use dioxus::hooks::UnboundedReceiver;
|
||||
use mumble_web2_common::{ClientConfig, ServerStatus};
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
|
||||
// ============================================================================
|
||||
// Trait Definitions
|
||||
// ============================================================================
|
||||
|
||||
/// Trait for spawn handles that can be stored and used to spawn tasks later.
|
||||
#[cfg(feature = "web")]
|
||||
pub trait SpawnHandleTrait: Clone + 'static {
|
||||
/// Spawn an async task using this handle.
|
||||
fn spawn<F>(&self, future: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static;
|
||||
|
||||
/// Get a spawn handle for the current context.
|
||||
fn current() -> Self;
|
||||
}
|
||||
|
||||
/// Trait for spawn handles that can be stored and used to spawn tasks later.
|
||||
#[cfg(any(feature = "desktop", feature = "mobile"))]
|
||||
pub trait SpawnHandleTrait: Clone + 'static {
|
||||
/// Spawn an async task using this handle.
|
||||
fn spawn<F>(&self, future: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static;
|
||||
|
||||
/// Get a spawn handle for the current context.
|
||||
fn current() -> Self;
|
||||
}
|
||||
|
||||
/// Runtime primitives: task spawning and async sleep.
|
||||
#[cfg(feature = "web")]
|
||||
pub trait PlatformRuntime {
|
||||
/// The spawn handle type for this platform.
|
||||
type SpawnHandle: SpawnHandleTrait;
|
||||
|
||||
/// Spawn an async task.
|
||||
fn spawn<F>(future: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static;
|
||||
|
||||
/// Async sleep for the given duration.
|
||||
fn sleep(duration: Duration) -> impl Future<Output = ()>;
|
||||
}
|
||||
|
||||
/// Runtime primitives: task spawning and async sleep.
|
||||
#[cfg(any(feature = "desktop", feature = "mobile"))]
|
||||
pub trait PlatformRuntime {
|
||||
/// The spawn handle type for this platform.
|
||||
type SpawnHandle: SpawnHandleTrait;
|
||||
|
||||
/// Spawn an async task.
|
||||
fn spawn<F>(future: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static;
|
||||
|
||||
/// Async sleep for the given duration.
|
||||
fn sleep(duration: Duration) -> impl Future<Output = ()>;
|
||||
}
|
||||
|
||||
/// Configuration persistence: loading and saving user preferences.
|
||||
pub trait PlatformConfig {
|
||||
/// Load the client configuration (proxy URL, cert hash, etc.).
|
||||
fn load_config() -> impl Future<Output = color_eyre::Result<ClientConfig>>;
|
||||
|
||||
/// Load saved username.
|
||||
fn load_username() -> Option<String>;
|
||||
|
||||
/// Load saved server URL.
|
||||
fn load_server_url() -> Option<String>;
|
||||
|
||||
/// Save the default username.
|
||||
fn set_default_username(username: &str) -> Option<()>;
|
||||
|
||||
/// Save the default server URL.
|
||||
fn set_default_server(server: &str) -> Option<()>;
|
||||
}
|
||||
|
||||
/// Network operations: connecting to servers.
|
||||
pub trait PlatformNetwork {
|
||||
/// Establish a connection to the Mumble server and run the network loop.
|
||||
fn network_connect(
|
||||
address: String,
|
||||
username: String,
|
||||
event_rx: &mut UnboundedReceiver<Command>,
|
||||
gui_config: &ClientConfig,
|
||||
) -> impl Future<Output = Result<(), Error>>;
|
||||
|
||||
/// Get server status (user count, version, etc.).
|
||||
fn get_status(client: &reqwest::Client)
|
||||
-> impl Future<Output = color_eyre::Result<ServerStatus>>;
|
||||
}
|
||||
|
||||
/// Platform initialization.
|
||||
pub trait PlatformInit {
|
||||
/// Initialize logging for the platform.
|
||||
fn init_logging();
|
||||
|
||||
/// Request runtime permissions (Android audio recording, etc.).
|
||||
fn request_permissions();
|
||||
}
|
||||
|
||||
/// Combined platform trait.
|
||||
///
|
||||
/// This is the main trait that each platform must implement. It combines all
|
||||
/// platform-specific functionality into a single interface, providing compile-time
|
||||
/// verification that all platforms implement the required functionality.
|
||||
pub trait Platform: PlatformRuntime + PlatformConfig + PlatformNetwork + PlatformInit {}
|
||||
|
||||
// ============================================================================
|
||||
// Platform Modules
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
mod web;
|
||||
|
||||
@@ -11,6 +134,30 @@ mod desktop;
|
||||
#[cfg(feature = "mobile")]
|
||||
mod mobile;
|
||||
|
||||
// ============================================================================
|
||||
// Platform Type Alias
|
||||
// ============================================================================
|
||||
|
||||
/// The current platform type, selected at compile time based on features.
|
||||
#[cfg(feature = "web")]
|
||||
pub type CurrentPlatform = web::WebPlatform;
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
pub type CurrentPlatform = desktop::DesktopPlatform;
|
||||
|
||||
#[cfg(feature = "mobile")]
|
||||
pub type CurrentPlatform = mobile::MobilePlatform;
|
||||
|
||||
/// Compile-time assertion that CurrentPlatform implements Platform.
|
||||
const _: () = {
|
||||
fn assert_platform<T: Platform>() {}
|
||||
let _ = assert_platform::<CurrentPlatform>;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Platform Re-exports
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
pub use desktop::*;
|
||||
#[cfg(feature = "mobile")]
|
||||
@@ -24,6 +171,3 @@ pub fn request_permissions() {}
|
||||
|
||||
#[cfg(all(feature = "web", not(any(feature = "desktop", feature = "mobile"))))]
|
||||
pub use web::*;
|
||||
|
||||
#[cfg(any(feature = "desktop"))]
|
||||
pub use desktop::*;
|
||||
|
||||
Reference in New Issue
Block a user