[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
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 »

For me it's not so much about the .valid check. I don't mind doing it. It's more about keeping track of things that would otherwise not need any on_tick involvement.

I've put a lot of effort in to avoiding using on_tick, and my bridges have highlighted a scenario where another mod deleting them is not covered by the event system.

I guess my only choice is to start iterating all my bridges, and the segments (entities) within them, every X ticks to see if another mod deleted them, at which point I need to replace some fake water with real water or maybe destroy whole bridge (depends on bridge type).

If LuaEntity.destroy() fired an event with at least some static info about the destroyed entity (id, position, type, name, force, surface, etc) then I could avoid using on_tick completely.
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: 13232
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: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.
"x" after calling destroy on it is simply a nullptr on the C++ side. The equivalent to "nil" in Lua. You can compare it all day long and it will equal other things that are "nil" but you can't tell *what* it was before it was set to "nil".
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 »

Rseding91 wrote:"x" after calling destroy on it is simply a nullptr on the C++ side. The equivalent to "nil" in Lua. You can compare it all day long and it will equal other things that are "nil" but you can't tell *what* it was before it was set to "nil".
Which is perfectly fine. Although at that point a.invalid is probably more efficient than a==b. More important would be providing the destroyed entity's unit_number if it had one (along with other static properties) since that is what's used as table key and for filtering. The critical thing here really is knowing when something was destroyed and what kind of thing it was, with unit_number allowing for faster processing where available.

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

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

Post by Mooncat »

mknejp wrote:
Rseding91 wrote:"x" after calling destroy on it is simply a nullptr on the C++ side. The equivalent to "nil" in Lua. You can compare it all day long and it will equal other things that are "nil" but you can't tell *what* it was before it was set to "nil".
Which is perfectly fine. Although at that point a.invalid is probably more efficient than a==b. More important would be providing the destroyed entity's unit_number if it had one (along with other static properties) since that is what's used as table key and for filtering. The critical thing here really is knowing when something was destroyed and what kind of thing it was, with unit_number allowing for faster processing where available.
I think Rseding meant you won't get any data about the entity after destroy() because it is invalid. I have tried printing entity.name after destroy() and it threw error.


Well, my previous suggestion about passing entity's name, force, surface, position, etc as the event parameters were rejected. I understand that it is too different from the other events....

How about on_pre_entity_destroyed? Like on_preplayer_mined_item. :mrgreen:

on_pre_entity_destroyed
Called after LuaEntity.destroy() is called, but before the entity becomes invalid. You can't cancel the destruction in this event.
Contains
entity :: LuaEntity: the entity that's going to be invalid.

User avatar
DedlySpyder
Filter Inserter
Filter Inserter
Posts: 253
Joined: Fri Jun 20, 2014 11:42 am
Contact:

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

Post by DedlySpyder »

@Mooncat, couldn't you just use LuaEntity.die() and clean up any Loot at the position within a small time frame? If the entity gives loot then add it's position (+/- a small amount) to a table for deletion.

Or even no big deal for the loot, as this mod isn't really for normal play, correct?

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

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

Post by Mooncat »

DedlySpyder wrote:@Mooncat, couldn't you just use LuaEntity.die() and clean up any Loot at the position within a small time frame? If the entity gives loot then add it's position (+/- a small amount) to a table for deletion.

Or even no big deal for the loot, as this mod isn't really for normal play, correct?
Good question. I have struggled on this question too when I was implementing the magic wands. But then I knew that die() isn't always feasible.

die() will cause explosions, death animations and corpses, which is not acceptable for instant deconstruction (the opposite of instant blueprint).
Instant Blueprint and Instant Deconstruction
Here is another magic wand that can "kill" the selected entities:
Die
It will be hilarious if I use die() for instant deconstruction. :lol:

If I need to get rid of the explosions, particles, death animations, corpses, loot, ghosts, etc. there will be 2 problems:
1) I will need to use find_entities, which is bad for performance. Still possible, just bad.
2) I need to know the time when the entity explode. Based on the current API of LuaEntityPrototype, it is impossible.

