Implement comparator accurately and remove WIP, remove ABMs for comparators and make updates triggered instead of polled, changes to redstone power transmission

This commit is contained in:
teknomunk 2024-04-14 22:35:32 +00:00
parent e970a5f414
commit 13888236d6
5 changed files with 109 additions and 126 deletions

View file

@ -1,4 +1,6 @@
local S = minetest.get_translator(minetest.get_current_modname()) local S = minetest.get_translator(minetest.get_current_modname())
mcl_comparators = {}
local mod = mcl_comparators
-- Functions that get the input/output rules of the comparator -- Functions that get the input/output rules of the comparator
@ -10,7 +12,6 @@ local function comparator_get_output_rules(node)
return rules return rules
end end
local function comparator_get_input_rules(node) local function comparator_get_input_rules(node)
local rules = { local rules = {
-- we rely on this order in update_self below -- we rely on this order in update_self below
@ -24,117 +25,73 @@ local function comparator_get_input_rules(node)
return rules return rules
end end
local POSSIBLE_COMPARATOR_POSITIONS = {
-- Functions that are called after the delay time vector.new( 1,0, 0),
vector.new(-1,0, 0),
local function comparator_turnon(params) vector.new( 0,0, 1),
local rules = comparator_get_output_rules(params.node) vector.new( 0,0,-1),
mesecon.receptor_on(params.pos, rules) }
end
local function comparator_turnoff(params)
local rules = comparator_get_output_rules(params.node)
mesecon.receptor_off(params.pos, rules)
end
-- Functions that set the correct node type an schedule a turnon/off
local function comparator_activate(pos, node)
local def = minetest.registered_nodes[node.name]
local onstate = def.comparator_onstate
if onstate then
minetest.swap_node(pos, { name = onstate, param2 = node.param2 })
end
minetest.after(0.1, comparator_turnon , {pos = pos, node = node})
end
local function comparator_deactivate(pos, node)
local def = minetest.registered_nodes[node.name]
local offstate = def.comparator_offstate
if offstate then
minetest.swap_node(pos, { name = offstate, param2 = node.param2 })
end
minetest.after(0.1, comparator_turnoff, {pos = pos, node = node})
end
-- weather pos has an inventory that contains at least one item
local function container_inventory_nonempty(pos)
local invnode = minetest.get_node(pos)
local invnodedef = minetest.registered_nodes[invnode.name]
-- Ignore stale nodes
if not invnodedef then return false end
-- Only accept containers. When a container is dug, it's inventory
-- seems to stay. and we don't want to accept the inventory of an air
-- block
if not invnodedef.groups.container then return false end
local inv = minetest.get_inventory({type="node", pos=pos})
if not inv then return false end
for listname, _ in pairs(inv:get_lists()) do
if not inv:is_empty(listname) then return true end
end
return false
end
-- weather pos has an constant signal output for the comparator
local function static_signal_output(pos)
local node = minetest.get_node(pos)
local g = minetest.get_item_group(node.name, "comparator_signal")
return g > 0
end
-- whether the comparator should be on according to its inputs
local function comparator_desired_on(pos, node)
local my_input_rules = comparator_get_input_rules(node);
local back_rule = my_input_rules[1]
local state
if back_rule then
local back_pos = vector.add(pos, back_rule)
state = mesecon.is_power_on(back_pos) or container_inventory_nonempty(back_pos) or static_signal_output(back_pos)
end
-- if back input if off, we don't need to check side inputs
if not state then return false end
-- without power levels, side inputs have no influence on output in compare
-- mode
local mode = minetest.registered_nodes[node.name].comparator_mode
if mode == "comp" then return state end
-- subtract mode, subtract max(side_inputs) from back input
local side_state = false
for ri = 2,3 do
if my_input_rules[ri] then
side_state = mesecon.is_power_on(vector.add(pos, my_input_rules[ri]))
end
if side_state then break end
end
-- state is known to be true
return not side_state
end
-- update comparator state, if needed -- update comparator state, if needed
local function update_self(pos, node) local function update_self(pos, node)
node = node or minetest.get_node(pos) node = node or minetest.get_node(pos)
local old_state = mesecon.is_receptor_on(node.name)
local new_state = comparator_desired_on(pos, node) -- Find the node we are pointing at
if new_state ~= old_state then local input_rules = comparator_get_input_rules(node);
if new_state then local back_rule = input_rules[1]
comparator_activate(pos, node) local back_pos = vector.add(pos, back_rule)
else local back_node = minetest.get_node(back_pos)
comparator_deactivate(pos, node) local back_nodedef = minetest.registered_nodes[back_node.name]
-- Get the comparator mode
local mode = minetest.registered_nodes[node.name].comparator_mode
-- Get a comparator reading from the block at the back of the comparator
local power_level = 0
if back_nodedef and back_nodedef._mcl_comparators_get_reading then
power_level = back_nodedef._mcl_comparators_get_reading(back_pos)
end
-- Get the maximum side power level
local side_power_level = 0
for i=2,3 do
local pl = vl_redstone.get_power_level(vector.add(pos,input_rules[i]))
if pl > side_power_level then
side_power_level = pl
end
end
-- Apply subtraction or comparison
if mode == "sub" then
power_level = power_level - side_power_level
if power_level < 0 then power_level = 0 end
elseif mode == "comp" then
if side_power_level > power_level then
power_level = 0
end
end
vl_redstone.set_power(pos, power_level)
end
function mod.trigger_update(pos)
-- try to find a comparator with the back rule leading to pos
for i = 1,#POSSIBLE_COMPARATOR_POSITIONS do
local candidate = vector.add(pos, POSSIBLE_COMPARATOR_POSITIONS[i])
local node = minetest.get_node(candidate)
if minetest.get_item_group(node.name,"mcl_comparator") ~= 0 then
update_self(candidate, node)
end end
end end
end end
function mod.read_inventory(inv, inv_name)
local stacks = inv:get_list(inv_name)
local count = 0
for i=1,#stacks do
count = count + ( stacks[i]:get_count() / stacks[i]:get_stack_max() )
end
return math.floor(count * 16 / 5)
end
-- compute tile depending on state and mode -- compute tile depending on state and mode
local function get_tiles(state, mode) local function get_tiles(state, mode)
@ -215,6 +172,7 @@ local groups = {
destroy_by_lava_flow = 1, destroy_by_lava_flow = 1,
dig_by_piston = 1, dig_by_piston = 1,
attached_node = 1, attached_node = 1,
mcl_comparator = 1,
} }
local on_rotate local on_rotate
@ -309,7 +267,7 @@ for _, mode in pairs{"comp", "sub"} do
end end
minetest.register_node(nodename, nodedef) minetest.register_node(nodename, nodedef)
mcl_wip.register_wip_item(nodename) --mcl_wip.register_wip_item(nodename)
end end
end end
@ -327,6 +285,7 @@ minetest.register_craft({
} }
}) })
--[[
-- Register active block handlers -- Register active block handlers
minetest.register_abm({ minetest.register_abm({
label = "Comparator signal input check (comparator is off)", label = "Comparator signal input check (comparator is off)",
@ -352,7 +311,7 @@ minetest.register_abm({
chance = 1, chance = 1,
action = update_self, action = update_self,
}) })
]]
-- Add entry aliases for the Help -- Add entry aliases for the Help
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then

View file

@ -1,3 +1,3 @@
name = mcl_comparators name = mcl_comparators
depends = mcl_wip, mesecons, mcl_sounds depends = mesecons, mcl_sounds, vl_redstone
optional_depends = doc, screwdriver optional_depends = doc, screwdriver

View file

@ -4,7 +4,6 @@ vl_redstone = {}
local mod = vl_redstone local mod = vl_redstone
local REDSTONE_POWER_META = modname .. ".power" local REDSTONE_POWER_META = modname .. ".power"
local REDSTONE_POWER_META_LAST_STATE = modname .. ".last-power"
local REDSTONE_POWER_META_SOURCE = REDSTONE_POWER_META.."." local REDSTONE_POWER_META_SOURCE = REDSTONE_POWER_META.."."
local function update_sink(pos) local function update_sink(pos)
@ -33,10 +32,10 @@ local function update_sink(pos)
end end
end end
local last_strength = meta:get_int(REDSTONE_POWER_META_LAST_STATE) local last_strength = meta:get_int(REDSTONE_POWER_META)
if last_strength ~= strength then if last_strength ~= strength then
print("Updating "..node.name.." at "..vector.to_string(pos).."("..tostring(last_strength).."->"..tostring(strength)..")") --print("Updating "..node.name.." at "..vector.to_string(pos).."("..tostring(last_strength).."->"..tostring(strength)..")")
-- Inform the node of changes -- Inform the node of changes
if strength > 0 then if strength > 0 then
-- Handle activation -- Handle activation
@ -54,7 +53,7 @@ local function update_sink(pos)
-- TODO: handle signal level change notification -- TODO: handle signal level change notification
-- Update the state as the last thing in case there is a crash in the above code -- Update the state as the last thing in case there is a crash in the above code
meta:set_int(REDSTONE_POWER_META_LAST_STATE, strength) meta:set_int(REDSTONE_POWER_META, strength)
end end
end end
@ -90,34 +89,35 @@ local function get_positions_from_node_rules(pos, rules_type, list, powered)
rules = POWERED_BLOCK_RULES rules = POWERED_BLOCK_RULES
end end
print("rules="..dump(rules)) --print("rules="..dump(rules))
-- Convert to absolute positions -- Convert to absolute positions
for i=1,#rules do for i=1,#rules do
local next_pos = vector.add(pos, rules[i]) local next_pos = vector.add(pos, rules[i])
local next_pos_str = vector.to_string(next_pos) local next_pos_str = vector.to_string(next_pos)
print("\tnext: "..next_pos_str..", prev="..tostring(list[next_pos_str])) --print("\tnext: "..next_pos_str..", prev="..tostring(list[next_pos_str]))
list[next_pos_str] = true list[next_pos_str] = true
-- Power solid blocks -- Power solid blocks
if rules[i].spread then if rules[i].spread then
powered[next_pos_str] = true powered[next_pos_str] = true
print("powering "..next_pos_str) --print("powering "..next_pos_str)
end end
end end
return list return list
end end
vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_pos, strength, distance) vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_pos, source_strength, distance)
print("Flowing lv"..tostring(strength).." power from "..vector.to_string(source_pos).." for "..tostring(distance).." blocks") print("Flowing lv"..tostring(source_strength).." power from "..vector.to_string(source_pos).." for "..tostring(distance).." blocks")
local processed = {} local processed = {}
local powered = {} local powered = {}
local source_pos_str = vector.to_string(source_pos) local source_pos_str = vector.to_string(source_pos)
processed[source_pos_str] = true
-- Update the source node's redstone power -- Update the source node's redstone power
local meta = minetest.get_meta(source_pos) local meta = minetest.get_meta(source_pos)
meta:set_int(REDSTONE_POWER_META, strength) meta:set_int(REDSTONE_POWER_META, source_strength)
-- Get rules -- Get rules
local list = {} local list = {}
@ -126,11 +126,11 @@ vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_po
for i=1,distance do for i=1,distance do
local next_list = {} local next_list = {}
local strength = strength - (i - 1) local strength = source_strength - (i - 1)
if strength < 0 then strength = 0 end if strength < 0 then strength = 0 end
for pos_str,dir in pairs(list) do for pos_str,dir in pairs(list) do
print("Processing "..pos_str) --print("Processing "..pos_str)
if not processed[pos_str] then if not processed[pos_str] then
processed[pos_str] = true processed[pos_str] = true
@ -140,7 +140,7 @@ vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_po
-- Update node power directly -- Update node power directly
meta:set_int(REDSTONE_POWER_META.."."..source_pos_str, strength) meta:set_int(REDSTONE_POWER_META.."."..source_pos_str, strength)
print("pos="..vector.to_string(pos)..", strength="..tostring(strength)) --print("pos="..vector.to_string(pos)..", strength="..tostring(strength))
-- handle spread -- handle spread
get_positions_from_node_rules(pos, "conductor", next_list, powered) get_positions_from_node_rules(pos, "conductor", next_list, powered)
@ -158,10 +158,23 @@ end)
function vl_redstone.set_power(pos, strength) function vl_redstone.set_power(pos, strength)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local distance = meta:get_int(REDSTONE_POWER_META) local distance = meta:get_int(REDSTONE_POWER_META)
-- Don't perform an update if the power level is the same as before
if distance == strength then return end
print("previous="..tostring(distance)..", new="..tostring(strength))
-- Make the update distance the maximum of the new strength and the old strength
if distance < strength then if distance < strength then
distance = strength distance = strength
end end
vl_scheduler.add_task(0, "vl_redstone:flow_power", 2, {pos, strength, distance}) -- Schedule an update
vl_scheduler.add_task(0, "vl_redstone:flow_power", 2, {pos, strength, distance + 1})
end
function vl_redstone.get_power_level(pos)
local meta = minetest.get_meta(pos)
return meta:get_int(REDSTONE_POWER_META)
end end

View file

@ -283,14 +283,17 @@ local def_hopper = {
end end
end, end,
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
mcl_comparators.trigger_update(pos)
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" moves stuff in mcl_hoppers at " .. minetest.pos_to_string(pos)) " moves stuff in mcl_hoppers at " .. minetest.pos_to_string(pos))
end, end,
on_metadata_inventory_put = function(pos, listname, index, stack, player) on_metadata_inventory_put = function(pos, listname, index, stack, player)
mcl_comparators.trigger_update(pos)
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" moves stuff to mcl_hoppers at " .. minetest.pos_to_string(pos)) " moves stuff to mcl_hoppers at " .. minetest.pos_to_string(pos))
end, end,
on_metadata_inventory_take = function(pos, listname, index, stack, player) on_metadata_inventory_take = function(pos, listname, index, stack, player)
mcl_comparators.trigger_update(pos)
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos)) " takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos))
end, end,
@ -334,6 +337,11 @@ local def_hopper = {
return true return true
end, end,
_mcl_comparators_get_reading = function (pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return mcl_comparators.read_inventory(inv, "main")
end,
sounds = mcl_sounds.node_sound_metal_defaults(), sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_blast_resistance = 4.8, _mcl_blast_resistance = 4.8,
@ -525,16 +533,19 @@ local def_hopper_side = {
end end
end, end,
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
mcl_comparators.trigger_update(pos)
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" moves stuff in mcl_hoppers at " .. minetest.pos_to_string(pos)) " moves stuff in mcl_hoppers at (1) " .. minetest.pos_to_string(pos))
end, end,
on_metadata_inventory_put = function(pos, listname, index, stack, player) on_metadata_inventory_put = function(pos, listname, index, stack, player)
mcl_comparators.trigger_update(pos)
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" moves stuff to mcl_hoppers at " .. minetest.pos_to_string(pos)) " moves stuff to mcl_hoppers at (2) " .. minetest.pos_to_string(pos))
end, end,
on_metadata_inventory_take = function(pos, listname, index, stack, player) on_metadata_inventory_take = function(pos, listname, index, stack, player)
mcl_comparators.trigger_update(pos)
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos)) " takes stuff from mcl_hoppers at (3) " .. minetest.pos_to_string(pos))
end, end,
on_rotate = on_rotate, on_rotate = on_rotate,
sounds = mcl_sounds.node_sound_metal_defaults(), sounds = mcl_sounds.node_sound_metal_defaults(),

View file

@ -1,4 +1,4 @@
name = mcl_hoppers name = mcl_hoppers
description = It's just a clone of Minecraft hoppers, functions nearly identical to them minus mesecons making them stop and the way they're placed. description = It's just a clone of Minecraft hoppers, functions nearly identical to them minus mesecons making them stop and the way they're placed.
depends = mcl_core, mcl_formspec, mcl_sounds, mcl_util, mcl_dye depends = mcl_core, mcl_formspec, mcl_sounds, mcl_util, mcl_dye, mcl_comparators
optional_depends = doc, screwdriver optional_depends = doc, screwdriver