TeamHypersomnia / Hypersomnia

Multiplayer top-down shooter made from scratch in C++. Play in your Browser! https://hypersomnia.io Made in 🇵🇱
https://hypersomnia.io/
GNU Affero General Public License v3.0
1.08k stars 47 forks source link

Implement reserve(size_t) for augs::pool (class used to pool entities), that expands the pool when no free indirector is available for allocation #239

Closed geneotech closed 6 years ago

geneotech commented 7 years ago

Hint: the underlying concept of augs::pool is thoroughly described here: https://gamedev.stackexchange.com/a/33905/16982

Considerations: Right now, space in the pool is only initialized on construction, and an exception is thrown when there is no more space for allocation.

systems inside game/systems_inferred/ hold a vector whose nth element corresponds to a cache (inferred state) of an entity whose indirection_index equals n. Upon resizing entity pool of the cosmos, these vectors will need to be resized as well, preferably by a call to reserve_caches_for_entities.

geneotech commented 6 years ago

I sort of didn't predict that it'll be dangerous to automatically expand the pool during logic, for example when a bullet entity or a shell entity is created, because if expansion happens at this point, then some dangling pointers inside existent entity_handles might become corrupted; and I've determined that usage of pointers for handles is twice as fast as just dereferencing the id each time.

I will then still require the user to explicitly make a reservation of an arbitrary number of entities when the cosmos is created, and throw when the pool of entities becomes full. The reservation might still be called manually somewhere outside of logic processing so that no handles are endangered; for example, the editor should allow the number of reserved entities to be configured in GUI.

At some point however, the expansion should be possibly done by the logic without pointer invalidations, because one cannot predict just how many new entities will a single logic step need; but for now I will just preallocate a huge amount and not care further. Pre-reservation of 100 000 entities (much more than we need), along with all components and audiovisual caches, takes up somewhere around 950 MB, and does not at all decrease runtime performance, so we should be fine.

geneotech commented 6 years ago

Future solution:

Let us introduce queue_create_entity, that accepts a logic_step, and returns a new_entity_handle. The returned object would contain an entity_id for the new entity, and a tuple of components (in-place, as opposed to a tuple of ids) with whom it is possible to perform read/write, as with the standard entity_handle.

The point is that the entity itself is not yet allocated within cosmos. pool.h will need to support delayed allocation, where it is possible to allocate a new entity_id without allocating the entity itself; when the pool is full, only the indirectors would be affected as well, avoiding invalidation.

Only the ~new_entity_handle will post a new_entity_message via the passed logic_step. Then, some entity_creation_system would iterate over new_entity_messages and create new entities in isolation, where it would be nearly impossible to invalidate some dangling entity_handles.

The logic can then:

{
    const auto round_entity = cosmos.queue_clone_entity(step, magic_missile_def);

    auto& sender = round_entity.get<components::sender>();
    sender.set(gun_entity); // i/o possible as with the standard handles

    // ...

    messages::interpolation_correction_request request;

    // round_entity contains an entity_id, even though it is still a dead id.
    // it is possible and correct to already store the id somewhere else,
    // but care must be taken that the id will not be referred to until entity_creation_system does its work.

    request.subject = round_entity.get_id();

} // round_entity goes out of scope; it pushes its id and the entity definition as a message for entity_creation_system

Alternatives:

Current state of affairs:

All of the above functions will also probably be called frequently by the editor code, but due care will be taken to isolate the call sites from the handles or pointers.

geneotech commented 6 years ago

Closed due to low priority. Refer to Current state of affairs

geneotech commented 6 years ago

Also for huge sizes this might fail... ensure(new_size <= std::numeric_limits::max());

but that would be only for around half the range of std::size_t