-- 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 -- param 2 - Action ID as type -- param 3 - Timer in seconds as type -- param 4 - Function to execute as type -- 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