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

add error handling when sending score #53

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
187 changes: 152 additions & 35 deletions bevy-jornet/examples/whac-a-square.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ fn main() {
option_env!("JORNET_LEADERBOARD_ID").unwrap_or("a920de64-3bdb-4f8e-87a8-e7bf20f00f81"),
option_env!("JORNET_LEADERBOARD_KEY").unwrap_or("a797039b-a91d-43e6-8e1c-94f9ca0aa1d6"),
))
.add_plugin(debug::DebugPlugin)
.add_startup_system(setup)
.add_state(GameState::Menu)
.add_plugin(menu::MenuPlugin)
Expand All @@ -29,6 +30,105 @@ fn main() {
.run();
}

mod debug {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be after GameState and setup()

use crate::{BACKGROUND, TEXT};
use bevy::prelude::*;
use bevy_jornet::ErrorEvent;

pub struct DebugPlugin;

impl Plugin for DebugPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_startup_system(setup)
.add_system_to_stage(CoreStage::PostUpdate, react_to_errors)
.add_system(despawn_after);
}
}

#[derive(Component)]
struct DebugContainer;

/// Despawns recursively host entity when `Time.seconds_since_startup()` exceeds than value
#[derive(Component)]
struct DespawnAfter(f64);

fn setup(mut commands: Commands) {
commands
.spawn_bundle(NodeBundle {
style: Style {
align_self: AlignSelf::FlexEnd,
flex_direction: FlexDirection::ColumnReverse,
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(5.0),
right: Val::Px(15.0),
..default()
},
max_size: Size {
width: Val::Px(400.),
height: Val::Undefined,
},
align_items: AlignItems::FlexEnd,
..default()
},
color: Color::hex(BACKGROUND).unwrap().into(),
..default()
})
.insert(DebugContainer);
}

fn react_to_errors(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut error_event: EventReader<bevy_jornet::ErrorEvent>,
query_error_container: Query<Entity, With<DebugContainer>>,
) {
for error in error_event.iter() {
for e in query_error_container.iter() {
commands.entity(e).with_children(|parent| {
parent
.spawn_bundle(
TextBundle::from_section(
match error {
bevy_jornet::ErrorEvent::SendScoreFailed(error) => {
format!("{error}")
}
},
TextStyle {
font: asset_server.load("FiraSans-Bold.ttf"),
font_size: 50.0,
color: Color::rgb(0.8, 0.2, 0.7),
},
)
.with_text_alignment(TextAlignment::CENTER)
.with_style(Style {
align_self: AlignSelf::FlexEnd,
margin: UiRect::all(Val::Auto),
justify_content: JustifyContent::Center,
flex_direction: FlexDirection::ColumnReverse,
..default()
}),
)
.insert(DespawnAfter(time.seconds_since_startup() + 3f64));
});
}
}
}

fn despawn_after(
time: Res<Time>,
mut commands: Commands,
q_to_unspawn: Query<(Entity, &DespawnAfter)>,
) {
for (e, despawn) in q_to_unspawn.iter() {
if despawn.0 < time.seconds_since_startup() {
commands.entity(e).despawn_recursive();
}
}
}
}

