Browse Source

Implement get_node with a get_node_raw (#14384)

Add /bench_bulk_get_node
Considerably improves the execution speed of core.get_node
DS 1 month ago
parent
commit
d4d4712361

+ 19 - 0
builtin/game/item.lua

@@ -736,3 +736,22 @@ core.noneitemdef_default = {  -- This is used for the hand and unknown items
 	on_drop = nil,
 	on_use = nil,
 }
+
+--
+-- get_node implementation
+--
+
+local get_node_raw = core.get_node_raw
+core.get_node_raw = nil
+
+function core.get_node(pos)
+	local content, param1, param2 = get_node_raw(pos.x, pos.y, pos.z)
+	return {name = core.get_name_from_content_id(content), param1 = param1, param2 = param2}
+end
+
+function core.get_node_or_nil(pos)
+	local content, param1, param2, pos_ok = get_node_raw(pos.x, pos.y, pos.z)
+	return pos_ok and
+			{name = core.get_name_from_content_id(content), param1 = param1, param2 = param2}
+			or nil
+end

+ 52 - 13
games/devtest/mods/benchmarks/init.lua

@@ -71,6 +71,22 @@ minetest.register_chatcommand("bench_content2name", {
 	end,
 })
 
+local function get_positions_cube(ppos)
+	local pos_list = {}
+
+	local i = 1
+	for x=2,100 do
+		for y=2,100 do
+			for z=2,100 do
+				pos_list[i] = ppos:offset(x, y, z)
+				i = i + 1
+			end
+		end
+	end
+
+	return pos_list
+end
+
 minetest.register_chatcommand("bench_bulk_set_node", {
 	params = "",
 	description = "Benchmark: Bulk-set 99×99×99 stone nodes",
@@ -79,25 +95,15 @@ minetest.register_chatcommand("bench_bulk_set_node", {
 		if not player then
 			return false, "No player."
 		end
-		local pos_list = {}
-		local ppos = player:get_pos()
-		local i = 1
-		for x=2,100 do
-			for y=2,100 do
-				for z=2,100 do
-					pos_list[i] = {x=ppos.x + x,y = ppos.y + y,z = ppos.z + z}
-					i = i + 1
-				end
-			end
-		end
+		local pos_list = get_positions_cube(player:get_pos())
 
-		minetest.chat_send_player(name, "Benchmarking minetest.bulk_set_node. Warming up ...");
+		minetest.chat_send_player(name, "Benchmarking minetest.bulk_set_node. Warming up ...")
 
 		-- warm up with stone to prevent having different callbacks
 		-- due to different node topology
 		minetest.bulk_set_node(pos_list, {name = "mapgen_stone"})
 
-		minetest.chat_send_player(name, "Warming up finished, now benchmarking ...");
+		minetest.chat_send_player(name, "Warming up finished, now benchmarking ...")
 
 		local start_time = minetest.get_us_time()
 		for i=1,#pos_list do
@@ -114,4 +120,37 @@ minetest.register_chatcommand("bench_bulk_set_node", {
 	end,
 })
 
+minetest.register_chatcommand("bench_bulk_get_node", {
+	params = "",
+	description = "Benchmark: Bulk-get 99×99×99 nodes",
+	func = function(name, param)
+		local player = minetest.get_player_by_name(name)
+		if not player then
+			return false, "No player."
+		end
+		local pos_list = get_positions_cube(player:get_pos())
+		local function bench()
+			local start_time = minetest.get_us_time()
+			for i=1,#pos_list do
+				local n = minetest.get_node(pos_list[i])
+				-- Make sure the name lookup is never optimized away.
+				-- Table allocation might still be omitted. But only accessing
+				-- the name of a node is a common pattern anyways.
+				if n.name == "benchmarks:nonexistent_node" then
+					error("should never happen")
+				end
+			end
+			return minetest.get_us_time() - start_time
+		end
 
+		minetest.chat_send_player(name, "Benchmarking minetest.get_node. Warming up ...")
+		bench()
+
+		minetest.chat_send_player(name, "Warming up finished, now benchmarking ...")
+		local result_us = bench()
+
+		local msg = string.format("Benchmark results: minetest.get_node loop 1: %.2f ms",
+				result_us / 1000)
+		return true, msg
+	end,
+})

+ 14 - 28
src/script/lua_api/l_env.cpp

@@ -328,39 +328,26 @@ int ModApiEnv::l_swap_node(lua_State *L)
 	return 1;
 }
 
-// get_node(pos)
-// pos = {x=num, y=num, z=num}
-int ModApiEnv::l_get_node(lua_State *L)
-{
-	GET_ENV_PTR;
-
-	// pos
-	v3s16 pos = read_v3s16(L, 1);
-	// Do it
-	MapNode n = env->getMap().getNode(pos);
-	// Return node
-	pushnode(L, n);
-	return 1;
-}
-
-// get_node_or_nil(pos)
-// pos = {x=num, y=num, z=num}
-int ModApiEnv::l_get_node_or_nil(lua_State *L)
+// get_node_raw(x, y, z) -> content, param1, param2, pos_ok
+int ModApiEnv::l_get_node_raw(lua_State *L)
 {
 	GET_ENV_PTR;
 
 	// pos
-	v3s16 pos = read_v3s16(L, 1);
+	// mirrors implementation of read_v3s16 (with the exact same rounding)
+	double x = lua_tonumber(L, 1);
+	double y = lua_tonumber(L, 2);
+	double z = lua_tonumber(L, 3);
+	v3s16 pos = doubleToInt(v3d(x, y, z), 1.0);
 	// Do it
 	bool pos_ok;
 	MapNode n = env->getMap().getNode(pos, &pos_ok);
-	if (pos_ok) {
-		// Return node
-		pushnode(L, n);
-	} else {
-		lua_pushnil(L);
-	}
-	return 1;
+	// Return node and pos_ok
+	lua_pushinteger(L, n.getContent());
+	lua_pushinteger(L, n.getParam1());
+	lua_pushinteger(L, n.getParam2());
+	lua_pushboolean(L, pos_ok);
+	return 4;
 }
 
 // get_node_light(pos, timeofday)
@@ -1483,8 +1470,7 @@ void ModApiEnv::Initialize(lua_State *L, int top)
 	API_FCT(swap_node);
 	API_FCT(add_item);
 	API_FCT(remove_node);
-	API_FCT(get_node);
-	API_FCT(get_node_or_nil);
+	API_FCT(get_node_raw);
 	API_FCT(get_node_light);
 	API_FCT(get_natural_light);
 	API_FCT(place_node);

+ 5 - 8
src/script/lua_api/l_env.h

@@ -74,13 +74,10 @@ private:
 	// pos = {x=num, y=num, z=num}
 	static int l_swap_node(lua_State *L);
 
-	// get_node(pos)
-	// pos = {x=num, y=num, z=num}
-	static int l_get_node(lua_State *L);
-
-	// get_node_or_nil(pos)
-	// pos = {x=num, y=num, z=num}
-	static int l_get_node_or_nil(lua_State *L);
+	// get_node_raw(x, y, z) -> content, param1, param2, pos_ok
+	// Used to implement get_node and get_node_or_nil in lua.
+	// This is still faster than doing it from C++ even with optimized pushnode.
+	static int l_get_node_raw(lua_State *L);
 
 	// get_node_light(pos, timeofday)
 	// pos = {x=num, y=num, z=num}
@@ -245,7 +242,7 @@ public:
 
 /*
  * Duplicates of certain env APIs that operate not on the global
- * map but on a VoxelManipulator. This is for emerge scripting. 
+ * map but on a VoxelManipulator. This is for emerge scripting.
  */
 class ModApiEnvVM : public ModApiEnvBase {
 private: