//! This file contains a system for signaling Discord RPC context changes between threads. //! //! ## Description //! //! The idea is that the thread containing the Discord RPC client can hold a `StatefulDiscordRpcSignalHandler` as a stateful context. //! The game thread can then send `DiscordRpcSignal` values through an `mpsc` sender, which will be received by the Discord RPC client thread. use chrono::Utc; use discord_sdk::activity::{ActivityBuilder, Assets}; /// Definitions of signals that can be sent to the Discord RPC thread to control how discord displays game status. #[derive(Debug, Clone)] pub enum DiscordRpcSignal { /// Signal to begin a game timer (Discord will display `XX:XX elapsed`) BeginGameTimer, /// Signal to end a game timer EndGameTimer, /// Signal to begin a countdown timer (Discord will display `XX:XX left`) SetGameTimeRemainingTimestamp(chrono::DateTime), /// Signal to clear the game remaining timer ClearGameTimeRemaining, /// Signal to set the details in the info card ChangeDetails { /// What the player is doing, eg. “Exploring the Wilds of Outland”. /// /// Limited to 128 bytes. details: String, /// The user’s currenty party status, eg. “Playing Solo”. /// /// Limited to 128 bytes. party_status: Option, }, /// Signal to change the graphical assets in the info card ChangeAssets(Assets), } /// A struct that can keep track of incoming signals and their effect on Discord #[derive(Default, Debug, Clone)] pub struct StatefulDiscordRpcSignalHandler { game_start_timer: Option>, game_end_timer: Option>, game_details: Option, game_party_status: Option, game_assets: Option, } impl StatefulDiscordRpcSignalHandler { /// Apply a signal to generate a new activity pub fn apply(&mut self, signal: DiscordRpcSignal) -> ActivityBuilder { // Fill in the data based on the contents of the signal match signal { DiscordRpcSignal::BeginGameTimer => self.game_start_timer = Some(chrono::Utc::now()), DiscordRpcSignal::EndGameTimer => self.game_start_timer = None, DiscordRpcSignal::SetGameTimeRemainingTimestamp(timestamp) => { self.game_end_timer = Some(timestamp) } DiscordRpcSignal::ClearGameTimeRemaining => self.game_end_timer = None, DiscordRpcSignal::ChangeDetails { details, party_status, } => { self.game_details = Some(details); self.game_party_status = party_status; } DiscordRpcSignal::ChangeAssets(assets) => self.game_assets = Some(assets), } // Decide how to build the Discord RPC activity let mut builder = ActivityBuilder::default(); if let Some(start_time) = &self.game_start_timer { builder = builder.start_timestamp(start_time.timestamp()); } if let Some(end_time) = &self.game_end_timer { builder = builder.end_timestamp(end_time.timestamp()); } if let Some(details) = &self.game_details { builder = builder.details(details); } if let Some(party_status) = &self.game_party_status { builder = builder.state(party_status); } if let Some(assets) = &self.game_assets { builder = builder.assets(assets.clone()); } return builder; } }