Add protected ME fallback for Create inputs
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
-- ATM10 Create factory scheduler V1
|
||||
-- Persistent jobs, dependency planning, progress-aware retries and diagnostics.
|
||||
|
||||
local VERSION = "1.3.2"
|
||||
local VERSION = "1.4.0"
|
||||
local REFRESH_SECONDS = 1
|
||||
local CONFIRM_SECONDS = 6
|
||||
local DRAIN_SECONDS = 60
|
||||
@@ -12,6 +12,8 @@ local STATE_TEMP_FILE = STATE_FILE .. ".tmp"
|
||||
local EVENT_LOG_FILE = "factory_scheduler_log.txt"
|
||||
local MAX_EVENTS = 5
|
||||
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.
|
||||
local RECIPES = {
|
||||
@@ -200,6 +202,7 @@ end
|
||||
|
||||
local tickerName = findPeripheral("Create_StockTicker")
|
||||
local monitorName = findPeripheral("monitor")
|
||||
local meBridgeName = findPeripheral("me_bridge")
|
||||
if not tickerName then error("Kein Create Stock Ticker gefunden", 0) end
|
||||
if not monitorName then error("Kein Monitor gefunden", 0) end
|
||||
local monitor = peripheral.wrap(monitorName)
|
||||
@@ -404,7 +407,10 @@ local function request(address, item, count)
|
||||
return sent
|
||||
end
|
||||
|
||||
local clearMEWait
|
||||
|
||||
local function finishJob(job)
|
||||
clearMEWait(job)
|
||||
job.status, job.activeStation, job.finishedAt = "DONE", nil, epoch()
|
||||
statusText, statusColor = "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
|
||||
|
||||
local function failJob(job, reason)
|
||||
clearMEWait(job)
|
||||
job.status, job.activeStation, job.finishedAt = "FAILED", nil, epoch()
|
||||
statusText, statusColor = "FEHLER #" .. job.id .. ": " .. reason, colors.red
|
||||
logEvent(statusText, colors.red) addError(job, "FAILED", reason) saveState()
|
||||
@@ -443,6 +450,78 @@ end
|
||||
|
||||
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)
|
||||
if not job.dependencyJobId then return true end
|
||||
local dependency = findJobById(job.dependencyJobId)
|
||||
@@ -473,9 +552,7 @@ end
|
||||
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
|
||||
return waitForME(job, item, required)
|
||||
end
|
||||
|
||||
if job.dependencyJobId then
|
||||
@@ -508,6 +585,9 @@ end
|
||||
local function dispatchNormal(job, missingOutput)
|
||||
local recipe = RECIPES[job.recipeId]
|
||||
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)
|
||||
if not selectedStation then job.status = "WAITING" return false end
|
||||
|
||||
@@ -560,6 +640,7 @@ local function dispatchNormal(job, missingOutput)
|
||||
job.lastBatchInput = nil
|
||||
job.activeStation, job.status = selectedStation, "RUNNING"
|
||||
job.waitingFor, job.dependencyJobId, job.lastDependencyLog = nil, nil, nil
|
||||
clearMEWait(job)
|
||||
job.phaseStartedAt, job.lastProgressAt, job.warned = epoch(), epoch(), false
|
||||
local inputTotal = 0
|
||||
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 }
|
||||
observeItems(job, watched)
|
||||
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)
|
||||
return
|
||||
end
|
||||
@@ -601,7 +682,7 @@ local function updateNormal(job)
|
||||
if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end
|
||||
return
|
||||
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
|
||||
retryOrFail(job, missing, "Nachlauf beendet, Output unvollstaendig")
|
||||
elseif job.status == "RUNNING" then
|
||||
@@ -613,8 +694,12 @@ end
|
||||
|
||||
local function dispatchMetalCrushing(job, remaining, plan)
|
||||
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))))
|
||||
if wanted <= 0 then failJob(job, "Zu wenig " .. plan.sourceName .. " fuer fehlende " .. plan.name .. " Nuggets") return end
|
||||
local required = math.max(1, math.min(64, math.ceil(remaining / 3)))
|
||||
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)
|
||||
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"
|
||||
@@ -646,7 +731,12 @@ local function updateMetal(job)
|
||||
if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end
|
||||
return
|
||||
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
|
||||
-- which has already reached storage can satisfy the remaining parent job.
|
||||
@@ -726,6 +816,7 @@ local function maintainStock()
|
||||
end
|
||||
|
||||
local function cancelJob(job)
|
||||
clearMEWait(job)
|
||||
job.status, job.activeStation, job.finishedAt = "CANCELLED", nil, epoch()
|
||||
logEvent("#" .. job.id .. " manuell abgebrochen", colors.red)
|
||||
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")
|
||||
.. (job.dependencyJobId and (" (#" .. job.dependencyJobId .. ")") or "")
|
||||
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
|
||||
return formatNumber(job.progress or 0) .. " ANGEKOMMEN " .. tostring(job.phase) .. " " .. idle .. "s"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user