working rpc in pre.15 now gets a nested native container error in pre.47

[BurstCompile]
public struct TestRPC : IComponentData, IRpcCommandSerializer<TestRPC>
{
   
   
    public NativeHashMap<FixedString64Bytes, int> items;




    [BurstCompile(DisableDirectCall = true)]
    private static void InvokeExecute(ref RpcExecutor.Parameters parameters)
    {
        RpcExecutor.ExecuteCreateRequestComponent<TestRPC, TestRPC>(ref parameters);
    }

    public void Serialize(ref DataStreamWriter writer, in RpcSerializerState state, in TestRPC data)
    {
       
        writer.WriteInt(data.items.Count);

        foreach (var item in data.items)
        {
           
            writer.WriteFixedString64(item.Key);
           
            writer.WriteInt(item.Value);
          
        }
        Debug.Log("SERIALIZED TEST RPC");
    }

    public void Deserialize(ref DataStreamReader reader, in RpcDeserializerState state, ref TestRPC data)
    {
       
        int count = reader.ReadInt();
        NativeHashMap<FixedString64Bytes, int> items = new NativeHashMap<FixedString64Bytes, int>(count,Allocator.Persistent);
        for (int i = 0; i < count; i++)
        {
           
            items.Add(reader.ReadFixedString64(), reader.ReadInt());
        }
        data.items = items;
       
        Debug.Log("DESERIALIZED TEST RPC");
    }

    public PortableFunctionPointer<RpcExecutor.ExecuteDelegate> CompileExecute()
    {
       
        return InvokeExecuteFunctionPointer;
    }

    static readonly PortableFunctionPointer<RpcExecutor.ExecuteDelegate> InvokeExecuteFunctionPointer = new PortableFunctionPointer<RpcExecutor.ExecuteDelegate>(InvokeExecute);
}



[UpdateInGroup(typeof(RpcCommandRequestSystemGroup))]
[CreateAfter(typeof(RpcSystem))]
[BurstCompile]
partial struct TestRpcCommandRequestSystem : ISystem
{
    RpcCommandRequest<TestRPC, TestRPC> m_Request;
    [BurstCompile]
    struct SendRpc : IJobChunk
    {
        public RpcCommandRequest<TestRPC, TestRPC>.SendRpcData data;
        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
        {
            Assert.IsFalse(useEnabledMask);
            data.Execute(chunk, unfilteredChunkIndex);
        }
    }
    public void OnCreate(ref SystemState state)
    {
        m_Request.OnCreate(ref state);
    }
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var sendJob = new SendRpc { data = m_Request.InitJobData(ref state) };
        state.Dependency = sendJob.Schedule(m_Request.Query, state.Dependency);
    }

    public void OnDestroy(ref SystemState state)
    {
       
    }
}

the above code works fine in 1.0.0-pre.15 but when trying to send the same rpc in pre.47 i get a nested native container error when i cant see a nested container at all, i didnt think nested native containers were ever allowed so not sure why it would’ve worked in the previous version, any ideas on how to fix this? i can use a workaround by sending an rpc for each item in the list but it just feels wrong

error message

InvalidOperationException: The ComponentTypeHandle SendRpc.JobData.data.actionRequestType can not be accessed. Nested native containers are illegal in jobs.
Unity.Entities.WorldUnmanagedImpl+UnmanagedUpdate_0000152E$BurstDirectCall.Invoke (System.Void* pSystemState) (at :0)
Unity.Entities.WorldUnmanagedImpl.UnmanagedUpdate (System.Void* pSystemState) (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/WorldUnmanaged.cs:810)
Unity.Entities.WorldUnmanagedImpl.UpdateSystem (Unity.Entities.SystemHandle sh) (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/WorldUnmanaged.cs:895)
Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/ComponentSystemGroup.cs:697)
UnityEngine.Debug:LogException(Exception)
Unity.Debug:LogException(Exception) (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/Stubs/Unity/Debug.cs:19)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/ComponentSystemGroup.cs:708)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/ComponentSystemGroup.cs:661)
Unity.NetCode.RpcCommandRequestSystemGroup:OnUpdate() (at ./Library/PackageCache/com.unity.netcode@1.0.0-pre.47/Runtime/Rpc/RpcCommandRequest.cs:84)
Unity.Entities.SystemBase:Update() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/SystemBase.cs:418)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/ComponentSystemGroup.cs:703)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/ComponentSystemGroup.cs:667)
Unity.Entities.SystemBase:Update() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/SystemBase.cs:418)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at ./Library/PackageCache/com.unity.entities@1.0.0-pre.47/Unity.Entities/ScriptBehaviourUpdateOrder.cs:526)

Using NativeContainer as a field in a component is supported but you can pass that component to a Job.
Entities added lots of check to guarantee that this pattern cannot be used. The reason is all around the fact safety handles cannot be collected and checked correctly in all cases.

The SendRpc.JobData.data.actionRequestType is a ComponentTypeHandle that is by per se a NativeContainer. So this become a nested container pattern.

You can work around that by either:

  • Use UnsafeHashmap instead.
  • Try adding a [NativeDisableContainerSafetyRestriction] to the field
[NativeDisableContainerSafetyRestriction]
public NativeHashMap<FixedString64Bytes, int> items;

The latter will not track race conditions or access to the map at the same time the RPC job is running. So you need to use another way to track the dependency.

thanks for the explanation, unsafehashmap did the trick

Another alternative is to use a FixedContainer as a field and do the “serialization/copy” to it when constructing the RPC. RPC cannot be larger than 1 MTU anyway (excluded headers etc, so more toward 1400 bytes).
So looping over all the hash key/value works only if the key value pair fit.
A possibility is also something like:

unsafe
{
    FixedString4096Bytes data;
    var streamWriter = new DataStreamWriter(data.GetUnsafePtr(), 1400);
    SerializeHashmap(streamWriter);
    streamWriter.Flush();
    data.Length = streamWriter.Length;
    var e = EntityManager.CreateEntity(
        typeof(MyRPC),
        typeof(SendRpcCommandRequestComponent));
    EntityManager.SetComponentData(e, new RPC
    {
        value = data
    });
}

That is slightly sub-optimal, but do the job too

That’s so much cleaner than what I was doing thanks for the tip