game-networking-sockets 0.1.0

Rust abstraction for Valve GameNetworkingSockets library.
docs.rs failed to build game-networking-sockets-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: game-networking-sockets-0.1.2

Rust wrapper for Valve GameNetworkingSockets.

Provides an abstraction over the low-level library. There are multiple advantage to use this abstraction:

  • Type safety: most of the low-level structures are wrapped and we leverage the type system to restrict the operations such that they are all safe.
  • High level: the library abstract most of the structure in such a way that you don't have to deal with the low-level FFI plumbering required. The API is idiomatic, pure Rust.

Example

use gns::{GnsGlobal, GnsSocket, IsCreated};
use std::net::Ipv6Addr;
use std::time::Duration;

// **uwrap** must be banned in production, we use it here to extract the most relevant part of the library.

// Initial the global networking state. Note that this instance must be unique per-process.
let gns_global = GnsGlobal::get().unwrap();

// Create a new [`GnsSocket`], the index type [`IsCreated`] is used to determine the state of the socket.
// The [`GnsSocket::new`] function is only available for the [`IsCreated`] state. This is the initial state of the socket.
let gns_socket = GnsSocket::<IsCreated>::new(gns_global.clone());

// Choose your own port
let port = 9001;

// We now do a transition from [`IsCreated`] to the [`IsClient`] state. The [`GnsSocket::connect`] operation does this transition for us.
// Since we are now using a client socket, we have access to a different set of operations.
let client = gns_socket.connect(Ipv6Addr::LOCALHOST.into(), port).unwrap();

// Now that we initiated a connection, there is three operation we must loop over:
// - polling for new messages
// - polling for connection status change
// - polling for callbacks (low-level callbacks required by the underlying library).
// Important to know, regardless of the type of socket, whether it is in [`IsClient`] or [`IsServer`] state, theses three operations are the same.
// The only difference is that polling for messages and status on the client only act on the client connection, while polling for messages and status on a server yield event for all connected clients.

// You would loop on the below code.
// Run the low-level callbacks.
gns_global.poll_callbacks();

// Receive a maximum of 100 messages on the client connection.
// For each messages, print it's payload.
let _actual_nb_of_messages_processed = client.poll_messages::<100>(|message| {
  println!("{}", core::str::from_utf8(message.payload()).unwrap());
});

// Don't do anything with events.
// One would check the event for connection status, i.e. doing something when we are connected/disconnected from the server.
let _actual_nb_of_events_processed = client.poll_event::<100>(|_| {
});

// Sleep a little bit.
std::thread::sleep(Duration::from_millis(10))

Note

Every instance of of [GnsSocket] has a dangling [Weak<SegQueue<GnsConnectionEvent>>] pointer associated due to how polling works. Polling is done globally and may buffer events for already destructed [GnsSocket]. We use a weak pointer as user data on client/server connections to push events on [GnsGlobal::poll_callbacks], see the queue field of [IsClient] and [IsServer]. For simplicity (we may fix this later), every [GnsSocket] has it's own queue and we accept this pretty small memory leak. If you only ever create one instance for the lifetime of your application, this will have no effect.