The easiest and most elegant way to solve this problem that I can think of is the on_pre_entity_destroyed event. :)
It doesn't violate the rule that entity is invalid because the event is called right before it becomes invalid.
It can get rid of on_tick listeners for just checking entity.valid. Good for performance.
If it is rejected, I think we will need the reason.

User avatar
LuziferSenpai
Filter Inserter
Filter Inserter
Posts: 339
Joined: Tue Jul 08, 2014 10:06 am
Contact:

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

Post by LuziferSenpai »

Mooncat wrote:
DedlySpyder wrote:@Mooncat, couldn't you just use LuaEntity.die() and clean up any Loot at the position within a small time frame? If the entity gives loot then add it's position (+/- a small amount) to a table for deletion.

Or even no big deal for the loot, as this mod isn't really for normal play, correct?
Good question. I have struggled on this question too when I was implementing the magic wands. But then I knew that die() isn't always feasible.

die() will cause explosions, death animations and corpses, which is not acceptable for instant deconstruction (the opposite of instant blueprint).
Instant Blueprint and Instant Deconstruction
Here is another magic wand that can "kill" the selected entities:
Die
It will be hilarious if I use die() for instant deconstruction. :lol:

If I need to get rid of the explosions, particles, death animations, corpses, loot, ghosts, etc. there will be 2 problems:
1) I will need to use find_entities, which is bad for performance. Still possible, just bad.
2) I need to know the time when the entity explode. Based on the current API of LuaEntityPrototype, it is impossible.

The easiest and most elegant way to solve this problem that I can think of is the on_pre_entity_destroyed event. :)
It doesn't violate the rule that entity is invalid because the event is called right before it becomes invalid.
It can get rid of on_tick listeners for just checking entity.valid. Good for performance.
If it is rejected, I think we will need the reason.
This is easy i think.

I stole this code from Klonan's Lab Builder and i think this will work for you ;)

Code: Select all

script.on_event( defines.events.on_player_selected_area, function( event )
	if event.item == wandname then
		for i, q in pairs( game.players[ event.player_index ].surface.find_entities( event.area ) )
			if q.name ~= "player" then
				v.destroy()
			end
		end
	end
end )
And if you want a second use on the same wand then use:

Code: Select all

script.on_event( defines.events.on_player_alt_selected_area, function( event )
	if event.item == wandname then
		for i, q in pairs( game.players[ event.player_index ].surface.find_entities( event.area ) )
			if q.name ~= "player" then
				v.destroy()
			end
		end
	end
end )
Greetz,

Senpai
Coding is awesome!
Animes are love!
Factorio is life!

My MODs:
Click

Greetz,

Senpai

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 »

Does the vanilla game work the same way? For example, when a mod creates or destroys an entity, the C code gets no notification of that happening, and just has to somehow work it out? I suspect this is not the case.

If the vanilla game needs to know about full lifecycle of entities, does it not stand to reason that mods kind of need that too? Specifically events fired when a mod uses LuaSurface.create_entity() or LuaEntity.destroy(). (I've not checked to see if LuaEntity.die() fires an event, if not, that would need adding to the list).
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.

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

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

Post by Mooncat »

LuziferSenpai wrote:This is easy i think.

I stole this code from Klonan's Lab Builder and i think this will work for you ;)

Code: Select all

script.on_event( defines.events.on_player_selected_area, function( event )
	if event.item == wandname then
		for i, q in pairs( game.players[ event.player_index ].surface.find_entities( event.area ) )
			if q.name ~= "player" then
				v.destroy()
			end
		end
	end
end )
And if you want a second use on the same wand then use:

Code: Select all

script.on_event( defines.events.on_player_alt_selected_area, function( event )
	if event.item == wandname then
		for i, q in pairs( game.players[ event.player_index ].surface.find_entities( event.area ) )
			if q.name ~= "player" then
				v.destroy()
			end
		end
	end
end )
Greetz,

Senpai

This is the code I am using, in addition to some filtering algorithms. :)
The problem is, also the reason why this thread is here, that other mods don't know the entity is destroyed.
aubergine18 wrote:I've not checked to see if LuaEntity.die() fires an event, if not, that would need adding to the list).
According to the doc:
Unlike LuaEntity::destroy, die will trigger on_entity_died
I trust it. :D
That's another reason why destroy should trigger on_pre_entity_destroyed. For consistency.

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

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

