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

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

Post by Mooncat »

aubergine18 wrote:@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.
The game should call the event, just like on_entity_died. ;)
I believe this is the only solution.

User avatar
bobingabout
Smart Inserter
Smart Inserter
Posts: 7352
Joined: Fri May 09, 2014 1:01 pm
Contact:

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

Post by bobingabout »

I seriously do not understand how this WTF question could reach 3 pages.

If something is destroy()ed, it is gone, forever, unrecoverable. All information about what it was is meaningless because it doesn't, and can never exist anymore... so why would you be interested in an on_destroyed event?

Destroy is effectively a god override command, your usage of it is non-standard.

Having said that... the devs are right, if a mod can't handle one of it's entities being destroy()ed unexpectedly, then the mod is poorly programmed, not your problem.


I only have one mod that monitors an entity. My Inserters Mod creates a GUI to modify an existing entity. I had to add entity.valid pretty much every time I accessed the entity to make sure it still existed before performing any functions on it. This is standard practice. If you're keeping track of a list of entities and one suddenly fails a valid check... remove that entry from your table!

Listen to the Devs, they know what they're talking about. If they tell you that you don't need something, you don't need it. If they tell you that you should do something before doing something else (Like checking entity.valid before trying to read/write to it) then listen to them.




So, okay, you're performing an entity.destroy() command on everything selected by your "Magic wand" god tool. WHY do you need to access information about said entity AFTER it is destroyed? If you want stats, record them before destroying it? I can't think of any other reason you'd want to know information about an entity you're destroying in the manner.
Creator of Bob's mods. Expanding your gameplay since version 0.9.8.
I also have a Patreon.

User avatar
LotA
Fast Inserter
Fast Inserter
Posts: 117
Joined: Fri Oct 10, 2014 11:41 am
Contact:

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

Post by LotA »

I also don't understand the need of accessing a destroyed entity. But on the other hand, the OP problem could be easily solved if LuaEntity.die() would accept a boolean parameter to decide if the entity plays a dying animation or just vanishes. (its would then be accessible via the on_entity_died event)

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 »

If .die() had a param to prevent the dying animation, explosions, loot, etc., then yes, that would solve the problem - at least for the "death" end of the lifecycle.

+1 for that, it seems reasonable change to a method that already fires event.

There's still nothing that solves the issue from the "creation" end of the lifecycle though.

Also, for all those who keep banging on that lifecycle events aren't needed, I have a challenge for you:

Download the Concrete Lamppost mod. Delete the following event handlers:

* defines.events.on_preplayer_mined_item
* defines.events.on_entity_died
* defines.events.on_robot_pre_mined

Let me know how you get on without them. After all, you can just check for .valid, right? There's literally no need for lifecycle events (mods don't need to know when an entity is destroyed).

