Aaron Suen ffb25a9162 More exact DNT names
Keep actual name of DNTs distinct and self-describing instead of
relying on modname, which sometimes changes when things are
refactored.
2021-08-05 07:35:20 -04:00

292 lines
7.7 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local math, minetest, nodecore, pairs, string, type, vector
= math, minetest, nodecore, pairs, string, type, vector
local math_floor, math_random, string_format
= math.floor, math.random, string.format
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
local optic_distance = 16
local optic_speed = 12
local optic_tick_limit = nodecore.setting_float(modname .. "_tick_limit", 0.2,
"Optic tick limit", [[Maxiumum amount of time in seconds that may be
spent during a single server step to calculate optic state. Optics
will be allowed to slow don to stay within this limit.]])
local optic_interval = nodecore.setting_float(modname .. "_interval", 5,
"Optic check interval", [[ABM interval for periodically pushing
optics into the "passive" queue.
Passive checks are used to catch optics in an inconsistent state, e.g.
that missed their change event.]])
local optic_passive_max = nodecore.setting_float(modname .. "_passive_max", 25,
"Optic check passive max", [[The maximum number of optics that can be
queued in a single pass for "passive" checks to run; pending passive
checks will be included to fill up remaining spaces up to this total.
Passive checks are used to catch optics in an inconsistent state, e.g.
that missed their change event.]])
local optic_passive_min = nodecore.setting_float(modname .. "_passive_min", 5,
"Optic check passive min", [[The minimum number of "passive" optic
checks that are run each cycle, overriding the max if needed.
Passive checks are used to catch optics in an inconsistent state, e.g.
that missed their change event.]])
local microtime = minetest.get_us_time
local hashpos = minetest.hash_node_position
local unhash = minetest.get_position_from_hash
local get_node = minetest.get_node
local node_optic_checks = {}
local node_optic_sources = {}
local node_opaque = {}
local node_visinv = {}
minetest.after(0, function()
for k, v in pairs(minetest.registered_nodes) do
node_optic_checks[k] = v.optic_check or nil
node_optic_sources[k] = v.optic_source or nil
node_opaque[k] = (not v.sunlight_propagates) or nil
node_visinv[k] = v.groups and v.groups.visinv or nil
end
end)
local optic_queue = {}
local passive_queue = {}
local dependency_index = {}
local dependency_reverse = {}
local function mapblock(pos)
return {
x = math_floor((pos.x + 0.5) / 16),
y = math_floor((pos.y + 0.5) / 16),
z = math_floor((pos.z + 0.5) / 16),
}
end
local function scan(pos, dir, max, getnode, cbbs)
local p = pos
if (not max) or (max > optic_distance) then max = optic_distance end
for _ = 1, max do
local o = p
p = vector.add(p, dir)
if cbbs and not vector.equals(mapblock(o), mapblock(p)) then
cbbs[#cbbs + 1] = {
pos = vector.add(o, vector.multiply(dir, 0.5)),
dir = dir,
plane = {
x = dir.x == 0 and 1 or 0,
y = dir.y == 0 and 1 or 0,
z = dir.z == 0 and 1 or 0,
}
}
end
local node = getnode(p)
if (not node) or node.name == "ignore" then return end
if node_opaque[node.name] then return p, node end
if node_visinv[node.name] then
local stack = nodecore.stack_get(p)
if node_opaque[stack:get_name()] then
return p, node
end
end
end
end
nodecore.optic_scan = scan
local function scan_recv(pos, dir, max, getnode)
local hit, node = scan(pos, dir, max, getnode)
if not node then return end
local src = node_optic_sources[node.name]
src = src and src(hit, node)
if not src then return end
local rev = vector.multiply(dir, -1)
for _, v in pairs(src) do
if vector.equals(v, rev) then
return hit, node
end
end
end
local function optic_check(pos)
optic_queue[hashpos(pos)] = pos
end
nodecore.optic_check = optic_check
local function optic_trigger(start, dir, max)
local pos, node = scan(start, dir, max, get_node)
if node and node_optic_checks[node.name] then
return optic_check(pos)
end
end
local function optic_process(trans, pos)
local node = get_node(pos)
if node.name == "ignore" then return end
local check = node_optic_checks[node.name]
if check then
local ignored
local deps = {}
local getnode = function(p)
local gn = get_node(p)
deps[hashpos(p)] = true
ignored = ignored or gn.name == "ignore"
return gn
end
local recv = function(dir, max)
return scan_recv(pos, dir, max, getnode)
end
local nn = check(pos, node, recv, getnode)
if (not ignored) and nn then
trans[hashpos(pos)] = {
pos = pos,
nn = nn,
deps = deps
}
end
end
end
local function optic_commit(v)
local node = get_node(v.pos)
local oldidx = {}
local oldsrc = node_optic_sources[node.name]
oldsrc = oldsrc and oldsrc(v.pos, node)
if oldsrc then
for _, dir in pairs(oldsrc) do
oldidx[hashpos(dir)] = dir
end
end
local nn = v.nn
if type(nn) == "string" then nn = {name = nn} end
nn.param = nn.param or node.param
nn.param2 = nn.param2 or node.param2
local vhash = hashpos(v.pos)
if node.name ~= nn.name or node.param ~= nn.param or nn.param2 ~= nn.param2 then
minetest.set_node(v.pos, nn)
local src = node_optic_sources[nn.name]
src = src and src(v.pos, nn)
local newidx = {}
if src then
for _, dir in pairs(src) do
local hash = hashpos(dir)
if not oldidx[hash] then
optic_trigger(v.pos, dir)
end
newidx[hash] = dir
end
end
for hash, dir in pairs(oldidx) do
if not newidx[hash] then optic_trigger(v.pos, dir) end
end
end
local olddep = dependency_reverse[vhash]
if olddep then
for k in pairs(olddep) do
local t = dependency_index[k]
if t then t[vhash] = nil end
end
end
for k in pairs(v.deps) do
local t = dependency_index[k]
if not t then
t = {}
dependency_index[k] = t
end
t[vhash] = true
end
end
minetest.register_abm({
label = "optic check",
interval = optic_interval,
chance = 1,
nodenames = {"group:optic_check"},
action = function(pos)
passive_queue[#passive_queue + 1] = pos
end
})
nodecore.register_lbm({
name = modname .. ":optic_check",
run_at_every_load = true,
nodenames = {"group:optic_check"},
action = optic_check
})
local optic_check_pump
do
local passive_batch = {}
optic_check_pump = function()
local batch = optic_queue
optic_queue = {}
if nodecore.stasis then
passive_queue = {}
return
end
if #passive_queue > 0 then
passive_batch = passive_queue
passive_queue = {}
for i = 1, #passive_batch do
local j = math_random(1, #passive_batch)
local t = passive_batch[i]
passive_batch[i] = passive_batch[j]
passive_batch[j] = t
end
end
local max = optic_passive_max - #batch
if max < optic_passive_min then max = optic_passive_min end
if max > #passive_batch then max = #passive_batch end
for _ = 1, max do
local pos = passive_batch[#passive_batch]
passive_batch[#passive_batch] = nil
batch[hashpos(pos)] = pos
end
local trans = {}
for _, pos in pairs(batch) do
optic_process(trans, pos)
end
for _, v in pairs(trans) do
optic_commit(v)
end
end
end
do
local tick = 1 / optic_speed
local total = 0
nodecore.register_globalstep("optic tick", function(dtime)
total = total + dtime / tick
local starttime = microtime()
local exp = starttime + optic_tick_limit * 1000000
local starttotal = total
while total > 1 do
optic_check_pump()
if microtime() >= exp then
nodecore.log("warning", string_format("optics stopped"
.. " after running %d cycles in %0.3fs"
.. ", behind %0.2f",
starttotal - total,
(microtime() - starttime) / 1000000,
total))
total = 0
else
total = total - 1
end
end
end)
end
nodecore.register_on_nodeupdate(function(pos)
local t = dependency_index[hashpos(pos)]
if t then
for k in pairs(t) do
optic_check(unhash(k))
end
end
end)