mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2024-11-27 21:11:05 +01:00
Move queue to its own file, move test code to a function
This commit is contained in:
parent
3117f932a6
commit
d40b52bea5
2 changed files with 205 additions and 190 deletions
|
@ -1,204 +1,39 @@
|
|||
--[[
|
||||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
|
||||
== Radix/Finger queue class
|
||||
mcl_scheduler = {}
|
||||
local mod = mcl_scheduler
|
||||
|
||||
This is a queue based off the concepts behind finger trees (https://en.wikipedia.org/wiki/Finger_tree),
|
||||
the radix sort (https://en.wikipedia.org/wiki/Radix_sort) and funnel sort (https://en.wikipedia.org/wiki/Funnelsort)
|
||||
dofile(modpath.."/queue.lua")
|
||||
|
||||
This algorithm has O(1) deletion and O(k) insertion (k porportional to the time in the future) and uses log_4(n) tree nodes.
|
||||
function mod.test()
|
||||
local t = mod.queue.new()
|
||||
|
||||
At the top level of the queue, there is a 20-element array of linked lists containing tasks that are scheduled to
|
||||
start running in the current second. Removing the next linked list of tasks from this array is an O(1) operation.
|
||||
local test_times = { 15, 1, 7, 34, 50, 150, 14, 18, 20, 20, 15, }
|
||||
|
||||
The queue then has a one-sided finger tree of 20-element lists that are used to replace the initial queue when the
|
||||
second rolls over.
|
||||
local start_time = minetest.get_us_time()
|
||||
for _,time in pairs(test_times) do
|
||||
t:add_task({ time = time })
|
||||
|
||||
]]
|
||||
|
||||
function Class()
|
||||
local cls = {}
|
||||
cls.mt = { __index = cls }
|
||||
function cls:new(...)
|
||||
local inst = setmetatable({}, cls.mt)
|
||||
local construct = inst.construct
|
||||
if construct then inst:construct(...) end
|
||||
return inst
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("took "..tostring(stop_time - start_time).."us")
|
||||
start_time = stop_time
|
||||
end
|
||||
return cls
|
||||
end
|
||||
|
||||
local inner_queue = Class()
|
||||
function inner_queue:construct(level)
|
||||
self.level = level
|
||||
self.items = {}
|
||||
self.unsorted_count = 0
|
||||
self.slots = 4
|
||||
print(dump(t:tick()))
|
||||
print(dump(t))
|
||||
|
||||
-- Precompute slot size
|
||||
local slot_size = 20
|
||||
for i = 2,level do
|
||||
slot_size = slot_size * 4
|
||||
end
|
||||
self.slot_size = slot_size
|
||||
end
|
||||
function inner_queue:get()
|
||||
local slots = self.slots
|
||||
local slot = 5 - slots
|
||||
local ret = self.items[slot]
|
||||
self.items[slot] = nil
|
||||
local start_time = minetest.get_us_time()
|
||||
for i=1,60 do
|
||||
local s = t:tick()
|
||||
print("time="..tostring(i+1))
|
||||
print(dump(s))
|
||||
|
||||
-- Take a way a slot, then refill if needed
|
||||
slots = slots - 1
|
||||
if slots == 0 then
|
||||
if self.next_level then
|
||||
local next_level_get = self.next_level:get()
|
||||
if next_level_get then
|
||||
self.items = next_level_get.items
|
||||
else
|
||||
self.items = {}
|
||||
end
|
||||
slots = 4
|
||||
end
|
||||
end
|
||||
self.slots = slots
|
||||
|
||||
return ret or self:init_slot_list()
|
||||
end
|
||||
function inner_queue:add_tasks(tasks, time)
|
||||
local task = tasks
|
||||
local slot_size = self.slot_size
|
||||
local level = self.level
|
||||
local slots = self.slots
|
||||
|
||||
while task do
|
||||
local t = task.time
|
||||
t = t - time
|
||||
|
||||
if t > slot_size * slots then
|
||||
-- Add to list for next level in the finger tree
|
||||
task.time = t
|
||||
local curr_task = task
|
||||
task = task.next
|
||||
curr_task.next = self.first_unsorted
|
||||
local count = self.unsorted_count + 1
|
||||
if count > 20 then
|
||||
if not self.next_level then
|
||||
self.next_level = inner_queue:new(self.level + 1)
|
||||
end
|
||||
self.next_level:add_tasks(curr_task, slot_size * slots)
|
||||
self.first_unsorted = nil
|
||||
self.unsorted_count = 0
|
||||
else
|
||||
self.first_unsorted = curr_task
|
||||
self.unsorted_count = count
|
||||
end
|
||||
else
|
||||
-- Task belongs in a slot on this level
|
||||
local slot = math.floor(t / slot_size) + 1 + ( slots - 4 )
|
||||
t = t % slot_size
|
||||
task.time = t
|
||||
local curr_task = task
|
||||
task = task.next
|
||||
|
||||
local list = self.items[slot] or self:init_slot_list()
|
||||
self.items[slot] = list
|
||||
|
||||
if level == 1 then
|
||||
curr_task.next = list[t]
|
||||
list[t] = curr_task
|
||||
else
|
||||
list:add_tasks(curr_task, (slot - 1) * slot_size)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function inner_queue:init_slot_list()
|
||||
local level = self.level
|
||||
if level == 1 then
|
||||
return { {}, {}, {}, {} }
|
||||
else
|
||||
local r = {}
|
||||
for i=1,4 do
|
||||
r[i] = inner_queue:new(self.level - 1)
|
||||
end
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("took "..tostring(stop_time - start_time).."us")
|
||||
start_time = stop_time
|
||||
end
|
||||
end
|
||||
|
||||
local queue = Class()
|
||||
function queue:construct()
|
||||
self.items = {}
|
||||
self.unsorted_count = 0
|
||||
self.m_tick = 1
|
||||
self.next_level = inner_queue:new(1)
|
||||
end
|
||||
function queue:add_task(task)
|
||||
-- Adjust time to align with the start of the current second
|
||||
task.time = task.time + self.m_tick
|
||||
|
||||
-- Handle task in current seccond
|
||||
if task.time <= 20 then
|
||||
task.next = self.items[task.time]
|
||||
self.items[task.time] = task
|
||||
return
|
||||
end
|
||||
|
||||
local count = self.unsorted_count + 1
|
||||
if count > 20 then
|
||||
-- Push to next level
|
||||
self.unsorted_count = 0
|
||||
else
|
||||
-- Add to the list of tasks for later time slots
|
||||
task.next = self.first_unsorted
|
||||
self.first_unsorted = task
|
||||
self.unsorted_count = count
|
||||
end
|
||||
end
|
||||
function queue:tick()
|
||||
-- Get the tasks for this tick
|
||||
local ret = self.items[self.m_tick]
|
||||
self.items[self.m_tick] = nil
|
||||
self.m_tick = self.m_tick + 1
|
||||
|
||||
-- Handle second rollover
|
||||
if self.m_tick == 21 then
|
||||
-- Push items to next level
|
||||
if self.first_unsorted then
|
||||
self.next_level:add_tasks(self.first_unsorted, 20)
|
||||
self.first_unsorted = nil
|
||||
self.unsorted_count = 0
|
||||
end
|
||||
|
||||
self.items = self.next_level:get()
|
||||
self.m_tick = 1
|
||||
end
|
||||
|
||||
return ret or {}
|
||||
end
|
||||
|
||||
local t = queue.new()
|
||||
|
||||
local test_times = { 15, 1, 7, 34, 50, 150, 14, 18, 20, 20, 15, }
|
||||
|
||||
local start_time = minetest.get_us_time()
|
||||
for _,time in pairs(test_times) do
|
||||
t:add_task({ time = time })
|
||||
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("took "..tostring(stop_time - start_time).."us")
|
||||
start_time = stop_time
|
||||
end
|
||||
|
||||
print(dump(t:tick()))
|
||||
print(dump(t))
|
||||
|
||||
local start_time = minetest.get_us_time()
|
||||
for i=1,60 do
|
||||
local s = t:tick()
|
||||
print("time="..tostring(i+1))
|
||||
print(dump(s))
|
||||
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("took "..tostring(stop_time - start_time).."us")
|
||||
start_time = stop_time
|
||||
end
|
||||
mod.test()
|
||||
|
||||
|
|
180
mods/CORE/mcl_scheduler/queue.lua
Normal file
180
mods/CORE/mcl_scheduler/queue.lua
Normal file
|
@ -0,0 +1,180 @@
|
|||
local mod = mcl_scheduler
|
||||
|
||||
--[[
|
||||
|
||||
== Radix/Finger queue class
|
||||
|
||||
This is a queue based off the concepts behind finger trees (https://en.wikipedia.org/wiki/Finger_tree),
|
||||
the radix sort (https://en.wikipedia.org/wiki/Radix_sort) and funnel sort (https://en.wikipedia.org/wiki/Funnelsort)
|
||||
|
||||
This algorithm has O(1) deletion and O(k) insertion (k porportional to the time in the future) and uses log_4(n) tree nodes.
|
||||
|
||||
At the top level of the queue, there is a 20-element array of linked lists containing tasks that are scheduled to
|
||||
start running in the current second. Removing the next linked list of tasks from this array is an O(1) operation.
|
||||
|
||||
The queue then has a one-sided finger tree of 20-element lists that are used to replace the initial queue when the
|
||||
second rolls over.
|
||||
|
||||
]]
|
||||
|
||||
function Class()
|
||||
local cls = {}
|
||||
cls.mt = { __index = cls }
|
||||
function cls:new(...)
|
||||
local inst = setmetatable({}, cls.mt)
|
||||
local construct = inst.construct
|
||||
if construct then inst:construct(...) end
|
||||
return inst
|
||||
end
|
||||
return cls
|
||||
end
|
||||
|
||||
local inner_queue = Class()
|
||||
function inner_queue:construct(level)
|
||||
self.level = level
|
||||
self.items = {}
|
||||
self.unsorted_count = 0
|
||||
self.slots = 4
|
||||
|
||||
-- Precompute slot size
|
||||
local slot_size = 20
|
||||
for i = 2,level do
|
||||
slot_size = slot_size * 4
|
||||
end
|
||||
self.slot_size = slot_size
|
||||
end
|
||||
function inner_queue:get()
|
||||
local slots = self.slots
|
||||
local slot = 5 - slots
|
||||
local ret = self.items[slot]
|
||||
self.items[slot] = nil
|
||||
|
||||
-- Take a way a slot, then refill if needed
|
||||
slots = slots - 1
|
||||
if slots == 0 then
|
||||
if self.next_level then
|
||||
local next_level_get = self.next_level:get()
|
||||
if next_level_get then
|
||||
self.items = next_level_get.items
|
||||
else
|
||||
self.items = {}
|
||||
end
|
||||
slots = 4
|
||||
end
|
||||
end
|
||||
self.slots = slots
|
||||
|
||||
return ret or self:init_slot_list()
|
||||
end
|
||||
function inner_queue:add_tasks(tasks, time)
|
||||
local task = tasks
|
||||
local slot_size = self.slot_size
|
||||
local level = self.level
|
||||
local slots = self.slots
|
||||
|
||||
while task do
|
||||
local t = task.time
|
||||
t = t - time
|
||||
|
||||
if t > slot_size * slots then
|
||||
-- Add to list for next level in the finger tree
|
||||
task.time = t
|
||||
local curr_task = task
|
||||
task = task.next
|
||||
curr_task.next = self.first_unsorted
|
||||
local count = self.unsorted_count + 1
|
||||
if count > 20 then
|
||||
if not self.next_level then
|
||||
self.next_level = inner_queue:new(self.level + 1)
|
||||
end
|
||||
self.next_level:add_tasks(curr_task, slot_size * slots)
|
||||
self.first_unsorted = nil
|
||||
self.unsorted_count = 0
|
||||
else
|
||||
self.first_unsorted = curr_task
|
||||
self.unsorted_count = count
|
||||
end
|
||||
else
|
||||
-- Task belongs in a slot on this level
|
||||
local slot = math.floor(t / slot_size) + 1 + ( slots - 4 )
|
||||
t = t % slot_size
|
||||
task.time = t
|
||||
local curr_task = task
|
||||
task = task.next
|
||||
|
||||
local list = self.items[slot] or self:init_slot_list()
|
||||
self.items[slot] = list
|
||||
|
||||
if level == 1 then
|
||||
curr_task.next = list[t]
|
||||
list[t] = curr_task
|
||||
else
|
||||
list:add_tasks(curr_task, (slot - 1) * slot_size)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function inner_queue:init_slot_list()
|
||||
local level = self.level
|
||||
if level == 1 then
|
||||
return { {}, {}, {}, {} }
|
||||
else
|
||||
local r = {}
|
||||
for i=1,4 do
|
||||
r[i] = inner_queue:new(self.level - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local queue = Class()
|
||||
mod.queue = queue
|
||||
function queue:construct()
|
||||
self.items = {}
|
||||
self.unsorted_count = 0
|
||||
self.m_tick = 1
|
||||
self.next_level = inner_queue:new(1)
|
||||
end
|
||||
function queue:add_task(task)
|
||||
-- Adjust time to align with the start of the current second
|
||||
task.time = task.time + self.m_tick
|
||||
|
||||
-- Handle task in current seccond
|
||||
if task.time <= 20 then
|
||||
task.next = self.items[task.time]
|
||||
self.items[task.time] = task
|
||||
return
|
||||
end
|
||||
|
||||
local count = self.unsorted_count + 1
|
||||
if count > 20 then
|
||||
-- Push to next level
|
||||
self.unsorted_count = 0
|
||||
else
|
||||
-- Add to the list of tasks for later time slots
|
||||
task.next = self.first_unsorted
|
||||
self.first_unsorted = task
|
||||
self.unsorted_count = count
|
||||
end
|
||||
end
|
||||
function queue:tick()
|
||||
-- Get the tasks for this tick
|
||||
local ret = self.items[self.m_tick]
|
||||
self.items[self.m_tick] = nil
|
||||
self.m_tick = self.m_tick + 1
|
||||
|
||||
-- Handle second rollover
|
||||
if self.m_tick == 21 then
|
||||
-- Push items to next level
|
||||
if self.first_unsorted then
|
||||
self.next_level:add_tasks(self.first_unsorted, 20)
|
||||
self.first_unsorted = nil
|
||||
self.unsorted_count = 0
|
||||
end
|
||||
|
||||
self.items = self.next_level:get()
|
||||
self.m_tick = 1
|
||||
end
|
||||
|
||||
return ret or {}
|
||||
end
|
||||
|
Loading…
Reference in a new issue