diff --git a/Cargo.lock b/Cargo.lock index 3738494..76f03c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4235,7 +4235,6 @@ dependencies = [ "dasp_ring_buffer", "deep_filter", "dioxus-asset-resolver", - "dioxus-signals", "etcetera", "futures", "futures-channel", diff --git a/client/Cargo.toml b/client/Cargo.toml index 6698c75..ae4041e 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -66,7 +66,6 @@ etcetera = { version = "0.10.0", optional = true } # Base Dependencies # ================ -dioxus-signals = "0.7.2" manganis = "0.7.2" once_cell = "1.19.0" asynchronous-codec = { workspace = true } diff --git a/client/src/app.rs b/client/src/app.rs index 41f8721..e98f809 100644 --- a/client/src/app.rs +++ b/client/src/app.rs @@ -1,8 +1,8 @@ -use dioxus_signals::{ReadableExt as _, Signal}; use mime_guess::Mime; use mumble_web2_common::ProxyOverrides; use ordermap::OrderSet; use std::collections::{HashMap, HashSet}; +use std::ops::{Deref, DerefMut}; use std::{fmt, sync::Arc}; pub type ChannelId = u32; @@ -197,19 +197,27 @@ impl ServerState { } } -pub struct State { - pub status: Signal, - pub server: Signal, - pub audio: Signal, +pub trait Reactivity { + type Signal; + + fn new(value: T) -> Self::Signal; + fn read(signal: &Self::Signal) -> impl Deref; + fn write(signal: &Self::Signal) -> impl DerefMut; } -impl fmt::Debug for State { +pub struct State { + pub status: R::Signal, + pub server: R::Signal, + pub audio: R::Signal, +} + +impl fmt::Debug for State { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("State") - .field("status", &self.status.read()) - .field("server", &self.server.read()) + .field("status", &*R::read(&self.status)) + .field("server", &*R::read(&self.server)) .finish() } } -pub type SharedState = Arc; +pub type SharedState = Arc>; diff --git a/client/src/imp/connect.rs b/client/src/imp/connect.rs index b6654b4..fcb3853 100644 --- a/client/src/imp/connect.rs +++ b/client/src/imp/connect.rs @@ -1,4 +1,5 @@ use crate::app::{Command, SharedState}; +use crate::Reactivity; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_protocol::control::ClientControlCodec; @@ -74,7 +75,7 @@ pub async fn network_connect( username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, - state: SharedState, + state: SharedState, ) -> Result<(), Error> { info!("connecting"); diff --git a/client/src/imp/desktop.rs b/client/src/imp/desktop.rs index 9da9e62..eade5f4 100644 --- a/client/src/imp/desktop.rs +++ b/client/src/imp/desktop.rs @@ -1,4 +1,5 @@ use crate::app::{Command, SharedState}; +use crate::Reactivity; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; @@ -28,7 +29,7 @@ impl super::PlatformInterface for DesktopPlatform { username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, - state: SharedState, + state: SharedState, ) -> Result<(), Error> { super::connect::network_connect(address, username, event_rx, overrides, state).await } diff --git a/client/src/imp/mobile.rs b/client/src/imp/mobile.rs index a973920..3f641b1 100644 --- a/client/src/imp/mobile.rs +++ b/client/src/imp/mobile.rs @@ -1,4 +1,5 @@ use crate::app::{Command, SharedState}; +use crate::Reactivity; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; @@ -24,7 +25,7 @@ impl super::PlatformInterface for MobilePlatform { username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, - state: SharedState, + state: SharedState, ) -> Result<(), Error> { super::connect::network_connect(address, username, event_rx, overrides, state).await } diff --git a/client/src/imp/mod.rs b/client/src/imp/mod.rs index c9bd707..60f0c2d 100644 --- a/client/src/imp/mod.rs +++ b/client/src/imp/mod.rs @@ -6,6 +6,7 @@ use crate::app::{Command, SharedState}; use crate::effects::AudioProcessor; +use crate::Reactivity; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; @@ -83,7 +84,7 @@ pub trait PlatformInterface { username: String, event_rx: &mut UnboundedReceiver, proxy_overrides: &ProxyOverrides, - state: SharedState, + state: SharedState, ) -> impl Future>; /// Get server status (user count, version, etc.) for the given address. diff --git a/client/src/imp/stub.rs b/client/src/imp/stub.rs index 6744514..7122a22 100644 --- a/client/src/imp/stub.rs +++ b/client/src/imp/stub.rs @@ -1,6 +1,6 @@ /// Stub implementation of the platform interface, so that we can /// `cargo check` without any --feature flags. -use crate::{app::SharedState, effects::AudioProcessor}; +use crate::{app::SharedState, effects::AudioProcessor, Reactivity}; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; @@ -25,7 +25,7 @@ impl super::PlatformInterface for StubPlatform { _username: String, _event_rx: &mut UnboundedReceiver, _overrides: &ProxyOverrides, - _state: SharedState, + _state: SharedState, ) -> impl Future> { async { panic!("stubbed platform") } } diff --git a/client/src/imp/web.rs b/client/src/imp/web.rs index 83080ec..835282d 100644 --- a/client/src/imp/web.rs +++ b/client/src/imp/web.rs @@ -1,5 +1,6 @@ use crate::app::{Command, SharedState}; use crate::effects::{AudioProcessor, AudioProcessorSender, TransmitState}; +use crate::Reactivity; use color_eyre::eyre::{bail, eyre, Error}; use crossbeam::atomic::AtomicCell; use futures_channel::mpsc::UnboundedReceiver; @@ -112,7 +113,7 @@ impl super::PlatformInterface for WebPlatform { username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, - state: SharedState, + state: SharedState, ) -> Result<(), Error> { network_connect(address, username, event_rx, overrides, state).await } diff --git a/client/src/mainloop.rs b/client/src/mainloop.rs index b6451e5..f6e038a 100644 --- a/client/src/mainloop.rs +++ b/client/src/mainloop.rs @@ -3,11 +3,10 @@ use crate::AudioSettings; use crate::Chat; use crate::Command; use crate::ConnectionState; +use crate::Reactivity; use asynchronous_codec::FramedRead; use asynchronous_codec::FramedWrite; use color_eyre::eyre::{bail, Error}; -use dioxus_signals::ReadableExt as _; -use dioxus_signals::WritableExt as _; use futures::select; use futures::AsyncRead; use futures::AsyncWrite; @@ -36,7 +35,10 @@ use crate::imp::{ Platform, PlatformInterface as _, }; -pub async fn network_entrypoint(mut event_rx: UnboundedReceiver, state: SharedState) { +pub async fn network_entrypoint( + mut event_rx: UnboundedReceiver, + state: SharedState, +) { loop { let Some(Command::Connect { address, @@ -47,16 +49,16 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver, state: panic!("did not receive connect command") }; - *state.server.write_unchecked() = Default::default(); - *state.status.write_unchecked() = ConnectionState::Connecting; + *X::write(&state.server) = Default::default(); + *X::write(&state.status) = ConnectionState::Connecting; if let Err(error) = Platform::network_connect(address, username, &mut event_rx, &config, state.clone()) .await { error!("could not connect {:?}", error); - *state.status.write_unchecked() = ConnectionState::Failed(error.to_string()); + *X::write(&state.status) = ConnectionState::Failed(error.to_string()); } else { - *state.status.write_unchecked() = ConnectionState::Disconnected; + *X::write(&state.status) = ConnectionState::Disconnected; } } } @@ -76,14 +78,14 @@ pub(crate) async fn sender_loop( } } -pub(crate) async fn network_loop( +pub(crate) async fn network_loop( username: String, - state: SharedState, + state: SharedState, event_rx: &mut UnboundedReceiver, mut outgoing: UnboundedSender>, mut reader: FramedRead>, ) -> Result<(), Error> { - let audio_settings = state.audio.read().clone(); + let audio_settings = X::read(&state.audio).clone(); // Get version packet let version = match reader.next().await { @@ -190,14 +192,14 @@ pub(crate) async fn network_loop( Ok(()) } -fn accept_command( +fn accept_command( command: Command, send_chan: &mut UnboundedSender>, audio: &mut AudioSystem, - state: &State, + state: &State, ) -> Result<(), Error> { use Command::*; - let Some(session) = state.server.read().session else { + let Some(session) = X::read(&state.server).session else { bail!("no session id") }; @@ -220,7 +222,7 @@ fn accept_command( }; { - let mut server = state.server.write_unchecked(); + let mut server = X::write(&state.server); let Some(me) = server.session else { bail!("not signed in with a session id") }; @@ -261,7 +263,7 @@ fn accept_command( }; { - let mut server = state.server.write_unchecked(); + let mut server = X::write(&state.server); let Some(me) = server.session else { bail!("not signed in with a session id") }; @@ -304,11 +306,11 @@ fn accept_command( Ok(()) } -fn accept_packet( +fn accept_packet( msg: ControlPacket, audio_context: &mut AudioSystem, player_map: &mut HashMap, - state: &State, + state: &State, ) -> Result<(), Error> { match msg { ControlPacket::UDPTunnel(u) => { @@ -345,15 +347,15 @@ fn accept_packet( } } ControlPacket::ChannelState(u) => { - let mut server = state.server.write_unchecked(); + let mut server = X::write(&state.server); server.channels_state.update_from_channel_state(&u); } ControlPacket::ChannelRemove(u) => { - let mut server = state.server.write_unchecked(); + let mut server = X::write(&state.server); server.channels_state.update_from_channel_remove(&u); } ControlPacket::UserState(u) => { - let mut server = state.server.write_unchecked(); + let mut server = X::write(&state.server); let server = &mut *server; let id = u.get_session(); @@ -397,7 +399,7 @@ fn accept_packet( } } ControlPacket::UserRemove(u) => { - let mut server = state.server.write_unchecked(); + let mut server = X::write(&state.server); let id = u.get_session(); if let Some(state) = server.users.remove(&id) { if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) { @@ -406,7 +408,7 @@ fn accept_packet( } } ControlPacket::TextMessage(u) => { - let mut server = state.server.write_unchecked(); + let mut server = X::write(&state.server); if u.has_message() { let text = u.get_message().to_string(); server.chat.push(Chat { @@ -421,8 +423,8 @@ fn accept_packet( } } ControlPacket::ServerSync(u) => { - *state.status.write_unchecked() = ConnectionState::Connected; - let mut server = state.server.write_unchecked(); + *X::write(&state.status) = ConnectionState::Connected; + let mut server = X::write(&state.server); if u.has_welcome_text() { let text = u.get_welcome_text().to_string(); server.chat.push(Chat { diff --git a/gui/src/main.rs b/gui/src/main.rs index ecdccd4..a424191 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; use mumble_web2_client::{ network_entrypoint, reqwest, AudioSettings, ChannelId, Command, ConfigSystem, ConfigSystemInterface as _, ConnectionState, Platform, PlatformInterface as _, ServerState, - SharedState, State, UserId, UserState, + UserId, UserState, }; use mumble_web2_common::{ProxyOverrides, ServerStatus}; use std::collections::{HashMap, HashSet}; @@ -12,6 +12,27 @@ use std::{fmt, sync::Arc}; use Command::*; use ConnectionState::*; +pub struct DioxusReactivity; + +impl mumble_web2_client::Reactivity for DioxusReactivity { + type Signal = Signal; + + fn new(value: T) -> Signal { + Signal::new(value) + } + + fn read(signal: &Signal) -> impl std::ops::Deref { + signal.read_unchecked() + } + + fn write(signal: &Signal) -> impl std::ops::DerefMut { + signal.write_unchecked() + } +} + +pub type SharedState = mumble_web2_client::SharedState; +pub type State = mumble_web2_client::State; + #[derive(Clone, Copy, PartialEq, Eq)] pub enum UserIcon { Normal, @@ -519,8 +540,7 @@ pub fn LoginView(overrides: Resource) -> Element { async move { let client = reqwest::Client::new(); loop { - *last_status.write_unchecked() = - Some(Platform::get_status(&client, &addr).await); + *last_status.write_unchecked() = Some(Platform::get_status(&client, &addr).await); Platform::sleep(std::time::Duration::from_secs_f32(1.0)).await; } }