Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion engine/Sandbox.Engine/Scene/Components/Component.Network.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ protected T __sync_GetValue<T>( WrappedPropertyGet<T> p )
[MethodImpl( MethodImplOptions.AggressiveInlining )]
protected void __rpc_Wrapper( in WrappedMethod m, params object[] argumentList )
{
Rpc.OnCallInstanceRpc( GameObject, this, m, argumentList );
using ( Rpc.WithCaller( Rpc.ConsumePendingRpcCaller() ) )
{
Rpc.OnCallInstanceRpc( GameObject, this, m, argumentList );
}
}

/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion engine/Sandbox.Engine/Scene/GameObject/GameObject.Network.cs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,10 @@ protected T __sync_GetValue<T>( WrappedPropertyGet<T> p )
[MethodImpl( MethodImplOptions.AggressiveInlining )]
protected void __rpc_Wrapper( in WrappedMethod m, params object[] argumentList )
{
Rpc.OnCallInstanceRpc( this, default, m, argumentList );
using ( Rpc.WithCaller( Rpc.ConsumePendingRpcCaller() ) )
{
Rpc.OnCallInstanceRpc( this, default, m, argumentList );
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ internal void ForceChangeId( Guid guid )
[MethodImpl( MethodImplOptions.AggressiveInlining )]
protected void __rpc_Wrapper( in WrappedMethod m, params object[] argumentList )
{
Rpc.OnCallInstanceRpc( this, m, argumentList );
using ( Rpc.WithCaller( Rpc.ConsumePendingRpcCaller() ) )
{
Rpc.OnCallInstanceRpc( this, m, argumentList );
}
}

[EditorBrowsable( EditorBrowsableState.Never )]
Expand Down
12 changes: 4 additions & 8 deletions engine/Sandbox.Engine/Scene/Networking/Rpc.InstanceRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ static void InvokeInstanceRpc( in SceneRpcMsg rpc, in object targetObject, in Co

NetworkDebugSystem.Current?.Track( rpcName, rpc );

using ( WithCaller( source ) )
using ( WithPendingCaller( source ) )
{
try
{
Expand Down Expand Up @@ -164,7 +164,7 @@ static void InvokeInstanceRpc( in ObjectRpcMsg rpc, in TypeDescription typeDesc,

NetworkDebugSystem.Current?.Track( rpcName, rpc );

using ( WithCaller( source ) )
using ( WithPendingCaller( source ) )
{
try
{
Expand Down Expand Up @@ -226,7 +226,7 @@ public static void OnCallInstanceRpc( in GameObjectSystem system, in WrappedMeth
//
// Send over network
//
if ( !Calling && Networking.IsActive )
if ( !IsRemoteCall && Networking.IsActive )
{
SendInstanceRpc( system, m, argumentList, attribute );
}
Expand All @@ -241,8 +241,6 @@ public static void OnCallInstanceRpc( in GameObjectSystem system, in WrappedMeth
if ( attribute.Mode == RpcMode.Owner && !isOwner ) return;
if ( attribute.Mode == RpcMode.Host && !Networking.IsHost ) return;

PreCall();

if ( !HasHostInstancePermission( Caller ?? Connection.Local, attribute.Flags ) ) return;

Resume( m );
Expand All @@ -260,7 +258,7 @@ public static void OnCallInstanceRpc( in GameObject go, in Component component,
//
// Send over network
//
if ( !Calling && Networking.IsActive )
if ( !IsRemoteCall && Networking.IsActive )
{
SendInstanceRpc( go, component, m, argumentList, attribute );
}
Expand All @@ -275,8 +273,6 @@ public static void OnCallInstanceRpc( in GameObject go, in Component component,
if ( attribute.Mode == RpcMode.Owner && !isOwner ) return;
if ( attribute.Mode == RpcMode.Host && !Networking.IsHost ) return;

PreCall();

// Can they even call this shit
if ( !HasInstancePermission( Caller ?? Connection.Local, go, attribute.Flags ) ) return;

Expand Down
39 changes: 20 additions & 19 deletions engine/Sandbox.Engine/Scene/Networking/Rpc.StaticRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal static void IncomingStaticRpcMsg( StaticRpcMsg message, Connection sour

NetworkDebugSystem.Current?.Track( $"{method.TypeDescription.FullName}.{method.Name}", message );

using ( WithCaller( source ) )
using ( WithPendingCaller( source ) )
{
try
{
Expand All @@ -43,30 +43,31 @@ internal static void IncomingStaticRpcMsg( StaticRpcMsg message, Connection sour
[EditorBrowsable( EditorBrowsableState.Never )]
public static void OnCallRpc( WrappedMethod m, params object[] argumentList )
{
var attribute = m.GetAttribute<RpcAttribute>();
if ( attribute is null ) return;

//
// Send over network
//
if ( !Calling && Networking.IsActive )
using ( WithCaller( ConsumePendingRpcCaller() ) )
{
SendStaticRpc( m, argumentList, attribute );
}
var attribute = m.GetAttribute<RpcAttribute>();
if ( attribute is null ) return;

// Was filtered out
if ( Filter.HasValue && !Filter.Value.IsRecipient( Connection.Local ) ) return;
//
// Send over network
//
if ( !IsRemoteCall && Networking.IsActive )
{
SendStaticRpc( m, argumentList, attribute );
}

// Was not included in the filter
if ( attribute.Mode == RpcMode.Owner && !Networking.IsHost ) return;
if ( attribute.Mode == RpcMode.Host && !Networking.IsHost ) return;
// Was filtered out
if ( Filter.HasValue && !Filter.Value.IsRecipient( Connection.Local ) ) return;

PreCall();
// Was not included in the filter
if ( attribute.Mode == RpcMode.Owner && !Networking.IsHost ) return;
if ( attribute.Mode == RpcMode.Host && !Networking.IsHost ) return;

// Can they even call this shit
if ( !HasStaticPermission( Caller ?? Connection.Local, attribute.Flags ) ) return;
// Can they even call this shit
if ( !HasStaticPermission( Caller, attribute.Flags ) ) return;

Resume( m );
Resume( m );
}
}

/// <summary>
Expand Down
73 changes: 53 additions & 20 deletions engine/Sandbox.Engine/Scene/Networking/Rpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,30 +109,77 @@ public static partial class Rpc
/// <summary>
/// Whether we're currently being called from a remote <see cref="Connection"/>.
/// </summary>
public static bool Calling { get; private set; }
public static bool IsRemoteCall { get; private set; }

internal static Connection.Filter? Filter { get; private set; }

internal static DisposeAction<Connection, bool> WithCaller( Connection caller )
{
var oldCaller = Caller;
var oldCalling = Calling;
var oldIsRemoteCall = IsRemoteCall;

Calling = true;
IsRemoteCall = caller != Connection.Local;
Caller = caller;

unsafe
{
return new DisposeAction<Connection, bool>( &RestoreCaller, oldCaller, oldCalling );
return new DisposeAction<Connection, bool>( &RestoreCaller, oldCaller, oldIsRemoteCall );
}
}

static void RestoreCaller( Connection oldCaller, bool oldCalling )
static void RestoreCaller( Connection oldCaller, bool oldIsRemoteCall )
{
Calling = oldCalling;
IsRemoteCall = oldIsRemoteCall;
Caller = oldCaller;
}

/// <summary>
/// Temporarily stores the connection that initiated a remote RPC.
/// This is consumed by <see cref="ConsumePendingRpcCaller"/> to establish the caller context.
/// <para>
/// This deferred approach ensures nested RPC calls maintain correct caller information:
/// - Remote RPC arrives → sets PendingRpcCaller
/// - First wrapper consumes it → creates WithCaller scope, resets to null
/// - Nested RPCs see null → correctly use Connection.Local
/// </para>
/// </summary>
private static Connection PendingRpcCaller { get; set; }

/// <summary>
/// Consumes <see cref="PendingRpcCaller"/> (if set) and resets it to null.
/// Returns the caller to use for the current RPC execution context.
/// </summary>
/// <returns>The remote caller if pending, otherwise <see cref="Connection.Local"/></returns>
internal static Connection ConsumePendingRpcCaller()
{
var caller = PendingRpcCaller ?? Connection.Local;
PendingRpcCaller = null;
return caller;
}

/// <summary>
/// Sets the pending caller for a remote RPC invocation.
/// </summary>
/// <param name="source">The remote connection invoking the RPC</param>
internal static DisposeAction WithPendingCaller( Connection source )
{
PendingRpcCaller = source;

unsafe
{
return new DisposeAction( ResetPendingCaller );
}
}

/// <summary>
/// Resets the <see cref="PendingRpcCaller"/> to null.
/// This doesn't need to restore the previous value as there will never be nested remote RPCs.
/// </summary>
private static void ResetPendingCaller()
{
PendingRpcCaller = null;
}

/// <summary>
/// Resume a method from an RPC. If the RPC caller is our local connection then we'll
/// first disable any active filter and restore it afterwards.
Expand Down Expand Up @@ -166,20 +213,6 @@ internal static void Resume( WrappedMethod m )
}
}

/// <summary>
/// Called right before calling an RPC function.
/// </summary>
public static void PreCall()
{
if ( Calling )
{
Calling = false;
return;
}

Caller = Connection.Local;
}

/// <summary>
/// Filter the recipients of any Rpc called in this scope to only include the specified <see cref="Connection"/> set.
/// </summary>
Expand Down