diff --git a/Cargo.lock b/Cargo.lock index 959943b9..0f81167d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2369,7 +2369,7 @@ dependencies = [ "objc2-core-text", "objc2-foundation 0.3.2", "parlance", - "read-fonts", + "read-fonts 0.37.0", "roxmltree", "smallvec", "windows 0.62.2", @@ -2611,8 +2611,7 @@ dependencies = [ [[package]] name = "ggrs" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777d9da4dfd802a272f1795a7a1887d6078fd31487e7e68d689596c4c5298046" +source = "git+https://github.com/gschup/ggrs.git#1f14703a5859c2bcb0fa789beab15e80b572775f" dependencies = [ "bincode 1.3.3", "bitfield-rle", @@ -2909,7 +2908,7 @@ dependencies = [ "bitflags 2.13.0", "bytemuck", "core_maths", - "read-fonts", + "read-fonts 0.37.0", "smallvec", ] @@ -3258,7 +3257,7 @@ dependencies = [ "fontique", "htmlparser", "pulldown-cmark", - "skrifa", + "skrifa 0.40.0", ] [[package]] @@ -3285,7 +3284,7 @@ dependencies = [ "resvg", "rowan", "rspolib", - "skrifa", + "skrifa 0.40.0", "smol_str 0.3.6", "strum", "swash", @@ -3327,7 +3326,7 @@ dependencies = [ "rgb", "scoped-tls-hkt", "scopeguard", - "skrifa", + "skrifa 0.40.0", "slab", "strum", "swash", @@ -3407,7 +3406,7 @@ dependencies = [ "pin-weak", "raw-window-handle", "raw-window-metal", - "read-fonts", + "read-fonts 0.37.0", "scoped-tls-hkt", "skia-safe", "softbuffer", @@ -3435,7 +3434,7 @@ dependencies = [ "integer-sqrt", "lyon_path", "num-traits", - "skrifa", + "skrifa 0.40.0", "swash", "zeno", ] @@ -3917,13 +3916,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -4209,7 +4207,7 @@ dependencies = [ "cfg-if", "derive_more", "serde", - "uuid 1.23.2", + "uuid 1.23.3", ] [[package]] @@ -5261,7 +5259,7 @@ dependencies = [ "linebender_resource_handle", "parlance", "parley_data", - "skrifa", + "skrifa 0.40.0", ] [[package]] @@ -5912,6 +5910,16 @@ dependencies = [ "font-types", ] +[[package]] +name = "read-fonts" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -6127,7 +6135,7 @@ dependencies = [ "rend", "rkyv_derive", "tinyvec", - "uuid 1.23.2", + "uuid 1.23.3", ] [[package]] @@ -6808,7 +6816,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564" +dependencies = [ + "bytemuck", + "read-fonts 0.39.2", ] [[package]] @@ -7174,11 +7192,11 @@ dependencies = [ [[package]] name = "swash" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +checksum = "d1804632b66a35ca2b1d277eb0a138e10f46cb365b9a6d297e876b69ef79de43" dependencies = [ - "skrifa", + "skrifa 0.42.1", "yazi", "zeno", ] @@ -7981,9 +7999,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -8103,9 +8121,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" dependencies = [ "cfg-if", "once_cell", @@ -8116,9 +8134,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ "js-sys", "wasm-bindgen", @@ -8126,9 +8144,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8136,9 +8154,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ "bumpalo", "proc-macro2", @@ -8149,9 +8167,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] @@ -8327,9 +8345,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" dependencies = [ "js-sys", "wasm-bindgen", @@ -8456,7 +8474,7 @@ dependencies = [ "tokio", "turn", "url", - "uuid 1.23.2", + "uuid 1.23.3", "waitgroup", "webrtc-mdns", "webrtc-util", @@ -9424,7 +9442,7 @@ dependencies = [ "indexmap", "kurbo", "log", - "read-fonts", + "read-fonts 0.37.0", ] [[package]] @@ -9664,7 +9682,7 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "uuid 1.23.2", + "uuid 1.23.3", "windows-sys 0.61.2", "winnow 1.0.3", "zbus_macros", diff --git a/Cargo.toml b/Cargo.toml index 514b83ff..d6f48fe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ mimalloc = "0.1" regex = "1.11" tokio-tungstenite = { version = "0.29", features = ["native-tls"] } matchbox_socket = "0.14" -ggrs = { version = "0.12", features = ["sync-send"] } +ggrs = { git = "https://github.com/gschup/ggrs.git", features = ["sync-send"] } rustc-hash = "2.1" futures = "0.3" rand = { version = "0.10", features = ["serde"] } diff --git a/parallel-rdp/interface.cpp b/parallel-rdp/interface.cpp index 1a0bee23..8290e68d 100644 --- a/parallel-rdp/interface.cpp +++ b/parallel-rdp/interface.cpp @@ -221,10 +221,18 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) { callback.reset_game = true; break; case SDL_SCANCODE_LEFTBRACKET: - callback.lower_volume = true; + if (event->key.mod & SDL_KMOD_ALT) { + callback.decrease_input_delay = true; + } else { + callback.lower_volume = true; + } break; case SDL_SCANCODE_RIGHTBRACKET: - callback.raise_volume = true; + if (event->key.mod & SDL_KMOD_ALT) { + callback.increase_input_delay = true; + } else { + callback.raise_volume = true; + } break; case SDL_SCANCODE_SLASH: callback.frame_advance = true; @@ -697,6 +705,8 @@ CALL_BACK rdp_check_callback() { callback.reset_game = false; callback.lower_volume = false; callback.raise_volume = false; + callback.decrease_input_delay = false; + callback.increase_input_delay = false; callback.frame_advance = false; return return_value; } diff --git a/parallel-rdp/interface.hpp b/parallel-rdp/interface.hpp index 9d8d23fe..8fe9274b 100644 --- a/parallel-rdp/interface.hpp +++ b/parallel-rdp/interface.hpp @@ -34,6 +34,8 @@ typedef struct { bool enable_speedlimiter; bool lower_volume; bool raise_volume; + bool decrease_input_delay; + bool increase_input_delay; bool paused; bool frame_advance; uint32_t save_state_slot; diff --git a/src/device/vi.rs b/src/device/vi.rs index 2b512444..0563681f 100644 --- a/src/device/vi.rs +++ b/src/device/vi.rs @@ -103,9 +103,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u if device.netplay.is_none() { savestates::process_savestates(device); } - if !netplay::netplay_in_rollback(device.netplay.as_ref()) { - let _ = device.ui.video.fps_tx.as_ref().unwrap().try_send(true); - } + let _ = device.ui.video.fps_tx.as_ref().unwrap().try_send(true); } } _ => { @@ -122,10 +120,8 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { cheats::execute_cheats(device, device.cheats.cheats.clone()); } - if !netplay::netplay_in_rollback(device.netplay.as_ref()) { - ui::video::render_frame(); - let _ = device.ui.video.vis_tx.as_ref().unwrap().try_send(true); - } + ui::video::render_frame(); + let _ = device.ui.video.vis_tx.as_ref().unwrap().try_send(true); retroachievements::do_frame(); @@ -143,21 +139,18 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { reset_pace_deadline(device); } - if !netplay::netplay_in_rollback(device.netplay.as_ref()) - && device - .speed_limiter - .frame_counter - .is_multiple_of(device.speed_limiter.limit_freq) + if device + .speed_limiter + .frame_counter + .is_multiple_of(device.speed_limiter.limit_freq) && device.speed_limiter.enabled { speed_limiter(device); } - if !netplay::netplay_in_rollback(device.netplay.as_ref()) { - unsafe { sdl3_sys::events::SDL_PumpEvents() }; - ui::video::update_screen(); - device.speed_limiter.frame_counter += 1; - } + unsafe { sdl3_sys::events::SDL_PumpEvents() }; + ui::video::update_screen(); + device.speed_limiter.frame_counter += 1; if device.netplay.is_some() { device.netplay.as_mut().unwrap().inputs = netplay::process_requests(device); diff --git a/src/netplay.rs b/src/netplay.rs index 3e2a23e7..047d1ca4 100644 --- a/src/netplay.rs +++ b/src/netplay.rs @@ -42,10 +42,12 @@ pub struct Netplay { pub peers: Vec, pub player_number: usize, pub connected: [bool; 4], + pub input_delay: usize, pub messages: std::collections::HashMap>, pub received_data: std::collections::VecDeque>, pub inputs: Vec<(ui::input::InputData, ggrs::InputStatus)>, pub requests: std::collections::VecDeque>, + pub incoming_message: Vec, } #[derive(serde::Serialize, serde::Deserialize)] @@ -64,31 +66,37 @@ fn send_message(netplay: &mut Netplay, message: NetplayMessage) { } } +fn process_reliable_messages(netplay: &mut Netplay) { + netplay.received_data.extend( + netplay + .reliable_channel + .receive() + .iter() + .map(|(_, data)| data.to_vec()), + ); + + while !netplay.received_data.is_empty() { + if let Some(data) = netplay.received_data.pop_front() { + netplay.incoming_message.extend(data); + + if let Ok(decoded_message) = + postcard::from_bytes::(&netplay.incoming_message) + { + netplay + .messages + .insert(decoded_message.name, decoded_message.data); + netplay.incoming_message.clear(); + } + } + } +} + fn receive_message(netplay: &mut Netplay, name: &str) -> Vec { let timeout = std::time::Duration::from_secs(10); let now = std::time::Instant::now(); - let mut message = vec![]; + loop { - netplay.received_data.extend( - netplay - .reliable_channel - .receive() - .iter() - .map(|(_, data)| data.to_vec()), - ); - - while !netplay.received_data.is_empty() { - if let Some(data) = netplay.received_data.pop_front() { - message.extend(data); - - if let Ok(decoded_message) = postcard::from_bytes::(&message) { - netplay - .messages - .insert(decoded_message.name, decoded_message.data); - message.clear(); - } - } - } + process_reliable_messages(netplay); if let Some(data) = netplay.messages.remove(name) { return data; } @@ -170,19 +178,35 @@ pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec< *save_data = message; } -fn pending_frames(netplay: &Netplay) -> usize { - netplay - .requests - .iter() - .filter(|r| matches!(r, ggrs::GgrsRequest::AdvanceFrame { .. })) - .count() +pub fn send_input_delay(netplay: &mut Netplay, input_delay: usize) { + let message = NetplayMessage { + name: "input_delay".to_string(), + data: input_delay.to_be_bytes().to_vec(), + }; + send_message(netplay, message); + change_input_delay(netplay, input_delay); } -pub fn netplay_in_rollback(netplay: Option<&Netplay>) -> bool { - if let Some(netplay) = netplay { - pending_frames(netplay) != 0 - } else { - false +fn change_input_delay(netplay: &mut Netplay, input_delay: usize) { + netplay.input_delay = input_delay; + for handle in netplay.session.local_player_handles() { + if let Err(e) = netplay.session.set_input_delay(handle, input_delay) { + eprintln!("Error setting input delay: {}", e); + } else { + ui::video::onscreen_message( + &format!("Input delay: {}", input_delay), + ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT, + ); + } + } +} + +fn check_input_delay(netplay: &mut Netplay) { + if let Some(data) = netplay.messages.remove("input_delay") { + let input_delay = usize::from_be_bytes(data.try_into().unwrap()); + if input_delay != netplay.input_delay { + change_input_delay(netplay, input_delay); + } } } @@ -213,12 +237,12 @@ pub fn process_requests( } } else { // unsafe { sdl3_sys::events::SDL_PumpEvents() }; // so the screen doesn't freeze - return process_netplay(device); + process_netplay(device); } } } -fn process_netplay(device: &mut device::Device) -> Vec<(ui::input::InputData, ggrs::InputStatus)> { +fn process_netplay(device: &mut device::Device) { let netplay = device.netplay.as_mut().unwrap(); netplay.session.poll_remote_clients(); @@ -252,8 +276,9 @@ fn process_netplay(device: &mut device::Device) -> Vec<(ui::input::InputData, gg } } + process_reliable_messages(netplay); + check_input_delay(netplay); advance_frame(device); - process_requests(device) } fn advance_frame(device: &mut device::Device) { @@ -324,6 +349,7 @@ pub fn init( .with_fps(if pal { 50 } else { 60 }) .unwrap() .with_desync_detection_mode(ggrs::DesyncDetection::On { interval: 60 }) + .with_max_prediction_window(0) .with_disconnect_timeout(std::time::Duration::from_secs(if cfg!(debug_assertions) { 10 } else { @@ -357,6 +383,8 @@ pub fn init( } Some(Netplay { + incoming_message: vec![], + input_delay, session, reliable_channel, peers, diff --git a/src/savestates.rs b/src/savestates.rs index 294d8803..633b58fc 100644 --- a/src/savestates.rs +++ b/src/savestates.rs @@ -263,9 +263,7 @@ pub fn load_savestate(device: &mut device::Device, rewind: bool, rewind_frame: O if let Some(mut state) = state_data && device.rdram.size == state.device.rdram.size { - if device.netplay.is_none() { - ui::video::idle(); - } + ui::video::idle(); device.savestate.last_rewind_saved = state.device.vi.elapsed_time; diff --git a/src/ui/audio.rs b/src/ui/audio.rs index 46f8cef9..17c08224 100644 --- a/src/ui/audio.rs +++ b/src/ui/audio.rs @@ -1,5 +1,4 @@ use crate::device; -use crate::netplay; use crate::ui; static ADJUST_LOCKED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); @@ -174,9 +173,6 @@ pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) { if device.ui.audio.audio_stream.is_null() { return; } - if netplay::netplay_in_rollback(device.netplay.as_ref()) { - return; - } let mut primary_buffer: Vec = vec![0; length as usize / 2]; let mut i = 0; diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 162067f8..8a81c3ab 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -22,7 +22,7 @@ pub struct NetplayDevice { pub server_addr: String, pub player_number: usize, pub number_of_players: usize, - pub input_delay: i32, + pub input_delay: usize, } fn check_latest_version(weak: slint::Weak) { diff --git a/src/ui/gui/appwindow.slint b/src/ui/gui/appwindow.slint index 43fba923..1606678a 100644 --- a/src/ui/gui/appwindow.slint +++ b/src/ui/gui/appwindow.slint @@ -34,7 +34,7 @@ export component AppWindow inherits Window { callback join_session_button_clicked; callback netplay_discord_button_clicked; callback netplay_feedback_button_clicked; - callback netplay_begin_game; + callback netplay_begin_game(int); callback netplay_send_chat_message(string); callback netplay_refresh_sessions; callback netplay_create_session(string, string, string, string, string, string); @@ -242,8 +242,8 @@ export component AppWindow inherits Window { send_chat_message(message) => { netplay_send_chat_message(message); } - begin_game => { - netplay_begin_game(); + begin_game(a) => { + netplay_begin_game(a); } close => { netplay_close(); diff --git a/src/ui/gui/netplay_wait.slint b/src/ui/gui/netplay_wait.slint index 0acae03e..d3f4cd35 100644 --- a/src/ui/gui/netplay_wait.slint +++ b/src/ui/gui/netplay_wait.slint @@ -15,7 +15,7 @@ export global NetplayWaitData { export component NetplayWait inherits Page { title: @tr("Netplay: Waiting Room"); callback send_chat_message(string); - callback begin_game; + callback begin_game(int); callback close; VerticalBox { alignment: start; @@ -94,7 +94,7 @@ export component NetplayWait inherits Page { horizontal-alignment: center; wrap: word-wrap; } - HorizontalBox { + if NetplayData.player_name == NetplayWaitData.players[0]: HorizontalBox { alignment: center; Text { text: @tr("Local Input Delay:"); @@ -124,7 +124,7 @@ export component NetplayWait inherits Page { enabled: NetplayWaitData.can_start; clicked => { NetplayWaitData.can_start = false; - begin_game(); + begin_game(NetplayWaitData.input_delay); } } Button { diff --git a/src/ui/netplay.rs b/src/ui/netplay.rs index 810f697a..d7f40328 100644 --- a/src/ui/netplay.rs +++ b/src/ui/netplay.rs @@ -34,6 +34,7 @@ struct NetplaySession { features: Option>, players: Vec, server_address: Option, + input_delay: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -256,6 +257,7 @@ fn create_session( features: Some(features), players: vec![player_name], server_address: None, + input_delay: None, }; let create_session = NetplayLobbyMessage { message_type: MessageType::RequestCreateSession, @@ -358,6 +360,7 @@ fn join_session( features: None, players: vec![player_name], server_address: None, + input_delay: None, }; let join_session = NetplayLobbyMessage { message_type: MessageType::RequestJoinSession, @@ -498,11 +501,11 @@ fn setup_wait_window( }); let sender = netplay_write_sender.clone(); - app.on_netplay_begin_game(move || { + app.on_netplay_begin_game(move |chosen_input_delay| { let begin_game = NetplayLobbyMessage { message_type: MessageType::RequestBeginGame, sessions: std::collections::HashMap::new(), - message: None, + message: Some(chosen_input_delay.to_string()), }; sender.send(Some(begin_game)).unwrap(); }); @@ -547,7 +550,14 @@ fn setup_wait_window( .upgrade_in_event_loop(move |handle| { let player_name = handle.get_netplay_player_name(); let players = handle.get_netplay_players(); - let input_delay = handle.get_netplay_input_delay(); + let input_delay = response + .sessions + .iter() + .next() + .unwrap() + .1 + .input_delay + .unwrap(); let player_number = players.iter().position(|x| x == player_name).unwrap(); run_rom( diff --git a/src/ui/video.rs b/src/ui/video.rs index 5c08a0bc..74daf0d3 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] #![allow(non_camel_case_types)] include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs")); -use crate::{device, retroachievements, ui}; +use crate::{device, netplay, retroachievements, ui}; const PAL_WIDESCREEN_WIDTH: i32 = 512; const PAL_STANDARD_WIDTH: i32 = 384; @@ -243,6 +243,16 @@ pub fn check_callback(device: &mut device::Device) -> (bool, bool) { } else if callback.raise_volume { ui::audio::raise_audio_volume(&mut device.ui); } + + if let Some(netplay) = &mut device.netplay + && netplay.player_number == 0 + { + if callback.decrease_input_delay && netplay.input_delay > 0 { + netplay::send_input_delay(netplay, netplay.input_delay - 1); + } else if callback.increase_input_delay { + netplay::send_input_delay(netplay, netplay.input_delay + 1); + } + } (speed_limiter_toggled, callback.paused) }