#[derive(Clone, PartialEq, Eq, Debug, Hash)]
enum GameState {
Game,
Expand All @@ -53,6 +153,9 @@ mod menu {
use crate::{GameState, BACKGROUND, BUTTON, TEXT};
pub struct MenuPlugin;

#[derive(Component)]
struct MenuUI;

impl Plugin for MenuPlugin {
fn build(&self, app: &mut App) {
app.add_system_set(SystemSet::on_enter(GameState::Menu).with_system(display_menu))
Expand Down Expand Up @@ -89,6 +192,7 @@ mod menu {
color: Color::hex(BACKGROUND).unwrap().into(),
..default()
})
.insert(MenuUI)
.with_children(|parent| {
parent.spawn_bundle(TextBundle::from_section(
"Whac-A-Square",
Expand Down Expand Up @@ -205,6 +309,7 @@ mod menu {
..default()
}),
)
.insert(MenuUI)
.insert(PlayerName);

leaderboard.refresh_leaderboard();
Expand Down Expand Up @@ -252,7 +357,10 @@ mod menu {
}
}

fn despawn_menu(mut commands: Commands, root_ui: Query<Entity, (With<Node>, Without<Parent>)>) {
fn despawn_menu(
mut commands: Commands,
root_ui: Query<Entity, (With<Node>, With<MenuUI>, Without<Parent>)>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added more control on what exactly is despawned to be able to keep the debug UI for errors display

) {
for entity in &root_ui {
commands.entity(entity).despawn_recursive();
}
Expand Down Expand Up @@ -317,6 +425,11 @@ mod game {
}
}

#[derive(Component)]
struct ScoreTextMarker;
#[derive(Component)]
struct TimerMarker;

fn setup_game(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(WinitSettings {
focused_mode: UpdateMode::Reactive {
Expand All @@ -329,41 +442,45 @@ mod game {
time_to_click: Timer::from_seconds(10.0, false),
since_start: Stopwatch::new(),
});
commands.spawn_bundle(
TextBundle::from_section(
"0",
TextStyle {
font: asset_server.load("FiraSans-Bold.ttf"),
font_size: 50.0,
color: Color::hex(TEXT).unwrap(),
},
)
.with_style(Style {
align_self: AlignSelf::FlexEnd,
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(10.0),
left: Val::Px(15.0),
commands
.spawn_bundle(
TextBundle::from_section(
"0",
TextStyle {
font: asset_server.load("FiraSans-Bold.ttf"),
font_size: 50.0,
color: Color::hex(TEXT).unwrap(),
},
)
.with_style(Style {
align_self: AlignSelf::FlexEnd,
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(10.0),
left: Val::Px(15.0),
..default()
},
..default()
},
..default()
}),
);
commands.spawn_bundle(NodeBundle {
style: Style {
align_self: AlignSelf::FlexEnd,
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(0.0),
left: Val::Px(15.0),
}),
)
.insert(ScoreTextMarker);
commands
.spawn_bundle(NodeBundle {
style: Style {
align_self: AlignSelf::FlexEnd,
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(0.0),
left: Val::Px(15.0),
..default()
},
size: Size::new(Val::Px(200.0), Val::Px(8.0)),
..default()
},
size: Size::new(Val::Px(200.0), Val::Px(8.0)),
color: Color::hex(SQUARE).unwrap().into(),
..default()
},
color: Color::hex(SQUARE).unwrap().into(),
..default()
});
})
.insert(TimerMarker);
}

#[derive(Component)]
Expand Down Expand Up @@ -440,8 +557,8 @@ mod game {

fn game_state(
mut status: ResMut<GameStatus>,
mut score_text: Query<&mut Text>,
mut timer: Query<&mut Style, Without<Text>>,
mut score_text: Query<&mut Text, With<ScoreTextMarker>>,
mut timer: Query<&mut Style, (Without<Text>, With<TimerMarker>)>,
time: Res<Time>,
mut state: ResMut<State<GameState>>,
) {
Expand All @@ -457,7 +574,7 @@ mod game {
status: Res<GameStatus>,
leaderboard: Res<Leaderboard>,
mut commands: Commands,
game_ui: Query<Entity, With<Node>>,
game_ui: Query<Entity, (With<Node>, Or<(With<ScoreTextMarker>, With<TimerMarker>)>)>,
squares: Query<Entity, With<Sprite>>,
) {
for entity in &game_ui {
Expand Down
57 changes: 54 additions & 3 deletions bevy-jornet/src/leaderboards.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::sync::{Arc, RwLock};
#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};
use std::{
fmt::Display,
sync::{Arc, RwLock},
};

use bevy::{
prelude::{warn, ResMut},
prelude::{warn, EventWriter, ResMut},
tasks::IoTaskPool,
};
use hmac::{Hmac, Mac};
Expand All @@ -13,12 +16,33 @@ use uuid::Uuid;

use crate::http;

pub struct SendScoreFailed {
score_to_send: ScoreInput,
}

impl std::fmt::Display for SendScoreFailed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SendScoreFailed: score: {}, player: {}",
self.score_to_send.score, self.score_to_send.player
)
}
}

/// Event to handle errors, will be sent asynchronously when occuring
pub enum ErrorEvent {
/// Occurs when [`send_score`] or [`send_score_with_meta`] fails
SendScoreFailed(SendScoreFailed),
}

/// Leaderboard resource, used to interact with Jornet leaderboard.
pub struct Leaderboard {
id: Uuid,
key: Uuid,
leaderboard: Vec<Score>,
updating: Arc<RwLock<Vec<Score>>>,
error_pending: Arc<RwLock<Vec<ErrorEvent>>>,
host: String,
new_player: Arc<RwLock<Option<Player>>>,
player: Option<Player>,
Expand All @@ -31,6 +55,7 @@ impl Leaderboard {
key,
leaderboard: Default::default(),
updating: Default::default(),
error_pending: Default::default(),
host: "https://jornet.vleue.com".to_string(),
new_player: Default::default(),
player: Default::default(),
Expand Down Expand Up @@ -96,8 +121,16 @@ impl Leaderboard {

if let Some(player) = self.player.as_ref() {
let score_to_send = ScoreInput::new(self.key, score, player, meta);
let error_pending = self.error_pending.clone();
thread_pool
.spawn(async move {
(*error_pending)
.write()
.unwrap()
.push(ErrorEvent::SendScoreFailed(SendScoreFailed {
score_to_send,
}));
/*
if http::post::<_, ()>(
&format!("{}/api/v1/scores/{}", host, leaderboard_id),
score_to_send,
Expand All @@ -106,7 +139,7 @@ impl Leaderboard {
.is_none()
{
warn!("error sending the score");
}
}*/
})
.detach();
Some(())
Expand Down Expand Up @@ -258,3 +291,21 @@ pub struct Player {
struct PlayerInput {
pub name: Option<String>,
}

/// System to handle errors in any tasks.
/// It is responsible to propagate an [`ErrorEvent`].
/// It is automatically added by the [`JornetPlugin`](crate::JornetPlugin) in stage
/// [`CoreStage::Update`](bevy::prelude::CoreStage).
pub fn handle_errors(leaderboard: ResMut<Leaderboard>, mut error_event: EventWriter<ErrorEvent>) {
if !leaderboard
.error_pending
.try_read()
.map(|v| v.is_empty())
.unwrap_or(true)
{
let mut errors = leaderboard.error_pending.write().unwrap();
for e in errors.drain(..) {
error_event.send(e);
}
}
}
9 changes: 6 additions & 3 deletions bevy-jornet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
//! - get a leaderboard

use bevy::prelude::{App, Plugin};
use leaderboards::handle_errors;
pub use leaderboards::Leaderboard;
use uuid::Uuid;

mod http;
mod leaderboards;

pub use leaderboards::{done_refreshing_leaderboard, Score};
pub use leaderboards::{done_refreshing_leaderboard, ErrorEvent, Score};

/// Bevy Plugin handling communications with the Jornet server.
pub struct JornetPlugin {
Expand All @@ -39,7 +40,9 @@ impl JornetPlugin {
impl Plugin for JornetPlugin {
fn build(&self, app: &mut App) {
let leaderboard = Leaderboard::with_leaderboard(self.leaderboard, self.key);
app.insert_resource(leaderboard)
.add_system(done_refreshing_leaderboard);
app.add_event::<ErrorEvent>()
.insert_resource(leaderboard)
.add_system(done_refreshing_leaderboard)
.add_system(handle_errors);
}
}