While you're at it, also delete the start of lifecycle events, because they aren't needed either (mods don't need to know when an entity is created):

defines.events.on_built_entity
defines.events.on_robot_built_entity

Let me know your solution to not needing any of those events, because it will completely solve all problem discussed in within this forum topic.

EDIT: As an alternate challenge, make a mod that places functional concrete lampposts without replicating the entirety of the Concrete Lampposts mod. You should be able to mark CL as a dependency of your mod, and then simply place down a concrete lamppost and have it just work (it won't work, because CL mod has no clue that you just created one of its entities). Then, after you get that working, destroy the lamppost without having to delete the entities associated with it (which are all neutral force), the CL mod should tidy up after itself (it won't work, because it has no clue you just destroyed one of it's entities, I'm sure you can find a simple way to fix that by making CL mod just check .valid). This is the sort of brick wall some of us are currently banging our heads against. The lack of these events makes it incredibly difficult to integrate with other mods.
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: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

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

Post by Mooncat »

bobingabout wrote:I seriously do not understand how this WTF question could reach 3 pages.

If something is destroy()ed, it is gone, forever, unrecoverable. All information about what it was is meaningless because it doesn't, and can never exist anymore... so why would you be interested in an on_destroyed event?

Destroy is effectively a god override command, your usage of it is non-standard.
Please read: viewtopic.php?f=34&t=34952&start=20#p218183 ;)
TL;DR It is not we need the destroyed entity, but we need to know when the entity is, or is going to be, destroyed.
I will write a summery of what we have discussed so far to the OP.

And how do you define the usage of a modding API standard or not? If LuaEntity.destroy() exists, you should expect people are using it without causing any problems.
I can say that I am not the one having problem with this function:
mknejp wrote: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.
I should also quote this in case not all people have read the first page:
mknejp wrote:Don't take me wrong I am not blaming Vehicle Wagon as I would have done it the same way,

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:So the only solution will be checking entity.valid in on_tick. It is bad.
That isn't the correct solution,

You should check for valid when you would naturally be doing something to your entity anyway,
If you are worried about another mod destroying your entities,
Don't be worried.

As long as you are checking valid before doing anything with it, it will be fine.

There are any huge number of reasons why a entity would become invalid, not just calling entity.destroy()
So an event for it wouldn't even help the majority of cases,

Such as setting blank tiles - Will destroy entities under it
Removing an entity prototype - will destroy them without notice
Them being migrated to a new entity

In any case, never trust that an entity will always be valid,
Don't trust other mods,
Check if something is valid, before you do something to it

The worst case in checking if it is valid is that it does nothing,
The worst case in not checking is lots and lots of complaints that your mod is crashing the game, when some other mod destroys something

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 »

You should check for valid when you would naturally be doing something to your entity anyway,
Except when the mod doesn't do anything like that... Here's how this issue affects my bridge:

Image

The ends of the bridge are `pipe-to-ground` entities. The ramps are the pipe covers :)

Once a bridge is constructed, my mod sits idle, there's no `on_tick`.

To allow the player to walk over the bridge, during construction I replace any water tiles between the two ends with fake water tiles that have the player collision mask removed.

If the end of the bridge, or any segment between the ends is destroyed, I get an event: If it's mined, if it dies (eg. due to fire), I get told about it. When this happens, I do one of two things:

* If it's bridge end, I find other end via `.neighbours`
* If it's a segment in the bridge, I know the`max_underground_distance` (well, I would) so I center that on the destroyed segment, then search that area for applicalbe end entity (`pipe-to-ground` of specific name), then find other end via `.neighbours`

Once I have both ends, I can safely destroy the bridge - both ends, the segments between them, and replacing fake water with real water again.

Now, along comes a mod that destroys one end of my bridge, or a segment... I know nothing about this. Even if that mod destroyed all entities in the bridge, the fake water would still be there and player would be able to walk on it.

This is why I need to know when something gets destroyed by a mod. It's why all this talk of "just check .valid" is of no use to me whatsoever, because my mod is not using `on_tick`. I've spent ages making sure the mod sits idle until triggered to do something (build bridge, destroy bridge) by an event. I've even set `.active = false` on the `pipe-to-ground` so that a built bridge has almost zero impact on UPS/FPS.

And then along comes this nightmare that `.destroy()` doesn't fire an event, with people telling me "just check .valid", which is equivalent to "just add on_tick handler and check .valid for every piece of every bridge on every surface" which in turn means "maintain a table of all your bridge entities, just so you can work out when one of them is destroyed by another mod".

A param on the destroy event to tell it to trigger an event would solve this problem, it would default to `false`. Mods such as Creative Mode, or anything else that can `.destroy` entities from other mods, would set the param to `true` to alert the other mod to their entity being destroyed.

As for the `create_entity()`, that also poses a problem. One of the things I wanted to do was write a separate mod that would automatically build a bridge at start of game if player is trapped on a small island. That should be simple case of placing two ends of bridge at which point my mod would do the rest (build the inner segments, swap water for fake water, etc). But, because `create_entity()` doesn't fire an event, my mod literally has no clue that another mod is trying to build a bridge. If `create_entity()` had an additional param, `false` by default, that told it to trigger an event, this problem would be easily solved.

