Search Unity

Ultra slow entity duplication (Snapshot creation)

Discussion in 'Entity Component System' started by e199, Mar 18, 2019.

  1. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    For rollback framework I store changed entities, by duplicating them, marking as Disabled and storing in NativeArray

    There is a problem when you try to do in for 2k entities 30 times in a frame. That happens when you got new Input from server and need to rollback, apply input and do fast forward.


    What happens there is I get all entities, duplicate, attach Disabled tag, store it on temp array and throw it into my API for keeping it.
    Code (CSharp):
    1. public class CaptureStateSystem : ComponentSystem, IRollbackLink
    2.     {
    3.         public RollbackExecutorBase Executor { get; set; }
    4.  
    5.         protected override void OnUpdate()
    6.         {
    7.             var entities = Executor.TrackedGroup.ToEntityArray(Allocator.TempJob);
    8.             var newEntities = new NativeArray<Entity>(entities.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    9.          
    10.             Profiler.BeginSample("DUPLICATING ENTITIES");
    11.             var len = entities.Length;
    12.             for (var i = 0; i < len; i++)
    13.             {
    14.                 var e = entities[i];
    15.                 var storedE = EntityManager.Instantiate(e);
    16.                 EntityManager.AddComponentData(storedE, new Disabled());
    17.                 newEntities[i] = storedE;
    18.             }
    19.             Profiler.EndSample();
    20.  
    21.             Executor.StoreSnapshot(ref newEntities);
    22.             newEntities.Dispose();
    23.             entities.Dispose();
    24.         }
    25.     }
    You may think about Jobifying it ;)
    But there is a problem, you can't get entity remap infomation which was used from commandBuffers. So I used 2 jobs:


    4 times performance degradation xD


    But that's not over, there is an API, which lets you add component to NativeArray<Entity> ;)
    Well, I thought it has some optimizations....
    Code (CSharp):
    1. public void AddComponent(NativeArray<Entity> entities, ComponentType componentType)
    2.         {
    3.             for(int i =0;i != entities.Length;i++)
    4.                 AddComponent(entities[i], componentType);
    5.         }
    The most useful would be ability to duplicate chunks.

    That is used for only changed entities.
    How would you create snapshots?
     
  2. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    As a temporary solution I will store snapshot each ~5th frame
    Simulation is pretty light, so a few additional frames won't make much difference
     
  3. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    For rollback of entire state i think you should really think about processing data per chunk, not per entity. Reaching into the lower level API's.
     
  4. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    We can't duplicate chunks AFAIK, so what I should do with them?
     
  5. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I think I read in a recent documentation about creating a chunk with entities/components (it caught my eye as I had a use case for creating a chunk per buffer entity to avoid heap). But I did not find it again, the docs have changed quite a bit over the last weeks. Maybe worth browsing through the sources...
     
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
  7. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    You should be using something similar to a ring buffer for this. Your history is something you can index into by sequence number. The only time you actually remove anything is when the logical entity itself is removed from the game.

    Using entities for handling the event stream is probably not going to work well. Keeping the logical game entities as ECS entities yes. But input handling I would use a separate abstraction, probably a native array per input type that you cycle through (contains say the last 60 or so input sequences). So you just index into the history, iterate from there to current to correct the inputs, then take that result and apply it to the ECS entity in a job.

    Something like that and you don't have to do any work on the main thread.
     
  8. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
  9. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    I don't save/rollback Input entities

    I store them like this {InputForFrame: int FrameNumber}{InputFrom: int ClientId}{ActualCommandComponent}
    Then there is a job to mark entities of current frame as {ExecuteInput}
    And actual InputExecutionSystems look for T command component AND ExecuteInput
    And then job to clear that tag

    For storing/loading/cleanup snapshots I use Frame%RollbackWindow, so there is no leftover entities hanging around forever

    I do it for complete deterministic framework, so I never need to get data from past frames.
    Visual entities keep reference to simulation entities by using {Id: int} component, they can maintain some data between frames