From 9b99d7e9671b227cf3d6d27a6415a42c78c17608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aaro=20Per=C3=A4maa?= Date: Sun, 15 Sep 2024 23:53:00 +0300 Subject: [PATCH] Fix NetworkingMessages session accept and add example --- Cargo.toml | 2 +- examples/networking-messages/Cargo.toml | 8 ++ examples/networking-messages/README.md | 8 ++ examples/networking-messages/src/main.rs | 108 +++++++++++++++++++++++ src/matchmaking_servers.rs | 4 +- src/networking_messages.rs | 12 ++- 6 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 examples/networking-messages/Cargo.toml create mode 100644 examples/networking-messages/README.md create mode 100644 examples/networking-messages/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index c2cb1dc..0c0c073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ members = [ "./steamworks-sys", "examples/chat-example", "examples/workshop", - "examples/lobby", + "examples/lobby", "examples/networking-messages", ] [dependencies] diff --git a/examples/networking-messages/Cargo.toml b/examples/networking-messages/Cargo.toml new file mode 100644 index 0000000..db7e06a --- /dev/null +++ b/examples/networking-messages/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "networking-messages" +version = "0.1.0" +edition = "2021" + +[dependencies] +steamworks = { path = "../.." } +eframe = "0.28.1" diff --git a/examples/networking-messages/README.md b/examples/networking-messages/README.md new file mode 100644 index 0000000..2095f7c --- /dev/null +++ b/examples/networking-messages/README.md @@ -0,0 +1,8 @@ +# NetworkingMessages Example + +This example demonstrates how to use the NetworkingMessages API to send and receive messages between friends playing the +same game. You can use any SteamID, doesn't have to be a friend. For example, networking messages can be with the steam +matchmaking service. + +## Note +To use this example, you need to have two instances on two machines with two steam accounts running at the same time. diff --git a/examples/networking-messages/src/main.rs b/examples/networking-messages/src/main.rs new file mode 100644 index 0000000..06e2ea7 --- /dev/null +++ b/examples/networking-messages/src/main.rs @@ -0,0 +1,108 @@ +use eframe::{egui::*, *}; +use steamworks::{ + networking_types::{NetworkingIdentity, SendFlags}, + FriendFlags, +}; + +fn main() -> eframe::Result { + // 480 is Spacewar!, the Steamworks SDK example app. + let client = + steamworks::Client::init_app(480).expect("Steam is not running or has not been detected"); + + // Get the API interfaces + let friends = client.friends(); + let messages = client.networking_messages(); + + // Even though NetworkingMessages appears as ad-hoc API, it's internally session based. We must accept any incoming + // messages before communicating with the peer. + messages.session_request_callback(move |req| { + println!("Accepting session request from {:?}", req.remote()); + assert!(req.accept()); + }); + + // Install a callback to debug print failed peer connections + messages.session_failed_callback(|info| { + eprintln!("Session failed: {info:#?}"); + }); + + // UI state + let mut text_field = "Hello, world!".to_string(); + let mut message_history = vec![]; + + run_simple_native("steamworks-rs", Default::default(), move |ctx, _| { + // Run callback periodically, this is usually your main game loop or networking thread + client.run_callbacks(); + ctx.request_repaint(); + + CentralPanel::default().show(ctx, |ui| { + let text_height = ui.text_style_height(&TextStyle::Body); + + // Get a list of friends who are playing Spacewar! + let mut friend_list = friends.get_friends(FriendFlags::IMMEDIATE); + friend_list.retain(|f| f.game_played().map_or(false, |g| g.game.app_id().0 == 480)); + + // Show the friend list + SidePanel::left("friends").show_inside(ui, |ui| { + ui.heading(format!("Logged in: {}", friends.name())); + ui.label(format!("Online friends: {}", friend_list.len())); + + // Show the list of friends + ScrollArea::both().show_rows(ui, text_height, friend_list.len(), |ui, range| { + for friend in &friend_list[range] { + ui.monospace(friend.name()); + } + }); + }); + + // Receive any pending messages + let new_messages = messages.receive_messages_on_channel(0, 10); + for msg in new_messages { + println!("Received message #{:?}", msg.message_number()); + + let peer = msg.identity_peer(); + let data = std::str::from_utf8(msg.data()).expect("Peer sent invalid UTF-8"); + + message_history.push(format!("{peer:?}: {data}")); + } + + // Show message history + ui.heading(format!("Chat history ({} messages)", message_history.len())); + ScrollArea::both().auto_shrink([false, true]).show_rows( + ui, + text_height, + message_history.len(), + |ui, range| { + for msg in &message_history[range] { + ui.label(msg); + } + }, + ); + + // Text box for inputting a message and a button to send it + TopBottomPanel::bottom("textbox").show_inside(ui, |ui| { + ui.horizontal(|ui| { + ui.text_edit_singleline(&mut text_field).request_focus(); + + // Send message to all friends + if ui.button("Send message").clicked() { + for friend in &friend_list { + println!("Sending to {:?}", friend.id()); + + if let Err(err) = messages.send_message_to_user( + NetworkingIdentity::new_steam_id(friend.id()), + SendFlags::RELIABLE, + text_field.as_bytes(), + 0, + ) { + eprintln!("Send error: {err:?}"); + } + } + + // We can't send message to ourselves, so add it to chat history manually + message_history.push(format!("Me: {text_field}")); + } + }); + }); + }); + }) +} diff --git a/src/matchmaking_servers.rs b/src/matchmaking_servers.rs index 89d9712..cee909f 100644 --- a/src/matchmaking_servers.rs +++ b/src/matchmaking_servers.rs @@ -544,7 +544,7 @@ impl MatchmakingServers { #[test] fn test_internet_servers() { - let (client, single) = Client::init_app(304930).unwrap(); + let client = Client::init_app(304930).unwrap(); let data = std::rc::Rc::new(Mutex::new(0)); let data2 = std::rc::Rc::clone(&data); @@ -572,7 +572,7 @@ fn test_internet_servers() { .unwrap(); for _ in 0..2000 { - single.run_callbacks(); + client.run_callbacks(); std::thread::sleep(std::time::Duration::from_millis(10)); } } diff --git a/src/networking_messages.rs b/src/networking_messages.rs index b61dc01..57376fd 100644 --- a/src/networking_messages.rs +++ b/src/networking_messages.rs @@ -237,6 +237,7 @@ impl SessionRequestBuilder { self.inner.upgrade().map(|inner| SessionRequest { remote, messages: self.message, + accepted: false, _inner: inner, }) } @@ -279,6 +280,10 @@ unsafe impl Callback for NetworkingMessagesSessionFailed { pub struct SessionRequest { remote: NetworkingIdentity, messages: *mut sys::ISteamNetworkingMessages, + /// Keep track if connection should be rejected on drop + // This is used instead of `std::mem::forget` to properly clean up other + // resources. Is it even wise to automatically reject the connection? + accepted: bool, _inner: Arc>, } @@ -292,7 +297,8 @@ impl SessionRequest { } /// Accept the connection. - pub fn accept(self) -> bool { + pub fn accept(mut self) -> bool { + self.accepted = true; unsafe { return sys::SteamAPI_ISteamNetworkingMessages_AcceptSessionWithUser( self.messages, @@ -319,6 +325,8 @@ impl SessionRequest { impl Drop for SessionRequest { fn drop(&mut self) { - self.reject_inner(); + if !self.accepted { + self.reject_inner(); + } } }