From 47098f60dd9b3a6bb867638590427199c48e34b9 Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:00:15 -0600 Subject: [PATCH] use timer for message lengths (#799) * use timer for message lengths * more * more * more * more * more * more --- parallel-rdp/interface.cpp | 49 +++++++++++++++------------ parallel-rdp/interface.hpp | 8 ++++- retroachievements/retroachievements.c | 16 ++++----- src/cheats.rs | 2 +- src/device/controller.rs | 15 ++++++-- src/device/cpu.rs | 5 ++- src/device/exceptions.rs | 2 +- src/device/vi.rs | 5 ++- src/netplay.rs | 12 +++++-- src/savestates.rs | 13 ++++--- src/ui/audio.rs | 4 +-- src/ui/video.rs | 7 ++-- 12 files changed, 87 insertions(+), 51 deletions(-) diff --git a/parallel-rdp/interface.cpp b/parallel-rdp/interface.cpp index c94550f0..c3f85d0f 100644 --- a/parallel-rdp/interface.cpp +++ b/parallel-rdp/interface.cpp @@ -82,6 +82,7 @@ typedef struct { typedef struct { std::string message; + uint64_t milliseconds; Vulkan::ImageHandle image; } Message; @@ -102,7 +103,6 @@ uint64_t sync_signal; static TTF_Font *message_font; static std::queue messages; -static uint64_t message_timer; static TTF_Font *achievement_challenge_indicator_font; static std::vector achievement_challenge_indicators; @@ -114,8 +114,6 @@ static bool display_challenge_indicator; static bool display_fps; static Vulkan::ImageHandle fps_image; -#define MESSAGE_TIME 3000 // 3 seconds - typedef struct { float SourceSize[4]; float OutputSize[4]; @@ -169,7 +167,7 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) { break; case SDL_SCANCODE_F8: if (messages.empty()) - SDL_RunOnMainThread(ra_display_inprogress_achievements, nullptr, true); + SDL_RunOnMainThread(ra_display_inprogress_achievements, nullptr, false); break; case SDL_SCANCODE_F9: display_challenge_indicator = !display_challenge_indicator; @@ -177,7 +175,7 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) { std::format("Challenge indicators: {}", display_challenge_indicator ? "ON" : "OFF") .c_str(), - false); + MESSAGE_SHORT); break; case SDL_SCANCODE_F12: callback.reset_game = true; @@ -334,7 +332,6 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, crop_letterbox = false; messages = std::queue(); - message_timer = 0; display_challenge_indicator = true; achievement_challenge_indicators.clear(); @@ -444,6 +441,13 @@ static void draw_fps(CommandBufferHandle cmd, VkViewport vp) { cmd->draw(3); } +static void pop_message(void *userdata) { messages.pop(); } +static uint32_t pop_message_callback(void *userdata, SDL_TimerID timerID, + uint32_t interval) { + SDL_RunOnMainThread(pop_message, NULL, false); + return 0; +} + static void render_frame(Vulkan::Device &device) { RDP::ScanoutOptions options = {}; options.persist_frame_on_invalid_input = true; @@ -519,6 +523,7 @@ static void render_frame(Vulkan::Device &device) { if (!message->image) { message->image = create_message_image(device, vp.width, message_font, message->message.c_str()); + SDL_AddTimer(message->milliseconds, pop_message_callback, NULL); } cmd->set_texture(0, 0, message->image->get_view(), Vulkan::StockSampler::NearestClamp); @@ -529,11 +534,6 @@ static void render_frame(Vulkan::Device &device) { cmd->set_viewport(vp); cmd->draw(3); - - if (SDL_GetTicks() > message_timer) { - messages.pop(); - message_timer = SDL_GetTicks() + MESSAGE_TIME; - } } else if (achievement_progress_indicator_image) { draw_indicator(cmd, achievement_progress_indicator_image, vp); } else if (achievement_challenge_indicator_image && @@ -608,16 +608,18 @@ void rdp_load_state(const uint8_t *state) { memcpy(&rdp_device, state, sizeof(RDP_DEVICE)); } -static void push_onscreen_message(void *message) { - if (messages.empty()) - message_timer = SDL_GetTicks() + MESSAGE_TIME; - messages.push({std::string((const char *)message), Vulkan::ImageHandle()}); +static void push_onscreen_message(void *data) { + Message *message = (Message *)data; + messages.push(*message); + delete message; } -void rdp_onscreen_message(const char *message, bool long_message) { - SDL_RunOnMainThread(push_onscreen_message, (void *)message, true); - if (long_message) - SDL_RunOnMainThread(push_onscreen_message, (void *)message, true); +void rdp_onscreen_message(const char *message, MESSAGE_LENGTH milliseconds) { + Message *data = new Message; + data->message = message; + data->milliseconds = static_cast(milliseconds); + data->image = Vulkan::ImageHandle(); + SDL_RunOnMainThread(push_onscreen_message, data, false); } uint32_t pixel_size(uint32_t pixel_type, uint32_t area) { @@ -849,17 +851,20 @@ uint64_t rdp_process_commands() { return interrupt_timer; } -void rdp_set_fps_callback(void *userdata) { +static void rdp_set_fps_callback(void *userdata) { FPS_DATA *data = (FPS_DATA *)userdata; fps_image = create_message_image( wsi->get_device(), 0, achievement_challenge_indicator_font, std::format("FPS: {} VI/S: {}", data->fps, data->vis).c_str()); + delete data; } void rdp_set_fps(uint32_t fps, uint32_t vis) { if (display_fps) { - FPS_DATA data = {fps, vis}; - SDL_RunOnMainThread(rdp_set_fps_callback, &data, true); + FPS_DATA *data = new FPS_DATA; + data->fps = fps; + data->vis = vis; + SDL_RunOnMainThread(rdp_set_fps_callback, data, false); } } diff --git a/parallel-rdp/interface.hpp b/parallel-rdp/interface.hpp index 2fb70a61..7ab6569b 100644 --- a/parallel-rdp/interface.hpp +++ b/parallel-rdp/interface.hpp @@ -37,6 +37,12 @@ typedef struct { uint32_t save_state_slot; } CALL_BACK; +typedef enum { + MESSAGE_VERY_SHORT = 500, + MESSAGE_SHORT = 3000, + MESSAGE_LONG = 6000, +} MESSAGE_LENGTH; + void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size, uint32_t save_state_slot); void rdp_close(); @@ -45,7 +51,7 @@ void rdp_update_screen(); void rdp_render_frame(); CALL_BACK rdp_check_callback(); uint64_t rdp_process_commands(); -void rdp_onscreen_message(const char *message, bool long_message); +void rdp_onscreen_message(const char *message, MESSAGE_LENGTH milliseconds); void rdp_new_processor(GFX_INFO _gfx_info); void rdp_check_framebuffers(uint32_t address, uint32_t length); size_t rdp_state_size(); diff --git a/retroachievements/retroachievements.c b/retroachievements/retroachievements.c index 228e06cc..2c7a3c9e 100644 --- a/retroachievements/retroachievements.c +++ b/retroachievements/retroachievements.c @@ -137,7 +137,7 @@ void ra_welcome() { char *error_message = rc_client_get_userdata(g_client); if (error_message) { snprintf(buffer, sizeof(buffer), "RA Error: %s", error_message); - rdp_onscreen_message(buffer, true); + rdp_onscreen_message(buffer, MESSAGE_LONG); free(error_message); rc_client_set_userdata(g_client, NULL); return; @@ -160,7 +160,7 @@ void ra_welcome() { snprintf(buffer + message_length, sizeof(buffer) - message_length, "Game has no achievements"); } - rdp_onscreen_message(buffer, true); + rdp_onscreen_message(buffer, MESSAGE_LONG); } void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata) { @@ -187,14 +187,14 @@ static void leaderboard_submitted(const rc_client_leaderboard_t *leaderboard) { snprintf(buffer, sizeof(buffer), "Leaderboard submitted: %s - %s", leaderboard->title, leaderboard->tracker_value); - rdp_onscreen_message(buffer, false); + rdp_onscreen_message(buffer, MESSAGE_SHORT); } static void achievement_triggered(const rc_client_achievement_t *achievement) { char buffer[512]; snprintf(buffer, sizeof(buffer), "Unlocked: %s", achievement->title); - rdp_onscreen_message(buffer, false); + rdp_onscreen_message(buffer, MESSAGE_SHORT); } static void game_completed(rc_client_t *client) { @@ -204,7 +204,7 @@ static void game_completed(rc_client_t *client) { snprintf(buffer, sizeof(buffer), "%s: %s", rc_client_get_hardcore_enabled(client) ? "Mastered" : "Completed", game->title); - rdp_onscreen_message(buffer, false); + rdp_onscreen_message(buffer, MESSAGE_SHORT); } static void subset_completed(const rc_client_subset_t *subset, @@ -214,7 +214,7 @@ static void subset_completed(const rc_client_subset_t *subset, snprintf(buffer, sizeof(buffer), "Subset %s: %s", rc_client_get_hardcore_enabled(client) ? "mastered" : "completed", subset->title); - rdp_onscreen_message(buffer, false); + rdp_onscreen_message(buffer, MESSAGE_SHORT); } static void server_error(const rc_client_server_error_t *server_error) { @@ -222,7 +222,7 @@ static void server_error(const rc_client_server_error_t *server_error) { snprintf(buffer, sizeof(buffer), "RA server error: %s", server_error->error_message); - rdp_onscreen_message(buffer, false); + rdp_onscreen_message(buffer, MESSAGE_SHORT); } static const char *get_leaderboard_title(const char *display) { @@ -393,7 +393,7 @@ void ra_display_inprogress_achievements(void *userdata) { } } if (buffer_length > 0) { - rdp_onscreen_message(buffer, false); + rdp_onscreen_message(buffer, MESSAGE_SHORT); } rc_client_destroy_achievement_list(list); } diff --git a/src/cheats.rs b/src/cheats.rs index 8c60dc35..fd704b4b 100644 --- a/src/cheats.rs +++ b/src/cheats.rs @@ -79,7 +79,7 @@ pub fn init( if !device.cheats.cheats.is_empty() { device.cheats.enabled = true; - ui::video::onscreen_message("Cheats enabled", false); + ui::video::onscreen_message("Cheats enabled", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT); } } diff --git a/src/device/controller.rs b/src/device/controller.rs index 322dee68..5d499221 100644 --- a/src/device/controller.rs +++ b/src/device/controller.rs @@ -190,21 +190,30 @@ pub fn pak_switch_event(device: &mut device::Device) { write: device::controller::mempak::write, pak_type: new_pak_type, }); - ui::video::onscreen_message("MemPak selected", false); + ui::video::onscreen_message( + "MemPak selected", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); } else if new_pak_type == PakType::RumblePak { channel.pak_handler = Some(device::controller::PakHandler { read: device::controller::rumble::read, write: device::controller::rumble::write, pak_type: new_pak_type, }); - ui::video::onscreen_message("RumblePak selected", false); + ui::video::onscreen_message( + "RumblePak selected", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); } else if new_pak_type == PakType::TransferPak { channel.pak_handler = Some(device::controller::PakHandler { read: device::controller::transferpak::read, write: device::controller::transferpak::write, pak_type: new_pak_type, }); - ui::video::onscreen_message("TransferPak selected", false); + ui::video::onscreen_message( + "TransferPak selected", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); } channel.change_pak = PakType::None; } diff --git a/src/device/cpu.rs b/src/device/cpu.rs index 1cd59ebd..ae6028f2 100644 --- a/src/device/cpu.rs +++ b/src/device/cpu.rs @@ -227,7 +227,10 @@ pub fn init(device: &mut device::Device) { device.cpu.clock_rate = if !device.cpu.overclock { 93750000 } else { - ui::video::onscreen_message("Overclocking enabled, setting clock rate to 125 MHz", false); + ui::video::onscreen_message( + "Overclocking enabled, setting clock rate to 125 MHz", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); 125000000 }; diff --git a/src/device/exceptions.rs b/src/device/exceptions.rs index 38fdc679..f244fa7f 100644 --- a/src/device/exceptions.rs +++ b/src/device/exceptions.rs @@ -166,7 +166,7 @@ pub fn reset_event(device: &mut device::Device) { device::pif::reset_pif(device, true); - ui::video::onscreen_message("Game reset", false); + ui::video::onscreen_message("Game reset", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT); } fn exception_general(device: &mut device::Device, vector_offset: u32) { diff --git a/src/device/vi.rs b/src/device/vi.rs index 6a6b90a1..d04d813b 100644 --- a/src/device/vi.rs +++ b/src/device/vi.rs @@ -150,7 +150,10 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { if device.netplay.is_none() && paused { if retroachievements::get_hardcore() { - ui::video::onscreen_message("Cannot pause in RA hardcore mode", false); + ui::video::onscreen_message( + "Cannot pause in RA hardcore mode", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); } else { ui::video::pause_loop(device.vi.frame_time); } diff --git a/src/netplay.rs b/src/netplay.rs index 0f9ce7b5..95e7c9a0 100644 --- a/src/netplay.rs +++ b/src/netplay.rs @@ -146,7 +146,10 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::Inpu .remove(&netplay.player_data[channel].count); if std::time::Instant::now() > timeout { - ui::video::onscreen_message("Lost connection to netplay server", false); + ui::video::onscreen_message( + "Lost connection to netplay server", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); input = Some(InputEvent { input: 0, plugin: 0, @@ -191,14 +194,17 @@ fn process_incoming(netplay: &mut Netplay) { } if current_status != netplay.status { if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 { - ui::video::onscreen_message("Netplay desync detected", true); + ui::video::onscreen_message( + "Netplay desync detected", + ui::video::MESSAGE_LENGTH_MESSAGE_LONG, + ); } for dis in 1..5 { if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0 { ui::video::onscreen_message( &format!("Player {dis} disconnected"), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } } diff --git a/src/savestates.rs b/src/savestates.rs index 2e42c2b3..74cc27a1 100644 --- a/src/savestates.rs +++ b/src/savestates.rs @@ -95,13 +95,16 @@ pub fn create_savestate(device: &device::Device) { "Savestate created in slot {}", device.ui.storage.save_state_slot ), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } pub fn load_savestate(device: &mut device::Device) { if retroachievements::get_hardcore() { - ui::video::onscreen_message("Cannot load savestate in RA hardcore mode", false); + ui::video::onscreen_message( + "Cannot load savestate in RA hardcore mode", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); return; } let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path); @@ -216,7 +219,7 @@ pub fn load_savestate(device: &mut device::Device) { "Savestate loaded from slot {}", device.ui.storage.save_state_slot ), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } else { ui::video::onscreen_message( @@ -224,7 +227,7 @@ pub fn load_savestate(device: &mut device::Device) { "Failed to load savestate from slot {}", device.ui.storage.save_state_slot ), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } } else { @@ -233,7 +236,7 @@ pub fn load_savestate(device: &mut device::Device) { "Could not find savestate in slot {}", device.ui.storage.save_state_slot ), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } } diff --git a/src/ui/audio.rs b/src/ui/audio.rs index f474f495..ee20b116 100644 --- a/src/ui/audio.rs +++ b/src/ui/audio.rs @@ -55,7 +55,7 @@ pub fn lower_audio_volume(ui: &mut ui::Ui) { } ui::video::onscreen_message( &format!("Audio volume: {:.0}%", ui.audio.gain * 100.0), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT, ); } @@ -69,7 +69,7 @@ pub fn raise_audio_volume(ui: &mut ui::Ui) { } ui::video::onscreen_message( &format!("Audio volume: {:.0}%", ui.audio.gain * 100.0), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT, ); } diff --git a/src/ui/video.rs b/src/ui/video.rs index 6de57848..409264ad 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -1,4 +1,5 @@ #![allow(non_snake_case)] +#![allow(non_camel_case_types)] include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs")); use crate::{device, retroachievements, ui}; @@ -215,7 +216,7 @@ pub fn check_callback(device: &mut device::Device) -> (bool, bool) { if device.ui.storage.save_state_slot != callback.save_state_slot { onscreen_message( &format!("Switching savestate slot to {}", callback.save_state_slot), - false, + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); device.ui.storage.save_state_slot = callback.save_state_slot; device @@ -247,10 +248,10 @@ pub fn check_framebuffers(address: u32, length: u32) { unsafe { rdp_check_framebuffers(address, length) } } -pub fn onscreen_message(message: &str, long_message: bool) { +pub fn onscreen_message(message: &str, milliseconds: MESSAGE_LENGTH) { unsafe { let c_message = std::ffi::CString::new(message).unwrap(); - rdp_onscreen_message(c_message.as_ptr(), long_message) + rdp_onscreen_message(c_message.as_ptr(), milliseconds) }; }