Add protected ME fallback for Create inputs

This commit is contained in:
2026-07-18 22:37:19 +02:00
parent 090fec482a
commit 1480119c8e
2 changed files with 111 additions and 9 deletions
+6
View File
@@ -34,6 +34,12 @@ wird erst freigegeben, nachdem der Vorauftrag seine Ankunft registriert hat. So
kann ein nachfolgender Auftrag den neuen Bestand nicht mehr im selben Tick kann ein nachfolgender Auftrag den neuen Bestand nicht mehr im selben Tick
wegnehmen und dadurch einen falschen Retry oder Fehler ausloesen. wegnehmen und dadurch einen falschen Retry oder Fehler ausloesen.
V1.4 bindet die Advanced-Peripherals-ME-Bridge als geschuetzten Fallback ein.
Vorhandener Create-Bestand und Create-eigene Produktionsketten haben weiterhin
Vorrang. Nur externe, im Create-Netz fehlende Rohstoffe werden in exakt der
fehlenden Menge aus dem ME in `sophisticatedstorage:chest_0` exportiert. Diese
erste Stufe startet bewusst noch kein ME-Autocrafting.
## Create-Fabrik: Peripherie erkennen ## Create-Fabrik: Peripherie erkennen
Das rein lesende Diagnoseprogramm herunterladen und starten: Das rein lesende Diagnoseprogramm herunterladen und starten:
+105 -9
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.3.2" local VERSION = "1.4.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
@@ -12,6 +12,8 @@ local STATE_TEMP_FILE = STATE_FILE .. ".tmp"
local EVENT_LOG_FILE = "factory_scheduler_log.txt" local EVENT_LOG_FILE = "factory_scheduler_log.txt"
local MAX_EVENTS = 5 local MAX_EVENTS = 5
local MAX_ERRORS = 50 local MAX_ERRORS = 50
local ME_BUFFER_NAME = "sophisticatedstorage:chest_0"
local ME_TRANSFER_TIMEOUT = 15
-- Add ordinary modules/recipes here. Multi-step chains use a named planner. -- Add ordinary modules/recipes here. Multi-step chains use a named planner.
local RECIPES = { local RECIPES = {
@@ -200,6 +202,7 @@ end
local tickerName = findPeripheral("Create_StockTicker") local tickerName = findPeripheral("Create_StockTicker")
local monitorName = findPeripheral("monitor") local monitorName = findPeripheral("monitor")
local meBridgeName = findPeripheral("me_bridge")
if not tickerName then error("Kein Create Stock Ticker gefunden", 0) end if not tickerName then error("Kein Create Stock Ticker gefunden", 0) end
if not monitorName then error("Kein Monitor gefunden", 0) end if not monitorName then error("Kein Monitor gefunden", 0) end
local monitor = peripheral.wrap(monitorName) local monitor = peripheral.wrap(monitorName)
@@ -404,7 +407,10 @@ local function request(address, item, count)
return sent return sent
end end
local clearMEWait
local function finishJob(job) local function finishJob(job)
clearMEWait(job)
job.status, job.activeStation, job.finishedAt = "DONE", nil, epoch() job.status, job.activeStation, job.finishedAt = "DONE", nil, epoch()
statusText, statusColor = "FERTIG: " .. RECIPES[job.recipeId].label, colors.lime statusText, statusColor = "FERTIG: " .. RECIPES[job.recipeId].label, colors.lime
logEvent("#" .. job.id .. " fertig: " .. RECIPES[job.recipeId].label, colors.lime) logEvent("#" .. job.id .. " fertig: " .. RECIPES[job.recipeId].label, colors.lime)
@@ -412,6 +418,7 @@ local function finishJob(job)
end end
local function failJob(job, reason) local function failJob(job, reason)
clearMEWait(job)
job.status, job.activeStation, job.finishedAt = "FAILED", nil, epoch() job.status, job.activeStation, job.finishedAt = "FAILED", nil, epoch()
statusText, statusColor = "FEHLER #" .. job.id .. ": " .. reason, colors.red statusText, statusColor = "FEHLER #" .. job.id .. ": " .. reason, colors.red
logEvent(statusText, colors.red) addError(job, "FAILED", reason) saveState() logEvent(statusText, colors.red) addError(job, "FAILED", reason) saveState()
@@ -443,6 +450,78 @@ end
local newJob local newJob
clearMEWait = function(job)
job.meWaitingFor, job.meRequired, job.meRequestedAt, job.meMoved = nil, nil, nil, nil
end
local function waitForME(job, item, required)
-- Create always has priority. Only the difference between the complete
-- requirement and the currently visible Create stock may leave the ME.
local createAvailable = stock[item] or 0
if createAvailable >= required then
clearMEWait(job)
return true
end
if not meBridgeName or not peripheral.isPresent(meBridgeName) then
failJob(job, "Zu wenig Input: " .. prettyItemName(item)
.. " (Create " .. formatNumber(createAvailable) .. "/" .. formatNumber(required)
.. ", ME Bridge fehlt)")
return false
end
if not peripheral.isPresent(ME_BUFFER_NAME) then
failJob(job, "ME-Puffer " .. ME_BUFFER_NAME .. " nicht erreichbar")
return false
end
local okOnline, online = pcall(peripheral.call, meBridgeName, "isConnected")
if not okOnline or not online then
failJob(job, "ME Bridge offline fuer " .. prettyItemName(item))
return false
end
if job.meWaitingFor == item then
job.status, job.activeStation, job.waitingFor = "WAITING_ME", nil, item
if epoch() - (job.meRequestedAt or epoch()) < ME_TRANSFER_TIMEOUT then return false end
clearMEWait(job)
createAvailable = stock[item] or 0
if createAvailable >= required then return true end
elseif job.meWaitingFor then
clearMEWait(job)
end
local missing = math.max(1, required - createAvailable)
local okItem, meItem = pcall(peripheral.call, meBridgeName, "getItem", { name = item })
local meAvailable = 0
if okItem and type(meItem) == "table" then
meAvailable = tonumber(meItem.amount or meItem.count) or 0
end
if meAvailable <= 0 then
failJob(job, "Zu wenig Input: " .. prettyItemName(item)
.. " (Create " .. formatNumber(createAvailable) .. "/" .. formatNumber(required)
.. ", ME 0)")
return false
end
local wanted = math.min(missing, meAvailable)
local okExport, result = pcall(peripheral.call, meBridgeName, "exportItem",
{ name = item, count = wanted }, ME_BUFFER_NAME)
local moved = okExport and tonumber(result) or 0
if moved <= 0 then
failJob(job, "ME-Export fehlgeschlagen: " .. prettyItemName(item)
.. (okExport and " (Puffer voll oder nicht erreichbar)" or (" - " .. tostring(result))))
return false
end
job.status, job.activeStation, job.waitingFor = "WAITING_ME", nil, item
job.meWaitingFor, job.meRequired = item, required
job.meRequestedAt, job.meMoved = epoch(), moved
logEvent("#" .. job.id .. " ME-Fallback: " .. formatNumber(moved) .. " "
.. prettyItemName(item) .. " (Create hatte " .. formatNumber(createAvailable) .. ")", colors.purple)
saveState()
return false
end
local function dependencyReleased(job) local function dependencyReleased(job)
if not job.dependencyJobId then return true end if not job.dependencyJobId then return true end
local dependency = findJobById(job.dependencyJobId) local dependency = findJobById(job.dependencyJobId)
@@ -473,9 +552,7 @@ end
local function waitForDependency(job, item, required) local function waitForDependency(job, item, required)
local producerId = PRODUCER_BY_ITEM[item] local producerId = PRODUCER_BY_ITEM[item]
if not producerId or producerId == job.recipeId then if not producerId or producerId == job.recipeId then
failJob(job, "Zu wenig Input: " .. prettyItemName(item) return waitForME(job, item, required)
.. " (" .. formatNumber(stock[item] or 0) .. "/" .. formatNumber(required) .. ")")
return false
end end
if job.dependencyJobId then if job.dependencyJobId then
@@ -508,6 +585,9 @@ end
local function dispatchNormal(job, missingOutput) local function dispatchNormal(job, missingOutput)
local recipe = RECIPES[job.recipeId] local recipe = RECIPES[job.recipeId]
if not dependencyReleased(job) then return false end if not dependencyReleased(job) then return false end
if job.meWaitingFor then
if not waitForME(job, job.meWaitingFor, job.meRequired or 1) then return false end
end
local selectedStation = chooseStation(recipe, job) local selectedStation = chooseStation(recipe, job)
if not selectedStation then job.status = "WAITING" return false end if not selectedStation then job.status = "WAITING" return false end
@@ -560,6 +640,7 @@ local function dispatchNormal(job, missingOutput)
job.lastBatchInput = nil job.lastBatchInput = nil
job.activeStation, job.status = selectedStation, "RUNNING" job.activeStation, job.status = selectedStation, "RUNNING"
job.waitingFor, job.dependencyJobId, job.lastDependencyLog = nil, nil, nil job.waitingFor, job.dependencyJobId, job.lastDependencyLog = nil, nil, nil
clearMEWait(job)
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
@@ -581,7 +662,7 @@ 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 if job.status == "WAITING" or job.status == "WAITING_INPUT" or job.status == "WAITING_ME" then
dispatchNormal(job, 1) dispatchNormal(job, 1)
return return
end end
@@ -601,7 +682,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" or job.status == "WAITING_INPUT" then dispatchNormal(job, missing) if job.status == "WAITING" or job.status == "WAITING_INPUT" or job.status == "WAITING_ME" 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
@@ -613,8 +694,12 @@ end
local function dispatchMetalCrushing(job, remaining, plan) local function dispatchMetalCrushing(job, remaining, plan)
if stationBusy("crushing", job) then return end if stationBusy("crushing", job) then return end
local wanted = math.min(stock[plan.source] or 0, math.max(1, math.min(64, math.ceil(remaining / 3)))) local required = math.max(1, math.min(64, math.ceil(remaining / 3)))
if wanted <= 0 then failJob(job, "Zu wenig " .. plan.sourceName .. " fuer fehlende " .. plan.name .. " Nuggets") return end local wanted = math.min(stock[plan.source] or 0, required)
if wanted <= 0 then
waitForME(job, plan.source, required)
return
end
local sent, err = request("crushing", plan.source, wanted) local sent, err = request("crushing", plan.source, wanted)
if sent <= 0 then failJob(job, plan.sourceName .. "-Versand fehlgeschlagen: " .. tostring(err or "unbekannt")) return end if sent <= 0 then failJob(job, plan.sourceName .. "-Versand fehlgeschlagen: " .. tostring(err or "unbekannt")) return end
job.activeStation, job.phase, job.status = "crushing", "CRUSHING", "RUNNING" job.activeStation, job.phase, job.status = "crushing", "CRUSHING", "RUNNING"
@@ -646,7 +731,12 @@ local function updateMetal(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 job.activeStation = nil dispatchMetalWashing(job, remaining, plan) return end if job.status == "WAITING" or job.status == "WAITING_ME" then
job.activeStation = nil
if job.meWaitingFor and not waitForME(job, job.meWaitingFor, job.meRequired or 1) then return end
dispatchMetalWashing(job, remaining, plan)
return
end
-- Do not wait for the complete 60-second crushing drain when the material -- Do not wait for the complete 60-second crushing drain when the material
-- which has already reached storage can satisfy the remaining parent job. -- which has already reached storage can satisfy the remaining parent job.
@@ -726,6 +816,7 @@ local function maintainStock()
end end
local function cancelJob(job) local function cancelJob(job)
clearMEWait(job)
job.status, job.activeStation, job.finishedAt = "CANCELLED", nil, epoch() job.status, job.activeStation, job.finishedAt = "CANCELLED", nil, epoch()
logEvent("#" .. job.id .. " manuell abgebrochen", colors.red) logEvent("#" .. job.id .. " manuell abgebrochen", colors.red)
addError(job, "CANCELLED", "Manuell abgebrochen; bereits versendete Pakete laufen aus") addError(job, "CANCELLED", "Manuell abgebrochen; bereits versendete Pakete laufen aus")
@@ -779,6 +870,11 @@ local function progressText(job)
return "WARTE: " .. prettyItemName(job.waitingFor or "Input") return "WARTE: " .. prettyItemName(job.waitingFor or "Input")
.. (job.dependencyJobId and (" (#" .. job.dependencyJobId .. ")") or "") .. (job.dependencyJobId and (" (#" .. job.dependencyJobId .. ")") or "")
end end
if job.status == "WAITING_ME" then
return "ME: " .. prettyItemName(job.meWaitingFor or job.waitingFor or "Input")
.. " " .. formatNumber(stock[job.meWaitingFor or ""] or 0)
.. "/" .. formatNumber(job.meRequired or 0)
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