Add recursive factory dependencies and press pool
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user