This introduces requests with responses, which are just RPCs that can send back a value. Requests are also just layered on top of RPCs and don't introduce any new special treatment, they are just some convenience sugar on top of RPCs where you want to send back a response.
Sending a request will return a Task already known from TAP [1], so you can easily await the response to your request from the receiver. It works similar to HTTP requests.
Context
I needed a way to send back information from one party to another after sending an RPC, and the only way to do that seemed to be two RPCs: one to send the request, and an RPC on the sender that the receiver can in turn invoke to send the response back. This approach resulted in rather entangled code where I would have two handler classes, one for each RPC, that needed to do bookkeeping about what was sent and what response went with what request.
This moves that bookkeeping into Forge, so senders can just await the response and don't have to worry about anything else.
Examples
networkObject.RegisterRequest<SayHelloRequest,SayHelloResponse>("SayHello", async (context) => {
await DoSomethingElseAsync();
// You can do more async stuff here, and even return a Task yourself. The response won't be sent back until
// the returned task resolves and will use the value produced by the returned task.
return new SayHelloResponse("You said: '" + context.Data.Message + "', hello back to you!");
});
var response = await networkObject.SendRequest<SayHelloResponse>(someOtherClient, "SayHello", new SayHelloRequest("Good day!"));
var theOtherPartyReplied = response.Message;
// theOtherPartyReplied = "You said: 'Good Day!', hello back to you!"
Limitations
There is no UI to generate requests (yet), like there is for RPCs. This means you need to register them yourself for the moment. It also means that no SendSayHelloRequest is automatically generated in the scenario above and you cannot (yet) avoid specifying the return type in SendRequest (nor enforce the request data type).
Requests receive a context that can only have exactly one parameter, the data for the request. If you want to send multiple, serializable, properties, you must put them in an ISerializable class or struct and pass that. Likewise, you can only return one response.
I specifically modelled ISerializable after Forge Alloy's new approach, so porting is easier.
Not all Receivers work for requests: you either send a request to a single party, or to multiple specific parties (and you will get an array of responses when all of them have come back); you cannot send a request to "all" parties without specifying them explicitly, currently, as the sender must know for how many responses to wait before it resolves the promise, and must in turn know how many parties are going to handle the request.
NOTE: This doesn't necessarily need to be merged, but I wanted to upstream it anyway, to contribute back to the Forge community. Similar support for TAP may also be interesting to port over to Alloy as it's just one message going out and another coming back, so this could act as a reference implementation.
Summary
This introduces requests with responses, which are just RPCs that can send back a value. Requests are also just layered on top of RPCs and don't introduce any new special treatment, they are just some convenience sugar on top of RPCs where you want to send back a response.
Sending a request will return a
Task
already known from TAP [1], so you can easilyawait
the response to your request from the receiver. It works similar to HTTP requests.Context
I needed a way to send back information from one party to another after sending an RPC, and the only way to do that seemed to be two RPCs: one to send the request, and an RPC on the sender that the receiver can in turn invoke to send the response back. This approach resulted in rather entangled code where I would have two handler classes, one for each RPC, that needed to do bookkeeping about what was sent and what response went with what request.
This moves that bookkeeping into Forge, so senders can just
await
the response and don't have to worry about anything else.Examples
Limitations
SendSayHelloRequest
is automatically generated in the scenario above and you cannot (yet) avoid specifying the return type inSendRequest
(nor enforce the request data type).ISerializable
class or struct and pass that. Likewise, you can only return one response.ISerializable
after Forge Alloy's new approach, so porting is easier.Receivers
work for requests: you either send a request to a single party, or to multiple specific parties (and you will get an array of responses when all of them have come back); you cannot send a request to "all" parties without specifying them explicitly, currently, as the sender must know for how many responses to wait before it resolves the promise, and must in turn know how many parties are going to handle the request.NOTE: This doesn't necessarily need to be merged, but I wanted to upstream it anyway, to contribute back to the Forge community. Similar support for TAP may also be interesting to port over to Alloy as it's just one message going out and another coming back, so this could act as a reference implementation.
[1] https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap