use nodetimers instead of abm's to run the autocrafters; only run autocrafters when needed

autocrafters will stop() when theres no valid recipe, no dst space or enough src material
it will resume again on inventory or recipe changes
This commit is contained in:
Tim 2015-01-27 03:45:38 +01:00
parent 2ccce52976
commit 44bafb844a
1 changed files with 86 additions and 75 deletions

View File

@ -2,48 +2,106 @@ local autocrafterCache = {} -- caches some recipe data to avoid to call the slo
local function count_index(invlist) local function count_index(invlist)
local index = {} local index = {}
for _, stack in ipairs(invlist) do for _, stack in pairs(invlist) do
local stack_name = stack:get_name() if not stack:is_empty() then
index[stack_name] = (index[stack_name] or 0) + stack:get_count() local stack_name = stack:get_name()
index[stack_name] = (index[stack_name] or 0) + stack:get_count()
end
end end
return index return index
end end
local function get_cached_craft(pos) local function get_craft(pos, inventory, hash)
local hash = minetest.hash_node_position(pos) local hash = hash or minetest.hash_node_position(pos)
return hash, autocrafterCache[hash] local craft = autocrafterCache[hash]
if not craft then
if inventory:is_empty("recipe") then return nil end
local recipe = inventory:get_list("recipe")
local output, decremented_input = minetest.get_craft_result({method = "normal", width = 3, items = recipe})
craft = {recipe = recipe, consumption=count_index(recipe), output = output, decremented_input = decremented_input}
autocrafterCache[hash] = craft
end
-- only return crafts that have an actual result
if not craft.output.item:is_empty() then
return craft
end
end end
-- note, that this function assumes allready being updated to virtual items -- note, that this function assumes allready being updated to virtual items
-- and doesn't handle recipes with stacksizes > 1 -- and doesn't handle recipes with stacksizes > 1
local function on_recipe_change(pos, inventory) local function on_recipe_change(pos, inventory)
if not inventory then return end -- if we emptied the grid, there's no point in keeping it running or cached
local recipe = inventory:get_list("recipe") if inventory:is_empty("recipe") then
if not recipe then return end minetest.get_node_timer(pos):stop()
autocrafterCache[minetest.hash_node_position(pos)] = nil
return
end
local recipe_changed = false local recipe_changed = false
local hash, craft = get_cached_craft(pos) local recipe = inventory:get_list("recipe")
if not craft then local hash = minetest.hash_node_position(pos)
recipe_changed = true local craft = autocrafterCache[hash]
else
if craft then
-- check if it changed -- check if it changed
local cached_recipe = craft.recipe local cached_recipe = craft.recipe
for i = 1, 9 do for i = 1, 9 do
if recipe[i]:get_name() ~= cached_recipe[i]:get_name() then if recipe[i]:get_name() ~= cached_recipe[i]:get_name() then
recipe_changed = true autocrafterCache[hash] = nil -- invalidate recipe
craft = nil
break break
end end
end end
end end
if recipe_changed then local timer = minetest.get_node_timer(pos)
local output, decremented_input = minetest.get_craft_result({method = "normal", width = 3, items = recipe}) if not timer:is_started() then
craft = {recipe = recipe, consumption=count_index(recipe), output = output, decremented_input = decremented_input} timer:start(1)
autocrafterCache[hash] = craft end
end
local function on_inventory_change(pos, inventory)
local timer = minetest.get_node_timer(pos)
if not timer:is_started() then
timer:start(1)
end
end
local function autocraft(inventory, craft)
if not craft then return false end
local output_item = craft.output.item
-- check if we have enough room in dst
if not inventory:room_for_item("dst", output_item) then return false end
local consumption = craft.consumption
local inv_index = count_index(inventory:get_list("src"))
-- check if we have enough material available
for itemname, number in pairs(consumption) do
if (not inv_index[itemname]) or inv_index[itemname] < number then return false end
end
-- consume material
for itemname, number in pairs(consumption) do
for i = 1, number do -- We have to do that since remove_item does not work if count > stack_max
inventory:remove_item("src", ItemStack(itemname))
end
end end
return craft -- craft the result into the dst inventory and add any "replacements" as well
inventory:add_item("dst", output_item)
for i = 1, 9 do
inventory:add_item("dst", craft.decremented_input.items[i])
end
return true
end
-- returns false to stop the timer, true to continue running
-- is started only from start_autocrafter(pos) after sanity checks and cached recipe
local function run_autocrafter(pos, elapsed)
local meta = minetest.get_meta(pos)
local inventory = meta:get_inventory()
local craft = get_craft(pos, inventory)
return autocraft(inventory, craft)
end end
local function update_autocrafter(pos) local function update_autocrafter(pos)
@ -61,51 +119,6 @@ local function update_autocrafter(pos)
end end
end end
local function autocraft(inventory, craft)
local output_item = craft.output.item
-- check if we have enough room in dst
if not inventory:room_for_item("dst", output_item) then return false end
local consumption = craft.consumption
local inv_index = count_index(inventory:get_list("src"))
-- check if we have enough materials available
for itemname, number in pairs(consumption) do
if (not inv_index[itemname]) or inv_index[itemname] < number then return false end
end
-- consume materials
for itemname, number in pairs(consumption) do
for i = 1, number do -- We have to do that since remove_item does not work if count > stack_max
inventory:remove_item("src", ItemStack(itemname))
end
end
-- craft the result into the dst inventory and add any "replacements" as well
inventory:add_item("dst", output_item)
for i = 1, 9 do
inventory:add_item("dst", craft.decremented_input.items[i])
end
return true
end
local function run_autocrafter(inventory, pos)
if not inventory then return end
local recipe = inventory:get_list("recipe")
if not recipe then return end
local _, craft = get_cached_craft(pos)
if craft == nil then
update_autocrafter(pos) -- only does some unnecessary calls for "old" autocrafters
craft = on_recipe_change(pos, inventory)
end
-- skip crafts that have no output
local output_item = craft.output.item
if output_item:is_empty() then return end
autocraft(inventory, craft)
end
minetest.register_node("pipeworks:autocrafter", { minetest.register_node("pipeworks:autocrafter", {
description = "Autocrafter", description = "Autocrafter",
drawtype = "normal", drawtype = "normal",
@ -114,7 +127,9 @@ minetest.register_node("pipeworks:autocrafter", {
tube = {insert_object = function(pos, node, stack, direction) tube = {insert_object = function(pos, node, stack, direction)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local inv = meta:get_inventory() local inv = meta:get_inventory()
return inv:add_item("src", stack) local added = inv:add_item("src", stack)
on_inventory_change(pos, inv)
return added
end, end,
can_insert = function(pos, node, stack, direction) can_insert = function(pos, node, stack, direction)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
@ -162,6 +177,7 @@ minetest.register_node("pipeworks:autocrafter", {
on_recipe_change(pos, inv) on_recipe_change(pos, inv)
return 0 return 0
else else
on_inventory_change(pos, inv)
return stack:get_count() return stack:get_count()
end end
end, end,
@ -173,6 +189,7 @@ minetest.register_node("pipeworks:autocrafter", {
on_recipe_change(pos, inv) on_recipe_change(pos, inv)
return 0 return 0
else else
on_inventory_change(pos, inv)
return stack:get_count() return stack:get_count()
end end
end, end,
@ -192,15 +209,9 @@ minetest.register_node("pipeworks:autocrafter", {
on_recipe_change(pos, inv) on_recipe_change(pos, inv)
return 0 return 0
else else
on_inventory_change(pos, inv)
return stack:get_count() return stack:get_count()
end end
end, end,
}) on_timer = run_autocrafter
minetest.register_abm({nodenames = {"pipeworks:autocrafter"}, interval = 1, chance = 1,
action = function(pos, node)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
run_autocrafter(inv, pos)
end
}) })