Add protected ME fallback for Create inputs
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user