Skip to content

Commit

Permalink
Fix NetworkingMessages session accept and add example
Browse files Browse the repository at this point in the history
  • Loading branch information
Noxime committed Sep 15, 2024
1 parent cbc9e8a commit 9b99d7e
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ members = [
"./steamworks-sys",
"examples/chat-example",
"examples/workshop",
"examples/lobby",
"examples/lobby", "examples/networking-messages",
]

[dependencies]
Expand Down
8 changes: 8 additions & 0 deletions examples/networking-messages/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "networking-messages"
version = "0.1.0"
edition = "2021"

[dependencies]
steamworks = { path = "../.." }
eframe = "0.28.1"
8 changes: 8 additions & 0 deletions examples/networking-messages/README.md
Original file line number Diff line number Diff line change
@@ -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.
108 changes: 108 additions & 0 deletions examples/networking-messages/src/main.rs
Original file line number Diff line number Diff line change
@@ -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}"));
}
});
});
});
})
}
4 changes: 2 additions & 2 deletions src/matchmaking_servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ impl<Manager> MatchmakingServers<Manager> {

#[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);
Expand Down Expand Up @@ -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));
}
}
12 changes: 10 additions & 2 deletions src/networking_messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ impl<Manager> SessionRequestBuilder<Manager> {
self.inner.upgrade().map(|inner| SessionRequest {
remote,
messages: self.message,
accepted: false,
_inner: inner,
})
}
Expand Down Expand Up @@ -279,6 +280,10 @@ unsafe impl Callback for NetworkingMessagesSessionFailed {
pub struct SessionRequest<Manager> {
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<Inner<Manager>>,
}

Expand All @@ -292,7 +297,8 @@ impl<Manager> SessionRequest<Manager> {
}

/// 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,
Expand All @@ -319,6 +325,8 @@ impl<Manager> SessionRequest<Manager> {

impl<Manager> Drop for SessionRequest<Manager> {
fn drop(&mut self) {
self.reject_inner();
if !self.accepted {
self.reject_inner();
}
}
}

0 comments on commit 9b99d7e

Please sign in to comment.