Add recursive factory dependencies and press pool

This commit is contained in:
2026-07-18 21:03:43 +02:00
parent c963557649
commit eee51ee05a
2 changed files with 145 additions and 13 deletions
+7
View File
@@ -21,6 +21,13 @@ erste Keep-Stock-Regler kleine Cogwheels, grosse Cogwheels und Iron Nuggets auf
jeweils 64 Stueck. Mixer-, Casing- und Precision-Rezepte sind ueber blätterbare jeweils 64 Stueck. Mixer-, Casing- und Precision-Rezepte sind ueber blätterbare
Rezeptseiten erreichbar. Rezeptseiten erreichbar.
Seit V1.3 erzeugt der Scheduler fehlende Zwischenprodukte als persistente
Vorauftraege. Beispielsweise wartet ein Andesite-Casing-Auftrag automatisch auf
fehlendes Andesite Alloy. `presse:1` bis `presse:6` bilden einen gemeinsamen Pool
fuer Gold-, Iron-, Copper-, Brass-, Zinc- und Steel-Plates. Precision Mechanisms
verwenden in ATM10 `alltheores:gold_plate` und warten vor dem Start auf ihre
Cogwheel- und Iron-Nugget-Puffer.
## Create-Fabrik: Peripherie erkennen ## Create-Fabrik: Peripherie erkennen
Das rein lesende Diagnoseprogramm herunterladen und starten: Das rein lesende Diagnoseprogramm herunterladen und starten:
+138 -13
View File
@@ -1,7 +1,7 @@
-- ATM10 Create factory scheduler V1 -- ATM10 Create factory scheduler V1
-- Persistent jobs, dependency planning, progress-aware retries and diagnostics. -- Persistent jobs, dependency planning, progress-aware retries and diagnostics.
local VERSION = "1.2.0" local VERSION = "1.3.0"
local REFRESH_SECONDS = 1 local REFRESH_SECONDS = 1
local CONFIRM_SECONDS = 6 local CONFIRM_SECONDS = 6
local DRAIN_SECONDS = 60 local DRAIN_SECONDS = 60
@@ -98,9 +98,44 @@ local RECIPES = {
}, },
precision_mechanism = { precision_mechanism = {
label = "16 GOLD SHEETS -> PRECISION", station = "precisionmechanism", address = "precisionmechanism", label = "16 GOLD SHEETS -> PRECISION", station = "precisionmechanism", address = "precisionmechanism",
input = "create:golden_sheet", inputCount = 16, input = "alltheores:gold_plate", inputCount = 16,
requires = {
{ item = "create:cogwheel", count = 80 },
{ item = "create:large_cogwheel", count = 80 },
{ item = "minecraft:iron_nugget", count = 80 },
},
outputs = { "create:precision_mechanism" }, completion = "activity", outputs = { "create:precision_mechanism" }, completion = "activity",
}, },
press_gold = {
label = "64 GOLD PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "minecraft:gold_ingot", inputCount = 64,
output = "alltheores:gold_plate", outputCount = 64, targetCount = 64,
},
press_iron = {
label = "64 IRON PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "minecraft:iron_ingot", inputCount = 64,
output = "alltheores:iron_plate", outputCount = 64, targetCount = 64,
},
press_copper = {
label = "64 COPPER PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "minecraft:copper_ingot", inputCount = 64,
output = "alltheores:copper_plate", outputCount = 64, targetCount = 64,
},
press_brass = {
label = "64 BRASS PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "create:brass_ingot", inputCount = 64,
output = "alltheores:brass_plate", outputCount = 64, targetCount = 64,
},
press_zinc = {
label = "64 ZINC PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "alltheores:zinc_ingot", inputCount = 64,
output = "alltheores:zinc_plate", outputCount = 64, targetCount = 64,
},
press_steel = {
label = "64 STEEL PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "alltheores:steel_ingot", inputCount = 64,
output = "alltheores:steel_plate", outputCount = 64, targetCount = 64,
},
zinc_nuggets = { zinc_nuggets = {
label = "576 ZINC NUGGETS", planner = "zinc_nuggets", label = "576 ZINC NUGGETS", planner = "zinc_nuggets",
output = "alltheores:zinc_nugget", targetCount = 576, output = "alltheores:zinc_nugget", targetCount = 576,
@@ -116,6 +151,7 @@ local ACTION_ORDER = {
"iron_nuggets", "iron_nuggets",
"mixer_alloy", "mixer_cogwheel", "mixer_large_cogwheel", "mixer_alloy", "mixer_cogwheel", "mixer_large_cogwheel",
"casing_andesite", "casing_copper", "casing_brass", "casing_railway", "precision_mechanism", "casing_andesite", "casing_copper", "casing_brass", "casing_railway", "precision_mechanism",
"press_gold", "press_iron", "press_copper", "press_brass", "press_zinc", "press_steel",
} }
local KEEP_STOCK = { local KEEP_STOCK = {
@@ -135,6 +171,27 @@ local METAL_PLANS = {
}, },
} }
local PRODUCER_BY_ITEM = {
["minecraft:stripped_oak_log"] = "strip_oak",
["minecraft:oak_planks"] = "planks_oak",
["create:shaft"] = "shafts",
["create:andesite_alloy"] = "mixer_alloy",
["create:cogwheel"] = "mixer_cogwheel",
["create:large_cogwheel"] = "mixer_large_cogwheel",
["alltheores:zinc_nugget"] = "zinc_nuggets",
["minecraft:iron_nugget"] = "iron_nuggets",
["create:andesite_casing"] = "casing_andesite",
["create:copper_casing"] = "casing_copper",
["create:brass_casing"] = "casing_brass",
["create:railway_casing"] = "casing_railway",
["alltheores:gold_plate"] = "press_gold",
["alltheores:iron_plate"] = "press_iron",
["alltheores:copper_plate"] = "press_copper",
["alltheores:brass_plate"] = "press_brass",
["alltheores:zinc_plate"] = "press_zinc",
["alltheores:steel_plate"] = "press_steel",
}
local function epoch() local function epoch()
if os.epoch then return math.floor(os.epoch("utc") / 1000) end if os.epoch then return math.floor(os.epoch("utc") / 1000) end
return math.floor(os.clock()) return math.floor(os.clock())
@@ -303,6 +360,12 @@ local function findJobByRecipe(recipeId)
end end
end end
local function findJobById(jobId)
for _, job in ipairs(state.jobs) do
if job.id == jobId then return job end
end
end
local function stationBusy(station, exceptJob) local function stationBusy(station, exceptJob)
for _, job in ipairs(state.jobs) do for _, job in ipairs(state.jobs) do
if job ~= exceptJob and not terminal(job) and job.activeStation == station then return true end if job ~= exceptJob and not terminal(job) and job.activeStation == station then return true end
@@ -310,6 +373,17 @@ local function stationBusy(station, exceptJob)
return false return false
end end
local function chooseStation(recipe, job)
if recipe.stations then
for _, station in ipairs(recipe.stations) do
if not stationBusy(station, job) then return station end
end
return nil
end
if stationBusy(recipe.station, job) then return nil end
return recipe.station
end
local function produced(job, item) local function produced(job, item)
-- Count real positive arrivals. Unlike a stock difference this never drops -- Count real positive arrivals. Unlike a stock difference this never drops
-- when another factory step consumes the output while the job is running. -- when another factory step consumes the output while the job is running.
@@ -372,9 +446,47 @@ local function observeItems(job, items)
return changed return changed
end end
local newJob
local function waitForDependency(job, item, required)
local producerId = PRODUCER_BY_ITEM[item]
if not producerId or producerId == job.recipeId then
failJob(job, "Zu wenig Input: " .. prettyItemName(item)
.. " (" .. formatNumber(stock[item] or 0) .. "/" .. formatNumber(required) .. ")")
return false
end
if job.dependencyJobId then
local previous = findJobById(job.dependencyJobId)
if previous and (previous.status == "FAILED" or previous.status == "CANCELLED") then
failJob(job, "Vorauftrag #" .. previous.id .. " fuer " .. prettyItemName(item)
.. " ist " .. previous.status)
return false
elseif previous and not terminal(previous) then
job.status, job.activeStation, job.waitingFor = "WAITING_INPUT", nil, item
return false
end
job.dependencyJobId = nil
end
local missing = math.max(1, required - (stock[item] or 0))
local child = findJobByRecipe(producerId)
if not child then child = newJob(producerId, missing, "DEPENDENCY", job.id) end
job.status, job.activeStation = "WAITING_INPUT", nil
job.waitingFor, job.dependencyJobId = item, child and child.id or nil
if job.lastDependencyLog ~= item then
job.lastDependencyLog = item
logEvent("#" .. job.id .. " wartet auf " .. prettyItemName(item)
.. " (Vorauftrag #" .. tostring(child and child.id or "?") .. ")", colors.cyan)
end
saveState()
return false
end
local function dispatchNormal(job, missingOutput) local function dispatchNormal(job, missingOutput)
local recipe = RECIPES[job.recipeId] local recipe = RECIPES[job.recipeId]
if stationBusy(recipe.station, job) then job.status = "WAITING" return false end local selectedStation = chooseStation(recipe, job)
if not selectedStation then job.status = "WAITING" return false end
local inputPlan = {} local inputPlan = {}
if recipe.inputs then if recipe.inputs then
@@ -392,20 +504,25 @@ local function dispatchNormal(job, missingOutput)
inputPlan[1] = { item = recipe.input, count = desiredInput } inputPlan[1] = { item = recipe.input, count = desiredInput }
end end
-- Requirements are kept in the logistics network and are not sent with the
-- main package. Precision Mechanisms use this for their deployer buffers.
for _, requirement in ipairs(recipe.requires or {}) do
if (stock[requirement.item] or 0) < requirement.count then
return waitForDependency(job, requirement.item, requirement.count)
end
end
-- Check the complete recipe before sending its first package. This prevents -- Check the complete recipe before sending its first package. This prevents
-- a mixer basin from receiving only one half of a planned batch. -- a mixer basin from receiving only one half of a planned batch.
for _, ingredient in ipairs(inputPlan) do for _, ingredient in ipairs(inputPlan) do
if ingredient.count <= 0 or (stock[ingredient.item] or 0) < ingredient.count then if ingredient.count <= 0 or (stock[ingredient.item] or 0) < ingredient.count then
failJob(job, "Zu wenig Input: " .. prettyItemName(ingredient.item) return waitForDependency(job, ingredient.item, ingredient.count)
.. " (" .. formatNumber(stock[ingredient.item] or 0)
.. "/" .. formatNumber(ingredient.count) .. ")")
return false
end end
end end
local sentInputs = {} local sentInputs = {}
for _, ingredient in ipairs(inputPlan) do for _, ingredient in ipairs(inputPlan) do
local sent, err = request(recipe.address, ingredient.item, ingredient.count) local sent, err = request(recipe.address or selectedStation, ingredient.item, ingredient.count)
if sent ~= ingredient.count then if sent ~= ingredient.count then
failJob(job, "Teilversand " .. prettyItemName(ingredient.item) failJob(job, "Teilversand " .. prettyItemName(ingredient.item)
.. ": " .. formatNumber(sent) .. "/" .. formatNumber(ingredient.count) .. ": " .. formatNumber(sent) .. "/" .. formatNumber(ingredient.count)
@@ -418,7 +535,8 @@ local function dispatchNormal(job, missingOutput)
job.lastBatchInputs = sentInputs job.lastBatchInputs = sentInputs
job.lastBatchInput = nil job.lastBatchInput = nil
job.activeStation, job.status = recipe.station, "RUNNING" job.activeStation, job.status = selectedStation, "RUNNING"
job.waitingFor, job.dependencyJobId, job.lastDependencyLog = nil, nil, nil
job.phaseStartedAt, job.lastProgressAt, job.warned = epoch(), epoch(), false job.phaseStartedAt, job.lastProgressAt, job.warned = epoch(), epoch(), false
local inputTotal = 0 local inputTotal = 0
for _, count in pairs(sentInputs) do inputTotal = inputTotal + count end for _, count in pairs(sentInputs) do inputTotal = inputTotal + count end
@@ -440,6 +558,10 @@ local function updateNormal(job)
local watched = recipe.outputs or { recipe.output } local watched = recipe.outputs or { recipe.output }
observeItems(job, watched) observeItems(job, watched)
if recipe.completion == "activity" then if recipe.completion == "activity" then
if job.status == "WAITING" or job.status == "WAITING_INPUT" then
dispatchNormal(job, 1)
return
end
local total = 0 local total = 0
for _, item in ipairs(watched) do total = total + produced(job, item) end for _, item in ipairs(watched) do total = total + produced(job, item) end
job.progress = total job.progress = total
@@ -456,7 +578,7 @@ local function updateNormal(job)
if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end
return return
end end
if job.status == "WAITING" then dispatchNormal(job, missing) if job.status == "WAITING" or job.status == "WAITING_INPUT" then dispatchNormal(job, missing)
elseif job.status == "DRAINING" and epoch() - job.lastProgressAt >= DRAIN_SECONDS then elseif job.status == "DRAINING" and epoch() - job.lastProgressAt >= DRAIN_SECONDS then
retryOrFail(job, missing, "Nachlauf beendet, Output unvollstaendig") retryOrFail(job, missing, "Nachlauf beendet, Output unvollstaendig")
elseif job.status == "RUNNING" then elseif job.status == "RUNNING" then
@@ -534,8 +656,6 @@ local function updateMetal(job)
elseif idle >= DRAIN_SECONDS then retryOrFail(job, remaining, plan.name .. "-Auftrag ohne Fortschritt") end elseif idle >= DRAIN_SECONDS then retryOrFail(job, remaining, plan.name .. "-Auftrag ohne Fortschritt") end
end end
local newJob
local function updateJobs() local function updateJobs()
if state.mode == "OFF" then return end if state.mode == "OFF" then return end
for _, job in ipairs(state.jobs) do for _, job in ipairs(state.jobs) do
@@ -548,13 +668,14 @@ local function updateJobs()
end end
end end
newJob = function(recipeId, targetCount, origin) newJob = function(recipeId, targetCount, origin, parentJobId)
local recipe = RECIPES[recipeId] local recipe = RECIPES[recipeId]
local job = { local job = {
id = state.nextJobId, recipeId = recipeId, status = "WAITING", id = state.nextJobId, recipeId = recipeId, status = "WAITING",
phase = recipe.planner and "PLAN" or "NORMAL", phase = recipe.planner and "PLAN" or "NORMAL",
targetCount = targetCount or recipe.targetCount or recipe.outputCount or 1, targetCount = targetCount or recipe.targetCount or recipe.outputCount or 1,
origin = origin or "MANUAL", origin = origin or "MANUAL",
parentJobId = parentJobId,
baseline = {}, lastObserved = {}, arrived = {}, progress = 0, retries = 0, baseline = {}, lastObserved = {}, arrived = {}, progress = 0, retries = 0,
createdAt = epoch(), lastProgressAt = epoch(), phaseStartedAt = epoch(), createdAt = epoch(), lastProgressAt = epoch(), phaseStartedAt = epoch(),
} }
@@ -631,6 +752,10 @@ end
local function progressText(job) local function progressText(job)
local recipe = RECIPES[job.recipeId] local recipe = RECIPES[job.recipeId]
local idle = math.max(0, epoch() - (job.lastProgressAt or epoch())) local idle = math.max(0, epoch() - (job.lastProgressAt or epoch()))
if job.status == "WAITING_INPUT" then
return "WARTE: " .. prettyItemName(job.waitingFor or "Input")
.. (job.dependencyJobId and (" (#" .. job.dependencyJobId .. ")") or "")
end
if recipe and recipe.completion == "activity" then if recipe and recipe.completion == "activity" then
return formatNumber(job.progress or 0) .. " ANGEKOMMEN " .. tostring(job.phase) .. " " .. idle .. "s" return formatNumber(job.progress or 0) .. " ANGEKOMMEN " .. tostring(job.phase) .. " " .. idle .. "s"
end end