diff --git a/game/Cargo.toml b/game/Cargo.toml index a25e253..a483b6d 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -20,6 +20,7 @@ puffin = "0.9" puffin_http = "0.6" dirty-fsm = "^0.2.1" num-traits = "0.2" +sentry = "0.23" [dev-dependencies] puffin_viewer = "0.6" diff --git a/game/assets/configs/application.json b/game/assets/configs/application.json index 5f4b42b..5b3d2f9 100644 --- a/game/assets/configs/application.json +++ b/game/assets/configs/application.json @@ -9,5 +9,6 @@ "Developer" ] } - ] + ], + "sentry_dsn": "https://d5d94e75f08841388287fa0c23606ac7@o398481.ingest.sentry.io/5985679" } diff --git a/game/assets/configs/discord.json b/game/assets/configs/discord.json index 3b98659..c9260aa 100644 --- a/game/assets/configs/discord.json +++ b/game/assets/configs/discord.json @@ -1,3 +1,3 @@ { "app_id": 889531982978117633 -} \ No newline at end of file +} diff --git a/game/src/discord_rpc.rs b/game/src/discord_rpc.rs new file mode 100644 index 0000000..824d29d --- /dev/null +++ b/game/src/discord_rpc.rs @@ -0,0 +1,37 @@ +use std::time::Duration; + +use discord_sdk::activity::ActivityBuilder; +use tracing::{error, log::info}; + +use crate::utilities::discord::{rpc::DiscordError, DiscordConfig, DiscordRpcClient}; + +/// How long to wait before we give up on connecting to Discord. +const DISCORD_CONNECT_TIMEOUT_SECONDS: u64 = 5; + +/// Try to connect to a local discord client for RPC, or return an error. +pub async fn try_connect_to_local_discord( + config: &DiscordConfig, +) -> Result { + info!("Trying to locate and connect to a local Discord process for RPC. Will wait up to {} seconds before timing out", DISCORD_CONNECT_TIMEOUT_SECONDS); + + // Connect while wrapped in a tokio timeout + let rpc_client = tokio::time::timeout( + Duration::from_secs(DISCORD_CONNECT_TIMEOUT_SECONDS), + DiscordRpcClient::new(config.app_id, discord_sdk::Subscriptions::ACTIVITY), + ) + .await??; + + info!("Successfully connected to Discord"); + Ok(rpc_client) +} + +/// If the discord client object exists, set rich presence, otherwise, do nothing. +pub async fn maybe_set_discord_presence( + client: &Option, + activity: ActivityBuilder, +) -> Result<(), DiscordError> { + if let Some(rpc) = client { + rpc.set_rich_presence(activity).await?; + } + Ok(()) +} diff --git a/game/src/lib.rs b/game/src/lib.rs index 324e33e..935e84a 100644 --- a/game/src/lib.rs +++ b/game/src/lib.rs @@ -64,20 +64,19 @@ nonstandard_style, rust_2018_idioms )] +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.57.0"] use std::cell::RefCell; use discord_sdk::activity::ActivityBuilder; use raylib::prelude::*; use tracing::{error, info}; -use utilities::{ - datastore::StaticGameData, - discord::{DiscordConfig, DiscordRpcClient}, - game_config::GameConfig, -}; +use utilities::discord::DiscordConfig; use crate::{ context::GameContext, + discord_rpc::{maybe_set_discord_presence, try_connect_to_local_discord}, scenes::build_screen_state_machine, utilities::shaders::{ shader::ShaderWrapper, @@ -91,45 +90,46 @@ extern crate thiserror; extern crate serde; mod context; +mod discord_rpc; mod scenes; mod utilities; +pub use utilities::{datastore::StaticGameData, game_config::GameConfig}; /// The game entrypoint -pub async fn game_begin() -> Result<(), Box> { - // Load the general config for the game - let game_config = GameConfig::load( - StaticGameData::get("configs/application.json").expect("Failed to load application.json"), - )?; - +pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box> { // Set up profiling #[cfg(debug_assertions)] let _puffin_server = - puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT))?; + puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT)).unwrap(); puffin::set_scopes_on(true); // Attempt to connect to a locally running Discord instance for rich presence access let discord_config = DiscordConfig::load( StaticGameData::get("configs/discord.json").expect("Failed to load discord.json"), - )?; - let discord_rpc = - match DiscordRpcClient::new(discord_config.app_id, discord_sdk::Subscriptions::ACTIVITY) - .await - { - Ok(client) => Some(client), - Err(err) => { - error!("Could not connect to or find a locally running Discord instance."); - error!("Discord connection error: {:?}", err); + ) + .unwrap(); + let discord_rpc = match try_connect_to_local_discord(&discord_config).await { + Ok(client) => Some(client), + Err(err) => match err { + utilities::discord::rpc::DiscordError::ConnectionTimeoutError(time) => { + error!( + "Could not find or connect to a local Discord instance after {} seconds", + time + ); None } - }; - - if let Some(rpc) = discord_rpc { - rpc.set_rich_presence(ActivityBuilder::default().details("Testing...")) - .await?; - } + _ => panic!("Failed to connect to Discord: {}", err), + }, + }; + maybe_set_discord_presence( + &discord_rpc, + ActivityBuilder::default().details("Testing..."), + ) + .await + .unwrap(); // Get the main state machine - let mut game_state_machine = build_screen_state_machine()?; + let mut game_state_machine = build_screen_state_machine().unwrap(); let context; let raylib_thread; @@ -171,7 +171,9 @@ pub async fn game_begin() -> Result<(), Box> { puffin::GlobalProfiler::lock().new_frame(); // Update the GPU texture that we draw to. This handles screen resizing and some other stuff - dynamic_texture.update(&mut context.renderer.borrow_mut(), &raylib_thread)?; + dynamic_texture + .update(&mut context.renderer.borrow_mut(), &raylib_thread) + .unwrap(); // Switch into draw mode the unsafe way (using unsafe code here to avoid borrow checker hell) #[allow(unsafe_code)] diff --git a/game/src/utilities/discord/mod.rs b/game/src/utilities/discord/mod.rs index 3cab9f3..bd24468 100644 --- a/game/src/utilities/discord/mod.rs +++ b/game/src/utilities/discord/mod.rs @@ -2,3 +2,5 @@ pub mod rpc; pub use rpc::DiscordRpcClient; pub mod config; pub use config::DiscordConfig; + + diff --git a/game/src/utilities/discord/rpc.rs b/game/src/utilities/discord/rpc.rs index 4992a91..70a9304 100644 --- a/game/src/utilities/discord/rpc.rs +++ b/game/src/utilities/discord/rpc.rs @@ -7,6 +7,7 @@ use discord_sdk::{ Discord, DiscordApp, Subscriptions, }; use tracing::info; +use tokio::time::error::Elapsed; #[derive(Debug, Error)] pub enum DiscordError { @@ -16,6 +17,8 @@ pub enum DiscordError { AwaitConnectionError(#[from] tokio::sync::watch::error::RecvError), #[error("Could not connect")] ConnectionError, + #[error(transparent)] + ConnectionTimeoutError(#[from] Elapsed) } /// The client wrapper for Discord RPC diff --git a/game/src/utilities/game_config.rs b/game/src/utilities/game_config.rs index ae50296..4a9bf1a 100644 --- a/game/src/utilities/game_config.rs +++ b/game/src/utilities/game_config.rs @@ -15,6 +15,7 @@ pub struct Author { pub struct GameConfig { pub name: String, pub authors: Vec, + pub sentry_dsn: String } impl GameConfig { diff --git a/wrapper/Cargo.toml b/wrapper/Cargo.toml index 6fbc952..0b6e65e 100644 --- a/wrapper/Cargo.toml +++ b/wrapper/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" [dependencies] game = { version = "0.1", path = "../game"} tracing-subscriber = "0.2" -tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } \ No newline at end of file +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +sentry = "0.23" diff --git a/wrapper/src/main.rs b/wrapper/src/main.rs index 3c5985f..e8336c3 100644 --- a/wrapper/src/main.rs +++ b/wrapper/src/main.rs @@ -1,10 +1,26 @@ -use game::game_begin; +use game::{GameConfig, StaticGameData, game_begin}; #[tokio::main] async fn main() { // Enable logging tracing_subscriber::fmt::init(); + // Load the general config for the game + // This happens here so we can properly track sentry events + let game_config = GameConfig::load( + StaticGameData::get("configs/application.json").expect("Failed to load application.json"), + ).unwrap(); + + // Connect to sentry + let _sentry_guard = sentry::init(( + game_config.sentry_dsn.clone(), + sentry::ClientOptions { + release: sentry::release_name!(), + attach_stacktrace: true, + ..Default::default() + }, + )); + // Start the game - game_begin().await.unwrap(); + game_begin(&game_config).await.unwrap(); }