diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index bfbffff2a8..08e8478f3b 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -92,6 +92,7 @@ static rcheevos_locals_t rcheevos_locals = {{0}},/* memory */ #ifdef HAVE_THREADS CMD_EVENT_NONE, /* queued_command */ + false, /* game_placard_requested */ #endif #ifndef HAVE_RC_CLIENT "", /* displayname */ @@ -134,6 +135,9 @@ rcheevos_locals_t* get_rcheevos_locals(void) Supporting functions. *****************************************************************************/ +#define CMD_CHEEVOS_NON_COMMAND -1 +static void rcheevos_show_game_placard(void); + #ifndef CHEEVOS_VERBOSE void rcheevos_log(const char* fmt, ...) { @@ -602,6 +606,39 @@ static void rcheevos_server_error(const char* api_name, const char* message) MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); } +static void rcheevos_server_disconnected() +{ + CHEEVOS_LOG(RCHEEVOS_TAG "Unable to communicate with RetroAchievements server\n"); + + /* always show message - even with widget. it helps the user understand what the widget is for */ + { + const char* message = msg_hash_to_str(MENU_ENUM_LABEL_CHEEVOS_SERVER_DISCONNECTED); + runloop_msg_queue_push(message, 0, 3 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + } + +#if defined(HAVE_GFX_WIDGETS) + if (gfx_widgets_ready()) + gfx_widget_set_cheevos_disconnect(true); +#endif +} + +static void rcheevos_server_reconnected() +{ + CHEEVOS_LOG(RCHEEVOS_TAG "All pending requests synced to RetroAchievements server\n"); + + { + const char* message = msg_hash_to_str(MENU_ENUM_LABEL_CHEEVOS_SERVER_RECONNECTED); + runloop_msg_queue_push(message, 0, 3 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS); + } + +#if defined(HAVE_GFX_WIDGETS) + if (gfx_widgets_ready()) + gfx_widget_set_cheevos_disconnect(false); +#endif +} + static void rcheevos_client_event_handler(const rc_client_event_t* event, rc_client_t* client) { switch (event->type) @@ -657,10 +694,10 @@ static void rcheevos_client_event_handler(const rc_client_event_t* event, rc_cli rcheevos_server_error(event->server_error->api, event->server_error->error_message); break; case RC_CLIENT_EVENT_DISCONNECTED: - CHEEVOS_LOG(RCHEEVOS_TAG "Unable to communicate with RetroAchievements server"); + rcheevos_server_disconnected(); break; case RC_CLIENT_EVENT_RECONNECTED: - CHEEVOS_LOG(RCHEEVOS_TAG "All pending requests synced to RetroAchievements server"); + rcheevos_server_reconnected(); break; default: #ifndef NDEBUG @@ -1202,11 +1239,14 @@ bool rcheevos_unload(void) /* Clean up after completed tasks */ task_queue_check(); } - - rcheevos_locals.queued_command = CMD_EVENT_NONE; #endif #endif +#ifdef HAVE_THREADS + rcheevos_locals.queued_command = CMD_EVENT_NONE; + rcheevos_locals.game_placard_requested = false; +#endif + if (rcheevos_locals.memory.count > 0) rc_libretro_memory_destroy(&rcheevos_locals.memory); @@ -1866,8 +1906,16 @@ void rcheevos_test(void) #ifdef HAVE_THREADS if (rcheevos_locals.queued_command != CMD_EVENT_NONE) { - command_event(rcheevos_locals.queued_command, NULL); + if (rcheevos_locals.queued_command != CMD_CHEEVOS_NON_COMMAND) + command_event(rcheevos_locals.queued_command, NULL); + rcheevos_locals.queued_command = CMD_EVENT_NONE; + + if (rcheevos_locals.game_placard_requested) + { + rcheevos_locals.game_placard_requested = false; + rcheevos_show_game_placard(); + } } #endif @@ -2414,7 +2462,16 @@ static void rcheevos_client_load_game_callback(int result, rc_client_set_read_memory_function(client, rcheevos_client_read_memory); } - rcheevos_show_game_placard(); +#ifdef HAVE_THREADS + if (!task_is_on_main_thread()) + { + /* have to "schedule" this. game image should not be loaded on background thread */ + rcheevos_locals.queued_command = CMD_CHEEVOS_NON_COMMAND; + rcheevos_locals.game_placard_requested = true; + } + else + rcheevos_show_game_placard(); +#endif rcheevos_finalize_game_load(client); diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h index 72077cbab2..708f166502 100644 --- a/cheevos/cheevos_locals.h +++ b/cheevos/cheevos_locals.h @@ -201,6 +201,7 @@ typedef struct rcheevos_locals_t #ifdef HAVE_THREADS enum event_command queued_command; /* action queued by background thread to be run on main thread */ + bool game_placard_requested; /* request to display game placard */ #endif #ifndef HAVE_RC_CLIENT diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c index e92d1a7013..4ff901ef4c 100644 --- a/cheevos/cheevos_menu.c +++ b/cheevos/cheevos_menu.c @@ -28,6 +28,7 @@ #include "../deps/rcheevos/include/rc_runtime_types.h" #include "../deps/rcheevos/include/rc_api_runtime.h" +#include "../deps/rcheevos/src/rc_client_internal.h" #include "../menu/menu_driver.h" #include "../menu/menu_entries.h" @@ -295,6 +296,15 @@ void rcheevos_menu_populate(void* data) rcheevos_menu_reset_badges(); rcheevos_locals->menuitem_count = 0; + if (rcheevos_locals->client->state.disconnect) + { + menu_entries_append(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_SERVER_UNREACHABLE), + msg_hash_to_str(MENU_ENUM_SUBLABEL_ACHIEVEMENT_SERVER_UNREACHABLE), + MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE, + MENU_INFO_ACHIEVEMENTS_SERVER_UNREACHABLE, 0, 0, NULL); + } + if (game && game->id != 0) { /* first menu item is the Pause/Resume Hardcore option (unless hardcore is completely disabled) */ @@ -466,7 +476,11 @@ uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool downlo return 0; /* OpenGL driver crashes if gfx_display_reset_textures_list is called on a background thread */ - retro_assert(task_is_on_main_thread()); + if (!task_is_on_main_thread()) + { + CHEEVOS_ERR(RCHEEVOS_TAG "attempt to load badge %s from background thread", badge); + retro_assert(task_is_on_main_thread()); + } snprintf(badge_file, sizeof(badge_file), "%s%s%s", badge, locked ? "_lock" : "", FILE_PATH_PNG_EXTENSION); diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index f61e456b7a..91ed42f630 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -384,6 +384,7 @@ void gfx_widgets_clear_leaderboard_displays(void); void gfx_widgets_set_challenge_display(unsigned id, const char* badge); void gfx_widgets_clear_challenge_displays(void); void gfx_widget_set_achievement_progress(const char* badge, const char* progress); +void gfx_widget_set_cheevos_disconnect(bool visible); #endif /* TODO/FIXME/WARNING: Not thread safe! */ diff --git a/gfx/widgets/gfx_widget_leaderboard_display.c b/gfx/widgets/gfx_widget_leaderboard_display.c index 5fabf5bc34..6383a30108 100644 --- a/gfx/widgets/gfx_widget_leaderboard_display.c +++ b/gfx/widgets/gfx_widget_leaderboard_display.c @@ -51,6 +51,8 @@ struct progress_tracker_info #define CHEEVO_LBOARD_FIRST_FIXED_CHAR 0x2D /* -./0123456789: */ #define CHEEVO_LBOARD_LAST_FIXED_CHAR 0x3A + +/* TODO: rename; this file handles all achievement tracker information, not just leaderboards */ struct gfx_widget_leaderboard_display_state { #ifdef HAVE_THREADS @@ -64,6 +66,7 @@ struct gfx_widget_leaderboard_display_state unsigned challenge_count; uint16_t char_width[CHEEVO_LBOARD_LAST_FIXED_CHAR - CHEEVO_LBOARD_FIRST_FIXED_CHAR + 1]; uint16_t fixed_char_width; + bool disconnected; }; typedef struct gfx_widget_leaderboard_display_state gfx_widget_leaderboard_display_state_t; @@ -109,7 +112,10 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) gfx_widget_leaderboard_display_state_t *state = &p_w_leaderboard_display_st; /* if there's nothing to display, just bail */ - if (state->tracker_count == 0 && state->challenge_count == 0 && state->progress_tracker.show_until == 0) + if (state->tracker_count == 0 && + state->challenge_count == 0 && + state->progress_tracker.show_until == 0 && + !state->disconnected) return; #ifdef HAVE_THREADS @@ -335,6 +341,38 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) TEXT_COLOR_INFO, TEXT_ALIGN_LEFT, true); } } + + if (state->disconnected) + { + const char* disconnected_text = "! RA !"; + const unsigned disconnect_widget_width = font_driver_get_message_width( + state->dispwidget_ptr->gfx_widget_fonts.msg_queue.font, + disconnected_text, 0, 1) + CHEEVO_LBOARD_DISPLAY_PADDING * 2; + const unsigned disconnect_widget_height = + p_dispwidget->gfx_widget_fonts.msg_queue.line_height + (CHEEVO_LBOARD_DISPLAY_PADDING - 1) * 2; + x = video_width - disconnect_widget_width - spacing; + y -= disconnect_widget_height + spacing; + + /* Backdrop */ + gfx_display_draw_quad( + p_disp, + video_info->userdata, + video_width, video_height, + (int)x, (int)y, disconnect_widget_width, disconnect_widget_height, + video_width, video_height, + p_dispwidget->backdrop_orig, + NULL); + + /* Text */ + char_x = (float)(x + CHEEVO_LBOARD_DISPLAY_PADDING); + char_y = (float)(y + disconnect_widget_height - (CHEEVO_LBOARD_DISPLAY_PADDING - 1) + - p_dispwidget->gfx_widget_fonts.msg_queue.line_descender); + + gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue, + disconnected_text, char_x, char_y, + video_width, video_height, + TEXT_COLOR_INFO, TEXT_ALIGN_LEFT, true); + } } #ifdef HAVE_THREADS @@ -542,6 +580,13 @@ void gfx_widget_set_achievement_progress(const char* badge, const char* progress video_driver_texture_unload(&old_badge_id); } +void gfx_widget_set_cheevos_disconnect(bool value) +{ + gfx_widget_leaderboard_display_state_t* state = &p_w_leaderboard_display_st; + state->disconnected = value; +} + + const gfx_widget_t gfx_widget_leaderboard_display = { &gfx_widget_leaderboard_display_init, &gfx_widget_leaderboard_display_free, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 7bab0f2ba5..7bd398d13d 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -9691,6 +9691,22 @@ MSG_HASH( MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME, "Resume achievements hardcore mode for the current session. This action will disable cheats, rewind, slow-motion, and loading save states and reset the current game." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_SERVER_UNREACHABLE, + "RetroAchievements server is unreachable" +) +MSG_HASH( + MENU_ENUM_SUBLABEL_ACHIEVEMENT_SERVER_UNREACHABLE, + "One or more achievement unlocks did not make it to the server. The unlocks will be retried as long as you leave the app open." +) +MSG_HASH( + MENU_ENUM_LABEL_CHEEVOS_SERVER_DISCONNECTED, + "RetroAchievements server is unreachable. Will retry until successful or the app is closed." +) +MSG_HASH( + MENU_ENUM_LABEL_CHEEVOS_SERVER_RECONNECTED, + "All pending requests have succesfully been synced to the RetroAchievements server." +) MSG_HASH( MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN, "Not logged in" diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 20e4ae0eaa..520595fd5b 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -301,6 +301,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_information_list_list, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_list, MENU_ENUM_SUBLABEL_ACHIEVEMENT_LIST) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_pause_cancel, MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE_CANCEL) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_resume_cancel, MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_CANCEL) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_server_unreachable,MENU_ENUM_SUBLABEL_ACHIEVEMENT_SERVER_UNREACHABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_enable, MENU_ENUM_SUBLABEL_CHEEVOS_ENABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_test_unofficial, MENU_ENUM_SUBLABEL_CHEEVOS_TEST_UNOFFICIAL) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_hardcore_mode_enable, MENU_ENUM_SUBLABEL_CHEEVOS_HARDCORE_MODE_ENABLE) @@ -4564,6 +4565,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_achievement_resume_cancel); break; + case MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_achievement_server_unreachable); + break; case MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY: case MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY_HARDCORE: case MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY: diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index f8d5287ef9..6e46c31c0d 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -2290,6 +2290,8 @@ static uintptr_t ozone_entries_icon_get_texture( return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; case MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_PAUSE]; + case MENU_INFO_ACHIEVEMENTS_SERVER_UNREACHABLE: + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_NETWORK]; case MENU_SETTING_GROUP: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SETTING]; case MENU_SET_SCREEN_BRIGHTNESS: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 09ffe50f04..816dbf0bc5 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -3465,6 +3465,8 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, return xmb->textures.list[XMB_TEXTURE_RELOAD]; case MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS: return xmb->textures.list[XMB_TEXTURE_PAUSE]; + case MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE: + return xmb->textures.list[XMB_TEXTURE_NETWORK]; case MENU_SET_SCREEN_BRIGHTNESS: return xmb->textures.list[XMB_TEXTURE_BRIGHTNESS]; case MENU_SETTING_GROUP: diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 98803360f1..e56be9fdd7 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -169,6 +169,7 @@ enum menu_settings_type MENU_SETTING_HORIZONTAL_MENU, MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, + MENU_INFO_ACHIEVEMENTS_SERVER_UNREACHABLE, MENU_SETTING_PLAYLIST_MANAGER_DEFAULT_CORE, MENU_SETTING_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE, MENU_SETTING_PLAYLIST_MANAGER_RIGHT_THUMBNAIL_MODE, diff --git a/msg_hash.h b/msg_hash.h index 8c708a109e..9b7270a8fd 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -2926,6 +2926,9 @@ enum msg_hash_enums MENU_LABEL(ACHIEVEMENT_RESUME_CANCEL), MENU_LABEL(ACHIEVEMENT_PAUSE), MENU_LABEL(ACHIEVEMENT_RESUME), + MENU_LABEL(ACHIEVEMENT_SERVER_UNREACHABLE), + MENU_LABEL(CHEEVOS_SERVER_DISCONNECTED), + MENU_LABEL(CHEEVOS_SERVER_RECONNECTED), MENU_LABEL(CORE_INFORMATION), MENU_LABEL(DISC_INFORMATION), MENU_LABEL(CORE_LOCK),