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();
|
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();
|
||||||
|
@ -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" );
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -109,34 +109,119 @@ namespace Steamworks
|
|||||||
}
|
}
|
||||||
|
|
||||||
public unsafe int Receive( int bufferSize = 32, bool receiveToEnd = true )
|
public unsafe int Receive( int bufferSize = 32, bool receiveToEnd = true )
|
||||||
{
|
{
|
||||||
if ( bufferSize < 1 || bufferSize > 256 ) throw new ArgumentOutOfRangeException( nameof( bufferSize ) );
|
if ( bufferSize < 1 || bufferSize > 256 ) throw new ArgumentOutOfRangeException( nameof( bufferSize ) );
|
||||||
|
|
||||||
int totalProcessed = 0;
|
int totalProcessed = 0;
|
||||||
NetMsg** messageBuffer = stackalloc NetMsg*[bufferSize];
|
NetMsg** messageBuffer = stackalloc NetMsg*[bufferSize];
|
||||||
|
|
||||||
while ( true )
|
while ( true )
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
||||||
for ( int i = 0; i < processed; i++ )
|
try
|
||||||
{
|
{
|
||||||
// TODO: if this throws we will leak the remaining NetMsgs (probably not going to happen much though)
|
for ( int i = 0; i < processed; i++ )
|
||||||
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
|
}
|
||||||
//
|
|
||||||
if ( !receiveToEnd || processed < bufferSize )
|
|
||||||
break;
|
//
|
||||||
|
// Keep going if receiveToEnd and we filled the buffer
|
||||||
|
//
|
||||||
|
if ( !receiveToEnd || processed < bufferSize )
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 )
|
||||||
|
@ -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 )" );
|
||||||
|
@ -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 Return( string varname )
|
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 $"return {varname}.ToType<{TypeName}>();";
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.Return( varname );
|
return base.Return( varname );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string PointerSuffix => new string( '*', NativeType.Count( c => c == '*' ) );
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user