Add ConnectionManager.Broadcast for sending the same message to

multiple clients
This commit is contained in:
Rohan Singh 2021-01-18 23:16:57 -05:00
parent 47b9aca324
commit 616571a3a9
42 changed files with 454 additions and 68 deletions

View File

@ -72,7 +72,7 @@ namespace Steamworks
Receive(); Receive();
await Task.Delay( 100 ); await Task.Delay( 100 );
if ( sw.Elapsed.TotalSeconds > 10 ) if ( sw.Elapsed.TotalSeconds > 30 )
{ {
Assert.Fail( "Client Took Too Long" ); Assert.Fail( "Client Took Too Long" );
break; break;
@ -95,9 +95,11 @@ namespace Steamworks
Console.WriteLine( $"[Connection][{messageNum}][{recvTime}][{channel}] Sending: How do you like 20 messages in a row?" ); Console.WriteLine( $"[Connection][{messageNum}][{recvTime}][{channel}] Sending: How do you like 20 messages in a row?" );
Connection.SendMessage( "How do you like 20 messages in a row?" ); Connection.SendMessage( "How do you like 20 messages in a row?" );
var connections = new[] { Connection };
for ( int i=0; i<20; i++ ) for ( int i=0; i<20; i++ )
{ {
Connection.SendMessage( $"BLAMMO!" ); Console.WriteLine( $"[Connection][{messageNum}][{recvTime}][{channel}] Sending: BLAMMO {i}!" );
Broadcast( connections, connections.Length, $"BLAMMO {i}!" );
} }
Connection.Flush(); Connection.Flush();

View File

@ -97,7 +97,7 @@ namespace Steamworks
Receive(); Receive();
await Task.Delay( 100 ); await Task.Delay( 100 );
if ( sw.Elapsed.TotalSeconds > 5 ) if ( sw.Elapsed.TotalSeconds > 100 )
{ {
Console.WriteLine( "Socket: This all took too long - throwing an exception" ); Console.WriteLine( "Socket: This all took too long - throwing an exception" );
Assert.Fail( "Socket Took Too Long" ); Assert.Fail( "Socket Took Too Long" );

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamAppList : SteamInterface internal unsafe class ISteamAppList : SteamInterface
{ {
internal ISteamAppList( bool IsGameServer ) internal ISteamAppList( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamApps : SteamInterface internal unsafe class ISteamApps : SteamInterface
{ {
internal ISteamApps( bool IsGameServer ) internal ISteamApps( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamClient : SteamInterface internal unsafe class ISteamClient : SteamInterface
{ {
internal ISteamClient( bool IsGameServer ) internal ISteamClient( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamController : SteamInterface internal unsafe class ISteamController : SteamInterface
{ {
internal ISteamController( bool IsGameServer ) internal ISteamController( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamFriends : SteamInterface internal unsafe class ISteamFriends : SteamInterface
{ {
internal ISteamFriends( bool IsGameServer ) internal ISteamFriends( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamGameSearch : SteamInterface internal unsafe class ISteamGameSearch : SteamInterface
{ {
internal ISteamGameSearch( bool IsGameServer ) internal ISteamGameSearch( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamGameServer : SteamInterface internal unsafe class ISteamGameServer : SteamInterface
{ {
internal ISteamGameServer( bool IsGameServer ) internal ISteamGameServer( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamGameServerStats : SteamInterface internal unsafe class ISteamGameServerStats : SteamInterface
{ {
internal ISteamGameServerStats( bool IsGameServer ) internal ISteamGameServerStats( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamHTMLSurface : SteamInterface internal unsafe class ISteamHTMLSurface : SteamInterface
{ {
internal ISteamHTMLSurface( bool IsGameServer ) internal ISteamHTMLSurface( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamHTTP : SteamInterface internal unsafe class ISteamHTTP : SteamInterface
{ {
internal ISteamHTTP( bool IsGameServer ) internal ISteamHTTP( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamInput : SteamInterface internal unsafe class ISteamInput : SteamInterface
{ {
internal ISteamInput( bool IsGameServer ) internal ISteamInput( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamInventory : SteamInterface internal unsafe class ISteamInventory : SteamInterface
{ {
internal ISteamInventory( bool IsGameServer ) internal ISteamInventory( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMatchmaking : SteamInterface internal unsafe class ISteamMatchmaking : SteamInterface
{ {
internal ISteamMatchmaking( bool IsGameServer ) internal ISteamMatchmaking( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMatchmakingPingResponse : SteamInterface internal unsafe class ISteamMatchmakingPingResponse : SteamInterface
{ {
internal ISteamMatchmakingPingResponse( bool IsGameServer ) internal ISteamMatchmakingPingResponse( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMatchmakingPlayersResponse : SteamInterface internal unsafe class ISteamMatchmakingPlayersResponse : SteamInterface
{ {
internal ISteamMatchmakingPlayersResponse( bool IsGameServer ) internal ISteamMatchmakingPlayersResponse( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMatchmakingRulesResponse : SteamInterface internal unsafe class ISteamMatchmakingRulesResponse : SteamInterface
{ {
internal ISteamMatchmakingRulesResponse( bool IsGameServer ) internal ISteamMatchmakingRulesResponse( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMatchmakingServerListResponse : SteamInterface internal unsafe class ISteamMatchmakingServerListResponse : SteamInterface
{ {
internal ISteamMatchmakingServerListResponse( bool IsGameServer ) internal ISteamMatchmakingServerListResponse( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMatchmakingServers : SteamInterface internal unsafe class ISteamMatchmakingServers : SteamInterface
{ {
internal ISteamMatchmakingServers( bool IsGameServer ) internal ISteamMatchmakingServers( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMusic : SteamInterface internal unsafe class ISteamMusic : SteamInterface
{ {
internal ISteamMusic( bool IsGameServer ) internal ISteamMusic( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamMusicRemote : SteamInterface internal unsafe class ISteamMusicRemote : SteamInterface
{ {
internal ISteamMusicRemote( bool IsGameServer ) internal ISteamMusicRemote( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamNetworking : SteamInterface internal unsafe class ISteamNetworking : SteamInterface
{ {
internal ISteamNetworking( bool IsGameServer ) internal ISteamNetworking( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamNetworkingMessages : SteamInterface internal unsafe class ISteamNetworkingMessages : SteamInterface
{ {
internal ISteamNetworkingMessages( bool IsGameServer ) internal ISteamNetworkingMessages( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamNetworkingSockets : SteamInterface internal unsafe class ISteamNetworkingSockets : SteamInterface
{ {
internal ISteamNetworkingSockets( bool IsGameServer ) internal ISteamNetworkingSockets( bool IsGameServer )
@ -162,12 +162,12 @@ namespace Steamworks
#region FunctionMeta #region FunctionMeta
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SendMessages", CallingConvention = Platform.CC)] [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SendMessages", CallingConvention = Platform.CC)]
private static extern void _SendMessages( IntPtr self, int nMessages, ref NetMsg pMessages, [In,Out] long[] pOutMessageNumberOrResult ); private static extern void _SendMessages( IntPtr self, int nMessages, NetMsg** pMessages, [In,Out] long[] pOutMessageNumberOrResult );
#endregion #endregion
internal void SendMessages( int nMessages, ref NetMsg pMessages, [In,Out] long[] pOutMessageNumberOrResult ) internal void SendMessages( int nMessages, NetMsg** pMessages, [In,Out] long[] pOutMessageNumberOrResult )
{ {
_SendMessages( Self, nMessages, ref pMessages, pOutMessageNumberOrResult ); _SendMessages( Self, nMessages, pMessages, pOutMessageNumberOrResult );
} }
#region FunctionMeta #region FunctionMeta

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamNetworkingUtils : SteamInterface internal unsafe class ISteamNetworkingUtils : SteamInterface
{ {
internal ISteamNetworkingUtils( bool IsGameServer ) internal ISteamNetworkingUtils( bool IsGameServer )
@ -22,13 +22,13 @@ namespace Steamworks
#region FunctionMeta #region FunctionMeta
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_AllocateMessage", CallingConvention = Platform.CC)] [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_AllocateMessage", CallingConvention = Platform.CC)]
private static extern IntPtr _AllocateMessage( IntPtr self, int cbAllocateBuffer ); private static extern NetMsg* _AllocateMessage( IntPtr self, int cbAllocateBuffer );
#endregion #endregion
internal NetMsg AllocateMessage( int cbAllocateBuffer ) internal NetMsg* AllocateMessage( int cbAllocateBuffer )
{ {
var returnValue = _AllocateMessage( Self, cbAllocateBuffer ); var returnValue = _AllocateMessage( Self, cbAllocateBuffer );
return returnValue.ToType<NetMsg>(); return returnValue;
} }
#region FunctionMeta #region FunctionMeta

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamParentalSettings : SteamInterface internal unsafe class ISteamParentalSettings : SteamInterface
{ {
internal ISteamParentalSettings( bool IsGameServer ) internal ISteamParentalSettings( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamParties : SteamInterface internal unsafe class ISteamParties : SteamInterface
{ {
internal ISteamParties( bool IsGameServer ) internal ISteamParties( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamRemotePlay : SteamInterface internal unsafe class ISteamRemotePlay : SteamInterface
{ {
internal ISteamRemotePlay( bool IsGameServer ) internal ISteamRemotePlay( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamRemoteStorage : SteamInterface internal unsafe class ISteamRemoteStorage : SteamInterface
{ {
internal ISteamRemoteStorage( bool IsGameServer ) internal ISteamRemoteStorage( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamScreenshots : SteamInterface internal unsafe class ISteamScreenshots : SteamInterface
{ {
internal ISteamScreenshots( bool IsGameServer ) internal ISteamScreenshots( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamUGC : SteamInterface internal unsafe class ISteamUGC : SteamInterface
{ {
internal ISteamUGC( bool IsGameServer ) internal ISteamUGC( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamUser : SteamInterface internal unsafe class ISteamUser : SteamInterface
{ {
internal ISteamUser( bool IsGameServer ) internal ISteamUser( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamUserStats : SteamInterface internal unsafe class ISteamUserStats : SteamInterface
{ {
internal ISteamUserStats( bool IsGameServer ) internal ISteamUserStats( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamUtils : SteamInterface internal unsafe class ISteamUtils : SteamInterface
{ {
internal ISteamUtils( bool IsGameServer ) internal ISteamUtils( bool IsGameServer )

View File

@ -7,7 +7,7 @@ using Steamworks.Data;
namespace Steamworks namespace Steamworks
{ {
internal class ISteamVideo : SteamInterface internal unsafe class ISteamVideo : SteamInterface
{ {
internal ISteamVideo( bool IsGameServer ) internal ISteamVideo( bool IsGameServer )

View File

@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using Steamworks.Data;
namespace Steamworks
{
internal static unsafe class BroadcastBufferManager
{
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( BroadcastBufferManager )} 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(BroadcastBufferManager)} 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 );
public static readonly IntPtr FreeFunctionPointer = Marshal.GetFunctionPointerForDelegate<FreeFn>( Free );
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( BroadcastBufferManager )} 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(BroadcastBufferManager)}: {ptr.ToInt64():X8}" );
return;
}
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose, $"{nameof( BroadcastBufferManager )} decrementing reference count of {ptr.ToInt64():X8}" );
#endif
if ( counter.Decrement() )
{
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose, $"{nameof( BroadcastBufferManager )} freeing {ptr.ToInt64():X8} as it is now unreferenced" );
if ( ptr != counter.Pointer )
{
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Bug,
$"{nameof( BroadcastBufferManager )} 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( BroadcastBufferManager )} 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( BroadcastBufferManager )} 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( BroadcastBufferManager )} allocated new poolable pointer {ptr.ToInt64():X8} (size={size})" );
#endif
return;
}
ptr = bucketPool.Pop();
size = bucketSize;
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BroadcastBufferManager )} 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( BroadcastBufferManager )} 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( BroadcastBufferManager )} pool overflow, freed pooled pointer {ptr.ToInt64():X8} (size={size})" );
#endif
return;
}
bucketPool.Push( ptr );
#if DEBUG
SteamNetworkingUtils.LogDebugMessage( NetDebugOutput.Verbose,
$"{nameof( BroadcastBufferManager )} returned pointer to pool {ptr.ToInt64():X8} (size={size})" );
#endif
}
}
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 <= 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 <= Bucket1Kb ) return 256;
if ( size <= Bucket4Kb ) return 64;
if ( size <= Bucket16Kb ) return 32;
if ( size <= Bucket64Kb ) return 16;
if ( size <= Bucket256Kb ) return 8;
return -1;
}
}
}

View File

@ -120,11 +120,26 @@ namespace Steamworks
int processed = SteamNetworkingSockets.Internal.ReceiveMessagesOnConnection( Connection, new IntPtr( &messageBuffer[0] ), bufferSize ); int processed = SteamNetworkingSockets.Internal.ReceiveMessagesOnConnection( Connection, new IntPtr( &messageBuffer[0] ), bufferSize );
totalProcessed += processed; totalProcessed += processed;
try
{
for ( int i = 0; i < processed; i++ ) for ( int i = 0; i < processed; i++ )
{ {
// TODO: if this throws we will leak the remaining NetMsgs (probably not going to happen much though) ReceiveMessage( ref messageBuffer[i] );
ReceiveMessage( 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 // Keep going if receiveToEnd and we filled the buffer
@ -136,7 +151,77 @@ namespace Steamworks
return totalProcessed; return totalProcessed;
} }
internal unsafe void ReceiveMessage( NetMsg* msg ) public unsafe void Broadcast( Connection[] connections, int connectionCount, IntPtr ptr, int size, SendType sendType = SendType.Reliable )
{
if ( connections == null )
throw new ArgumentNullException( nameof( connections ) );
if ( connectionCount < 0 || connectionCount > connections.Length )
throw new ArgumentException( nameof( connectionCount ) );
if ( connectionCount > 1024 )
throw new ArgumentOutOfRangeException( nameof( connectionCount ) );
if ( ptr == IntPtr.Zero )
throw new ArgumentNullException( nameof( ptr ) );
if ( size == 0 )
throw new ArgumentException( nameof( size ) );
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 = BroadcastBufferManager.Get( size, connectionCount );
Buffer.MemoryCopy( (void*)ptr, (void*)copyPtr, size, size );
var messages = stackalloc NetMsg*[connectionCount];
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 = BroadcastBufferManager.FreeFunctionPointer;
}
SteamNetworkingSockets.Internal.SendMessages( connectionCount, messages, null );
}
/// <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 Broadcast( Connection[] connections, int connectionCount, byte[] data, SendType sendType = SendType.Reliable )
{
fixed ( byte* ptr = data )
{
Broadcast( connections, connectionCount, (IntPtr)ptr, data.Length, sendType );
}
}
/// <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 Broadcast( Connection[] connections, int connectionCount, byte[] data, int offset, int length, SendType sendType = SendType.Reliable )
{
fixed ( byte* ptr = data )
{
Broadcast( connections, connectionCount, (IntPtr)ptr + offset, length, sendType );
}
}
/// <summary>
/// This creates a ton of garbage - so don't do anything with this beyond testing!
/// </summary>
public void Broadcast( Connection[] connections, int connectionCount, string str, SendType sendType = SendType.Reliable )
{
var bytes = System.Text.Encoding.UTF8.GetBytes( str );
Broadcast( connections, connectionCount, bytes, sendType );
}
internal unsafe void ReceiveMessage( ref NetMsg* msg )
{ {
try try
{ {
@ -148,6 +233,7 @@ namespace Steamworks
// Releases the message // Releases the message
// //
NetMsg.InternalRelease( msg ); NetMsg.InternalRelease( msg );
msg = null;
} }
} }

View File

@ -1,5 +1,4 @@
using Steamworks.Data; using System;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Steamworks.Data namespace Steamworks.Data
@ -17,5 +16,7 @@ namespace Steamworks.Data
internal IntPtr FreeDataPtr; internal IntPtr FreeDataPtr;
internal IntPtr ReleasePtr; internal IntPtr ReleasePtr;
internal int Channel; internal int Channel;
internal SendType Flags;
internal long UserData;
} }
} }

View File

@ -271,6 +271,11 @@ namespace Steamworks
debugMessages.Enqueue( new DebugMessage { Type = nType, Msg = Helpers.MemoryToString( str ) } ); debugMessages.Enqueue( new DebugMessage { Type = nType, Msg = Helpers.MemoryToString( str ) } );
} }
internal static void LogDebugMessage( NetDebugOutput type, string message )
{
debugMessages.Enqueue( new DebugMessage { Type = type, Msg = message } );
}
/// <summary> /// <summary>
/// Called regularly from the Dispatch loop so we can provide a timely /// Called regularly from the Dispatch loop so we can provide a timely
/// stream of messages. /// stream of messages.
@ -286,6 +291,11 @@ namespace Steamworks
} }
} }
internal static unsafe NetMsg* AllocateMessage()
{
return Internal.AllocateMessage(0);
}
#region Config Internals #region Config Internals
internal unsafe static bool SetConfigInt( NetConfig type, int value ) internal unsafe static bool SetConfigInt( NetConfig type, int value )

View File

@ -23,7 +23,7 @@ namespace Generator
StartBlock( $"namespace Steamworks" ); StartBlock( $"namespace Steamworks" );
{ {
StartBlock( $"internal class {iface.Name} : SteamInterface" ); StartBlock( $"internal unsafe class {iface.Name} : SteamInterface" );
{ {
WriteLine(); WriteLine();
StartBlock( $"internal {iface.Name}( bool IsGameServer )" ); StartBlock( $"internal {iface.Name}( bool IsGameServer )" );

View File

@ -15,17 +15,27 @@ internal class StructType : BaseType
{ {
public string StructName; public string StructName;
public override string TypeName => StructName; public override string TypeName => IsPointer && TreatAsPointer ? StructName + PointerSuffix : StructName;
public override string TypeNameFrom => NativeType.EndsWith( "*" ) ? "IntPtr" : base.ReturnType; public override string TypeNameFrom => IsPointer && !TreatAsPointer ? "IntPtr" : TypeName;
public override string AsArgument() => IsPointer && TreatAsPointer ? $"{TypeName} {VarName}" : base.AsArgument();
public override string AsCallArgument() => IsPointer && TreatAsPointer ? VarName : base.AsCallArgument();
public bool IsPointer => NativeType.EndsWith( "*" );
public bool TreatAsPointer => StructName == "NetMsg";
public override string Return( string varname ) public override string Return( string varname )
{ {
if ( NativeType.EndsWith( "*" ) ) if ( IsPointer && !TreatAsPointer )
{ {
return $"return {varname}.ToType<{TypeName}>();"; return $"return {varname}.ToType<{TypeName}>();";
} }
return base.Return( varname ); return base.Return( varname );
} }
private string PointerSuffix => new string( '*', NativeType.Count( c => c == '*' ) );
} }