mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-02 11:28:19 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
308 lines
6.0 KiB
C#
308 lines
6.0 KiB
C#
using Sandbox.Network;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// A network table that can be used with <see cref="SyncAttribute">[Sync]</see>. It will be serialized
|
|
/// fully for clients when they first need it, and then it will be sent as delta snapshots when individual
|
|
/// entries change.
|
|
/// </summary>
|
|
internal class NetworkTable<T> : INetworkDeltaSnapshot, INetworkProperty, INetworkSerializer
|
|
{
|
|
protected Dictionary<int, byte[]> Serialized { get; set; } = new();
|
|
protected Dictionary<int, T> Table { get; set; } = new();
|
|
|
|
protected byte[] SerializedKeys { get; set; }
|
|
protected HashSet<int> Keys { get; set; } = new();
|
|
|
|
private bool CanWriteChanges() => !Parent?.IsProxy ?? true;
|
|
private readonly Dictionary<int, int> _subSlotHashes = new();
|
|
private HashSet<int> KeysToRemove { get; set; } = [];
|
|
|
|
protected int _parentSlot;
|
|
protected int _keysHash;
|
|
|
|
/// <summary>
|
|
/// Read-only access for all entries in the network table.
|
|
/// </summary>
|
|
public IReadOnlyDictionary<int, T> Entries => Table;
|
|
|
|
/// <summary>
|
|
/// Clear the network table.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
if ( !CanWriteChanges() )
|
|
return;
|
|
|
|
InternalClear();
|
|
}
|
|
|
|
void InternalClear()
|
|
{
|
|
_subSlotHashes.Clear();
|
|
SerializedKeys = [];
|
|
Table.Clear();
|
|
Serialized.Clear();
|
|
Keys.Clear();
|
|
|
|
OnCleared();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the value of an entry in the specified slot.
|
|
/// </summary>
|
|
public void Set( int slot, T value )
|
|
{
|
|
if ( EqualityComparer<T>.Default.Equals( Get( slot ), value ) )
|
|
return;
|
|
|
|
if ( !CanWriteChanges() )
|
|
return;
|
|
|
|
Serialized[slot] = Game.TypeLibrary.ToBytes( value );
|
|
Table[slot] = value;
|
|
|
|
OnValueChanged( slot, value );
|
|
|
|
if ( !Keys.Add( slot ) )
|
|
return;
|
|
|
|
WriteSerializedKeys();
|
|
UpdateSubSlotHash( slot );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value of an entry in the specified slot.
|
|
/// </summary>
|
|
public T Get( int slot )
|
|
{
|
|
return Table.GetValueOrDefault( slot );
|
|
}
|
|
|
|
void WriteSerializedKeys()
|
|
{
|
|
var bs = ByteStream.Create( 1024 );
|
|
bs.Write( Keys.Count );
|
|
|
|
foreach ( var key in Keys )
|
|
{
|
|
bs.Write( key );
|
|
}
|
|
|
|
SerializedKeys = bs.ToArray();
|
|
}
|
|
|
|
void UpdateSubSlotHash( int slot )
|
|
{
|
|
if ( _parentSlot == 0 )
|
|
return;
|
|
|
|
_subSlotHashes[slot] = $"{_parentSlot}.{slot}".FastHash();
|
|
}
|
|
|
|
void ReadSerializedKeys( byte[] data )
|
|
{
|
|
if ( data.Length == 0 )
|
|
{
|
|
SerializedKeys = data;
|
|
Keys.Clear();
|
|
return;
|
|
}
|
|
|
|
var reader = ByteStream.CreateReader( data );
|
|
var count = reader.Read<int>();
|
|
|
|
Keys.Clear();
|
|
|
|
for ( var i = 0; i < count; i++ )
|
|
{
|
|
Keys.Add( reader.Read<int>() );
|
|
}
|
|
|
|
SerializedKeys = data;
|
|
}
|
|
|
|
private INetworkProxy Parent { get; set; }
|
|
|
|
void INetworkProperty.Init( int slot, INetworkProxy parent )
|
|
{
|
|
Parent = parent;
|
|
|
|
_parentSlot = slot;
|
|
_keysHash = $"{slot}.keys".FastHash();
|
|
|
|
foreach ( var key in Keys )
|
|
{
|
|
_subSlotHashes[key] = $"{slot}.{key}".FastHash();
|
|
}
|
|
|
|
OnInit( slot );
|
|
}
|
|
|
|
protected virtual void WriteSnapshot( int slot, LocalSnapshotState snapshot )
|
|
{
|
|
foreach ( var entry in Serialized )
|
|
{
|
|
var subSlot = _subSlotHashes.GetValueOrDefault( entry.Key );
|
|
|
|
if ( subSlot == 0 )
|
|
{
|
|
subSlot = $"{_parentSlot}.{entry.Key}".FastHash();
|
|
_subSlotHashes[entry.Key] = subSlot;
|
|
}
|
|
|
|
snapshot.AddSerialized( subSlot, entry.Value );
|
|
}
|
|
}
|
|
|
|
void INetworkDeltaSnapshot.WriteSnapshotState( int slot, LocalSnapshotState snapshot )
|
|
{
|
|
if ( SerializedKeys is not null )
|
|
{
|
|
snapshot.AddSerialized( _keysHash, SerializedKeys );
|
|
}
|
|
|
|
WriteSnapshot( slot, snapshot );
|
|
}
|
|
|
|
protected virtual void ReadSnapshot( int slot, DeltaSnapshot snapshot )
|
|
{
|
|
foreach ( var key in Keys )
|
|
{
|
|
var subSlot = _subSlotHashes.GetValueOrDefault( key );
|
|
|
|
if ( subSlot == 0 )
|
|
{
|
|
subSlot = $"{_parentSlot}.{key}".FastHash();
|
|
_subSlotHashes[key] = subSlot;
|
|
}
|
|
|
|
if ( !snapshot.TryGetValue<T>( subSlot, out var value ) )
|
|
continue;
|
|
|
|
Serialized[key] = snapshot.Lookup[subSlot].Value;
|
|
Table[key] = value;
|
|
}
|
|
}
|
|
|
|
void INetworkDeltaSnapshot.ReadSnapshot( int slot, DeltaSnapshot snapshot )
|
|
{
|
|
if ( snapshot.Lookup.TryGetValue( _keysHash, out var keys ) )
|
|
{
|
|
ReadSerializedKeys( keys.Value );
|
|
}
|
|
|
|
ReadSnapshot( slot, snapshot );
|
|
|
|
PruneRemovedEntries();
|
|
}
|
|
|
|
void PruneRemovedEntries()
|
|
{
|
|
foreach ( var entry in Table.Where( entry => !Keys.Contains( entry.Key ) ) )
|
|
{
|
|
KeysToRemove.Add( entry.Key );
|
|
}
|
|
|
|
if ( KeysToRemove.Count == 0 )
|
|
return;
|
|
|
|
foreach ( var key in KeysToRemove )
|
|
{
|
|
OnKeyRemoved( key );
|
|
_subSlotHashes.Remove( key );
|
|
Serialized.Remove( key );
|
|
Table.Remove( key );
|
|
}
|
|
|
|
KeysToRemove.Clear();
|
|
}
|
|
|
|
void INetworkSerializer.WriteChanged( ref ByteStream data )
|
|
{
|
|
|
|
}
|
|
|
|
void INetworkSerializer.WriteAll( ref ByteStream data )
|
|
{
|
|
data.Write( Serialized.Count );
|
|
|
|
foreach ( var entry in Serialized )
|
|
{
|
|
data.Write( entry.Key );
|
|
data.Write( entry.Value.Length );
|
|
data.Write( entry.Value );
|
|
}
|
|
}
|
|
|
|
void INetworkSerializer.Read( ref ByteStream data )
|
|
{
|
|
var count = data.Read<int>();
|
|
|
|
if ( count <= 0 )
|
|
{
|
|
InternalClear();
|
|
return;
|
|
}
|
|
|
|
for ( var i = 0; i < count; i++ )
|
|
{
|
|
var key = data.Read<int>();
|
|
var byteCount = data.Read<int>();
|
|
|
|
var bytes = new byte[byteCount];
|
|
data.Read( bytes, 0, byteCount );
|
|
|
|
var value = Game.TypeLibrary.FromBytes<T>( bytes );
|
|
|
|
Serialized[key] = bytes;
|
|
Table[key] = value;
|
|
|
|
OnValueChanged( key, value );
|
|
|
|
if ( !Keys.Add( key ) )
|
|
continue;
|
|
|
|
UpdateSubSlotHash( key );
|
|
}
|
|
|
|
WriteSerializedKeys();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when initialized from a parent network table.
|
|
/// </summary>
|
|
/// <param name="parentSlot"></param>
|
|
protected virtual void OnInit( int parentSlot )
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called whenever a key is removed from the table.
|
|
/// </summary>
|
|
protected virtual void OnKeyRemoved( int key )
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called whenever a value changes in a specific slot.
|
|
/// </summary>
|
|
protected virtual void OnValueChanged( int slot, T value )
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the network table is cleared.
|
|
/// </summary>
|
|
protected virtual void OnCleared()
|
|
{
|
|
|
|
}
|
|
|
|
bool INetworkSerializer.HasChanges => false;
|
|
}
|