Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -0,0 +1,282 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using Steamworks.Data;
namespace Steamworks
{
internal static unsafe class BufferManager
{
private sealed class ReferenceCounter
{
public IntPtr Pointer { get; private set; }
public int Size { get; private set; }
private int _count;
public void Set( IntPtr ptr, int size, int referenceCount )
{
if ( ptr == IntPtr.Zero )
throw new ArgumentNullException( nameof( ptr ) );
if ( size <= 0 )
throw new ArgumentOutOfRangeException( nameof( size ) );
if ( referenceCount <= 0 )
throw new ArgumentOutOfRangeException( nameof( referenceCount ) );
Pointer = ptr;
Size = size;
var prevCount = Interlocked.Exchange(ref _count, referenceCount);
if (prevCount != 0)
{
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Warning, $"{nameof( BufferManager )} set reference count when current count was not 0" );
#endif
}
}
public bool Decrement()
{
var newCount = Interlocked.Decrement( ref _count );
if ( newCount < 0 )
{
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Bug, $"Prevented double free of {nameof(BufferManager)} pointer" );
return false;
}
return newCount == 0;
}
}
[UnmanagedFunctionPointer( CallingConvention.Cdecl )]
private delegate void FreeFn( NetMsg* msg );
private static readonly Stack<ReferenceCounter> ReferenceCounterPool =
new Stack<ReferenceCounter>( 1024 );
private static readonly Dictionary<int, Stack<IntPtr>> BufferPools =
new Dictionary<int, Stack<IntPtr>>();
private static readonly Dictionary<IntPtr, ReferenceCounter> ReferenceCounters =
new Dictionary<IntPtr, ReferenceCounter>( 1024 );
private static readonly FreeFn FreeFunctionPin = new FreeFn( Free );
public static readonly IntPtr FreeFunctionPointer = Marshal.GetFunctionPointerForDelegate( FreeFunctionPin );
public static IntPtr Get( int size, int referenceCount )
{
const int maxSize = 16 * 1024 * 1024;
if ( size < 0 || size > maxSize )
throw new ArgumentOutOfRangeException( nameof( size ) );
if ( referenceCount <= 0 )
throw new ArgumentOutOfRangeException( nameof( referenceCount ) );
AllocateBuffer( size, out var ptr, out var actualSize );
var counter = AllocateReferenceCounter( ptr, actualSize, referenceCount );
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BufferManager )} allocated {ptr.ToInt64():X8} (size={size}, actualSize={actualSize}) with {referenceCount} references" );
#endif
lock ( ReferenceCounters )
{
ReferenceCounters.Add( ptr, counter );
}
return ptr;
}
[MonoPInvokeCallback]
private static void Free( NetMsg* msg )
{
var ptr = msg->DataPtr;
lock ( ReferenceCounters )
{
if ( !ReferenceCounters.TryGetValue( ptr, out var counter ) )
{
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Bug, $"Attempt to free pointer not tracked by {nameof(BufferManager)}: {ptr.ToInt64():X8}" );
return;
}
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose, $"{nameof( BufferManager )} decrementing reference count of {ptr.ToInt64():X8}" );
#endif
if ( counter.Decrement() )
{
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose, $"{nameof( BufferManager )} freeing {ptr.ToInt64():X8} as it is now unreferenced" );
if ( ptr != counter.Pointer )
{
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Bug,
$"{nameof( BufferManager )} freed pointer ({ptr.ToInt64():X8}) does not match counter pointer ({counter.Pointer.ToInt64():X8})" );
}
var bucketSize = GetBucketSize( counter.Size );
if ( counter.Size != bucketSize )
{
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Bug,
$"{nameof( BufferManager )} freed pointer size ({counter.Size}) does not match bucket size ({bucketSize})" );
}
#endif
ReferenceCounters.Remove( ptr );
FreeBuffer( ptr, counter.Size );
FreeReferenceCounter( counter );
}
}
}
private static ReferenceCounter AllocateReferenceCounter( IntPtr ptr, int size, int referenceCount )
{
lock ( ReferenceCounterPool )
{
var counter = ReferenceCounterPool.Count > 0
? ReferenceCounterPool.Pop()
: new ReferenceCounter();
counter.Set( ptr, size, referenceCount );
return counter;
}
}
private static void FreeReferenceCounter( ReferenceCounter counter )
{
if ( counter == null )
throw new ArgumentNullException( nameof( counter ) );
lock ( ReferenceCounterPool )
{
if ( ReferenceCounterPool.Count >= 1024 )
{
// we don't want to keep a ton of these lying around - let it GC if we have too many
return;
}
ReferenceCounterPool.Push( counter );
}
}
private static void AllocateBuffer( int minimumSize, out IntPtr ptr, out int size )
{
var bucketSize = GetBucketSize( minimumSize );
if ( bucketSize <= 0 )
{
// not bucketed, no pooling for this size
ptr = Marshal.AllocHGlobal( minimumSize );
size = minimumSize;
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BufferManager )} allocated unpooled pointer {ptr.ToInt64():X8} (size={size})" );
#endif
return;
}
lock ( BufferPools )
{
if ( !BufferPools.TryGetValue( bucketSize, out var bucketPool ) || bucketPool.Count == 0 )
{
// nothing pooled yet, but we can pool this size
ptr = Marshal.AllocHGlobal( bucketSize );
size = bucketSize;
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BufferManager )} allocated new poolable pointer {ptr.ToInt64():X8} (size={size})" );
#endif
return;
}
ptr = bucketPool.Pop();
size = bucketSize;
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BufferManager )} allocated pointer from pool {ptr.ToInt64():X8} (size={size})" );
#endif
}
}
private static void FreeBuffer( IntPtr ptr, int size )
{
var bucketSize = GetBucketSize( size );
var bucketLimit = GetBucketLimit( size );
if ( bucketSize <= 0 || bucketLimit <= 0 )
{
// not bucketed, no pooling for this size
Marshal.FreeHGlobal( ptr );
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BufferManager )} freed unpooled pointer {ptr.ToInt64():X8} (size={size})" );
#endif
return;
}
lock ( BufferPools )
{
if ( !BufferPools.TryGetValue( bucketSize, out var bucketPool ) )
{
bucketPool = new Stack<IntPtr>( bucketLimit );
BufferPools.Add( bucketSize, bucketPool );
}
if ( bucketPool.Count >= bucketLimit )
{
// pool overflow, get rid
Marshal.FreeHGlobal( ptr );
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BufferManager )} pool overflow, freed pooled pointer {ptr.ToInt64():X8} (size={size})" );
#endif
return;
}
bucketPool.Push( ptr );
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BufferManager )} returned pointer to pool {ptr.ToInt64():X8} (size={size})" );
#endif
}
}
private const int Bucket512 = 512;
private const int Bucket1Kb = 1 * 1024;
private const int Bucket4Kb = 4 * 1024;
private const int Bucket16Kb = 16 * 1024;
private const int Bucket64Kb = 64 * 1024;
private const int Bucket256Kb = 256 * 1024;
private static int GetBucketSize( int size )
{
if ( size <= Bucket512 ) return Bucket512;
if ( size <= Bucket1Kb ) return Bucket1Kb;
if ( size <= Bucket4Kb ) return Bucket4Kb;
if ( size <= Bucket16Kb ) return Bucket16Kb;
if ( size <= Bucket64Kb ) return Bucket64Kb;
if ( size <= Bucket256Kb ) return Bucket256Kb;
return -1;
}
private static int GetBucketLimit( int size )
{
if ( size <= Bucket512 ) return 1024;
if ( size <= Bucket1Kb ) return 512;
if ( size <= Bucket4Kb ) return 128;
if ( size <= Bucket16Kb ) return 32;
if ( size <= Bucket64Kb ) return 16;
if ( size <= Bucket256Kb ) return 8;
return -1;
}
}
}
@@ -11,13 +11,18 @@ namespace Steamworks.Data
/// You can override all the virtual functions to turn it into what you
/// want it to do.
/// </summary>
public struct Connection
public struct Connection : IEquatable<Connection>
{
public uint Id { get; set; }
public bool Equals( Connection other ) => Id == other.Id;
public override bool Equals( object obj ) => obj is Connection other && Id == other.Id;
public override int GetHashCode() => Id.GetHashCode();
public override string ToString() => Id.ToString();
public static implicit operator Connection( uint value ) => new Connection() { Id = value };
public static implicit operator uint( Connection value ) => value.Id;
public static bool operator ==( Connection value1, Connection value2 ) => value1.Equals( value2 );
public static bool operator !=( Connection value1, Connection value2 ) => !value1.Equals( value2 );
/// <summary>
/// Accept an incoming connection that has been received on a listen socket.
@@ -64,21 +69,41 @@ namespace Steamworks.Data
/// <summary>
/// This is the best version to use.
/// </summary>
public Result SendMessage( IntPtr ptr, int size, SendType sendType = SendType.Reliable )
public unsafe Result SendMessage( IntPtr ptr, int size, SendType sendType = SendType.Reliable, ushort laneIndex = 0 )
{
if ( ptr == IntPtr.Zero )
throw new ArgumentNullException( nameof( ptr ) );
if ( size == 0 )
throw new ArgumentException( "`size` cannot be zero", nameof( size ) );
var copyPtr = BufferManager.Get( size, 1 );
Buffer.MemoryCopy( (void*)ptr, (void*)copyPtr, size, size );
var message = SteamNetworkingUtils.AllocateMessage();
message->Connection = this;
message->Flags = sendType;
message->DataPtr = copyPtr;
message->DataSize = size;
message->FreeDataPtr = BufferManager.FreeFunctionPointer;
message->IdxLane = laneIndex;
long messageNumber = 0;
return SteamNetworkingSockets.Internal?.SendMessageToConnection( this, ptr, (uint) size, (int)sendType, ref messageNumber ) ?? Result.Fail;
SteamNetworkingSockets.Internal?.SendMessages( 1, &message, &messageNumber );
return messageNumber >= 0
? Result.OK
: (Result)(-messageNumber);
}
/// <summary>
/// Ideally should be using an IntPtr version unless you're being really careful with the byte[] array and
/// you're not creating a new one every frame (like using .ToArray())
/// </summary>
public unsafe Result SendMessage( byte[] data, SendType sendType = SendType.Reliable )
public unsafe Result SendMessage( byte[] data, SendType sendType = SendType.Reliable, ushort laneIndex = 0 )
{
fixed ( byte* ptr = data )
{
return SendMessage( (IntPtr)ptr, data.Length, sendType );
return SendMessage( (IntPtr)ptr, data.Length, sendType, laneIndex );
}
}
@@ -86,21 +111,21 @@ namespace Steamworks.Data
/// Ideally should be using an IntPtr version unless you're being really careful with the byte[] array and
/// you're not creating a new one every frame (like using .ToArray())
/// </summary>
public unsafe Result SendMessage( byte[] data, int offset, int length, SendType sendType = SendType.Reliable )
public unsafe Result SendMessage( byte[] data, int offset, int length, SendType sendType = SendType.Reliable, ushort laneIndex = 0 )
{
fixed ( byte* ptr = data )
{
return SendMessage( (IntPtr)ptr + offset, length, sendType );
return SendMessage( (IntPtr)ptr + offset, length, sendType, laneIndex );
}
}
/// <summary>
/// This creates a ton of garbage - so don't do anything with this beyond testing!
/// </summary>
public unsafe Result SendMessage( string str, SendType sendType = SendType.Reliable )
public unsafe Result SendMessage( string str, SendType sendType = SendType.Reliable, ushort laneIndex = 0 )
{
var bytes = System.Text.Encoding.UTF8.GetBytes( str );
return SendMessage( bytes, sendType );
return SendMessage( bytes, sendType, laneIndex );
}
/// <summary>
@@ -121,5 +146,27 @@ namespace Steamworks.Data
return strVal;
}
/// <summary>
/// Returns a small set of information about the real-time state of the connection.
/// </summary>
public ConnectionStatus QuickStatus()
{
ConnectionStatus connectionStatus = default( ConnectionStatus );
SteamNetworkingSockets.Internal?.GetConnectionRealTimeStatus( this, ref connectionStatus, 0, null );
return connectionStatus;
}
/// <summary>
/// Configure multiple outbound messages streams ("lanes") on a connection, and
/// control head-of-line blocking between them.
/// </summary>
public Result ConfigureConnectionLanes( int[] lanePriorities, ushort[] laneWeights )
{
return SteamNetworkingSockets.Internal?.ConfigureConnectionLanes( this, lanePriorities.Length, lanePriorities, laneWeights )
?? Result.Fail;
}
}
}
@@ -0,0 +1,34 @@
using System.Runtime.InteropServices;
namespace Steamworks.Data
{
/// <summary>
/// Describe the status of a connection
/// </summary>
[StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )]
public struct ConnectionLaneStatus
{
internal int cbPendingUnreliable; // m_cbPendingUnreliable int
internal int cbPendingReliable; // m_cbPendingReliable int
internal int cbSentUnackedReliable; // m_cbSentUnackedReliable int
internal int _reservePad1; // _reservePad1 int
internal long ecQueueTime; // m_usecQueueTime SteamNetworkingMicroseconds
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 10, ArraySubType = UnmanagedType.U4 )]
internal uint[] reserved; // reserved uint32 [10]
/// <summary>
/// Number of bytes unreliable data pending to be sent. This is data that you have recently requested to be sent but has not yet actually been put on the wire.
/// </summary>
public int PendingUnreliable => cbPendingUnreliable;
/// <summary>
/// Number of bytes reliable data pending to be sent. This is data that you have recently requested to be sent but has not yet actually been put on the wire.
/// </summary>
public int PendingReliable => cbPendingReliable;
/// <summary>
/// Number of bytes of reliable data that has been placed the wire, but for which we have not yet received an acknowledgment, and thus we may have to re-transmit.
/// </summary>
public int SentUnackedReliable => cbSentUnackedReliable;
}
}
@@ -1,6 +1,5 @@
using Steamworks.Data;
using System;
using System.Runtime.InteropServices;
namespace Steamworks
{
@@ -36,7 +35,10 @@ namespace Steamworks
set => Connection.UserData = value;
}
public void Close() => Connection.Close();
public void Close( bool linger = false, int reasonCode = 0, string debugString = "Closing Connection" )
{
Connection.Close( linger, reasonCode, debugString );
}
public override string ToString() => Connection.ToString();
@@ -44,18 +46,40 @@ namespace Steamworks
{
ConnectionInfo = info;
//
// Some notes:
// - Update state before the callbacks, in case an exception is thrown
// - ConnectionState.None happens when a connection is destroyed, even if it was already disconnected (ClosedByPeer / ProblemDetectedLocally)
//
switch ( info.State )
{
case ConnectionState.Connecting:
OnConnecting( info );
if ( !Connecting && !Connected )
{
Connecting = true;
OnConnecting( info );
}
break;
case ConnectionState.Connected:
OnConnected( info );
if ( Connecting && !Connected )
{
Connecting = false;
Connected = true;
OnConnected( info );
}
break;
case ConnectionState.ClosedByPeer:
case ConnectionState.ProblemDetectedLocally:
case ConnectionState.None:
OnDisconnected( info );
if ( Connecting || Connected )
{
Connecting = false;
Connected = false;
OnDisconnected( info );
}
break;
}
}
@@ -66,8 +90,6 @@ namespace Steamworks
public virtual void OnConnecting( ConnectionInfo info )
{
Interface?.OnConnecting( info );
Connecting = true;
}
/// <summary>
@@ -76,9 +98,6 @@ namespace Steamworks
public virtual void OnConnected( ConnectionInfo info )
{
Interface?.OnConnected( info );
Connected = true;
Connecting = false;
}
/// <summary>
@@ -87,52 +106,166 @@ namespace Steamworks
public virtual void OnDisconnected( ConnectionInfo info )
{
Interface?.OnDisconnected( info );
Connected = false;
Connecting = false;
}
public void Receive( int bufferSize = 32 )
public unsafe int Receive( int bufferSize = 32, bool receiveToEnd = true )
{
if (SteamNetworkingSockets.Internal is null) { return; }
if (SteamNetworkingSockets.Internal is null) { return 0; }
if ( bufferSize < 1 || bufferSize > 256 ) throw new ArgumentOutOfRangeException( nameof( bufferSize ) );
int totalProcessed = 0;
NetMsg** messageBuffer = stackalloc NetMsg*[bufferSize];
int processed = 0;
IntPtr messageBuffer = Marshal.AllocHGlobal( IntPtr.Size * bufferSize );
try
while ( true )
{
processed = SteamNetworkingSockets.Internal.ReceiveMessagesOnConnection( Connection, messageBuffer, bufferSize );
int processed = SteamNetworkingSockets.Internal.ReceiveMessagesOnConnection( Connection, new IntPtr( &messageBuffer[0] ), bufferSize );
totalProcessed += processed;
for ( int i = 0; i < processed; i++ )
try
{
ReceiveMessage( Marshal.ReadIntPtr( messageBuffer, i * IntPtr.Size ) );
for ( int i = 0; i < processed; i++ )
{
ReceiveMessage( ref messageBuffer[i] );
}
}
catch
{
for ( int i = 0; i < processed; i++ )
{
if ( messageBuffer[i] != null )
{
NetMsg.InternalRelease( messageBuffer[i] );
}
}
throw;
}
//
// Keep going if receiveToEnd and we filled the buffer
//
if ( !receiveToEnd || processed < bufferSize )
break;
}
return totalProcessed;
}
/// <summary>
/// Sends a message to multiple connections.
/// </summary>
/// <param name="connections">The connections to send the message to.</param>
/// <param name="connectionCount">The number of connections to send the message to, to allow reusing the connections array.</param>
/// <param name="ptr">Pointer to the message data.</param>
/// <param name="size">Size of the message data.</param>
/// <param name="sendType">Flags to control delivery of the message.</param>
/// <param name="results">An optional array to hold the results of sending the messages for each connection.</param>
public unsafe void SendMessages( Connection[] connections, int connectionCount, IntPtr ptr, int size, SendType sendType = SendType.Reliable, Result[]? results = null )
{
if ( connections == null )
throw new ArgumentNullException( nameof( connections ) );
if ( connectionCount < 0 || connectionCount > connections.Length )
throw new ArgumentException( "`connectionCount` must be between 0 and `connections.Length`", nameof( connectionCount ) );
if ( results != null && connectionCount > results.Length )
throw new ArgumentException( "`results` must have at least `connectionCount` entries", nameof( results ) );
if ( connectionCount > 1024 ) // restricting this because we stack allocate based on this value
throw new ArgumentOutOfRangeException( nameof( connectionCount ) );
if ( ptr == IntPtr.Zero )
throw new ArgumentNullException( nameof( ptr ) );
if ( size == 0 )
throw new ArgumentException( "`size` cannot be zero", nameof( size ) );
if ( SteamNetworkingSockets.Internal is null )
return;
if ( connectionCount == 0 )
return;
// SendMessages does not make a copy of the data. We will need to copy because we don't want to force the caller to keep the pointer valid.
// 1. We don't want a copy per message. They all refer to the same data. This is the benefit of using Broadcast vs. many sends.
// 2. We need to use unmanaged memory. Managed memory may move around and invalidate pointers so it's not an option.
// 3. We'll use a reference counter and custom free() function to release this unmanaged memory.
var copyPtr = BufferManager.Get( size, connectionCount );
Buffer.MemoryCopy( (void*)ptr, (void*)copyPtr, size, size );
var messages = stackalloc NetMsg*[connectionCount];
var messageNumberOrResults = stackalloc long[results != null ? connectionCount : 0];
for ( var i = 0; i < connectionCount; i++ )
{
messages[i] = SteamNetworkingUtils.AllocateMessage();
messages[i]->Connection = connections[i];
messages[i]->Flags = sendType;
messages[i]->DataPtr = copyPtr;
messages[i]->DataSize = size;
messages[i]->FreeDataPtr = BufferManager.FreeFunctionPointer;
}
SteamNetworkingSockets.Internal.SendMessages( connectionCount, messages, messageNumberOrResults );
if (results == null)
return;
for ( var i = 0; i < connectionCount; i++ )
{
if ( messageNumberOrResults[i] < 0 )
{
results[i] = (Result)( -messageNumberOrResults[i] );
}
else
{
results[i] = Result.OK;
}
}
finally
{
Marshal.FreeHGlobal( messageBuffer );
}
//
// Overwhelmed our buffer, keep going
//
if ( processed == bufferSize )
Receive( bufferSize );
}
internal unsafe void ReceiveMessage( IntPtr msgPtr )
/// <summary>
/// Ideally should be using an IntPtr version unless you're being really careful with the byte[] array and
/// you're not creating a new one every frame (like using .ToArray())
/// </summary>
public unsafe void SendMessages( Connection[] connections, int connectionCount, byte[] data, SendType sendType = SendType.Reliable, Result[]? results = null )
{
fixed ( byte* ptr = data )
{
SendMessages( connections, connectionCount, (IntPtr)ptr, data.Length, sendType, results );
}
}
/// <summary>
/// Ideally should be using an IntPtr version unless you're being really careful with the byte[] array and
/// you're not creating a new one every frame (like using .ToArray())
/// </summary>
public unsafe void SendMessages( Connection[] connections, int connectionCount, byte[] data, int offset, int length, SendType sendType = SendType.Reliable, Result[]? results = null )
{
fixed ( byte* ptr = data )
{
SendMessages( connections, connectionCount, (IntPtr)ptr + offset, length, sendType, results );
}
}
/// <summary>
/// This creates a ton of garbage - so don't do anything with this beyond testing!
/// </summary>
public void SendMessages( Connection[] connections, int connectionCount, string str, SendType sendType = SendType.Reliable, Result[]? results = null )
{
var bytes = System.Text.Encoding.UTF8.GetBytes( str );
SendMessages( connections, connectionCount, bytes, sendType, results );
}
internal unsafe void ReceiveMessage( ref NetMsg* msg )
{
var msg = Marshal.PtrToStructure<NetMsg>( msgPtr );
try
{
OnMessage( msg.DataPtr, msg.DataSize, msg.RecvTime, msg.MessageNumber, msg.Channel );
OnMessage( msg->DataPtr, msg->DataSize, msg->RecvTime, msg->MessageNumber, msg->Channel );
}
finally
{
//
// Releases the message
//
NetMsg.InternalRelease( (NetMsg*) msgPtr );
NetMsg.InternalRelease( msg );
msg = null;
}
}
@@ -141,4 +274,4 @@ namespace Steamworks
Interface?.OnMessage( data, size, messageNum, recvTime, channel );
}
}
}
}
@@ -0,0 +1,77 @@
using System.Runtime.InteropServices;
namespace Steamworks.Data
{
/// <summary>
/// Describe the status of a connection
/// </summary>
[StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )]
public struct ConnectionStatus
{
internal ConnectionState state; // m_eState ESteamNetworkingConnectionState
internal int ping; // m_nPing int
internal float connectionQualityLocal; // m_flConnectionQualityLocal float
internal float connectionQualityRemote; // m_flConnectionQualityRemote float
internal float outPacketsPerSec; // m_flOutPacketsPerSec float
internal float outBytesPerSec; // m_flOutBytesPerSec float
internal float inPacketsPerSec; // m_flInPacketsPerSec float
internal float inBytesPerSec; // m_flInBytesPerSec float
internal int sendRateBytesPerSecond; // m_nSendRateBytesPerSecond int
internal int cbPendingUnreliable; // m_cbPendingUnreliable int
internal int cbPendingReliable; // m_cbPendingReliable int
internal int cbSentUnackedReliable; // m_cbSentUnackedReliable int
internal long ecQueueTime; // m_usecQueueTime SteamNetworkingMicroseconds
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 16, ArraySubType = UnmanagedType.U4 )]
internal uint[] reserved; // reserved uint32 [16]
/// <summary>
/// Current ping (ms)
/// </summary>
public int Ping => ping;
/// <summary>
/// Outgoing packets per second
/// </summary>
public float OutPacketsPerSec => outPacketsPerSec;
/// <summary>
/// Outgoing bytes per second
/// </summary>
public float OutBytesPerSec => outBytesPerSec;
/// <summary>
/// Incoming packets per second
/// </summary>
public float InPacketsPerSec => inPacketsPerSec;
/// <summary>
/// Incoming bytes per second
/// </summary>
public float InBytesPerSec => inBytesPerSec;
/// <summary>
/// Connection quality measured locally, 0...1 (percentage of packets delivered end-to-end in order).
/// </summary>
public float ConnectionQualityLocal => connectionQualityLocal;
/// <summary>
/// Packet delivery success rate as observed from remote host, 0...1 (percentage of packets delivered end-to-end in order).
/// </summary>
public float ConnectionQualityRemote => connectionQualityRemote;
/// <summary>
/// Number of bytes unreliable data pending to be sent. This is data that you have recently requested to be sent but has not yet actually been put on the wire.
/// </summary>
public int PendingUnreliable => cbPendingUnreliable;
/// <summary>
/// Number of bytes reliable data pending to be sent. This is data that you have recently requested to be sent but has not yet actually been put on the wire.
/// </summary>
public int PendingReliable => cbPendingReliable;
/// <summary>
/// Number of bytes of reliable data that has been placed the wire, but for which we have not yet received an acknowledgment, and thus we may have to re-transmit.
/// </summary>
public int SentUnackedReliable => cbSentUnackedReliable;
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Steamworks.Data;
namespace Steamworks
{
[UnmanagedFunctionPointer( Platform.CC )]
internal delegate void NetDebugFunc( [In] NetDebugOutput nType, [In] IntPtr pszMsg );
[UnmanagedFunctionPointer( Platform.CC )]
internal unsafe delegate void FnSteamNetConnectionStatusChanged( ref SteamNetConnectionStatusChangedCallback_t arg );
[UnmanagedFunctionPointer( Platform.CC )]
internal delegate void FnSteamNetAuthenticationStatusChanged( ref SteamNetAuthenticationStatus_t arg );
[UnmanagedFunctionPointer( Platform.CC )]
internal delegate void FnSteamRelayNetworkStatusChanged( ref SteamRelayNetworkStatus_t arg );
[UnmanagedFunctionPointer( Platform.CC )]
internal delegate void FnSteamNetworkingMessagesSessionRequest( ref SteamNetworkingMessagesSessionRequest_t arg );
[UnmanagedFunctionPointer( Platform.CC )]
internal delegate void FnSteamNetworkingMessagesSessionFailed( ref SteamNetworkingMessagesSessionFailed_t arg );
[UnmanagedFunctionPointer( Platform.CC )]
internal delegate void FnSteamNetworkingFakeIPResult( ref SteamNetworkingFakeIPResult_t arg );
}
@@ -16,7 +16,7 @@ namespace Steamworks
void OnConnected( Connection connection, ConnectionInfo info );
/// <summary>
/// Called when the connection leaves
/// Called when the connection leaves. Must call Close on the connection
/// </summary>
void OnDisconnected( Connection connection, ConnectionInfo info );
@@ -110,6 +110,18 @@ namespace Steamworks.Data
}
}
/// <summary>
/// Return true if IP is a fake IPv4 for Steam Datagram Relay
/// </summary>
public bool IsFakeIPv4
{
get
{
NetAddress self = this;
return SteamNetworkingUtils.Internal != null && SteamNetworkingUtils.Internal.IsFakeIPv4( InternalGetIPv4( ref self ) );
}
}
/// <summary>
/// Return true if this identity is localhost. (Either IPv6 ::1, or IPv4 127.0.0.1)
/// </summary>
@@ -1,9 +0,0 @@
using Steamworks.Data;
using System;
using System.Runtime.InteropServices;
namespace Steamworks.Data
{
[UnmanagedFunctionPointer( Platform.CC )]
delegate void NetDebugFunc( [In] NetDebugOutput nType, [In] IntPtr pszMsg );
}
@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
namespace Steamworks.Data
{
[StructLayout( LayoutKind.Explicit, Pack = Platform.StructPlatformPackSize )]
internal struct NetKeyValue
internal partial struct NetKeyValue
{
[FieldOffset(0)]
internal NetConfig Value; // m_eValue ESteamNetworkingConfigValue
@@ -1,5 +1,4 @@
using Steamworks.Data;
using System;
using System;
using System.Runtime.InteropServices;
namespace Steamworks.Data
@@ -17,5 +16,9 @@ namespace Steamworks.Data
internal IntPtr FreeDataPtr;
internal IntPtr ReleasePtr;
internal int Channel;
internal SendType Flags;
internal long UserData;
internal ushort IdxLane;
internal ushort _pad1__;
}
}
}
@@ -16,8 +16,9 @@ namespace Steamworks
{
public ISocketManager? Interface { get; set; }
public List<Connection> Connecting = new List<Connection>();
public List<Connection> Connected = new List<Connection>();
public HashSet<Connection> Connecting = new HashSet<Connection>();
public HashSet<Connection> Connected = new HashSet<Connection>();
public Socket Socket { get; internal set; }
public override string ToString() => Socket.ToString();
@@ -44,17 +45,23 @@ namespace Steamworks
public virtual void OnConnectionChanged( Connection connection, ConnectionInfo info )
{
//
// Some notes:
// - Update state before the callbacks, in case an exception is thrown
// - ConnectionState.None happens when a connection is destroyed, even if it was already disconnected (ClosedByPeer / ProblemDetectedLocally)
//
switch ( info.State )
{
case ConnectionState.Connecting:
if ( !Connecting.Contains( connection ) )
if ( !Connecting.Contains( connection ) && !Connected.Contains( connection ) )
{
Connecting.Add( connection );
OnConnecting( connection, info );
}
break;
case ConnectionState.Connected:
if ( !Connected.Contains( connection ) )
if ( Connecting.Contains( connection ) && !Connected.Contains( connection ) )
{
Connecting.Remove( connection );
Connected.Add( connection );
@@ -67,6 +74,9 @@ namespace Steamworks
case ConnectionState.None:
if ( Connecting.Contains( connection ) || Connected.Contains( connection ) )
{
Connecting.Remove( connection );
Connected.Remove( connection );
OnDisconnected( connection, info );
}
break;
@@ -81,7 +91,6 @@ namespace Steamworks
if ( Interface != null )
{
Interface.OnConnecting( connection, info );
return;
}
else
{
@@ -104,17 +113,17 @@ namespace Steamworks
/// </summary>
public virtual void OnDisconnected( Connection connection, ConnectionInfo info )
{
SteamNetworkingSockets.Internal?.SetConnectionPollGroup( connection, 0 );
connection.Close();
Connecting.Remove( connection );
Connected.Remove( connection );
Interface?.OnDisconnected( connection, info );
if ( Interface != null )
{
Interface.OnDisconnected( connection, info );
}
else
{
connection.Close();
}
}
public void Receive( int bufferSize = 32 )
public int Receive( int bufferSize = 32, bool receiveToEnd = true )
{
int processed = 0;
IntPtr messageBuffer = Marshal.AllocHGlobal( IntPtr.Size * bufferSize );
@@ -137,8 +146,10 @@ namespace Steamworks
//
// Overwhelmed our buffer, keep going
//
if ( processed == bufferSize )
Receive( bufferSize );
if ( receiveToEnd && processed == bufferSize )
processed += Receive( bufferSize );
return processed;
}
internal unsafe void ReceiveMessage( IntPtr msgPtr )
@@ -162,4 +173,4 @@ namespace Steamworks
Interface?.OnMessage( connection, identity, data, size, messageNum, recvTime, channel );
}
}
}
}