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