The events would benefit any mod that uses compound entities (concrete lamppost, torches, ks_power, electric vehicles, etc) or wish to create/destroy entities from external mods (creative mode, vehicle wagon, etc).
Last edited by aubergine18 on Tue Oct 25, 2016 4:18 pm, edited 2 times in total.
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: 5150
Joined: Sun Jan 11, 2015 2:09 pm
Contact:

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

Post by Klonan »

aubergine18 wrote:
The events would benefit any mod that uses compound entities (concrete lamppost, torches, ks_power, electric vehicles, etc) or wish to create/destroy entities from external mods (creative mode, vehicle wagon, etc).

You mention those mods,

But in their entire existence i have never seen or heard any complaint or issue about other mods destroying things,
And if they do, i wouldn't care. If another mod is messing with things, they should be the ones to fix it

If i make a mod which breaks bobs mods something or other, it wouldn't be fair to ask bob to fix his mod,
Same with this, if another mods scripting is breaking your mod, it is their issue to fix

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 »

So if I have a mod that creates roads, and I want to line the roads with concrete lampposts, and destroy those lampposts when road is destroyed, how would I fix that in my mod? The only way I could do it is to completely duplicate your mod, graphics and code, in to my mod, and have a set of renamed entities that my mod uses, just because there aren't create/destroy events. This is nuts, absolutely nuts.

If the events existed, then it would be two line change in your mod:

Code: Select all

script.on_event(defines.events.on_create_entity, BuiltEntity)
script.on_event(defines.events.on_pre_destroyed, MinedEntity)
And two lines of code in my mod:

Code: Select all

-- when creating:
myRoad.surface.create_entity( { name = 'concrete-lamppost', .... }, true )

-- when destroying:
aLamppost.destroy( true )
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.

keyboardhack
Filter Inserter
Filter Inserter
Posts: 478
Joined: Sat Aug 23, 2014 11:43 pm
Contact:

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

Post by keyboardhack »

aubergine18 wrote:So if I have a mod that creates roads, and I want to line the roads with concrete lampposts, and destroy those lampposts when road is destroyed, how would I fix that in my mod? The only way I could do it is to completely duplicate your mod, graphics and code, in to my mod, and have a set of renamed entities that my mod uses, just because there aren't create/destroy events. This is nuts, absolutely nuts.

If the events existed, then it would be two line change in your mod:

Code: Select all

script.on_event(defines.events.on_create_entity, BuiltEntity)
script.on_event(defines.events.on_pre_destroyed, MinedEntity)
And two lines of code in my mod:

Code: Select all

-- when creating:
myRoad.surface.create_entity( { name = 'concrete-lamppost', .... }, true )

-- when destroying:
aLamppost.destroy( true )
Maybe the real solution is to allow making a group of entities acting as a single entity.
You can subsribe to destroy() events of different group entities so you can handle the destruction correctly.
Do the same for creation of the entity and everything should be fixed(maybe).

data.lua

Code: Select all

data:extend(
{
    {
        type="group-entity"
        name="lamp-group"
        entities = --what entities this group contains
        {
            "lamp",
            "small-pole",
            "electric-power-thingie"
        },
        on_created_event_name = "lamp_group_create",
        on_destroyed_event_name = "lamp_group_destroy"
    }
})
And then in control.lua

Code: Select all

script.on_costum_event("lamp_group_create", function()
    --do the create stuff
end)

script.on_costum_event("lamp_group_destroy", function()
    --do the destroy stuff
end)
Waste of bytes : P

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 »

Thanks, Klonan.
I think your answer has shown your concern on this problem. I will take it as one of the many reasons why Rseding91 rejected my suggestion.

But I think your examples are solvable:
Klonan wrote:Such as setting blank tiles - Will destroy entities under it
How about calling entity.destroy() or die() on your side, such that it will also call the corresponding event in this situation?
Klonan wrote:Removing an entity prototype - will destroy them without notice
I think this should only happen when a mod is updated / removed?
If it is updated, on_configuration_changed will be called, and mods should do things accordingly in this function. Modders should make their mods updatable. But I think how to do that is outside this topic.
If it is removed, there is nothing we can do, but to tell the users how to uninstall our mod correctly if they want to delete it from an existing save.
Klonan wrote:In any case, never trust that an entity will always be valid,
Don't trust other mods,
Check if something is valid, before you do something to it
I think the argument between us mainly lies on "before you do something to it".

Problem is it doesn't always exist, as aubergine18 said. We just place the entities on map and that's it. No more script to control them.
To validate them, we will need to explicitly add listener to on_tick. But it can cause bad performance.

Take my previous example about turret A and B. Player place turret A, script place indestructible turret B for special effect....
We like to spam turrets, don't we? :) It is easy to get more than 1k turrets on the map. If we use on_tick to iterate all of them in every frame, it will cause heavy workload.
Iterating only a fixed amount of turrets in each frame may be a solution, but it can look ugly, as turret B may get removed an obvious delay after A is mined by player.
Klonan wrote:Same with this, if another mods scripting is breaking your mod, it is their issue to fix
But Rseding91 have said this before:
Rseding91 wrote:If some mod errors because yours deletes it then it's a fault of that other mod and not yours.
:cry:

If there is on_pre_entity_destroyed event, modders are expected to listen it, just like how they listen on_entity_died, on_preplayer_mined_item, etc. It will be clear to see who should take the responsibility on fixing this issue.

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:
aubergine18 wrote:
The events would benefit any mod that uses compound entities (concrete lamppost, torches, ks_power, electric vehicles, etc) or wish to create/destroy entities from external mods (creative mode, vehicle wagon, etc).

You mention those mods,

But in their entire existence i have never seen or heard any complaint or issue about other mods destroying things,
And if they do, i wouldn't care. If another mod is messing with things, they should be the ones to fix it

If i make a mod which breaks bobs mods something or other, it wouldn't be fair to ask bob to fix his mod,
Same with this, if another mods scripting is breaking your mod, it is their issue to fix
Ok, so how does Vehicle Wagon fix the issue that unloading an electric car from the wagon creates an electric car that doesn't work? Every tick I need to transfer energy from the car's equipment grid to the car's energy buffer. In order to do that I need a mapping of [unit_number] => LuaEquipmentGrid. The *only* way to get this relationship is by listening to on_built_entity and get the vehicle's .grid property the moment it is placed. When Vehicle Wagon unloads the car using create_entity I do not get notified of the new entity and therefore cannot establish the entity => grid relationship resulting in the electric vehicle being useless.

One way I see it is they can raise on_built_entity manually but I still consider this a bad practice because there are assumptions around game-triggered events that mods can easily violate.

Another way is making a remote interface in Electric vehicles which Vehicle Wagon can call to tell me about this newly created car. So the author of that mod now has to special-case every single mod in existence that requires a remote call when a vehicle is placed. Not very practical.

Or, they could simply tell create_entity to trigger an entity_created event which has no associated game-related preconditions other than that a new entity was created. Although that still doesn't solve the problem because the moment the car is created it's grid is empty and I don't remember LuaEquipmentGrid.put() firing an event, leaving me with a car that will never move unless the player manually re-arranges the equipment or manually places the car.

On the problem of destroying, well I can call .valid every tick for every car and locomotive, but that's just wasteful. Saying calling .valid has no performance impact is just nonsense as explained here viewtopic.php?f=28&t=34916#p217808. I know very well how Lua works and what the associated overheads are of that innocent looking call before the game even gets to do the actual validity check.

This whole point of "mod actions don't fire events" goes way beyond accessing an invalid entity. But without coming up with a unified protocol of how mods should announce their actions it's just going to be a mess.

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 »

Had a chat on IRC, Rseding91 explained the reason:
<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.
My quick thought is that, on_pre_entity_destroyed will be called only after mod has called LuaEntity::destroy(), but still before the entity becomes invalid.
We don't need to know when a smoke particle is destroyed, we don't need to know a shell on ground is destroyed. We just care about our modded entities.
I understand it is inconsistent to the other events. Maybe someone can come up with a better solution? :)

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 »

Agreed - it will suffice for the events to only happen when Lua calls LuaSurface:create_entity() or LuaEntity.destroy().

