Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: per client state #330

Merged
merged 40 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9553267
feat(socketio/extensions): use `RwLock<HashMap>` rather than `DashMap`
Totodore Apr 19, 2024
46c7a1b
chore(bench): add bencher ci
Totodore Apr 19, 2024
d7d6a3f
fix: socketioxide benches with `Bytes`
Totodore Apr 19, 2024
c307a73
chore(bench): fix ci name
Totodore Apr 19, 2024
2894317
chore(bench): add RUSTFLAG for testing
Totodore Apr 19, 2024
44d73ba
fix: engineioxide benches
Totodore Apr 19, 2024
191e3fa
chore(bench): remove matrix test
Totodore Apr 19, 2024
66aeef9
chore(bench): add groups
Totodore Apr 19, 2024
82fefb5
chore(bench): improve extensions bench
Totodore Apr 19, 2024
3652d0a
Merge branch 'bencher' into feat-extensions-rework
Totodore Apr 20, 2024
df530f1
Merge branch 'main' into feat-extensions-rework
Totodore Apr 20, 2024
164a7ae
feat(socketio/extract): refactor extract mod
Totodore Apr 20, 2024
f6008a5
feat(socketio/extract): add `(Maybe)(Http)Extension` extractors
Totodore Apr 20, 2024
ecff81a
docs(example): update examples with `Extension` extractor
Totodore Apr 20, 2024
5070dd0
test(socketio/extract): add tests for `Extension` and `MaybeExtension`
Totodore Apr 20, 2024
a744f7b
docs(example) fmt chat example
Totodore Apr 20, 2024
bf8daab
Merge branch 'main' into feat-extensions-rework
Totodore Apr 20, 2024
f7106db
Merge branch 'main' into feat-extensions-rework
Totodore Apr 21, 2024
50372f2
test(socketio): fix extractors test
Totodore Apr 21, 2024
76c72ca
doc(socketio): improve doc for socketioxide
Totodore Apr 21, 2024
df94f5c
test(socketio): increase timeout
Totodore Apr 21, 2024
5888b37
Merge branch 'main' into feat-extensions-rework
Totodore May 6, 2024
1805c10
Merge branch 'main' into feat-extensions-rework
Totodore May 10, 2024
7b22160
doc(socketio): improve doc
Totodore May 21, 2024
6887304
feat(io): store io client in socketdata so it is possible to retrieve…
Totodore Jun 3, 2024
4aebb03
feat(state): per client state
Totodore Jun 4, 2024
334e32a
doc(socketio): improve doc
Totodore May 21, 2024
567ca16
Merge branch 'feat-extensions-rework' into feat-io-client-in-socketdata
Totodore Jun 4, 2024
a46a733
Merge branch 'feat-io-client-in-socketdata' into feat-per-client-state
Totodore Jun 4, 2024
b45c5a9
Merge branch 'main' into feat-per-client-state
Totodore Jun 5, 2024
12f43bf
Merge branch 'main' into feat-per-client-state
Totodore Jun 5, 2024
185fee6
test: add state for client init
Totodore Jun 5, 2024
a6326d0
test
Totodore Jun 5, 2024
2358ee9
doc(examples): fix examples
Totodore Jun 6, 2024
f0b7547
doc(examples): fix examples
Totodore Jun 6, 2024
527789e
doc(examples): fix loco example
Totodore Jun 6, 2024
43afa4b
doc(examples): fix state example
Totodore Jun 6, 2024
ecc8bed
test: revert excessive timeout due to previous state collision
Totodore Jun 6, 2024
8e54403
Merge branch 'main' into feat-per-client-state
Totodore Jun 7, 2024
1708136
doc(socketio): improve documentation for state per client feature
Totodore Jun 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/angular-todomvc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ use socketioxide::{
extract::{Data, SocketRef, State},
SocketIo,
};
use std::sync::Arc;
use tower::ServiceBuilder;
use tower_http::{cors::CorsLayer, services::ServeDir};
use tracing::info;
use tracing_subscriber::FmtSubscriber;

