[Solved] Which event should be used for LuaEntity.destroy()?

Place to post guides, observations, things related to modding that are not mods themselves.
User avatar
Mooncat
Smart Inserter
Smart Inserter
Posts: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

[Solved] Which event should be used for LuaEntity.destroy()?

Post by Mooncat »

on_entity_died seems to be the only option right now. But it doesn't sound right, since the entity is not really killed, but vanished without leaving anything.
I wonder if there is any mod uses the on_entity_died event and assumes the entity is killed by anything, leaving a corpse on the ground. :?
Or did I miss something from the API doc?

Edit: also, should I raise the event before or after LuaEntity.destroy()? It returns a boolean, because it is possible that destroy() cannot be executed:
Note: Not all entities can be destroyed - things such as rails under trains cannot be destroyed until the train is moved or destroyed.
So, it means I have to raise the event after the entity is vanished? :?

-------------------------------------------------------------------------------

Some background information:

- Problem of LuaEntity.destroy()
Sometimes, we need to know when our entities are destroyed. Example:
When player places entity A, our mod places another hidden entity B. When A is mined, killed or destroyed, B should also be removed via script.
A and B can act naturally as they act in vanilla games, so no extra scripts are required to control them.

Ideally, we listen on_built_entity and on_robot_built_entity to detect when entity A is placed, so we can also place B via script.
Then, we listen on_preplayer_mined_item and on_robot_pre_mined to detect when entity A is mined, so we should remove B.
We listen on_entity_died to detect when A is killed, so we should remove B.
Something is missing... how do we know when A is removed by LuaEntity.destroy()? Sadly, no, we can't.
There is no event for destroy(). This thread aims to ask for the solution for that.


- Can't use die()? It calls on_entity_died!
die() and destroy() behave very differently. die() creates explosion, corpse, particles, loot, etc. which are too troublesome to clear out with find_entities. Some entities, like the turrets, have short delay before they explode after die() was called. It is unpredictable with the current API.


- Our suggestions
1) on_pre_entity_destroyed event - this event would be called after LuaEntity.destroy() is called, but right before the entity becomes invalid. But this suggestion is rejected. Here is a quote of Rseding91 on IRC:
<Rseding91> Mooncat: you don't understand how many things are destroyed() each game tick by the normal game that would trigger that event if it existed. It's not going to happen. check for valid before accessing something.
Although we only asked for calling the event after a mod has called LuaEntity.destroy(), as we don't need to know the destruction of other objects like smoke, corpse, etc. we know it is inconsistent to the other events as they also work for the vanilla objects.

2) LuaEntity.can_destroy() - it returns the result of LuaEntity.destroy(), without destroying the entity.
Rseding91 has not responded on this yet, but I think this is going to be rejected. He would just say: "what if a mod place a train on the rail after this method is called?" :|

-------------------------------------------------------------------------------

After the 4-page discussion, we come up to a conclusion:

Sadly, there is no perfect solution. The event system is not ready for events like on_pre_entity_destroyed.

But as Klonan said,
Same with this, if another mods scripting is breaking your mod, it is their issue to fix
When you use LuaEntity.destroy() to destroy entities that are external from your mod, it is your responsibility to let the other mods know their entities are destroyed.

A simple, not perfect but working, solution has been suggested by Klonan:
Klonan wrote:Just use on_mined_entity/on_entity_died/on_robot_mined_entity,

When you do you code, like this

Code: Select all

function my_function(entity)
  game.raise_event(on_died)
  entity.destroy()
end
That way any mods which remove entities when they die/mined are notified, with a still valid entity reference, after which the entity is destroyed by your script.
Similarly, if you use LuaSurface.create_entity() to create entities that are not from your mod, you should tell the other mods about that with on_built_entity or on_robot_built_entity.
(create_entity() won't trigger any even by default.)

Based on this, aubergine18 has created a nice, small repository: https://github.com/aubergine10/lifecycle-events
Please feel free to use it. ;)

