290 lines
6.9 KiB
Plaintext
290 lines
6.9 KiB
Plaintext
|
-- 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
|