mirror of
https://github.com/Facepunch/Facepunch.Steamworks.git
synced 2025-01-24 20:48:03 +03:00
Add ConnectionManager.Broadcast for sending the same message to
multiple clients
This commit is contained in:
parent
47b9aca324
commit
616571a3a9
@ -72,7 +72,7 @@ namespace Steamworks
|
||||
Receive();
|
||||
await Task.Delay( 100 );
|
||||
|
||||
if ( sw.Elapsed.TotalSeconds > 10 )
|
||||
if ( sw.Elapsed.TotalSeconds > 30 )
|
||||
{
|
||||
Assert.Fail( "Client Took Too Long" );
|
||||
break;
|
||||
@ -95,9 +95,11 @@ namespace Steamworks
|
||||
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?" );
|
||||
|
||||
var connections = new[] { Connection };
|
||||
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();
|
||||
|
@ -97,7 +97,7 @@ namespace Steamworks
|
||||
Receive();
|
||||
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" );
|
||||
Assert.Fail( "Socket Took Too Long" );
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamAppList : SteamInterface
|
||||
internal unsafe class ISteamAppList : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamAppList( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamApps : SteamInterface
|
||||
internal unsafe class ISteamApps : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamApps( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamClient : SteamInterface
|
||||
internal unsafe class ISteamClient : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamClient( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamController : SteamInterface
|
||||
internal unsafe class ISteamController : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamController( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamFriends : SteamInterface
|
||||
internal unsafe class ISteamFriends : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamFriends( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamGameSearch : SteamInterface
|
||||
internal unsafe class ISteamGameSearch : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamGameSearch( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamGameServer : SteamInterface
|
||||
internal unsafe class ISteamGameServer : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamGameServer( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamGameServerStats : SteamInterface
|
||||
internal unsafe class ISteamGameServerStats : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamGameServerStats( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamHTMLSurface : SteamInterface
|
||||
internal unsafe class ISteamHTMLSurface : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamHTMLSurface( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamHTTP : SteamInterface
|
||||
internal unsafe class ISteamHTTP : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamHTTP( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamInput : SteamInterface
|
||||
internal unsafe class ISteamInput : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamInput( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamInventory : SteamInterface
|
||||
internal unsafe class ISteamInventory : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamInventory( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMatchmaking : SteamInterface
|
||||
internal unsafe class ISteamMatchmaking : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMatchmaking( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMatchmakingPingResponse : SteamInterface
|
||||
internal unsafe class ISteamMatchmakingPingResponse : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMatchmakingPingResponse( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMatchmakingPlayersResponse : SteamInterface
|
||||
internal unsafe class ISteamMatchmakingPlayersResponse : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMatchmakingPlayersResponse( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMatchmakingRulesResponse : SteamInterface
|
||||
internal unsafe class ISteamMatchmakingRulesResponse : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMatchmakingRulesResponse( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMatchmakingServerListResponse : SteamInterface
|
||||
internal unsafe class ISteamMatchmakingServerListResponse : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMatchmakingServerListResponse( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMatchmakingServers : SteamInterface
|
||||
internal unsafe class ISteamMatchmakingServers : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMatchmakingServers( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMusic : SteamInterface
|
||||
internal unsafe class ISteamMusic : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMusic( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamMusicRemote : SteamInterface
|
||||
internal unsafe class ISteamMusicRemote : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamMusicRemote( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamNetworking : SteamInterface
|
||||
internal unsafe class ISteamNetworking : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamNetworking( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamNetworkingMessages : SteamInterface
|
||||
internal unsafe class ISteamNetworkingMessages : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamNetworkingMessages( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamNetworkingSockets : SteamInterface
|
||||
internal unsafe class ISteamNetworkingSockets : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamNetworkingSockets( bool IsGameServer )
|
||||
@ -162,12 +162,12 @@ namespace Steamworks
|
||||
|
||||
#region FunctionMeta
|
||||
[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
|
||||
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
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamNetworkingUtils : SteamInterface
|
||||
internal unsafe class ISteamNetworkingUtils : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamNetworkingUtils( bool IsGameServer )
|
||||
@ -22,13 +22,13 @@ namespace Steamworks
|
||||
|
||||
#region FunctionMeta
|
||||
[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
|
||||
internal NetMsg AllocateMessage( int cbAllocateBuffer )
|
||||
internal NetMsg* AllocateMessage( int cbAllocateBuffer )
|
||||
{
|
||||
var returnValue = _AllocateMessage( Self, cbAllocateBuffer );
|
||||
return returnValue.ToType<NetMsg>();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
#region FunctionMeta
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamParentalSettings : SteamInterface
|
||||
internal unsafe class ISteamParentalSettings : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamParentalSettings( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamParties : SteamInterface
|
||||
internal unsafe class ISteamParties : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamParties( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamRemotePlay : SteamInterface
|
||||
internal unsafe class ISteamRemotePlay : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamRemotePlay( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamRemoteStorage : SteamInterface
|
||||
internal unsafe class ISteamRemoteStorage : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamRemoteStorage( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamScreenshots : SteamInterface
|
||||
internal unsafe class ISteamScreenshots : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamScreenshots( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamUGC : SteamInterface
|
||||
internal unsafe class ISteamUGC : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamUGC( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamUser : SteamInterface
|
||||
internal unsafe class ISteamUser : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamUser( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamUserStats : SteamInterface
|
||||
internal unsafe class ISteamUserStats : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamUserStats( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamUtils : SteamInterface
|
||||
internal unsafe class ISteamUtils : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamUtils( bool IsGameServer )
|
||||
|
@ -7,7 +7,7 @@ using Steamworks.Data;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal class ISteamVideo : SteamInterface
|
||||
internal unsafe class ISteamVideo : SteamInterface
|
||||
{
|
||||
|
||||
internal ISteamVideo( bool IsGameServer )
|
||||
|
277
Facepunch.Steamworks/Networking/BroadcastBufferManager.cs
Normal file
277
Facepunch.Steamworks/Networking/BroadcastBufferManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -120,11 +120,26 @@ namespace Steamworks
|
||||
int processed = SteamNetworkingSockets.Internal.ReceiveMessagesOnConnection( Connection, new IntPtr( &messageBuffer[0] ), bufferSize );
|
||||
totalProcessed += processed;
|
||||
|
||||
try
|
||||
{
|
||||
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( messageBuffer[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
|
||||
@ -136,7 +151,77 @@ namespace Steamworks
|
||||
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
|
||||
{
|
||||
@ -148,6 +233,7 @@ namespace Steamworks
|
||||
// Releases the message
|
||||
//
|
||||
NetMsg.InternalRelease( msg );
|
||||
msg = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Steamworks.Data;
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Steamworks.Data
|
||||
@ -17,5 +16,7 @@ namespace Steamworks.Data
|
||||
internal IntPtr FreeDataPtr;
|
||||
internal IntPtr ReleasePtr;
|
||||
internal int Channel;
|
||||
internal SendType Flags;
|
||||
internal long UserData;
|
||||
}
|
||||
}
|
@ -271,6 +271,11 @@ namespace Steamworks
|
||||
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>
|
||||
/// Called regularly from the Dispatch loop so we can provide a timely
|
||||
/// stream of messages.
|
||||
@ -286,6 +291,11 @@ namespace Steamworks
|
||||
}
|
||||
}
|
||||
|
||||
internal static unsafe NetMsg* AllocateMessage()
|
||||
{
|
||||
return Internal.AllocateMessage(0);
|
||||
}
|
||||
|
||||
#region Config Internals
|
||||
|
||||
internal unsafe static bool SetConfigInt( NetConfig type, int value )
|
||||
|
@ -23,7 +23,7 @@ namespace Generator
|
||||
|
||||
StartBlock( $"namespace Steamworks" );
|
||||
{
|
||||
StartBlock( $"internal class {iface.Name} : SteamInterface" );
|
||||
StartBlock( $"internal unsafe class {iface.Name} : SteamInterface" );
|
||||
{
|
||||
WriteLine();
|
||||
StartBlock( $"internal {iface.Name}( bool IsGameServer )" );
|
||||
|
@ -15,17 +15,27 @@ internal class StructType : BaseType
|
||||
{
|
||||
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 )
|
||||
{
|
||||
if ( NativeType.EndsWith( "*" ) )
|
||||
if ( IsPointer && !TreatAsPointer )
|
||||
{
|
||||
return $"return {varname}.ToType<{TypeName}>();";
|
||||
}
|
||||
|
||||
return base.Return( varname );
|
||||
}
|
||||
|
||||
private string PointerSuffix => new string( '*', NativeType.Count( c => c == '*' ) );
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user