Add a stateful discord activity system

This commit is contained in:
Evan Pratten 2022-03-18 12:10:46 -04:00
parent 467a525d01
commit 683d90fe1d
5 changed files with 111 additions and 2 deletions

View File

@ -12,4 +12,6 @@ log = "0.4.14"
profiling = "1.0.5"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
directories = "4.0.1"
directories = "4.0.1"
chrono = { verison = "0.4.19", features = ["serde"] }
discord-sdk = "0.3.0"

View File

@ -0,0 +1,4 @@
//! This module contains code needed for interacting with a local Discord instance.
mod signal;
pub use signal::DiscordRpcSignal;

View File

@ -0,0 +1,94 @@
//! 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, IntoTimestamp};
/// Definitions of signals that can be sent to the Discord RPC thread to control how discord displays game status.
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<Utc>),
/// 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 users currenty party status, eg. “Playing Solo”.
///
/// Limited to 128 bytes.
party_status: Option<String>,
},
/// 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<chrono::DateTime<Utc>>,
game_end_timer: Option<chrono::DateTime<Utc>>,
game_details: Option<String>,
game_party_status: Option<String>,
game_assets: Option<Assets>,
}
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;
}
}

View File

@ -4,6 +4,7 @@ use std::borrow::Borrow;
mod persistent;
mod rendering;
mod discord;
/// This is the game logic entrypoint. Despite being async,
/// this is expected to block the main thread for rendering and stuff.

View File

@ -1,11 +1,13 @@
use raylib::RaylibBuilder;
/// Will begin rendering graphics. Returns when the window closes
pub fn handle_graphics_blocking<ConfigBuilder>(config: ConfigBuilder, target_frames_per_second: u32)
where
ConfigBuilder: FnOnce(&mut RaylibBuilder),
{
// Let the caller configure Raylib's internal window stuff
let (mut raylib_handle, raylib_thread) = {
log::trace!("Configuring Raylib");
let mut builder = raylib::init();
config(&mut builder);
builder.build()
@ -16,5 +18,11 @@ where
raylib_handle.set_target_fps(target_frames_per_second);
// Run the event loop
while !raylib_handle.window_should_close() {}
log::trace!("Running event loop");
while !raylib_handle.window_should_close() {
// Tell the profiler that we ended the frame
profiling::finish_frame!();
}
log::trace!("Event loop ended");
}