Please note that such events are not needed if your mod can only destroy or create entities that are from your mod, as it will have no impact to other mods, thus not vital for the events to be sent.
You can change the methods such that they accept a boolean for choosing whether to send the events or not.

TL;DR Just check the repository. It is small anyway. :lol:

So I guess it is the end of the discussion. Thanks for your precious time for participating and reading this. :)
Last edited by Mooncat on Fri Oct 28, 2016 6:27 am, edited 5 times in total.

Rseding91
Factorio Staff
Factorio Staff
Posts: 13204
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Rseding91 »

There is no event for destroy() because the game doesn't support such a concept. Destroying something is completely different from killing or mining in that the thing just ceases to exist. You're expected to simply check "entity.valid" before using something (exactly as the game does) to ensure it still exists.
If you want to get ahold of me I'm almost always on Discord.

User avatar
Mooncat
Smart Inserter
Smart Inserter
Posts: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Mooncat »

Rseding91 wrote:There is no event for destroy() because the game doesn't support such a concept. Destroying something is completely different from killing or mining in that the thing just ceases to exist. You're expected to simply check "entity.valid" before using something (exactly as the game does) to ensure it still exists.
Yes. That's the reason why I didn't want to use on_entity_died for destroy().

But sometimes I don't need to "use something" via script. I can just let the entities act like how they act in vanilla.
Like, I build a turret, then when on_built_entity is called, I create another hidden enemy turret at the same position. The enemy turret will attack the original turret, but it doesn't deal damage, it causes special effect instead.
I haven't tried that, but if this works, I don't need to use script to create the special effect at all. Ideally, I would just need to listen on_entity_built and on_robot_built_entity for creating the enemy turret, and on_entity_died, on_preplayer_mined_item and on_robot_pre_mined for removing the turret.

If there is really no event for destroy(), then I will have to add listener to on_tick just for checking entity.valid. I hate this. :cry:
Possible to add defines.events.on_entity_destroyed? :mrgreen:

User avatar
Klonan
Factorio Staff
Factorio Staff
Posts: 5150
Joined: Sun Jan 11, 2015 2:09 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Klonan »

Mooncat wrote:
Rseding91 wrote:There is no event for destroy() because the game doesn't support such a concept. Destroying something is completely different from killing or mining in that the thing just ceases to exist. You're expected to simply check "entity.valid" before using something (exactly as the game does) to ensure it still exists.
Yes. That's the reason why I didn't want to use on_entity_died for destroy().

But sometimes I don't need to "use something" via script. I can just let the entities act like how they act in vanilla.
Like, I build a turret, then when on_built_entity is called, I create another hidden enemy turret at the same position. The enemy turret will attack the original turret, but it doesn't deal damage, it causes special effect instead.
I haven't tried that, but if this works, I don't need to use script to create the special effect at all. Ideally, I would just need to listen on_entity_built and on_robot_built_entity for creating the enemy turret, and on_entity_died, on_preplayer_mined_item and on_robot_pre_mined for removing the turret.

If there is really no event for destroy(), then I will have to add listener to on_tick just for checking entity.valid. I hate this. :cry:
Possible to add defines.events.on_entity_destroyed? :mrgreen:

Typically you just assume no other mod should be destroying your entities, such that unless you are calling entity.destroy() on it yourself, you won't need to worry

I have a couple mods which use the same thing, spawning entity, listening to on_died/mined etc. for when to destroy its counterpart, and i have never had any situation where another mod has destroyed my entity

User avatar
Mooncat
Smart Inserter
Smart Inserter
Posts: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Mooncat »

Klonan wrote:Typically you just assume no other mod should be destroying your entities, such that unless you are calling entity.destroy() on it yourself, you won't need to worry