It would be ideal if there was a fast way to filter the events - mod triggering event doesn't really need to receive it, as it already knows what's happened.
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: 1190
Joined: Wed May 18, 2016 4:55 pm
Contact:

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

Post by Mooncat »

Ok, after a (not so well) sleep, I come up with 2 proposals:

1) give LuaEntity.destroy() an optional boolean parameter, such that the on_pre_entity_destroyed event is invoked only if the parameter is provided and is true. Entity destruction in vanilla game won’t trigger the event.
If you do this, please also consider doing the same for LuaSurface.create_entity().


2) Add LuaEntity.can_destroy(). It returns whether the entity can be destroyed, so we can use it before raising on_entity_died safely.

Some people think that it is our (modders’) responsibility to find out our way to cooperate with each others. I would prefer the devs to help us solving conflicts by providing standard events. Anyway, it will be an endless debate as both cases work, with different pros and cons. But at least make sure the API is enough for us to do things right please? ;)

User avatar
bobingabout
Smart Inserter
Smart Inserter
Posts: 7352
Joined: Fri May 09, 2014 1:01 pm
Contact:

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

Post by bobingabout »

Klonan wrote:If i make a mod which breaks bobs mods something or other, it wouldn't be fair to ask bob to fix his mod,
Same with this, if another mods scripting is breaking your mod, it is their issue to fix
Actually... People have asked me to make adjustments to my mods before, because something they did in theirs would cause mine to break.

And while I generally agree, that if you wrote a mod that breaks mine, then it should be your responsibility to fix it, it depends entirely on why something is breaking.

Some of the changes to my mods include...
me adding an optional dependency of other persons mod so my data is loaded after.
Adding a check and remote.call to my mod to call a script in someone elses to get a modified daytime value.
Looking to see if a specific recipe exists from someone elses mod so I can do a recipe tweak to both theirs and mind to merge 2 similar but different items together. (Okay, THEY could have done that one)
And adding in various safety checks to not perform a data update on some entities during my blanket modification functions. (EG, modules when adding filters to productivity modules that don't have a limitation key set. Base game does, all mine do, so I didn't originally account for it not being there!)
I even completely re-wrote my ores mod to act more like a Dynamic ore creation library. though since split part of that off into the library mod. And that was back when Myself, Dytech and 5dim were talking about collaborating to make an combined ores mod for all 3 packages. unfortunately DyTech sort of bailed on the idea, and 5Dim then just decided to use mine.
aubergine18 wrote:
You should check for valid when you would naturally be doing something to your entity anyway,
Except when the mod doesn't do anything like that... Here's how this issue affects my bridge:

The ends of the bridge are `pipe-to-ground` entities. The ramps are the pipe covers :)

Once a bridge is constructed, my mod sits idle, there's no `on_tick`.

To allow the player to walk over the bridge, during construction I replace any water tiles between the two ends with fake water tiles that have the player collision mask removed.

If the end of the bridge, or any segment between the ends is destroyed, I get an event: If it's mined, if it dies (eg. due to fire), I get told about it. When this happens, I do one of two things:

* If it's bridge end, I find other end via `.neighbours`
* If it's a segment in the bridge, I know the`max_underground_distance` (well, I would) so I center that on the destroyed segment, then search that area for applicalbe end entity (`pipe-to-ground` of specific name), then find other end via `.neighbours`

Once I have both ends, I can safely destroy the bridge - both ends, the segments between them, and replacing fake water with real water again.

Now, along comes a mod that destroys one end of my bridge, or a segment... I know nothing about this. Even if that mod destroyed all entities in the bridge, the fake water would still be there and player would be able to walk on it.

This is why I need to know when something gets destroyed by a mod. It's why all this talk of "just check .valid" is of no use to me whatsoever, because my mod is not using `on_tick`. I've spent ages making sure the mod sits idle until triggered to do something (build bridge, destroy bridge) by an event. I've even set `.active = false` on the `pipe-to-ground` so that a built bridge has almost zero impact on UPS/FPS.

