From 9aa0e6ab3d83ae3fcd65968ef617619b72681356 Mon Sep 17 00:00:00 2001 From: HybridDog Date: Mon, 13 Apr 2020 18:11:34 +0200 Subject: [PATCH] Add MapDataStorage code as replacement for get_data_from_pos etc. In comparison to using Minetest's hash_node_position, this saves additional information about the z-only and zy-only components, which is what the vector_extras code did. In comparison to the old vector_extras code, this does not create lots of lua tables, which is slow, but instead uses one table as hashmap. --- moremesecons_utils/init.lua | 238 +++++++++++++++++++++++++++++++++++- 1 file changed, 235 insertions(+), 3 deletions(-) diff --git a/moremesecons_utils/init.lua b/moremesecons_utils/init.lua index 1a6d2eb..37c513f 100644 --- a/moremesecons_utils/init.lua +++ b/moremesecons_utils/init.lua @@ -52,9 +52,241 @@ function moremesecons.remove_data_from_pos(sto, pos) sto.storage:set_string(sto.name, minetest.serialize(sto.tab)) end --- Vector helpers --- All the following functions are from the vector_extras mod (https://github.com/HybridDog/vector_extras). --- If you enable that mod, its functions will be used instead of the ones defined below +-- Some additional vector helpers + +-- The same as minetest.hash_node_position; I copied it to ensure backwards +-- compatibility and used hexadecimal number notation +local function node_position_key(pos) + return (pos.z + 0x8000) * 0x10000 * 0x10000 + + (pos.y + 0x8000) * 0x10000 + + pos.x + 0x8000 +end + +local MapDataStorage = {} +setmetatable(MapDataStorage, {__call = function() + local obj = {} + setmetatable(obj, MapDataStorage) + return obj +end}) +MapDataStorage.__index = { + getAt = function(self, pos) + return self[node_position_key(pos)] + end, + setAt = function(self, pos, data) + -- If x, y or z is omitted, the key corresponds to a position outside + -- of the map (hopefully), so it can be used to skip lines and planes + local vi_z = (pos.z + 0x8000) * 0x10000 * 0x10000 + local vi_zy = vi_z + (pos.y + 0x8000) * 0x10000 + local vi = vi_zy + pos.x + 0x8000 + local is_new = self[vi] == nil + self[vi] = data + if is_new then + self[vi_z] = (self[vi_z] or 0) + 1 + self[vi_zy] = (self[vi_zy] or 0) + 1 + end + end, + removeAt = function(self, pos) + local vi_z = (pos.z + 0x8000) * 0x10000 * 0x10000 + local vi_zy = vi_z + (pos.y + 0x8000) * 0x10000 + local vi = vi_zy + pos.x + 0x8000 + if self[vi] == nil then + -- Nothing to remove + return + end + self[vi] = nil + -- Update existence information for the xy plane and x line + self[vi_z] = self[vi_z] - 1 + if self[vi_z] == 0 then + self[vi_z] = nil + self[vi_zy] = nil + return + end + self[vi_zy] = self[vi_zy] - 1 + if self[vi_zy] == 0 then + self[vi_zy] = nil + end + end, + iter = function(self, pos1, pos2) + local ystride = 0x10000 + local zstride = 0x10000 * 0x10000 + + -- Skip z values where no data can be found + pos1 = vector.new(pos1) + local vi_z = (pos1.z + 0x8000) * 0x10000 * 0x10000 + while not self[vi_z] do + pos1.z = pos1.z + 1 + vi_z = vi_z + zstride + if pos1.z > pos2.z then + -- There are no values to iterate through + return function() return end + end + end + -- Skipping y values is not yet implemented and may require much code + + local xrange = pos2.x - pos1.x + 1 + local yrange = pos2.y - pos1.y + 1 + local zrange = pos2.z - pos1.z + 1 + + -- x-only and y-only parts of the vector index of pos1 + local vi_y = (pos1.y + 0x8000) * 0x10000 + local vi_x = pos1.x + 0x8000 + + local y = 0 + local z = 0 + + local vi = node_position_key(pos1) + local pos = vector.new(pos1) + local nextaction = vi + xrange + pos.x = pos.x - 1 + vi = vi - 1 + local function iterfunc() + -- continue along x until it needs to jump + vi = vi + 1 + pos.x = pos.x + 1 + if vi ~= nextaction then + local v = self[vi] + if v == nil then + -- No data here + return iterfunc() + end + -- The returned position must not be changed + return pos, v + end + + -- Reset x position + vi = vi - xrange + -- Go along y until pos2.y is exceeded + while true do + y = y + 1 + pos.y = pos.y + 1 + -- Set vi to index(pos1.x, pos1.y + y, pos1.z + z) + vi = vi + ystride + if y == yrange then + break + end + if self[vi - vi_x] then + nextaction = vi + xrange + + vi = vi - 1 + pos.x = pos1.x - 1 + return iterfunc() + end + -- Nothing along this x line, so increase y again + end + + -- Go back along y + vi = vi - yrange * ystride + y = 0 + pos.y = pos1.y + -- Go along z until pos2.z is exceeded + while true do + z = z + 1 + pos.z = pos.z + 1 + vi = vi + zstride + if z == zrange then + -- Cuboid finished, return nil + return + end + if self[vi - vi_x - vi_y] then + y = 0 + nextaction = vi + xrange + + vi = vi - 1 + pos.x = pos1.x - 1 + return iterfunc() + end + -- Nothing in this xy plane, so increase z again + end + end + return iterfunc + end, + iterAll = function(self) + local pairsfunc = pairs(self) + local function iterfunc() + local vi, v = pairsfunc() + if not vi then + return + end + local z = math.floor(vi / (0x10000 * 0x10000)) + vi = vi - z * 0x10000 * 0x10000 + local y = math.floor(vi / 0x10000) + if y == 0 or z == 0 then + -- The index does not refer to a position inside the map + return iterfunc() + end + local x = vi - y * 0x10000 - 0x8000 + y = y - 0x8000 + z = z - 0x8000 + return {x=x, y=y, z=z}, v + end + end, +} +moremesecons.MapDataStorage = MapDataStorage + + +-- This testing code shows an example usage of the MapDataStorage code +local function do_test() + print("Test if iter returns correct positions when a lot is set") + local data = MapDataStorage() + local k = 0 + for x = -5, 3 do + for y = -5, 3 do + for z = -5, 3 do + k = k + 1 + data:setAt({x=x, y=y, z=z}, k) + end + end + end + local expected_positions = {} + for z = -4, 2 do + for y = -4, 2 do + for x = -4, 2 do + expected_positions[#expected_positions+1] = {x=x, y=y, z=z} + end + end + end + local i = 0 + for pos, v in data:iter({x=-4, y=-4, z=-4}, {x=2, y=2, z=2}) do + i = i + 1 + assert(vector.equals(pos, expected_positions[i])) + end + + print("Test if iter works correctly on a corner") + local found = false + for pos, v in data:iter({x=-8, y=-7, z=-80}, {x=-5, y=-5, z=-5}) do + assert(not found) + found = true + assert(vector.equals(pos, {x=-5, y=-5, z=-5})) + end + assert(found) + + print("Test if iter finds all corners") + local expected_positions = {} + local k = 1 + for _, z in ipairs({-9, -6}) do + for _, y in ipairs({-9, -6}) do + for _, x in ipairs({-8, -6}) do + local pos = {x=x, y=y, z=z} + expected_positions[#expected_positions+1] = pos + data:setAt(pos, k) + k = k + 1 + end + end + end + local i = 1 + for pos, v in data:iter({x=-8, y=-9, z=-9}, {x=-6, y=-6, z=-6}) do + assert(v == i) + assert(vector.equals(pos, expected_positions[i])) + i = i + 1 + --~ print("found " .. minetest.pos_to_string(pos)) + end + assert(i == 8 + 1, "Not enough or too many corners found") + + --~ data:iterAll() +end + +do_test() + if not vector.get_data_from_pos then function vector.get_data_from_pos(tab, z,y,x)