using Sandbox.Network;
namespace Sandbox;
internal sealed partial class NetworkObject
{
internal NetworkTable dataTable;
///
/// Get a deterministic property slot for use with a network table.
///
internal static int GetPropertySlot( int propertyIdent, Guid guid )
{
//
// https://github.com/dotnet/runtime/blob/457c6b709b54b354efd8d36a9b5c03db53494612/docs/design/security/System.HashCode.md?plain=1#L52
// tony: If we were to use System.HashCode.Combine here, it would be non-deterministic across different processes for "security reasons" - what the fuck
//
return guid.GetHashCode() ^ propertyIdent;
}
void CreateDataTable()
{
dataTable?.Dispose();
dataTable = new();
RegisterPropertiesRecursive( GameObject );
}
internal void RegisterPropertiesRecursive( GameObject go = null, bool gameObjects = true, bool components = true )
{
go ??= GameObject;
if ( gameObjects )
{
RegisterProperties( go, go.Id );
}
if ( components )
{
foreach ( var component in go.Components.GetAll() )
{
if ( component is null ) continue;
RegisterProperties( component, component.Id );
}
}
foreach ( var child in go.Children.Where( child => child.NetworkMode == NetworkMode.Snapshot ) )
{
// Conna: pass false here so that we don't add properties from child GameObjects. We only
// want to add properties from components on child GameObjects. This is because we don't
// want to add potentially hundreds of entries for OwnerTransfer, etc.
RegisterPropertiesRecursive( child, false );
}
}
internal void RegisterProperties( object instance, Guid guid )
{
var type = instance.GetType();
// Register all our Sync properties with the data table.
foreach ( var propertyAndAttribute in ReflectionQueryCache.SyncProperties( type ) )
{
var isHostSync = propertyAndAttribute.Attribute.Flags.HasFlag( SyncFlags.FromHost );
var isQuery = propertyAndAttribute.Attribute.Flags.HasFlag( SyncFlags.Query );
try
{
var originType = propertyAndAttribute.Property.DeclaringType ?? type;
var identity = GetPropertySlot( $"{originType.FullName}.{propertyAndAttribute.Property.Name}".FastHash(), guid );
var entry = new NetworkTable.Entry
{
TargetType = propertyAndAttribute.Property.PropertyType,
ControlCondition = c => isHostSync ? c.IsHost : HasControl( c ),
GetValue = () => propertyAndAttribute.Property.GetValue( instance ),
SetValue = ( v ) => propertyAndAttribute.Property?.SetValue( instance, 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 {GameObject}.{propertyAndAttribute.Property.Name}) - {e.Message}" );
}
}
}
///
/// Write all reliable data table entries.
///
byte[] WriteReliableData()
{
var data = ByteStream.Create( 32 );
dataTable.WriteAllReliable( ref data );
var bytes = data.ToArray();
data.Dispose();
return bytes;
}
///
/// Write all pending data table changes.
///
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;
}
///
/// Read the network table data.
///
private 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();
}
}