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 = {}
local function bytes_to_string(bytes)
s = {}
for i = 1, #bytes do
s[i] = string.char(bytes[i])
end
return table.concat(s)
s = {}
for i = 1, #bytes do
s[i] = string.char(bytes[i])
end
return table.concat(s)
end
local function string_to_bytes(str)
s = {}
for i = 1, string.len(str) do
s[i] = string.byte(str, i)
end
return s
s = {}
for i = 1, string.len(str) do
s[i] = string.byte(str, i)
end
return s
end
function boxes.save(minp, maxp)
--[[ 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
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.
]]
local vm = minetest.get_voxel_manip(minp, maxp)
local emin, emax = vm:get_emerged_area()
local data = vm:get_data()
local param2 = vm:get_param2_data()
local flat_data = {}
local flat_index = 1
local function add_u8(x)
flat_data[flat_index] = x
flat_index = flat_index + 1
end
local function add_u16(x)
add_u8(math.floor(x / 256))
add_u8(x % 256)
end
local function add_position(p)
add_u16(p.x - minp.x)
add_u16(p.y - minp.y)
add_u16(p.z - minp.z)
end
local function add_string(s)
add_u16(string.len(s))
for i = 1, string.len(s) do
add_u8(string.byte(s, i))
end
end
--[[ 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
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.
]]
local vm = minetest.get_voxel_manip(minp, maxp)
local emin, emax = vm:get_emerged_area()
local data = vm:get_data()
local param2 = vm:get_param2_data()
local flat_data = {}
local flat_index = 1
local function add_u8(x)
flat_data[flat_index] = x
flat_index = flat_index + 1
end
local function add_u16(x)
add_u8(math.floor(x / 256))
add_u8(x % 256)
end
local function add_position(p)
add_u16(p.x - minp.x)
add_u16(p.y - minp.y)
add_u16(p.z - minp.z)
end
local function add_string(s)
add_u16(string.len(s))
for i = 1, string.len(s) do
add_u8(string.byte(s, i))
end
end
-- Version
add_u8(1)
-- Version
add_u8(1)
local sx = maxp.x - minp.x + 1
local sy = maxp.y - minp.y + 1
local sz = maxp.z - minp.z + 1
-- Size
add_u16(sx)
add_u16(sy)
add_u16(sz)
local sx = maxp.x - minp.x + 1
local sy = maxp.y - minp.y + 1
local sz = maxp.z - minp.z + 1
-- Size
add_u16(sx)
add_u16(sy)
add_u16(sz)
local cid_mapping = {}
local cid_index = 0
local cid_rmapping = {}
local schem_size = sx * sy * sz
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
local cid_mapping = {}
local cid_index = 0
local cid_rmapping = {}
local schem_size = sx * sy * sz
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do
local cid = data[index]
if cid_rmapping[cid] == nil then
cid_rmapping[cid] = cid_index
cid_mapping[cid_index] = minetest.get_name_from_content_id(cid)
cid_index = cid_index + 1
end
index = index + 1
end
end
end
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do
local cid = data[index]
if cid_rmapping[cid] == nil then
cid_rmapping[cid] = cid_index
cid_mapping[cid_index] = minetest.get_name_from_content_id(cid)
cid_index = cid_index + 1
end
index = index + 1
end
end
end
-- Write cid mapping
add_u16(cid_index)
for i = 0, cid_index - 1 do
add_string(cid_mapping[i])
end
-- Write cid mapping
add_u16(cid_index)
for i = 0, cid_index - 1 do
add_string(cid_mapping[i])
end
-- Write bulk node data
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do
add_u16(cid_rmapping[data[index]])
add_u8(param2[index])
index = index + 1
end
end
end
-- Write bulk node data
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do
add_u16(cid_rmapping[data[index]])
add_u8(param2[index])
index = index + 1
end
end
end
local meta_to_save = {}
local meta_save_index = 1
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
for x = minp.x, maxp.x do
local meta = minetest.get_meta({x = x, y = y, z = z})
local meta_table = meta:to_table()
if next(meta_table.fields) ~= nil or next(meta_table.inventory) ~= nil then
local inv = {}
for inv_name, inv_list in pairs(meta_table.inventory) do
local inv_list_2 = {}
for i, stack in ipairs(inv_list) do
inv_list_2[i] = stack:to_string()
end
inv[inv_name] = inv_list_2
end
meta_to_save[meta_save_index] =
{ x = x - minp.x, y = y - minp.y, z = z - minp.z,
fields = meta_table.fields,
inventory = inv
}
meta_save_index = meta_save_index + 1
end
end
end
end
local meta_to_save = {}
local meta_save_index = 1
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
for x = minp.x, maxp.x do
local meta = minetest.get_meta({x = x, y = y, z = z})
local meta_table = meta:to_table()
if next(meta_table.fields) ~= nil or next(meta_table.inventory) ~= nil then
local inv = {}
for inv_name, inv_list in pairs(meta_table.inventory) do
local inv_list_2 = {}
for i, stack in ipairs(inv_list) do
inv_list_2[i] = stack:to_string()
end
inv[inv_name] = inv_list_2
end
meta_to_save[meta_save_index] =
{ x = x - minp.x, y = y - minp.y, z = z - minp.z,
fields = meta_table.fields,
inventory = inv
}
meta_save_index = meta_save_index + 1
end
end
end
end
add_u16(meta_save_index - 1)
for i = 1, meta_save_index - 1 do
local m = meta_to_save[i]
add_u16(m.x)
add_u16(m.y)
add_u16(m.z)
add_string(minetest.serialize(m.fields))
add_string(minetest.serialize(m.inventory))
end
add_u16(meta_save_index - 1)
for i = 1, meta_save_index - 1 do
local m = meta_to_save[i]
add_u16(m.x)
add_u16(m.y)
add_u16(m.z)
add_string(minetest.serialize(m.fields))
add_string(minetest.serialize(m.inventory))
end
--print(dump(flat_data))
--print(dump(bytes_to_string(flat_data)))
local raw_data = bytes_to_string(flat_data)
return minetest.compress(raw_data, "deflate")
--print(dump(flat_data))
--print(dump(bytes_to_string(flat_data)))
local raw_data = bytes_to_string(flat_data)
return minetest.compress(raw_data, "deflate")
end
function boxes.load(minp, compressed)
local raw_data = minetest.decompress(compressed, "deflate")
local raw_data_index = 1
local function read_u8()
local c = string.byte(raw_data, raw_data_index)
raw_data_index = raw_data_index + 1
return c
end
local function read_u16()
local a = read_u8()
local b = read_u8()
return 256 * a + b
end
local function read_string()
local len = read_u16()
local r = string.sub(raw_data, raw_data_index, raw_data_index + len - 1)
raw_data_index = raw_data_index + len
return r
end
local raw_data = minetest.decompress(compressed, "deflate")
local raw_data_index = 1
local function read_u8()
local c = string.byte(raw_data, raw_data_index)
raw_data_index = raw_data_index + 1
return c
end
local function read_u16()
local a = read_u8()
local b = read_u8()
return 256 * a + b
end
local function read_string()
local len = read_u16()
local r = string.sub(raw_data, raw_data_index, raw_data_index + len - 1)
raw_data_index = raw_data_index + len
return r
end
local version = read_u8()
assert (version == 1)
local version = read_u8()
assert (version == 1)
local sx = read_u16()
local sy = read_u16()
local sz = read_u16()
local sx = read_u16()
local sy = read_u16()
local sz = read_u16()
-- Read cid mapping
local cid_mapping = {}
cid_index = read_u16()
for i = 0, cid_index - 1 do
cid_mapping[i] = minetest.get_content_id(read_string())
end
-- Read cid mapping
local cid_mapping = {}
local cid_index = read_u16()
for i = 0, cid_index - 1 do
cid_mapping[i] = minetest.get_content_id(read_string())
end
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 emin, emax = vm:get_emerged_area()
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
local vmdata = vm:get_data()
local param2 = vm:get_param2_data()
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 emin, emax = vm:get_emerged_area()
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
local vmdata = vm:get_data()
local param2 = vm:get_param2_data()
-- Read bulk node data
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do
vmdata[index] = cid_mapping[read_u16()]
param2[index] = read_u8()
index = index + 1
end
end
end
vm:set_data(vmdata)
vm:set_param2_data(param2)
vm:update_liquids()
vm:write_to_map()
vm:update_map()
-- Read bulk node data
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
local index = va:index(minp.x, y, z)
for x = minp.x, maxp.x do
vmdata[index] = cid_mapping[read_u16()]
param2[index] = read_u8()
index = index + 1
end
end
end
vm:set_data(vmdata)
vm:set_param2_data(param2)
vm:update_liquids()
vm:write_to_map()
vm:update_map()
-- Finally, read metadata
local nmeta = read_u16()
for i = 1, nmeta do
local x = read_u16()
local y = read_u16()
local z = read_u16()
local p = {x = minp.x + x, y = minp.y + y, z = minp.z + z}
local meta = minetest.get_meta(p)
local fields = minetest.deserialize(read_string())
local inv = minetest.deserialize(read_string())
for inv_name, inv_list in pairs(inv) do
for i, stack in ipairs(inv_list) do
inv_list[i] = ItemStack(inv_list[i])
end
end
meta:from_table({fields = fields, inventory = inv})
end
-- Finally, read metadata
local nmeta = read_u16()
for i = 1, nmeta do
local x = read_u16()
local y = read_u16()
local z = read_u16()
local p = {x = minp.x + x, y = minp.y + y, z = minp.z + z}
local meta = minetest.get_meta(p)
local fields = minetest.deserialize(read_string())
local inv = minetest.deserialize(read_string())
for inv_name, inv_list in pairs(inv) do
for i, stack in ipairs(inv_list) do
inv_list[i] = ItemStack(inv_list[i])
end
end
meta:from_table({fields = fields, inventory = inv})
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
--[[
minetest.after(5, function()
local data = boxes.save({x = -1, y = -4, z = -1},
{x = 1, y = -2, z = 1})
print(string.len(data))
boxes.load({x = -10, y = 0, z = 5}, data)
local data = boxes.save({x = -1, y = -4, z = -1},
{x = 1, y = -2, z = 1})
print(string.len(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)
]]