Currently, using RemoveEntity() in an action callback results in undefined behavior, since CBaseNPC behavior (and by extension the actions) is released in UpdateOnRemove() which is directly invoked from the native. This also violates the atomic principle that the Actions were originally built upon (OnEnd() ends up getting called in the invoking action callback, which shouldn't happen). The dirty workaround to this is delaying the removal via AcceptEntityInput() or RequestFrame().
This PR fixes that case, allowing RemoveEntity() to be used in action callbacks without crashes. Object destruction is now handled via destructor hooks and not in UpdateOnRemove(), making objects behave more normally by C++ standards.
As a result, uninstalling factories now removes entities IMMEDIATELY instead of waiting for next frame to prevent invalid memory access, since removing entities immediately calls UpdateOnRemove() and destroys the entity in the same frame. Of course, this may be unstable anyways since destroying entities during a frame isn't recommended, but since this case should only happen in a dev environment it's a drawback I'm willing to live with.
Some factory install/uninstall code had to be changed as a result of this fix. To ensure that RemoveEntityImmediate() can take place, entities will no longer be automatically removed in the OnCoreMapEnd() functions. Factory entities will now be removed at the same time as any other entity in the game upon map change, which unfortunately happens after SM has performed its map cleanup which includes Timer handles. Warnings were added to let developers know that Timer handles with the TIMER_FLAG_NO_MAPCHANGE flag may have been cleaned up already by the time a CEntityFactory's onRemove callback or NextBotActionFactory's OnEnd() callback is fired.
Currently, using
RemoveEntity()
in an action callback results in undefined behavior, since CBaseNPC behavior (and by extension the actions) is released inUpdateOnRemove()
which is directly invoked from the native. This also violates the atomic principle that the Actions were originally built upon (OnEnd()
ends up getting called in the invoking action callback, which shouldn't happen). The dirty workaround to this is delaying the removal viaAcceptEntityInput()
orRequestFrame()
.This PR fixes that case, allowing
RemoveEntity()
to be used in action callbacks without crashes. Object destruction is now handled via destructor hooks and not inUpdateOnRemove()
, making objects behave more normally by C++ standards.As a result, uninstalling factories now removes entities IMMEDIATELY instead of waiting for next frame to prevent invalid memory access, since removing entities immediately calls
UpdateOnRemove()
and destroys the entity in the same frame. Of course, this may be unstable anyways since destroying entities during a frame isn't recommended, but since this case should only happen in a dev environment it's a drawback I'm willing to live with.Some factory install/uninstall code had to be changed as a result of this fix. To ensure that
RemoveEntityImmediate()
can take place, entities will no longer be automatically removed in theOnCoreMapEnd()
functions. Factory entities will now be removed at the same time as any other entity in the game upon map change, which unfortunately happens after SM has performed its map cleanup which includes Timer handles. Warnings were added to let developers know that Timer handles with theTIMER_FLAG_NO_MAPCHANGE
flag may have been cleaned up already by the time aCEntityFactory
'sonRemove
callback orNextBotActionFactory
'sOnEnd()
callback is fired.Tested on Windows with the following code:
Results: