612 lines
19 KiB
Lua

-- -------------------------------------------------------------------------------------------------------------------------------------------------------------
-- RAILUALIB EVENT MODULE
-- Event registration, conditional event management, GUI event filtering.
-- dependencies
local migration = require("__RaiLuaLib__.lualib.migration")
-- locals
local string_find = string.find
local table_insert = table.insert
local table_remove = table.remove
-- object
local event = {}
-- -----------------------------------------------------------------------------
-- DISPATCHING
-- holds registered events for dispatch
local events = {}
-- holds conditional event data
local conditional_events = {}
-- conditional events by group
local conditional_event_groups = {}
-- calls handler functions tied to an event
-- all non-bootstrap events go through this function
local function dispatch_event(e)
local global_data = global.__lualib.event
local con_registry = global_data.conditional_events
local player_lookup = global_data.players
local id = e.name
-- set ID for special events
if e.nth_tick then
id = -e.nth_tick
end
if e.input_name then
id = e.input_name
end
-- error checking
if not events[id] then
error("Event is registered but has no handlers!")
end
-- for later use
local elem = e.element
-- for every handler registered to this event
for _,t in ipairs(events[id]) do
local has_name = false
local options = t.options
-- check if any userdata has gone invalid since last iteration
if not options.skip_validation then
for _,v in pairs(e) do
if type(v) == "table" and v.__self and not v.valid then
return
end
end
end
-- check static GUI filters, if any
local filters = t.gui_filters
if filters then
if not elem then
log("Static event "..id.." has GUI filters but no GUI element, skipping!")
goto continue
end
if filters[elem.index] or filters[elem.name] then
goto call_handler
elseif options.match_filter_strings then
local name = elem.name
for _,filter in pairs(filters) do
-- check all string GUI filters to see if they partially match
if type(filter) == "string" and string_find(name, filter) then
goto call_handler
end
end
end
goto continue -- none of them matched
end
-- check conditional events
for name,_ in pairs(t.conditional_names) do
has_name = true
local con_data = con_registry[name]
if not con_data then error("Conditional event ["..name.."] has been raised, but has no data!") end
-- if con_data is true, then skip all checks and just call the handler
if con_data == true then
goto call_handler
else
local players = con_data.players
-- add registered players to the event
e.registered_players = players
if e.player_index then
local player_events = player_lookup[e.player_index]
-- check if this player is registered
if player_events and player_events[name] then
-- check GUI filters
local player_filters = con_data.gui_filters[e.player_index]
if player_filters then
if e.element then
if player_filters[elem.index] or player_filters[elem.name] then
goto call_handler
elseif options.match_filter_strings then
local elem_name = elem.name
for _,filter in pairs(player_filters) do
-- check all string GUI filters to see if they partially match
if type(filter) == "string" and string_find(elem_name, filter) then
goto call_handler
end
end
end
else
log("Conditional event "..name.." has GUI filters but no GUI element, skipping!")
end
else
goto call_handler
end
end
else
goto call_handler
end
end
end
-- if we're here and there was at least one conditional name, nothing caused the handler to trigger, so skip it
if has_name then goto continue end
::call_handler::
-- call the handler
t.handler(e)
::continue::
if options.force_crc then
game.force_crc()
end
end
return
end
-- BOOTSTRAP EVENTS
-- these events are handled specially and do not go through dispatch_event
script.on_init(function()
global.__lualib = {
__version = script.active_mods["RaiLuaLib"], -- current version
event = {conditional_events={}, players={}}
}
-- dispatch events
for _,t in ipairs(events.on_init or {}) do
t.handler()
end
-- dispatch postprocess events
for _,t in ipairs(events.on_init_postprocess or {}) do
t.handler()
end
end)
script.on_load(function()
-- dispatch events
for _,t in ipairs(events.on_load or {}) do
t.handler()
end
-- dispatch postprocess events
for _,t in ipairs(events.on_load_postprocess or {}) do
t.handler()
end
-- re-register conditional events
local registered = global.__lualib.event.conditional_events
for n,_ in pairs(registered) do
if conditional_events[n] then
event.enable(n, nil, nil, true)
end
end
end)
script.on_configuration_changed(function(e)
-- module migrations
if script.active_mods["RaiLuaLib"] ~= global.__lualib.__version then
migration.run(global.__lualib.__version or "0.1.0", {
["0.2.0"] = function()
-- convert all GUI filters to like-key -> value
for _,con_data in pairs(global.__lualib.event.conditional_events) do
for i,filters in pairs(con_data.gui_filters) do
local new = {}
for fi=1,#filters do
new[filters[fi]] = filters[fi]
end
con_data.gui_filters[i] = new
end
end
end
})
end
-- dispatch events
for _,t in ipairs(events.on_configuration_changed or {}) do
t.handler(e)
end
-- update lualib version
global.__lualib.__version = script.active_mods["RaiLuaLib"]
end)
-- -----------------------------------------------------------------------------
-- REGISTRATION
local bootstrap_events = {on_init=true, on_init_postprocess=true, on_load=true, on_load_postprocess=true, on_configuration_changed=true}
-- register static (non-conditional) events
-- used by register_conditional to insert the handler
-- conditional name is not to be used by the modder - it is internal only!
function event.register(id, handler, gui_filters, options, conditional_name)
options = options or {}
-- register handler
if type(id) ~= "table" then id = {id} end
for _,n in pairs(id) do
-- create event registry if it doesn't exist
if not events[n] then
events[n] = {}
end
local registry = events[n]
-- create master handler if not already created
if not bootstrap_events[n] then
if #registry == 0 then
if type(n) == "number" and n < 0 then
script.on_nth_tick(-n, dispatch_event)
else
script.on_event(n, dispatch_event)
end
end
end
-- make sure the handler has not already been registered
for _,t in ipairs(registry) do
if t.handler == handler then
-- add conditional name to the list if it's not already there
if conditional_name and not t.conditional_names[conditional_name] then
t.conditional_names[conditional_name] = true
end
-- do nothing else
return
end
end
-- format gui filters
local filters
if gui_filters then
if type(gui_filters) ~= "table" then
gui_filters = {gui_filters}
end
filters = {}
for _,filter in pairs(gui_filters) do
filters[filter] = filter
end
end
-- insert handler
local data = {handler=handler, gui_filters=filters, options=options, conditional_names={}}
if conditional_name then
data.conditional_names[conditional_name] = true
end
if options.insert_at then
table_insert(registry, options.insert_at, data)
else
table_insert(registry, data)
end
end
return
end
-- register conditional (non-static) events
-- called in on_init and on_load ONLY
function event.register_conditional(data)
for n,t in pairs(data) do
if conditional_events[n] then
error("Duplicate conditional event: "..n)
end
t.options = t.options or {}
-- add to conditional events table
conditional_events[n] = t
-- add to group lookup
local groups = t.group
if groups then
if type(groups) ~= "table" then groups = {groups} end
for i=1,#groups do
local group = conditional_event_groups[groups[i]]
if group then
group[#group+1] = n
else
conditional_event_groups[groups[i]] = {n}
end
end
end
end
end
-- enables a conditional event
function event.enable(name, player_index, gui_filters, reregister)
local data = conditional_events[name]
if not data then
error("Conditional event ["..name.."] was not registered and has no data!")
end
local global_data = global.__lualib.event
local saved_data = global_data.conditional_events[name]
local add_player_data = false
if saved_data then
-- update existing data / add this player
if player_index then
if saved_data == true then error("Tried to add a player to a global conditional event!") end
local player_lookup = global_data.players[player_index]
-- check if they're already registered
if player_lookup and player_lookup[name] then
-- don't do anything
if not data.options.suppress_logging then
log("Tried to re-register conditional event ["..name.."] for player "..player_index..", skipping!")
end
return
else
add_player_data = true
end
elseif not reregister then
if not data.options.suppress_logging then
log("Conditional event ["..name.."] was already registered, skipping!")
end
return
end
else
-- add to global
if player_index then
global_data.conditional_events[name] = {gui_filters={}, players={}}
add_player_data = true
else
global_data.conditional_events[name] = true
end
saved_data = global_data.conditional_events[name]
end
-- nest GUI filters into an array if they're not already
if gui_filters then
if type(gui_filters) ~= "table" then
gui_filters = {gui_filters}
end
end
-- add to player lookup table
if add_player_data then
local player_lookup = global_data.players[player_index]
-- add the player to the event
if gui_filters then
local new_filters = {}
for _,filter in pairs(gui_filters) do
new_filters[filter] = filter
end
saved_data.gui_filters[player_index] = new_filters
end
table_insert(saved_data.players, player_index)
-- add to player lookup table
if not player_lookup then
global_data.players[player_index] = {[name]=true}
else
player_lookup[name] = true
end
end
-- register handler
event.register(data.id, data.handler, data.gui_filters, data.options, name)
end
-- disables a conditional event
function event.disable(name, player_index)
local data = conditional_events[name]
if not data then
error("Tried to disable conditional event ["..name.."], which does not exist!")
end
local global_data = global.__lualib.event
local saved_data = global_data.conditional_events[name]
if not saved_data then
if not data.options.suppress_logging then
log("Tried to disable conditional event ["..name.."], which is not enabled!")
end
return
end
-- remove player from / manipulate global data
if player_index then
-- check if the player is actually registered to this event
if global_data.players[player_index][name] then
-- remove from players subtable
for i,pi in ipairs(saved_data.players) do
if pi == player_index then
table_remove(saved_data.players, i)
break
end
end
-- remove GUI filters
saved_data.gui_filters[player_index] = nil
-- remove from lookup table
global_data.players[player_index][name] = nil
-- remove lookup table if it's empty
if table_size(global_data.players[player_index]) == 0 then
global_data.players[player_index] = nil
end
else
if not data.options.suppress_logging then
log("Tried to disable conditional event ["..name.."] from player "..player_index.." when it wasn't enabled for them!")
end
return
end
if #saved_data.players == 0 then
global_data.conditional_events[name] = nil
else
-- don't do anything else
return
end
else
if type(saved_data) == "table" then
-- remove from all player lookup tables
local players = global_data.players
for i=1,#saved_data.players do
players[saved_data.players[i]][name] = nil
end
end
global_data.conditional_events[name] = nil
end
-- deregister handler
local id = data.id
if type(id) ~= "table" then id = {id} end
for _,n in pairs(id) do
local registry = events[n]
-- error checking
if not registry or #registry == 0 then
log("Tried to deregister an unregistered event of id: "..n)
return
end
for i,t in ipairs(registry) do
if t.handler == data.handler then
-- remove conditional name from table
t.conditional_names[name] = nil
if table_size(t.conditional_names) > 0 then
-- don't actually remove or deregister the handler
return
end
-- remove the handler from the events tables
table_remove(registry, i)
end
end
-- de-register the master handler if it's no longer needed
if #registry == 0 then
if type(n) == "number" and n < 0 then
script.on_nth_tick(math.abs(n), nil)
else
script.on_event(n, nil)
end
events[n] = nil
end
end
end
-- enables a group of conditional events
function event.enable_group(group, player_index, gui_filters)
local group_events = conditional_event_groups[group]
if not group_events then error("Group ["..group.."] has no handlers!") end
for i=1,#group_events do
event.enable(group_events[i], player_index, gui_filters)
end
end
-- disables a group of conditional events
function event.disable_group(group, player_index)
local group_events = conditional_event_groups[group]
if not group_events then error("Group ["..group.."] has no handlers!") end
for i=1,#group_events do
event.disable(group_events[i], player_index)
end
end
-- -------------------------------------
-- SHORTCUT FUNCTIONS
-- bootstrap events
function event.on_init(handler, options)
return event.register("on_init", handler, nil, options)
end
function event.on_load(handler, options)
return event.register("on_load", handler, nil, options)
end
function event.on_configuration_changed(handler, options)
return event.register("on_configuration_changed", handler, nil, options)
end
function event.on_nth_tick(nthTick, handler, options)
return event.register(-nthTick, handler, nil, options)
end
-- defines.events
for n,id in pairs(defines.events) do
event[n] = function(handler, options)
event.register(id, handler, options)
end
end
-- -----------------------------------------------------------------------------
-- EVENT MANIPULATION
-- raises an event as if it were actually called
function event.raise(id, table)
script.raise_event(id, table)
return
end
-- set or remove event filters
function event.set_filters(id, filters)
if type(id) ~= "table" then id = {id} end
for _,n in pairs(id) do
script.set_event_filter(n, filters)
end
return
end
-- holds custom event IDs
local custom_id_registry = {}
-- generates or retrieves a custom event ID
function event.get_id(name)
if not custom_id_registry[name] then
custom_id_registry[name] = script.generate_event_name()
end
return custom_id_registry[name]
end
-- saves a custom event ID
function event.save_id(name, id)
if custom_id_registry[name] then
log("Overwriting entry in custom event registry: "..name)
end
custom_id_registry[name] = id
end
-- updates the GUI filters for the given conditional event
function event.update_gui_filters(name, player_index, filters, mode)
mode = mode or "overwrite"
if type(filters) ~= "table" then
filters = {filters}
end
local con_data = global.__lualib.event.conditional_events[name]
if not con_data then
error("Tried to update GUI filters for event ["..name.."], which is not enabled!")
end
local filters_table = con_data.gui_filters
if mode == "overwrite" then
local t = {}
for _,filter in pairs(filters) do
t[filter] = filter
end
filters_table[player_index] = t
else
-- retrieve or create player GUI filters table
local player_filters = filters_table[player_index]
if not player_filters then
filters_table[player_index] = {}
player_filters = filters_table[player_index]
end
-- modify filters
if mode == "add" then -- add each one
for _,filter in pairs(filters) do
player_filters[filter] = filter
end
elseif mode == "remove" then -- remove each one
for _,filter in pairs(filters) do
player_filters[filter] = nil
end
-- remove filters table if it is empty
if table_size(player_filters) == 0 then
filters_table[player_index] = nil
end
end
end
-- return the filters
return filters_table[player_index]
end
-- retrieves and returns GUI filters for the given conditional event and player
function event.get_gui_filters(name, player_index)
local con_data = global.__lualib.event.conditional_events[name]
if not con_data then
error("Tried to retrieve GUI filters for conditional event ["..name.."], which is not enabled!")
end
return con_data.gui_filters[player_index]
end
-- retrieves and returns the global data for the given conditional event
function event.get_event_data(name)
return global.__lualib.event.conditional_events[name]
end
-- returns true if the conditional event is registered
function event.is_enabled(name, player_index)
local global_data = global.__lualib.event
local registry = global_data.conditional_events[name]
if registry then
if player_index then
for _,i in ipairs(registry.players) do
if i == player_index then
return true
end
end
return false
end
return true
end
return false
end
-- -----------------------------------------------------------------------------
event.events = events
event.conditional_events = conditional_events
event.conditional_event_groups = conditional_event_groups
return event