Tabs -> spaces

Add box allocation
This commit is contained in:
Ekdohibs 2017-01-26 16:12:57 +01:00
parent 396886ad28
commit c4c203384c

View File

@ -1,232 +1,390 @@
boxes = {} boxes = {}
local function bytes_to_string(bytes) local function bytes_to_string(bytes)
s = {} s = {}
for i = 1, #bytes do for i = 1, #bytes do
s[i] = string.char(bytes[i]) s[i] = string.char(bytes[i])
end end
return table.concat(s) return table.concat(s)
end end
local function string_to_bytes(str) local function string_to_bytes(str)
s = {} s = {}
for i = 1, string.len(str) do for i = 1, string.len(str) do
s[i] = string.byte(str, i) s[i] = string.byte(str, i)
end end
return s return s
end end
function boxes.save(minp, maxp) function boxes.save(minp, maxp)
--[[ TODO: save the box in incremental steps to avoid stalling the server. The --[[ TODO: save the box in incremental steps to avoid stalling the server. The
problem is that we have to iterate over all positions since there is no way problem is that we have to iterate over all positions since there is no way
to get the metadata of all nodes in an area, although the engine can. to get the metadata of all nodes in an area, although the engine can.
However, we still use a voxelmanip to load node bulk data. However, we still use a voxelmanip to load node bulk data.
]] ]]
local vm = minetest.get_voxel_manip(minp, maxp) local vm = minetest.get_voxel_manip(minp, maxp)
local emin, emax = vm:get_emerged_area() local emin, emax = vm:get_emerged_area()
local data = vm:get_data() local data = vm:get_data()
local param2 = vm:get_param2_data() local param2 = vm:get_param2_data()
local flat_data = {} local flat_data = {}
local flat_index = 1 local flat_index = 1
local function add_u8(x) local function add_u8(x)
flat_data[flat_index] = x flat_data[flat_index] = x
flat_index = flat_index + 1 flat_index = flat_index + 1
end end
local function add_u16(x) local function add_u16(x)
add_u8(math.floor(x / 256)) add_u8(math.floor(x / 256))
add_u8(x % 256) add_u8(x % 256)
end end
local function add_position(p) local function add_position(p)
add_u16(p.x - minp.x) add_u16(p.x - minp.x)
add_u16(p.y - minp.y) add_u16(p.y - minp.y)
add_u16(p.z - minp.z) add_u16(p.z - minp.z)
end end
local function add_string(s) local function add_string(s)
add_u16(string.len(s)) add_u16(string.len(s))
for i = 1, string.len(s) do for i = 1, string.len(s) do
add_u8(string.byte(s, i)) add_u8(string.byte(s, i))
end end
end end
-- Version -- Version
add_u8(1) add_u8(1)
local sx = maxp.x - minp.x + 1 local sx = maxp.x - minp.x + 1
local sy = maxp.y - minp.y + 1 local sy = maxp.y - minp.y + 1
local sz = maxp.z - minp.z + 1 local sz = maxp.z - minp.z + 1
-- Size -- Size
add_u16(sx) add_u16(sx)
add_u16(sy) add_u16(sy)
add_u16(sz) add_u16(sz)
local cid_mapping = {} local cid_mapping = {}
local cid_index = 0 local cid_index = 0
local cid_rmapping = {} local cid_rmapping = {}
local schem_size = sx * sy * sz local schem_size = sx * sy * sz
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax} local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
for z = minp.z, maxp.z do for z = minp.z, maxp.z do
for y = minp.y, maxp.y do for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z) local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do for x = minp.x, maxp.x do
local cid = data[index] local cid = data[index]
if cid_rmapping[cid] == nil then if cid_rmapping[cid] == nil then
cid_rmapping[cid] = cid_index cid_rmapping[cid] = cid_index
cid_mapping[cid_index] = minetest.get_name_from_content_id(cid) cid_mapping[cid_index] = minetest.get_name_from_content_id(cid)
cid_index = cid_index + 1 cid_index = cid_index + 1
end end
index = index + 1 index = index + 1
end end
end end
end end
-- Write cid mapping -- Write cid mapping
add_u16(cid_index) add_u16(cid_index)
for i = 0, cid_index - 1 do for i = 0, cid_index - 1 do
add_string(cid_mapping[i]) add_string(cid_mapping[i])
end end
-- Write bulk node data -- Write bulk node data
for z = minp.z, maxp.z do for z = minp.z, maxp.z do
for y = minp.y, maxp.y do for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z) local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do for x = minp.x, maxp.x do
add_u16(cid_rmapping[data[index]]) add_u16(cid_rmapping[data[index]])
add_u8(param2[index]) add_u8(param2[index])
index = index + 1 index = index + 1
end end
end end
end end
local meta_to_save = {} local meta_to_save = {}
local meta_save_index = 1 local meta_save_index = 1
for z = minp.z, maxp.z do for z = minp.z, maxp.z do
for y = minp.y, maxp.y do for y = minp.y, maxp.y do
for x = minp.x, maxp.x do for x = minp.x, maxp.x do
local meta = minetest.get_meta({x = x, y = y, z = z}) local meta = minetest.get_meta({x = x, y = y, z = z})
local meta_table = meta:to_table() local meta_table = meta:to_table()
if next(meta_table.fields) ~= nil or next(meta_table.inventory) ~= nil then if next(meta_table.fields) ~= nil or next(meta_table.inventory) ~= nil then
local inv = {} local inv = {}
for inv_name, inv_list in pairs(meta_table.inventory) do for inv_name, inv_list in pairs(meta_table.inventory) do
local inv_list_2 = {} local inv_list_2 = {}
for i, stack in ipairs(inv_list) do for i, stack in ipairs(inv_list) do
inv_list_2[i] = stack:to_string() inv_list_2[i] = stack:to_string()
end end
inv[inv_name] = inv_list_2 inv[inv_name] = inv_list_2
end end
meta_to_save[meta_save_index] = meta_to_save[meta_save_index] =
{ x = x - minp.x, y = y - minp.y, z = z - minp.z, { x = x - minp.x, y = y - minp.y, z = z - minp.z,
fields = meta_table.fields, fields = meta_table.fields,
inventory = inv inventory = inv
} }
meta_save_index = meta_save_index + 1 meta_save_index = meta_save_index + 1
end end
end end
end end
end end
add_u16(meta_save_index - 1) add_u16(meta_save_index - 1)
for i = 1, meta_save_index - 1 do for i = 1, meta_save_index - 1 do
local m = meta_to_save[i] local m = meta_to_save[i]
add_u16(m.x) add_u16(m.x)
add_u16(m.y) add_u16(m.y)
add_u16(m.z) add_u16(m.z)
add_string(minetest.serialize(m.fields)) add_string(minetest.serialize(m.fields))
add_string(minetest.serialize(m.inventory)) add_string(minetest.serialize(m.inventory))
end end
--print(dump(flat_data)) --print(dump(flat_data))
--print(dump(bytes_to_string(flat_data))) --print(dump(bytes_to_string(flat_data)))
local raw_data = bytes_to_string(flat_data) local raw_data = bytes_to_string(flat_data)
return minetest.compress(raw_data, "deflate") return minetest.compress(raw_data, "deflate")
end end
function boxes.load(minp, compressed) function boxes.load(minp, compressed)
local raw_data = minetest.decompress(compressed, "deflate") local raw_data = minetest.decompress(compressed, "deflate")
local raw_data_index = 1 local raw_data_index = 1
local function read_u8() local function read_u8()
local c = string.byte(raw_data, raw_data_index) local c = string.byte(raw_data, raw_data_index)
raw_data_index = raw_data_index + 1 raw_data_index = raw_data_index + 1
return c return c
end end
local function read_u16() local function read_u16()
local a = read_u8() local a = read_u8()
local b = read_u8() local b = read_u8()
return 256 * a + b return 256 * a + b
end end
local function read_string() local function read_string()
local len = read_u16() local len = read_u16()
local r = string.sub(raw_data, raw_data_index, raw_data_index + len - 1) local r = string.sub(raw_data, raw_data_index, raw_data_index + len - 1)
raw_data_index = raw_data_index + len raw_data_index = raw_data_index + len
return r return r
end end
local version = read_u8() local version = read_u8()
assert (version == 1) assert (version == 1)
local sx = read_u16() local sx = read_u16()
local sy = read_u16() local sy = read_u16()
local sz = read_u16() local sz = read_u16()
-- Read cid mapping -- Read cid mapping
local cid_mapping = {} local cid_mapping = {}
cid_index = read_u16() local cid_index = read_u16()
for i = 0, cid_index - 1 do for i = 0, cid_index - 1 do
cid_mapping[i] = minetest.get_content_id(read_string()) cid_mapping[i] = minetest.get_content_id(read_string())
end end
local maxp = {x = minp.x + sx - 1, y = minp.y + sy - 1, z = minp.z + sz - 1} local maxp = {x = minp.x + sx - 1, y = minp.y + sy - 1, z = minp.z + sz - 1}
local vm = minetest.get_voxel_manip(minp, maxp) local vm = minetest.get_voxel_manip(minp, maxp)
local emin, emax = vm:get_emerged_area() local emin, emax = vm:get_emerged_area()
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax} local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
local vmdata = vm:get_data() local vmdata = vm:get_data()
local param2 = vm:get_param2_data() local param2 = vm:get_param2_data()
-- Read bulk node data -- Read bulk node data
for z = minp.z, maxp.z do for z = minp.z, maxp.z do
for y = minp.y, maxp.y do for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z) local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do for x = minp.x, maxp.x do
vmdata[index] = cid_mapping[read_u16()] vmdata[index] = cid_mapping[read_u16()]
param2[index] = read_u8() param2[index] = read_u8()
index = index + 1 index = index + 1
end end
end end
end end
vm:set_data(vmdata) vm:set_data(vmdata)
vm:set_param2_data(param2) vm:set_param2_data(param2)
vm:update_liquids() vm:update_liquids()
vm:write_to_map() vm:write_to_map()
vm:update_map() vm:update_map()
-- Finally, read metadata -- Finally, read metadata
local nmeta = read_u16() local nmeta = read_u16()
for i = 1, nmeta do for i = 1, nmeta do
local x = read_u16() local x = read_u16()
local y = read_u16() local y = read_u16()
local z = read_u16() local z = read_u16()
local p = {x = minp.x + x, y = minp.y + y, z = minp.z + z} local p = {x = minp.x + x, y = minp.y + y, z = minp.z + z}
local meta = minetest.get_meta(p) local meta = minetest.get_meta(p)
local fields = minetest.deserialize(read_string()) local fields = minetest.deserialize(read_string())
local inv = minetest.deserialize(read_string()) local inv = minetest.deserialize(read_string())
for inv_name, inv_list in pairs(inv) do for inv_name, inv_list in pairs(inv) do
for i, stack in ipairs(inv_list) do for i, stack in ipairs(inv_list) do
inv_list[i] = ItemStack(inv_list[i]) inv_list[i] = ItemStack(inv_list[i])
end end
end end
meta:from_table({fields = fields, inventory = inv}) meta:from_table({fields = fields, inventory = inv})
end end
end end
--[[
Now for 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 = 64
local box_alloc_miny = 50
local boxes_tree = {
minp = {x = -16384, z = -16384},
edge_size = 32768,
max_alloc_size = 32768,
}
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
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
-- test code -- test code
--[[ --[[
minetest.after(5, function() minetest.after(5, function()
local data = boxes.save({x = -1, y = -4, z = -1}, local data = boxes.save({x = -1, y = -4, z = -1},
{x = 1, y = -2, z = 1}) {x = 1, y = -2, z = 1})
print(string.len(data)) print(string.len(data))
boxes.load({x = -10, y = 0, z = 5}, data) boxes.load({x = -10, y = 0, z = 5}, data)
print(dump(boxes_tree))
local minp = boxes.valloc(237)
print(dump(minp))
print(dump(boxes_tree))
boxes.vfree(minp)
print(dump(boxes_tree))
end) end)
]] ]]