diff --git a/mods/CORE/vl_map_index/api.lua b/mods/CORE/vl_map_index/api.lua index c7c47b3bb..935c5d596 100644 --- a/mods/CORE/vl_map_index/api.lua +++ b/mods/CORE/vl_map_index/api.lua @@ -7,6 +7,25 @@ local storage = minetest.get_mod_storage() local mod = vl_map_index mod.main_thread = true +local cache = {} +function mod.get_index(key) + local cached = cache[key] + if cached then return cached end + + local value = storage:get_string(key) + if value ~= "" then + local data = mod.unpack_index(value) + cache[key] = data + return data + end + + return nil +end +function mod.clear_cache(key) + cache[key] = nil +end + + local index_cache = {} local index_cache_count = 0 function mod.cache_map_index(map_block_hash, indexer, result) diff --git a/mods/CORE/vl_map_index/init.lua b/mods/CORE/vl_map_index/init.lua index 6da8aabc6..adf81d4f6 100644 --- a/mods/CORE/vl_map_index/init.lua +++ b/mods/CORE/vl_map_index/init.lua @@ -87,7 +87,9 @@ local function process_dirty_block() if post_processor then result = post_processor(map_block_hash, result) end - storage:set_string(block_data.index_string, tostring(result)) + local key = block_data.index_string + storage:set_string(key, tostring(result)) + mod.clear_cache(key) index_in_process = false decompress_queue[#decompress_queue + 1] = {map_block_hash, indexer, result} diff --git a/mods/CORE/vl_map_index/node_counter.lua b/mods/CORE/vl_map_index/node_counter.lua index 51e05b171..3d8556dae 100644 --- a/mods/CORE/vl_map_index/node_counter.lua +++ b/mods/CORE/vl_map_index/node_counter.lua @@ -9,15 +9,21 @@ end local MIN_SEARCH_SIZE = 1000 local function block_pos_hash_vector(min, pos) - return (pos.x - min.x) * 10000+ - (pos.y - min.y) * 100 + + return (pos.x - min.x) * 10000 + + (pos.y - min.y) * 100 + (pos.z - min.z) end local function block_pos_hash(min, x, y, z) - return (x - min.x) * 10000+ - (y - min.y) * 100 + + return (x - min.x) * 10000 + + (y - min.y) * 100 + (z - min.z) end +local function get_block_pos_from_hash(min, hash) + local dz = hash % 100; hash = (hash - dz) / 100 + local dy = hash % 100; hash = (hash - dy) / 100 + local dx = hash % 100 + return vector.offset(min, dx, dy, dz) +end local l2_tasks = {} local l3_tasks = {} @@ -252,12 +258,18 @@ mod.register_indexer("node_counter",{ local pos = minetest.get_position_from_hash(map_block_hash) local l2_block = mod.map_block(pos) - storage:set_string("node_counter.l2"..vector.to_string(l2_block), unpacked_result.l2_index) + local key = "node_counter.l2"..vector.to_string(l2_block) + storage:set_string(key, unpacked_result.l2_index) + mod.clear_cache(key) local l3_block = mod.map_block(l2_block) - storage:set_string("node_counter.l3"..vector.to_string(l3_block), unpacked_result.l3_index) + local key = "node_counter.l3"..vector.to_string(l3_block) + storage:set_string(key, unpacked_result.l3_index) + mod.clear_cache(key) - storage:set_string("node_counter.l4", unpacked_result.l4_index) + key = "node_counter.l4" + storage:set_string(key, unpacked_result.l4_index) + mod.clear_cache(key) return unpacked_result.index end, @@ -265,48 +277,233 @@ mod.register_indexer("node_counter",{ if not mod.main_thread then return end +local function closest_block_in_region(pos, min, max) + local closest = vector.new(pos) + + if closest.x < min.x then + closest.x = min.x + elseif closest.x > max.x then + closest.x = max.x + end + + if closest.y < min.y then + closest.y = min.y + elseif closest.y > max.y then + closest.y = max.y + end + + if closest.z < min.z then + closest.z = min.z + elseif closest.z > max.z then + closest.z = max.z + end + + return closest +end +local function distance_to_block(pos, min, max) + return vector.distance(pos, closest_block_in_region(pos, min, max)) +end + +local function expand_nodenames(nodenames, l4_index) + local nodes = {} + local groups = {} + + for i = 1,#nodenames do + local name = nodenames[i] + if name:sub(1,6) == "group:" then + local group_name = name:sub(7) + groups[#groups + 1] = group_name + else + nodes[name] = true + end + end + + local result = {} + local cid_from_nodename = {} + for cid,nodename in pairs(l4_index.cid_map) do + local nodedef = minetest.registered_nodes[nodename] + if nodes[nodename] then + result[#result + 1] = nodename + cid_from_nodename[nodename] = cid + elseif nodedef then + local node_groups = nodedef.groups + local hit = false + for j = 1,#groups do + if not hit and (node_groups[groups[j]] or 0) ~= 0 then + result[#result + 1] = nodename + cid_from_nodename[nodename] = cid + hit = true + end + end + end + + end + return result,cid_from_nodename +end +function expand_subindex(nodenames, cid_from_nodename, subindex_name, index, size, cost_filter) + if not cid_from_nodename then + cid_from_nodename = {} + for cid,nodename in pairs(index.cid_map) do + cid_from_nodename[nodename] = cid + end + end + + -- Get union of subnode bitmap index where these nodes are located + local union_index = {} + for i = 1,#nodenames do + local nodename = nodenames[i] + local cid = cid_from_nodename[nodename] + if cid then + local node_index = index.subblocks[cid] + for subnode,part in pairs(node_index) do + local union_part = union_index[subnode] or 0 + union_part = bit.bor(union_part, part) + union_index[subnode] = union_part + end + end + end + + -- Build a cost-sorted candidate list from the list of subindexes that have the desired blocks + local candidates = {} + for k,v in pairs(union_index) do + for i=0,31 do + local bit = v % 2 + v = (v - bit) / 2 + if bit == 1 then + local offset = k * 32 + i + local x = offset % 16; offset = (offset - x)/16 + local y = offset % 16; offset = (offset - y)/16 + local z = offset % 16 + local min = vector.multiply(vector.new(x,y,z), size) + local max = vector.offset(min, size-1, size-1, size-1) + local subindex = nil + if subindex_name then + subindex = string.format(subindex_name.."(%d, %d, %d)",x,y,z) + end + local cost = cost_filter(min, max) + if cost then + local idx = #candidates + 1 + candidates[idx] = {cost, subindex, min, max} + + -- Insersion sort according to cost so the lowest cost is at the end + if idx > 1 then + local a = candidates[idx] + local b = candidates[idx - 1] + while idx > 1 and a[1] > b[1] do + -- Swap + candidates[idx ] = b + candidates[idx-1] = a + idx = idx - 1 + b = candidates[idx - 1] + end + end + end + end + end + end + return candidates +end + -- Public-facing API function mod.find_nodes_in_area(pos1, pos2, nodenames, grouped) local size = math.abs(pos1.x - pos2.x) * math.abs(pos1.y - pos2.y) * math.abs(pos1.z - pos2.z) - if size >= MIN_SEARCH_SIZE then - local indexes = mod.get_indexes_for_area(pos1, pos2, "node_counter") - if indexes then - for i=1,#indexes do - -- TODO: find nodes in area - end - end + if size < MIN_SEARCH_SIZE then + return minetest.find_nodes_in_area(pos1, pos2, nodenames, grouped) end - -- Otherwize, fallback onto minetest builtin function - return minetest.find_nodes_in_area(pos1, pos2, nodenames, grouped) + -- Start at the top-level index, then work down + local l4_index = mod.get_index("node_counter.l4") + print("l4_index="..dump(l4_index)) + + -- Expand nodenames to actual list of nodenames + local actual_nodenames = expand_nodenames(nodenames, l4_index) + if #nodenames == 0 then return {},{} end + + -- No results + return {},{} end function mod.find_node_near(pos, radius, nodenames, search_center) local size = radius * radius * radius - if size >= MIN_SEARCH_SIZE then - local indexes = mod.get_indexes_near(pos, radius, "node_counter") - if indexes then - for i=1,#indexes do - -- TODO: find nodes in area - end - end - return nil + if size < MIN_SEARCH_SIZE then + return minetest.find_node_near(pos, radius, nodenames, search_center) end - -- Otherwize, fallback onto minetest builtin function - return minetest.find_node_near(pos, radius, nodenames, search_center) -end -function mod.find_nodes_in_area_under_air(pos1, pos2, nodenames) - local size = math.abs(pos1.x - pos2.x) * math.abs(pos1.y - pos2.y) * math.abs(pos1.z - pos2.z) - if size >= MIN_SEARCH_SIZE then - local indexes = mod.get_indexes_for_area(pos1, pos2, "node_counter") - if indexes then - for i=1,#indexes do - -- TODO: find nodes under air in area - end - end + local function distance_filter(min, max) + local distance = distance_to_block(pos, min, max) + if distance > radius then return nil end + return distance end - -- Otherwize, fallback onto minetest builtin function - return minetest.find_nodes_in_area_under_air(pos1, pos2, nodenames) + -- Start at the top-level index, then work down + local l4_index = mod.get_index("node_counter.l4") + + -- Expand nodenames to actual list of nodenames, return if we can't match any indexed nodes + local nodenames,cid_from_nodename = expand_nodenames(nodenames, l4_index) + if #nodenames == 0 then return nil end + + -- Expand out 3 more levels of subindexes, starting closest to pos + local l3_candidates = expand_subindex(nodenames, cid_from_nodename, "node_counter.l3", l4_index, 16*16*16, distance_filter ) + local l3_idx = #l3_candidates + while l3_idx > 0 do + local l3_candidate = l3_candidates[l3_idx] + local l3_index = mod.get_index(l3_candidate[2]) + local l2_candidates = expand_subindex(nodenames, nil, "node_counter.l2", l3_index, 16*16, distance_filter ) + local l2_idx = #l2_candidates + while l2_idx > 0 do + local l2_candidate = l2_candidates[l2_idx] + local l2_index = mod.get_index(l2_candidate[2]) + local l1_candidates = expand_subindex(nodenames, nil, "node_counter", l2_index, 16, distance_filter ) + local l1_idx = #l1_candidates + while l1_idx > 0 do + local l1_candidate = l1_candidates[l1_idx] + local l1_index = mod.get_index(l1_candidate[2]) + local block_min = l1_candidate[3] + + local cid_from_nodename = {} + for cid,nodename in pairs(l1_index.cid_map) do + cid_from_nodename[nodename] = cid + end + + -- Find closest node + local best_pos = nil + local best_dist = radius + for i = 1,#nodenames do + local cid = cid_from_nodename[nodenames[i]] + local cid_positions = l1_index.positions[cid] or {} + for j = 1,#cid_positions do + local cid_position = cid_positions[j] + local block_pos = get_block_pos_from_hash(block_min,cid_position) + local dist = vector.distance(block_pos, pos) + if dist < best_dist then + best_dist = dist + best_pos = block_pos + end + end + end + + -- Because L3, L2 and L1 candidates are all sorted by closest to pos, if we have + -- position at this point this will always be the closest indexed node inside the + -- provided radius + if best_pos then + return best_pos + end + + -- remove the L1 candidate + l1_candidates[l1_idx] = nil + l1_idx = l1_idx - 1 + end + + -- remove the L2 candidate + l2_candidates[l2_idx] = nil + l2_idx = l2_idx - 1 + end + + -- Remove the L3 candidate + l3_candidates[l3_idx] = nil + l3_idx = l3_idx - 1 + end + + -- No result + return nil end