Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    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:
    326
    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