Remote Procedure Call (RPC)¶
Remote procedural call (RPC) is a powerful feature in PhysioLabXR that allows you to define functions in a PhyScript, and you can call these functions from a different environment, such as a different PhyScript, anther Python script, C# in Unity, and more.
A common use case is when your experiment paradigm is designed in Unity and you use PhyScript to process the physiological data and provide feedback to the Unity environment. For example, if your experiment needs emotion classification, you can call a PhyRPC function defined in PhyScript from Unity to perform the classification and return the result to Unity.
Note
Normally, to make an RPC work, you also need to create protocol buffer (protobuf) request and response messages. They are wrappers around the input arguments and output returns to and from the RPC function. Conveniently, PhysioLabXR automatically generate the protobuf messages for you when you define an RPC function in PhyScript. The protobuf messages are generated based on python type hints, which should be familiar to Python intermediate users. If you are not, you can learn more about type hints in Python here.
Why RPC?¶
More generally, RPC promotes stateless programming, which is a good practice for parallel programming. It simplifies data processing pipelines, if you have an close-loop experiment with external stimulus presentation software, instead of keeping track of the state of the experiment in PhyScript, you can simply call the function in the external software to update the state of the experiment, or request specific processing to be performed on the data such as training a machine learning model, or request an inference from a trained model.
Basic Concept¶
PhysioLabXR builds on top of gRPC, a mature and language-agnostic RPC framework. It comes with a compiler that compiles the any functions defined in a PhyScript into a remote procedure call (RPC) server. Any functions decorated with the @rpc decorator are compiled to RPC functions.
For example, the following function is defined in a PhyScript:
@rpc
def SayHello(self, name: str) -> str:
return "Hello, %s!" % name
When you run the script, the compiler will generate all the necessary code to create an RPC server and expose the SayHello function to the RPC client. You can find more information about the compiler in the compiler documentation.
Important
Functions decorated with @rpc has some requirements such as 1) all its argument must have type hints. 2) must be declared as a method in PhyScript class 3) the first argument must be self. Read more about
the requirements in the RPC compiler docs.
To call the RPC from a client, you need to pass in the protocol buffer (protobuf) request. The return value will be a protobuf response. Both the request and response are automatically generated by the PhysioLabXR RPC compiler. The request is always named <function name>Request and the response is always named <function name>Response. For example, the request and response for the SayHello function will be as follows:
message SayHelloRequest {
string name = 1;
}
message SayHelloResponse {
string message = 1;
}
Once the server is running, you can call the SayHello function from a client in other languages. Check out the follow simple example to see how to call the SayHello function from a Python client.
Supported Languages¶
Currently, PhysioLabXR’s native RPC compiler supports Python and C# as the client language. The client-side code is automatically generated when PhyScript with RPC functions is executed from the scripting interface.
Quick Start with an Example¶
Let’s start with a simple example. We will create a PhyScript with an RPC function that takes a name as an argument and returns a greeting message. Then we will create a Python client to call the RPC function.
Here’s the complete PhyScript code with an RPC function defined in it:
from physiolabxr.rpc.decorator import rpc
from physiolabxr.scripting.RenaScript import RenaScript
class RPCExample(RenaScript):
def __init__(self, *args, **kwargs):
"""
Please do not edit this function
"""
super().__init__(*args, **kwargs)
# Start will be called once when the run button is hit.
def init(self):
pass
# loop is called <Run Frequency> times per second
def loop(self):
print(f'Loop: rpc server {self.rpc_server}')
# cleanup is called when the stop button is hit
def cleanup(self):
print('Cleanup function is called')
@rpc
def SayHello(self, name: str) -> str:
return "Hello, %s!" % name
From the scripting interface, load the above script. Then run the script, the RPC server will be started and the SayHello function is now callable from clients!
Note
When compiled, by default, the Python client definition will be generated in the same directory as the script file. You can define additional client languages and locations in the RPC Options in the lower right corner of the scripting interface.
To call the SayHello function from a different environment, you need to create an RPC client. Here is a simple client code in Python.
import grpc
from physiolabxr.examples.rpc.HellowWorld import HelloWorldRPCExample_pb2_grpc, HelloWorldRPCExample_pb2
channel = grpc.insecure_channel('localhost:8004') # replace the port with the port number that shows on the RPC options button once the server is running
stub = HelloWorldRPCExample_pb2_grpc.HelloWorldRPCStub(channel)
response = stub.SayHello(HelloWorldRPCExample_pb2.SayHelloRequest(name='python client'))
print(f"{response.message}")
You may need to replace the port number with the actual port number that shows on the RPC options button once the server is running. Run the client code, and you should see the following output: Hello, python client!.
Here’s a video showing the above example in action:
Setup for C Sharp (Unity)¶
You will need some additional setups for C# clients. Please refer to the C# (Unity) example for more details.
More examples¶
Examples with different number of arguments and returns are available for server in PhyScript, and clients in Python and C#.
Please find their documentation in the following links: