refactor: Wait-puljen udskilt i adWaitPool (reservationer, rutetjek, redirect) til deling med udkørsel

This commit is contained in:
2026-09-23 09:47:47 +02:00
parent 68ad63ce1f
commit def054ca43
6 changed files with 233 additions and 78 deletions
+4 -2
View File
@@ -43,7 +43,8 @@ end
-- Nærmeste silo med plads til resten og vejnet til/fra; siloer der allerede har varen foretrækkes. -- Nærmeste silo med plads til resten og vejnet til/fra; siloer der allerede har varen foretrækkes.
-- Nærmeste silo med plads til resten og vejnet fra traktoren; siloer der allerede har varen -- Nærmeste silo med plads til resten og vejnet fra traktoren; siloer der allerede har varen
-- foretrækkes. Returnerer marker — eller nil + en forklaring (til loggen). -- foretrækkes. Returnerer marker — eller nil + en forklaring (til loggen).
function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointId, isReachable) -- excludedPlaceables (valgfri): {[placeable] = true} der aldrig må vælges (fx udkørslens kilder).
function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointId, isReachable, excludedPlaceables)
if vehicle.rootNode == nil then if vehicle.rootNode == nil then
return nil, "køretøjet har ingen position" return nil, "køretøjet har ingen position"
end end
@@ -54,7 +55,8 @@ function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointI
local silos, withSpace = 0, 0 local silos, withSpace = 0, 0
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId) local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId)
if station ~= nil and isSilo(station, homeStation) then local isExcluded = excludedPlaceables ~= nil and excludedPlaceables[station ~= nil and station.owningPlaceable or false] == true
if station ~= nil and isSilo(station, homeStation) and not isExcluded then
silos = silos + 1 silos = silos + 1
if acceptsRest(station, fillType, rest, farmId) then if acceptsRest(station, fillType, rest, farmId) then
withSpace = withSpace + 1 withSpace = withSpace + 1
+13 -76
View File
@@ -18,8 +18,6 @@ ADUnloadWait.LOG_PREFIX = "ADSmartPickup: "
-- køretøj -> {task, homeDestinationID, waitMarker, fillType, rest} -- køretøj -> {task, homeDestinationID, waitMarker, fillType, rest}
local waits = setmetatable({}, {__mode = "k"}) local waits = setmetatable({}, {__mode = "k"})
-- markør-id -> køretøj der har reserveret Wait-punktet
local claims = setmetatable({}, {__mode = "v"})
local warnedNoWaitPoint = setmetatable({}, {__mode = "k"}) local warnedNoWaitPoint = setmetatable({}, {__mode = "k"})
local isInstalled = false local isInstalled = false
@@ -39,72 +37,23 @@ local function getRestLoad(adEnv, vehicle)
return bestFillType, bestLevel return bestFillType, bestLevel
end end
-- poolGroup: run'ets Wait-mappe; tom/nil = alle Wait-mapper.
local function getWaitCandidates(adEnv, poolGroup)
local candidates = {}
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
local isInPool = poolGroup == nil or poolGroup == "" or marker.group == poolGroup
if isInPool and ADSupplyPlanner.isWaitMarker(marker) then
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
if wayPoint ~= nil then
table.insert(candidates, {marker = marker, x = wayPoint.x, z = wayPoint.z})
end
end
end
return candidates
end
local function getWaitPoolGroup(vehicle) local function getWaitPoolGroup(vehicle)
local run = ADRunsController ~= nil and ADRunsController.getRunForVehicle(vehicle) or nil local run = ADRunsController ~= nil and ADRunsController.getRunForVehicle(vehicle) or nil
return run ~= nil and run.waitPoolGroup or nil return run ~= nil and run.waitPoolGroup or nil
end end
local function getVehiclePosition(vehicle) -- Rutetjek, reservationer og omdirigering deles med udkørsels-modulet (adWaitPool).
if vehicle.rootNode == nil then -- Kun "til punktet" ved silo-aflæsning: næste tur er AD's egen rute fra siloen.
return nil
end
local x, _, z = getWorldTranslation(vehicle.rootNode)
return x, z
end
-- Ender ruten i målet? (AutoDrive returnerer tom/afkortet liste når der ingen vej er.)
local function pathReaches(path, wayPointId)
return path ~= nil and #path > 0 and path[#path].id == wayPointId
end
-- Findes der vejnet fra køretøjet til Wait-punktet OG fra Wait-punktet hjem til målet?
-- Sendes AutoDrive mod et punkt uden rute, viser den "cannot reach" og slukker sig selv.
-- getPathTo/pathFromTo er rene opslag uden bivirkninger.
-- Kun "til punktet": ved aflæsning af (forkert) last i en silo er næste tur AD's egen
-- rute fra siloen, så "fra siloen hjem" er ikke et krav.
local function hasRouteTo(adEnv, vehicle, wayPointId) local function hasRouteTo(adEnv, vehicle, wayPointId)
local graph = adEnv.ADGraphManager return ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId)
if graph.getPathTo == nil then
return true
end
return pathReaches(graph:getPathTo(vehicle, wayPointId), wayPointId)
end end
local function hasRouteBothWays(adEnv, vehicle, waitWayPointId, homeWayPointId) local function hasRouteBothWays(adEnv, vehicle, waitWayPointId, homeWayPointId)
local graph = adEnv.ADGraphManager return ADWaitPool.hasRouteBothWays(adEnv, vehicle, waitWayPointId, homeWayPointId)
if graph.getPathTo == nil or graph.pathFromTo == nil then
return true
end
return pathReaches(graph:getPathTo(vehicle, waitWayPointId), waitWayPointId)
and pathReaches(graph:pathFromTo(waitWayPointId, homeWayPointId), homeWayPointId)
end end
-- Står AD midt i en (blokeret) aflæsning, stoppes den pænt først — ellers kører traktoren
-- væk med tippet vogn.
local function redirect(task, destinationID) local function redirect(task, destinationID)
local trailerModule = task.vehicle.ad.trailerModule ADWaitPool.redirect(task, destinationID)
if trailerModule ~= nil and trailerModule.isActiveAtTrigger ~= nil and trailerModule:isActiveAtTrigger() then
if trailerModule.stopUnloading ~= nil then trailerModule:stopUnloading() end
if trailerModule.stopLoading ~= nil then trailerModule:stopLoading() end
end
task.destinationID = destinationID
task.state = ADUnloadWait.STATE_DRIVING
task.vehicle.ad.drivePathModule:setPathTo(destinationID)
end end
-- køretøj -> AD's oprindelige 2. markør (waypoint-id) mens der læsses af i en silo -- køretøj -> AD's oprindelige 2. markør (waypoint-id) mens der læsses af i en silo
@@ -137,9 +86,8 @@ local function restoreTarget(vehicle)
end end
local function release(vehicle) local function release(vehicle)
local wait = waits[vehicle] if waits[vehicle] ~= nil then
if wait ~= nil and claims[wait.waitMarker.id] == vehicle then ADWaitPool.releaseVehicle(vehicle)
claims[wait.waitMarker.id] = nil
end end
waits[vehicle] = nil waits[vehicle] = nil
if ADLoadSwap ~= nil then if ADLoadSwap ~= nil then
@@ -199,12 +147,11 @@ local function startWaiting(adEnv, task)
return return
end end
local x, z = getVehiclePosition(vehicle)
local isReachable = function(candidate) local isReachable = function(candidate)
return hasRouteBothWays(adEnv, vehicle, candidate.marker.id, task.destinationID) return hasRouteBothWays(adEnv, vehicle, candidate.marker.id, task.destinationID)
end end
local pool = getWaitPoolGroup(vehicle) local pool = getWaitPoolGroup(vehicle)
local candidate = x ~= nil and ADSupplyPlanner.pickNearestFreeWaitPoint(getWaitCandidates(adEnv, pool), claims, x, z, vehicle, isReachable) or nil local candidate = ADWaitPool.pickFree(adEnv, vehicle, pool, isReachable)
if candidate == nil then if candidate == nil then
if not warnedNoWaitPoint[vehicle] then if not warnedNoWaitPoint[vehicle] then
warnedNoWaitPoint[vehicle] = true warnedNoWaitPoint[vehicle] = true
@@ -214,7 +161,7 @@ local function startWaiting(adEnv, task)
return return
end end
warnedNoWaitPoint[vehicle] = nil warnedNoWaitPoint[vehicle] = nil
claims[candidate.marker.id] = vehicle ADWaitPool.claim(candidate.marker.id, vehicle)
waits[vehicle] = {task = task, homeDestinationID = task.destinationID, waitMarker = candidate.marker, fillType = fillType, rest = rest} waits[vehicle] = {task = task, homeDestinationID = task.destinationID, waitMarker = candidate.marker, fillType = fillType, rest = rest}
-- AD tipper (og holder vognen stille) så længe den står nær sit EGET mål — også i små -- AD tipper (og holder vognen stille) så længe den står nær sit EGET mål — også i små
-- portioner, når målet forbruger løbende. Derfor peger AD's mål på Wait-punktet imens. -- portioner, når målet forbruger løbende. Derfor peger AD's mål på Wait-punktet imens.
@@ -366,20 +313,9 @@ function ADUnloadWait.getWaitFor(vehicle)
return {markerName = wait.waitMarker ~= nil and wait.waitMarker.name or nil, fillType = wait.fillType, rest = wait.rest} return {markerName = wait.waitMarker ~= nil and wait.waitMarker.name or nil, fillType = wait.fillType, rest = wait.rest}
end end
function ADUnloadWait.getClaims() -- Alle reservationer i puljen (også udkørsels-traktorer der holder).
local result = {} function ADUnloadWait.getClaims(adEnv)
for _, wait in pairs(waits) do return ADWaitPool.getClaims(adEnv)
if wait.waitMarker ~= nil and claims[wait.waitMarker.id] ~= nil then
table.insert(result, {marker = wait.waitMarker, vehicle = claims[wait.waitMarker.id]})
end
end
table.sort(result, function(a, b)
if a.marker.group ~= b.marker.group then
return tostring(a.marker.group) < tostring(b.marker.group)
end
return a.marker.id < b.marker.id
end)
return result
end end
function ADUnloadWait.install(adEnv) function ADUnloadWait.install(adEnv)
@@ -389,6 +325,7 @@ function ADUnloadWait.install(adEnv)
end end
isInstalled = true isInstalled = true
ADUnloadWait.STATE_DRIVING = task.STATE_DRIVING or 2 ADUnloadWait.STATE_DRIVING = task.STATE_DRIVING or 2
ADWaitPool.STATE_DRIVING = ADUnloadWait.STATE_DRIVING
local originalUpdate, originalFinished, originalGetSetting = task.update, task.finished, autoDrive.getSetting local originalUpdate, originalFinished, originalGetSetting = task.update, task.finished, autoDrive.getSetting
+118
View File
@@ -0,0 +1,118 @@
-- AD Wait Pool
-- Fælles pulje af Wait-punkter (markører i mapper med ordet "Wait" i navnet) med
-- reservationer, nærmeste-ledige-opslag, rutetjek og omdirigering af en AutoDrive-opgave.
-- Bruges af adUnloadWait (rest-læs ved fuldt mål) og adOutbound (hold uden tur).
ADWaitPool = {}
ADWaitPool.LOG_PREFIX = "ADSmartPickup: "
-- sættes af den der installerer opgave-hooks (task.STATE_DRIVING); 2 er AD's værdi
ADWaitPool.STATE_DRIVING = 2
-- markør-id -> køretøj der har reserveret Wait-punktet
ADWaitPool.claims = setmetatable({}, {__mode = "v"})
-- poolGroup: run'ets Wait-mappe; tom/nil = alle Wait-mapper.
function ADWaitPool.getCandidates(adEnv, poolGroup)
local candidates = {}
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
local isInPool = poolGroup == nil or poolGroup == "" or marker.group == poolGroup
if isInPool and ADSupplyPlanner.isWaitMarker(marker) then
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
if wayPoint ~= nil then
table.insert(candidates, {marker = marker, x = wayPoint.x, z = wayPoint.z})
end
end
end
return candidates
end
function ADWaitPool.claim(markerId, vehicle)
ADWaitPool.claims[markerId] = vehicle
end
function ADWaitPool.getOwner(markerId)
return ADWaitPool.claims[markerId]
end
function ADWaitPool.releaseVehicle(vehicle)
for markerId, owner in pairs(ADWaitPool.claims) do
if owner == vehicle then
ADWaitPool.claims[markerId] = nil
end
end
end
local function getVehiclePosition(vehicle)
if vehicle.rootNode == nil then
return nil
end
local x, _, z = getWorldTranslation(vehicle.rootNode)
return x, z
end
-- Nærmeste ledige (eller eget reserverede) Wait-punkt fra vognens position; isReachable(candidate)
-- valgfri. nil uden position eller uden brugbart punkt. Reserverer IKKE selv.
function ADWaitPool.pickFree(adEnv, vehicle, poolGroup, isReachable)
local x, z = getVehiclePosition(vehicle)
if x == nil then
return nil
end
return ADSupplyPlanner.pickNearestFreeWaitPoint(ADWaitPool.getCandidates(adEnv, poolGroup), ADWaitPool.claims, x, z, vehicle, isReachable)
end
-- Ender ruten i målet? (AutoDrive returnerer tom/afkortet liste når der ingen vej er.)
local function pathReaches(path, wayPointId)
return path ~= nil and #path > 0 and path[#path].id == wayPointId
end
-- Sendes AutoDrive mod et punkt uden rute, viser den "cannot reach" og slukker sig selv.
-- getPathTo/pathFromTo er rene opslag uden bivirkninger.
function ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId)
local graph = adEnv.ADGraphManager
if graph.getPathTo == nil then
return true
end
return pathReaches(graph:getPathTo(vehicle, wayPointId), wayPointId)
end
function ADWaitPool.hasRouteBothWays(adEnv, vehicle, toWayPointId, homeWayPointId)
local graph = adEnv.ADGraphManager
if graph.getPathTo == nil or graph.pathFromTo == nil then
return true
end
return pathReaches(graph:getPathTo(vehicle, toWayPointId), toWayPointId)
and pathReaches(graph:pathFromTo(toWayPointId, homeWayPointId), homeWayPointId)
end
-- Står AD midt i en (blokeret) af-/pålæsning, stoppes den pænt først — ellers kører
-- traktoren væk med tippet vogn. Kun opgavens destination ændres.
function ADWaitPool.redirect(task, destinationID)
local trailerModule = task.vehicle.ad.trailerModule
if trailerModule ~= nil and trailerModule.isActiveAtTrigger ~= nil and trailerModule:isActiveAtTrigger() then
if trailerModule.stopUnloading ~= nil then trailerModule:stopUnloading() end
if trailerModule.stopLoading ~= nil then trailerModule:stopLoading() end
end
task.destinationID = destinationID
task.state = ADWaitPool.STATE_DRIVING
task.vehicle.ad.drivePathModule:setPathTo(destinationID)
end
-- Hvem holder hvor (GUI'ets Wait-puljer-visning). Sorteret på mappe, dernæst markør-id.
function ADWaitPool.getClaims(adEnv)
local result = {}
local graph = adEnv ~= nil and adEnv.ADGraphManager or (FS25_AutoDrive ~= nil and FS25_AutoDrive.ADGraphManager or nil)
for markerId, vehicle in pairs(ADWaitPool.claims) do
local marker = graph ~= nil and graph.getMapMarkerByWayPointId ~= nil and graph:getMapMarkerByWayPointId(markerId) or nil
if marker ~= nil then
table.insert(result, {marker = marker, vehicle = vehicle})
end
end
table.sort(result, function(a, b)
if a.marker.group ~= b.marker.group then
return tostring(a.marker.group) < tostring(b.marker.group)
end
return a.marker.id < b.marker.id
end)
return result
end
+1
View File
@@ -17,6 +17,7 @@
<sourceFile filename="adRunsStorage.lua"/> <sourceFile filename="adRunsStorage.lua"/>
<sourceFile filename="adRunsController.lua"/> <sourceFile filename="adRunsController.lua"/>
<sourceFile filename="adLoadSwap.lua"/> <sourceFile filename="adLoadSwap.lua"/>
<sourceFile filename="adWaitPool.lua"/>
<sourceFile filename="adUnloadWait.lua"/> <sourceFile filename="adUnloadWait.lua"/>
<sourceFile filename="adSmartPickup.lua"/> <sourceFile filename="adSmartPickup.lua"/>
</extraSourceFiles> </extraSourceFiles>
+1
View File
@@ -168,6 +168,7 @@ dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
dofile("FS25_ADSmartPickup/adRuns.lua") dofile("FS25_ADSmartPickup/adRuns.lua")
dofile("FS25_ADSmartPickup/adRunsController.lua") dofile("FS25_ADSmartPickup/adRunsController.lua")
dofile("FS25_ADSmartPickup/adLoadSwap.lua") dofile("FS25_ADSmartPickup/adLoadSwap.lua")
dofile("FS25_ADSmartPickup/adWaitPool.lua")
dofile("FS25_ADSmartPickup/adUnloadWait.lua") dofile("FS25_ADSmartPickup/adUnloadWait.lua")
dofile("FS25_ADSmartPickup/adSmartPickup.lua") dofile("FS25_ADSmartPickup/adSmartPickup.lua")
listener:update(16) -- installerer hook'en listener:update(16) -- installerer hook'en
+96
View File
@@ -0,0 +1,96 @@
-- Kør: luajit tests/test_adWaitPool.lua (fra repo-roden)
-- Wait-puljen: kandidater pr. mappe, reservationer, nærmeste ledige, rutetjek, redirect.
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
dofile("FS25_ADSmartPickup/adWaitPool.lua")
local failures = 0
local function check(name, actual, expected)
if actual == expected then
print("OK " .. name)
else
failures = failures + 1
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
end
end
local markers, wayPoints = {}, {}
local function addMarker(id, name, group, x, z)
wayPoints[id] = {id = id, x = x, z = z, incoming = {}}
local marker = {id = id, markerIndex = #markers + 1, name = name, group = group}
table.insert(markers, marker)
return marker
end
local unreachable = {}
local adEnv = {
ADGraphManager = {
getMapMarkers = function() return markers end,
getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end,
getWayPointById = function(_, id) return wayPoints[id] end,
getPathTo = function(_, vehicle, id) if unreachable[id] then return {} end return {{id = id}} end,
pathFromTo = function(_, from, to) if unreachable[to] then return {} end return {{id = to}} end,
},
}
getWorldTranslation = function(node) return node.x, 0, node.z end
addMarker(10, "cow wait 1", "cow wait", 0, 0)
addMarker(11, "cow wait 2", "cow wait", 50, 0)
addMarker(20, "Wait 3", "General Wait", 10, 0)
addMarker(30, "silo 1", "silo", 5, 0)
-- W1: kandidater
check("W1 kun mappen", #ADWaitPool.getCandidates(adEnv, "cow wait"), 2)
check("W1 tom pulje = alle Wait-mapper", #ADWaitPool.getCandidates(adEnv, ""), 3)
check("W1 nil = alle", #ADWaitPool.getCandidates(adEnv, nil), 3)
-- W2: nærmeste ledige fra vognens position; reserverede springes over; egen reservation OK
local v1, v2 = {name = "v1", rootNode = {x = 45, z = 0}}, {name = "v2", rootNode = {x = 45, z = 0}}
check("W2 nærmeste", ADWaitPool.pickFree(adEnv, v1, "cow wait").marker.id, 11)
ADWaitPool.claim(11, v1)
check("W2 reserveret springes over", ADWaitPool.pickFree(adEnv, v2, "cow wait").marker.id, 10)
check("W2 egen reservation genbruges", ADWaitPool.pickFree(adEnv, v1, "cow wait").marker.id, 11)
-- W3: claim / owner / release
check("W3 ejer", ADWaitPool.getOwner(11), v1)
ADWaitPool.releaseVehicle(v1)
check("W3 frigivet", ADWaitPool.getOwner(11), nil)
check("W3 nu ledig for v2", ADWaitPool.pickFree(adEnv, v2, "cow wait").marker.id, 11)
-- W4: nåelighed
unreachable[11] = true
check("W4 uopnåelig nærmeste -> næste", ADWaitPool.pickFree(adEnv, v2, "cow wait", function(c) return ADWaitPool.hasRouteTo(adEnv, v2, c.marker.id) end).marker.id, 10)
check("W4 hasRouteTo false", ADWaitPool.hasRouteTo(adEnv, v2, 11), false)
check("W4 hasRouteBothWays true", ADWaitPool.hasRouteBothWays(adEnv, v2, 10, 30), true)
check("W4 hasRouteBothWays hjem uopnåelig", ADWaitPool.hasRouteBothWays(adEnv, v2, 10, 11), false)
unreachable[11] = nil
check("W4 intet punkt -> nil", ADWaitPool.pickFree(adEnv, v2, "cow wait", function() return false end), nil)
-- W5: getClaims sorteret på mappe, dernæst id
ADWaitPool.claim(20, v2)
ADWaitPool.claim(10, v1)
local claims = ADWaitPool.getClaims(adEnv)
check("W5 to reservationer", #claims, 2)
check("W5 første = General Wait", claims[1].marker.group, "General Wait")
check("W5 anden = cow wait 1", claims[2].marker.id, 10)
ADWaitPool.releaseVehicle(v1)
ADWaitPool.releaseVehicle(v2)
check("W5 alt frigivet", #ADWaitPool.getClaims(adEnv), 0)
-- W6: redirect stopper trigger-aktivitet og sætter sti
local stopped = {}
local task = {
destinationID = 99, state = 1,
vehicle = {ad = {
trailerModule = {isActiveAtTrigger = function() return true end, stopUnloading = function() stopped.unload = true end, stopLoading = function() stopped.load = true end},
drivePathModule = {setPathTo = function(_, id) stopped.pathTo = id end},
}},
}
ADWaitPool.STATE_DRIVING = 2
ADWaitPool.redirect(task, 10)
check("W6 destination", task.destinationID, 10)
check("W6 state driving", task.state, 2)
check("W6 setPathTo", stopped.pathTo, 10)
check("W6 stopUnloading", stopped.unload, true)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)