Closed geneotech closed 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.
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_message
s and create new entities in isolation, where it would be nearly impossible to invalidate some dangling entity_handle
s.
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:
cosmos
will be painfully slow, while it is quite an important operation, especially during networking.create_entity
and clone_entity
and somehow ensure that other handles do not get invalidated in the process, or are manually reacquired, but if a bug occurs it might be very hard to hunt down.try { }
block, and if it throws, expand the pools and perform the logic step again. This would require that we have a means to rewind the cosmos, thus that we have a history of snapshots and entropies. Possible in editor, but would be an unnecessary overhead in local play.entity_handle
constructor could push a reference to that entity handle to some global stack, while ~entity_handle
would pop it. This however may slow down dereferences, of which there are many, and which have been proven to be the logic's bottleneck.pool
have a member int version
that is incremented on every expansion. The entity_handle
, apart from the pointer, stores the pool's version at the time of acquiring that pointer; if, when using that pointer, the stored pool's version differs from the current pool version, dereference entity_id
again. We still need to do something about acquired component pointers - it may, though, impair performance as they are constantly acted upon.Current state of affairs:
cosmos::create_entity
will not invalidate any entity_handle
, but will throw an exception instead when the reservation would be required, in order to fail early.cosmos::delete_entity
can invalidate some entity_handle
s (due to a move of the last element), but it is only called inside cosmic_delta
and destroy_system
, where it is called in isolation and there are no endangered entity_handle
s at the call site.cosmic_delta
and inside cosmos::delete_entity
, where it is called in isolation and there are no endangered component pointers at the call site.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.
Closed due to low priority. Refer to Current state of affairs
Also for huge sizes this might fail...
ensure(new_size <= std::numeric_limits
but that would be only for around half the range of std::size_t
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.