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
584#[derive(Default, Copy, Clone)]
585pub struct GnsConnectionEvent(SteamNetConnectionStatusChangedCallback_t);
586
587impl GnsConnectionEvent {
588    #[inline]
589    pub fn old_state(&self) -> ESteamNetworkingConnectionState {
590        self.0.m_eOldState
591    }
592
593    #[inline]
594    pub fn connection(&self) -> GnsConnection {
595        GnsConnection(self.0.m_hConn)
596    }
597
598    #[inline]
599    pub fn info(&self) -> GnsConnectionInfo {
600        GnsConnectionInfo(self.0.m_info)
601    }
602}
603
604/// [`GnsSocket`] is the most important structure of this library.
605/// This structure is used to create client ([`GnsSocket<IsClient>`]) and server ([`GnsSocket<IsServer>`]) sockets via the [`GnsSocket::connect`] and [`GnsSocket::listen`] functions.
606/// 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**.
607pub struct GnsSocket<S> {
608    global: Arc<GnsGlobal>,
609    state: S,
610}
611
612impl<S> GnsSocket<S>
613where
614    S: IsReady,
615{
616    /// Get a connection lane status.
617    /// This call is possible only if lanes has been previously configured using configure_connection_lanes
618    #[inline]
619    pub fn get_connection_real_time_status(
620        &self,
621        GnsConnection(conn): GnsConnection,
622        nb_of_lanes: u32,
623    ) -> GnsResult<(
624        GnsConnectionRealTimeStatus,
625        Vec<GnsConnectionRealTimeLaneStatus>,
626    )> {
627        let mut lanes: Vec<GnsConnectionRealTimeLaneStatus> =
628            vec![Default::default(); nb_of_lanes as _];
629        let mut status: GnsConnectionRealTimeStatus = Default::default();
630        GnsError(unsafe {
631            SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus(
632                get_interface(),
633                conn,
634                &mut status as *mut GnsConnectionRealTimeStatus
635                    as *mut SteamNetConnectionRealTimeStatus_t,
636                nb_of_lanes as _,
637                lanes.as_mut_ptr() as *mut GnsConnectionRealTimeLaneStatus
638                    as *mut SteamNetConnectionRealTimeLaneStatus_t,
639            )
640        })
641        .into_result()?;
642        Ok((status, lanes))
643    }
644
645    #[inline]
646    pub fn get_connection_info(
647        &self,
648        GnsConnection(conn): GnsConnection,
649    ) -> Option<GnsConnectionInfo> {
650        let mut info: SteamNetConnectionInfo_t = Default::default();
651        if unsafe {
652            SteamAPI_ISteamNetworkingSockets_GetConnectionInfo(get_interface(), conn, &mut info)
653        } {
654            Some(GnsConnectionInfo(info))
655        } else {
656            None
657        }
658    }
659
660    #[inline]
661    pub fn flush_messages_on_connection(
662        &self,
663        GnsConnection(conn): GnsConnection,
664    ) -> GnsResult<()> {
665        GnsError(unsafe {
666            SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection(get_interface(), conn)
667        })
668        .into_result()
669    }
670
671    #[inline]
672    pub fn close_connection(
673        &self,
674        GnsConnection(conn): GnsConnection,
675        reason: u32,
676        debug: &str,
677        linger: bool,
678    ) -> bool {
679        let debug_c = CString::new(debug).expect("str; qed;");
680        unsafe {
681            SteamAPI_ISteamNetworkingSockets_CloseConnection(
682                get_interface(),
683                conn,
684                reason as _,
685                debug_c.as_ptr(),
686                linger,
687            )
688        }
689    }
690
691    #[inline]
692    pub fn poll_messages<const K: usize>(
693        &self,
694        mut message_callback: impl FnMut(&GnsNetworkMessage<ToReceive>),
695    ) -> Option<usize> {
696        // Do not implements default for networking messages as they must be allocated by the lib.
697        let mut messages: [GnsNetworkMessage<ToReceive>; K] =
698            unsafe { MaybeUninit::zeroed().assume_init() };
699        let nb_of_messages = self.state.receive(&mut messages);
700        if nb_of_messages == usize::MAX {
701            None
702        } else {
703            for message in messages.into_iter().take(nb_of_messages) {
704                message_callback(&message);
705            }
706            Some(nb_of_messages)
707        }
708    }
709
710    #[inline]
711    pub fn poll_event<const K: usize>(
712        &self,
713        mut event_callback: impl FnMut(GnsConnectionEvent),
714    ) -> usize {
715        let mut processed = 0;
716        'a: while let Some(event) = self.state.queue().pop() {
717            event_callback(event);
718            processed += 1;
719            if processed == K {
720                break 'a;
721            }
722        }
723        processed
724    }
725
726    #[inline]
727    pub fn configure_connection_lanes(
728        &self,
729        GnsConnection(connection): GnsConnection,
730        lanes: &[GnsLane],
731    ) -> GnsResult<()> {
732        let (priorities, weights): (Vec<_>, Vec<_>) = lanes.iter().copied().unzip();
733        GnsError(unsafe {
734            SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes(
735                get_interface(),
736                connection,
737                lanes.len() as _,
738                priorities.as_ptr() as *const u32 as *const i32,
739                weights.as_ptr(),
740            )
741        })
742        .into_result()
743    }
744
745    #[inline]
746    pub fn send_messages(
747        &self,
748        messages: Vec<GnsNetworkMessage<ToSend>>,
749    ) -> Vec<Either<GnsMessageNumber, EResult>> {
750        let mut result = vec![0i64; messages.len()];
751        unsafe {
752            SteamAPI_ISteamNetworkingSockets_SendMessages(
753                get_interface(),
754                messages.len() as _,
755                messages.as_ptr() as *const _,
756                result.as_mut_ptr(),
757            );
758        }
759        result
760            .into_iter()
761            .map(|value| {
762                if value < 0 {
763                    Either::Right(unsafe { core::mem::transmute((-value) as u32) })
764                } else {
765                    Either::Left(value as _)
766                }
767            })
768            .collect()
769    }
770}
771
772impl GnsSocket<IsCreated> {
773    /// Unsafe, C-like callback, we use the user data to pass the queue ID, so we can find the
774    /// correct queue in GnsGlobal.
775    unsafe extern "C" fn on_connection_state_changed(
776        info: &mut SteamNetConnectionStatusChangedCallback_t,
777    ) {
778        let gns_global = GnsGlobal::get()
779            // GnsGlobal needs to be initialized to even reach this point in the first place.
780            .expect("GnsGlobal should be initialized");
781
782        let queue_id = info.m_info.m_nUserData as _;
783        let mut queues = gns_global.event_queues.lock().unwrap();
784        if let Some(queue) = queues.get(&queue_id) {
785            if let Some(queue) = queue.upgrade() {
786                queue.push(GnsConnectionEvent(*info));
787            } else {
788                // The queue is no longer valid as the associated GnsSocket has been dropped
789                queues.remove(&queue_id);
790            }
791        }
792    }
793
794    /// Initialize a new socket in [`IsCreated`] state.
795    #[inline]
796    pub fn new(global: Arc<GnsGlobal>) -> Self {
797        GnsSocket {
798            global,
799            state: IsCreated,
800        }
801    }
802
803    #[inline]
804    fn setup_common(
805        address: IpAddr,
806        port: u16,
807        queue_id: int64,
808    ) -> (SteamNetworkingIPAddr, [SteamNetworkingConfigValue_t; 2]) {
809        let addr = SteamNetworkingIPAddr {
810            __bindgen_anon_1: match address {
811                IpAddr::V4(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
812                    m_ipv4: SteamNetworkingIPAddr_IPv4MappedAddress {
813                        m_8zeros: 0,
814                        m_0000: 0,
815                        m_ffff: 0xffff,
816                        m_ip: address.octets(),
817                    },
818                },
819                IpAddr::V6(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
820                    m_ipv6: address.octets(),
821                },
822            },
823            m_port: port,
824        };
825        let options = [SteamNetworkingConfigValue_t {
826            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr,
827            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
828            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
829              m_ptr: Self::on_connection_state_changed as *const fn(&SteamNetConnectionStatusChangedCallback_t) as *mut c_void
830            }
831          }, SteamNetworkingConfigValue_t {
832            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int64,
833            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_ConnectionUserData,
834            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
835              m_int64: queue_id
836            }
837        }];
838        (addr, options)
839    }
840
841    /// Listen for incoming connections, the socket transition from [`IsCreated`] to [`IsServer`], allowing a new set of server operations.
842    #[inline]
843    pub fn listen(self, address: IpAddr, port: u16) -> Result<GnsSocket<IsServer>, ()> {
844        let (queue_id, queue) = self.global.create_queue();
845        let (addr, options) = Self::setup_common(address, port, queue_id);
846        let listen_socket = unsafe {
847            SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
848                get_interface(),
849                &addr,
850                options.len() as _,
851                options.as_ptr(),
852            )
853        };
854        if listen_socket == k_HSteamListenSocket_Invalid {
855            Err(())
856        } else {
857            let poll_group =
858                unsafe { SteamAPI_ISteamNetworkingSockets_CreatePollGroup(get_interface()) };
859            if poll_group == k_HSteamNetPollGroup_Invalid {
860                Err(())
861            } else {
862                Ok(GnsSocket {
863                    global: self.global,
864                    state: IsServer {
865                        queue,
866                        listen_socket: GnsListenSocket(listen_socket),
867                        poll_group: GnsPollGroup(poll_group),
868                    },
869                })
870            }
871        }
872    }
873
874    /// Connect to a remote host, the socket transition from [`IsCreated`] to [`IsClient`], allowing a new set of client operations.
875    #[inline]
876    pub fn connect(self, address: IpAddr, port: u16) -> Result<GnsSocket<IsClient>, ()> {
877        let (queue_id, queue) = self.global.create_queue();
878        let (addr, options) = Self::setup_common(address, port, queue_id);
879        let connection = unsafe {
880            SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(
881                get_interface(),
882                &addr,
883                options.len() as _,
884                options.as_ptr(),
885            )
886        };
887        if connection == k_HSteamNetConnection_Invalid {
888            Err(())
889        } else {
890            Ok(GnsSocket {
891                global: self.global,
892                state: IsClient {
893                    queue,
894                    connection: GnsConnection(connection),
895                },
896            })
897        }
898    }
899}
900
901impl GnsSocket<IsServer> {
902    /// Accept an incoming connection. This operation is available only if the socket is in the [`IsServer`] state.
903    #[inline]
904    pub fn accept(&self, connection: GnsConnection) -> GnsResult<()> {
905        GnsError(unsafe {
906            SteamAPI_ISteamNetworkingSockets_AcceptConnection(get_interface(), connection.0)
907        })
908        .into_result()?;
909        if !unsafe {
910            SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
911                get_interface(),
912                connection.0,
913                self.state.poll_group.0,
914            )
915        } {
916            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.");
917        }
918        Ok(())
919    }
920}
921
922impl GnsSocket<IsClient> {
923    /// Return the socket connection. This operation is available only if the socket is in the [`IsClient`] state.
924    #[inline]
925    pub fn connection(&self) -> GnsConnection {
926        self.state.connection
927    }
928}
929
930/// The configuration value used to define configure global variables in [`GnsUtils::set_global_config_value`]
931pub enum GnsConfig<'a> {
932    Float(f32),
933    Int32(u32),
934    String(&'a str),
935    Ptr(*mut c_void),
936}
937
938pub struct GnsUtils(());
939
940type MsgPtr = *const ::std::os::raw::c_char;
941
942impl GnsUtils {
943    #[inline]
944    pub fn enable_debug_output(
945        &self,
946        ty: ESteamNetworkingSocketsDebugOutputType,
947        f: fn(ty: ESteamNetworkingSocketsDebugOutputType, msg: String),
948    ) {
949        static mut F: Option<fn(ty: ESteamNetworkingSocketsDebugOutputType, msg: String)> = None;
950        unsafe {
951            F = Some(f);
952        }
953        unsafe extern "C" fn debug(ty: ESteamNetworkingSocketsDebugOutputType, msg: MsgPtr) {
954            F.unwrap()(ty, CStr::from_ptr(msg).to_string_lossy().to_string());
955        }
956        unsafe {
957            SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(get_utils(), ty, Some(debug));
958        }
959    }
960
961    /// Allocate a new message to be sent.
962    /// This message must be sent if allocated, as the message can only be freed by the `GnsSocket::send_messages` call.
963    #[inline]
964    pub fn allocate_message(
965        &self,
966        conn: GnsConnection,
967        flags: i32,
968        payload: &[u8],
969    ) -> GnsNetworkMessage<ToSend> {
970        let message_ptr = unsafe {
971            SteamAPI_ISteamNetworkingUtils_AllocateMessage(get_utils(), payload.len() as _)
972        };
973        GnsNetworkMessage::new(message_ptr, conn, flags, payload)
974    }
975
976    /// Set a global configuration value, i.e. k_ESteamNetworkingConfig_FakePacketLag_Send => 1000 ms
977    #[inline]
978    pub fn set_global_config_value<'a>(
979        &self,
980        typ: ESteamNetworkingConfigValue,
981        value: GnsConfig<'a>,
982    ) -> Result<(), ()> {
983        let result = match value {
984            GnsConfig::Float(x) => unsafe {
985                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat(get_utils(), typ, x)
986            },
987            GnsConfig::Int32(x) => unsafe {
988                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32(get_utils(), typ, x as i32)
989            },
990            GnsConfig::String(x) => unsafe {
991                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
992                    get_utils(),
993                    typ,
994                    CString::new(x).expect("str; qed;").as_c_str().as_ptr(),
995                )
996            },
997            GnsConfig::Ptr(x) => unsafe {
998                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr(get_utils(), typ, x)
999            },
1000        };
1001        if result {
1002            Ok(())
1003        } else {
1004            Err(())
1005        }
1006    }
1007}