Unstable 1.1.14.0
This commit is contained in:
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user