Aaron Suen 166cfe5f14 Mitigate excess server block sends
The problem was pointed out by Josh
(gitlab.com/krazy-j) in merge request !22.
Apparently MT is not very smart about marking
mapblocks dirty to send to clients based on
calling mt.set_node(), i.e. it will mark them
dirty presumptively even if you set the node to
the same value it had already been.

This behavior can be confirmed by registering
an ABM against a common node like grass and
setting action = minetest.set_node.  This
causes every mapblock containing that node
to be invalidated every interval, causing a big
spike in the packets received each interval
that you can clearly see on the F5 graph.

Rather than just fixing it for the most easily
observed case (fire checks), add utlity functions
to check this for ALL node change situations,
and apply it more or less universally anywhere
that we are not certain that the node is being
changed and we don't need to worry about the
extra overhead cost of the check.

Note that we don't need a
nodecore.set_loud_check call, as set_loud was
only ever being used already in cases where
we were pretty sure we were actually changing
a node.
2022-08-24 20:44:14 -04:00

191 lines
5.5 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ItemStack, ipairs, math, minetest, nodecore, pairs, string, type,
vector
= ItemStack, ipairs, math, minetest, nodecore, pairs, string, type,
vector
local math_floor, math_pow, math_random, string_format
= math.floor, math.pow, math.random, string.format
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
nodecore.fire_max = 8
do
local flamedirs = nodecore.dirs()
local ventitems = {}
minetest.after(0, function()
for k, v in pairs(minetest.registered_items) do
if v.groups.flammable and not v.groups.fire_fuel
and not v.on_ignite or v.groups.flame or v.name == "air" then
ventitems[k] = v.groups.flame or 0
end
end
end)
local stackonly = {}
minetest.after(0, function()
for k, v in pairs(minetest.registered_items) do
if v.groups.is_stack_only then
stackonly[k] = true
end
end
end)
function nodecore.fire_vents(pos)
local found = {}
for _, dp in ipairs(flamedirs) do
local npos = vector.add(pos, dp)
local node = minetest.get_node_or_nil(npos)
if not node then return end
local q
if stackonly[node.name] then
q = ventitems[nodecore.stack_get(npos):get_name()]
else
q = ventitems[node.name]
end
if q then
npos.q = q
found[#found + 1] = npos
end
end
return found
end
end
local function burneject(pos, stack)
if not stack then return end
if type(stack) == "table" then
for _, v in pairs(stack) do
burneject(pos, v)
end
return
end
if type(stack) == "string" then stack = ItemStack(stack) end
if not stack.is_empty then return end
if stack and (not stack:is_empty()) then
local p = nodecore.scan_flood(pos, 2, nodecore.buildable_to)
nodecore.item_eject(p or pos, stack, 1)
end
end
function nodecore.fire_ignite(pos, node)
node = node or minetest.get_node(pos)
nodecore.log("action", string_format("ignite %s at %s", node.name,
minetest.pos_to_string(pos)))
local def = minetest.registered_items[node.name]
if def and def.on_ignite then
local ign = def.on_ignite
if type(ign) == "function" then
ign = ign(pos, node)
if ign == true then return end
end
burneject(pos, ign)
end
if node and node.count and node.count > 1 then
nodecore.item_disperse(pos, node.name, node.count - 1)
end
local fuel = nodecore.node_group("fire_fuel", pos, node) or 0
if fuel < 0 then fuel = 0 end
if fuel > nodecore.fire_max then fuel = nodecore.fire_max end
fuel = math_floor(fuel)
if fuel > 0 then
nodecore.set_node_check(pos, {name = modname .. ":ember" .. fuel})
else
nodecore.set_node_check(pos, {name = modname .. ":fire"})
end
nodecore.sound_play("nc_fire_ignite", {gain = 1, pos = pos})
nodecore.sound_play("nc_fire_flamy", {gain = 3, pos = pos})
nodecore.fallcheck(pos)
return true
end
function nodecore.fire_check_ignite(pos, node, force, ...)
if not force then
node = node or minetest.get_node(pos)
local def = minetest.registered_items[node.name] or {}
local flam = def.groups and def.groups.flammable
if not flam then return end
if math_random(1, flam) ~= 1 then return end
end
local vents = nodecore.fire_vents(pos)
if (not vents) or #vents < 1 then return end
if nodecore.quenched(pos) then return end
return nodecore.fire_ignite(pos, node, ...)
end
local function snuff(cons, coal, pos, node, ember)
ember = ember or nodecore.node_group("ember", pos, node)
if not ember then return end
ember = ember - cons
if ember > 0 then
if coal then
nodecore.set_node_check(pos, {name = modname .. ":coal" .. ember})
nodecore.sound_play("nc_fire_snuff", {gain = 1, pos = pos})
else
nodecore.set_node_check(pos, {name = modname .. ":ember" .. ember})
end
else
nodecore.set_node_check(pos, {name = modname .. ":ash"})
nodecore.sound_play("nc_fire_snuff", {gain = 1, pos = pos})
end
nodecore.fallcheck(pos)
return true
end
function nodecore.fire_snuff(...) return snuff(1, true, ...) end
function nodecore.fire_expend(...) return snuff(1, false, ...) end
function nodecore.fire_check_expend(pos, node)
local ember = nodecore.node_group("ember", pos, node)
if not ember then return end
local r = math_random(1, 16 * math_pow(2, ember))
if r == 1 then return nodecore.fire_expend(pos, node, ember) end
end
local function snuffcheck(pos, node)
if nodecore.quenched(pos) then return true end
local vents = nodecore.fire_vents(pos, node)
if not vents then return end
if #vents < 1 then return true end
return false, vents
end
function nodecore.fire_check_snuff(pos, node)
local res, vents = snuffcheck(pos, node)
res = res and nodecore.fire_snuff(pos)
return res, vents
end
minetest.register_chatcommand("ignite", {
description = "Set fire to all nearby flammables",
privs = {["debug"] = true},
func = function(pname)
local player = minetest.get_player_by_name(pname)
if not player then return end
local pos = player:get_pos()
for _, p in pairs(nodecore.find_nodes_around(pos, "group:flammable", 5)) do
nodecore.fire_check_ignite(p, nil, true)
end
end
})
minetest.register_chatcommand("snuff", {
description = "Extinguish all nearby embers",
privs = {["debug"] = true},
func = function(pname)
local player = minetest.get_player_by_name(pname)
if not player then return end
local pos = player:get_pos()
for _, p in pairs(nodecore.find_nodes_around(pos, "group:ember", 5)) do
snuff(0, true, p)
end
for _, p in pairs(nodecore.find_nodes_around(pos, modname .. ":fire", 5)) do
minetest.remove_node(p)
end
end
})