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

How to separate entities by distance?

Discussion in 'Entity Component System' started by arczewski, Nov 7, 2020.

  1. arczewski

    arczewski

    Joined:
    Mar 24, 2018
    Posts:
    8
    I'm making multiplayer prototype. It will have pretty large map for example 512x512 when player is of size 1x1. Each player will see a max of 16x16 so I want to send only regions on which he stands and additional regions next to his position. I previously was generating state for each player and radius from him. Currently I want to generate state for each chunk 16x16 and then send it to selected players.

    Case:
    Player count: 3000
    Map size: 512x512

    Previously I was generating state for each player so the worst and the best scenario was to always generate 3000 network states.

    When serializing per chunk, bad case has total of 1024 states and best is 1 when all players are in a single chunk.

    Solution I came up with is to use ISharedComponentData with int2 as a position.
    Each player position is rounded to 0, 16, 32, 48, 64 etc. and if he moves out of current chunk his ISharedComponent changes.

    Then when building states I'm using:
    Code (CSharp):
    1. EntityManager.GetAllUniqueSharedComponentData<ChunkNetworkSharedPosition>(_cachedSharedComponentList);
    iterate over it and generate state entity for each value of shared component. Is this good usecase of ISharedComponentData or will it be to slow/I should rethink this system?
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,579
    One possible options is, DynamicBuffers.
    Each chunk is entity. Entities chunks hold players.
    When players move to new chunks, you just update buffers accordingly.

    You could also build multihashmaps, where key is chunk index and values are players.
     
  3. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    I have not yet played with network yet but I guess my first way of doing would be to do a sphere cast on each player and populate a dynamic buffer of entity/distance on each entity. I don't know about performance but I would batch these cast after the transform systems and use the buffer after for each system that need it.
    Based on the movement speed of your entites you can perfomr this batched sphere cast in a wider interval that the actual simulation at least put it in the fixed update group.
     
  4. arczewski

    arczewski

    Joined:
    Mar 24, 2018
    Posts:
    8
    @Antypodish How to code something like that? If I have entities with position component and depending on this component value I want to put it in the right dynamic buffer I don't see how to solve something like that. I could iterate over all entities with position, do some calculation and then how to decide in which entity buffer it should go?
    What I mean is: I got bunch of entities, I calculated that half of them should go to buffer A and the others to buffer B how to pass them to this buffers? I cannot do nested foreach. MultiHashMaps is a solution but is it a ECS solution?
     
  5. arczewski

    arczewski

    Joined:
    Mar 24, 2018
    Posts:
    8
    @WAYN_Games I want to avoid doing calculation per player. If 100 players is on chunk X I don't need to calculate state/distance for each one of them, I can just calculate state once for this chunk and everybody on this chunk will recive this single state.

    What I'm currently doing is:
    1. Job loops thru every entity with NetworkSync component and push serialization result for each object to NativeArray
    2. Now I have a NativeArray with every entity state(for example position).
    3. I have entities with NetworkChunk component containing informations about chunk position and chunk size.
    4. Now I want to push onto each NetworkChunk dynamic buffer coresponding object states by using previously serialized position.

    I don't know how to do 4th step. I could pass this native array to a job iterating over all chunks and in this job I would verify if object position is inside chunk borders but this approach will lead to iterating over NxM entities(for each chunk I would need to iterate over all serialized objects).
     
  6. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    Again I'm not even a noob in network stuff but my naive thinking leads me to think I would
    1. have the systems I described in my previous post to define the scope/priority of entity to synchonize for a player (connection)
    2. then have a system to compute the sate of each entity put those in a nativehashmap
    3. lastly have a system that read for that hash map to send the updated state of the most imporatant entity in scope or all of them if it fit the packet size.

    We could probaly have a system between 2 and 3 that does the delta compression maybe using some caching mechanisms to avoid doing it multiple time for each player.
     
  7. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,579
    Yes it is.
    Look forum for multihashmap. Strictly saying NativeMultiHashmap. We got few threads about this subject.
    Then of course look into boids example.
    https://github.com/Unity-Technologi...s/Assets/Advanced/Boids/Scripts/BoidSystem.cs
    EntityComponentSystemSamples/ECSSamples/Assets/Advanced/Boids/Scripts/BoidSystem.cs /

    Mind, Boids example is using, IJobNativeMultiHashMapMergedSharedKeyIndices, and this is obsolete and removed with latest Entities packages. We have discussions on the replacement too.

    There are multiple ways, in which you can define, in which chunk your player is.
    If you have global position, you just do as you did with int2, use its hash as key in NativeHashMap.
    Then use values as Entities of chunks.

    Other way, is to know neighbors entities chunks of each chunk.
    So if chunk got 4 neighbours and player move to the left for example, you grab left neighbour chunk entity.

    Either way, once you got neighbour chunk entity, you can access DynamiBuffer of players in this chunk.
     
  8. arczewski

    arczewski

    Joined:
    Mar 24, 2018
    Posts:
    8
    @Antypodish Thank you for you help. I manage to do this with hashmap and dynamic buffers - part of it. I manage to append buffers(with command buffer) when entity moves from one chunk to the other but I have a hard time with removing it from previous chunk(dynamic buffer). It seems like I will need to recreate dynamic buffer without removed entity. Is my thinking correct or there is an easier way of doing this?
     
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,579
    You can "clear" value from buffer.
    That will leave "hole" in it.

    Then you can either store empty indexes in buffer, to fill with new values.
    Or if you want to compress, defrag and resize the buffer, move indexes from the back of the buffer, to the holes. And resize buffer, to actual number of elements.

    Alternatively, if you have all known entities / players in given chunk, you can just use
    Code (CSharp):
    1. myBuffer.CopyFrom ( nativeArrayOfKNownPlayers ) ;
     
  10. arczewski

    arczewski

    Joined:
    Mar 24, 2018
    Posts:
    8
    But CopyFrom will not work in job right?
     
  11. UsmanMemon

    UsmanMemon

    Joined:
    Jan 24, 2020
    Posts:
    87
    Imo using shared component is not recommend because map size is 512 * 512 that means max unique shared components = 262144 and each archetype chunks waste maximum of 16kb + some internal data for archetype and chunks, so that's 262144 * 16 = 4194304 kb or 4gb so that's potentially 4 gb of memory wasted. I don't think ecs is optimized for 262144 unique components. If you are rounding player position to intervals of 16 than you may use SCs but still there will be 16 * 16 = 256 different components and only 4mb will be wasted.
     
  12. arczewski

    arczewski

    Joined:
    Mar 24, 2018
    Posts:
    8
    @UsmanMemon Thanks for answer. Im going in direction of stroing entities in dynamic buffers per network zone/chunk. About rounding position. Yes I'm rounding it to intervals of 16 and possibily in the future rounding 16 => 1, 32 =>2 and using short to waste less memory.

    As for my previous question about CopyFrom in job. I had problem with something different(native array usage without read only) and missinterpreted it as a problem with dynamic buffer so CopyFrom is actually possible inside ForEach.

    Just starting with ecs so sorry for dumb questions. Thread can be closed. Initial problem is solved using hashmaps.