SendCommand - generator to enforce function + parameter safety

Is there a way to invoke SendCommand in a type safe manner?
Instead of calling requester.SendCommand<Type>(name, MessageTarget, arguments) to be able to call the arguments so that they are respective of the target method.
Passing along strings and arbitrary arguments is error prone.

example: the global counter:

public class NumberRequester : MonoBehaviour
{
	[Command]
	public void GotNumber(int number)
	{
		Debug.Log($"Got number: {number}");
	}
}

public class Counter : MonoBehaviour
{
	public int counter;
	public void NextNumber(CoherenceSync requester)
	{
		requester.SendCommand<NumberRequester>(
			nameof(NumberRequester.GotNumber),
			MessageTarget.AuthorityOnly, 
			counter
		);
	}
}

If we change GotNumber’s call signature to

public void ReceivedNumber(int number, int previous)

Then the code above would break; and it would be tricky to find where the wrong / missing variables are until the function is called

(coherence) CoherenceSync: Unable to send command '<name>' to 'Number Requester': command was not found. Check the command's name and parameters, or Bake again 
(coherence) CoherenceSync: Unable to send command 'NumberRequester.GotNumber' to 'Number Requester': command NumberRequester.GotNumber(<arguments>) was not found. Check the command's name and parameters, or Bake again

I made a small script that extracts all [Command] attributes into helpers:

requester.For<NumberRequester>().GotNumber(counter);

Which is pretty much 3 extension methods + a struct to associate the method invocations to

public struct NumberRequester_RPCHelper
{
	public Coherence.Toolkit.CoherenceSync sync;
	public NumberRequester_RPCHelper(Coherence.Toolkit.CoherenceSync sync)
	{
		this.sync = sync; 
	}
}

public static CoherenceSyncExtensions
{
	// by having T data = default the user can use it as sync.For(variable) or sync.For<VariableType>
	public static NumberRequester_RPCHelper For<T>(this Coherence.Toolkit.CoherenceSync sync, T data = default) where T:NumberRequester => new NumberRequester_RPCHelper(sync);

	// allow rounting target
	public static void GotNumber(this NumberRequester_RPCHelper helper, Coherence.MessageTarget target, System.Int32 number)
	{
		helper.sync.SendCommand<NumberRequester>("GotNumber", target, number);
	}

	// without routing target
	public static void GotNumber(this NumberRequester_RPCHelper helper, System.Int32 number)
	{
		// by default the [Command] has defaultRouting = MessageTarget.All . If the default routing is changed - it would be changed here as well
		helper.sync.SendCommand<NumberRequester>("GotNumber",  MessageTarget.All, number); 
	}
}

Thoughts?
Would it be possible to integrate something like this in the future versions? Thanks!

1 Like

Yes, type safe commands are actually already supported (although not very well documented).

In your example, you can write:

> requester.SendCommand(requester.GetComponent<NumberRequester>().GotNumber, MessageTarget.AuthorityOnly, counter);

There are overloads supporting up to 8 (type-safe) parameters.

I’ll make a note about adding this to our docs. Thanks!