perf(marker): hak hvert sekund — ingen kopier når intet ændrer sig, passive marker i tiendedele, scan hvert 2. s, cachet markliste/markører, tick-tidsmåling i loggen (v1.27.1.0)
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.
This commit is contained in:
@@ -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)
|
||||
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
|
||||
|
||||
@@ -223,17 +223,28 @@ 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
|
||||
end
|
||||
return copy
|
||||
end
|
||||
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)
|
||||
return field.stage == "ready" and (field.plan.mode == "auto" or field.startRequested == true)
|
||||
|
||||
@@ -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,7 +319,10 @@ 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)
|
||||
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
|
||||
@@ -318,10 +330,14 @@ function ADFields.scanNext(adEnv, farmId)
|
||||
if not ids[id] then ADFields.cache[id] = nil end
|
||||
end
|
||||
ADFields.order = owned
|
||||
ADFields.listCache = nil
|
||||
end
|
||||
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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<modDesc descVersion="107">
|
||||
<author>masterdraco</author>
|
||||
<version>1.27.0.0</version>
|
||||
<version>1.27.1.0</version>
|
||||
<title>
|
||||
<en>AD Smart Pickup</en>
|
||||
</title>
|
||||
|
||||
@@ -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)*
|
||||
|
||||
|
||||
@@ -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".
|
||||
|
||||
+1
-1
@@ -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)*
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user