#[derive(Default)]
struct Todos(pub Mutex<Vec<Todo>>);
#[derive(Default, Clone)]
struct Todos(Arc<Mutex<Vec<Todo>>>);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Todo {
Expand Down
6 changes: 3 additions & 3 deletions examples/basic-crud-application/src/handlers/todo.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::HashMap, sync::RwLock};
use std::{collections::HashMap, sync::{RwLock, Arc}};

use serde::{Deserialize, Serialize};
use socketioxide::extract::{AckSender, Data, SocketRef, State};
Expand All @@ -21,8 +21,8 @@ pub struct PartialTodo {
title: String,
}

#[derive(Default)]
pub struct Todos(RwLock<HashMap<Uuid, Todo>>);
#[derive(Clone, Default)]
pub struct Todos(Arc<RwLock<HashMap<Uuid, Todo>>>);
impl Todos {
fn insert(&self, id: Uuid, todo: Todo) {
self.0.write().unwrap().insert(id, todo);
Expand Down
7 changes: 4 additions & 3 deletions examples/chat/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use tower::ServiceBuilder;
use tower_http::{cors::CorsLayer, services::ServeDir};
use tracing::info;
use tracing_subscriber::FmtSubscriber;
use std::sync::Arc;

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(transparent)]
Expand All @@ -34,11 +35,11 @@ enum Res {
username: Username,
},
}

