mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-07 22:08:34 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
466 lines
11 KiB
C#
466 lines
11 KiB
C#
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using Sandbox.Network;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// A connection, usually to a server or a client.
|
|
/// </summary>
|
|
[Expose, ActionGraphIgnore]
|
|
public abstract partial class Connection
|
|
{
|
|
internal abstract void InternalSend( ByteStream stream, NetFlags flags );
|
|
internal abstract void InternalRecv( NetworkSystem.MessageHandler handler );
|
|
internal abstract void InternalClose( int closeCode, string closeReason );
|
|
|
|
/// <summary>
|
|
/// Called when we receive <see cref="ServerInfo"/> from this <see cref="Connection"/>.
|
|
/// </summary>
|
|
/// <param name="userInfo">Our outgoing <see cref="UserInfo"/> data.</param>
|
|
/// <param name="serverInfo"></param>
|
|
/// <returns>Whether or not we want to continue to connect.</returns>
|
|
internal virtual bool OnReceiveServerInfo( ref UserInfo userInfo, ServerInfo serverInfo )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when we receive <see cref="UserInfo"/> from this <see cref="Connection"/>.
|
|
/// </summary>
|
|
/// <param name="info"></param>
|
|
/// <returns>Whether or not we want to allow this connection</returns>
|
|
internal virtual bool OnReceiveUserInfo( UserInfo info )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get whether this connection has a specific permission.
|
|
/// </summary>
|
|
public virtual bool HasPermission( string permission )
|
|
{
|
|
// The host has every permission by default.
|
|
return IsHost;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This connection's unique identifier.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public Guid Id { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// A unique identifier that is set when the connection starts handshaking. This identifier will
|
|
/// be passed into all handshake messages, so that if a new handshaking process starts while one
|
|
/// is already active, old handshake messages will be ignored.
|
|
/// </summary>
|
|
internal Guid HandshakeId { get; set; }
|
|
|
|
/// <summary>
|
|
/// The <see cref="NetworkSystem"/> this connection belongs to.
|
|
/// </summary>
|
|
internal NetworkSystem System { get; set; }
|
|
|
|
/// <summary>
|
|
/// An array of Pvs sources from this connection.
|
|
/// </summary>
|
|
internal Vector3[] VisibilityOrigins = [];
|
|
|
|
/// <summary>
|
|
/// Calculate the closest distance (squared) to a position based on the Pvs sources from
|
|
/// this <see cref="Connection"/>.
|
|
/// </summary>
|
|
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
|
public float DistanceSquared( Vector3 position )
|
|
{
|
|
if ( VisibilityOrigins == null || VisibilityOrigins.Length == 0 )
|
|
return float.PositiveInfinity;
|
|
|
|
var minSq = float.PositiveInfinity;
|
|
var sources = VisibilityOrigins;
|
|
var count = sources.Length;
|
|
|
|
for ( var i = 0; i < count; i++ )
|
|
{
|
|
var source = VisibilityOrigins[i];
|
|
var distance = source.DistanceSquared( position );
|
|
|
|
if ( distance > minSq )
|
|
continue;
|
|
|
|
if ( distance == 0f )
|
|
return 0f;
|
|
|
|
minSq = distance;
|
|
}
|
|
|
|
return minSq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the closest distance to a position based on the Pvs sources from
|
|
/// this <see cref="Connection"/>.
|
|
/// </summary>
|
|
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
|
internal float Distance( Vector3 position )
|
|
{
|
|
return MathF.Sqrt( DistanceSquared( position ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Can this connection spawn networked objects?
|
|
/// </summary>
|
|
public bool CanSpawnObjects
|
|
{
|
|
get => IsHost || (Info?.CanSpawnObjects ?? true);
|
|
set
|
|
{
|
|
Assert.True( Networking.IsHost );
|
|
|
|
var info = Info;
|
|
if ( info is null ) return;
|
|
if ( info.CanSpawnObjects == value ) return;
|
|
|
|
info.CanSpawnObjects = value;
|
|
info.UpdateStringTable();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Can this connection refresh networked objects that they own?
|
|
/// </summary>
|
|
public bool CanRefreshObjects
|
|
{
|
|
get => IsHost || (Info?.CanRefreshObjects ?? true);
|
|
set
|
|
{
|
|
Assert.True( Networking.IsHost );
|
|
|
|
var info = Info;
|
|
if ( info is null ) return;
|
|
if ( info.CanRefreshObjects == value ) return;
|
|
|
|
info.CanRefreshObjects = value;
|
|
info.UpdateStringTable();
|
|
}
|
|
}
|
|
|
|
public virtual float Latency => 0;
|
|
|
|
[ActionGraphInclude]
|
|
public virtual string Name => "Unnammed";
|
|
|
|
[ActionGraphInclude]
|
|
public virtual float Time => 0.0f;
|
|
|
|
[ActionGraphInclude]
|
|
public virtual string Address => "unknown";
|
|
|
|
[ActionGraphInclude]
|
|
public virtual bool IsHost => false;
|
|
|
|
/// <summary>
|
|
/// True if this channel is still currently connecting.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool IsConnecting => State <= ChannelState.Snapshot;
|
|
|
|
/// <summary>
|
|
/// True if this channel is fully connnected and fully logged on.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public bool IsActive => State == ChannelState.Connected;
|
|
|
|
private int _messagesSent;
|
|
private int _messagesReceived;
|
|
|
|
/// <summary>
|
|
/// How many messages have been sent to this connection?
|
|
/// </summary>
|
|
public int MessagesSent
|
|
{
|
|
get => Interlocked.CompareExchange( ref _messagesSent, 0, 0 );
|
|
internal set => Interlocked.Exchange( ref _messagesSent, value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// How many messages have been received from this connection?
|
|
/// </summary>
|
|
public int MessagesRecieved
|
|
{
|
|
get => Interlocked.CompareExchange( ref _messagesReceived, 0, 0 );
|
|
internal set => Interlocked.Exchange( ref _messagesReceived, value );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kick this <see cref="Connection"/> from the server. Only the host can kick clients.
|
|
/// </summary>
|
|
/// <param name="reason">The reason to display to this client.</param>
|
|
public virtual void Kick( string reason )
|
|
{
|
|
Assert.NotNull( System );
|
|
|
|
if ( !System.IsHost )
|
|
return;
|
|
|
|
if ( string.IsNullOrWhiteSpace( reason ) )
|
|
reason = "Kicked";
|
|
|
|
// TODO: do we immediately close this connection locally
|
|
// instead of waiting for a disconnection message?
|
|
|
|
SendMessage( new KickMsg
|
|
{
|
|
Reason = reason
|
|
} );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Log a message to the console for this connection.
|
|
/// </summary>
|
|
public void SendLog( LogLevel level, string message )
|
|
{
|
|
if ( Local == this || System is null )
|
|
{
|
|
switch ( level )
|
|
{
|
|
case LogLevel.Info:
|
|
Log.Info( message );
|
|
break;
|
|
case LogLevel.Warn:
|
|
Log.Warning( message );
|
|
break;
|
|
case LogLevel.Error:
|
|
Log.Error( message );
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var msg = new LogMsg { Level = (byte)level, Message = message };
|
|
SendMessage( msg, NetFlags.Reliable );
|
|
}
|
|
|
|
internal void InitializeSystem( NetworkSystem parent )
|
|
{
|
|
System = parent;
|
|
}
|
|
|
|
internal void SendMessage<T>( InternalMessageType type, T t )
|
|
{
|
|
Assert.NotNull( System );
|
|
|
|
var msg = ByteStream.Create( 256 );
|
|
msg.Write( type );
|
|
|
|
System.Serialize( t, ref msg );
|
|
|
|
SendRawMessage( msg );
|
|
msg.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get stats about this connection such as bandwidth usage and how many packets are being
|
|
/// sent and received.
|
|
/// </summary>
|
|
public virtual ConnectionStats Stats => default;
|
|
|
|
/// <summary>
|
|
/// Send a message to this connection.
|
|
/// </summary>
|
|
public void SendMessage<T>( T t )
|
|
{
|
|
SendMessage( t, NetFlags.Reliable );
|
|
}
|
|
|
|
internal void SendMessage<T>( T t, NetFlags flags )
|
|
{
|
|
Assert.NotNull( System );
|
|
|
|
var msg = ByteStream.Create( 256 );
|
|
msg.Write( InternalMessageType.Packed );
|
|
|
|
System.Serialize( t, ref msg );
|
|
|
|
SendRawMessage( msg, flags );
|
|
msg.Dispose();
|
|
}
|
|
|
|
internal virtual void SendRawMessage( ByteStream stream, NetFlags flags = NetFlags.Reliable )
|
|
{
|
|
// Note: this is basically quater of k_cbMaxSteamNetworkingSocketsMessageSizeSend
|
|
var maxChunkSize = 128 * 1024;
|
|
var isReliableMessage = (flags & NetFlags.Reliable) != 0;
|
|
|
|
if ( !isReliableMessage || stream.Length < maxChunkSize )
|
|
{
|
|
InternalSend( stream, flags );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Split messages into multiple parts, this should hardly ever happen.
|
|
//
|
|
|
|
var chunkHeader = 32;
|
|
var chunks = (stream.Length / (float)maxChunkSize).CeilToInt();
|
|
|
|
Log.Trace( $"splitting {stream.Length} bytes into {chunks} {maxChunkSize}b chunks" );
|
|
|
|
for ( int i = 0; i < chunks; i++ )
|
|
{
|
|
using ByteStream chunkMessage = ByteStream.Create( maxChunkSize + chunkHeader );
|
|
chunkMessage.Write( InternalMessageType.Chunk );
|
|
chunkMessage.Write( (uint)i );
|
|
chunkMessage.Write( (uint)chunks );
|
|
chunkMessage.Write( stream, i * maxChunkSize, maxChunkSize );
|
|
|
|
Log.Trace( $"Chunk {i + 1} is {chunkMessage.Length}b" );
|
|
|
|
InternalSend( chunkMessage, flags );
|
|
|
|
chunkMessage.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is called on a worker thread and should handle any threaded processing of messages.
|
|
/// </summary>
|
|
internal virtual void ProcessMessagesInThread()
|
|
{
|
|
|
|
}
|
|
|
|
internal void GetIncomingMessages( NetworkSystem.MessageHandler handler )
|
|
{
|
|
InternalRecv( handler );
|
|
}
|
|
|
|
internal void Close( int reasonCode, string reasonString )
|
|
{
|
|
InternalClose( reasonCode, reasonString );
|
|
}
|
|
|
|
ChannelState InternalState { get; set; }
|
|
|
|
/// <summary>
|
|
/// Current internal progression of this connection.
|
|
/// </summary>
|
|
internal ChannelState State
|
|
{
|
|
get
|
|
{
|
|
return Info?.State ?? InternalState;
|
|
}
|
|
set
|
|
{
|
|
InternalState = value;
|
|
|
|
var info = Info;
|
|
if ( info is null ) return;
|
|
if ( info.State == value ) return;
|
|
|
|
info.State = value;
|
|
info.UpdateStringTable();
|
|
}
|
|
}
|
|
|
|
internal enum ChannelState
|
|
{
|
|
Unconnected,
|
|
LoadingServerInformation,
|
|
Welcome,
|
|
MountVPKs,
|
|
Snapshot,
|
|
Connected,
|
|
}
|
|
|
|
public override string ToString() => Name;
|
|
|
|
/// <summary>
|
|
/// Generate an ID for this connection. This is called by the server to allocate
|
|
/// the connection an identifier. We're avoiding sequential, allocated ids because
|
|
/// who needs to deal with that bullshit.
|
|
/// </summary>
|
|
internal void GenerateConnectionId()
|
|
{
|
|
Id = Guid.NewGuid();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update this channel's info. Usually called from the host.
|
|
/// </summary>
|
|
internal void UpdateFrom( ChannelInfo host )
|
|
{
|
|
Id = host.Id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called once a second.
|
|
/// </summary>
|
|
internal virtual void Tick( NetworkSystem parent )
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// The ping of this connection (in milliseconds.)
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public float Ping => Info?.Ping ?? 0f;
|
|
|
|
/// <summary>
|
|
/// The server has worked out the round trip time on a connection using the heartbeat.
|
|
/// We want to keep a sliding window of this timing and use it to predict the latency.
|
|
/// </summary>
|
|
internal void UpdateRtt( float rtt )
|
|
{
|
|
Info?.UpdatePing( (rtt * 1000f * 0.5f).CeilToInt() );
|
|
}
|
|
|
|
ConnectionInfo Info => FindConnectionInfo( Id ) ?? PreInfo;
|
|
|
|
/// <summary>
|
|
/// The connection info before connection is added
|
|
/// </summary>
|
|
internal ConnectionInfo PreInfo { get; set; }
|
|
|
|
[ActionGraphInclude]
|
|
public string DisplayName => Info?.DisplayName ?? "Unknown Player";
|
|
|
|
[ActionGraphInclude]
|
|
public SteamId SteamId => Info?.SteamId ?? default;
|
|
|
|
/// <summary>
|
|
/// The Id of the party that this user is a part of. This can be used to compare to other users to
|
|
/// group them into parties.
|
|
/// </summary>
|
|
[ActionGraphInclude]
|
|
public SteamId PartyId => Info?.PartyId ?? default;
|
|
|
|
public DateTimeOffset ConnectionTime => Info?.ConnectionTime ?? default;
|
|
|
|
[ActionGraphInclude]
|
|
public string GetUserData( string key ) => Info?.GetUserData( key ) ?? default;
|
|
|
|
/// <summary>
|
|
/// New, updated UserInfo data arrived. Replace our old data with this.
|
|
/// </summary>
|
|
internal void UpdateUserData( Dictionary<string, string> userData )
|
|
{
|
|
Info?.UpdateUserData( userData );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set or update an individual UserInfo data key.
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <param name="value"></param>
|
|
internal void SetUserData( string key, string value )
|
|
{
|
|
Info?.SetUserData( key, value );
|
|
}
|
|
}
|