The Renderer Protocol consists of messages that are sent between the Browser Interface and the Renderer. These messages are defined in the [Renderer Protocol] folder (http://github.com/decentraland/unity-renderer/tree/dev/renderer-protocol) using the proto3 format.
The message exchange is defined by RPC calls, which are bi-directional Services. For example, the Renderer
can call the Browser Interface
using a KernelService
, or the Browser Interface
can call the Renderer
using a RendererService
.
To add a message to the Renderer Protocol, you must first add it to the protocol definition
Example of RendererService
.
Example of KernelService
.
After adding a KernelService
or RendererService
, you must run npm run build
to regenerate the Renderer Protocol.
The Renderer acts as an RPC Server
, while the Browser Interface is the RPC Client
. The Renderer implements a service called TransportService which allows it to create an RPC Transport that functions as an InverseRPC. This allows the Browser Interface to act as anRPC Server
and the Renderer to act as an RPC Client
. As a result, services can be implemented in either direction. There are both Browser Interface services and Renderer services for the Renderer protocol.
NOTE: You can read the following articles to understand RPC article 1; article 2
In the next example, we will implement the service described in the protobuf below:
// Service implemented in Renderer and used in Browser Interface
service EmotesRendererService {
// Triggers an expression in our own avatar (use example: SDK triggers a expression)
rpc TriggerSelfUserExpression(TriggerSelfUserExpressionRequest) returns (EmotesResponse) {}
}
Once we've generated the code, we need first to create a folder named EmotesService
in the path Assets\Scripts\MainScripts\DCL\WorldRuntime\KernelCommunication\RPC\Services
and then create the following files:
RPC.Service.Emotes.asmdef
EmotesRendererServiceImpl.cs
In the EmotesRendererServiceImpl.cs
file, we need to add the code:
using System.Threading;
using Cysharp.Threading.Tasks;
using rpc_csharp;
namespace RPC.Services
{
public class EmotesRendererServiceImpl : IEmotesRendererService<RPCContext>
{
public static void RegisterService(RpcServerPort<RPCContext> port)
{
EmotesRendererServiceCodeGen.RegisterService(port, new EmotesRendererServiceImpl());
}
public UniTask<EmotesResponse> TriggerSelfUserExpression(TriggerSelfUserExpressionRequest request, RPCContext context, CancellationToken ct)
{
DataStore.i.myInventedDataStoreWithAGoodName.avatarExpression new AvatarExpression()
{
Id = request.Id,
Command = UserProfile.EmoteSource.Command,
Timestamp = UTC.Now
}
return default;
}
}
}
To run this code in the Browser Interface, we need first create the file packages/renderer-protocol/services/emotesRendererService.ts
and then paste this code:
import { RpcClientPort } from '@dcl/rpc'
import * as codegen from '@dcl/rpc/dist/codegen'
import { EmotesRendererServiceDefinition } from 'shared/protocol/decentraland/renderer/renderer_services/emotes_renderer.gen'
import defaultLogger from 'shared/logger'
export function registerEmotesService<Context>(
clientPort: RpcClientPort
): codegen.RpcClientModule<EmotesRendererServiceDefinition, Context> | undefined {
try {
return codegen.loadService<Context, EmotesRendererServiceDefinition>(clientPort, EmotesRendererServiceDefinition)
} catch (e) {
defaultLogger.error('EmotesService could not be loaded')
return undefined
}
}
Then, in packages/shared/renderer/sagas.ts
we need to add the following line:
...
const emotesService = registerEmotesService(clientPort) // add this line here.
...
Finally, in packages/shared/renderer/types.ts
we need to add the service in the RendererModules type:
export type RendererModules = {
...
emotesService: codegen.RpcClientModule<EmotesRendererServiceDefinition, any> | undefined // add this line here.
}
To use it, we call it as:
getRendererModules(store.getState())
?.emotesService?.triggerSelfUserExpression({ id: req.predefinedEmote })
.catch(defaultLogger.error)
Note of caution: When you're migrating messages, remember that the Kernel must send the message with the renderer protocol and must keep the old way (with JSON based mechanism) for a good period of time to avoid compatibility issues.
- protocol: decentraland/protocol#81
- unity-renderer: #3605
Create the service:
In: packages/renderer-protocol/inverseRpc/services/emotesService.ts
import { RpcServerPort } from '@dcl/rpc'
import { RendererProtocolContext } from '../context'
import * as codegen from '@dcl/rpc/dist/codegen'
import { EmotesKernelServiceDefinition } from 'shared/protocol/decentraland/renderer/kernel_services/emotes_kernel.gen'
import { allScenesEvent } from '../../../shared/world/parcelSceneManager'
import { sendPublicChatMessage } from '../../../shared/comms'
export function registerEmotesKernelService(port: RpcServerPort<RendererProtocolContext>) {
codegen.registerService(port, EmotesKernelServiceDefinition, async () => ({
async triggerExpression(req, _) {
allScenesEvent({
eventType: 'playerExpression',
payload: {
expressionId: req.id
}
})
const body = `␐${req.id} ${req.timestamp}`
sendPublicChatMessage(body)
return {}
}
}))
}
Add the service to the registering list in: packages/renderer-protocol/inverseRpc/rpcServer.ts
async function registerKernelServices(serverPort: RpcServerPort<RendererProtocolContext>) {
...
registerEmotesKernelService(serverPort)
}
And done! We've implemented the Kernel Service.
To use it from the Renderer we need to add the Client Service to the IRPC
in: Assets/Scripts/MainScripts/DCL/WorldRuntime/KernelCommunication/RPC/Interfaces/IRPC.cs
public interface IRPC : IService
{
...
public ClientEmotesKernelService Emotes();
}
Then we load the module in: Assets/Scripts/MainScripts/DCL/WorldRuntime/KernelCommunication/RPC/RPC.cs
private ClientEmotesKernelService emotes;
public ClientEmotesKernelService Emotes() => emotes;
private async UniTaskVoid LoadRpcModulesAsync(RpcClientPort port)
{
emotes = await SafeLoadModule(EmotesKernelServiceCodeGen.ServiceName, port,
module => new ClientEmotesKernelService(module));
...
}
Finally, we can use it with the following code:
ClientEmotesKernelService emotes = DCL.Environment.i.serviceLocator.Get<IRPC>().emotes;
emotes?.TriggerExpression(new TriggerExpressionRequest()
{
Id = id,
Timestamp = timestamp
});