struct UserCnt(AtomicUsize);
#[derive(Clone)]
struct UserCnt(Arc<AtomicUsize>);
impl UserCnt {
fn new() -> Self {
Self(AtomicUsize::new(0))
Self(Arc::new(AtomicUsize::new(0)))
}
fn add_user(&self) -> usize {
self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1
Expand Down
2 changes: 1 addition & 1 deletion examples/loco-rooms-chat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ rust-version = "1.70" # required by loco

[dependencies]

loco-rs = { version = "0.3.1", default-features = false, features = [
loco-rs = { version = "0.5.0", default-features = false, features = [
"cli",
"channels",
] }
Expand Down
5 changes: 3 additions & 2 deletions examples/loco-rooms-chat/src/channels/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::{HashMap, VecDeque};
use tokio::sync::RwLock;
use std::sync::Arc;

#[derive(serde::Serialize, Clone, Debug)]
pub struct Message {
Expand All @@ -10,9 +11,9 @@ pub struct Message {

pub type RoomStore = HashMap<String, VecDeque<Message>>;

#[derive(Default)]
#[derive(Default, Clone)]
pub struct MessageStore {
pub messages: RwLock<RoomStore>,
pub messages: Arc<RwLock<RoomStore>>,
}

impl MessageStore {
Expand Down
8 changes: 4 additions & 4 deletions examples/private-messaging/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ pub struct Message {
pub content: String,
}

#[derive(Default)]
pub struct Sessions(RwLock<HashMap<Uuid, Arc<Session>>>);
#[derive(Clone, Default)]
pub struct Sessions(Arc<RwLock<HashMap<Uuid, Arc<Session>>>>);

impl Sessions {
pub fn get_all_other_sessions(&self, user_id: Uuid) -> Vec<Arc<Session>> {
Expand All @@ -69,8 +69,8 @@ impl Sessions {
self.0.write().unwrap().insert(session.session_id, session);
}
}
#[derive(Default)]
pub struct Messages(RwLock<Vec<Message>>);
#[derive(Clone, Default)]
pub struct Messages(Arc<RwLock<Vec<Message>>>);

impl Messages {
pub fn add(&self, message: Message) {
Expand Down
4 changes: 2 additions & 2 deletions examples/react-rooms-chat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
socketioxide = { version = "0.9.0", features = ["state"] }
socketioxide = { path = "../../socketioxide", features = ["state"] }
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
axum = "0.7.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tower-http = {version = "0.5.0", features = ["cors"]}
tower-http = { version = "0.5.0", features = ["cors"] }
tower = "0.4"
chrono = { version = "0.4", features = ["serde"] }
6 changes: 3 additions & 3 deletions examples/react-rooms-chat/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{HashMap, VecDeque};
use std::{collections::{HashMap, VecDeque}, sync::Arc};
use tokio::sync::RwLock;

#[derive(serde::Serialize, Clone, Debug)]
Expand All @@ -10,9 +10,9 @@ pub struct Message {

pub type RoomStore = HashMap<String, VecDeque<Message>>;

#[derive(Default)]
#[derive(Clone, Default)]
pub struct MessageStore {
pub messages: RwLock<RoomStore>,
pub messages: Arc<RwLock<RoomStore>>,
}

impl MessageStore {
Expand Down
2 changes: 1 addition & 1 deletion socketioxide/src/adapter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Adapters are responsible for managing the state of the server.
//! Adapters are responsible for managing the internal state of the server (rooms, sockets, etc...).
//! When a socket joins or leaves a room, the adapter is responsible for updating the state.
//! The default adapter is the [`LocalAdapter`], which stores the state in memory.
//! Other adapters can be made to share the state between multiple servers.
Expand Down
27 changes: 23 additions & 4 deletions socketioxide/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,26 @@ use crate::{
};
use crate::{ProtocolVersion, SocketIo};

#[derive(Debug)]
pub struct Client<A: Adapter> {
pub(crate) config: Arc<SocketIoConfig>,
ns: RwLock<HashMap<Cow<'static, str>, Arc<Namespace<A>>>>,
#[cfg(feature = "state")]
pub(crate) state: state::TypeMap![Send + Sync],
}

impl<A: Adapter> Client<A> {
pub fn new(config: Arc<SocketIoConfig>) -> Self {
pub fn new(
config: Arc<SocketIoConfig>,
#[cfg(feature = "state")] mut state: state::TypeMap![Send + Sync],
) -> Self {
#[cfg(feature = "state")]
crate::state::freeze_state();
state.freeze();

Self {
config,
ns: RwLock::new(HashMap::new()),
#[cfg(feature = "state")]
state,
}
}

Expand Down Expand Up @@ -346,6 +352,15 @@ impl<A: Adapter> EngineIoHandler for Client<A> {
}
}
}
impl<A: Adapter> std::fmt::Debug for Client<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut f = f.debug_struct("Client");
f.field("config", &self.config).field("ns", &self.ns);
#[cfg(feature = "state")]
let f = f.field("state", &self.state);
f.finish()
}
}

/// Utility that applies an incoming binary payload to a partial binary packet
/// waiting to be filled with all the payloads
Expand Down Expand Up @@ -382,7 +397,11 @@ mod test {
connect_timeout: CONNECT_TIMEOUT,
..Default::default()
};
let client = Client::<LocalAdapter>::new(std::sync::Arc::new(config));
let client = Client::<LocalAdapter>::new(
std::sync::Arc::new(config),
#[cfg(feature = "state")]
Default::default(),
);
client.add_ns("/".into(), || {});
Arc::new(client)
}
Expand Down
2 changes: 1 addition & 1 deletion socketioxide/src/extract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//! * [`ProtocolVersion`](crate::ProtocolVersion): extracts the protocol version
//! * [`TransportType`](crate::TransportType): extracts the transport type
//! * [`DisconnectReason`](crate::socket::DisconnectReason): extracts the reason of the disconnection
//! * [`State`]: extracts a reference to a state previously set with [`SocketIoBuilder::with_state`](crate::io::SocketIoBuilder).
//! * [`State`]: extracts a [`Clone`] of a state previously set with [`SocketIoBuilder::with_state`](crate::io::SocketIoBuilder).
//! * [`Extension`]: extracts an extension of the given type stored on the called socket by cloning it.
//! * [`MaybeExtension`]: extracts an extension of the given type if it exists or [`None`] otherwise
//! * [`HttpExtension`]: extracts an http extension of the given type coming from the request.
Expand Down
44 changes: 19 additions & 25 deletions socketioxide/src/extract/state.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use bytes::Bytes;

use crate::state::get_state;
use std::ops::Deref;
use std::sync::Arc;

use crate::adapter::Adapter;
use crate::handler::{FromConnectParts, FromDisconnectParts, FromMessageParts};
use crate::socket::{DisconnectReason, Socket};

/// An Extractor that contains a reference to a state previously set with [`SocketIoBuilder::with_state`](crate::io::SocketIoBuilder).
/// An Extractor that contains a [`Clone`] of a state previously set with [`SocketIoBuilder::with_state`](crate::io::SocketIoBuilder).
/// It implements [`std::ops::Deref`] to access the inner type so you can use it as a normal reference.
///
/// The specified state type must be the same as the one set with [`SocketIoBuilder::with_state`](crate::io::SocketIoBuilder).
Expand All @@ -20,11 +18,10 @@ use crate::socket::{DisconnectReason, Socket};
/// ```
/// # use socketioxide::{SocketIo, extract::{SocketRef, State}};
/// # use serde::{Serialize, Deserialize};
/// # use std::sync::atomic::AtomicUsize;
/// # use std::sync::atomic::Ordering;
/// #[derive(Default)]
/// # use std::sync::{Arc, atomic::{Ordering, AtomicUsize}};
/// #[derive(Default, Clone)]
/// struct MyAppData {
/// user_cnt: AtomicUsize,
/// user_cnt: Arc<AtomicUsize>,
/// }
/// impl MyAppData {
/// fn add_user(&self) {
Expand All @@ -39,7 +36,7 @@ use crate::socket::{DisconnectReason, Socket};
/// state.add_user();
/// println!("User count: {}", state.user_cnt.load(Ordering::SeqCst));
/// });
pub struct State<T: 'static>(pub &'static T);
pub struct State<T>(pub T);

/// It was impossible to find the given state and therefore the handler won't be called.
pub struct StateNotFound<T>(std::marker::PhantomData<T>);
Expand All @@ -48,7 +45,7 @@ impl<T> std::fmt::Display for StateNotFound<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"State of type {} not found, maybe you forgot to insert it in the extensions map?",
"State of type {} not found, maybe you forgot to insert it in the state map?",
std::any::type_name::<T>()
)
}
Expand All @@ -60,46 +57,43 @@ impl<T> std::fmt::Debug for StateNotFound<T> {
}
impl<T> std::error::Error for StateNotFound<T> {}

impl<A: Adapter, T: Send + Sync + 'static> FromConnectParts<A> for State<T> {
impl<A: Adapter, T: Clone + Send + Sync + 'static> FromConnectParts<A> for State<T> {
type Error = StateNotFound<T>;
fn from_connect_parts(
_: &Arc<Socket<A>>,
s: &Arc<Socket<A>>,
_: &Option<String>,
) -> Result<Self, StateNotFound<T>> {
get_state::<T>()
s.get_io()
.get_state::<T>()
.map(State)
.ok_or(StateNotFound(std::marker::PhantomData))
}
}
impl<A: Adapter, T: Send + Sync + 'static> FromDisconnectParts<A> for State<T> {
impl<A: Adapter, T: Clone + Send + Sync + 'static> FromDisconnectParts<A> for State<T> {
type Error = StateNotFound<T>;
fn from_disconnect_parts(
_: &Arc<Socket<A>>,
s: &Arc<Socket<A>>,
_: DisconnectReason,
) -> Result<Self, StateNotFound<T>> {
get_state::<T>()
s.get_io()
.get_state::<T>()
.map(State)
.ok_or(StateNotFound(std::marker::PhantomData))
}
}
impl<A: Adapter, T: Send + Sync + 'static> FromMessageParts<A> for State<T> {
impl<A: Adapter, T: Clone + Send + Sync + 'static> FromMessageParts<A> for State<T> {
type Error = StateNotFound<T>;
fn from_message_parts(
_: &Arc<Socket<A>>,
s: &Arc<Socket<A>>,
_: &mut serde_json::Value,
_: &mut Vec<Bytes>,
_: &Option<i64>,
) -> Result<Self, StateNotFound<T>> {
get_state::<T>()
s.get_io()
.get_state::<T>()
.map(State)
.ok_or(StateNotFound(std::marker::PhantomData))
}
}

impl<T> Deref for State<T> {
type Target = &'static T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
super::__impl_deref!(State);
Loading