mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-17 10:49:21 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
315 lines
7.4 KiB
C#
315 lines
7.4 KiB
C#
namespace Sandbox.Network;
|
|
|
|
internal class ConnectionInfoManager
|
|
{
|
|
public readonly Dictionary<Guid, ConnectionInfo> All = new();
|
|
public StringTable StringTable { get; } = new( "ConnectionInfo", true );
|
|
public NetworkSystem NetworkSystem { get; }
|
|
|
|
public ConnectionInfoManager( NetworkSystem networkSystem )
|
|
{
|
|
NetworkSystem = networkSystem;
|
|
|
|
StringTable.OnChangeOrAdd = OnTableEntryUpdated;
|
|
StringTable.OnRemoved = OnTableEntryRemoved;
|
|
StringTable.OnSnapshot = OnTableSnapshot;
|
|
StringTable.PostNetworkUpdate = NetworkSystem.OnConnectionInfoUpdated;
|
|
}
|
|
|
|
public ConnectionInfo Add( Connection connection )
|
|
{
|
|
var c = new ConnectionInfo( this )
|
|
{
|
|
ConnectionId = connection.Id,
|
|
State = connection.State
|
|
};
|
|
|
|
All[connection.Id] = c;
|
|
return c;
|
|
}
|
|
|
|
void OnTableEntryUpdated( StringTable.Entry entry )
|
|
{
|
|
if ( !TryParseEntry( entry, out var id, out var key, out var isUserInfo ) )
|
|
return;
|
|
|
|
if ( id == Guid.Empty || string.IsNullOrEmpty( key ) )
|
|
return;
|
|
|
|
if ( !All.TryGetValue( id, out var c ) )
|
|
{
|
|
c = All[id] = new( this ) { ConnectionId = id };
|
|
}
|
|
|
|
c.FromStringTable( key, entry.Data, isUserInfo );
|
|
}
|
|
|
|
void OnTableEntryRemoved( StringTable.Entry entry )
|
|
{
|
|
if ( !TryParseEntry( entry, out var id, out _, out _ ) )
|
|
return;
|
|
|
|
if ( id == Guid.Empty )
|
|
return;
|
|
|
|
All.Remove( id );
|
|
}
|
|
|
|
bool TryParseEntry( StringTable.Entry entry, out Guid id, out string key, out bool isUserInfo )
|
|
{
|
|
id = Guid.Empty;
|
|
key = string.Empty;
|
|
isUserInfo = false;
|
|
string[] split;
|
|
|
|
// This is a UserInfo ConVar...
|
|
if ( entry.Name.Contains( "#" ) )
|
|
{
|
|
isUserInfo = true;
|
|
split = entry.Name.Split( '#' );
|
|
id = Guid.Parse( split[0] );
|
|
key = string.Join( '#', split[1..] );
|
|
|
|
return true;
|
|
}
|
|
|
|
if ( !entry.Name.Contains( ":" ) )
|
|
return false;
|
|
|
|
// This is regular data...
|
|
split = entry.Name.Split( ':' );
|
|
id = Guid.Parse( split[0] );
|
|
key = string.Join( ':', split[1..] );
|
|
|
|
return true;
|
|
}
|
|
|
|
void OnTableSnapshot()
|
|
{
|
|
All.Clear();
|
|
|
|
foreach ( var e in StringTable.Entries.Values )
|
|
{
|
|
OnTableEntryUpdated( e );
|
|
}
|
|
|
|
NetworkSystem.OnConnectionInfoUpdated();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get info for this connection
|
|
/// </summary>
|
|
internal ConnectionInfo Get( Guid id )
|
|
{
|
|
return All.GetValueOrDefault( id );
|
|
}
|
|
|
|
internal void Remove( Guid id )
|
|
{
|
|
if ( All.Remove( id, out var c ) )
|
|
{
|
|
c.Dispose();
|
|
}
|
|
|
|
var entries = StringTable.Entries.Keys
|
|
.Where( c => c.StartsWith( $"{id}:" ) || c.StartsWith( $"{id}#" ) )
|
|
.ToArray();
|
|
|
|
foreach ( var key in entries )
|
|
{
|
|
StringTable.Remove( key );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Information about a connection. The difference between this and the actual connections is that
|
|
/// this can be networked between clients, so all clients have the same information about each other.
|
|
/// This is going to be required in p2p games, where players need to take over hosting from each other.
|
|
/// In a game like Rust, with a dedicated server, this won't need to be networked to other clients.
|
|
/// </summary>
|
|
internal sealed class ConnectionInfo
|
|
{
|
|
private ConnectionInfoManager manager;
|
|
private NetworkSystem NetworkSystem => manager.NetworkSystem;
|
|
|
|
public int Ping { get; private set; }
|
|
public Guid ConnectionId { get; internal set; }
|
|
public string DisplayName { get; internal set; }
|
|
public SteamId SteamId { get; internal set; }
|
|
public DateTimeOffset ConnectionTime { get; internal set; }
|
|
public bool CanRefreshObjects { get; internal set; } = true;
|
|
public bool CanSpawnObjects { get; internal set; } = true;
|
|
public SteamId PartyId { get; internal set; }
|
|
internal Connection.ChannelState State { get; set; }
|
|
|
|
internal ConnectionInfo( ConnectionInfoManager connectionInfoManager )
|
|
{
|
|
manager = connectionInfoManager;
|
|
ConnectionTime = DateTime.UtcNow;
|
|
CanSpawnObjects = Sandbox.ProjectSettings.Networking.ClientsCanSpawnObjects;
|
|
CanRefreshObjects = Sandbox.ProjectSettings.Networking.ClientsCanRefreshObjects;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Key values that come straight from the user. Can't be trusted, they could send anything. Used
|
|
/// for things like preferences, avatar clothing etc.
|
|
/// </summary>
|
|
public Dictionary<string, string> UserData { get; } = new();
|
|
|
|
internal void Update( UserInfo userInfo )
|
|
{
|
|
DisplayName = userInfo.DisplayName;
|
|
SteamId = userInfo.SteamId;
|
|
PartyId = userInfo.PartyId;
|
|
|
|
UserData.Clear();
|
|
|
|
foreach ( var p in userInfo.UserData )
|
|
{
|
|
UserData.Add( p.Key, p.Value );
|
|
}
|
|
|
|
UpdateStringTable();
|
|
}
|
|
|
|
internal void UpdatePing( int ping )
|
|
{
|
|
// This is the case for local mock info
|
|
if ( manager is null )
|
|
return;
|
|
|
|
// Only the host updates the string table
|
|
if ( !NetworkSystem.IsHost )
|
|
return;
|
|
|
|
UpdateStringTable( "ping", ping );
|
|
Ping = ping;
|
|
}
|
|
|
|
internal void UpdateStringTable()
|
|
{
|
|
// This is the case for local mock info
|
|
if ( manager is null )
|
|
return;
|
|
|
|
// Only the host updates the string table
|
|
if ( !NetworkSystem.IsHost )
|
|
return;
|
|
|
|
UpdateStringTable( "ping", Ping );
|
|
UpdateStringTable( "name", DisplayName );
|
|
UpdateStringTable( "state", State );
|
|
UpdateStringTable( "steamid", SteamId );
|
|
UpdateStringTable( "connect", ConnectionTime );
|
|
UpdateStringTable( "canSpawnObjects", CanSpawnObjects );
|
|
UpdateStringTable( "canRefreshObjects", CanRefreshObjects );
|
|
UpdateStringTable( "party", PartyRoom.Current?.Id ?? new( 0 ) );
|
|
|
|
foreach ( var (k, v) in UserData )
|
|
{
|
|
UpdateStringTable( k, v, true );
|
|
}
|
|
}
|
|
|
|
void UpdateStringTable<T>( string key, T value, bool isUserInfo = false )
|
|
{
|
|
var stringTableKey = isUserInfo ? $"{ConnectionId}#{key}" : $"{ConnectionId}:{key}";
|
|
var data = NetworkSystem.TypeLibrary.ToBytes( value );
|
|
|
|
if ( manager.StringTable.Entries.TryGetValue( stringTableKey, out var entry ) )
|
|
{
|
|
// The value is the same, no need to update it.
|
|
if ( data.SequenceEqual( entry.Data ) )
|
|
return;
|
|
}
|
|
|
|
manager.StringTable.Set( stringTableKey, data );
|
|
}
|
|
|
|
internal void FromStringTable( string key, byte[] data, bool isUserInfo )
|
|
{
|
|
var value = NetworkSystem.TypeLibrary.FromBytes<object>( data );
|
|
|
|
if ( isUserInfo )
|
|
{
|
|
UserData[key] = (string)value;
|
|
return;
|
|
}
|
|
|
|
switch ( key )
|
|
{
|
|
case "name":
|
|
DisplayName = (string)value;
|
|
break;
|
|
case "ping":
|
|
Ping = (int)value;
|
|
break;
|
|
case "steamid":
|
|
SteamId = (SteamId)value;
|
|
break;
|
|
case "connect":
|
|
ConnectionTime = (DateTimeOffset)value;
|
|
break;
|
|
case "canSpawnObjects":
|
|
CanSpawnObjects = (bool)value;
|
|
break;
|
|
case "canRefreshObjects":
|
|
CanRefreshObjects = (bool)value;
|
|
break;
|
|
case "party":
|
|
PartyId = (SteamId)value;
|
|
break;
|
|
case "state":
|
|
State = (Connection.ChannelState)value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal void Dispose()
|
|
{
|
|
manager = null;
|
|
}
|
|
|
|
internal void UpdateUserData( Dictionary<string, string> userData )
|
|
{
|
|
UserData.Clear();
|
|
|
|
// tony todo: Can this be one loop?
|
|
foreach ( var (k, v) in userData )
|
|
{
|
|
UserData.Add( k, v );
|
|
}
|
|
|
|
foreach ( var (k, v) in userData )
|
|
{
|
|
UpdateStringTable( k, v, true );
|
|
}
|
|
}
|
|
|
|
internal void SetUserData( string key, string value )
|
|
{
|
|
UpdateStringTable( key, value, true );
|
|
}
|
|
|
|
internal string GetUserData( string key )
|
|
{
|
|
return UserData.GetValueOrDefault( key );
|
|
}
|
|
|
|
/// <summary>
|
|
/// When a user isn't connected to a server, or hosting a server, the
|
|
/// client info table doesn't exist. So we provide info here for the
|
|
/// local connection.
|
|
/// </summary>
|
|
static ConnectionInfo _localMock;
|
|
|
|
internal static ConnectionInfo GetLocalMock()
|
|
{
|
|
_localMock ??= new( null );
|
|
_localMock.Update( UserInfo.Local );
|
|
|
|
return _localMock;
|
|
}
|
|
}
|