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 index = {}
for _, stack in ipairs(invlist) do
local stack_name = stack:get_name()
index[stack_name] = (index[stack_name] or 0) + stack:get_count()
for _, stack in pairs(invlist) do
if not stack:is_empty() then
local stack_name = stack:get_name()
index[stack_name] = (index[stack_name] or 0) + stack:get_count()
end
end
return index
end
local function get_cached_craft(pos)
local hash = minetest.hash_node_position(pos)
return hash, autocrafterCache[hash]
local function get_craft(pos, inventory, hash)
local hash = hash or minetest.hash_node_position(pos)
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
-- note, that this function assumes allready being updated to virtual items
-- and doesn't handle recipes with stacksizes > 1
local function on_recipe_change(pos, inventory)
if not inventory then return end
local recipe = inventory:get_list("recipe")
if not recipe then return end
-- if we emptied the grid, there's no point in keeping it running or cached
if inventory:is_empty("recipe") then
minetest.get_node_timer(pos):stop()
autocrafterCache[minetest.hash_node_position(pos)] = nil
return
end
local recipe_changed = false
local hash, craft = get_cached_craft(pos)
local recipe = inventory:get_list("recipe")
if not craft then
recipe_changed = true
else
local hash = minetest.hash_node_position(pos)
local craft = autocrafterCache[hash]
if craft then
-- check if it changed
local cached_recipe = craft.recipe
local cached_recipe = craft.recipe
for i = 1, 9 do
if recipe[i]:get_name() ~= cached_recipe[i]:get_name() then
recipe_changed = true
autocrafterCache[hash] = nil -- invalidate recipe
craft = nil
break
end
end
end
if recipe_changed then
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
local timer = minetest.get_node_timer(pos)
if not timer:is_started() then
timer:start(1)
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
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
local function update_autocrafter(pos)
@ -61,51 +119,6 @@ local function update_autocrafter(pos)
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", {
description = "Autocrafter",
drawtype = "normal",
@ -114,7 +127,9 @@ minetest.register_node("pipeworks:autocrafter", {
tube = {insert_object = function(pos, node, stack, direction)
local meta = minetest.get_meta(pos)
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,
can_insert = function(pos, node, stack, direction)
local meta = minetest.get_meta(pos)
@ -162,6 +177,7 @@ minetest.register_node("pipeworks:autocrafter", {
on_recipe_change(pos, inv)
return 0
else
on_inventory_change(pos, inv)
return stack:get_count()
end
end,
@ -173,6 +189,7 @@ minetest.register_node("pipeworks:autocrafter", {
on_recipe_change(pos, inv)
return 0
else
on_inventory_change(pos, inv)
return stack:get_count()
end
end,
@ -192,15 +209,9 @@ minetest.register_node("pipeworks:autocrafter", {
on_recipe_change(pos, inv)
return 0
else
on_inventory_change(pos, inv)
return stack:get_count()
end
end,
})
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
on_timer = run_autocrafter
})