Post by Klonan »

Mooncat wrote:
LuziferSenpai wrote:This is easy i think.

I stole this code from Klonan's Lab Builder and i think this will work for you ;)

Code: Select all

script.on_event( defines.events.on_player_selected_area, function( event )
	if event.item == wandname then
		for i, q in pairs( game.players[ event.player_index ].surface.find_entities( event.area ) )
			if q.name ~= "player" then
				v.destroy()
			end
		end
	end
end )
And if you want a second use on the same wand then use:

Code: Select all

script.on_event( defines.events.on_player_alt_selected_area, function( event )
	if event.item == wandname then
		for i, q in pairs( game.players[ event.player_index ].surface.find_entities( event.area ) )
			if q.name ~= "player" then
				v.destroy()
			end
		end
	end
end )
Greetz,

Senpai

This is the code I am using, in addition to some filtering algorithms. :)
The problem is, also the reason why this thread is here, that other mods don't know the entity is destroyed.
aubergine18 wrote:I've not checked to see if LuaEntity.die() fires an event, if not, that would need adding to the list).
According to the doc:
Unlike LuaEntity::destroy, die will trigger on_entity_died
I trust it. :D
That's another reason why destroy should trigger on_pre_entity_destroyed. For consistency.

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.

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 »

Klonan wrote: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.
And what happens when we try to destroy rail that turns out to have train on top and thus doesn't get destroyed? We've already sent the event, so other mods think rail is destroyed, only to find that we can't destroy it. This is why LuaEntity.destroy() should be sending event, would you agree?

Also, is it guaranteed that the event would be received by other mods (eg. in MP games) prior to destruction of the entity?
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: 13232
Joined: Wed Jun 11, 2014 5:23 am
Contact:

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

Post by Rseding91 »

It's not going to happen.
If you want to get ahold of me I'm almost always on Discord.

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

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

Post by Mooncat »

Rseding91 wrote:It's not going to happen.
Why?.... I thought you would be interested on it because it is about optimization. :(
I guess you should be able to see the difference between
1) we add listeners to on_tick to iterate all our entities in every frame for checking entity.valid, and
2) we add listeners to on_pre_entity_destroyed and do things only when our entities are destroyed?

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 »

Why do you need to add on_tick if you don't currently have one? Just stick some .valid checks before all spots in your existing code where you modify an entity, and you should be good to go, no?

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 »

Supercheese wrote:Why do you need to add on_tick if you don't currently have one? Just stick some .valid checks before all spots in your existing code where you modify an entity, and you should be good to go, no?
Ok, so for this .valid == false entity, can you tell me it's unit_number please? Or its position? Or get me the data I'd associated with it in a dictionary?
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.

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

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

Post by Klonan »

aubergine18 wrote:
Klonan wrote: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.
And what happens when we try to destroy rail that turns out to have train on top and thus doesn't get destroyed? We've already sent the event, so other mods think rail is destroyed, only to find that we can't destroy it. This is why LuaEntity.destroy() should be sending event, would you agree?

Also, is it guaranteed that the event would be received by other mods (eg. in MP games) prior to destruction of the entity?
You're talking about an edge case of an edge case...
Really i have seen entity.destroy() used in a huge variety of cases without ever raising an event, and haven't seen a single complaint

And yea, the event will be fired and processed by all mod scripts registered to the event, before continuing in the function

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 »

aubergine18 wrote:Ok, so for this .valid == false entity, can you tell me it's unit_number please? Or its position? Or get me the data I'd associated with it in a dictionary?
What I mean is, say you've got currently a table where you insert/remove entities to iterate:

Code: Select all

for i = 1, #global.Entity_Table do
	modify_Entity(global.Entity_Table[i])
