Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Proper RPC serialization

Discussion in 'NetCode for ECS' started by Noctiphobia, Jul 14, 2021.

  1. Noctiphobia

    Noctiphobia

    Joined:
    Jun 9, 2021
    Posts:
    8
    Hi everyone,

    I think I followed everything from the RPC docs to serialize a RPC. However, I'm getting this error:

    Code (CSharp):
    1. InvalidOperationException: Registering RPC Game.Networking.Request.WorldDataRequest multiple times is not allowed
    What did I do wrong?

    Here's my RPC/serialization. For context, the server has generated some map, and wants to send it in batches to the client.
    Code (CSharp):
    1. [BurstCompile]
    2.     public struct WorldDataRequest : IRpcCommand, IRpcCommandSerializer<WorldDataRequest>
    3.     {
    4.         public int RegionCount;
    5.         public FixedList128<int> TargetServerEntitiesAndCounts;
    6.         public FixedList4096<float3> Vertices;
    7.  
    8.  
    9.         public void Serialize(ref DataStreamWriter writer, in RpcSerializerState state, in WorldDataRequest data)
    10.         {
    11.             writer.WriteInt(data.RegionCount);
    12.             writer.WriteInt(data.TargetServerEntitiesAndCounts.Length);
    13.             for (var i = 0; i < data.TargetServerEntitiesAndCounts.Length; ++i)
    14.                 writer.WriteInt(data.TargetServerEntitiesAndCounts[i]);
    15.             writer.WriteInt(data.Vertices.Length);
    16.             for (var i = 0; i < data.Vertices.Length; ++i)
    17.             {
    18.                 writer.WriteFloat(data.Vertices[i].x);
    19.                 writer.WriteFloat(data.Vertices[i].y);
    20.                 writer.WriteFloat(data.Vertices[i].z);
    21.             }
    22.         }
    23.         public void Deserialize(ref DataStreamReader reader, in RpcDeserializerState state, ref WorldDataRequest data)
    24.         {
    25.             data.RegionCount = reader.ReadInt();
    26.             var entitiesLength = reader.ReadInt();
    27.             data.TargetServerEntitiesAndCounts = new FixedList128<int>();
    28.             for (var i = 0; i < entitiesLength; ++i)
    29.                 data.TargetServerEntitiesAndCounts.Add(reader.ReadInt());
    30.             var vertexCount = reader.ReadInt();
    31.             data.Vertices = new FixedList4096<float3>();
    32.             for (var i = 0; i < vertexCount; ++i)
    33.                 data.Vertices.Add(new float3(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat()));
    34.         }
    35.         public PortableFunctionPointer<RpcExecutor.ExecuteDelegate> CompileExecute()
    36.         {
    37.             return InvokeExecuteFunctionPointer;
    38.         }
    39.  
    40.         [BurstCompile]
    41.         private static void InvokeExecute(ref RpcExecutor.Parameters parameters)
    42.         {
    43.             RpcExecutor.ExecuteCreateRequestComponent<WorldDataRequest, WorldDataRequest>(ref parameters);
    44.         }
    45.  
    46.         private static readonly PortableFunctionPointer<RpcExecutor.ExecuteDelegate> InvokeExecuteFunctionPointer = new PortableFunctionPointer<RpcExecutor.ExecuteDelegate>(InvokeExecute);
    47.     }
    Alternatively, when I comment the RpcCommandRequestSystem boilerplate out, nothing gets sent at all. Here's the boilerplate for reference, pretty much copied from the doc:
    Code (CSharp):
    1. public class WorldDataRequestRpcCommandSystem : RpcCommandRequestSystem<WorldDataRequest, WorldDataRequest>
    2.     {
    3.         [BurstCompile]
    4.         protected struct SendRpc : IJobEntityBatch
    5.         {
    6.             public SendRpcData Data;
    7.             public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
    8.             {
    9.                 Data.Execute(batchInChunk, batchIndex);
    10.             }
    11.         }
    12.        
    13.         protected override void OnUpdate()
    14.         {
    15.             var sendJob = new SendRpc {Data = InitJobData()};
    16.             ScheduleJobData(sendJob);
    17.         }
    18.     }
    I figured I could just work around the need to serialize this myself by abusing fixed strings, but obviously I'd rather avoid that and do this properly.
     
  2. tobias_heiles

    tobias_heiles

    Joined:
    Jan 30, 2021
    Posts:
    2
    Try changing line 2 to the following:
    Code (CSharp):
    1. public struct WorldDataRequest : IComponentData, IRpcCommandSerializer<WorldDataRequest>
     
    NikiWalker likes this.
  3. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    241
    Tobias is correct. When you add IRpcCommand, it'll code gen (and self register). When you add IRpcCommandSerializer<WorldDataRequest> it'll do less code-gen, and you must self-register (see example of RpcSetNetworkId) and not use IRpcCommand.
    E.g.
    Code (CSharp):
    1. SystemAPI.GetSingleton<RpcCollection>().RegisterRpc(ComponentType.ReadWrite<[B]WorldDataRequest[/B]>(), default(WorldDataRequest).CompileExecute());
     
    Last edited: Jun 6, 2023