use crossbeam_queue::SegQueue;
pub use gns_sys as sys;
use std::sync::atomic::{AtomicI64, Ordering};
use std::{
collections::HashMap,
ffi::{c_void, CStr, CString},
marker::PhantomData,
mem::MaybeUninit,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::{Arc, Mutex, OnceLock, RwLock, Weak},
time::Duration,
};
use sys::*;
#[inline]
fn get_interface() -> *mut ISteamNetworkingSockets {
unsafe { SteamAPI_SteamNetworkingSockets_v009() }
}
#[inline]
fn get_utils() -> *mut ISteamNetworkingUtils {
unsafe { SteamAPI_SteamNetworkingUtils_v003() }
}
pub type GnsMessageNumber = u64;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum GnsError {
#[error("GameNetworkingSockets_Init failed: {0}")]
Init(String),
#[error("listen failed: invalid handle")]
Listen,
#[error("connect failed: invalid handle")]
Connect,
#[error("create socket pair failed")]
SocketPair,
#[error("receive failed: invalid connection or poll group handle")]
Receive,
#[error("accept failed: could not set connection poll group")]
Accept,
#[error("close failed: invalid connection handle")]
Close,
#[error("steam api: {0:?}")]
Api(EResult),
#[error("config: {0}")]
Config(&'static str),
}
pub type GnsResult<T> = Result<T, GnsError>;
#[inline]
fn ip_from_steam_ip_addr(addr: &SteamNetworkingIPAddr) -> IpAddr {
let ipv4 = unsafe { addr.__bindgen_anon_1.m_ipv4 };
if ipv4.m_8zeros == 0 && ipv4.m_0000 == 0 && ipv4.m_ffff == 0xffff {
IpAddr::from(Ipv4Addr::from(ipv4.m_ip))
} else {
IpAddr::from(Ipv6Addr::from(unsafe { addr.__bindgen_anon_1.m_ipv6 }))
}
}
#[inline]
fn check(e: EResult) -> GnsResult<()> {
match e {
EResult::k_EResultOK => Ok(()),
e => Err(GnsError::Api(e)),
}
}
pub struct GnsGlobal {
utils: GnsUtils,
next_queue_id: AtomicI64,
event_queues: RwLock<HashMap<i64, Weak<SegQueue<GnsConnectionEvent>>>>,
}
static GNS_GLOBAL: OnceLock<GnsGlobal> = OnceLock::new();
impl Drop for GnsGlobal {
#[inline]
fn drop(&mut self) {
unsafe { GameNetworkingSockets_Kill() }
}
}
impl GnsGlobal {
pub fn get() -> GnsResult<&'static Self> {
if let Some(g) = GNS_GLOBAL.get() {
return Ok(g);
}
static INIT_LOCK: Mutex<()> = Mutex::new(());
let _guard = INIT_LOCK.lock().unwrap();
if let Some(g) = GNS_GLOBAL.get() {
return Ok(g);
}
unsafe {
let mut error: SteamDatagramErrMsg = MaybeUninit::zeroed().assume_init();
if !GameNetworkingSockets_Init(core::ptr::null(), &mut error) {
return Err(GnsError::Init(
CStr::from_ptr(error.as_ptr())
.to_str()
.unwrap_or("")
.to_owned(),
));
}
}
let _ = GNS_GLOBAL.set(GnsGlobal {
utils: GnsUtils(()),
next_queue_id: AtomicI64::new(0),
event_queues: RwLock::new(HashMap::new()),
});
Ok(GNS_GLOBAL.get().expect("impossible; qed;"))
}
#[inline]
pub fn poll_callbacks(&self) {
unsafe {
SteamAPI_ISteamNetworkingSockets_RunCallbacks(get_interface());
}
}
#[inline]
pub fn utils(&self) -> &GnsUtils {
&self.utils
}
#[inline]
pub fn queue_count(&self) -> usize {
self.event_queues.read().unwrap().len()
}
#[inline]
fn create_queue(&self) -> (i64, Arc<SegQueue<GnsConnectionEvent>>) {
let queue = Arc::new(SegQueue::new());
let queue_id = self.next_queue_id.fetch_add(1, Ordering::SeqCst);
self.event_queues
.write()
.unwrap()
.insert(queue_id, Arc::downgrade(&queue));
(queue_id, queue)
}
}
#[repr(transparent)]
pub(crate) struct GnsListenSocket(HSteamListenSocket);
#[repr(transparent)]
pub(crate) struct GnsPollGroup(HSteamNetPollGroup);
pub struct IsCreated;
mod private {
pub trait Sealed {}
impl Sealed for super::IsServer {}
impl Sealed for super::IsClient {}
}
pub trait IsReady: private::Sealed {
fn queue(&self) -> &SegQueue<GnsConnectionEvent>;
fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize>;
}
pub struct IsServer {
queue: Arc<SegQueue<GnsConnectionEvent>>,
queue_id: i64,
global: &'static GnsGlobal,
listen_socket: GnsListenSocket,
poll_group: GnsPollGroup,
}
impl Drop for IsServer {
#[inline]
fn drop(&mut self) {
unsafe {
SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
get_interface(),
self.listen_socket.0,
);
SteamAPI_ISteamNetworkingSockets_DestroyPollGroup(get_interface(), self.poll_group.0);
}
self.global
.event_queues
.write()
.unwrap()
.remove(&self.queue_id);
}
}
impl IsReady for IsServer {
#[inline]
fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
&self.queue
}
fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize> {
let result = unsafe {
SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
get_interface(),
self.poll_group.0,
slots.as_mut_ptr() as _,
slots.len() as _,
) as _
};
if result == usize::MAX {
Err(GnsError::Receive)
} else {
Ok(result)
}
}
}
pub struct IsClient {
queue: Arc<SegQueue<GnsConnectionEvent>>,
queue_id: i64,
global: &'static GnsGlobal,
connection: GnsConnection,
}
impl Drop for IsClient {
fn drop(&mut self) {
unsafe {
SteamAPI_ISteamNetworkingSockets_CloseConnection(
get_interface(),
self.connection.0,
0,
core::ptr::null(),
false,
);
}
self.global
.event_queues
.write()
.unwrap()
.remove(&self.queue_id);
}
}
impl IsReady for IsClient {
#[inline]
fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
&self.queue
}
fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize> {
let result = unsafe {
SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(
get_interface(),
self.connection.0,
slots.as_mut_ptr() as _,
slots.len() as _,
) as _
};
if result == usize::MAX {
Err(GnsError::Receive)
} else {
Ok(result)
}
}
}
pub struct ToReceive(());
pub struct ToSend(());
pub type MessageSlot = MaybeUninit<*mut ISteamNetworkingMessage>;
#[inline]
unsafe fn take_message(slot: &MessageSlot) -> GnsNetworkMessage<ToReceive> {
GnsNetworkMessage(unsafe { slot.assume_init() }, PhantomData)
}
struct SlotCursor {
len: usize,
pos: usize,
}
impl SlotCursor {
fn next(&mut self, slots: &[MessageSlot]) -> Option<GnsNetworkMessage<ToReceive>> {
if self.pos < self.len {
let message = unsafe { take_message(&slots[self.pos]) };
self.pos += 1;
Some(message)
} else {
None
}
}
#[inline]
fn remaining(&self) -> usize {
self.len - self.pos
}
fn drain_unconsumed(&mut self, slots: &[MessageSlot]) {
for slot in &slots[self.pos..self.len] {
drop(unsafe { take_message(slot) });
}
self.pos = self.len;
}
}
pub struct ReceivedMessages<const K: usize> {
slots: [MessageSlot; K],
cursor: SlotCursor,
}
impl<const K: usize> Iterator for ReceivedMessages<K> {
type Item = GnsNetworkMessage<ToReceive>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.cursor.next(&self.slots)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.cursor.remaining();
(remaining, Some(remaining))
}
}
impl<const K: usize> ExactSizeIterator for ReceivedMessages<K> {}
impl<const K: usize> core::iter::FusedIterator for ReceivedMessages<K> {}
impl<const K: usize> Drop for ReceivedMessages<K> {
#[inline]
fn drop(&mut self) {
self.cursor.drain_unconsumed(&self.slots);
}
}
pub struct ReceivedMessagesInto<'a> {
slots: &'a mut [MessageSlot],
cursor: SlotCursor,
}
impl Iterator for ReceivedMessagesInto<'_> {
type Item = GnsNetworkMessage<ToReceive>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.cursor.next(self.slots)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.cursor.remaining();
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for ReceivedMessagesInto<'_> {}
impl core::iter::FusedIterator for ReceivedMessagesInto<'_> {}
impl Drop for ReceivedMessagesInto<'_> {
#[inline]
fn drop(&mut self) {
self.cursor.drain_unconsumed(self.slots);
}
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SendFlags: i32 {
const UNRELIABLE = sys::k_nSteamNetworkingSend_Unreliable;
const NO_NAGLE = sys::k_nSteamNetworkingSend_NoNagle;
const NO_DELAY = sys::k_nSteamNetworkingSend_NoDelay;
const RELIABLE = sys::k_nSteamNetworkingSend_Reliable;
const USE_CURRENT_THREAD = sys::k_nSteamNetworkingSend_UseCurrentThread;
const AUTO_RESTART_BROKEN_SESSION = sys::k_nSteamNetworkingSend_AutoRestartBrokenSession;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct GnsLane {
pub priority: i32,
pub weight: u16,
}
impl GnsLane {
#[inline]
pub const fn new(priority: i32, weight: u16) -> Self {
Self { priority, weight }
}
}
pub type GnsLaneId = u16;
#[must_use = "Failed/Skipped variants own a message that needs inspection or drop"]
pub enum SendOutcome {
Sent(GnsMessageNumber),
Failed(EResult, GnsNetworkMessage<ToSend>),
Skipped(GnsNetworkMessage<ToSend>),
}
pub unsafe trait Payload: Send + 'static {
fn into_raw(self) -> (*mut u8, usize);
unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self;
}
extern "C" fn free_payload<P: Payload>(msg: *mut ISteamNetworkingMessage) {
let ptr = unsafe { (*msg).m_pData } as *mut u8;
let len = unsafe { (*msg).m_cbSize } as usize;
drop(unsafe { P::from_raw(ptr, len) });
}
unsafe impl Payload for Box<[u8]> {
#[inline]
fn into_raw(self) -> (*mut u8, usize) {
let len = self.len();
let raw = Box::into_raw(self) as *mut u8;
(raw, len)
}
#[inline]
unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
let slice = core::ptr::slice_from_raw_parts_mut(ptr, len);
unsafe { Box::from_raw(slice) }
}
}
unsafe impl Payload for Vec<u8> {
#[inline]
fn into_raw(self) -> (*mut u8, usize) {
<Box<[u8]> as Payload>::into_raw(self.into_boxed_slice())
}
#[inline]
unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
unsafe { Vec::from_raw_parts(ptr, len, len) }
}
}
unsafe impl Payload for String {
#[inline]
fn into_raw(self) -> (*mut u8, usize) {
<Vec<u8> as Payload>::into_raw(self.into_bytes())
}
#[inline]
unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
unsafe { String::from_raw_parts(ptr, len, len) }
}
}
unsafe impl Payload for Arc<[u8]> {
#[inline]
fn into_raw(self) -> (*mut u8, usize) {
let len = self.len();
let raw = Arc::into_raw(self) as *const u8 as *mut u8;
(raw, len)
}
#[inline]
unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
let slice = core::ptr::slice_from_raw_parts(ptr as *const u8, len);
unsafe { Arc::from_raw(slice) }
}
}
unsafe impl Payload for &'static [u8] {
#[inline]
fn into_raw(self) -> (*mut u8, usize) {
(self.as_ptr() as *mut u8, self.len())
}
#[inline]
unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
unsafe { core::slice::from_raw_parts(ptr as *const u8, len) }
}
}
unsafe impl Payload for &'static str {
#[inline]
fn into_raw(self) -> (*mut u8, usize) {
(self.as_ptr() as *mut u8, self.len())
}
#[inline]
unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
let bytes = unsafe { core::slice::from_raw_parts(ptr as *const u8, len) };
unsafe { core::str::from_utf8_unchecked(bytes) }
}
}
#[repr(transparent)]
pub struct GnsNetworkMessage<T>(*mut ISteamNetworkingMessage, PhantomData<T>);
impl<T> Drop for GnsNetworkMessage<T> {
#[inline]
fn drop(&mut self) {
if !self.0.is_null() {
unsafe {
SteamAPI_SteamNetworkingMessage_t_Release(self.0);
}
}
}
}
impl<T> GnsNetworkMessage<T> {
#[inline]
pub unsafe fn into_inner(self) -> *mut ISteamNetworkingMessage {
core::mem::ManuallyDrop::new(self).0
}
#[inline]
pub fn payload(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts((*self.0).m_pData as *const u8, (*self.0).m_cbSize as _)
}
}
#[inline]
pub fn message_number(&self) -> u64 {
unsafe { (*self.0).m_nMessageNumber as _ }
}
#[inline]
pub fn time_received(&self) -> SteamNetworkingMicroseconds {
unsafe { (*self.0).m_usecTimeReceived }
}
#[inline]
pub fn lane(&self) -> GnsLaneId {
unsafe { (*self.0).m_idxLane }
}
#[inline]
pub fn flags(&self) -> SendFlags {
SendFlags::from_bits_retain(unsafe { (*self.0).m_nFlags })
}
#[inline]
pub fn user_data(&self) -> u64 {
unsafe { (*self.0).m_nUserData as _ }
}
#[inline]
pub fn connection(&self) -> GnsConnection {
GnsConnection(unsafe { (*self.0).m_conn })
}
#[inline]
pub fn connection_user_data(&self) -> u64 {
unsafe { (*self.0).m_nConnUserData as _ }
}
}
impl GnsNetworkMessage<ToSend> {
#[inline]
fn new<P: Payload>(
ptr: *mut ISteamNetworkingMessage,
conn: GnsConnection,
flags: SendFlags,
payload: P,
) -> Self {
let (data_ptr, len) = payload.into_raw();
unsafe {
(*ptr).m_pData = data_ptr as *mut c_void;
(*ptr).m_cbSize = len as i32;
(*ptr).m_pfnFreeData = Some(free_payload::<P>);
}
GnsNetworkMessage(ptr, PhantomData)
.set_flags(flags)
.set_connection(conn)
}
#[inline]
pub fn set_connection(self, GnsConnection(conn): GnsConnection) -> Self {
unsafe { (*self.0).m_conn = conn }
self
}
#[inline]
pub fn set_lane(self, lane: GnsLaneId) -> Self {
unsafe { (*self.0).m_idxLane = lane }
self
}
#[inline]
pub fn set_flags(self, flags: SendFlags) -> Self {
unsafe { (*self.0).m_nFlags = flags.bits() as _ }
self
}
#[inline]
pub fn set_user_data(self, userdata: u64) -> Self {
unsafe { (*self.0).m_nUserData = userdata as _ }
self
}
}
#[repr(transparent)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GnsConnection(HSteamNetConnection);
impl GnsConnection {
#[inline]
pub const fn from_raw(handle: HSteamNetConnection) -> Self {
Self(handle)
}
#[inline]
pub fn is_valid(self) -> bool {
self.0 != k_HSteamNetConnection_Invalid
}
}
#[derive(Default, Copy, Clone)]
pub struct GnsConnectionInfo(SteamNetConnectionInfo_t);
impl GnsConnectionInfo {
#[inline]
pub fn state(&self) -> ESteamNetworkingConnectionState {
self.0.m_eState
}
#[inline]
pub fn end_reason(&self) -> u32 {
self.0.m_eEndReason as u32
}
#[inline]
pub fn end_debug(&self) -> &str {
unsafe { CStr::from_ptr(self.0.m_szEndDebug.as_ptr()) }
.to_str()
.unwrap_or("")
}
#[inline]
pub fn remote_address(&self) -> IpAddr {
ip_from_steam_ip_addr(&self.0.m_addrRemote)
}
#[inline]
pub fn remote_port(&self) -> u16 {
self.0.m_addrRemote.m_port
}
}
#[derive(Debug, Default, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct GnsConnectionRealTimeLaneStatus(SteamNetConnectionRealTimeLaneStatus_t);
impl GnsConnectionRealTimeLaneStatus {
#[inline]
pub fn pending_bytes_unreliable(&self) -> u32 {
self.0.m_cbPendingUnreliable as _
}
#[inline]
pub fn pending_bytes_reliable(&self) -> u32 {
self.0.m_cbPendingReliable as _
}
#[inline]
pub fn bytes_sent_unacked_reliable(&self) -> u32 {
self.0.m_cbSentUnackedReliable as _
}
#[inline]
pub fn approximated_queue_time(&self) -> Duration {
Duration::from_micros(self.0.m_usecQueueTime as _)
}
}
#[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq)]
pub struct GnsConnectionRealTimeStatus(SteamNetConnectionRealTimeStatus_t);
impl GnsConnectionRealTimeStatus {
#[inline]
pub fn state(&self) -> ESteamNetworkingConnectionState {
self.0.m_eState
}
#[inline]
pub fn ping(&self) -> u32 {
self.0.m_nPing as _
}
#[inline]
pub fn quality_local(&self) -> f32 {
self.0.m_flConnectionQualityLocal
}
#[inline]
pub fn quality_remote(&self) -> f32 {
self.0.m_flConnectionQualityRemote
}
#[inline]
pub fn out_packets_per_sec(&self) -> f32 {
self.0.m_flOutPacketsPerSec
}
#[inline]
pub fn out_bytes_per_sec(&self) -> f32 {
self.0.m_flOutBytesPerSec
}
#[inline]
pub fn in_packets_per_sec(&self) -> f32 {
self.0.m_flInPacketsPerSec
}
#[inline]
pub fn in_bytes_per_sec(&self) -> f32 {
self.0.m_flInBytesPerSec
}
#[inline]
pub fn send_rate_bytes_per_sec(&self) -> u32 {
self.0.m_nSendRateBytesPerSecond as _
}
#[inline]
pub fn pending_bytes_unreliable(&self) -> u32 {
self.0.m_cbPendingUnreliable as _
}
#[inline]
pub fn pending_bytes_reliable(&self) -> u32 {
self.0.m_cbPendingReliable as _
}
#[inline]
pub fn bytes_sent_unacked_reliable(&self) -> u32 {
self.0.m_cbSentUnackedReliable as _
}
#[inline]
pub fn approximated_queue_time(&self) -> Duration {
Duration::from_micros(self.0.m_usecQueueTime as _)
}
#[inline]
pub fn max_jitter_usec(&self) -> Option<i32> {
let val = self.0.m_usecMaxJitter;
if val < 0 {
None
} else {
Some(val)
}
}
}
#[derive(Default, Copy, Clone)]
pub struct GnsConnectionEvent(SteamNetConnectionStatusChangedCallback_t);
impl GnsConnectionEvent {
#[inline]
pub fn old_state(&self) -> ESteamNetworkingConnectionState {
self.0.m_eOldState
}
#[inline]
pub fn connection(&self) -> GnsConnection {
GnsConnection(self.0.m_hConn)
}
#[inline]
pub fn info(&self) -> GnsConnectionInfo {
GnsConnectionInfo(self.0.m_info)
}
}
pub struct GnsSocket<S> {
global: &'static GnsGlobal,
state: S,
}
impl<S> GnsSocket<S>
where
S: IsReady,
{
pub fn get_connection_real_time_status(
&self,
GnsConnection(conn): GnsConnection,
nb_of_lanes: u32,
) -> GnsResult<(
GnsConnectionRealTimeStatus,
Vec<GnsConnectionRealTimeLaneStatus>,
)> {
let mut lanes: Vec<GnsConnectionRealTimeLaneStatus> =
vec![Default::default(); nb_of_lanes as _];
let mut status: GnsConnectionRealTimeStatus = Default::default();
check(unsafe {
SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus(
get_interface(),
conn,
&mut status as *mut GnsConnectionRealTimeStatus
as *mut SteamNetConnectionRealTimeStatus_t,
nb_of_lanes as _,
lanes.as_mut_ptr() as *mut SteamNetConnectionRealTimeLaneStatus_t,
)
})?;
Ok((status, lanes))
}
pub fn get_connection_info(
&self,
GnsConnection(conn): GnsConnection,
) -> Option<GnsConnectionInfo> {
let mut info: SteamNetConnectionInfo_t = Default::default();
if unsafe {
SteamAPI_ISteamNetworkingSockets_GetConnectionInfo(get_interface(), conn, &mut info)
} {
Some(GnsConnectionInfo(info))
} else {
None
}
}
pub fn get_detailed_connection_status(
&self,
GnsConnection(conn): GnsConnection,
) -> Option<String> {
let mut buf = vec![0u8; 2048];
loop {
let result = unsafe {
SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus(
get_interface(),
conn,
buf.as_mut_ptr() as *mut std::ffi::c_char,
buf.len() as _,
)
};
if result < 0 {
return None;
}
if result == 0 {
let text = CStr::from_bytes_until_nul(&buf).ok()?;
return Some(text.to_string_lossy().into_owned());
}
buf.resize(result as usize, 0);
}
}
pub fn get_connection_name(&self, GnsConnection(conn): GnsConnection) -> Option<String> {
let mut buf = [0u8; 256];
if unsafe {
SteamAPI_ISteamNetworkingSockets_GetConnectionName(
get_interface(),
conn,
buf.as_mut_ptr() as *mut std::ffi::c_char,
buf.len() as _,
)
} {
let name = CStr::from_bytes_until_nul(&buf).ok()?;
Some(name.to_string_lossy().into_owned())
} else {
None
}
}
pub fn set_connection_name(
&self,
GnsConnection(conn): GnsConnection,
name: &str,
) -> GnsResult<()> {
let c = CString::new(name).map_err(|_| GnsError::Config("interior NUL"))?;
unsafe {
SteamAPI_ISteamNetworkingSockets_SetConnectionName(get_interface(), conn, c.as_ptr());
}
Ok(())
}
pub fn flush_messages_on_connection(
&self,
GnsConnection(conn): GnsConnection,
) -> GnsResult<()> {
check(unsafe {
SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection(get_interface(), conn)
})
}
pub fn close_connection(
&self,
GnsConnection(conn): GnsConnection,
reason: u32,
debug: Option<&CStr>,
linger: bool,
) -> GnsResult<()> {
let debug_ptr = debug.map(|d| d.as_ptr()).unwrap_or(core::ptr::null());
if unsafe {
SteamAPI_ISteamNetworkingSockets_CloseConnection(
get_interface(),
conn,
reason as _,
debug_ptr,
linger,
)
} {
Ok(())
} else {
Err(GnsError::Close)
}
}
pub fn receive_messages<const K: usize>(&self) -> GnsResult<ReceivedMessages<K>> {
let mut slots: [MessageSlot; K] = [const { MessageSlot::uninit() }; K];
let len = self.state.receive(&mut slots)?;
Ok(ReceivedMessages {
slots,
cursor: SlotCursor { len, pos: 0 },
})
}
pub fn receive_messages_into<'a>(
&self,
buffer: &'a mut [MessageSlot],
) -> GnsResult<ReceivedMessagesInto<'a>> {
let len = self.state.receive(buffer)?;
Ok(ReceivedMessagesInto {
slots: buffer,
cursor: SlotCursor { len, pos: 0 },
})
}
pub fn receive_events(&self) -> impl Iterator<Item = GnsConnectionEvent> + '_ {
core::iter::from_fn(|| self.state.queue().pop())
}
pub fn configure_connection_lanes(
&self,
GnsConnection(connection): GnsConnection,
lanes: &[GnsLane],
) -> GnsResult<()> {
let (priorities, weights): (Vec<i32>, Vec<u16>) =
lanes.iter().map(|l| (l.priority, l.weight)).unzip();
check(unsafe {
SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes(
get_interface(),
connection,
lanes.len() as _,
priorities.as_ptr(),
weights.as_ptr(),
)
})
}
pub fn send_message(&self, message: GnsNetworkMessage<ToSend>) -> GnsResult<GnsMessageNumber> {
match self.send_messages(core::iter::once(message)).pop() {
Some(SendOutcome::Sent(number)) => Ok(number),
Some(SendOutcome::Failed(result, _)) => Err(GnsError::Api(result)),
_ => Err(GnsError::Api(EResult::k_EResultFail)),
}
}
pub fn send_messages(
&self,
messages: impl IntoIterator<Item = GnsNetworkMessage<ToSend>>,
) -> Vec<SendOutcome> {
let mut raw: Vec<*mut ISteamNetworkingMessage> = messages
.into_iter()
.map(|message| {
let message = core::mem::ManuallyDrop::new(message);
message.0
})
.collect();
let mut result = vec![0i64; raw.len()];
unsafe {
SteamAPI_ISteamNetworkingSockets_SendMessages(
get_interface(),
raw.len() as _,
raw.as_mut_ptr(),
result.as_mut_ptr(),
false,
);
}
result
.into_iter()
.zip(raw)
.map(|(value, ptr)| {
if value > 0 {
SendOutcome::Sent(value as _)
} else if value < 0 {
let result = unsafe { core::mem::transmute::<u32, EResult>((-value) as u32) };
SendOutcome::Failed(result, GnsNetworkMessage(ptr, PhantomData))
} else {
SendOutcome::Skipped(GnsNetworkMessage(ptr, PhantomData))
}
})
.collect()
}
}
impl GnsSocket<IsCreated> {
unsafe extern "C" fn on_connection_state_changed(
info: &mut SteamNetConnectionStatusChangedCallback_t,
) {
let gns_global = GnsGlobal::get()
.expect("GnsGlobal should be initialized");
let queue_id = info.m_info.m_nUserData as _;
let needs_purge = {
let queues = gns_global.event_queues.read().unwrap();
match queues.get(&queue_id).and_then(Weak::upgrade) {
Some(queue) => {
queue.push(GnsConnectionEvent(*info));
false
}
None => queues.contains_key(&queue_id),
}
};
if needs_purge {
gns_global.event_queues.write().unwrap().remove(&queue_id);
}
}
#[inline]
pub fn new(global: &'static GnsGlobal) -> Self {
GnsSocket {
global,
state: IsCreated,
}
}
fn setup_common(
address: IpAddr,
port: u16,
queue_id: int64,
) -> (SteamNetworkingIPAddr, [SteamNetworkingConfigValue_t; 2]) {
let addr = SteamNetworkingIPAddr {
__bindgen_anon_1: match address {
IpAddr::V4(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
m_ipv4: SteamNetworkingIPAddr_IPv4MappedAddress {
m_8zeros: 0,
m_0000: 0,
m_ffff: 0xffff,
m_ip: address.octets(),
},
},
IpAddr::V6(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
m_ipv6: address.octets(),
},
},
m_port: port,
};
let options = [SteamNetworkingConfigValue_t {
m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr,
m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
m_ptr: Self::on_connection_state_changed as *const fn(&SteamNetConnectionStatusChangedCallback_t) as *mut c_void
}
}, SteamNetworkingConfigValue_t {
m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int64,
m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_ConnectionUserData,
m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
m_int64: queue_id
}
}];
(addr, options)
}
pub fn listen(self, address: IpAddr, port: u16) -> GnsResult<GnsSocket<IsServer>> {
let (queue_id, queue) = self.global.create_queue();
let (addr, options) = Self::setup_common(address, port, queue_id);
let listen_socket = unsafe {
SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
get_interface(),
&addr,
options.len() as _,
options.as_ptr(),
)
};
if listen_socket == k_HSteamListenSocket_Invalid {
Err(GnsError::Listen)
} else {
let poll_group =
unsafe { SteamAPI_ISteamNetworkingSockets_CreatePollGroup(get_interface()) };
if poll_group == k_HSteamNetPollGroup_Invalid {
Err(GnsError::Listen)
} else {
Ok(GnsSocket {
global: self.global,
state: IsServer {
queue,
queue_id,
global: self.global,
listen_socket: GnsListenSocket(listen_socket),
poll_group: GnsPollGroup(poll_group),
},
})
}
}
}
pub fn connect(self, address: IpAddr, port: u16) -> GnsResult<GnsSocket<IsClient>> {
let (queue_id, queue) = self.global.create_queue();
let (addr, options) = Self::setup_common(address, port, queue_id);
let connection = unsafe {
SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(
get_interface(),
&addr,
options.len() as _,
options.as_ptr(),
)
};
if connection == k_HSteamNetConnection_Invalid {
Err(GnsError::Connect)
} else {
Ok(GnsSocket {
global: self.global,
state: IsClient {
queue,
queue_id,
global: self.global,
connection: GnsConnection(connection),
},
})
}
}
pub fn socket_pair(
self,
use_network_loopback: bool,
) -> GnsResult<(GnsSocket<IsClient>, GnsSocket<IsClient>)> {
let (queue_id_a, queue_a) = self.global.create_queue();
let (queue_id_b, queue_b) = self.global.create_queue();
let mut conn_a = k_HSteamNetConnection_Invalid;
let mut conn_b = k_HSteamNetConnection_Invalid;
let ok = unsafe {
SteamAPI_ISteamNetworkingSockets_CreateSocketPair(
get_interface(),
&mut conn_a,
&mut conn_b,
use_network_loopback,
core::ptr::null(),
core::ptr::null(),
)
};
if !ok {
let mut queues = self.global.event_queues.write().unwrap();
queues.remove(&queue_id_a);
queues.remove(&queue_id_b);
return Err(GnsError::SocketPair);
}
let make_client = |connection, queue, queue_id| GnsSocket {
global: self.global,
state: IsClient {
queue,
queue_id,
global: self.global,
connection: GnsConnection(connection),
},
};
let a = make_client(conn_a, queue_a, queue_id_a);
let b = make_client(conn_b, queue_b, queue_id_b);
for (conn, queue_id) in [(conn_a, queue_id_a), (conn_b, queue_id_b)] {
unsafe {
SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
get_interface(),
conn,
queue_id,
);
}
self.global.utils().set_config_value_scoped(
ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Connection,
conn as isize,
GnsConfig::Ptr(Self::on_connection_state_changed as *const fn(&SteamNetConnectionStatusChangedCallback_t) as *mut c_void),
)?;
}
Ok((a, b))
}
}
impl GnsSocket<IsServer> {
pub fn accept(&self, connection: GnsConnection) -> GnsResult<()> {
check(unsafe {
SteamAPI_ISteamNetworkingSockets_AcceptConnection(get_interface(), connection.0)
})?;
if !unsafe {
SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
get_interface(),
connection.0,
self.state.poll_group.0,
)
} {
return Err(GnsError::Accept);
}
Ok(())
}
pub fn get_listen_socket_address(&self) -> Option<(IpAddr, u16)> {
let mut addr: SteamNetworkingIPAddr = unsafe { MaybeUninit::zeroed().assume_init() };
if unsafe {
SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress(
get_interface(),
self.state.listen_socket.0,
&mut addr,
)
} {
Some((ip_from_steam_ip_addr(&addr), addr.m_port))
} else {
None
}
}
pub fn set_listen_socket_config_value(
&self,
typ: ESteamNetworkingConfigValue,
value: GnsConfig<'_>,
) -> GnsResult<()> {
self.global.utils().set_config_value_scoped(
typ,
ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_ListenSocket,
self.state.listen_socket.0 as isize,
value,
)
}
pub fn get_listen_socket_config_value(
&self,
typ: ESteamNetworkingConfigValue,
) -> GnsResult<GnsConfigValue> {
self.global.utils().get_config_value_scoped(
typ,
ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_ListenSocket,
self.state.listen_socket.0 as isize,
)
}
}
impl GnsSocket<IsClient> {
#[inline]
pub fn connection(&self) -> GnsConnection {
self.state.connection
}
}
#[non_exhaustive]
pub enum GnsConfig<'a> {
Float(f32),
Int32(i32),
String(&'a str),
CStr(&'a CStr),
Ptr(*mut c_void),
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum GnsConfigValue {
Float(f32),
Int32(i32),
Int64(i64),
String(String),
Ptr(*mut c_void),
}
pub struct GnsUtils(());
type MsgPtr = *const ::std::os::raw::c_char;
type DebugCallback = dyn Fn(ESteamNetworkingSocketsDebugOutputType, &str) + Send + Sync + 'static;
static DEBUG_CB: OnceLock<Box<DebugCallback>> = OnceLock::new();
unsafe extern "C" fn debug_trampoline(ty: ESteamNetworkingSocketsDebugOutputType, msg: MsgPtr) {
if let Some(cb) = DEBUG_CB.get() {
let s = unsafe { CStr::from_ptr(msg) }.to_str().unwrap_or("");
cb(ty, s);
}
}
impl GnsUtils {
pub fn enable_debug_output(
&self,
ty: ESteamNetworkingSocketsDebugOutputType,
f: impl Fn(ESteamNetworkingSocketsDebugOutputType, &str) + Send + Sync + 'static,
) {
let _ = DEBUG_CB.set(Box::new(f));
unsafe {
SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(
get_utils(),
ty,
Some(debug_trampoline),
);
}
}
#[inline]
pub fn allocate_message<P: Payload>(
&self,
conn: GnsConnection,
flags: SendFlags,
payload: P,
) -> GnsNetworkMessage<ToSend> {
let message_ptr = unsafe { SteamAPI_ISteamNetworkingUtils_AllocateMessage(get_utils(), 0) };
GnsNetworkMessage::new(message_ptr, conn, flags, payload)
}
pub fn set_global_config_value(
&self,
typ: ESteamNetworkingConfigValue,
value: GnsConfig<'_>,
) -> GnsResult<()> {
let result = match value {
GnsConfig::Float(x) => unsafe {
SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat(get_utils(), typ, x)
},
GnsConfig::Int32(x) => unsafe {
SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32(get_utils(), typ, x)
},
GnsConfig::String(x) => {
let c = CString::new(x).map_err(|_| GnsError::Config("interior NUL"))?;
unsafe {
SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
get_utils(),
typ,
c.as_ptr(),
)
}
}
GnsConfig::CStr(x) => unsafe {
SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
get_utils(),
typ,
x.as_ptr(),
)
},
GnsConfig::Ptr(x) => unsafe {
SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr(get_utils(), typ, x)
},
};
if result {
Ok(())
} else {
Err(GnsError::Config("SetGlobalConfigValue rejected"))
}
}
pub fn set_connection_config_value(
&self,
conn: GnsConnection,
typ: ESteamNetworkingConfigValue,
value: GnsConfig<'_>,
) -> GnsResult<()> {
let result = match value {
GnsConfig::Float(x) => unsafe {
SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat(
get_utils(),
conn.0,
typ,
x,
)
},
GnsConfig::Int32(x) => unsafe {
SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32(
get_utils(),
conn.0,
typ,
x,
)
},
GnsConfig::String(x) => {
let c = CString::new(x).map_err(|_| GnsError::Config("interior NUL"))?;
unsafe {
SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(
get_utils(),
conn.0,
typ,
c.as_ptr(),
)
}
}
GnsConfig::CStr(x) => unsafe {
SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(
get_utils(),
conn.0,
typ,
x.as_ptr(),
)
},
GnsConfig::Ptr(x) => {
return self.set_config_value_scoped(
typ,
ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Connection,
conn.0 as isize,
GnsConfig::Ptr(x),
)
}
};
if result {
Ok(())
} else {
Err(GnsError::Config("SetConnectionConfigValue rejected"))
}
}
fn set_config_value_scoped(
&self,
typ: ESteamNetworkingConfigValue,
scope: ESteamNetworkingConfigScope,
scope_obj: isize,
value: GnsConfig<'_>,
) -> GnsResult<()> {
let owned_string;
let (data_type, arg): (ESteamNetworkingConfigDataType, *const c_void) = match &value {
GnsConfig::Float(x) => (
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Float,
x as *const f32 as _,
),
GnsConfig::Int32(x) => (
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32,
x as *const i32 as _,
),
GnsConfig::String(x) => {
owned_string = CString::new(*x).map_err(|_| GnsError::Config("interior NUL"))?;
(
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_String,
owned_string.as_ptr() as _,
)
}
GnsConfig::CStr(x) => (
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_String,
x.as_ptr() as _,
),
GnsConfig::Ptr(x) => (
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr,
x as *const *mut c_void as _,
),
};
if unsafe {
SteamAPI_ISteamNetworkingUtils_SetConfigValue(
get_utils(),
typ,
scope,
scope_obj,
data_type,
arg,
)
} {
Ok(())
} else {
Err(GnsError::Config("SetConfigValue rejected"))
}
}
fn get_config_value_scoped(
&self,
typ: ESteamNetworkingConfigValue,
scope: ESteamNetworkingConfigScope,
scope_obj: isize,
) -> GnsResult<GnsConfigValue> {
let mut data_type = ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32;
let mut buf = vec![0u8; 64];
let mut len = buf.len();
loop {
let result = unsafe {
SteamAPI_ISteamNetworkingUtils_GetConfigValue(
get_utils(),
typ,
scope,
scope_obj,
&mut data_type,
buf.as_mut_ptr() as *mut c_void,
&mut len,
)
};
match result {
ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_OK
| ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_OKInherited => {
break
}
ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_BufferTooSmall => {
buf.resize(len, 0);
}
ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_BadValue => {
return Err(GnsError::Config("unknown config value"))
}
ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_BadScopeObj => {
return Err(GnsError::Config("bad scope object"))
}
_ => return Err(GnsError::Config("GetConfigValue failed")),
}
}
let value = match data_type {
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32 => {
GnsConfigValue::Int32(unsafe { (buf.as_ptr() as *const i32).read_unaligned() })
}
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int64 => {
GnsConfigValue::Int64(unsafe { (buf.as_ptr() as *const i64).read_unaligned() })
}
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Float => {
GnsConfigValue::Float(unsafe { (buf.as_ptr() as *const f32).read_unaligned() })
}
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_String => {
let s = CStr::from_bytes_until_nul(&buf)
.map_err(|_| GnsError::Config("string value missing NUL"))?;
GnsConfigValue::String(s.to_string_lossy().into_owned())
}
ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr => {
GnsConfigValue::Ptr(unsafe {
(buf.as_ptr() as *const *mut c_void).read_unaligned()
})
}
_ => return Err(GnsError::Config("unknown config data type")),
};
Ok(value)
}
#[inline]
pub fn get_global_config_value(
&self,
typ: ESteamNetworkingConfigValue,
) -> GnsResult<GnsConfigValue> {
self.get_config_value_scoped(
typ,
ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global,
0,
)
}
#[inline]
pub fn get_connection_config_value(
&self,
conn: GnsConnection,
typ: ESteamNetworkingConfigValue,
) -> GnsResult<GnsConfigValue> {
self.get_config_value_scoped(
typ,
ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Connection,
conn.0 as isize,
)
}
pub fn get_config_value_info(
&self,
typ: ESteamNetworkingConfigValue,
) -> Option<(
&'static str,
ESteamNetworkingConfigDataType,
ESteamNetworkingConfigScope,
)> {
let mut data_type = ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32;
let mut scope = ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global;
let name = unsafe {
SteamAPI_ISteamNetworkingUtils_GetConfigValueInfo(
get_utils(),
typ,
&mut data_type,
&mut scope,
)
};
if name.is_null() {
None
} else {
let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("");
Some((name, data_type, scope))
}
}
#[inline]
pub fn local_timestamp(&self) -> SteamNetworkingMicroseconds {
unsafe { SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp(get_utils()) }
}
}