Files
sbox-public/engine/Sandbox.Engine/Systems/Networking/Tables/StringTable.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

237 lines
4.7 KiB
C#

using System.Runtime.InteropServices;
using System.Text.Json;
namespace Sandbox.Network;
internal class StringTable
{
/// <summary>
/// The maximum size of a snapshot entry in megabytes.
/// </summary>
private const int MaxSnapshotEntrySize = 8;
public readonly Dictionary<string, Entry> Entries = new();
public string Name { get; }
public bool Compressed { get; }
public bool HasChanged => changed.Count > 0 || removed.Count > 0;
/// <summary>
/// Called when a table entry is changed or added from the network
/// </summary>
public Action<Entry> OnChangeOrAdd;
/// <summary>
/// The table has been fully replaced. Anything or everything could have changed.
/// </summary>
public Action OnSnapshot;
/// <summary>
/// Called when a table entry is removed by the network
/// </summary>
public Action<Entry> OnRemoved;
/// <summary>
/// Called after a network update of the table is complete. Not called after snapshots!
/// </summary>
public Action PostNetworkUpdate;
readonly HashSet<string> changed = new();
readonly HashSet<string> removed = new();
public StringTable( string name, bool compressed )
{
Name = name;
Compressed = compressed;
}
internal Entry Remove( string name )
{
if ( !Entries.Remove( name, out var e ) )
return default;
Log.Trace( $"Removed Key {name}" );
removed.Add( name );
changed.Remove( name );
return e;
}
internal void Reset()
{
Log.Trace( "Table Reset" );
Entries.Clear();
removed.Clear();
changed.Clear();
}
internal Entry Set<T>( string name, T assemblyBytes ) where T : unmanaged
{
var size = Marshal.SizeOf<T>();
var arr = new byte[size];
MemoryMarshal.Write( arr, assemblyBytes );
return Set( name, arr );
}
internal Entry Set( string name, byte[] assemblyBytes )
{
if ( Entries.TryGetValue( name, out var entry ) )
{
entry.Data = assemblyBytes;
Log.Trace( $"Updated {name} [{assemblyBytes.Length}]" );
}
else
{
entry = new Entry { Name = name, Data = assemblyBytes };
Entries[name] = entry;
Log.Trace( $"Added {name} [{assemblyBytes.Length}]" );
}
changed.Add( name );
removed.Remove( name );
return entry;
}
public class Entry
{
public string Name { get; set; }
public byte[] Data { get; set; }
/// <summary>
/// Read an unmanaged struct from the data
/// </summary>
internal T Read<T>() where T : unmanaged
{
var bytes = ByteStream.CreateReader( Data );
return bytes.Read<T>();
}
internal string ReadAsString()
{
return System.Text.Encoding.UTF8.GetString( Data );
}
internal T ReadJson<T>()
{
var json = ReadAsString();
if ( string.IsNullOrWhiteSpace( json ) ) return default;
return JsonSerializer.Deserialize<T>( json );
}
}
internal void SendSnapshot( Connection source )
{
var bs = ByteStream.Create( 1024 );
bs.Write( InternalMessageType.TableSnapshot );
bs.Write( Name );
BuildSnapshotMessage( ref bs );
source.SendRawMessage( bs );
bs.Dispose();
}
internal void ReadSnapshot( ByteStream bs )
{
while ( true )
{
var key = bs.Read<string>();
if ( key == null ) break;
var data = bs.ReadArraySpan<byte>( 1024 * 1024 * MaxSnapshotEntrySize );
Set( key, data.ToArray() );
}
OnSnapshot?.Invoke();
}
internal void BuildSnapshotMessage( ref ByteStream bs )
{
foreach ( var entry in Entries )
{
bs.Write( entry.Value.Name );
bs.WriteArray( entry.Value.Data );
}
bs.Write( (string)null );
}
internal void BuildUpdateMessage( ref ByteStream bs )
{
bs.Write( removed.Count );
bs.Write( changed.Count );
foreach ( var entry in removed )
{
bs.Write( entry );
}
foreach ( var entry in changed )
{
var e = Entries[entry];
bs.Write( e.Name );
bs.Write( e.Data.Length );
bs.Write( e.Data );
}
}
internal void ReadUpdate( ByteStream bs )
{
var removed = bs.Read<int>();
var changed = bs.Read<int>();
for ( var i = 0; i < removed; i++ )
{
var e = Remove( bs.Read<string>() );
if ( e is not null )
{
OnRemoved?.Invoke( e );
}
}
for ( var i = 0; i < changed; i++ )
{
var key = bs.Read<string>();
var len = bs.Read<int>();
if ( len == 0 )
{
var e = Set( key, [] );
if ( e is not null )
{
OnChangeOrAdd?.Invoke( e );
}
}
else
{
using var data = bs.ReadByteStream( len );
var e = Set( key, data.ToArray() );
if ( e is not null )
{
OnChangeOrAdd?.Invoke( e );
}
}
}
PostNetworkUpdate?.Invoke();
}
internal void ClearChanges()
{
changed.Clear();
removed.Clear();
}
internal Entry SetJson<T>( string key, T value )
{
var json = JsonSerializer.Serialize( value );
return SetString( key, json );
}
internal Entry SetString( string key, string value )
{
return Set( key, System.Text.Encoding.UTF8.GetBytes( value ) );
}
}