Wiki

Clone wiki

agtools / Removing Entities

Removing or Killing Entities

The most common case

After spawning entities into the world, some will need to be removed before the 'game level' is completed. Such short-lived objects can include enemies or gun placements, temporary bullets or powerups, keys, temporary effects such as explosions, sparks or puffs etc. etc.

Removing objects is simpler than spawning them. Simply call EntityRemove from the object's tick function.

    EntityRemove(_pself);

Most of the time, you will want to have the entity remove itself, upon satisfying some condition or criteria. The most common examples are: depleted health, elapsed time, contact with another object. However many other useful cases do surface when writing a game.

The main advantage of the self-removal policy (vs other objects removing it) is that links to or from other objects are not involved at all, and messy logic or global pointers (to entities) in the game mainloop is not required. Everything can be safely determined internally by the object itself.

As soon as objects become involved in the deletion of other objects, some additional safety measures need to be taken to ensure that the target object hasn't been deleted already, by 'someone else'. This functionality is provided, but in the majority of cases it is not required, and the game code will be simpler and more efficient without it. Most of the demos and samples use self-removal only, and it can be taken quite far with a little thought and care.

Having said that - it is possible to remove objects via the mainloop (or at least, code outside of the entity system) or have objects remove other objects via pointers. There are cases where this is useful or necessary but should only be used as a last resort.

For one entity (observing object) to safely remove another (target object), it pays to first consider the relationship between the two objects.

Are there any other routes through which the target object can be be removed? Can more than one object remove the target object, using any route, including the one being considered?

If the answer to both of these is no, then the observing object can remove the target object with impunity, without any safety checks.

If the answer to either is yes, then the second object should be validated first, in case another object got to it first - and the link broken if the object was already removed - or otherwise, after it is removed.

To validate an object (in the case below - a parent), you must test three things:

1) check that the link hasn't been broken previously.

2) check that it is not frozen in the KILLED state, and pending removal.

3) check that it hasn't been recycled into a new object since it was last observed.

#!c++
if (_pself->pparent)
{
 // we are still observing across the 'parent' link
 if (
    ((_pself->pparent->f_self & EntityFlags_KILLED) == 0) &&
    (_pself->pparent->rev_self != _pself->rev_parent)
 )
 {
  // OK! it is safe to kill the 'parent' object...
  EntityRemove(_pself->pparent);
 }
 else
 {
  // oops! too late - somebody removed it already during this tick! or...
  // oops! this is no longer the object we thought it was...
 }
 // prevent references to this object in future (at least, via this link)
 _pself->pparent = 0;
}
else
{
 // we are no longer observing across this link
}

If these tests pass, the object pointed is the correct one and is still alive and valid.

The example above combines all 3 tests together, removes the object if successful, and breaks a dead link afterwards.

Few of the demos/samples/tutorials need to use this pattern - which should give some idea of what is possible without it. But some of the more complex AI behaviours are only possible with it.

Note: For link tests to work well, it is necessary to break the link as soon as you are aware the object is invalid - either because you removed it, or because you detect it has been removed. This shortcuts future tests for that link (until a new link is formed, anyway). It is also necessary to implement ALL code using that link as if it may be NULL/0 at any time, and must be tested before use.

Note: Entities never point to invalid or uninitialised memory. They always point to entities in some state or other, active or dead - or NULL/0. This limits the kinds of things which can go wrong across entity links - but pointing at the wrong object can be sufficient to lead to very obscure bugs. Pay attention here. If in doubt, don't optimise the use of links, and prefer safety checks before using all links.

Note: A future revision of AGT will remove the need for testing KILLED state - it will be sufficient to check just the link is present, and the revision is correct. This will make the test more efficient.

Note: Even currently, it is not strictly necessary to test the KILLED state - the EntityRemove function will just fail in a safe manner. However it will not report a failed remove, and will emit a console warning. It therefore makes sense to test KILLED state yourself before removal (for the time being at least).

Note: An alternative mechanism is being considered which removes the need for the revision test as well, but does not remove the need for NULL tests on the links. Whether this gets adopted will depend on the hidden costs involved. It may turn out cheaper to test the rev_ fields before using a link, than to maintain full link tracking internally within the engine. However if it works out better it will be swapped in, further simplifying usage.

Updated