Search Unity

Is it possible to access data of entity by ComponentType or TypeIndex in Job?

Discussion in 'Entity Component System' started by quabug, Jan 2, 2020.

  1. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    I have tried create a `NativeHashMap<int/*TypeIndex*/, ArchetypeChunkComponentTypeDynamic>` and pass it into Job, but `ArchetypeChunkComponentTypeDynamic` cannot be used in `NativeContainer`.

    Code (CSharp):
    1.     struct Job : IJobChunk
    2.     {
    3.         // ArchetypeChunkComponentTypeDynamic is not a valid type of NativeContainer
    4.         public NativeHashMap<int, ArchetypeChunkComponentTypeDynamic> Types;
    5.      
    6.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    7.         {
    8.             chunk.GetDynamicComponentDataArrayReinterpret<A>(Types[ComponentType.ReadOnly<A>().TypeIndex], UnsafeUtility.SizeOf<A>());
    9.         }
    10.     }
     
    Last edited: Jan 3, 2020
    eterlan likes this.
  2. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    While I don't have a direct answer to your question, I have a similar use case for serializing/deserializaing components over the network. I use an array of Burst function pointers (indexed by TypeIndex). The functions are codegen'ed for each possible ComponentType. This leads to max performance (Burstable Jobs) and easy maintenance.
     
    quabug likes this.
  3. elcionap

    elcionap

    Joined:
    Jan 11, 2016
    Posts:
    138
    I thought that TypeIndex wasn't deterministic. I'm doing something close to what you are doing but didn't used TypeIndex thinking that wouldn't be deterministic.

    []'s
     
  4. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    Yup, I neglected to mention that since the OP may not be doing networking stuff. The actual TypeIndex I use is not managed by the TypeManager that comes with Entities. My own "NetTypeIndex" is generated and shared for both client and server to get around the determinism issue and to reduce the size to 1 byte instead of 4.
     
  5. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    Thank you to mention function pointer of burst, I haven't notice it before.

    About your use case on serializing, do you have `ComponentData` like this:
    Code (CSharp):
    1. struct Data : IComponentData
    2. {
    3.     delegate void AccessData(DataAccessor accessor);
    4.     public int Value;
    5.     public void Access(DataAccessor accessor) { ... }
    6. }
    And the function pointer created by
    Code (CSharp):
    1. Entities.ForEach((ref Data data) =>
    2. {
    3.     var functionPointer = BurstCompiler.CompileFunctionPointer<AccessData>(data.Access);
    4. });
    5.  
    But I still have no clue how to create same function pointer just from `Entity`.
    Code (CSharp):
    1. Entities.ForEach((Entity entity) =>
    2. {
    3.     var functionPointer = //create from entity only?
    4. });
    5.  
     
    Last edited: Jan 4, 2020
  6. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    Oh, I was just suggesting that you can statically generate your logic on all the possible ComponentData's into Burst functions. And then just invoke the function using TypeIndex. For example:

    Code (CSharp):
    1. public partial class NetCompProviderSystem {    // Generated class that provides the function pointers
    2.  
    3.     protected override void OnCreate() {
    4.         m_DeserializeFunctions = new NativeArray<FunctionPointer<DeserializeFunction>>(NUM_OF_COMPS, Allocator.Persistent);
    5.         m_DeserializeFunctions[0] = BurstCompiler.CompileFunctionPointer<DeserializeFunction>(CharacterDeserialize);
    6.         ....
    7. }
    8.  
    9.     [BurstCompile]
    10.     static void CharacterDeserialize(ref EntityCommandBuffer.Concurrent cmdbuffer, int index, ref Entity targetEntity, ref DataStreamReader reader, ref DataStreamReader.Context readerCtx) {
    11.         var comp = new Character();
    12.         comp.Deserialize(ref reader, ref readerCtx);
    13.         cmdbuffer.AddComponent<Character>(index, targetEntity, comp);
    14.     }
    15.  
    16.     ....
    17.  
    18. }
    Then use somewhere:

    Code (CSharp):
    1. deserializeFunctions[typeIndex].Invoke(ref cmdBuffer, 0, ref playerEntity, ref reader, ref readerCtx);
     
    Last edited: Jan 4, 2020
  7. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    Thank you for your deserialize code example.
    I am still curious about how you serialize your components.

    Do you create different Job for different type of entity with certain components, like:
    Code (CSharp):
    1. Entities.ForEach((ref Character character, ref Translation translation) => {
    2.     character.Serialize(writer);
    3.     translation.Serialize(writer);
    4. });
    Or do you access component data by its type index during serializing?
    Code (CSharp):
    1. Entities.ForEach((ref Character character, ref TypeIndices typeIndices) => {
    2.     character.Serialize(ref writer);
    3.     // traverse type indices and serialize ComponentData by its type index into writer?
    4. });
     
  8. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    I found a hacky way to do this.
    Concept is simple, use Mono.Cecil to add an `[InternalsVisibleTo("MyAssembly")]` into assembly of Unity.Entities.dll, then from custom code call `ChunkDataUtility.GetComponentDataWithTypeRW` directly to get pointer of data by typeindex.
     
  9. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    Sure, my code for serializing is fairly simple:
    Code (CSharp):
    1. [BurstCompile]
    2. unsafe struct SerializeDataJob<B, C> : IJobForEach_BCC<B, C> where B : struct, IBufferElementData where C : struct, INetComp, IHasTypeIndex {
    3.  
    4.     public unsafe void Execute(DynamicBuffer<B> dataBuffer, [ReadOnly] ref C comp) {
    5.         Serialize(dataBuffer, comp);
    6.     }
    7. }
    8.  
    9. public static unsafe void Serialize<B, C>(DynamicBuffer<B> dataBuffer, C comp) where B : struct, IBufferElementData where C : struct, INetComp, IHasTypeIndex {
    10.     var size = comp.Size();
    11.     var writer = new DataStreamWriter(size, Allocator.Temp);
    12.     comp.Serialize(ref writer);
    13.     var reinterpreted = dataBuffer.Reinterpret<byte>();
    14.     reinterpreted.Add(comp.TypeIndex());
    15.     reinterpreted.AddRange(NativeSliceUnsafeUtility.GetUnsafeReadOnlyPtr(writer.GetNativeSlice(0, size)), size);
    16.     reinterpreted[0] = (byte)(reinterpreted[0] + 1);
    17. }
    Then in OnUpdate:
    Code (CSharp):
    1. // This part can be codegen'ed or coded in manually, depending on your preference.
    2. handle = new SerializeDataJob<PublicNetData, Pos>().Schedule(this, handle);
    3. handle = new SerializeDataJob<PublicNetData, Dir>().Schedule(this, handle);
    4. handle = new SerializeDataJob<PublicNetData, Pitch>().Schedule(this, handle);
    PublicNetData
    is the dynamic buffer (of bytes) that will hold the serialized data. One thing to note is that I also codegen the
    TypeIndex()
    function on each net component.
     
    Last edited: Jan 5, 2020