end
Change it to:

Code: Select all

for i = #global.Entity_Table, 1, -1 do
	if global.Entity_Table[i] and global.Entity_Table[i].valid then	
		modify_Entity(global.Entity_Table[i])
	else
		table.remove(global.Entity_Table, i)
	end
end
Or other similar; say if you iterate using pairs() and index by unit_number, you can test if the associated entry exists and if it is .valid, and if not, then remove the entry by setting it to nil.

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 »

What if I need access to the data associated with the entity, and with that data I need to update other arrays or associated entities, etc? And, assuming I'm keying via entity object --> metadata, will that index in the table even persist a save-load cycle? Depending on when I next iterate the table, the entire record might have been nuked by save-load cycle, and I literally wouldn't even know the entity had been destroyed, thus potentially leaving orphan entities or orphan data in other tables/arrays etc.

Code: Select all

for i = #global.Entity_Table, 1, -1 do
   if global.Entity_Table[i] and global.Entity_Table[i].valid then   
      modify_Entity(global.Entity_Table[i])
   else
      clean_up_destroyed_entity_metadata(global.Entity_Table[i]) -- error!
      table.remove(global.Entity_Table, i)
   end
end
How do I clean up data associated with the entity, particularly if that data is not just in the current table, but potentially in several other tables?

If the entity is created/destroyed by anything other than a mod, I get an event with the entity in it. Built? Event. Mined? Event. Died? Event.

Mod creates it? Nope, you're on your own. Mod destroys it? Nope, you're on your own.
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.

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

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

Post by Mooncat »

@Supercheese You are just saying the same thing as Rseding before:
You're expected to simply check "entity.valid" before using something (exactly as the game does) to ensure it still exists.
and we did read that.

Problem is, what if "using something" doesn't exist in the first place?
When player places entity A, my mod places entity B. And then no further script to control the behaviours of A and B. They can act naturally.
So, there is no "modify_Entity".
When A is mined, killed or DESTROYED, B should be removed too. We have events to detect when A is mined or killed, but we don't know when it is destroyed. So the only solution will be checking entity.valid in on_tick. It is bad.

Anyway, I will use Klonan's solution for now. Thanks. :)
But we know it is not perfect. It has 2 major problems:
1) LuaEntity.destroy() returns a boolean, because, according to the doc, not all entities can be destroyed. Rails under trains are just an example. Who knows what new features we will have in the future, making there more examples. :lol:
2) Shouldn't expect modders to raise event for the built-in functions, unless there is some kind of modding protocol for Factorio.

So we still need to find a better solution to avoid conflicts between mods in long term.
And I really want to know why we can't have on_pre_entity_destroyed. :?

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 »

There's 4 mods in past week that have hit this issue (vehicle carriage mod, electric vehicles mod, creative mode, and my WIP bridges mod). I imagine the VW camper van mod also will suffer the same fate.

The mod ecosystem continues to grow, and now we are starting to see some quite complex mods emerge, a trend that is likely to continue. As we get more mods, interacting with each other, the lack of events on LuaSurface.create_entity() and LuaEntity.destroy() is going to become more of a thorn in the side of modders.

If we want to create custom event, it's likely that we'll need a library mod that other mods will have to use. Alternatively we could start setting up a labyrinth of remote interfaces (way too painful in this particular scenario IMO).

As for "just check .valid", it's not that simple for some of these mods - they need to know specifically which entity died, when it dies, and then do stuff based on that entity, which by time .valid == false it's too late in many cases. And .valid is not going to help us detect when another mod create_entity() one of our entities.

As modders, we'll find a way to automate our way out of this mess, but it's not going to be pretty. :(

@Mooncat: note also that entity properties cannot be iterated with pairs(), so we can't even table.deepcopy() entity prior to .destroy() in order to conditionally send event containing static clone of props post .destroy(). That means before doing .destroy() we'll have to first determine *if* the entity can be destroyed within Lua script, and if so, send event, then destroy it.
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.

Post Reply

Return to “Modding discussion”