From 0fca6e8a9a750afddfa5616a0ed7010fba9c7b5b Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sat, 26 Sep 2026 11:07:16 +0200 Subject: [PATCH] =?UTF-8?q?perf+fix:=20rutetjek=20gemmes=2030=20s=20(hak?= =?UTF-8?q?=20hvert=205.=20s);=20kilde=20der=20gav=20lidt=20hviler=203=20m?= =?UTF-8?q?in=20(=C3=A6g-vognen=20til=20samme=20h=C3=B8nsehus=203x)=20(v1.?= =?UTF-8?q?27.3.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- FS25_ADSmartPickup/adOutbound.lua | 15 ++++++- FS25_ADSmartPickup/adWaitPool.lua | 73 ++++++++++++++++++++++++++++--- FS25_ADSmartPickup/modDesc.xml | 2 +- docs/BRUGERMANUAL.md | 2 +- docs/TESTPLAN-markarbejde.md | 4 +- docs/USER_MANUAL.md | 2 +- tests/test_adOutbound.lua | 21 +++++++++ tests/test_adWaitPool.lua | 30 +++++++++++++ 8 files changed, 138 insertions(+), 11 deletions(-) diff --git a/FS25_ADSmartPickup/adOutbound.lua b/FS25_ADSmartPickup/adOutbound.lua index 456bbea..baf1232 100644 --- a/FS25_ADSmartPickup/adOutbound.lua +++ b/FS25_ADSmartPickup/adOutbound.lua @@ -25,6 +25,9 @@ ADOutbound.NOTIFICATION_MS = 8000 -- spawnet, forkert farm, ...): kilden udelukkes for varen i EMPTY_SOURCE_TTL_MS og turen afsluttes. ADOutbound.EMPTY_SOURCE_MS = 30000 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 -- 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. @@ -42,6 +45,11 @@ local warned = setmetatable({}, {__mode = "k"}) local emptyUntil = {} local isInstalled = false +-- Glem alle "tom/hvilende kilde"-markeringer (tests; og når runs genindlæses). +function ADOutbound.resetEmptySources() + emptyUntil = {} +end + local function now() if g_currentMission ~= nil and type(g_currentMission.time) == "number" then return g_currentMission.time @@ -753,7 +761,12 @@ function ADOutbound.beforeLoadUpdate(adEnv, task, dt) return true end 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 task:finished() return true diff --git a/FS25_ADSmartPickup/adWaitPool.lua b/FS25_ADSmartPickup/adWaitPool.lua index 31af1dd..02f5691 100644 --- a/FS25_ADSmartPickup/adWaitPool.lua +++ b/FS25_ADSmartPickup/adWaitPool.lua @@ -124,11 +124,42 @@ local function graphReaches(adEnv, vehicle, targetId) return false end -function ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId) - local graph = adEnv.ADGraphManager - if graph.getPathTo == nil then - return true +-- Rutesvar gemmes ROUTE_CACHE_MS pr. mål og 10 m-område: holdende vogne spørger hvert 5. s for hver +-- kandidat, og AD's stifinding (+ reserve-søgningen) gennem hele vejnettet gav et hak hvert 5. s +-- (in-game 26/9). Vejnettet ændrer sig ikke så hurtigt. +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 + 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 return true end @@ -142,13 +173,45 @@ function ADWaitPool.hasRouteTo(adEnv, vehicle, wayPointId) return false 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) local graph = adEnv.ADGraphManager if graph.getPathTo == nil or graph.pathFromTo == nil then return true 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) + if key ~= nil then remember(key, result, now) end + return result end -- Står AD midt i en (blokeret) af-/pålæsning, stoppes den pænt først — ellers kører diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 0a3c87a..1ca7da9 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.27.2.0 + 1.27.3.0 <en>AD Smart Pickup</en> diff --git a/docs/BRUGERMANUAL.md b/docs/BRUGERMANUAL.md index f71b401..7bc8e42 100644 --- a/docs/BRUGERMANUAL.md +++ b/docs/BRUGERMANUAL.md @@ -1,6 +1,6 @@ # 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)* diff --git a/docs/TESTPLAN-markarbejde.md b/docs/TESTPLAN-markarbejde.md index 0e4fb43..95f87af 100644 --- a/docs/TESTPLAN-markarbejde.md +++ b/docs/TESTPLAN-markarbejde.md @@ -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. 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.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 …" 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". diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md index 0639f47..1d65936 100644 --- a/docs/USER_MANUAL.md +++ b/docs/USER_MANUAL.md @@ -1,6 +1,6 @@ # 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)* diff --git a/tests/test_adOutbound.lua b/tests/test_adOutbound.lua index 89fbec7..6ee26c8 100644 --- a/tests/test_adOutbound.lua +++ b/tests/test_adOutbound.lua @@ -345,6 +345,26 @@ check("O13 WARN", lastLog("intet at læsse") ~= nil, true) local choice13b = ADOutbound.choosePickup(adEnv, v13) 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 local routeChecks = 0 local originalGetPathTo = adEnv.ADGraphManager.getPathTo @@ -613,5 +633,6 @@ do ADLoadSwap.findDumpMarker = dumpBefore end + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1) diff --git a/tests/test_adWaitPool.lua b/tests/test_adWaitPool.lua index 541d731..bb0e5a8 100644 --- a/tests/test_adWaitPool.lua +++ b/tests/test_adWaitPool.lua @@ -107,5 +107,35 @@ do unreachable[903], unreachable[904] = nil, nil 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")) os.exit(failures == 0 and 0 or 1)