Skip to main content

gns/
lib.rs

1//! # Rust wrapper for Valve GameNetworkingSockets.
2//!
3//! Provides an abstraction over the low-level library.
4//! There are multiple advantage to use this abstraction:
5//! - 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**.
6//! - 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.
7//!
8//! # Example
9//!
10//! ```
11//! use gns::{GnsGlobal, GnsSocket, IsCreated};
12//! use std::net::Ipv6Addr;
13//! use std::time::Duration;
14//!
15//! // **uwrap** must be banned in production, we use it here to extract the most relevant part of the library.
16//!
17//! // Initial the global networking state. Note that this instance must be unique per-process.
18//! let gns_global = GnsGlobal::get().unwrap();
19//!
20//! // Create a new [`GnsSocket`], the index type [`IsCreated`] is used to determine the state of the socket.
21//! // The [`GnsSocket::new`] function is only available for the [`IsCreated`] state. This is the initial state of the socket.
22//! let gns_socket = GnsSocket::<IsCreated>::new(gns_global.clone());
23//!
24//! // Choose your own port
25//! let port = 9001;
26//!
27//! // We now do a transition from [`IsCreated`] to the [`IsClient`] state. The [`GnsSocket::connect`] operation does this transition for us.
28//! // Since we are now using a client socket, we have access to a different set of operations.
29//! let client = gns_socket.connect(Ipv6Addr::LOCALHOST.into(), port).unwrap();
30//!
31//! // Now that we initiated a connection, there is three operation we must loop over:
32//! // - polling for new messages
33//! // - polling for connection status change
34//! // - polling for callbacks (low-level callbacks required by the underlying library).
35//! // Important to know, regardless of the type of socket, whether it is in [`IsClient`] or [`IsServer`] state, theses three operations are the same.
36//! // 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.
37//!
38//! // You would loop on the below code.
39//! // Run the low-level callbacks.
40//! gns_global.poll_callbacks();
41//!
42//! // Receive a maximum of 100 messages on the client connection.
43//! // For each messages, print it's payload.
44//! let _actual_nb_of_messages_processed = client.poll_messages::<100>(|message| {
45//!   println!("{}", core::str::from_utf8(message.payload()).unwrap());
46//! });
47//!
48//! // Don't do anything with events.
49//! // One would check the event for connection status, i.e. doing something when we are connected/disconnected from the server.
50//! let _actual_nb_of_events_processed = client.poll_event::<100>(|_| {
51//! });
52//!
53//! // Sleep a little bit.
54//! std::thread::sleep(Duration::from_millis(10))
55//! ```
56//!
57//! # Note
58//!
59//! 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.
60
61use crossbeam_queue::SegQueue;
62use either::Either;
63pub use gns_sys as sys;
64use std::sync::atomic::{AtomicI64, Ordering};
65use std::{
66    collections::HashMap,
67    ffi::{c_void, CStr, CString},
68    marker::PhantomData,
69    mem::MaybeUninit,
70    net::{IpAddr, Ipv4Addr, Ipv6Addr},
71    sync::{Arc, Mutex, Weak},
72    time::Duration,
73};
74use sys::*;
75
76fn get_interface() -> *mut ISteamNetworkingSockets {
77    unsafe { SteamAPI_SteamNetworkingSockets_v009() }
78}
79
80fn get_utils() -> *mut ISteamNetworkingUtils {
81    unsafe { SteamAPI_SteamNetworkingUtils_v003() }
82}
83
84/// A network message number. Simple alias for documentation.
85pub type GnsMessageNumber = u64;
86
87/// Outcome of many functions from this library, basic type alias with steam [`sys::EResult`] as error.
88/// If the result is [`sys::EResult::k_EResultOK`], the value can safely be wrapped, otherwise we return the error.
89pub type GnsResult<T> = Result<T, EResult>;
90
91/// Wrapper around steam [`sys::EResult`].
92/// The library ensure that the wrapped value is not [`sys::EResult::k_EResultOK`].
93#[repr(transparent)]
94#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
95pub struct GnsError(EResult);
96
97impl Into<EResult> for GnsError {
98    fn into(self) -> EResult {
99        self.0
100    }
101}
102
103impl GnsError {
104    pub fn into_result(self) -> GnsResult<()> {
105        self.into()
106    }
107}
108
109impl From<GnsError> for GnsResult<()> {
110    fn from(GnsError(result): GnsError) -> Self {
111        match result {
112            EResult::k_EResultOK => Ok(()),
113            e => Err(e),
114        }
115    }
116}
117
118/// Wraps the initialization/destruction of the low-level *GameNetworkingSockets* and associated
119/// singletons.
120///
121/// A reference can be retrieved via [`GnsGlobal::get()`], which will initialize
122/// *GameNetworkingSockets* if it has not yet been initialized.
123pub struct GnsGlobal {
124    utils: GnsUtils,
125    next_queue_id: AtomicI64,
126    event_queues: Mutex<HashMap<i64, Weak<SegQueue<GnsConnectionEvent>>>>,
127}
128
129static GNS_GLOBAL: Mutex<Option<Arc<GnsGlobal>>> = Mutex::new(None);
130
131impl GnsGlobal {
132    /// Try to acquire a reference to the [`GnsGlobal`] instance.
133    ///
134    /// If GnsGlobal has not yet been successfully initialized, a call to
135    /// [`sys::GameNetworkingSockets_Init`] will be made. If successful, a reference to GnsGlobal
136    /// will be returned.
137    ///
138    /// If GnsGlobal has already been initialized, this method returns a reference to the already
139    /// created GnsGlobal instance.
140    ///
141    /// # Errors
142    /// If a call to [`sys::GameNetworkingSockets_Init`] errors, that error will be propagated as a
143    /// String message.
144    pub fn get() -> Result<Arc<Self>, String> {
145        let mut lock = GNS_GLOBAL.lock().unwrap();
146        if let Some(gns_global) = lock.clone() {
147            Ok(gns_global)
148        } else {
149            unsafe {
150                let mut error: SteamDatagramErrMsg = MaybeUninit::zeroed().assume_init();
151                if !GameNetworkingSockets_Init(core::ptr::null(), &mut error) {
152                    Err(format!(
153                        "{}",
154                        CStr::from_ptr(error.as_ptr()).to_str().unwrap_or("")
155                    ))
156                } else {
157                    let gns_global = Arc::new(GnsGlobal {
158                        utils: GnsUtils(()),
159                        next_queue_id: AtomicI64::new(0),
160                        event_queues: Mutex::new(HashMap::new()),
161                    });
162                    *lock = Some(gns_global.clone());
163                    Ok(gns_global)
164                }
165            }
166        }
167    }
168
169    #[inline]
170    pub fn poll_callbacks(&self) {
171        unsafe {
172            SteamAPI_ISteamNetworkingSockets_RunCallbacks(get_interface());
173        }
174    }
175
176    pub fn utils(&self) -> &GnsUtils {
177        &self.utils
178    }
179
180    fn create_queue(&self) -> (i64, Arc<SegQueue<GnsConnectionEvent>>) {
181        let queue = Arc::new(SegQueue::new());
182        let queue_id = self.next_queue_id.fetch_add(1, Ordering::SeqCst);
183        self.event_queues
184            .lock()
185            .unwrap()
186            .insert(queue_id, Arc::downgrade(&queue));
187        (queue_id, queue)
188    }
189}
190
191/// Opaque wrapper around the low-level [`sys::HSteamListenSocket`].
192#[repr(transparent)]
193pub struct GnsListenSocket(HSteamListenSocket);
194
195/// Opaque wrapper around the low-level [`sys::HSteamNetPollGroup`].
196#[repr(transparent)]
197pub struct GnsPollGroup(HSteamNetPollGroup);
198
199/// Initial state of a [`GnsSocket`].
200/// This state represent a socket that has not been used as a Server or Client implementation.
201/// Consequently, the state is empty.
202pub struct IsCreated;
203
204/// Common functions available for any [`GnsSocket`] state that is implementing it.
205/// Regardless of being a client or server, a ready socket will allow us to query for connection events as well as receive messages.
206pub trait IsReady {
207    /// Return a reference to the connection event queue. The queue is thread-safe.
208    fn queue(&self) -> &SegQueue<GnsConnectionEvent>;
209    /// Poll for incoming messages. K represent the maximum number of messages we are willing to receive.
210    /// Return the actual number of messsages that has been received.
211    fn receive<const K: usize>(&self, messages: &mut [GnsNetworkMessage<ToReceive>; K]) -> usize;
212}
213
214/// State of a [`GnsSocket`] that has been determined to be a server, usually via the [`GnsSocket::listen`] call.
215/// In this state, the socket hold the data required to accept connections and poll them for messages.
216pub struct IsServer {
217    /// Thread-safe FIFO queue used to read the connection status changes.
218    /// Note that this structure is pinned to ensure that it's address remain the same as we are using it as connection **UserData**.
219    /// This queue is meant to be passed to [`GnsSocket::on_connection_state_changed`].
220    /// As long as the socket exists, this queue must exists.
221    queue: Arc<SegQueue<GnsConnectionEvent>>,
222    /// The low-level listen socket. Irrelevant to the user.
223    listen_socket: GnsListenSocket,
224    /// The low-level polling group. Irrelevant to the user.
225    poll_group: GnsPollGroup,
226}
227
228impl Drop for IsServer {
229    fn drop(&mut self) {
230        unsafe {
231            SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
232                get_interface(),
233                self.listen_socket.0,
234            );
235            SteamAPI_ISteamNetworkingSockets_DestroyPollGroup(get_interface(), self.poll_group.0);
236        }
237    }
238}
239
240impl IsReady for IsServer {
241    #[inline]
242    fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
243        &self.queue
244    }
245
246    #[inline]
247    fn receive<const K: usize>(&self, messages: &mut [GnsNetworkMessage<ToReceive>; K]) -> usize {
248        unsafe {
249            SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
250                get_interface(),
251                self.poll_group.0,
252                messages.as_mut_ptr() as _,
253                K as _,
254            ) as _
255        }
256    }
257}
258
259/// State of a [`GnsSocket`] that has been determined to be a client, usually via the [`GnsSocket::connect`] call.
260/// In this state, the socket hold the data required to receive and send messages.
261pub struct IsClient {
262    /// Equals to [`IsServer.queue`].
263    queue: Arc<SegQueue<GnsConnectionEvent>>,
264    /// Actual client connection, used to receive/send messages.
265    connection: GnsConnection,
266}
267
268impl Drop for IsClient {
269    fn drop(&mut self) {
270        unsafe {
271            SteamAPI_ISteamNetworkingSockets_CloseConnection(
272                get_interface(),
273                self.connection.0,
274                0,
275                core::ptr::null(),
276                false,
277            );
278        }
279    }
280}
281
282impl IsReady for IsClient {
283    #[inline]
284    fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
285        &self.queue
286    }
287
288    #[inline]
289    fn receive<const K: usize>(&self, messages: &mut [GnsNetworkMessage<ToReceive>; K]) -> usize {
290        unsafe {
291            SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(
292                get_interface(),
293                self.connection.0,
294                messages.as_mut_ptr() as _,
295                K as _,
296            ) as _
297        }
298    }
299}
300
301pub trait MayDrop {
302    const MUST_DROP: bool;
303}
304
305pub struct ToSend(());
306
307impl MayDrop for ToSend {
308    const MUST_DROP: bool = false;
309}
310
311pub struct ToReceive(());
312
313impl MayDrop for ToReceive {
314    const MUST_DROP: bool = true;
315}
316
317/// Lane priority
318pub type Priority = u32;
319/// Lane weight
320pub type Weight = u16;
321/// A lane is represented by a Priority and a Weight
322pub type GnsLane = (Priority, Weight);
323/// A lane Id.
324pub type GnsLaneId = u16;
325
326/// Wrapper around the low-level equivalent.
327/// This type is used to implements a more type-safe version of messages.
328///
329/// You will encounter two instances, either [`GnsNetworkMessage<ToReceive>`] or [`GnsNetworkMessage<ToSend>`].
330/// The former is generated by the library and must be freed unpon handling.
331/// The later is created prior to sending it via the low-level call and the low-level call itself make sure that it is freed.
332#[repr(transparent)]
333pub struct GnsNetworkMessage<T: MayDrop>(*mut ISteamNetworkingMessage, PhantomData<T>);
334
335impl<T> Drop for GnsNetworkMessage<T>
336where
337    T: MayDrop,
338{
339    fn drop(&mut self) {
340        if T::MUST_DROP && !self.0.is_null() {
341            unsafe {
342                SteamAPI_SteamNetworkingMessage_t_Release(self.0);
343            }
344        }
345    }
346}
347
348impl<T> GnsNetworkMessage<T>
349where
350    T: MayDrop,
351{
352    /// Unsafe function you will highly unlikely use.
353    #[inline]
354    pub unsafe fn into_inner(self) -> *mut ISteamNetworkingMessage {
355        self.0
356    }
357
358    #[inline]
359    pub fn payload(&self) -> &[u8] {
360        unsafe {
361            core::slice::from_raw_parts((*self.0).m_pData as *const u8, (*self.0).m_cbSize as _)
362        }
363    }
364
365    #[inline]
366    pub fn message_number(&self) -> u64 {
367        unsafe { (*self.0).m_nMessageNumber as _ }
368    }
369
370    #[inline]
371    pub fn lane(&self) -> GnsLaneId {
372        unsafe { (*self.0).m_idxLane }
373    }
374
375    #[inline]
376    pub fn flags(&self) -> i32 {
377        unsafe { (*self.0).m_nFlags as _ }
378    }
379
380    #[inline]
381    pub fn user_data(&self) -> u64 {
382        unsafe { (*self.0).m_nUserData as _ }
383    }
384
385    #[inline]
386    pub fn connection(&self) -> GnsConnection {
387        GnsConnection(unsafe { (*self.0).m_conn })
388    }
389
390    pub fn connection_user_data(&self) -> u64 {
391        unsafe { (*self.0).m_nConnUserData as _ }
392    }
393}
394
395impl GnsNetworkMessage<ToSend> {
396    #[inline]
397    fn new(
398        ptr: *mut ISteamNetworkingMessage,
399        conn: GnsConnection,
400        flags: i32,
401        payload: &[u8],
402    ) -> Self {
403        GnsNetworkMessage(ptr, PhantomData)
404            .set_flags(flags)
405            .set_payload(payload)
406            .set_connection(conn)
407    }
408
409    #[inline]
410    pub fn set_connection(self, GnsConnection(conn): GnsConnection) -> Self {
411        unsafe { (*self.0).m_conn = conn }
412        self
413    }
414
415    #[inline]
416    pub fn set_payload(self, payload: &[u8]) -> Self {
417        unsafe {
418            core::ptr::copy_nonoverlapping(
419                payload.as_ptr(),
420                (*self.0).m_pData as *mut u8,
421                payload.len(),
422            );
423        }
424        self
425    }
426
427    #[inline]
428    pub fn set_lane(self, lane: u16) -> Self {
429        unsafe { (*self.0).m_idxLane = lane }
430        self
431    }
432
433    #[inline]
434    pub fn set_flags(self, flags: i32) -> Self {
435        unsafe { (*self.0).m_nFlags = flags as _ }
436        self
437    }
438
439    #[inline]
440    pub fn set_user_data(self, userdata: u64) -> Self {
441        unsafe { (*self.0).m_nUserData = userdata as _ }
442        self
443    }
444}
445
446#[repr(transparent)]
447#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
448pub struct GnsConnection(HSteamNetConnection);
449
450#[derive(Default, Copy, Clone)]
451pub struct GnsConnectionInfo(SteamNetConnectionInfo_t);
452
453impl GnsConnectionInfo {
454    #[inline]
455    pub fn state(&self) -> ESteamNetworkingConnectionState {
456        self.0.m_eState
457    }
458
459    #[inline]
460    pub fn end_reason(&self) -> u32 {
461        self.0.m_eEndReason as u32
462    }
463
464    #[inline]
465    pub fn end_debug(&self) -> &str {
466        unsafe { CStr::from_ptr(self.0.m_szEndDebug.as_ptr()) }
467            .to_str()
468            .unwrap_or("")
469    }
470
471    #[inline]
472    pub fn remote_address(&self) -> IpAddr {
473        let ipv4 = unsafe { self.0.m_addrRemote.__bindgen_anon_1.m_ipv4 };
474        if ipv4.m_8zeros == 0 && ipv4.m_0000 == 0 && ipv4.m_ffff == 0xffff {
475            IpAddr::from(Ipv4Addr::from(ipv4.m_ip))
476        } else {
477            IpAddr::from(Ipv6Addr::from(unsafe {
478                self.0.m_addrRemote.__bindgen_anon_1.m_ipv6
479            }))
480        }
481    }
482
483    #[inline]
484    pub fn remote_port(&self) -> u16 {
485        self.0.m_addrRemote.m_port
486    }
487}
488
489#[derive(Debug, Default, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
490pub struct GnsConnectionRealTimeLaneStatus(SteamNetConnectionRealTimeLaneStatus_t);
491
492impl GnsConnectionRealTimeLaneStatus {
493    #[inline]
494    pub fn pending_bytes_unreliable(&self) -> u32 {
495        self.0.m_cbPendingUnreliable as _
496    }
497
498    #[inline]
499    pub fn pending_bytes_reliable(&self) -> u32 {
500        self.0.m_cbPendingReliable as _
501    }
502
503    #[inline]
504    pub fn bytes_sent_unacked_reliable(&self) -> u32 {
505        self.0.m_cbSentUnackedReliable as _
506    }
507
508    #[inline]
509    pub fn approximated_queue_time(&self) -> Duration {
510        Duration::from_micros(self.0.m_usecQueueTime as _)
511    }
512}
513
514#[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq)]
515pub struct GnsConnectionRealTimeStatus(SteamNetConnectionRealTimeStatus_t);
516
517impl GnsConnectionRealTimeStatus {
518    #[inline]
519    pub fn state(&self) -> ESteamNetworkingConnectionState {
520        self.0.m_eState
521    }
522
523    #[inline]
524    pub fn ping(&self) -> u32 {
525        self.0.m_nPing as _
526    }
527
528    #[inline]
529    pub fn quality_local(&self) -> f32 {
530        self.0.m_flConnectionQualityLocal
531    }
532
533    #[inline]
534    pub fn quality_remote(&self) -> f32 {
535        self.0.m_flConnectionQualityRemote
536    }
537
538    #[inline]
539    pub fn out_packets_per_sec(&self) -> f32 {
540        self.0.m_flOutPacketsPerSec
541    }
542
543    #[inline]
544    pub fn out_bytes_per_sec(&self) -> f32 {
545        self.0.m_flOutBytesPerSec
546    }
547
548    #[inline]
549    pub fn in_packets_per_sec(&self) -> f32 {
550        self.0.m_flInPacketsPerSec
551    }
552
553    #[inline]
554    pub fn in_bytes_per_sec(&self) -> f32 {
555        self.0.m_flInBytesPerSec
556    }
557
558    #[inline]
559    pub fn send_rate_bytes_per_sec(&self) -> u32 {
560        self.0.m_nSendRateBytesPerSecond as _
561    }
562
563    #[inline]
564    pub fn pending_bytes_unreliable(&self) -> u32 {
565        self.0.m_cbPendingUnreliable as _
566    }
567
568    #[inline]
569    pub fn pending_bytes_reliable(&self) -> u32 {
570        self.0.m_cbPendingReliable as _
571    }
572
573    #[inline]
574    pub fn bytes_sent_unacked_reliable(&self) -> u32 {
575        self.0.m_cbSentUnackedReliable as _
576    }
577
578    #[inline]
579    pub fn approximated_queue_time(&self) -> Duration {
580        Duration::from_micros(self.0.m_usecQueueTime as _)
581    }
582
583    /// Returns the highest packet jitter experienced since the last time this
584    /// information was fetched. The high water mark is cleared each time you
585    /// fetch the info.
586    ///
587    /// Returns `None` if no jitter data is available (the underlying value is negative),
588    /// or if the connection type doesn't support jitter measurement.
589    #[inline]
590    pub fn max_jitter_usec(&self) -> Option<i32> {
591        let val = self.0.m_usecMaxJitter;
592        if val < 0 { None } else { Some(val) }
593    }
594}
595
596#[derive(Default, Copy, Clone)]
597pub struct GnsConnectionEvent(SteamNetConnectionStatusChangedCallback_t);
598
599impl GnsConnectionEvent {
600    #[inline]
601    pub fn old_state(&self) -> ESteamNetworkingConnectionState {
602        self.0.m_eOldState
603    }
604
605    #[inline]
606    pub fn connection(&self) -> GnsConnection {
607        GnsConnection(self.0.m_hConn)
608    }
609
610    #[inline]
611    pub fn info(&self) -> GnsConnectionInfo {
612        GnsConnectionInfo(self.0.m_info)
613    }
614}
615
616/// [`GnsSocket`] is the most important structure of this library.
617/// This structure is used to create client ([`GnsSocket<IsClient>`]) and server ([`GnsSocket<IsServer>`]) sockets via the [`GnsSocket::connect`] and [`GnsSocket::listen`] functions.
618/// The drop implementation make sure that everything related to this structure is correctly freed, except the [`GnsGlobal`] instance and the user has a strong guarantee that all the available operations over the socket are **safe**.
619pub struct GnsSocket<S> {
620    global: Arc<GnsGlobal>,
621    state: S,
622}
623
624impl<S> GnsSocket<S>
625where
626    S: IsReady,
627{
628    /// Get a connection lane status.
629    /// This call is possible only if lanes has been previously configured using configure_connection_lanes
630    #[inline]
631    pub fn get_connection_real_time_status(
632        &self,
633        GnsConnection(conn): GnsConnection,
634        nb_of_lanes: u32,
635    ) -> GnsResult<(
636        GnsConnectionRealTimeStatus,
637        Vec<GnsConnectionRealTimeLaneStatus>,
638    )> {
639        let mut lanes: Vec<GnsConnectionRealTimeLaneStatus> =
640            vec![Default::default(); nb_of_lanes as _];
641        let mut status: GnsConnectionRealTimeStatus = Default::default();
642        GnsError(unsafe {
643            SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus(
644                get_interface(),
645                conn,
646                &mut status as *mut GnsConnectionRealTimeStatus
647                    as *mut SteamNetConnectionRealTimeStatus_t,
648                nb_of_lanes as _,
649                lanes.as_mut_ptr() as *mut GnsConnectionRealTimeLaneStatus
650                    as *mut SteamNetConnectionRealTimeLaneStatus_t,
651            )
652        })
653        .into_result()?;
654        Ok((status, lanes))
655    }
656
657    #[inline]
658    pub fn get_connection_info(
659        &self,
660        GnsConnection(conn): GnsConnection,
661    ) -> Option<GnsConnectionInfo> {
662        let mut info: SteamNetConnectionInfo_t = Default::default();
663        if unsafe {
664            SteamAPI_ISteamNetworkingSockets_GetConnectionInfo(get_interface(), conn, &mut info)
665        } {
666            Some(GnsConnectionInfo(info))
667        } else {
668            None
669        }
670    }
671
672    #[inline]
673    pub fn flush_messages_on_connection(
674        &self,
675        GnsConnection(conn): GnsConnection,
676    ) -> GnsResult<()> {
677        GnsError(unsafe {
678            SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection(get_interface(), conn)
679        })
680        .into_result()
681    }
682
683    #[inline]
684    pub fn close_connection(
685        &self,
686        GnsConnection(conn): GnsConnection,
687        reason: u32,
688        debug: &str,
689        linger: bool,
690    ) -> bool {
691        let debug_c = CString::new(debug).expect("str; qed;");
692        unsafe {
693            SteamAPI_ISteamNetworkingSockets_CloseConnection(
694                get_interface(),
695                conn,
696                reason as _,
697                debug_c.as_ptr(),
698                linger,
699            )
700        }
701    }
702
703    #[inline]
704    pub fn poll_messages<const K: usize>(
705        &self,
706        mut message_callback: impl FnMut(&GnsNetworkMessage<ToReceive>),
707    ) -> Option<usize> {
708        // Do not implements default for networking messages as they must be allocated by the lib.
709        let mut messages: [GnsNetworkMessage<ToReceive>; K] =
710            unsafe { MaybeUninit::zeroed().assume_init() };
711        let nb_of_messages = self.state.receive(&mut messages);
712        if nb_of_messages == usize::MAX {
713            None
714        } else {
715            for message in messages.into_iter().take(nb_of_messages) {
716                message_callback(&message);
717            }
718            Some(nb_of_messages)
719        }
720    }
721
722    #[inline]
723    pub fn poll_event<const K: usize>(
724        &self,
725        mut event_callback: impl FnMut(GnsConnectionEvent),
726    ) -> usize {
727        let mut processed = 0;
728        'a: while let Some(event) = self.state.queue().pop() {
729            event_callback(event);
730            processed += 1;
731            if processed == K {
732                break 'a;
733            }
734        }
735        processed
736    }
737
738    #[inline]
739    pub fn configure_connection_lanes(
740        &self,
741        GnsConnection(connection): GnsConnection,
742        lanes: &[GnsLane],
743    ) -> GnsResult<()> {
744        let (priorities, weights): (Vec<_>, Vec<_>) = lanes.iter().copied().unzip();
745        GnsError(unsafe {
746            SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes(
747                get_interface(),
748                connection,
749                lanes.len() as _,
750                priorities.as_ptr() as *const u32 as *const i32,
751                weights.as_ptr(),
752            )
753        })
754        .into_result()
755    }
756
757    #[inline]
758    pub fn send_messages(
759        &self,
760        messages: Vec<GnsNetworkMessage<ToSend>>,
761    ) -> Vec<Either<GnsMessageNumber, EResult>> {
762        let mut result = vec![0i64; messages.len()];
763        unsafe {
764            SteamAPI_ISteamNetworkingSockets_SendMessages(
765                get_interface(),
766                messages.len() as _,
767                messages.as_ptr() as *const _,
768                result.as_mut_ptr(),
769            );
770        }
771        result
772            .into_iter()
773            .map(|value| {
774                if value < 0 {
775                    Either::Right(unsafe { core::mem::transmute((-value) as u32) })
776                } else {
777                    Either::Left(value as _)
778                }
779            })
780            .collect()
781    }
782}
783
784impl GnsSocket<IsCreated> {
785    /// Unsafe, C-like callback, we use the user data to pass the queue ID, so we can find the
786    /// correct queue in GnsGlobal.
787    unsafe extern "C" fn on_connection_state_changed(
788        info: &mut SteamNetConnectionStatusChangedCallback_t,
789    ) {
790        let gns_global = GnsGlobal::get()
791            // GnsGlobal needs to be initialized to even reach this point in the first place.
792            .expect("GnsGlobal should be initialized");
793
794        let queue_id = info.m_info.m_nUserData as _;
795        let mut queues = gns_global.event_queues.lock().unwrap();
796        if let Some(queue) = queues.get(&queue_id) {
797            if let Some(queue) = queue.upgrade() {
798                queue.push(GnsConnectionEvent(*info));
799            } else {
800                // The queue is no longer valid as the associated GnsSocket has been dropped
801                queues.remove(&queue_id);
802            }
803        }
804    }
805
806    /// Initialize a new socket in [`IsCreated`] state.
807    #[inline]
808    pub fn new(global: Arc<GnsGlobal>) -> Self {
809        GnsSocket {
810            global,
811            state: IsCreated,
812        }
813    }
814
815    #[inline]
816    fn setup_common(
817        address: IpAddr,
818        port: u16,
819        queue_id: int64,
820    ) -> (SteamNetworkingIPAddr, [SteamNetworkingConfigValue_t; 2]) {
821        let addr = SteamNetworkingIPAddr {
822            __bindgen_anon_1: match address {
823                IpAddr::V4(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
824                    m_ipv4: SteamNetworkingIPAddr_IPv4MappedAddress {
825                        m_8zeros: 0,
826                        m_0000: 0,
827                        m_ffff: 0xffff,
828                        m_ip: address.octets(),
829                    },
830                },
831                IpAddr::V6(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
832                    m_ipv6: address.octets(),
833                },
834            },
835            m_port: port,
836        };
837        let options = [SteamNetworkingConfigValue_t {
838            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr,
839            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
840            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
841              m_ptr: Self::on_connection_state_changed as *const fn(&SteamNetConnectionStatusChangedCallback_t) as *mut c_void
842            }
843          }, SteamNetworkingConfigValue_t {
844            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int64,
845            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_ConnectionUserData,
846            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
847              m_int64: queue_id
848            }
849        }];
850        (addr, options)
851    }
852
853    /// Listen for incoming connections, the socket transition from [`IsCreated`] to [`IsServer`], allowing a new set of server operations.
854    #[inline]
855    pub fn listen(self, address: IpAddr, port: u16) -> Result<GnsSocket<IsServer>, ()> {
856        let (queue_id, queue) = self.global.create_queue();
857        let (addr, options) = Self::setup_common(address, port, queue_id);
858        let listen_socket = unsafe {
859            SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
860                get_interface(),
861                &addr,
862                options.len() as _,
863                options.as_ptr(),
864            )
865        };
866        if listen_socket == k_HSteamListenSocket_Invalid {
867            Err(())
868        } else {
869            let poll_group =
870                unsafe { SteamAPI_ISteamNetworkingSockets_CreatePollGroup(get_interface()) };
871            if poll_group == k_HSteamNetPollGroup_Invalid {
872                Err(())
873            } else {
874                Ok(GnsSocket {
875                    global: self.global,
876                    state: IsServer {
877                        queue,
878                        listen_socket: GnsListenSocket(listen_socket),
879                        poll_group: GnsPollGroup(poll_group),
880                    },
881                })
882            }
883        }
884    }
885
886    /// Connect to a remote host, the socket transition from [`IsCreated`] to [`IsClient`], allowing a new set of client operations.
887    #[inline]
888    pub fn connect(self, address: IpAddr, port: u16) -> Result<GnsSocket<IsClient>, ()> {
889        let (queue_id, queue) = self.global.create_queue();
890        let (addr, options) = Self::setup_common(address, port, queue_id);
891        let connection = unsafe {
892            SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(
893                get_interface(),
894                &addr,
895                options.len() as _,
896                options.as_ptr(),
897            )
898        };
899        if connection == k_HSteamNetConnection_Invalid {
900            Err(())
901        } else {
902            Ok(GnsSocket {
903                global: self.global,
904                state: IsClient {
905                    queue,
906                    connection: GnsConnection(connection),
907                },
908            })
909        }
910    }
911}
912
913impl GnsSocket<IsServer> {
914    /// Accept an incoming connection. This operation is available only if the socket is in the [`IsServer`] state.
915    #[inline]
916    pub fn accept(&self, connection: GnsConnection) -> GnsResult<()> {
917        GnsError(unsafe {
918            SteamAPI_ISteamNetworkingSockets_AcceptConnection(get_interface(), connection.0)
919        })
920        .into_result()?;
921        if !unsafe {
922            SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
923                get_interface(),
924                connection.0,
925                self.state.poll_group.0,
926            )
927        } {
928            panic!("It's impossible not to be able to set the connection poll group as both the poll group and the connection must be valid at this point.");
929        }
930        Ok(())
931    }
932}
933
934impl GnsSocket<IsClient> {
935    /// Return the socket connection. This operation is available only if the socket is in the [`IsClient`] state.
936    #[inline]
937    pub fn connection(&self) -> GnsConnection {
938        self.state.connection
939    }
940}
941
942/// The configuration value used to define configure global variables in [`GnsUtils::set_global_config_value`]
943pub enum GnsConfig<'a> {
944    Float(f32),
945    Int32(u32),
946    String(&'a str),
947    Ptr(*mut c_void),
948}
949
950pub struct GnsUtils(());
951
952type MsgPtr = *const ::std::os::raw::c_char;
953
954impl GnsUtils {
955    #[inline]
956    pub fn enable_debug_output(
957        &self,
958        ty: ESteamNetworkingSocketsDebugOutputType,
959        f: fn(ty: ESteamNetworkingSocketsDebugOutputType, msg: String),
960    ) {
961        static mut F: Option<fn(ty: ESteamNetworkingSocketsDebugOutputType, msg: String)> = None;
962        unsafe {
963            F = Some(f);
964        }
965        unsafe extern "C" fn debug(ty: ESteamNetworkingSocketsDebugOutputType, msg: MsgPtr) {
966            F.unwrap()(ty, CStr::from_ptr(msg).to_string_lossy().to_string());
967        }
968        unsafe {
969            SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(get_utils(), ty, Some(debug));
970        }
971    }
972
973    /// Allocate a new message to be sent.
974    /// This message must be sent if allocated, as the message can only be freed by the `GnsSocket::send_messages` call.
975    #[inline]
976    pub fn allocate_message(
977        &self,
978        conn: GnsConnection,
979        flags: i32,
980        payload: &[u8],
981    ) -> GnsNetworkMessage<ToSend> {
982        let message_ptr = unsafe {
983            SteamAPI_ISteamNetworkingUtils_AllocateMessage(get_utils(), payload.len() as _)
984        };
985        GnsNetworkMessage::new(message_ptr, conn, flags, payload)
986    }
987
988    /// Set a global configuration value, i.e. k_ESteamNetworkingConfig_FakePacketLag_Send => 1000 ms
989    #[inline]
990    pub fn set_global_config_value<'a>(
991        &self,
992        typ: ESteamNetworkingConfigValue,
993        value: GnsConfig<'a>,
994    ) -> Result<(), ()> {
995        let result = match value {
996            GnsConfig::Float(x) => unsafe {
997                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat(get_utils(), typ, x)
998            },
999            GnsConfig::Int32(x) => unsafe {
1000                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32(get_utils(), typ, x as i32)
1001            },
1002            GnsConfig::String(x) => unsafe {
1003                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
1004                    get_utils(),
1005                    typ,
1006                    CString::new(x).expect("str; qed;").as_c_str().as_ptr(),
1007                )
1008            },
1009            GnsConfig::Ptr(x) => unsafe {
1010                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr(get_utils(), typ, x)
1011            },
1012        };
1013        if result {
1014            Ok(())
1015        } else {
1016            Err(())
1017        }
1018    }
1019
1020    /// Set a per-connection configuration value, e.g. k_ESteamNetworkingConfig_SendRateMin/Max on an individual accepted connection
1021    #[inline]
1022    pub fn set_connection_config_value<'a>(
1023        &self,
1024        conn: GnsConnection,
1025        typ: ESteamNetworkingConfigValue,
1026        value: GnsConfig<'a>,
1027    ) -> Result<(), ()> {
1028        let result = match value {
1029            GnsConfig::Float(x) => unsafe {
1030                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat(
1031                    get_utils(), conn.0, typ, x,
1032                )
1033            },
1034            GnsConfig::Int32(x) => unsafe {
1035                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32(
1036                    get_utils(), conn.0, typ, x as i32,
1037                )
1038            },
1039            GnsConfig::String(x) => unsafe {
1040                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(
1041                    get_utils(),
1042                    conn.0,
1043                    typ,
1044                    CString::new(x).expect("str; qed;").as_c_str().as_ptr(),
1045                )
1046            },
1047            GnsConfig::Ptr(_) => return Err(()),
1048        };
1049        if result {
1050            Ok(())
1051        } else {
1052            Err(())
1053        }
1054    }
1055}