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 fra traktoren; siloer der allerede har varen
-- 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
return nil, "køretøjet har ingen position"
end
@@ -54,7 +55,8 @@ function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointI
local silos, withSpace = 0, 0
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
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
if acceptsRest(station, fillType, rest, farmId) then
withSpace = withSpace + 1
+13 -76
View File
@@ -18,8 +18,6 @@ ADUnloadWait.LOG_PREFIX = "ADSmartPickup: "
-- køretøj -> {task, homeDestinationID, waitMarker, fillType, rest}
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 isInstalled = false
@@ -39,72 +37,23 @@ local function getRestLoad(adEnv, vehicle)
return bestFillType, bestLevel
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 run = ADRunsController ~= nil and ADRunsController.getRunForVehicle(vehicle) or nil
return run ~= nil and run.waitPoolGroup or nil
end
local function getVehiclePosition(vehicle)
if vehicle.rootNode == nil then
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.
-- Rutetjek, reservationer og omdirigering deles med udkørsels-modulet (adWaitPool).
-- Kun "til punktet" ved silo-aflæsning: næste tur er AD's egen rute fra siloen.
local function hasRouteTo(adEnv, vehicle, wayPointId)
local graph = adEnv.ADGraphManager
if graph.getPathTo == nil then
return true
end
return pathReaches(graph:getPathTo(vehicle, wayPointId), wayPointId)
return ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId)
end
local function hasRouteBothWays(adEnv, vehicle, waitWayPointId, homeWayPointId)
local graph = adEnv.ADGraphManager
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)
return ADWaitPool.hasRouteBothWays(adEnv, vehicle, waitWayPointId, homeWayPointId)
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 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 = ADUnloadWait.STATE_DRIVING
task.vehicle.ad.drivePathModule:setPathTo(destinationID)
ADWaitPool.redirect(task, destinationID)
end
-- 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
local function release(vehicle)
local wait = waits[vehicle]
if wait ~= nil and claims[wait.waitMarker.id] == vehicle then
claims[wait.waitMarker.id] = nil
if waits[vehicle] ~= nil then
ADWaitPool.releaseVehicle(vehicle)
end
waits[vehicle] = nil
if ADLoadSwap ~= nil then
@@ -199,12 +147,11 @@ local function startWaiting(adEnv, task)
return
end
local x, z = getVehiclePosition(vehicle)
local isReachable = function(candidate)
return hasRouteBothWays(adEnv, vehicle, candidate.marker.id, task.destinationID)
end
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 not warnedNoWaitPoint[vehicle] then
warnedNoWaitPoint[vehicle] = true
@@ -214,7 +161,7 @@ local function startWaiting(adEnv, task)
return
end
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}
-- 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.
@@ -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}
end
function ADUnloadWait.getClaims()
local result = {}
for _, wait in pairs(waits) do
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
-- Alle reservationer i puljen (også udkørsels-traktorer der holder).
function ADUnloadWait.getClaims(adEnv)
return ADWaitPool.getClaims(adEnv)
end
function ADUnloadWait.install(adEnv)
@@ -389,6 +325,7 @@ function ADUnloadWait.install(adEnv)
end
isInstalled = true
ADUnloadWait.STATE_DRIVING = task.STATE_DRIVING or 2
ADWaitPool.STATE_DRIVING = ADUnloadWait.STATE_DRIVING
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="adRunsController.lua"/>
<sourceFile filename="adLoadSwap.lua"/>
<sourceFile filename="adWaitPool.lua"/>
<sourceFile filename="adUnloadWait.lua"/>
<sourceFile filename="adSmartPickup.lua"/>
</extraSourceFiles>
+1
View File
@@ -168,6 +168,7 @@ dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
dofile("FS25_ADSmartPickup/adRuns.lua")
dofile("FS25_ADSmartPickup/adRunsController.lua")
dofile("FS25_ADSmartPickup/adLoadSwap.lua")
dofile("FS25_ADSmartPickup/adWaitPool.lua")
dofile("FS25_ADSmartPickup/adUnloadWait.lua")
dofile("FS25_ADSmartPickup/adSmartPickup.lua")
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)