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:
2026-09-26 02:03:31 +02:00
parent 7a073ae047
commit c34a472b1c
8 changed files with 101 additions and 24 deletions
+44 -9
View File
@@ -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
+13 -2
View File
@@ -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)
+27 -8
View File
@@ -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
+1 -1
View File
@@ -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>