And then along comes this nightmare that `.destroy()` doesn't fire an event, with people telling me "just check .valid", which is equivalent to "just add on_tick handler and check .valid for every piece of every bridge on every surface" which in turn means "maintain a table of all your bridge entities, just so you can work out when one of them is destroyed by another mod".

A param on the destroy event to tell it to trigger an event would solve this problem, it would default to `false`. Mods such as Creative Mode, or anything else that can `.destroy` entities from other mods, would set the param to `true` to alert the other mod to their entity being destroyed.

As for the `create_entity()`, that also poses a problem. One of the things I wanted to do was write a separate mod that would automatically build a bridge at start of game if player is trapped on a small island. That should be simple case of placing two ends of bridge at which point my mod would do the rest (build the inner segments, swap water for fake water, etc). But, because `create_entity()` doesn't fire an event, my mod literally has no clue that another mod is trying to build a bridge. If `create_entity()` had an additional param, `false` by default, that told it to trigger an event, this problem would be easily solved.

The events would benefit any mod that uses compound entities (concrete lamppost, torches, ks_power, electric vehicles, etc) or wish to create/destroy entities from external mods (creative mode, vehicle wagon, etc).
I would say that your use case is a bit unique. The only real way I would suggest you work around this problem is.... by doing exactly what you don't want to do.
Keep a table of every bridge that has been placed... or at least their co-ordinates. and then during an on_tick, perform a check to see if the bridge still exists. Now, you don't need to do the check EVERY tick, you could perform a check every 300 ticks. or perhaps do something similar to what the devs are going to do with pollution, and perform a staggered check of your table every few ticks, so that it doesn't all hit at once.

You have options. There are solutions.
Creator of Bob's mods. Expanding your gameplay since version 0.9.8.
I also have a Patreon.

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 »

bobingabout wrote: I would say that your use case is a bit unique. The only real way I would suggest you work around this problem is.... by doing exactly what you don't want to do.
Keep a table of every bridge that has been placed... or at least their co-ordinates. and then during an on_tick, perform a check to see if the bridge still exists. Now, you don't need to do the check EVERY tick, you could perform a check every 300 ticks. or perhaps do something similar to what the devs are going to do with pollution, and perform a staggered check of your table every few ticks, so that it doesn't all hit at once.

You have options. There are solutions.

The best way to check in this case it 1 entity per tick, like this:

Code: Select all

function check_bridges()
  if not global.my_bridges then return end
  if not global.check_index then global.check_index = 1 end
  local bridge = global.my_bridges[global.check_index]
  if not bridge then global.check_index = 1 return end
  if not bridge.valid then 
    table.remove(global.my_bridges, global.check_index)
    return
  end
  if not bridge.neighbours[1] then
    bridge.destroy()
    table.remove(global.my_bridges, global.check_index)
    return
  end
  global.check_index = global.check_index + 1