I have a couple mods which use the same thing, spawning entity, listening to on_died/mined etc. for when to destroy its counterpart, and i have never had any situation where another mod has destroyed my entity
As the current developer of Creative Mode, I have made a magic wand that can remove every selected entities. So it is going to cause problems in your mods. :twisted: :lol:
(It is like the mjollnir in rk84's Test Mode. Yes, I have asked for his permission.)

But maybe you are right. Maybe it is just an edge case.... if defines.events.on_entity_destroyed is impossible, I think on_entity_died is my best option. Hopefully it wouldn't cause any trouble. :P

Rseding91
Factorio Staff
Factorio Staff
Posts: 13204
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Rseding91 »

There can never be an entity destroyed event because once an entity is destroyed it's no longer valid for any mod to hold a reference to it so an event would be invalid to fire at that point.

You just have to check "valid" before you use anything.
If you want to get ahold of me I'm almost always on Discord.

User avatar
Mooncat
Smart Inserter
Smart Inserter
Posts: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Mooncat »

Rseding91 wrote:There can never be an entity destroyed event because once an entity is destroyed it's no longer valid for any mod to hold a reference to it so an event would be invalid to fire at that point.

You just have to check "valid" before you use anything.
Aw, yes, my approach of firing on_entity_died after destroy() doesn't work.
I can't even read the entity's name after that because it is invalid. :cry:

So we are going back to on_entity_destroyed,
how about not passing the entity, but its name, force, position, surface as the event parameters? :mrgreen:

on_entity_destroyed
Called when an entity is destroyed by LuaEntity.destroy().
Contains
  • entity_name :: string: name of the destroyed entity.
  • entity_force :: LuaForce (optional): force of the entity.
  • entity_surface :: LuaSurface
  • entity_position :: Position

Rseding91
Factorio Staff
Factorio Staff
Posts: 13204
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Rseding91 »

Mooncat wrote:... how about not passing the entity, but its name, force, position, surface as the event parameters? :mrgreen:

on_entity_destroyed
Called when an entity is destroyed by LuaEntity.destroy().
Contains
  • entity_name :: string: name of the destroyed entity.
  • entity_force :: LuaForce (optional): force of the entity.
  • entity_surface :: LuaSurface
  • entity_position :: Position
No.
If you want to get ahold of me I'm almost always on Discord.

User avatar
Mooncat
Smart Inserter
Smart Inserter
Posts: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Mooncat »

Rseding91 wrote:No.
:cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry:

Then I have to warn my users that destroying mod entities with the magic wand may cause unexpected behavior. :(
The magic wand I am talking about

Rseding91
Factorio Staff
Factorio Staff
Posts: 13204
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Rseding91 »

Mooncat wrote:
Rseding91 wrote:No.
:cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry:

Then I have to warn my users that destroying mod entities with the magic wand may cause unexpected behavior. :(
The magic wand I am talking about
That's Factorio mod dev 1-1: check "valid" before accessing anything if *anything* else may have touched it since you last checked. If some mod errors because yours deletes it then it's a fault of that other mod and not yours.
If you want to get ahold of me I'm almost always on Discord.

User avatar
Klonan
Factorio Staff
Factorio Staff
Posts: 5150
Joined: Sun Jan 11, 2015 2:09 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Klonan »

Mooncat wrote:
Rseding91 wrote:No.
:cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry: :cry:

Then I have to warn my users that destroying mod entities with the magic wand may cause unexpected behavior. :(
The magic wand I am talking about
Just script it so it searches the area selected for all entities, instead of only destroying those that are selected

User avatar
Mooncat
Smart Inserter
Smart Inserter
Posts: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Mooncat »

Klonan wrote:Just script it so it searches the area selected for all entities, instead of only destroying those that are selected
hm... if I have already set the selection_mode flag to {"any-entity"}, shouldn't it already be able to destroy all entities in the area? :shock:
(I used if-check to filter entities before destroying them, so I can apply my custom rules on it.)

And I just wonder even your way may not cover all cases.... like if the linked entity (the enemy turret in my previous example) is not at the same position, so it may be outside the selected area... I can't control how other modders write their mods. :lol:

User avatar
aubergine18
Smart Inserter
Smart Inserter
Posts: 1264
Joined: Fri Jul 22, 2016 8:51 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by aubergine18 »

Mooncat wrote:And I just wonder even your way may not cover all cases.... like if the linked entity (the enemy turret in my previous example) is not at the same position, so it may be outside the selected area... I can't control how other modders write their mods. :lol:
The bridges my mod adds would be another example where that could happen - two ends of a bridge, only one end (and some bridge segments) might get destroyed. That being said, if I know there's a destroyed event being fired by any mod that does that, then I can take appropriate action. The list of event properties you mentioned earlier would be exactly what I need to maintain my internal mod state (as I have to locate other end of bridge, which requires name, position and surface of the deleted end or segment).
Better forum search for modders: Enclose your search term in quotes, eg. "font_color" or "custom-input" - it prevents the forum search from splitting on hypens and underscores, resulting in much more accurate results.

Supercheese
Filter Inserter
Filter Inserter
Posts: 841
Joined: Mon Sep 14, 2015 7:40 am
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Supercheese »

Rseding91 wrote:That's Factorio mod dev 1-1: check "valid" before accessing anything if *anything* else may have touched it since you last checked. If some mod errors because yours deletes it then it's a fault of that other mod and not yours.
Listen to the devs, people.
Just. Do. This.
Seriously, it's not that hard.

User avatar
aubergine18
Smart Inserter
Smart Inserter
Posts: 1264
Joined: Fri Jul 22, 2016 8:51 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by aubergine18 »

tl;dr: the key thing here is that in order to get full lifecycle tracking, I'm going to have to resort to checking all my bridges every X ticks, because events don't cover mod-related lifecycle.

@Supercheese - in my case .valid isn't of any consequence, as once my bridge is built my mod doesn't do anything with it until it gets destroyed or mined. The problem here is that if some other mod destroys bits of it, there's no event, so my mod has no idea that there's now only half a bridge left (in particular, it won't replace the fake water beneath the mod-destroyed bridge segments with real water).

I could start having my mod periodically check all bridges every X ticks, just to check if some other mod deleted something, or I could ignore other mods doing stuff and just leave it to the player to sort out.

This ties in with a related discussion, about having the Lua API generate some additional events (particularly for create/destroy entity) with an additional flag in the event table to enable event handlers to discern between vanilla event and mod-caused event.

The LuaEntity.destroy() method is a key example - there are certain situations where it won't succeed (eg. a rail with a train on top), so the only way a mod can reliably fire an event, as discussed in posts above, is to first cache some entity properties, then check the return value of .destroy() and only then send the event if applicable. However, if the destroy() method did the event internally, it could include the full entity (or at least a copy of some key props listed above). Mods can do this too, obviously, but then we'd have to rely on every mod doing that legwork to create the custom event.
Better forum search for modders: Enclose your search term in quotes, eg. "font_color" or "custom-input" - it prevents the forum search from splitting on hypens and underscores, resulting in much more accurate results.

mknejp
Fast Inserter
Fast Inserter
Posts: 154
Joined: Wed Apr 27, 2016 8:29 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by mknejp »

Rseding91 wrote:There can never be an entity destroyed event because once an entity is destroyed it's no longer valid for any mod to hold a reference to it so an event would be invalid to fire at that point.

You just have to check "valid" before you use anything.
There are still two things we can do with an invalid entity handle and that is calling .invalid and testing for equality with other entity handles. That is sufficient to remove any existing entity handles pointing to the invalid entity in the mod's data and ensure it is no longer referenced. If the event provides key properties like type, name, prototype, force which can obviously no longer be queried from the handle itself it still allows for event filtering. The docs should make it glaringly clear that the entity handle is invalid and can only be used for the previously mentioned operations. After all the mod calling destroy() implicitly still holds on to the entity handle so the associated Lua wrapper must still exist.

User avatar
aubergine18
Smart Inserter
Smart Inserter
Posts: 1264
Joined: Fri Jul 22, 2016 8:51 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by aubergine18 »

IIRC, the event system will filter out any events containing invalid entities, so the mod still wouldn't be able to send that invalid entity wrapper as the event metadata.
Better forum search for modders: Enclose your search term in quotes, eg. "font_color" or "custom-input" - it prevents the forum search from splitting on hypens and underscores, resulting in much more accurate results.

Rseding91
Factorio Staff
Factorio Staff
Posts: 13204
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by Rseding91 »

mknejp wrote:
Rseding91 wrote:There can never be an entity destroyed event because once an entity is destroyed it's no longer valid for any mod to hold a reference to it so an event would be invalid to fire at that point.

You just have to check "valid" before you use anything.
There are still two things we can do with an invalid entity handle and that is calling .invalid and testing for equality with other entity handles. That is sufficient to remove any existing entity handles pointing to the invalid entity in the mod's data and ensure it is no longer referenced. If the event provides key properties like type, name, prototype, force which can obviously no longer be queried from the handle itself it still allows for event filtering. The docs should make it glaringly clear that the entity handle is invalid and can only be used for the previously mentioned operations. After all the mod calling destroy() implicitly still holds on to the entity handle so the associated Lua wrapper must still exist.
Equality checks don't work once an entity is invalid. It will always compare false with any other valid entity or compare true with any other invalid entity regardless of them actually being equal before they got destroyed.

Because as I said before: once something is destroyed it's not valid for anything to hold on to a handle to that thing and as such all of the wrappers are cleared.
If you want to get ahold of me I'm almost always on Discord.

mknejp
Fast Inserter
Fast Inserter
Posts: 154
Joined: Wed Apr 27, 2016 8:29 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by mknejp »

Klonan wrote:Typically you just assume no other mod should be destroying your entities, such that unless you are calling entity.destroy() on it yourself, you won't need to worry

I have a couple mods which use the same thing, spawning entity, listening to on_died/mined etc. for when to destroy its counterpart, and i have never had any situation where another mod has destroyed my entity
And then comes Vehicle Wagon which destroyed my electric car, which has to have its energy buffer refueled from the equipment grid every tick, when it's winched on a wagon, meaning I have to actively waste performance every tick by checking if the vehicle is valid. In this case this might be negligible for cars because there usually aren't *that* many on a map, but the same goes for locomotives if some mod decides to destroy them for whatever purpose it is trying to achieve.

Don't take me wrong I am not blaming Vehicle Wagon as I would have done it the same way, but assuming other mods will never destroy your entities is an assumption that can break as soon as somebody has a new mod idea. I'd rather not waste cycles checking for validity if it can be avoided with new events.

mknejp
Fast Inserter
Fast Inserter
Posts: 154
Joined: Wed Apr 27, 2016 8:29 pm
Contact:

Re: Which event should be used for LuaEntity.destroy()? When?

Post by mknejp »

Rseding91 wrote:Equality checks don't work once an entity is invalid. It will always compare false with any other valid entity or compare true with any other invalid entity regardless of them actually being equal before they got destroyed.
That just means it clears out all the invalid entities in one go which is even better.
Rseding91 wrote:Because as I said before: once something is destroyed it's not valid for anything to hold on to a handle to that thing and as such all of the wrappers are cleared.
The mod calling "x.destroy()" still has an "x" handle to the now destroyed entity after the function returns, so some form of metadata for equality comparison and validity checking still has to exist. More is not required, or even asked for, to be available inside the on_destroyed event. Critical properties like name, type, prototype, force, unit_number can be provided in addition to the entity handle for filtering to reduce the amount of work done inside the event handler.

Post Reply

Return to “Modding discussion”