From c34a472b1c30a43c44bbb61bd7638f1d100cabcd Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sat, 26 Sep 2026 02:03:31 +0200 Subject: [PATCH] =?UTF-8?q?perf(marker):=20hak=20hvert=20sekund=20?= =?UTF-8?q?=E2=80=94=20ingen=20kopier=20n=C3=A5r=20intet=20=C3=A6ndrer=20s?= =?UTF-8?q?ig,=20passive=20marker=20i=20tiendedele,=20scan=20hvert=202.=20?= =?UTF-8?q?s,=20cachet=20markliste/mark=C3=B8rer,=20tick-tidsm=C3=A5ling?= =?UTF-8?q?=20i=20loggen=20(v1.27.1.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In-game 26/9: lille hak ca. hvert sekund. Markarbejdets tick kopierede alle markers data hvert sekund (affald -> GC-pause), hentede alle kortets marker og alle AD-markører. --- FS25_ADSmartPickup/adFieldJobs.lua | 53 ++++++++++++++++++++++++----- FS25_ADSmartPickup/adFieldModel.lua | 15 ++++++-- FS25_ADSmartPickup/adFields.lua | 35 ++++++++++++++----- FS25_ADSmartPickup/modDesc.xml | 2 +- docs/BRUGERMANUAL.md | 2 +- docs/TESTPLAN-markarbejde.md | 4 +-- docs/USER_MANUAL.md | 2 +- tests/test_adFieldModel.lua | 12 +++++++ 8 files changed, 101 insertions(+), 24 deletions(-) diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua index a5dea01..04dd800 100644 --- a/FS25_ADSmartPickup/adFieldJobs.lua +++ b/FS25_ADSmartPickup/adFieldJobs.lua @@ -7,6 +7,12 @@ ADFieldJobs = {} ADFieldJobs.LOG_PREFIX = "ADSmartPickup: " ADFieldJobs.TICK_MS = 1000 +-- én mark scannes hvert andet sekund; passive marker (manuelle, uden mål) vurderes i ti portioner +ADFieldJobs.SCAN_EVERY_TICKS = 2 +ADFieldJobs.PASSIVE_SLICES = 10 +-- tidsmåling: tager et tick mere end dette, skrives det i loggen (højst én gang pr. minut) +ADFieldJobs.SLOW_TICK_MS = 3 +ADFieldJobs.tickCount = 0 -- Courseplay kan efterlade hjørner på skæve marker: efter så mange færdige ture pr. sæt går marken videre. ADFieldJobs.MAX_PASSES = 2 -- fleetMode: "all" = alle ledige maskiner (standard, "vælg og glem"), "ticked" = kun med flueben @@ -226,12 +232,13 @@ end -- Seneste beslutning pr. mark (GUI viser pleje-behov og grunde). ADFieldJobs.lastDecision = {} -local function stepField(adEnv, info) - local field = ADFieldModel.onReadiness(ADFieldJobs.getField(info.id), info.readyRatio or 0) - ADFieldJobs.setField(field) +local function stepField(adEnv, info, env) + local current = ADFieldJobs.getField(info.id) + local field = ADFieldModel.onReadiness(current, info.readyRatio or 0) + if field ~= current then ADFieldJobs.setField(field) end local assignments = ADFieldJobs.assignmentsFor(info.id) for _, done in ipairs(finishedFor(info.id)) do table.insert(assignments, done) end - local decision = ADFieldJobs.decide(field, info, assignments, ADFieldJobs.now, ADFieldJobs.environment()) + local decision = ADFieldJobs.decide(field, info, assignments, ADFieldJobs.now, env) ADFieldJobs.lastDecision[info.id] = decision if decision.action == "start" then local started = ADFieldModel.start(field, info.kind, info.needs, info.fruitName) @@ -262,14 +269,32 @@ local function stepField(adEnv, info) end end +local function clockMs() + local ok, seconds = pcall(getTimeSec) + return ok and type(seconds) == "number" and seconds * 1000 or nil +end + +-- Et langsomt tick (> SLOW_TICK_MS) skrives i loggen med fordeling, højst én gang pr. minut. +local function reportSlow(total, scan, follow, steps, stepped) + if total == nil or total <= ADFieldJobs.SLOW_TICK_MS then return end + if ADFieldJobs.lastSlowReport ~= nil and ADFieldJobs.now - ADFieldJobs.lastSlowReport < 60000 then return end + ADFieldJobs.lastSlowReport = ADFieldJobs.now + Logging.info(ADFieldJobs.LOG_PREFIX .. "markarbejde-tick %.1f ms (scan %.1f, sæt %.1f, marker %.1f for %d marker)", + total, scan or 0, follow or 0, steps or 0, stepped) +end + function ADFieldJobs.tick(adEnv, dt) ADFieldJobs.now = ADFieldJobs.now + dt ADFieldJobs.timer = ADFieldJobs.timer + dt if ADFieldJobs.timer < ADFieldJobs.TICK_MS then return end ADFieldJobs.timer = 0 + ADFieldJobs.tickCount = ADFieldJobs.tickCount + 1 if not ADFieldJobs.loaded then ADFieldJobs.load() end - local farmId = g_currentMission:getFarmId() - ADFields.scanNext(adEnv, farmId) + local t0 = clockMs() + if ADFieldJobs.tickCount % ADFieldJobs.SCAN_EVERY_TICKS == 0 then + ADFields.scanNext(adEnv, g_currentMission:getFarmId(), ADFieldJobs.now) + end + local t1 = clockMs() for _, assignment in pairs(ADFieldJobs.state.assignments) do local ok, err = pcall(ADFieldWork.follow, adEnv, assignment) if not ok then @@ -277,8 +302,18 @@ function ADFieldJobs.tick(adEnv, dt) ADFieldWork.release(assignment) end end - for _, info in ipairs(ADFields.list()) do - local ok, err = pcall(stepField, adEnv, info) - if not ok then Logging.warning(ADFieldJobs.LOG_PREFIX .. "F%s fejl: %s", tostring(info.id), tostring(err)) end + local t2 = clockMs() + local env = ADFieldJobs.environment() + local slice = ADFieldJobs.tickCount % ADFieldJobs.PASSIVE_SLICES + local stepped = 0 + for index, info in ipairs(ADFields.list()) do + -- passive marker (der sker intet uden brugeren) vurderes kun i deres tiendedel af sekunderne + if not ADFieldModel.isPassive(ADFieldJobs.getField(info.id)) or index % ADFieldJobs.PASSIVE_SLICES == slice then + stepped = stepped + 1 + local ok, err = pcall(stepField, adEnv, info, env) + if not ok then Logging.warning(ADFieldJobs.LOG_PREFIX .. "F%s fejl: %s", tostring(info.id), tostring(err)) end + end end + local t3 = clockMs() + if t0 ~= nil and t3 ~= nil then reportSlow(t3 - t0, t1 - t0, t2 - t1, t3 - t2, stepped) end end diff --git a/FS25_ADSmartPickup/adFieldModel.lua b/FS25_ADSmartPickup/adFieldModel.lua index f40666d..b75e09b 100644 --- a/FS25_ADSmartPickup/adFieldModel.lua +++ b/FS25_ADSmartPickup/adFieldModel.lua @@ -223,16 +223,27 @@ function ADFieldModel.lanePositions(n) end -- Venter/færdig -> klar ved fuld vækst; klar -> venter hvis afgrøden blev taget uden os. +-- Uændret -> samme tabel (kaldes for hver mark hvert sekund; nye kopier giver affald og GC-hak). function ADFieldModel.onReadiness(field, ratio) - local copy = copyField(field) if (field.stage == "idle" or field.stage == "done") and ratio >= ADFieldModel.READY_RATIO then + local copy = copyField(field) copy.stage = "ready" + return copy elseif field.stage == "ready" and ratio < ADFieldModel.CUT_RATIO and not field.startRequested then -- (en Start nu-forespørgsel holder marken klar: bare marker har klar-andel 0) + local copy = copyField(field) copy.stage = "idle" copy.startRequested = false + return copy end - return copy + return field +end + +-- Passiv mark: manuel, uden mål, venter/færdig, ingen Start nu — der sker intet uden brugeren. +function ADFieldModel.isPassive(field) + local plan = field.plan + return plan.mode ~= "auto" and (plan.targetFruit == nil or plan.targetFruit == "") and not field.startRequested + and (field.stage == "idle" or field.stage == "done") end function ADFieldModel.shouldStart(field) diff --git a/FS25_ADSmartPickup/adFields.lua b/FS25_ADSmartPickup/adFields.lua index c1588c2..80a9873 100644 --- a/FS25_ADSmartPickup/adFields.lua +++ b/FS25_ADSmartPickup/adFields.lua @@ -11,6 +11,11 @@ ADFields.cache = {} ADFields.order = {} ADFields.scanIndex = 0 ADFields.mowable = nil +-- markliste og F-markører ændrer sig sjældent: slås op igen efter så lang tid (ikke hvert scan) +ADFields.OWNED_REFRESH_MS = 30000 +ADFields.MARKER_REFRESH_MS = 30000 +ADFields.ownedAt = nil +ADFields.now = 0 function ADFields.parseMarkerName(name) if type(name) ~= "string" then return nil end @@ -287,11 +292,15 @@ local function scanField(adEnv, field, fieldId) info.kind = "bare" end readSoil(info) - if info.x ~= nil then + local previous = ADFields.cache[fieldId] + if previous ~= nil and previous.markerCheckedAt ~= nil and ADFields.now - previous.markerCheckedAt < ADFields.MARKER_REFRESH_MS then + info.markerId, info.markerName, info.markerDistance, info.markerCheckedAt = previous.markerId, previous.markerName, previous.markerDistance, previous.markerCheckedAt + elseif info.x ~= nil then local marker, distance = findMarker(adEnv, fieldId, info.x, info.z) info.markerId = marker ~= nil and marker.id or nil info.markerName = marker ~= nil and marker.name or nil info.markerDistance = distance + info.markerCheckedAt = ADFields.now end return info end @@ -310,18 +319,25 @@ local function ownedFields(farmId) end -- Scanner næste mark (round-robin) og returnerer alle kendte. Kaldes hvert sekund af job-løkken. -function ADFields.scanNext(adEnv, farmId) - local owned = ownedFields(farmId) - local ids = {} - for _, entry in ipairs(owned) do ids[entry.id] = true end - for id in pairs(ADFields.cache) do - if not ids[id] then ADFields.cache[id] = nil end +function ADFields.scanNext(adEnv, farmId, now) + ADFields.now = now or ADFields.now + if ADFields.ownedAt == nil or ADFields.now - ADFields.ownedAt >= ADFields.OWNED_REFRESH_MS then + ADFields.ownedAt = ADFields.now + local owned = ownedFields(farmId) + local ids = {} + for _, entry in ipairs(owned) do ids[entry.id] = true end + for id in pairs(ADFields.cache) do + if not ids[id] then ADFields.cache[id] = nil end + end + ADFields.order = owned + ADFields.listCache = nil end - ADFields.order = owned + local owned = ADFields.order if #owned == 0 then return end ADFields.scanIndex = ADFields.scanIndex % #owned + 1 local entry = owned[ADFields.scanIndex] local ok, info = pcall(scanField, adEnv, entry.field, entry.id) + ADFields.listCache = nil if ok then ADFields.cache[entry.id] = info else @@ -329,12 +345,15 @@ function ADFields.scanNext(adEnv, farmId) end end +-- Samme liste indtil næste scanning (list() kaldes hvert sekund; ingen ny tabel hver gang). function ADFields.list() + if ADFields.listCache ~= nil then return ADFields.listCache end local infos = {} for _, entry in ipairs(ADFields.order) do local info = ADFields.cache[entry.id] if info ~= nil then table.insert(infos, info) end end + ADFields.listCache = infos return infos end diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index b34400a..a7fc787 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.27.0.0 + 1.27.1.0 <en>AD Smart Pickup</en> diff --git a/docs/BRUGERMANUAL.md b/docs/BRUGERMANUAL.md index ff18393..a267171 100644 --- a/docs/BRUGERMANUAL.md +++ b/docs/BRUGERMANUAL.md @@ -1,6 +1,6 @@ # AD Smart Pickup — brugermanual -Version 1.27.0 · Farming Simulator 25 · tillæg til AutoDrive +Version 1.27.1 · 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 15837fc..5134a62 100644 --- a/docs/TESTPLAN-markarbejde.md +++ b/docs/TESTPLAN-markarbejde.md @@ -1,4 +1,4 @@ -# Testplan — Markarbejde (fanen Marker), version 1.27.0.0 +# Testplan — Markarbejde (fanen Marker), version 1.27.1.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.0.0) FS25_ADSmartPickup`. +- [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.27.1.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 4286ca4..4489692 100644 --- a/docs/USER_MANUAL.md +++ b/docs/USER_MANUAL.md @@ -1,6 +1,6 @@ # AD Smart Pickup — user manual -Version 1.27.0 · Farming Simulator 25 · add-on for AutoDrive +Version 1.27.1 · Farming Simulator 25 · add-on for AutoDrive *Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)* diff --git a/tests/test_adFieldModel.lua b/tests/test_adFieldModel.lua index f458975..e1b7a68 100644 --- a/tests/test_adFieldModel.lua +++ b/tests/test_adFieldModel.lua @@ -289,5 +289,17 @@ check("M22 ballesamling ikke vejrfølsom", M.isWeatherSensitive("baleCollecting" check("M22 gemmes", M.fromRecord(M.toRecord(M.with(M.newField(4), {plan = {straw = "bale"}}))).plan.straw, "bale") check("M22 ukendt -> hakkes", M.fromRecord({id = 5, straw = "x"}).plan.straw, "chop") +-- M23: ingen ny tabel når vækst-reglen ikke ændrer noget (undgår affald hvert sekund -> GC-hak) +local calm = M.newField(95) +check("M23 uændret -> samme tabel", M.onReadiness(calm, 0.2) == calm, true) +check("M23 ændret -> ny tabel", M.onReadiness(calm, 0.95) ~= calm, true) +-- M24: passive marker kan springes over (manuel, uden mål, venter, ingen forespørgsel) +check("M24 manuel venter = passiv", M.isPassive(calm), true) +check("M24 auto = ikke passiv", M.isPassive(M.with(calm, {plan = {mode = "auto"}})), false) +check("M24 mål = ikke passiv", M.isPassive(M.setTarget(calm, "WHEAT")), false) +check("M24 klar = ikke passiv", M.isPassive(M.with(calm, {stage = "ready"})), false) +check("M24 i gang = ikke passiv", M.isPassive(started), false) +check("M24 Start nu = ikke passiv", M.isPassive(M.with(calm, {startRequested = true})), false) + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)