end
Checking one at a time is pretty fine, it will never really affect performance, but it may take a long while to check all of them, which shouldn't be a problem in this case

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:Checking one at a time is pretty fine, it will never really affect performance, but it may take a long while to check all of them, which shouldn't be a problem in this case
But if you build many of them, the delay before a particular entity is iterated will be obvious. :(


Anyway, I have turned my concept into a solid example. It is not finished. Some technical problems still need to be solved, but they are out of this topic.
It aims to show the impact of using on_tick to check all entities in every frame.

Here is the mod:
supportive-turrets_0.1.0.zip
(4.2 KiB) Downloaded 92 times
What it does:
When player place the new turret, script will create another entity at the same position.
The entity is an indestructible unit. Once it is created, it is ordered to attack the turret. (It cannot be a turret because only unit accepts command. But now it doesn't consume electricity. I will find an alternative.)
But it doesn't deal damage. Instead, it creates poison cloud with negative damage, healing the surrounding ally.
Script is light. I added listeners to on_built_entity, on_robot_built_entity and on_canceled_deconstruction to create a unit when a turret is built or back to functional.
I also added listeners to on_preplayer_mined_item, on_entity_died and on_marked_for_deconstruction to remove the associated unit when a turret is mined, killed or becomes nonfunctional.
GIF
Because there is no event for entity.destroy(), on_tick is needed, as discussed.
To find the difference with and without validating all turrets in each frame, I created a remote interface, so you can use the following commands:
- Add the on_tick listener.

Code: Select all

/c remote.call("supportive-turrets", "add_on_tick")
- Remove the on_tick listener.

Code: Select all

/c remote.call("supportive-turrets", "remove_on_tick")
Here is the save in Factorio 0.14.16, with 19k turrets built.
Turrets.zip
(4.2 MiB) Downloaded 105 times
Statistics on my side:
When the on_tick listener was added, FPS/UPS started to drop slowly when the number of turrets reached 8k.
When the on_tick listener was removed, FPS/UPS started to drop when the number reached 18k. FPS/UPS was around 54 when there were 19k turrets.
Then, the on_tick listener was added back, FPS/UPS dropped to 34.

I couldn't say whether the number of turrets is good or not for the users, as I mainly write mods recently rather than actually playing the game. I would say it is better than I thought.
Few things to note:
- the numbers are showing the situation when ONLY ONE mod is running. I expect the actual number of modded entities before FPS/UPS starts dropping will be divided by the number of mods running the same algorithm.
- when I tried to blueprint 1k turrets and placed them at once, FPS/UPS dropped significantly and could not recover. (I doubt it is something related to the code in game.)
(Creative Mode was used for clearing out obstacles, creating the nice flat land and placing turrets. But it has been removed before testing.)

Now judge it yourself. ;)

User avatar
bobingabout
Smart Inserter
Smart Inserter
Posts: 7352
Joined: Fri May 09, 2014 1:01 pm
Contact:

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

Post by bobingabout »

Klonan wrote:Checking one at a time is pretty fine, it will never really affect performance, but it may take a long while to check all of them, which shouldn't be a problem in this case
I did actually think about that, but as Mooncat pointed out, if you have a very large number of them (thousands) it could take a long time to go through the list. 3600 ticks per minute.

But, I agree that sort of thing would probably be best, a couple of seconds probably isn't going to cause any issues.

Another method could be to choose an arbitrary number of ticks, say, 120. And then do a check of every 120th list entry, with a starting point of tick%120.

Still though, it's nice to see we're both thinking on the same page.
Creator of Bob's mods. Expanding your gameplay since version 0.9.8.
I also have a Patreon.

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: Statistics on my side:
When the on_tick listener was added, FPS/UPS started to drop slowly when the number of turrets reached 8k.
When the on_tick listener was removed, FPS/UPS started to drop when the number reached 18k. FPS/UPS was around 54 when there were 19k turrets.
Then, the on_tick listener was added back, FPS/UPS dropped to 34.

I couldn't say whether the number of turrets is good or not for the users, as I mainly write mods recently rather than actually playing the game. I would say it is better than I thought.
Few things to note:
- the numbers are showing the situation when ONLY ONE mod is running. I expect the actual number of modded entities before FPS/UPS starts dropping will be divided by the number of mods running the same algorithm.
- when I tried to blueprint 1k turrets and placed them at once, FPS/UPS dropped significantly and could not recover. (I doubt it is something related to the code in game.)
(Creative Mode was used for clearing out obstacles, creating the nice flat land and placing turrets. But it has been removed before testing.)

Now judge it yourself. ;)

You function is just very plain, you can optimize it very easily, you are checking all entities on every tick, you could spread the checks across multiple ticks, or only do a check every 60 ticks, or any number of things,
The way you have done it is somewhat of the least efficient way, for example this small addition would increase performance 60 times:

Code: Select all

local function on_tick(event)
  for surface_name, surface_position_x in pairs(global.supportive_turrets) do
    for x, surface_position_y in pairs(surface_position_x) do
      if (x + game.tick) % 60 == 0 then
        for y, data in pairs(surface_position_y) do
          if (x + y + game.tick) % check_period == 0 then
            if not data.turret.valid thn
              if data.unit.valid then
                data.unit.destroy()
              end
              global.supportive_turrets[surface_name][x][y] = nil
            end
          end
        end
      end
    end
  end
end

Post Reply

Return to “Modding discussion”