insidethebox/mods/boxes/valloc.lua
Auke Kok 80cbd6d34a Add license headers to all lua files.
Some of these are copies from the respective origins from mtg,
to make sure we have headers everywhere listing the proper code.

I've relicensed spectator_mode from WT*PL to LGPL-2.1. No other
licenses were changed.
2018-06-21 22:56:48 -07:00

179 lines
4.8 KiB
Lua

--[[
ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 2.1
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
MA 02111-1307 USA
]]--
--[[
Box allocation.
To keep things simple, we will always allocate boxes with same width and
depth, and with a sizee that is a multiple of boxes_resolution. Height is
assumed to be unlimited above box_alloc_miny.
In order to do that, we keep a quadtree of allocatable positions; an allocated
box will always be a leaf of that tree. We also keep for each subtree the max
size that can be allocated in it.
Thus, this is https://en.wikipedia.org/wiki/Buddy_memory_allocation in 2D.
]]
local boxes_resolution = 256
local box_alloc_miny = 50
local boxes_tree = {
minp = {x = 1024, z = 1024},
edge_size = 8192,
max_alloc_size = 8192,
}
local function split_leaf(node)
assert (node.edge_size >= 2 * boxes_resolution)
assert (node.children == nil)
local new_size = node.edge_size / 2
local x = node.minp.x
local z = node.minp.z
node.children = {
{
minp = {x = x, z = z},
edge_size = new_size,
max_alloc_size = new_size,
parent = node
},
{
minp = {x = x + new_size, z = z},
edge_size = new_size,
max_alloc_size = new_size,
parent = node,
},
{
minp = {x = x, z = z + new_size},
edge_size = new_size,
max_alloc_size = new_size,
parent = node,
},
{
minp = {x = x + new_size, z = z + new_size},
edge_size = new_size,
max_alloc_size = new_size,
parent = node,
},
}
end
-- Update `max_alloc_size` for node and all its parents.
-- Also, merge subtrees if possible.
local function update_alloc_sizes(node)
while node ~= nil do
local max_size = 0
local el = node.edge_size / 2
local can_merge = true
-- a crash here with the following error msg:
-- `bad argument #1 to 'ipairs' (table expected, got nil)`
-- means that there is an unknown node on the map
-- hence, this assert:
assert(node.children)
for _, child in ipairs(node.children) do
max_size = math.max(max_size, child.max_alloc_size)
if child.max_alloc_size < el then
can_merge = false
end
end
if can_merge then
node.children = nil
node.max_alloc_size = node.edge_size
else
node.max_alloc_size = max_size
end
node = node.parent
end
end
-- Function to know how close to zero a node is.
-- Used to keep allocations close to (0, box_alloc_miny, 0).
local function zero_close(node)
local x = node.minp.x
local z = node.minp.z
local size = node.edge_size
if x < 0 then x = x + size end
if z < 0 then z = z + size end
return math.abs(x) + math.abs(z)
end
-- Allocated a box of size `size` horizontally, and unbounded vertically
-- Returns box minimum position. The minimum position must be given to
-- boxes.vfree to free the corresponding area.
function boxes.valloc(size)
assert (size > 0)
if boxes_tree.max_alloc_size < size then
return nil
end
-- Find leaf with enough room, splitting it if big enough
local node = boxes_tree
while node.edge_size / 2 >= size and node.edge_size / 2 >= boxes_resolution do
if node.children == nil then
split_leaf(node)
end
local best = nil
local best_distance = 1e10 -- infinity
for _, child in ipairs(node.children) do
if child.max_alloc_size >= size and zero_close(child) < best_distance then
best_distance = zero_close(child)
best = child
end
end
assert (best ~= nil)
node = best
end
local result = {x = node.minp.x, y = box_alloc_miny, z = node.minp.z}
node.max_alloc_size = 0
update_alloc_sizes(node.parent)
return result
end
function boxes.vfree(minp)
assert (minp.y == box_alloc_miny)
assert (boxes_tree.minp.x <= minp.x)
assert (minp.x < boxes_tree.minp.x + boxes_tree.edge_size)
assert (boxes_tree.minp.z <= minp.z)
assert (minp.z < boxes_tree.minp.z + boxes_tree.edge_size)
-- Find leaf containing minp
local node = boxes_tree
while node.children ~= nil do
local cld = nil
local el = node.edge_size / 2
for _, child in ipairs(node.children) do
if child.minp.x <= minp.x and minp.x < child.minp.x + el
and child.minp.z <= minp.z and minp.z < child.minp.z + el then
cld = child
break
end
end
assert (cld ~= nil)
node = cld
end
assert (node.max_alloc_size == 0)
node.max_alloc_size = node.edge_size
update_alloc_sizes(node.parent)
end