Files
sbox-public/engine/Sandbox.Engine/Scene/GameObjectSystem/GameObjectSystem.Network.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

284 lines
7.2 KiB
C#

using System.ComponentModel;
using System.Runtime.CompilerServices;
using Sandbox.Network;
namespace Sandbox;
public abstract partial class GameObjectSystem : IDeltaSnapshot
{
private readonly Dictionary<string, IInterpolatedSyncVar> InterpolatedVars = new();
private NetworkTable dataTable = new();
private Guid _id;
public Guid Id
{
get => _id;
internal set
{
if ( _id == value ) return;
var oldId = _id;
_id = value;
Scene?.Directory?.Add( this, oldId );
CreateDataTable();
}
}
bool IDeltaSnapshot.IsProxy => !Networking.IsHost;
bool IDeltaSnapshot.ShouldTransmit( Connection target ) => true;
bool IDeltaSnapshot.UpdateTransmitState( Connection[] targets ) => true;
ushort IDeltaSnapshot.SnapshotVersion => 0;
/// <summary>
/// Should only be called by <see cref="GameObjectDirectory.Add( GameObjectSystem )"/>.
/// </summary>
internal void ForceChangeId( Guid guid )
{
_id = guid;
}
[EditorBrowsable( EditorBrowsableState.Never )]
[MethodImpl( MethodImplOptions.AggressiveInlining )]
protected void __rpc_Wrapper( in WrappedMethod m, params object[] argumentList )
{
Rpc.OnCallInstanceRpc( this, m, argumentList );
}
[EditorBrowsable( EditorBrowsableState.Never )]
protected void __sync_SetValue<T>( in WrappedPropertySet<T> p )
{
try
{
// If we aren't valid then just set the property value anyway.
if ( !Scene.IsValid() )
{
p.Setter?.Invoke( p.Value );
return;
}
var slot = NetworkObject.GetPropertySlot( p.MemberIdent, Id );
if ( !dataTable.IsRegistered( slot ) )
{
p.Setter?.Invoke( p.Value );
return;
}
if ( !dataTable.HasControl( slot ) )
{
if ( !NetworkTable.IsReadingChanges )
return;
var attribute = p.GetAttribute<SyncAttribute>();
var interpolate = attribute?.Flags.HasFlag( SyncFlags.Interpolate ) ?? false;
if ( interpolate && p.Value is not null )
{
var interpolated = GetOrCreateInterpolatedVar( p.Value, p.PropertyName );
interpolated?.Update( p.Value );
}
p.Setter?.Invoke( p.Value );
return;
}
dataTable.UpdateSlotHash( slot, p.Value );
p.Setter?.Invoke( p.Value );
}
catch ( Exception e )
{
Log.Error( e, $"Exception when setting {p.TypeName}.{p.PropertyName} - {e.Message}" );
}
}
[EditorBrowsable( EditorBrowsableState.Never )]
protected T __sync_GetValue<T>( WrappedPropertyGet<T> p )
{
var attribute = p.GetAttribute<SyncAttribute>();
var interpolate = attribute?.Flags.HasFlag( SyncFlags.Interpolate ) ?? false;
if ( !interpolate ) return p.Value;
var slot = NetworkObject.GetPropertySlot( p.MemberIdent, Id );
if ( !dataTable.IsRegistered( slot ) || dataTable.HasControl( slot ) )
return p.Value;
if ( InterpolatedVars.TryGetValue( p.PropertyName, out var i ) )
return (T)i.Query( Time.Now );
return p.Value;
}
/// <summary>
/// Get or create a new interpolated variable. This will set the current interpolated value to the
/// provided one if it hasn't been created yet.
/// </summary>
private InterpolatedSyncVar<T> GetOrCreateInterpolatedVar<T>( T value, string propertyName )
{
if ( InterpolatedVars.TryGetValue( propertyName, out var i ) )
return (InterpolatedSyncVar<T>)i;
var interpolator = IInterpolatedSyncVar.Create( value );
if ( interpolator is null )
return null;
var interpolated = new InterpolatedSyncVar<T>( interpolator );
InterpolatedVars[propertyName] = interpolated;
return interpolated;
}
private void RegisterSyncVarProperties()
{
var type = GetType();
// Register all our Sync properties with the data table.
foreach ( var propertyAndAttribute in ReflectionQueryCache.SyncProperties( type ) )
{
var isQuery = propertyAndAttribute.Attribute.Flags.HasFlag( SyncFlags.Query );
try
{
var originType = propertyAndAttribute.Property.DeclaringType ?? type;
var identity = NetworkObject.GetPropertySlot( $"{originType.FullName}.{propertyAndAttribute.Property.Name}".FastHash(), Id );
var entry = new NetworkTable.Entry
{
TargetType = propertyAndAttribute.Property.PropertyType,
ControlCondition = c => c.IsHost,
GetValue = () => propertyAndAttribute.Property.GetValue( this ),
SetValue = v => propertyAndAttribute.Property?.SetValue( this, v ),
NeedsQuery = isQuery,
DebugName = $"{originType.Name}.{propertyAndAttribute.Property.Name}"
};
dataTable.Register( identity, entry );
}
catch ( Exception e )
{
Log.Warning( e, $"Got exception when creating network table (reading {this}.{propertyAndAttribute.Property.Name}) - {e.Message}" );
}
}
}
private void CreateDataTable()
{
dataTable?.Dispose();
dataTable = new();
RegisterSyncVarProperties();
}
internal readonly LocalSnapshotState LocalSnapshotState = new();
void IDeltaSnapshot.OnSnapshotAck( Connection source, DeltaSnapshot snapshot, RemoteSnapshotState state )
{
var hasFullSnapshotState = true;
foreach ( var entry in LocalSnapshotState.Entries )
{
if ( state.IsValueHashEqual( entry.Slot, entry.Hash, snapshot.SnapshotId ) )
{
entry.Connections.Add( source.Id );
}
else
{
entry.Connections.Remove( source.Id );
hasFullSnapshotState = false;
}
}
if ( hasFullSnapshotState )
LocalSnapshotState.UpdatedConnections.Add( source.Id );
else
LocalSnapshotState.UpdatedConnections.Remove( source.Id );
}
LocalSnapshotState IDeltaSnapshot.WriteSnapshotState()
{
var system = SceneNetworkSystem.Instance;
if ( system is null ) return null;
LocalSnapshotState.SnapshotId = system.DeltaSnapshots.CreateSnapshotId( Id );
LocalSnapshotState.ObjectId = Id;
dataTable.QueryValues();
dataTable.WriteSnapshotState( LocalSnapshotState );
return LocalSnapshotState;
}
/// <summary>
/// Try to send a network update or do nothing if no update is required. This is most
/// likely called after WriteDeltaSnapshot.
/// </summary>
void IDeltaSnapshot.SendNetworkUpdate( bool queryValues )
{
if ( queryValues )
dataTable.QueryValues( true );
if ( !dataTable.HasReliableChanges() )
return;
var msg = new SceneNetworkTableMsg { Guid = Id };
var data = ByteStream.Create( 4096 );
dataTable.WriteReliableChanged( ref data );
msg.TableData = data.ToArray();
data.Dispose();
SceneNetworkSystem.Instance.Broadcast( msg );
}
/// <summary>
/// Write all pending data table changes.
/// </summary>
internal byte[] WriteDataTable( bool full )
{
if ( !dataTable.HasAnyChanges && !full )
return null;
var data = ByteStream.Create( 32 );
if ( full )
dataTable.WriteAll( ref data );
else
dataTable.WriteChanged( ref data );
var bytes = data.ToArray();
data.Dispose();
return bytes;
}
/// <summary>
/// Read the network table data.
/// </summary>
internal void ReadDataTable( byte[] data, NetworkTable.ReadFilter filter = null )
{
if ( data is null ) return;
var reader = ByteStream.CreateReader( data );
dataTable.Read( ref reader, filter );
reader.Dispose();
}
bool IDeltaSnapshot.OnSnapshot( Connection source, DeltaSnapshot snapshot )
{
// Don't process this if the source connection is not the host.
if ( !source.IsHost )
return false;
dataTable.ReadSnapshot( source, snapshot );
return true;
}
internal void OnHotload()
{
CreateDataTable();
}
}