Code/RpcClient.cs
using System.Threading;
using System.Threading.Tasks;

namespace Extend.Callbacks;

/// <summary>
/// Client for making RPC calls to the server
/// </summary>
public static class RpcClient
{
	/// <summary>
	/// Sends an RPC call to the server and waits for the response with a timeout
	/// </summary>
	/// <typeparam name="TResult">The return type of the RPC call</typeparam>
	/// <param name="methodIdent">The method ident of the RPC method</param>
	/// <param name="methodReturnType">The type of the method return type</param>
	/// <param name="timeout">The maximum time to wait in seconds for the response./></param>
	/// <param name="cancellationToken">The cancellation token</param>
	/// <param name="args">Arguments for the RPC method</param>
	/// <returns>The result of the RPC call</returns>
	/// <exception cref="Exception">If the RPC call fails</exception>
	public static async Task<TResult?> Send<TResult>( int methodIdent, Type methodReturnType, TimeSpan timeout,
		CancellationToken cancellationToken,
		params object[] args )
	{
		var request = RpcMessage.Create( methodIdent, methodReturnType, Connection.Local.Id );

		try
		{
			RpcServer.SendRpc( request, args );
			return await RpcCallbackSystem.Current.WaitForResponse<TResult>( request.Id, timeout, cancellationToken );
		}
		catch ( Exception e )
		{
			var method = TypeLibrary.GetMemberByIdent( methodIdent ) as MethodDescription;
			Log.Warning( $"RPC call failed for {method!.Name}: {e.Message}" );

			return default!;
		}
	}

	/// <summary>
	/// Internal callback for when the server sends a successful RPC response
	/// </summary>
	/// <param name="response">The original RPC message</param>
	/// <param name="result">The result of the RPC call</param>
	/// <param name="methodIdent">The name of the RPC method</param>
	[Sandbox.Rpc.Broadcast( NetFlags.Reliable | NetFlags.HostOnly )]
	internal static void OnRpcResponse( RpcMessage response, object? result, int methodIdent )
	{
		RpcCallbackSystem.Current.CompleteResponse( response, result, methodIdent );
	}

	/// <summary>
	/// Internal callback for when the server sends an error RPC response
	/// </summary>
	/// <param name="id">The original RPC message id</param>
	/// <param name="error">The error from the server</param>
	/// <param name="methodIdent">The method identity of the RPC method</param>
	[Sandbox.Rpc.Broadcast( NetFlags.Reliable | NetFlags.HostOnly )]
	internal static void OnRpcError( Guid id, RpcError error, int methodIdent )
	{
		RpcCallbackSystem.Current.CompleteWithError( id, error, methodIdent );
	}
}