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.
This commit is contained in:
HybridDog 2020-04-13 18:11:34 +02:00 committed by Pierre-Adrien Langrognet
parent 54030d1620
commit 9aa0e6ab3d
1 changed files with 235 additions and 3 deletions

View File

@ -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)