diff --git a/engine/Sandbox.Engine/Scene/Components/Collider/Rigidbody.cs b/engine/Sandbox.Engine/Scene/Components/Collider/Rigidbody.cs
index fb858629..c775d121 100644
--- a/engine/Sandbox.Engine/Scene/Components/Collider/Rigidbody.cs
+++ b/engine/Sandbox.Engine/Scene/Components/Collider/Rigidbody.cs
@@ -176,10 +176,37 @@ sealed public partial class Rigidbody : Component, Component.ExecuteInEditor, IG
Vector3 _lastVelocity;
Vector3 _lastAngularVelocity;
- [Sync( SyncFlags.Query )]
+ [Sync]
+ private Vector3 NetworkedVelocity
+ {
+ get;
+ set
+ {
+ _lastVelocity = value;
+ field = value;
+ }
+ }
+
+ [Sync]
+ private Vector3 NetworkedAngularVelocity
+ {
+ get;
+ set
+ {
+ _lastAngularVelocity = value;
+ field = value;
+ }
+ }
+
public Vector3 Velocity
{
- get => _body?.Velocity ?? default;
+ get
+ {
+ if ( IsProxy )
+ return NetworkedVelocity;
+
+ return _body?.Velocity ?? default;
+ }
set
{
if ( _body.IsValid() && !IsProxy )
@@ -191,10 +218,15 @@ sealed public partial class Rigidbody : Component, Component.ExecuteInEditor, IG
}
}
- [Sync( SyncFlags.Query )]
public Vector3 AngularVelocity
{
- get => _body?.AngularVelocity ?? default;
+ get
+ {
+ if ( IsProxy )
+ return NetworkedAngularVelocity;
+
+ return _body?.AngularVelocity ?? default;
+ }
set
{
if ( _body.IsValid() && !IsProxy )
@@ -267,8 +299,8 @@ sealed public partial class Rigidbody : Component, Component.ExecuteInEditor, IG
}
///
- /// Gets or sets the inertia tensor for this body.
- /// By default, the inertia tensor is automatically calculated from the shapes attached to the body.
+ /// Gets or sets the inertia tensor for this body.
+ /// By default, the inertia tensor is automatically calculated from the shapes attached to the body.
/// Setting this property overrides the automatically calculated inertia tensor until is called.
///
public Vector3 InertiaTensor
@@ -282,8 +314,8 @@ sealed public partial class Rigidbody : Component, Component.ExecuteInEditor, IG
}
///
- /// Gets or sets the rotation applied to the inertia tensor.
- /// Like , this acts as an override to the automatically calculated inertia tensor rotation
+ /// Gets or sets the rotation applied to the inertia tensor.
+ /// Like , this acts as an override to the automatically calculated inertia tensor rotation
/// and remains in effect until is called.
///
public Rotation InertiaTensorRotation
@@ -317,8 +349,23 @@ sealed public partial class Rigidbody : Component, Component.ExecuteInEditor, IG
}
}
+ void IGameObjectNetworkEvents.BeforeDropOwnership()
+ {
+ // Before we drop ownership, we want to make sure the networked vars
+ // are fully synchronized with the PhysicsBody. This is because when
+ // we drop ownership, we'll send a packet about our current state to
+ // other clients, and we may not have updated these yet in the physics
+ // step.
+
+ if ( !_body.IsValid() )
+ return;
+
+ NetworkedAngularVelocity = _body.AngularVelocity;
+ NetworkedVelocity = _body.Velocity;
+ }
+
///
- /// Resets the inertia tensor and its rotation to the values automatically calculated from the attached colliders.
+ /// Resets the inertia tensor and its rotation to the values automatically calculated from the attached colliders.
/// This removes any custom overrides set via or .
///
public void ResetInertiaTensor()
@@ -531,6 +578,12 @@ sealed public partial class Rigidbody : Component, Component.ExecuteInEditor, IG
// Networked proxy should use velocity to move to world transform.
_body.Move( Transform.TargetWorld, Time.Delta );
}
+
+ if ( IsProxy )
+ return;
+
+ NetworkedAngularVelocity = _body.AngularVelocity;
+ NetworkedVelocity = _body.Velocity;
}
///
diff --git a/engine/Sandbox.Engine/Scene/Components/Component.Network.cs b/engine/Sandbox.Engine/Scene/Components/Component.Network.cs
index 5710fdb6..9d77485c 100644
--- a/engine/Sandbox.Engine/Scene/Components/Component.Network.cs
+++ b/engine/Sandbox.Engine/Scene/Components/Component.Network.cs
@@ -22,13 +22,28 @@ public abstract partial class Component
{
try
{
- // If we aren't valid then just set the property value anyway.
+ // If we aren't valid, then just set the property value anyway.
if ( !IsValid )
{
p.Setter?.Invoke( p.Value );
return;
}
+ // If it's the same value, just call the original setter because
+ // we don't want to do all the logic below for the same value.
+ // Obviously, if we're reading changes from the network, then we
+ // should just allow all the logic to go through.
+ if ( !NetworkTable.IsReadingChanges )
+ {
+ var currentValue = p.Getter();
+
+ if ( Equals( currentValue, p.Value ) )
+ {
+ p.Setter?.Invoke( p.Value );
+ return;
+ }
+ }
+
var root = GameObject.FindNetworkRoot();
var slot = NetworkObject.GetPropertySlot( p.MemberIdent, Id );
diff --git a/engine/Sandbox.Engine/Scene/Events/IGameObjectNetworkEvents.cs b/engine/Sandbox.Engine/Scene/Events/IGameObjectNetworkEvents.cs
index 4ce23762..339feed3 100644
--- a/engine/Sandbox.Engine/Scene/Events/IGameObjectNetworkEvents.cs
+++ b/engine/Sandbox.Engine/Scene/Events/IGameObjectNetworkEvents.cs
@@ -5,6 +5,11 @@
///
public interface IGameObjectNetworkEvents : ISceneEvent
{
+ ///
+ /// Called before we are about to drop ownership of a network GameObject
+ ///
+ internal void BeforeDropOwnership() { }
+
///
/// Called when the owner of a network GameObject is changed
///
diff --git a/engine/Sandbox.Engine/Scene/GameObject/GameObject.Network.cs b/engine/Sandbox.Engine/Scene/GameObject/GameObject.Network.cs
index e05566ed..f973f0d1 100644
--- a/engine/Sandbox.Engine/Scene/GameObject/GameObject.Network.cs
+++ b/engine/Sandbox.Engine/Scene/GameObject/GameObject.Network.cs
@@ -218,11 +218,10 @@ public partial class GameObject
///
/// Make a request from the host to stop being the network owner of this game object.
///
- [Rpc.Broadcast]
+ [Rpc.Host]
void Msg_RequestDropOwnership( ushort snapshotVersion )
{
if ( _net is null ) return;
- if ( !Networking.IsHost ) return;
if ( OwnerTransfer != OwnerTransfer.Request ) return;
var caller = Rpc.Caller;
@@ -269,11 +268,10 @@ public partial class GameObject
///
/// Make a request from the host to become the network owner of this game object.
///
- [Rpc.Broadcast]
+ [Rpc.Host]
void Msg_RequestTakeOwnership( ushort snapshotVersion )
{
if ( _net is null ) return;
- if ( !Networking.IsHost ) return;
if ( OwnerTransfer != OwnerTransfer.Request ) return;
// Can this caller take ownership?
@@ -339,11 +337,10 @@ public partial class GameObject
///
/// Make a request from the host to assign ownership of this game object to the specified connection .
///
- [Rpc.Broadcast]
+ [Rpc.Host]
void Msg_RequestAssignOwnership( Guid guid, ushort snapshotVersion )
{
if ( _net is null ) return;
- if ( !Networking.IsHost ) return;
if ( OwnerTransfer != OwnerTransfer.Request ) return;
// Can this caller assign ownership?
@@ -809,12 +806,7 @@ public partial class GameObject
if ( !IsProxy )
{
- // Clear interpolation and set that flag here.
- go.Transform.ClearInterpolation();
-
- // Force a delta snapshot for this object since we changed owner.
- var system = SceneNetworkSystem.Instance;
- system?.DeltaSnapshots?.Send( go._net, NetFlags.Reliable, true );
+ UpdateStateBeforeOwnerChange();
}
if ( !Networking.IsHost && go.OwnerTransfer == OwnerTransfer.Request )
@@ -839,12 +831,7 @@ public partial class GameObject
if ( !IsProxy )
{
- // Clear interpolation and set that flag here.
- go.Transform.ClearInterpolation();
-
- // Force a delta snapshot for this object since we changed owner.
- var system = SceneNetworkSystem.Instance;
- system?.DeltaSnapshots?.Send( go._net, NetFlags.Reliable, true );
+ UpdateStateBeforeOwnerChange();
}
go.Msg_AssignOwnership( connectionId, go.Network.SnapshotVersion );
@@ -886,12 +873,7 @@ public partial class GameObject
if ( !IsProxy )
{
- // Clear interpolation and set that flag here.
- go.Transform.ClearInterpolation();
-
- // Force a delta snapshot for this object since we changed owner.
- var system = SceneNetworkSystem.Instance;
- system?.DeltaSnapshots?.Send( go._net, NetFlags.Reliable, true );
+ UpdateStateBeforeOwnerChange();
}
if ( Networking.IsHost )
@@ -930,5 +912,25 @@ public partial class GameObject
{
return go.NetworkSpawn( owner );
}
+
+ ///
+ /// Before we drop ownership (or assign ownership to somebody else), there are a few things
+ /// we want to do first. We want to let any listeners know so that they can react to it, we
+ /// want to clear interpolation, and finally, we want to force a full delta snapshot of the
+ /// object's state.
+ ///
+ private void UpdateStateBeforeOwnerChange()
+ {
+ // Let any listeners know that we're about to drop ownership and send
+ // a full state update to everyone
+ IGameObjectNetworkEvents.PostToGameObject( go, x => x.BeforeDropOwnership() );
+
+ // Clear interpolation and set that flag here
+ go.Transform.ClearInterpolation();
+
+ // Force a delta snapshot for this object since we changed the owner
+ var system = SceneNetworkSystem.Instance;
+ system?.DeltaSnapshots?.Send( go._net, NetFlags.Reliable, true );
+ }
}
}
diff --git a/engine/Sandbox.Engine/Scene/GameObjectSystem/GameObjectSystem.Network.cs b/engine/Sandbox.Engine/Scene/GameObjectSystem/GameObjectSystem.Network.cs
index a32c37c7..38a516c9 100644
--- a/engine/Sandbox.Engine/Scene/GameObjectSystem/GameObjectSystem.Network.cs
+++ b/engine/Sandbox.Engine/Scene/GameObjectSystem/GameObjectSystem.Network.cs
@@ -208,6 +208,7 @@ public abstract partial class GameObjectSystem : IDeltaSnapshot
var system = SceneNetworkSystem.Instance;
if ( system is null ) return null;
+ LocalSnapshotState.Begin();
LocalSnapshotState.SnapshotId = system.DeltaSnapshots.CreateSnapshotId( Id );
LocalSnapshotState.ObjectId = Id;
diff --git a/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/DeltaSnapshotSystem.cs b/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/DeltaSnapshotSystem.cs
index 12a3d051..ecb1072a 100644
--- a/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/DeltaSnapshotSystem.cs
+++ b/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/DeltaSnapshotSystem.cs
@@ -18,13 +18,13 @@ internal class DeltaSnapshotSystem
internal class GuidUlongComparer : IEqualityComparer
{
[MethodImpl( MethodImplOptions.AggressiveInlining )]
- public bool Equals( Guid x, Guid y ) => x.Equals( y );
+ public bool Equals( Guid x, Guid y ) => x == y;
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public int GetHashCode( Guid guid )
{
- var lowBits = MemoryMarshal.Read( MemoryMarshal.AsBytes( MemoryMarshal.CreateReadOnlySpan( in guid, 1 ) ) );
- return (int)(lowBits ^ (lowBits >> 32));
+ var span = MemoryMarshal.Cast( MemoryMarshal.CreateSpan( ref guid, 1 ) );
+ return (int)(span[0] ^ span[1]);
}
}
@@ -674,13 +674,12 @@ internal class DeltaSnapshotSystem
///
public ushort CreateSnapshotId( Guid objectId )
{
- ushort snapshotId = 0;
+ ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault( LastSentSnapshotIds, objectId, out bool exists );
- if ( LastSentSnapshotIds.TryGetValue( objectId, out var id ) )
- snapshotId = (ushort)(id + 1);
+ if ( exists )
+ id++;
- LastSentSnapshotIds[objectId] = snapshotId;
- return snapshotId;
+ return id;
}
///
diff --git a/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/LocalSnapshotState.cs b/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/LocalSnapshotState.cs
index db258323..6aa7fce8 100644
--- a/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/LocalSnapshotState.cs
+++ b/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/LocalSnapshotState.cs
@@ -26,6 +26,8 @@ internal class LocalSnapshotState
public Guid ObjectId { get; set; }
public int Size { get; private set; }
+ private bool _isHashInvalid { get; set; }
+
[Flags]
public enum HashFlags
{
@@ -59,6 +61,7 @@ internal class LocalSnapshotState
return;
_parentIdBytes ??= new byte[16];
+ _isHashInvalid = true;
value.TryWriteBytes( _parentIdBytes );
field = value;
@@ -78,6 +81,7 @@ internal class LocalSnapshotState
_flagsBytes ??= new byte[1];
_flagsBytes[0] = (byte)value;
+ _isHashInvalid = true;
field = value;
}
@@ -86,7 +90,15 @@ internal class LocalSnapshotState
private byte[] _parentIdBytes;
private byte[] _flagsBytes;
- private readonly XxHash3 _hasher = new();
+ private static readonly XxHash3 Hasher = new();
+
+ ///
+ /// Call this every time you begin updating the snapshot state.
+ ///
+ public void Begin()
+ {
+ _isHashInvalid = false;
+ }
///
/// Remove a connection from stored state acknowledgements.
@@ -138,9 +150,9 @@ internal class LocalSnapshotState
///
public ulong Hash( byte[] value )
{
- _hasher.Reset();
- _hasher.Append( value );
- return _hasher.GetCurrentHashAsUInt64();
+ Hasher.Reset();
+ Hasher.Append( value );
+ return Hasher.GetCurrentHashAsUInt64();
}
///
@@ -189,16 +201,16 @@ internal class LocalSnapshotState
///
public void AddSerialized( int slot, byte[] value, HashFlags hashFlags = HashFlags.Default )
{
- _hasher.Reset();
- _hasher.Append( value );
+ Hasher.Reset();
+ Hasher.Append( value );
if ( (hashFlags & HashFlags.WithParentId) != 0 && _parentIdBytes is not null )
- _hasher.Append( _parentIdBytes );
+ Hasher.Append( _parentIdBytes );
if ( (hashFlags & HashFlags.WithNetworkFlags) != 0 && _flagsBytes is not null )
- _hasher.Append( _flagsBytes );
+ Hasher.Append( _flagsBytes );
- var hash = _hasher.GetCurrentHashAsUInt64();
+ var hash = Hasher.GetCurrentHashAsUInt64();
AddSerialized( slot, value, hash );
}
@@ -209,6 +221,11 @@ internal class LocalSnapshotState
///
public void AddCached( SnapshotValueCache cache, int slot, T value, HashFlags hashFlags = HashFlags.Default )
{
- AddSerialized( slot, cache.GetCached( slot, value ), hashFlags );
+ var cached = cache.GetCached( slot, value, out var isEqual );
+
+ if ( isEqual && !_isHashInvalid )
+ return;
+
+ AddSerialized( slot, cached, hashFlags );
}
}
diff --git a/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/SnapshotValueCache.cs b/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/SnapshotValueCache.cs
index 0c9cc696..9feedb34 100644
--- a/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/SnapshotValueCache.cs
+++ b/engine/Sandbox.Engine/Scene/Networking/DeltaSnapshots/SnapshotValueCache.cs
@@ -1,37 +1,47 @@
+using System.Runtime.InteropServices;
using Sandbox.Engine;
namespace Sandbox.Network;
internal class SnapshotValueCache
{
- private Dictionary Serialized { get; } = new();
- private Dictionary Cache { get; } = new();
+ private readonly Dictionary _serialized = new();
+ private readonly Dictionary _hashCache = new();
///
- /// Get cached bytes from the specified value if they exist. If the value is different
+ /// Get cached bytes from the specified value if they exist. If the value is different,
/// then re-serialize and cache again.
///
- public byte[] GetCached( int slot, T value )
+ public byte[] GetCached( int slot, T value, out bool isEqual )
{
- if ( Cache.TryGetValue( slot, out var cached ) && Equals( cached, value ) )
- return Serialized[slot];
+ var hash = value?.GetHashCode() ?? 0;
+
+ ref var cachedHash = ref CollectionsMarshal.GetValueRefOrAddDefault( _hashCache, slot, out bool exists );
+
+ if ( exists && cachedHash == hash )
+ {
+ isEqual = true;
+ return _serialized[slot];
+ }
var bytes = GlobalContext.Current.TypeLibrary.ToBytes( value );
- Serialized[slot] = bytes;
- Cache[slot] = value;
+ _serialized[slot] = bytes;
+
+ cachedHash = hash;
+ isEqual = false;
return bytes;
}
public void Remove( int slot )
{
- Serialized.Remove( slot );
- Cache.Remove( slot );
+ _serialized.Remove( slot );
+ _hashCache.Remove( slot );
}
public void Clear()
{
- Serialized.Clear();
- Cache.Clear();
+ _serialized.Clear();
+ _hashCache.Clear();
}
}
diff --git a/engine/Sandbox.Engine/Scene/Networking/NetworkObject.cs b/engine/Sandbox.Engine/Scene/Networking/NetworkObject.cs
index 49ca786a..75cc2579 100644
--- a/engine/Sandbox.Engine/Scene/Networking/NetworkObject.cs
+++ b/engine/Sandbox.Engine/Scene/Networking/NetworkObject.cs
@@ -533,6 +533,7 @@ internal sealed partial class NetworkObject : IValid, IDeltaSnapshot
var flags = GameObject.Network.Flags;
+ LocalSnapshotState.Begin();
LocalSnapshotState.SnapshotId = system.DeltaSnapshots.CreateSnapshotId( Id );
LocalSnapshotState.ParentId = GameObject.Parent is Scene ? Guid.Empty : GameObject.Parent.Id;
LocalSnapshotState.ObjectId = Id;
diff --git a/engine/Sandbox.Engine/Scene/Networking/NetworkTable.cs b/engine/Sandbox.Engine/Scene/Networking/NetworkTable.cs
index 7fd406da..7bc95490 100644
--- a/engine/Sandbox.Engine/Scene/Networking/NetworkTable.cs
+++ b/engine/Sandbox.Engine/Scene/Networking/NetworkTable.cs
@@ -12,33 +12,34 @@ internal class NetworkTable : IDisposable
public class Entry : INetworkProxy
{
- public Type TargetType { get; init; }
- public string DebugName { get; init; }
- public bool NeedsQuery { get; set; }
- public Func ControlCondition { get; init; } = c => true;
- public Func