Divergent/mods/PDA Taskboard/gamedata/scripts/optimized_time_events.script

290 lines
6.9 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
-- Optimized time events, using sorted arrays to peek only closest event to come
-- Designed to fully reimplement existing timed events with all its quirks
-- Written by demonized
-- Original description
--------------------------------------------------------------------------------------------------
-- DELAYED EVENT QUEUE
-------------------------------------------------------------------------------------------------------
--[[
-- Events must have a unique id. Such as object id or another identifier unique to the occasion.
-- Action id must be unique to the specific Event. This allows a single event to have many queued
-- actions waiting to happen.
--
-- Returning true will remove the queued action. Returning false will execute the action continuously.
-- This allows for events to wait for a specific occurrence, such as triggering after a certain amount of
-- time only when object is offline
--
-- param 1 - Event ID as type<any>
-- param 2 - Action ID as type<any>
-- param 3 - Timer in seconds as type<number>
-- param 4 - Function to execute as type<function>
-- extra params are passed to executing function as table as param 1
-- see on_game_load or state_mgr_animation.script for example uses
-- This does not persists through saves! So only use for non-important things.
-- For example, do not try to destroy npcs unless you do not care that it can fail before player saved then loaded.
--]]
local ev_queue = {}
local computed_ev_queue = {}
local math_floor = math.floor
local math_huge = math.huge
local table_insert = table.insert
local table_remove = table.remove
local has_alife_info = has_alife_info
local time_global = time_global
local pairs = pairs
local unpack = unpack
local tg = 0
local function print_table(table, subs)
local sub
if subs ~= nil then
sub = subs
else
sub = ""
end
for k,v in pairs(table) do
if type(v) == "table" then
print_table(v, sub.."["..k.."]----->")
elseif type(v) == "function" then
printf(sub.."%s = function",k)
elseif type(v) == "userdata" then
if (v.x) then
printf(sub.."%s = %s",k,utils_data.vector_to_string(v))
else
printf(sub.."%s = userdata", k)
end
elseif type(v) == "boolean" then
if v == true then
if(type(k)~="userdata") then
printf(sub.."%s = true",k)
else
printf(sub.."userdata = true")
end
else
if(type(k)~="userdata") then
printf(sub.."%s = false", k)
else
printf(sub.."userdata = false")
end
end
else
if v ~= nil then
printf(sub.."%s = %s", k,v)
else
printf(sub.."%s = nil", k,v)
end
end
end
end
local function queue_timer_compare(a, b)
return a.timer < b.timer
end
-- http://lua-users.org/wiki/BinaryInsert
local function binary_insert(t, value, fcomp)
-- Initialise compare function
local fcomp = fcomp or function(a, b) return a < b end
-- print_table(value)
-- Initialise numbers
local iStart, iEnd, iMid, iState = 1, #t, 1, 0
if iEnd == 0 then
t[1] = value
-- printf("adding in beginning table empty")
return 1
end
if fcomp(value, t[1]) then
-- printf("adding in beginning %s of %s", 1, iEnd)
table_insert(t, 1, value)
return 1
end
if not fcomp(value, t[iEnd]) then
-- printf("adding in end %s of %s", iEnd + 1, iEnd)
local pos = iEnd + 1
t[pos] = value
return pos
end
-- Get insert position
while iStart <= iEnd do
-- calculate middle
iMid = math_floor((iStart + iEnd) / 2)
-- compare
if fcomp(value, t[iMid]) then
iEnd, iState = iMid - 1, 0
else
iStart, iState = iMid + 1, 1
end
end
local pos = iMid + iState
-- printf("adding in middle %s of %s", pos, iEnd)
table_insert(t, pos, value)
return pos
end
local function refresh_ev_queue()
empty_table(computed_ev_queue)
-- tg = time_global()
local t = computed_ev_queue
for ev_id,actions in pairs(ev_queue) do
for act_id,act in pairs(actions) do
if (act_id ~= "__size") then
local d = {
ev_id = ev_id,
act_id = act_id,
timer = act.timer,
f = act.f,
p = act.p
}
binary_insert(t, d, queue_timer_compare)
end
end
end
end
local function find_ev_queue(ev_id, act_id)
for i = 1, #computed_ev_queue do
local t = computed_ev_queue[i]
if t.ev_id == ev_id and t.act_id == act_id then
return i
end
end
end
local function remove_ev_queue(ev_id, act_id)
local pos = find_ev_queue(ev_id, act_id)
if pos then return table_remove(computed_ev_queue, pos) end
end
function CreateTimeEvent(ev_id,act_id,timer,f,...)
if not (ev_queue[ev_id]) then
ev_queue[ev_id] = {}
ev_queue[ev_id].__size = 0
end
if not (ev_queue[ev_id][act_id]) then
local new_timer = time_global() + timer*1000
ev_queue[ev_id][act_id] = {}
ev_queue[ev_id][act_id].timer = new_timer
ev_queue[ev_id][act_id].f = f
ev_queue[ev_id][act_id].p = {...}
ev_queue[ev_id].__size = ev_queue[ev_id].__size + 1
local d = {
ev_id = ev_id,
act_id = act_id,
timer = new_timer,
f = f,
p = {...}
}
binary_insert(computed_ev_queue, d, queue_timer_compare)
end
end
function RemoveTimeEvent(ev_id,act_id)
if (ev_queue[ev_id] and ev_queue[ev_id][act_id]) then
ev_queue[ev_id][act_id] = nil
ev_queue[ev_id].__size = ev_queue[ev_id].__size - 1
remove_ev_queue(ev_id, act_id)
end
end
function ResetTimeEvent(ev_id,act_id,timer)
if (ev_queue[ev_id] and ev_queue[ev_id][act_id]) then
local new_timer = time_global() + timer*1000
ev_queue[ev_id][act_id].timer = new_timer
local el = remove_ev_queue(ev_id, act_id)
el.timer = new_timer
binary_insert(computed_ev_queue, el, queue_timer_compare)
end
end
local tg_past = 0
local to_remove = {}
function ProcessEventQueue(force)
if has_alife_info("sleep_active") then
return false
end
tg = time_global()
-- if tg > tg_past then
-- printf("tg %s", tg)
-- printf("computed")
-- print_table(computed_ev_queue)
-- tg_past = tg + 100
-- end
local force_refresh
for i = 1, #computed_ev_queue do
local t = computed_ev_queue[i] or (function()
force_refresh = true
return { timer = math_huge }
end)() -- Failsafe if event does not exist, refresh queue and postpone to next tick
if tg < t.timer then
break
end
if t.f(unpack(t.p)) == true then
local t1 = ev_queue[t.ev_id]
t1[t.act_id] = nil
t1.__size = t1.__size - 1
if t1.__size == 0 then
ev_queue[t.ev_id] = nil
end
to_remove[#to_remove + 1] = {
ev_id = t.ev_id,
act_id = t.act_id
}
end
end
if force_refresh then
refresh_ev_queue()
empty_table(to_remove)
elseif to_remove[1] then
for i = 1, #to_remove do
local t = to_remove[i]
remove_ev_queue(t.ev_id, t.act_id)
end
empty_table(to_remove)
end
return false
end
function ProcessEventQueueState(m_data,save)
if (save) then
m_data.event_queue = ev_queue
else
ev_queue = m_data.event_queue or ev_queue
end
refresh_ev_queue()
end
_G.CreateTimeEvent = CreateTimeEvent
_G.RemoveTimeEvent = RemoveTimeEvent
_G.ResetTimeEvent = ResetTimeEvent
_G.ProcessEventQueue = ProcessEventQueue
_G.ProcessEventQueueState = ProcessEventQueueState