Add in parallel autocrafter/machine support. 100 Furnaces are now

100x faster for larger crafts.
This commit is contained in:
Mike Stump 2024-01-07 16:33:50 -08:00
parent a030319338
commit 9971678820

@ -69,7 +69,7 @@ function me.reserve(net, pos, original_start, length)
end end
-- Testing: HV solar is realiable, big loans are screwy. -- Testing: HV solar is realiable, big loans are screwy.
-- Building 2 MV batteries manages to miss a single LV battery, odd. -- HV batteries are realiable.
local function build(net, cpos, inv, name, count, stack, sink, time) local function build(net, cpos, inv, name, count, stack, sink, time)
-- The autocrafters nor the machines can take really large amounts -- The autocrafters nor the machines can take really large amounts
-- of things, help them out. -- of things, help them out.
@ -103,33 +103,49 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
local second_output = nil local second_output = nil
local main_action_time = count * pipeworks_craft_time + 1 local main_action_time = count * pipeworks_craft_time + 1
if net.process and net.process[name] then if net.process and net.process[name] then
dat.apos, dat.ipos = next(net.process[name]) local machines = net.process[name]
dat.rinv = minetest.get_meta(dat.apos):get_inventory() for k, v in pairs(machines) do
local i = #dat + 1
dat[i] = {}
dat[i].apos = k
dat[i].ipos = v
dat[i].rinv = minetest.get_meta(dat[i].apos):get_inventory()
-- todo: figure out if we should use total.
if i == count then
break
end
end
me.log("INT: looking up output "..name, "error") me.log("INT: looking up output "..name, "error")
local inputs = me.find_by_output(name) local inputs = me.find_by_output(name)
local machine_name = minetest.get_node(dat.apos).name local machine_name = minetest.get_node(dat[1].apos).name
local typename = me.block_to_typename_map[machine_name] local typename = me.block_to_typename_map[machine_name]
me.log("Looking up "..(typename or "nil").." recipe for a "..(machine_name or nil), "error") me.log("Looking up "..(typename or "nil").." recipe for a "..(machine_name or nil), "error")
dat.recip = me.get_recipe(typename, inputs) dat[1].recip = me.get_recipe(typename, inputs)
me.log("MACHINE: "..machine_name.." typename is "..typename.." and time is "..tostring(dat.recip.time), "error") me.log("MACHINE: "..machine_name.." typename is "..typename.." and time is "..tostring(dat[1].recip.time), "error")
dat.recip.input = inputs dat[1].recip.input = inputs
-- freezer can produce two outputs, we only care about the first. -- freezer can produce two outputs, we only care about the first.
if dat.recip.output[1] then if dat[1].recip.output[1] then
second_output = dat.recip.output[2] second_output = dat[1].recip.output[2]
dat.recip.output = dat.recip.output[1] dat[1].recip.output = dat[1].recip.output[1]
end end
dat.ostack = ItemStack(dat.recip.output) dat[1].ostack = ItemStack(dat[1].recip.output)
-- me.log("SEE: "..machine_name.." "..minetest.serialize(technic.recipes)) -- me.log("SEE: "..machine_name.." "..minetest.serialize(technic.recipes))
local speed = me.speed[machine_name] local speed = me.speed[machine_name]
local craft_count = dat.ostack:get_count() local craft_count = dat[1].ostack:get_count()
local total = math.ceil(count/craft_count) local total = math.ceil(count/craft_count)
-- Remove the extra machines. In theory we could remove the busy machines.
while #dat > total do
table.remove(dat)
end
-- crafting 4 carbon plates misses taking 1 carbon plate on output, make this bigger -- crafting 4 carbon plates misses taking 1 carbon plate on output, make this bigger
-- we'll try 1 for now, figure out right formula. 1 looks perfect. 128 glue is short by 2 -- we'll try 1 for now, figure out right formula. 1 looks perfect. 128 glue is short by 2
-- 1 + 1 is a second too slow on the doped for 81., 2 +0 doesn't work, a second shy -- 1 + 1 is a second too slow on the doped for 81., 2 +0 doesn't work, a second shy
--main_action_time = round((total+2)*dat.recip.time/speed) + 1 --main_action_time = round((total+2)*dat[1].recip.time/speed) + 1
--main_action_time = (total+1)*round(dat.recip.time/speed) -- one shy --main_action_time = (total+1)*round(dat[1].recip.time/speed) -- one shy
--main_action_time = total*dat.recip.time/speed + 2 -- 2 at 80 shy, 3 at 160 shy --main_action_time = total*dat[1].recip.time/speed + 2 -- 2 at 80 shy, 3 at 160 shy
main_action_time = total*1.025*dat.recip.time/speed + 2 -- local subtotal = math.ceil(total/#dat)
--main_action_time = subtotal*1.025*dat[1].recip.time/speed + 2 -- ok
main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1 -- too fast?
if second_output then if second_output then
second_output = ItemStack(second_output) second_output = ItemStack(second_output)
second_output:set_count(second_output:get_count()*total) second_output:set_count(second_output:get_count()*total)
@ -138,35 +154,54 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
elseif net.autocrafters[name] then elseif net.autocrafters[name] then
-- fill all recipe slots, wait, grab all output slots -- fill all recipe slots, wait, grab all output slots
-- "src" "dst" "recipe" "output" -- "src" "dst" "recipe" "output"
dat.apos, dat.ipos = next(net.autocrafters[name]) local machines = net.autocrafters[name]
for k, v in pairs(machines) do
local i = #dat + 1
dat[i] = {}
dat[i].apos = k
dat[i].ipos = v
dat[i].rinv = minetest.get_meta(dat[i].apos):get_inventory()
-- TODO: If we set up pipeworks ac, then we remove interface for it and craft -- TODO: If we set up pipeworks ac, then we remove interface for it and craft
-- it goes to ac, and dies here. Flush net.autocrafters for all the -- it goes to ac, and dies here. Flush net.autocrafters for all the
-- attached inventories during interface removal. -- attached inventories during interface removal.
dat.rinv = minetest.get_meta(dat.apos):get_inventory() if dat[i].rinv == nil then
if dat.rinv == nil then
me.log("no inventory", "error") me.log("no inventory", "error")
return return
end end
dat.ostack = dat.rinv:get_stack("output", 1) dat[i].ostack = dat[i].rinv:get_stack("output", 1)
if dat.ostack:get_name() ~= name then if dat[i].ostack:get_name() ~= name then
-- invalidate it -- invalidate it
net.autocrafters[name][dat.apos] = nil net.autocrafters[name][dat[i].apos] = nil
-- me.log("invalidating autocrafter for "..name, "error") --me.log("invalidating autocrafter for "..name, "error")
table.remove(dat)
if #dat == 0 then
return return
end end
end
-- todo: figure out if we should use total. Test with crafting planks.
if i == total then
break
end
end
-- Consider looking up the recipe and finding the replacements that way. -- Consider looking up the recipe and finding the replacements that way.
if name == "technic:copper_coil" or name == "technic:control_logic_unit" then if name == "technic:copper_coil" or name == "technic:control_logic_unit" then
second_output = ItemStack("basic_materials:empty_spool 999") second_output = ItemStack("basic_materials:empty_spool 999")
end end
local craft_count = dat[1].ostack:get_count()
local total = math.ceil(count/craft_count)
local subtotal = math.ceil(total/#dat)
main_action_time = subtotal * pipeworks_craft_time + 1
else else
me.log("can't craft a "..name, "error") me.log("can't craft a "..name, "error")
return return
end end
dat.isink = function(sstack) for i = 1, #dat do
dat[i].isink = function(sstack)
me.log("TIMER: prep inputs, moving "..sstack:get_count().." "..sstack:get_name(), "error") me.log("TIMER: prep inputs, moving "..sstack:get_count().." "..sstack:get_name(), "error")
return dat.rinv:add_item("src", sstack) return dat[i].rinv:add_item("src", sstack)
end end
local craft_count = dat.ostack:get_count() end
local craft_count = dat[1].ostack:get_count()
-- These will be returned to the me system -- These will be returned to the me system
local extra = ItemStack(name) local extra = ItemStack(name)
local total = math.ceil(count/craft_count) local total = math.ceil(count/craft_count)
@ -178,23 +213,26 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
local consume = {} local consume = {}
if net.process and net.process[name] then if net.process and net.process[name] then
for i = 1, #dat.recip.input do for i = 1, #dat[1].recip.input do
local inp = dat.recip.input[i] local inp = dat[1].recip.input[i]
me.log("MID: consuming "..inp:get_name().." count: "..count.." inp:getcount: "..inp:get_count(), "error") me.log("MID: consuming "..inp:get_name().." count: "..count.." inp:getcount: "..inp:get_count(), "error")
consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count() consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count()
end end
else else
for i = 1, 9 do for i = 1, 9 do
local inp = dat.rinv:get_stack("recipe", i) -- TODO: This assumes that all the crafters have the same exact recipe.
local inp = dat[1].rinv:get_stack("recipe", i)
if inp and not inp:is_empty() then if inp and not inp:is_empty() then
consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count() consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count()
end end
end end
end end
local replace = true local replace = true
local next_time = me.reserve(net, dat.apos, time, main_action_time) local next_time = {}
me.log("RESERVE: "..name.." stime "..time.." step "..main_action_time.." reserve "..next_time, "error") for i = 1, #dat do
-- local next_time = time next_time[i] = me.reserve(net, dat[i].apos, time, main_action_time)
end
--me.log("RESERVE: "..name.." stime "..time.." step "..main_action_time.." reserve "..next_time[1], "error")
me.log("PREP: pre count is "..count, "error") me.log("PREP: pre count is "..count, "error")
-- prepwork -- prepwork
me.log("PREP: count is "..count, "error") me.log("PREP: count is "..count, "error")
@ -217,7 +255,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
local grabbed = me.remove_item(net, inv, "main", istack) local grabbed = me.remove_item(net, inv, "main", istack)
if grabbed then if grabbed then
me.log("ac grabbed "..name, "error") me.log("ac grabbed "..name, "error")
net.ac_status = net.ac_status .. "Grabbed "..count.." "..name..".\n" net.ac_status = net.ac_status .. time.." Grabbed "..count.." "..name..".\n"
local slot = inv:get_size("ac")+1 local slot = inv:get_size("ac")+1
inv:set_size("ac", slot) inv:set_size("ac", slot)
inv:set_stack("ac", slot, grabbed) inv:set_stack("ac", slot, grabbed)
@ -226,25 +264,46 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
me.log("PREP: about to move "..name, "error") me.log("PREP: about to move "..name, "error")
local stack = inv:get_stack("ac", slot) local stack = inv:get_stack("ac", slot)
me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error") me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error")
local leftovers = dat.rinv:add_item("src", stack) local leftovers = 0
me.log("PREP: post move into real inventory "..leftovers:get_count().." leftovers", "error") for i = 1, #dat do
me.log("PREP: did move "..name, "error") -- todo: prove the below is correct.
inv:set_stack("ac", slot, leftovers) -- This spreads across evenly when craft_count is > 0 (stainless, carbon steel for example).
local inner_stack = stack:take_item(count/total*math.floor((total+i-1)/#dat))
leftovers = leftovers + dat[i].rinv:add_item("src", inner_stack):get_count()
end
stack:set_count(leftovers)
me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error")
inv:set_stack("ac", slot, stack)
end] = name end] = name
-- and then something moves the size of ac back to before we started -- and then something moves the size of ac back to before we started
end end
else else
-- Try and autocraft it -- Try and autocraft it
me.log("AC: recursive crafting "..count.." "..istack:get_count(), "error") me.log("AC: recursive crafting "..count.." "..istack:get_count(), "error")
net.ac_status = net.ac_status .. "Need to craft "..count.." "..name..".\n" net.ac_status = net.ac_status .. time.." Need to craft "..count.." "..name..".\n"
local built, step_time = build(net, cpos, inv, name, count, istack, dat.isink, time)
if built then
hasit = true hasit = true
next_time = math.max(next_time, time + step_time) local final_step_time = 0
net.ac_status = net.ac_status .. "Craft "..count.." "..name.." in "..step_time.." seconds.\n" for i = 1, #dat do
-- todo: prove the below is correct.
-- Does this spread across evenly when craft_count is > 0 (? for example)?
-- I think this works, but it is slightly wasteful, but in a good way as
-- 10 on 10 machines will each craft 1 on craft_count 2 item yielding 10 extra.
local subcount = math.floor((count+i-1)/#dat)
local inner_istack = istack
inner_istack:set_count(subcount)
local built, step_time = build(net, cpos, inv, name, subcount, inner_istack, dat[i].isink, time)
if built then
next_time[i] = math.max(next_time[i], time + step_time)
else
hasit = false
end
final_step_time = math.max(final_step_time, step_time)
end
if hasit then
net.ac_status = net.ac_status .. time.." Craft "..count.." "..name.." in "..final_step_time.." seconds.\n"
else else
me.log("can't craft "..istack:get_count().." "..istack:get_name(), "error") me.log("can't craft "..istack:get_count().." "..istack:get_name(), "error")
net.ac_status = net.ac_status .. "Can't craft "..count.." "..name..".\n" net.ac_status = net.ac_status .. time.." Can't craft "..count.." "..name..".\n"
end end
end end
replace = replace and hasit replace = replace and hasit
@ -265,6 +324,11 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
-- Existing items are already loaded. -- Existing items are already loaded.
return return
end end
local tmp_next_time = next_time
next_time = 0
for i = 1, #dat do
next_time = math.max(next_time, tmp_next_time[i])
end
local main_action = function() local main_action = function()
me.log("ACTION: prep for "..stack:get_name(), "error") me.log("ACTION: prep for "..stack:get_name(), "error")
prepwork() prepwork()
@ -290,11 +354,12 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
inv:set_size("ac", previous_ac_size) inv:set_size("ac", previous_ac_size)
end end
me.log("ACTION: main for "..stack:get_name(), "error") me.log("ACTION: main for "..stack:get_name(), "error")
local rmeta = minetest.get_meta(dat.apos) for i = 1, #dat do
local rmeta = minetest.get_meta(dat[i].apos)
-- Let's start up the crafter since we loaded it up to run -- Let's start up the crafter since we loaded it up to run
if (net.process and net.process[name]) or rmeta:get_int("enabled") == 1 then if (net.process and net.process[name]) or rmeta:get_int("enabled") == 1 then
local timer = minetest.get_node_timer(dat.apos) local timer = minetest.get_node_timer(dat[i].apos)
if not timer:is_started() then if not timer:is_started() then
me.log("TIMER: starting ac now for "..stack:get_name(), "error") me.log("TIMER: starting ac now for "..stack:get_name(), "error")
timer:start(pipeworks_craft_time) timer:start(pipeworks_craft_time)
@ -303,65 +368,78 @@ local function build(net, cpos, inv, name, count, stack, sink, time)
local action_time_step = main_action_time local action_time_step = main_action_time
local action = function(net) local action = function(net)
me.log("ACTION: post craft for "..stack:get_name(), "error") me.log("ACTION: post craft for "..stack:get_name(), "error")
me.log("TIMER: moving "..stack:get_count().." "..stack:get_name(), "error") local inner_stack = stack
-- todo: prove the below is correct.
-- TODO: currently testing and fixing this with stainless steel crafting.
-- See extra below for how I think it fails.
inner_stack:set_count(craft_count*math.floor((total+i-1)/#dat))
me.log("TIMER: moving "..inner_stack:get_count().." "..stack:get_name(), "error")
-- deal with output and replacements -- deal with output and replacements
local dst_stack = dat.rinv:remove_item("dst", stack) local dst_stack = dat[i].rinv:remove_item("dst", inner_stack)
if dst_stack:get_count() ~= stack:get_count() then local ctime = next_time+action_time_step
if dst_stack:get_count() ~= inner_stack:get_count() then
me.log("wow, missing items that should have been crafted "..stack:get_name(), "error") me.log("wow, missing items that should have been crafted "..stack:get_name(), "error")
-- me.log("saw "..dst_stack:get_count().." items instead of "..stack:get_count().." items", "error") -- me.log("saw "..dst_stack:get_count().." items instead of "..inner_stack:get_count().." items", "error")
net.ac_status = net.ac_status .. "Missing "..(stack:get_count()-dst_stack:get_count()).." "..name..", only made "..dst_stack:get_count()..".\n" net.ac_status = net.ac_status .. ctime.." Missing "..(inner_stack:get_count()-dst_stack:get_count()).." "..name..", only made "..dst_stack:get_count()..".\n"
end end
if not dst_stack:is_empty() then if not dst_stack:is_empty() then
me.log("TIMER: inserting "..dst_stack:get_count().." "..dst_stack:get_name(), "error") me.log("TIMER: inserting "..dst_stack:get_count().." "..dst_stack:get_name(), "error")
local leftovers = sink(dst_stack) local leftovers = sink(dst_stack)
if leftovers and not leftovers:is_empty() then if leftovers and not leftovers:is_empty() then
me.log("autocrafter overflow, backpressuring", "error") me.log("autocrafter overflow, backpressuring", "error")
net.ac_status = net.ac_status .. "Backpressure of "..name..".\n" net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n"
-- If any don't fit, back pressure on the crafter, we don't -- If any don't fit, back pressure on the crafter, we don't
-- mean to do this, and want to chunk the crafting items smaller -- mean to do this, and want to chunk the crafting items smaller
dat.rinv:add_item("dst", leftovers) dat[i].rinv:add_item("dst", leftovers)
end end
end end
if not extra:is_empty() then if not extra:is_empty() and i == #dat then
dst_stack = dat.rinv:remove_item("dst", extra) -- extra is once, not per machine. It will appear in the
-- last machine as extra.
-- todo: extra I think is broken by switch the dst getter from being count based
-- to being total*craft count based. This leaves extra when we need to craft
-- for a recipe that needs less than an even multiple of the craft_count. Test.
dst_stack = dat[i].rinv:remove_item("dst", extra)
if dst_stack:get_count() ~= extra:get_count() then if dst_stack:get_count() ~= extra:get_count() then
me.log("wow, missing items that should have been crafted "..stack:get_name(), "error") me.log("wow, missing items that should have been crafted "..stack:get_name(), "error")
me.log("saw "..dst_stack:get_count().." items instead of "..extra:get_count().." items", "error") me.log("saw "..dst_stack:get_count().." items instead of "..extra:get_count().." items", "error")
net.ac_status = net.ac_status .. ctime.." Missing "..(extra:get_count() - dst_stack:get_count()).." extra "..name..".\n"
end end
if not dst_stack:is_empty() then if not dst_stack:is_empty() then
local leftovers = me.insert_item(dst_stack, net, inv, "main") local leftovers = me.insert_item(dst_stack, net, inv, "main")
net:set_storage_space(true) net:set_storage_space(true)
if leftovers and not leftovers:is_empty() then if leftovers and not leftovers:is_empty() then
me.log("autocrafter overflow, backpressuring", "error") me.log("autocrafter overflow, backpressuring", "error")
net.ac_status = net.ac_status .. "Backpressure of "..name..".\n" net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n"
-- If any don't fit, back pressure on the crafter, we don't -- If any don't fit, back pressure on the crafter, we don't
-- mean to do this, and want to chunk the crafting items smaller -- mean to do this, and want to chunk the crafting items smaller
dat.rinv:add_item("dst", leftovers) dat[i].rinv:add_item("dst", leftovers)
end end
end end
end end
if second_output then if second_output then
local second = dat.rinv:remove_item("dst", second_output) local second = dat[i].rinv:remove_item("dst", second_output)
if second and not second:is_empty() then if second and not second:is_empty() then
local leftovers = me.insert_item(second, net, inv, "main") local leftovers = me.insert_item(second, net, inv, "main")
if leftovers and not leftovers:is_empty() then if leftovers and not leftovers:is_empty() then
me.log("autocrafter overflow, backpressuring", "error") me.log("autocrafter overflow, backpressuring", "error")
net.ac_status = net.ac_status .. "Backpressure of "..name..".\n" net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n"
-- If any don't fit, back pressure on the crafter, we don't -- If any don't fit, back pressure on the crafter, we don't
-- mean to do this, and want to chunk the crafting items smaller -- mean to do this, and want to chunk the crafting items smaller
dat.rinv:add_item("dst", leftovers) dat[i].rinv:add_item("dst", leftovers)
end end
end end
end end
me.log("ACTION: done post craft for "..stack:get_name(), "error") me.log("ACTION: done post craft for "..stack:get_name(), "error")
end end
local net,cpos = me.get_connected_network(dat.ipos) local net,cpos = me.get_connected_network(dat[i].ipos)
me.later(net, cpos, action, next_time + action_time_step) me.later(net, cpos, action, next_time + action_time_step)
end end
end
me.log("ACTION: main done for "..stack:get_name(), "error") me.log("ACTION: main done for "..stack:get_name(), "error")
end end
local net,cpos = me.get_connected_network(dat.ipos) local net,cpos = me.get_connected_network(dat[1].ipos)
-- queue main action for later -- queue main action for later
me.log("LATER: main action for "..stack:get_name().." in "..next_time.." seconds", "error") me.log("LATER: main action for "..stack:get_name().." in "..next_time.." seconds", "error")
me.later(net, cpos, main_action, next_time) me.later(net, cpos, main_action, next_time)
@ -438,20 +516,20 @@ function me.autocraft(autocrafterCache, cpos, net, linv, inv, count)
if not net.pending or not net.ac_status then if not net.pending or not net.ac_status then
net.ac_status = "" net.ac_status = ""
end end
net.ac_status = net.ac_status .. "using pipeworks autocrafter\n" local start_time = me.autocraft_next_start(net) or 0
net.ac_status = net.ac_status .. start_time.." using pipeworks autocrafter\n"
local sink = function(stack) local sink = function(stack)
local leftovers = me.insert_item(stack, net, inv, "main") local leftovers = me.insert_item(stack, net, inv, "main")
net:set_storage_space(true) net:set_storage_space(true)
return leftovers return leftovers
end end
local start_time = me.autocraft_next_start(net)
local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, sink, start_time) local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, sink, start_time)
if built then if built then
me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error") me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error")
net.ac_status = net.ac_status .. "Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds.\n" net.ac_status = net.ac_status .. start_time.." Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds.\n"
else else
me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error") me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error")
net.ac_status = net.ac_status .. "Can't craft "..(count*craft_count).." "..name..".\n" net.ac_status = net.ac_status .. start_time.." Can't craft "..(count*craft_count).." "..name..".\n"
end end
return return
end end