perf+fix: rutetjek gemmes 30 s (hak hvert 5. s); kilde der gav lidt hviler 3 min (æg-vognen til samme hønsehus 3x) (v1.27.3.0)

In-game 26/9: hak ca. hvert 5. s efter 1.27.2 = holdende vognes genplanlægning (ADOutbound/ADLoadSwap hvert
5 s) med AD-stifinding + reserve-BFS gennem 13.550 waypoints pr. kandidat. Rutesvar caches pr. mål og 10 m-
område i 30 s (max 2000 poster); langsomme rutetjek logges. Æg: 'ingen mere last' markerer nu kilden som
hvilende 3 min, så runden roterer mellem hønsehusene.
This commit is contained in:
2026-09-26 11:07:16 +02:00
parent 038cb5be54
commit 0fca6e8a9a
8 changed files with 138 additions and 11 deletions
+14 -1
View File
@@ -25,6 +25,9 @@ ADOutbound.NOTIFICATION_MS = 8000
-- spawnet, forkert farm, ...): kilden udelukkes for varen i EMPTY_SOURCE_TTL_MS og turen afsluttes. -- spawnet, forkert farm, ...): kilden udelukkes for varen i EMPTY_SOURCE_TTL_MS og turen afsluttes.
ADOutbound.EMPTY_SOURCE_MS = 30000 ADOutbound.EMPTY_SOURCE_MS = 30000
ADOutbound.EMPTY_SOURCE_TTL_MS = 300000 ADOutbound.EMPTY_SOURCE_TTL_MS = 300000
-- kilde der gav lidt og så intet mere (fx paller uden for Universal Autoloads rækkevidde): hviler så længe,
-- så de andre kilder kommer til (in-game 26/9: æg-vognen kørte til samme hønsehus tre gange i træk)
ADOutbound.THIN_SOURCE_REST_MS = 180000
-- Autoload-bil på vej til aflæsning: stropperne løsnes så mange meter FØR markøren. Markøren står -- Autoload-bil på vej til aflæsning: stropperne løsnes så mange meter FØR markøren. Markøren står
-- hvor næsen stopper, og en lastbils lad har passeret salgstriggeren dér; AutoDrive løsner selv -- hvor næsen stopper, og en lastbils lad har passeret salgstriggeren dér; AutoDrive løsner selv
-- først ved markøren ("AL unload = center"), hvilket er for sent for en lastbil. -- først ved markøren ("AL unload = center"), hvilket er for sent for en lastbil.
@@ -42,6 +45,11 @@ local warned = setmetatable({}, {__mode = "k"})
local emptyUntil = {} local emptyUntil = {}
local isInstalled = false local isInstalled = false
-- Glem alle "tom/hvilende kilde"-markeringer (tests; og når runs genindlæses).
function ADOutbound.resetEmptySources()
emptyUntil = {}
end
local function now() local function now()
if g_currentMission ~= nil and type(g_currentMission.time) == "number" then if g_currentMission ~= nil and type(g_currentMission.time) == "number" then
return g_currentMission.time return g_currentMission.time
@@ -753,7 +761,12 @@ function ADOutbound.beforeLoadUpdate(adEnv, task, dt)
return true return true
end end
if state.noProgressMs > ADOutbound.NO_PROGRESS_MS and level > 0 and not spawnPending then if state.noProgressMs > ADOutbound.NO_PROGRESS_MS and level > 0 and not spawnPending then
Logging.info(ADOutbound.LOG_PREFIX .. "'%s': ingen mere last ved %s; kører med %.0f l", tostring(ADRuns.vehicleLabel(vehicle)), tostring(source ~= nil and source.name or state.current), level) local rests = now() + ADOutbound.THIN_SOURCE_REST_MS
for _, fillType in ipairs(state.trip.fillTypes) do
local key = tostring(state.current) .. ":" .. tostring(fillType)
if emptyUntil[key] == nil or emptyUntil[key] < rests then emptyUntil[key] = rests end
end
Logging.info(ADOutbound.LOG_PREFIX .. "'%s': ingen mere last ved %s; kører med %.0f l (kilden hviler %d min, så de andre kommer til)", tostring(ADRuns.vehicleLabel(vehicle)), tostring(source ~= nil and source.name or state.current), level, math.floor(ADOutbound.THIN_SOURCE_REST_MS / 60000))
state.noProgressMs, state.completed = 0, true state.noProgressMs, state.completed = 0, true
task:finished() task:finished()
return true return true
+68 -5
View File
@@ -124,11 +124,42 @@ local function graphReaches(adEnv, vehicle, targetId)
return false return false
end end
function ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId) -- Rutesvar gemmes ROUTE_CACHE_MS pr. mål og 10 m-område: holdende vogne spørger hvert 5. s for hver
local graph = adEnv.ADGraphManager -- kandidat, og AD's stifinding (+ reserve-søgningen) gennem hele vejnettet gav et hak hvert 5. s
if graph.getPathTo == nil then -- (in-game 26/9). Vejnettet ændrer sig ikke så hurtigt.
return true ADWaitPool.ROUTE_CACHE_MS = 30000
ADWaitPool.ROUTE_CELL_M = 10
ADWaitPool.SLOW_ROUTE_MS = 3
local routeCache = {}
local routeCacheSize = 0
ADWaitPool.ROUTE_CACHE_MAX = 2000
local function remember(key, result, at)
if routeCache[key] == nil then
routeCacheSize = routeCacheSize + 1
if routeCacheSize > ADWaitPool.ROUTE_CACHE_MAX then
routeCache, routeCacheSize = {}, 1
end end
end
routeCache[key] = {result = result, at = at}
end
local function clockMs()
local ok, ms = pcall(function() return netGetTime() end)
if ok and type(ms) == "number" then return ms end
return nil
end
local function routeKey(vehicle, wayPointId)
if vehicle.rootNode == nil or getWorldTranslation == nil then return nil end
local ok, x, _, z = pcall(getWorldTranslation, vehicle.rootNode)
if not ok or x == nil then return nil end
local cell = ADWaitPool.ROUTE_CELL_M
return string.format("%s:%d:%d", tostring(wayPointId), math.floor(x / cell), math.floor(z / cell))
end
local function computeRoute(adEnv, vehicle, wayPointId)
local graph = adEnv.ADGraphManager
if pathReaches(graph:getPathTo(vehicle, wayPointId), wayPointId) then if pathReaches(graph:getPathTo(vehicle, wayPointId), wayPointId) then
return true return true
end end
@@ -142,13 +173,45 @@ function ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId)
return false return false
end end
function ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId)
local graph = adEnv.ADGraphManager
if graph.getPathTo == nil then
return true
end
local now = clockMs()
local key = now ~= nil and routeKey(vehicle, wayPointId) or nil
local cached = key ~= nil and routeCache[key] or nil
if cached ~= nil and now - cached.at < ADWaitPool.ROUTE_CACHE_MS then
return cached.result
end
local result = computeRoute(adEnv, vehicle, wayPointId)
if key ~= nil then
local spent = (clockMs() or now) - now
remember(key, result, now)
if spent > ADWaitPool.SLOW_ROUTE_MS and Logging ~= nil and Logging.info ~= nil
and (ADWaitPool.lastSlowLog == nil or now - ADWaitPool.lastSlowLog > 60000) then
ADWaitPool.lastSlowLog = now
Logging.info("ADSmartPickup: rutetjek til markør %s tog %.1f ms (%s) — gemmes nu i %d s", tostring(wayPointId), spent, result and "vej" or "ingen vej", ADWaitPool.ROUTE_CACHE_MS / 1000)
end
end
return result
end
function ADWaitPool.hasRouteBothWays(adEnv, vehicle, toWayPointId, homeWayPointId) function ADWaitPool.hasRouteBothWays(adEnv, vehicle, toWayPointId, homeWayPointId)
local graph = adEnv.ADGraphManager local graph = adEnv.ADGraphManager
if graph.getPathTo == nil or graph.pathFromTo == nil then if graph.getPathTo == nil or graph.pathFromTo == nil then
return true return true
end end
return pathReaches(graph:getPathTo(vehicle, toWayPointId), toWayPointId) local now = clockMs()
local key = now ~= nil and routeKey(vehicle, tostring(toWayPointId) .. ">" .. tostring(homeWayPointId)) or nil
local cached = key ~= nil and routeCache[key] or nil
if cached ~= nil and now - cached.at < ADWaitPool.ROUTE_CACHE_MS then
return cached.result
end
local result = pathReaches(graph:getPathTo(vehicle, toWayPointId), toWayPointId)
and pathReaches(graph:pathFromTo(toWayPointId, homeWayPointId), homeWayPointId) and pathReaches(graph:pathFromTo(toWayPointId, homeWayPointId), homeWayPointId)
if key ~= nil then remember(key, result, now) end
return result
end end
-- Står AD midt i en (blokeret) af-/pålæsning, stoppes den pænt først — ellers kører -- Står AD midt i en (blokeret) af-/pålæsning, stoppes den pænt først — ellers kører
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107"> <modDesc descVersion="107">
<author>masterdraco</author> <author>masterdraco</author>
<version>1.27.2.0</version> <version>1.27.3.0</version>
<title> <title>
<en>AD Smart Pickup</en> <en>AD Smart Pickup</en>
</title> </title>
+1 -1
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — brugermanual # AD Smart Pickup — brugermanual
Version 1.27.2 · Farming Simulator 25 · tillæg til AutoDrive Version 1.27.3 · Farming Simulator 25 · tillæg til AutoDrive
*English version: [USER_MANUAL.md](USER_MANUAL.md)* *English version: [USER_MANUAL.md](USER_MANUAL.md)*
+2 -2
View File
@@ -1,4 +1,4 @@
# Testplan — Markarbejde (fanen Marker), version 1.27.2.0 # Testplan — Markarbejde (fanen Marker), version 1.27.3.0
Stor test-session. Kør testene i rækkefølge: de første bygger opsætningen, de senere bygger på den. Stor test-session. Kør testene i rækkefølge: de første bygger opsætningen, de senere bygger på den.
Hver test har **Gør**, **Forvent** og **Log** (linjer i `log.txt`, der beviser at det virkede). Loglinjer Hver test har **Gør**, **Forvent** og **Log** (linjer i `log.txt`, der beviser at det virkede). Loglinjer
@@ -11,7 +11,7 @@ samme rækkefølge.
## 0. Forberedelse ## 0. Forberedelse
- [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.27.2.0) FS25_ADSmartPickup`. - [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.27.3.0) FS25_ADSmartPickup`.
- [ ] **0.2 Fanen Marker åbner.** ESC → Smart Pickup → fanen **Marker**. Forvent: "Marker indlæses …" - [ ] **0.2 Fanen Marker åbner.** ESC → Smart Pickup → fanen **Marker**. Forvent: "Marker indlæses …"
i få sekunder, derefter én række pr. ejet mark (også korn-, roe- og bare marker, ikke kun græs). i få sekunder, derefter én række pr. ejet mark (også korn-, roe- og bare marker, ikke kun græs).
Afgrøde-kolonnen viser afgrøden eller "bar jord". Afgrøde-kolonnen viser afgrøden eller "bar jord".
+1 -1
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — user manual # AD Smart Pickup — user manual
Version 1.27.2 · Farming Simulator 25 · add-on for AutoDrive Version 1.27.3 · Farming Simulator 25 · add-on for AutoDrive
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)* *Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
+21
View File
@@ -345,6 +345,26 @@ check("O13 WARN", lastLog("intet at læsse") ~= nil, true)
local choice13b = ADOutbound.choosePickup(adEnv, v13) local choice13b = ADOutbound.choosePickup(adEnv, v13)
check("O13 næste plan udelukker den tomme kilde", choice13b.markerIndex, mSugar.markerIndex) check("O13 næste plan udelukker den tomme kilde", choice13b.markerIndex, mSugar.markerIndex)
-- O16: kilden gav LIDT og så intet mere ("ingen mere last") -> kilden hviler, næste tur tager en anden
-- (in-game 26/9: æg-vognen kørte til Æg Pickup 4 tre gange i træk; én palle inden for rækkevidde, resten udenfor)
local v16 = newVehicle("Thin", 0, 0, 20000)
runs[v16] = outboundRun(16, {1, 2}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = false})
ADOutbound.resetEmptySources()
local choice16 = ADOutbound.choosePickup(adEnv, v16)
local first16 = choice16.markerIndex
local task16 = newTask(v16, choice16.markerIndex == mSilo.markerIndex and 2 or 1)
v16.atTarget = true
v16.trailers.level = 500
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
ADOutbound.beforeLoadUpdate(adEnv, task16, 16000)
check("O16 kører med det den fik", task16.finishedCalls, 1)
check("O16 log", lastLog("ingen mere last") ~= nil, true)
v16.trailers.level = 0
local choice16b = ADOutbound.choosePickup(adEnv, v16)
check("O16 næste tur tager en anden kilde", choice16b ~= nil and choice16b.markerIndex ~= first16, true)
ADOutbound.resetEmptySources() -- (senere tests forventer alle kilder ledige)
-- O14 (I6): rutetjek kun for salgssteder over tærsklen, bedste først, stop ved første nåelige -- O14 (I6): rutetjek kun for salgssteder over tærsklen, bedste først, stop ved første nåelige
local routeChecks = 0 local routeChecks = 0
local originalGetPathTo = adEnv.ADGraphManager.getPathTo local originalGetPathTo = adEnv.ADGraphManager.getPathTo
@@ -613,5 +633,6 @@ do
ADLoadSwap.findDumpMarker = dumpBefore ADLoadSwap.findDumpMarker = dumpBefore
end end
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)
+30
View File
@@ -107,5 +107,35 @@ do
unreachable[903], unreachable[904] = nil, nil unreachable[903], unreachable[904] = nil, nil
end end
-- RC: rutesvar gemmes 30 s pr. mål og 10 m-område (holdende vogne spurgte hvert 5. s -> hak hvert 5. s)
do
local pathCalls = 0
local realGetPathTo = adEnv.ADGraphManager.getPathTo
adEnv.ADGraphManager.getPathTo = function(self, vehicle, id) pathCalls = pathCalls + 1; return realGetPathTo(self, vehicle, id) end
local clock = 0
netGetTime = function() return clock end
local positions = {[500] = {100, 100}}
getWorldTranslation = function(node) local p = positions[node]; return p[1], 0, p[2] end
local parked = {rootNode = 500, getClosestWayPoint = function() return 901, 1 end}
ADWaitPool.hasRouteTo(adEnv, parked, 10)
ADWaitPool.hasRouteTo(adEnv, parked, 10)
check("RC samme svar genbruges", pathCalls, 1)
positions[500] = {104, 103}
ADWaitPool.hasRouteTo(adEnv, parked, 10)
check("RC samme 10 m-område genbruges", pathCalls, 1)
positions[500] = {160, 100}
ADWaitPool.hasRouteTo(adEnv, parked, 10)
check("RC nyt sted -> nyt svar", pathCalls, 2)
clock = 31000
ADWaitPool.hasRouteTo(adEnv, parked, 10)
check("RC efter 30 s -> nyt svar", pathCalls, 3)
local first = ADWaitPool.hasRouteTo(adEnv, parked, 11)
check("RC andet mål -> nyt svar", pathCalls, 4)
check("RC samme svar igen", ADWaitPool.hasRouteTo(adEnv, parked, 11), first)
check("RC uden nyt kald", pathCalls, 4)
adEnv.ADGraphManager.getPathTo = realGetPathTo
netGetTime = nil
end
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)