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. Dismiss Notice

Determinism in CopyEntitiesFrom and MoveEntitiesFrom

Discussion in 'Entity Component System' started by redwren, Jun 21, 2022.

  1. redwren

    redwren

    Joined:
    Aug 2, 2019
    Posts:
    69
    I've been trying to get to the bottom of some non-determinism in the initial state of a custom simulation world. Using a SceneSystem in the custom world seems to load subscene contents deterministically, but copying the same entities out of e.g. the default world leads to non-deterministic results.

    Are these methods supposed to be deterministic? Is there a better way to additively copy entities into a world with deterministic order?
     
  2. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    228
    I don't think anybody checked for determinism there, because nobody needed it yet. I'm curious where the nondeterminism comes from, though; when you step through two runs going different ways, can you tell where the divergence begins?
     
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,574
    You should never rely on entities determinism.
    Most likely you may get chance, to get same orders of entities indexes few times in a session.
    Then after restart game / Unity, orders may change.
    That specifically is true, if you use jobs and entities to create them.
    Also, entities orders may change at runtime.
    Plus, CPU architecture may also affect orders of entities.

    If you change something in the scenes, your loading orders of entities will most likely change.
    Likely entities will keep order, if you use for example EntityManager, at loading time.
    But safest is, to use Native Collection, to track entities, to keep them in order as you desire.
     
  4. msfredb7

    msfredb7

    Joined:
    Nov 1, 2012
    Posts:
    143
    I have a similar use case and I tracked down the divergence to this method call:
    upload_2023-8-8_14-11-6.png
    For some reason, this call will not always order the new chunks in the same way. This causes the following "var instantiated" query to get the chunks in a different order and MoveEntitiesFromInternalQuery to move entities in a different order (so the Entities in the destination world will have different indices).

    I found the divergence by diff'ing all the Entity debug infos like this:
    upload_2023-8-8_14-15-43.png
    and using WinMerge to compare the files between two runs.

    Only the _postAddComp.txt files had a divergence. The two files before were identical.

    I'll try to dig a bit more. Would you know what could cause AddComponent to produce different results? In both runs, the world is pretty much brand new and empty (except for system entities).
     
  5. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    228
    I don't know, but I could imagine type indices changing sometimes, since they aren't specifically trying to be stable. Is this between different runs of the same player build, different runs in the editor, or between different machines? Also, if it's a player build, is it il2cpp or mono?
     
  6. msfredb7

    msfredb7

    Joined:
    Nov 1, 2012
    Posts:
    143
    Ok, I found the issue.

    EntityManager.CopyEntitiesFrom uses
    EntityManager.AddComponent which uses
    EntityComponentStore.CreateEntityBatchList which uses
    EntityComponentStore.SortEntityInChunk which is not deterministic because ptr values are used to sort.
     
    TheOtherMonarch and UniqueCode like this.
  7. msfredb7

    msfredb7

    Joined:
    Nov 1, 2012
    Posts:
    143
    I think making EntityManager.AddComponent deterministic would be great, since it's a very commonly used method.
     
    TheOtherMonarch likes this.
  8. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    791
    CopyEntitiesFrom sister function CopyAndReplaceEntitiesFrom says it is deterministic and during my quick look at I did not see AddComponent. I was never able to test CopyAndReplaceEntitiesFrom because of other bugs. If AddComponent is nondeterministic then that really breaks determinism for a lot of code.
     
    msfredb7 likes this.
  9. msfredb7

    msfredb7

    Joined:
    Nov 1, 2012
    Posts:
    143
    Unfortunately, since each scene load adds new entities, I believe I cannot use CopyAndReplace in my use case. Every call would erase the existing entities.
     
  10. msfredb7

    msfredb7

    Joined:
    Nov 1, 2012
    Posts:
    143
    For anyone who wants EntityManager.AddComponent to be deterministic, here's what worked for me:
    In EntityInChunk.cs, change this:
    Code (CSharp):
    1. public int CompareTo(EntityInChunk other)
    2. {
    3.     ulong lhs = (ulong)Chunk;
    4.     ulong rhs = (ulong)other.Chunk;
    5.     int chunkCompare = lhs < rhs ? -1 : 1;
    6.     int indexCompare = IndexInChunk - other.IndexInChunk;
    7.     return (lhs != rhs) ? chunkCompare : indexCompare;
    8. }
    to this:
    Code (CSharp):
    1.  
    2. public int CompareTo(EntityInChunk other)
    3. {
    4.     // order by archetype first
    5.     int archetypeCompare = Chunk->Archetype->StableHash.CompareTo(other.Chunk->Archetype->StableHash);
    6.     if (archetypeCompare != 0)
    7.         return archetypeCompare;
    8.  
    9.     // then by chunk index (within archetype chunk list)
    10.     int lhs = Chunk->ListIndex;
    11.     int rhs = other.Chunk->ListIndex;
    12.     int chunkCompare = lhs < rhs ? -1 : 1;
    13.  
    14.     // then by entity index
    15.     int indexCompare = IndexInChunk - other.IndexInChunk;
    16.     return (lhs != rhs) ? chunkCompare : indexCompare;
    17. }
    I'll update this post if I ever find an issue with this approach (crash, bug, etc.).
     
    Last edited: Sep 17, 2023