Page 1 of 1

Event filters

Posted: Tue Sep 27, 2016 12:26 am
by aubergine18
I'm always cringing at the thought that my event handlers are wasting CPU cycles. I'm aware that the game already provides a lot of optimisation such as lazy-loading LuaCustomTable objects, but any unwanted events hitting Lua are eating up time, especially when lots of players have 50+ mods active in their games.

Would it be possible to add an additional parameter to `script.on_tick()` and `script.on_event()` that allows the game engine to do some internal filtering of events before sending stuff to Lua? The new "filter" param would be optional, and not all events will have it.

Example for on_tick:

Code: Select all

-- wait 30 ticks between each invocation of myHandlerFn:
script.on_tick( myHandlerFn, 30 )
I know there's a trivial way to do that in Lua, but it would still result in 29 unwanted calls to Lua.

By having the game engine handle the throttling, there are two potential advantages:
  • I could define multiple tick handlers, at different frequencies (eg. regular task every 2 ticks, housekeeping task every 600 ticks)
  • The game engine could defer calling the event if too much time has already been spent on current tick; because the Lua code is no longer checking specific tick modulus.
Another example: handling entity removal - I usually end up listening to 3 of events:

Code: Select all

-- only trigger these if the entity is 'foo' or 'bar'
local filter = { 'foo', 'bar' }
local e = defines.events
script.on_event(e.on_entity_died         , myHandlerFn, filter)
script.on_event(e.on_robot_pre_mined     , myHandlerFn, filter)
script.on_event(e.on_preplayer_mined_item, myHandlerFn, filter)
This way, my event handlers only get called when the event relates to the listed entities.

When removing event handlers (by specifying nil) the corresponding filter (if applicable) would need to be specified, for example:

Code: Select all

script.on_tick(nil, 30)
script.on_event(e.on_entity_died, nil, filter) -- remove listener for that specific filter
Note: I don't think I've yet seen any mod that ever removes event listeners, although I imagine it would be a good practice for modders to start doing that more often where possible.

Re: Event filters

Posted: Tue Sep 27, 2016 6:00 am
by DaveMcW
The lua compiler is very efficient for simple functions like on_tick() and "game.tick % 30".

Things only get slow when you need to call an expensive lua function or create a complex lua object.

Re: Event filters

Posted: Tue Sep 27, 2016 12:11 pm
by aubergine18
C++ is significantly more efficient.

Re: Event filters

Posted: Tue Oct 04, 2016 9:47 pm
by ssilk
Jumping in, cause we're discussing this also for the factorio-lib.

Event-Filters

Reference-code:
https://github.com/Afforess/Factorio-St ... trains.lua - at the end

Code: Select all

-- Filters events related to entity_type
-- @tparam string event_parameter The event parameter to look inside to find the entity type
-- @tparam string entity_type The entity type to filter events for
-- @tparam callable callback The callback to invoke if the filter passes. The object defined in the event parameter is passed.
local function filter_event(event_parameter, entity_type, callback)
    return function(evt)
        if(evt[event_parameter].name == entity_type) then
            callback(evt[event_parameter])
        end
    end
end

Code: Select all

-- When a locomotive is removed ..
Event.register(defines.events.on_entity_died, filter_event('entity', 'diesel-locomotive', Trains._on_locomotive_changed))
Event.register(defines.events.on_picked_up_item, filter_event('item_stack', 'diesel-locomotive', Trains._on_locomotive_changed))
Event.register(defines.events.on_player_mined_item, filter_event('item_stack', 'diesel-locomotive', Trains._on_locomotive_changed))
Event.register(defines.events.on_robot_mined, filter_event('item_stack', 'diesel-locomotive', Trains._on_locomotive_changed))

-- When a locomotive is added ..
Event.register(defines.events.on_built_entity, filter_event('created_entity', 'diesel-locomotive', Trains._on_locomotive_created))
Event.register(defines.events.on_robot_built_entity, filter_event('created_entity', 'diesel-locomotive', Trains._on_locomotive_created))
In C implemented and more tricky of course (with AND and OR or lists of searched entries and such stuff), this would be really useful.

Filter could be implemented as a table of options:

source: name of event-value, like "entity.train.locomotives" (source must be a scalar)
comperator: "==" (default), "<", ">", "contains", "in" ... the operation, that is evaluated between source and target
target: the value, that the comperator compared with the source. For the "in" operator it could be a list

So the a filter, that looks if my mod loco is used as first or last loco in a train can look somehow like so:

Code: Select all

{
    {
        source = "entity.train.locomotives.front_train.name[1]",
        target = "mymods-cool-loconame"
    },
    'or',
    {
        source = "entity.train.locomotives.back_train.name[1]",
        target = "mymods-cool-loconame"
    },

}
Timers
That is for me a completely different story. Events are coming mostly from the player or are more or less random state changes of the game-engine.

A timer is different from that, cause you KNOW when it should happen!

You can handle that kind of event completely different!

So - logically - for timer based events we need a different approach than for events. What I want is, that I can register an endless number of "timer-events" for ticks in the future.
Perhaps an implementation like javascript Timing Events? http://www.w3schools.com/js/js_timing.asp


Well, just quick ideas, how to improve life of mod-programmer a lot.


PS: Filtering mails is a more or less similar job. There are languages, that are specialized to this job: https://en.wikipedia.org/wiki/Sieve_(ma ... _language)
I just want to say, that configuration by complex array structure is a possible way to go for this kind of task.

Re: Event filters

Posted: Tue Oct 04, 2016 10:16 pm
by aubergine18
I'm not convinced the event filters would need to be so complex, as further processing could be done after the event is fired in Lua. IMO it would be better to keep the Lua API simple, with just an entity type filter, and then in the triggered Lua function any additional checks could be done to filter it more. GUI events might be an exception - there was some talk of adding a .mod field to GUI elements so that their events could more easily be filtered to a specific mod, but if not then we'd probably want to filter by the name field if it "contains" something (eg. a prefix that a mod uses for element names in a specific section of its gui).

Re: Event filters

Posted: Tue Oct 04, 2016 10:35 pm
by ssilk
Hm. Maybe that is too complex. :) Just simple "I want only events, that contain X at Y" should be enough.

I think the idea with the timers is still quite useful.

Re: Event filters

Posted: Tue Oct 04, 2016 10:44 pm
by aubergine18
Yes, I agree about timers.

As for the other events, the filter could be either `nil` (no filter, also ensures backwards compatibility), a string (single filter) or an array (list of filters done as 'OR').

Most mods are only really interested in the stuff they add, or quite specific things otherwise, so it's just simple way to reduce the number of times their event handlers are triggered by completely un-related stuff.