boxes = {} local function bytes_to_string(bytes) 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 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 -- 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 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 -- 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 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 --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 version = read_u8() assert (version == 1) local sx = read_u16() local sy = read_u16() local sz = read_u16() -- 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() -- 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 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) print(dump(boxes_tree)) local minp = boxes.valloc(237) print(dump(minp)) print(dump(boxes_tree)) boxes.vfree(minp) print(dump(boxes_tree)) end) ]]