wip improved trait shit

This commit is contained in:
2026-01-24 20:16:02 -07:00
committed by Liam Warfield
parent ff14f577fe
commit 411d923c2a
9 changed files with 118 additions and 181 deletions
+9 -9
View File
@@ -6,7 +6,7 @@ use mumble_web2_common::{ClientConfig, ServerStatus};
use ordermap::OrderSet;
use std::collections::{HashMap, HashSet};
use crate::imp;
use crate::imp::{Platform, PlatformInterface as _};
pub type ChannelId = u32;
pub type UserId = u32;
@@ -690,12 +690,12 @@ pub fn LoginView(config: Resource<ClientConfig>) -> Element {
use_resource(move || async move {
let client = reqwest::Client::new();
loop {
*last_status.write_unchecked() = Some(imp::get_status(&client).await);
imp::sleep(std::time::Duration::from_secs_f32(1.0)).await;
*last_status.write_unchecked() = Some(Platform::get_status(&client).await);
Platform::sleep(std::time::Duration::from_secs_f32(1.0)).await;
}
});
let mut address_input = use_signal(|| imp::load_server_url());
let mut address_input = use_signal(|| Platform::load_server_url());
let address = use_memo(move || {
if let Some(addr) = address_input() {
addr.clone()
@@ -706,14 +706,14 @@ pub fn LoginView(config: Resource<ClientConfig>) -> Element {
}
});
let previous_username = imp::load_username();
let previous_username = Platform::load_username();
let mut username = use_signal(|| previous_username.unwrap_or(String::new()));
let do_connect = move |_| {
//let _ = set_default_username(&username.read());
let _ = imp::set_default_username(&username.read());
let _ = Platform::set_default_username(&username.read());
if config.read().as_ref().is_some_and(|cfg| cfg.any_server) {
imp::set_default_server(&address.read());
Platform::set_default_server(&address.read());
}
net.send(Connect {
address: address.read().clone(),
@@ -860,13 +860,13 @@ pub fn app() -> Element {
use_coroutine(|rx: UnboundedReceiver<Command>| super::network_entrypoint(rx));
let config = use_resource(|| async move {
match imp::load_config().await {
match Platform::load_config().await {
Ok(config) => config,
Err(_) => ClientConfig::default(),
}
});
imp::request_permissions();
Platform::request_permissions();
rsx!(
document::Link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap" }
+8 -9
View File
@@ -7,10 +7,7 @@ use std::cell::RefCell;
use std::sync::Arc;
use tracing::{error, info};
use crate::imp::{CurrentPlatform, PlatformRuntime, SpawnHandleTrait};
/// The spawn handle type for the current platform.
type SpawnHandle = <CurrentPlatform as PlatformRuntime>::SpawnHandle;
use crate::imp::{SpawnHandle, SpawnHandleInterface as _};
static DF_MODEL: Asset = asset!("/assets/DeepFilterNet3_ll_onnx.tar.gz");
// TODO: make this user configurable.
@@ -35,10 +32,7 @@ enum DenoisingModelState {
Availible(Box<DfTract>),
}
fn with_denoising_model<O>(
spawn: &SpawnHandle,
func: impl FnOnce(&mut DfTract) -> O,
) -> Option<O> {
fn with_denoising_model<O>(spawn: &SpawnHandle, func: impl FnOnce(&mut DfTract) -> O) -> Option<O> {
// Using a thread local is super gross, but DfTract is not Send (so it can never leave the current
// thread) while AudioProcessing itself might change threads whenever.
thread_local! {
@@ -126,7 +120,12 @@ impl AudioProcessor {
}
impl AudioProcessor {
pub fn process(&mut self, audio: &[f32], channels: usize, output: &mut Vec<f32>) -> TransmitState {
pub fn process(
&mut self,
audio: &[f32],
channels: usize,
output: &mut Vec<f32>,
) -> TransmitState {
let mut include_raw = true;
if self.denoise {
with_denoising_model(&self.spawn, |df| {
+5 -2
View File
@@ -7,7 +7,10 @@ use std::collections::HashMap;
use std::future::Future;
use std::time::Duration;
use super::{Platform, PlatformConfig, PlatformInit, PlatformNetwork, PlatformRuntime, SpawnHandleTrait};
use super::{
PlatformConfig, PlatformInit, PlatformInterface, PlatformNetwork, PlatformRuntime,
SpawnHandleTrait,
};
pub use super::connect::*;
pub use super::native_audio::*;
@@ -107,7 +110,7 @@ impl PlatformInit for DesktopPlatform {
}
}
impl Platform for DesktopPlatform {}
impl PlatformInterface for DesktopPlatform {}
fn get_config_path() -> std::path::PathBuf {
let strategy = choose_app_strategy(AppStrategyArgs {
+2 -2
View File
@@ -7,7 +7,7 @@ use mumble_web2_common::{ClientConfig, ServerStatus};
use std::future::Future;
use std::time::Duration;
use super::{Platform, PlatformConfig, PlatformInit, PlatformNetwork, PlatformRuntime, SpawnHandleTrait};
use super::{PlatformInterface, PlatformConfig, PlatformInit, PlatformNetwork, PlatformRuntime, SpawnHandleTrait};
pub use super::connect::*;
pub use super::native_audio::*;
@@ -107,7 +107,7 @@ impl PlatformInit for MobilePlatform {
}
}
impl Platform for MobilePlatform {}
impl PlatformInterface for MobilePlatform {}
pub fn set_default_username(_username: &str) -> Option<()> {
None
+45 -96
View File
@@ -15,8 +15,7 @@ use std::time::Duration;
// ============================================================================
/// Trait for spawn handles that can be stored and used to spawn tasks later.
#[cfg(feature = "web")]
pub trait SpawnHandleTrait: Clone + 'static {
pub trait SpawnHandleInterface: Clone + 'static {
/// Spawn an async task using this handle.
fn spawn<F>(&self, future: F)
where
@@ -26,51 +25,39 @@ pub trait SpawnHandleTrait: Clone + 'static {
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;
pub trait AudioSystemInterface {
type AudioPlayer: AudioPlayerInterface;
}
/// Runtime primitives: task spawning and async sleep.
#[cfg(feature = "web")]
pub trait PlatformRuntime {
/// The spawn handle type for this platform.
type SpawnHandle: SpawnHandleTrait;
pub trait AudioPlayerInterface {}
/// Spawn an async task.
fn spawn<F>(future: F)
where
F: Future<Output = ()> + 'static;
/// 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 PlatformInterface {
type AudioSystem: AudioSystemInterface;
type SpawnHandle: SpawnHandleInterface;
/// Async sleep for the given duration.
fn sleep(duration: Duration) -> impl Future<Output = ()>;
}
/// Initialize logging for the platform.
fn init_logging();
/// 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;
/// Request runtime permissions (Android audio recording, etc.).
fn request_permissions();
/// Spawn an async task.
fn spawn<F>(future: F)
where
F: Future<Output = ()> + Send + 'static;
/// 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>>;
/// Async sleep for the given duration.
fn sleep(duration: Duration) -> impl Future<Output = ()>;
}
/// Get server status (user count, version, etc.).
fn get_status(
client: &reqwest::Client,
) -> impl Future<Output = color_eyre::Result<ServerStatus>>;
/// Configuration persistence: loading and saving user preferences.
pub trait PlatformConfig {
/// Load the client configuration (proxy URL, cert hash, etc.).
/// Load the proxy overrides (proxy URL, cert hash, etc.).
fn load_config() -> impl Future<Output = color_eyre::Result<ClientConfig>>;
/// Load saved username.
@@ -84,39 +71,16 @@ pub trait PlatformConfig {
/// Save the default server URL.
fn set_default_server(server: &str) -> Option<()>;
/// 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 = ()>;
}
/// 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
// ============================================================================
@@ -138,36 +102,21 @@ mod mobile;
// Platform Type Alias
// ============================================================================
/// The current platform type, selected at compile time based on features.
#[cfg(feature = "web")]
pub type CurrentPlatform = web::WebPlatform;
pub type Platform = web::WebPlatform;
#[cfg(feature = "desktop")]
pub type CurrentPlatform = desktop::DesktopPlatform;
#[cfg(all(feature = "desktop", not(feature = "web")))]
pub type Platform = desktop::DesktopPlatform;
#[cfg(feature = "mobile")]
pub type CurrentPlatform = mobile::MobilePlatform;
#[cfg(all(feature = "mobile", not(feature = "web"), not(feature = "desktop")))]
pub type Platform = mobile::MobilePlatform;
pub type AudioSystem = <Platform as PlatformInterface>::AudioSystem;
pub type AudioPlayer = <AudioSystem as AudioSystemInterface>::AudioPlayer;
pub type SpawnHandle = <Platform as PlatformInterface>::SpawnHandle;
/// Compile-time assertion that CurrentPlatform implements Platform.
const _: () = {
fn assert_platform<T: Platform>() {}
let _ = assert_platform::<CurrentPlatform>;
fn assert_platform<T: PlatformInterface>() {}
let _ = assert_platform::<Platform>;
};
// ============================================================================
// Platform Re-exports
// ============================================================================
#[cfg(feature = "desktop")]
pub use desktop::*;
#[cfg(feature = "mobile")]
pub use mobile::*;
#[cfg(feature = "mobile")]
pub use mobile::request_permissions;
#[cfg(any(feature = "desktop", feature = "web"))]
pub fn request_permissions() {}
#[cfg(all(feature = "web", not(any(feature = "desktop", feature = "mobile"))))]
pub use web::*;
+2 -1
View File
@@ -124,7 +124,8 @@ impl AudioSystem {
if let Some(new_processor) = processors.take() {
current_processor = new_processor;
}
let state = current_processor.process(frame, config.channels as usize, &mut output_buffer);
let state =
current_processor.process(frame, config.channels as usize, &mut output_buffer);
encode_and_send(state, &mut output_buffer, &mut encoder, &mut each);
};
+32 -52
View File
@@ -38,8 +38,6 @@ use web_sys::WorkletOptions;
use web_sys::{console, window};
use web_sys::{AudioContext, AudioDataCopyToOptions};
use super::{Platform, PlatformConfig, PlatformInit, PlatformNetwork, PlatformRuntime, SpawnHandleTrait};
pub use wasm_bindgen_futures::spawn_local as spawn;
pub trait ImpRead: AsyncRead + Unpin + 'static {}
@@ -55,15 +53,14 @@ impl<T: AsyncWrite + Unpin + 'static> ImpWrite for T {}
/// Web platform implementation using WebTransport and Web Audio API.
pub struct WebPlatform;
pub async fn sleep(d: Duration) {
TimeoutFuture::new(d.as_millis() as u32).await
}
// ============================================================================
// Trait Implementations
// ============================================================================
impl SpawnHandleTrait for SpawnHandle {
#[derive(Clone)]
pub struct WebSpawnHandle;
impl super::SpawnHandleInterface for WebSpawnHandle {
fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + 'static,
@@ -72,26 +69,22 @@ impl SpawnHandleTrait for SpawnHandle {
}
fn current() -> Self {
SpawnHandle
WebSpawnHandle
}
}
impl PlatformRuntime for WebPlatform {
type SpawnHandle = SpawnHandle;
impl super::PlatformInterface for WebPlatform {
type AudioSystem = WebAudioSystem;
type SpawnHandle = WebSpawnHandle;
fn spawn<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
fn init_logging() {
init_logging();
}
async fn sleep(duration: Duration) {
TimeoutFuture::new(duration.as_millis() as u32).await;
fn request_permissions() {
// No-op on web
}
}
impl PlatformConfig for WebPlatform {
async fn load_config() -> color_eyre::Result<ClientConfig> {
load_config().await
}
@@ -111,9 +104,7 @@ impl PlatformConfig for WebPlatform {
fn set_default_server(server: &str) -> Option<()> {
set_default_server(server)
}
}
impl PlatformNetwork for WebPlatform {
async fn network_connect(
address: String,
username: String,
@@ -126,20 +117,19 @@ impl PlatformNetwork for WebPlatform {
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
get_status(client).await
}
}
impl PlatformInit for WebPlatform {
fn init_logging() {
init_logging();
fn spawn<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
}
fn request_permissions() {
// No-op on web
async fn sleep(duration: Duration) {
TimeoutFuture::new(duration.as_millis() as u32).await;
}
}
impl Platform for WebPlatform {}
trait ResultExt<T> {
fn ey(self) -> Result<T, Error>;
}
@@ -162,7 +152,7 @@ impl<T> ResultExt<T> for Result<T, JsError> {
}
}
pub struct AudioSystem {
pub struct WebAudioSystem {
webctx: AudioContext,
processors: AudioProcessorSender,
}
@@ -193,7 +183,11 @@ async fn attach_worklet(audio_context: &AudioContext) -> Result<(), Error> {
Ok(())
}
impl AudioSystem {
impl super::AudioSystemInterface for WebAudioSystem {
type AudioPlayer = WebAudioPlayer;
}
impl WebAudioSystem {
pub async fn new() -> Result<Self, Error> {
// Create MediaStreams to playback decoded audio
// The audio context is used to reproduce audio.
@@ -202,7 +196,7 @@ impl AudioSystem {
let processors = AudioProcessorSender::default();
Ok(AudioSystem { webctx, processors })
Ok(WebAudioSystem { webctx, processors })
}
pub fn set_processor(&self, processor: AudioProcessor) {
@@ -224,7 +218,7 @@ impl AudioSystem {
Ok(())
}
pub fn create_player(&mut self) -> Result<AudioPlayer, Error> {
pub fn create_player(&mut self) -> Result<WebAudioPlayer, Error> {
let sink_node = AudioWorkletNode::new(&self.webctx, "rust_speaker_worklet").ey()?;
// Connect worklet to destination
@@ -277,13 +271,15 @@ impl AudioSystem {
decoder_error.forget();
output.forget();
Ok(AudioPlayer(audio_decoder))
Ok(WebAudioPlayer(audio_decoder))
}
}
pub struct AudioPlayer(AudioDecoder);
pub struct WebAudioPlayer(AudioDecoder);
impl AudioPlayer {
impl super::AudioPlayerInterface for WebAudioPlayer {}
impl WebAudioPlayer {
pub fn play_opus(&mut self, payload: &[u8]) {
let js_audio_payload = Uint8Array::from(payload);
let _ = self.0.decode(
@@ -583,19 +579,3 @@ pub fn init_logging() {
info!("logging initiated");
}
#[derive(Clone)]
pub struct SpawnHandle;
impl SpawnHandle {
pub fn current() -> Self {
SpawnHandle
}
pub fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + 'static,
{
spawn(future);
}
}
+13 -8
View File
@@ -7,11 +7,12 @@ use asynchronous_codec::FramedWrite;
use color_eyre::eyre::{bail, Error};
use dioxus::prelude::*;
use futures::select;
use futures::AsyncRead;
use futures::AsyncWrite;
use futures::FutureExt as _;
use futures::SinkExt as _;
use futures::StreamExt as _;
use futures_channel::mpsc::UnboundedSender;
pub use imp::spawn;
use msghtml::process_message_html;
use mumble_protocol::control::msgs;
use mumble_protocol::control::ControlCodec;
@@ -27,7 +28,9 @@ use tracing::error;
use tracing::info;
use crate::effects::AudioProcessor;
use crate::imp::{AudioSystem, CurrentPlatform, Platform, PlatformNetwork, PlatformRuntime};
use crate::imp::{
AudioPlayer, AudioSystem, AudioSystemInterface as _, Platform, PlatformInterface as _,
};
pub mod app;
mod effects;
@@ -47,7 +50,9 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
*STATE.server.write() = Default::default();
*STATE.status.write() = ConnectionState::Connecting;
if let Err(error) = CurrentPlatform::network_connect(address, username, &mut event_rx, &config).await {
if let Err(error) =
Platform::network_connect(address, username, &mut event_rx, &config).await
{
error!("could not connect {:?}", error);
*STATE.status.write() = ConnectionState::Failed(error.to_string());
} else {
@@ -56,7 +61,7 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
}
}
pub async fn network_loop<R: imp::ImpRead, W: imp::ImpWrite>(
pub async fn network_loop<R: AsyncRead + Unpin + 'static, W: AsyncWrite + Unpin + 'static>(
username: String,
event_rx: &mut UnboundedReceiver<Command>,
mut reader: FramedRead<R, ControlCodec<Serverbound, Clientbound>>,
@@ -105,12 +110,12 @@ pub async fn network_loop<R: imp::ImpRead, W: imp::ImpWrite>(
break;
}
CurrentPlatform::sleep(Duration::from_millis(3000)).await;
Platform::sleep(Duration::from_millis(3000)).await;
}
});
}
let mut audio = imp::AudioSystem::new().await?;
let mut audio = AudioSystem::new().await?;
{
let send_chan = send_chan.clone();
let mut sequence_num = 0;
@@ -296,8 +301,8 @@ fn accept_command(
fn accept_packet(
msg: ControlPacket<mumble_protocol::Clientbound>,
audio_context: &mut imp::AudioSystem,
player_map: &mut HashMap<u32, imp::AudioPlayer>,
audio_context: &mut AudioSystem,
player_map: &mut HashMap<u32, AudioPlayer>,
) -> Result<(), Error> {
match msg {
ControlPacket::UDPTunnel(u) => {
+2 -2
View File
@@ -1,6 +1,6 @@
use mumble_web2_gui::{app, imp};
use mumble_web2_gui::{app, imp::Platform, imp::PlatformInterface as _};
pub fn main() {
imp::init_logging();
Platform::init_logging();
dioxus::launch(app::app);
}