//! This module provides a central `Context` type that ties together all the //! different bits of client state. use parking_lot::Mutex; use solstice_proto::ServerRequest; use tokio::sync::mpsc::{channel, Receiver, Sender}; use crate::control::Response as ControlResponse; use crate::room::RoomMap; use crate::user::UserMap; /// Contains all the different bits of client state. #[derive(Debug, Default)] pub struct State { pub rooms: RoomMap, pub users: UserMap, } /// Holds process-wide context for message handlers to execute against. #[derive(Debug)] pub struct Context { /// Mutable state. pub state: Mutex, /// Sender half of a channel used to send requests to the server. pub server_request_tx: Sender, /// Sender half of a channel used to send responses to the controller. pub control_response_tx: Sender, } /// Convenience bundle for creating new `Context` structs. #[derive(Debug)] pub struct ContextBundle { /// The context itself. pub context: Context, /// The receiver corresponding to `context.server_request_tx`. pub server_request_rx: Receiver, /// The receiver corresponsing to `context.control_response_tx`. pub control_response_rx: Receiver, } /// Specifies options for new `ContextBundle` structs. #[derive(Debug)] pub struct ContextOptions { /// The state to start out with. pub initial_state: State, /// The buffer size of the server request channel. pub server_request_buffer: usize, /// The buffer size of the control response channel. pub control_response_buffer: usize, } impl Default for ContextOptions { fn default() -> Self { Self { initial_state: State::default(), server_request_buffer: 100, control_response_buffer: 100, } } } impl ContextBundle { /// Builds a new context bundle as configured by `options`. fn new(options: ContextOptions) -> Self { let (server_request_tx, server_request_rx) = channel(options.server_request_buffer); let (control_response_tx, control_response_rx) = channel(options.control_response_buffer); Self { context: Context { state: Mutex::new(options.initial_state), server_request_tx, control_response_tx, }, server_request_rx, control_response_rx, } } } impl Default for ContextBundle { fn default() -> Self { Self::new(ContextOptions::default()) } } #[cfg(test)] mod tests { use super::{Context, ContextBundle, State}; #[test] fn default_state_is_empty() { let state = State::default(); assert_eq!(state.rooms.get_room_list(), vec![]); assert_eq!(state.users.get_list(), vec![]); } #[test] fn context_is_sync() { let option: Option = None; if let Some(context) = option { let _sync: &dyn Sync = &context; } } #[test] fn default_bundle_state_is_empty() { let bundle = ContextBundle::default(); let guard = bundle.context.state.lock(); assert_eq!(guard.rooms.get_room_list(), vec![]); assert_eq!(guard.users.get_list(), vec![]); } }