perf: hak-audit — spredte timere, fælles cache (markør->station, salgssteder, identify, Wait-punkter, lagre, positioner), bygningsliste 10 s, profiler, simulering (v1.28.0.0)
Audit (2 reviewere) + simulering bench/bench_hak.lua: værste frame 124,7 ms -> 0,9 ms. - Holdende vognes genplanlægning (5 s) spredes tilfældigt over hele intervallet (før: alle i samme frame). - ADCache: korttids-cache m. spredt udløb (+0-50 %), kun aktiv i spillet; stationsopslag 30 s. - Bygningsliste 10 s (før 1 s: bygninger x markører hvert sekund). Markarbejde-tick forskudt 0,5 s fra menuen. - ADProfiler: logger top-6 langsomste indgange pr. minut ved > 2 ms.
This commit is contained in:
@@ -10,7 +10,8 @@ ADBuildings.LOG_PREFIX = "ADSmartPickup: "
|
|||||||
ADBuildings.MAX_MARKER_DISTANCE = 40
|
ADBuildings.MAX_MARKER_DISTANCE = 40
|
||||||
-- en separat drop-markør (≠ bygningens læssemarkør) må højst stå så langt fra aflæsningstriggeren
|
-- en separat drop-markør (≠ bygningens læssemarkør) må højst stå så langt fra aflæsningstriggeren
|
||||||
ADBuildings.MAX_DROP_DISTANCE = 25
|
ADBuildings.MAX_DROP_DISTANCE = 25
|
||||||
ADBuildings.CACHE_MS = 1000
|
-- bygninger ændrer sig sjældent; genopbygningen (bygninger x markører) er dyr — 1 s gav et hak hvert sekund
|
||||||
|
ADBuildings.CACHE_MS = 10000
|
||||||
|
|
||||||
local cache = {list = nil, farmId = nil, time = -math.huge}
|
local cache = {list = nil, farmId = nil, time = -math.huge}
|
||||||
|
|
||||||
@@ -363,6 +364,7 @@ local function build(adEnv, farmId)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ADBuildings.invalidate()
|
function ADBuildings.invalidate()
|
||||||
|
if ADCache ~= nil then ADCache.clear() end
|
||||||
cache.list, cache.time = nil, -math.huge
|
cache.list, cache.time = nil, -math.huge
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
-- AD Cache
|
||||||
|
-- Fælles korttids-cache for dyre opslag (markør -> station, salgssteder, identify, Wait-punkter, lagre).
|
||||||
|
-- Holdende vogne genplanlægger hvert 5. s og slog de samme ting op igen og igen (markører x stationer x
|
||||||
|
-- triggere) -> hak (in-game 26/9). Verden ændrer sig ikke så hurtigt. Uden spillets tid (tests) caches
|
||||||
|
-- intet, så adfærden er som før. "Intet fundet" gemmes også.
|
||||||
|
|
||||||
|
ADCache = {}
|
||||||
|
|
||||||
|
ADCache.buckets = {}
|
||||||
|
-- tændes af ADSmartPickup når hooksene er installeret (i spillet); tests kører uden cache
|
||||||
|
ADCache.enabled = false
|
||||||
|
local NOTHING = {}
|
||||||
|
|
||||||
|
-- Til moduler der indlæses uden adCache (tests): kald fn direkte.
|
||||||
|
function ADCacheGet(bucket, key, ttlMs, fn)
|
||||||
|
if ADCache == nil or not ADCache.enabled then return fn() end
|
||||||
|
return ADCache.get(bucket, key, ttlMs, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function now()
|
||||||
|
local mission = g_currentMission
|
||||||
|
if mission ~= nil and type(mission.time) == "number" then return mission.time end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Resultatet af fn() for (bucket, key), genbrugt i ttlMs.
|
||||||
|
function ADCache.get(bucket, key, ttlMs, fn)
|
||||||
|
local time = now()
|
||||||
|
if not ADCache.enabled or time == nil or key == nil then return fn() end
|
||||||
|
local store = ADCache.buckets[bucket]
|
||||||
|
if store == nil then
|
||||||
|
store = {size = 0, entries = {}}
|
||||||
|
ADCache.buckets[bucket] = store
|
||||||
|
end
|
||||||
|
local entry = store.entries[key]
|
||||||
|
if entry ~= nil and time - entry.at < ttlMs then
|
||||||
|
if entry.value == NOTHING then return nil end
|
||||||
|
return entry.value
|
||||||
|
end
|
||||||
|
local value = fn()
|
||||||
|
if entry == nil then
|
||||||
|
store.size = store.size + 1
|
||||||
|
if store.size > 5000 then store.entries, store.size = {}, 1 end
|
||||||
|
end
|
||||||
|
-- spredt udløb (levetid +0-50 %): poster der blev lavet i samme kald, udløber ikke i samme frame
|
||||||
|
-- (simulering 26/9: 600 markør-opslag udløb samtidig -> hak hvert 10. s)
|
||||||
|
store.entries[key] = {value = value == nil and NOTHING or value, at = time + math.random() * ttlMs * 0.5}
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADCache.clear(bucket)
|
||||||
|
if bucket == nil then ADCache.buckets = {} else ADCache.buckets[bucket] = nil end
|
||||||
|
end
|
||||||
@@ -20,7 +20,8 @@ ADFieldJobs.state = {fields = {}, fleet = {}, assignments = {}, fleetMode = "all
|
|||||||
-- efter genindlæsning hentes slå-/rivekurset fra maskinen, som CP selv gemmer kurset på)
|
-- efter genindlæsning hentes slå-/rivekurset fra maskinen, som CP selv gemmer kurset på)
|
||||||
ADFieldJobs.courses = {}
|
ADFieldJobs.courses = {}
|
||||||
ADFieldJobs.finished = {}
|
ADFieldJobs.finished = {}
|
||||||
ADFieldJobs.timer = 0
|
-- starter halvt forskudt, så markarbejdet og menuens opdatering (begge hvert sekund) ikke rammer samme frame
|
||||||
|
ADFieldJobs.timer = 500
|
||||||
ADFieldJobs.now = 0
|
ADFieldJobs.now = 0
|
||||||
ADFieldJobs.loaded = false
|
ADFieldJobs.loaded = false
|
||||||
|
|
||||||
|
|||||||
@@ -166,7 +166,8 @@ end
|
|||||||
-- Kaldes fra Wait-logikken hvert tick; redirect(task, id) og release(vehicle) er dens egne.
|
-- Kaldes fra Wait-logikken hvert tick; redirect(task, id) og release(vehicle) er dens egne.
|
||||||
-- true = traktoren er sendt til en silo (Wait-punktet er frigivet).
|
-- true = traktoren er sendt til en silo (Wait-punktet er frigivet).
|
||||||
function ADLoadSwap.tryFromWait(adEnv, task, wait, dt, redirect, release, isReachable)
|
function ADLoadSwap.tryFromWait(adEnv, task, wait, dt, redirect, release, isReachable)
|
||||||
wait.swapTimerMs = (wait.swapTimerMs or 0) + (dt or 0)
|
-- tilfældig start (0-2 s): ventende vogne tjekker ikke alle i samme frame
|
||||||
|
wait.swapTimerMs = (wait.swapTimerMs or math.random(0, ADLoadSwap.CHECK_INTERVAL_MS)) + (dt or 0)
|
||||||
if wait.swapTimerMs < ADLoadSwap.CHECK_INTERVAL_MS then
|
if wait.swapTimerMs < ADLoadSwap.CHECK_INTERVAL_MS then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ ADOutbound = {}
|
|||||||
ADOutbound.LOG_PREFIX = "ADSmartPickup: "
|
ADOutbound.LOG_PREFIX = "ADSmartPickup: "
|
||||||
-- Holdende traktorer spørger planlæggeren så tit (opslag over alle markører).
|
-- Holdende traktorer spørger planlæggeren så tit (opslag over alle markører).
|
||||||
ADOutbound.CHECK_INTERVAL_MS = 5000
|
ADOutbound.CHECK_INTERVAL_MS = 5000
|
||||||
|
-- spred over HELE intervallet, så genplanlægningerne (og cache-genberegninger) fordeles jævnt
|
||||||
|
ADOutbound.CHECK_JITTER_MS = 5000
|
||||||
-- "Kør uanset mængde": stiger vognens fyldning ikke så længe ved kilden, køres der med det der er.
|
-- "Kør uanset mængde": stiger vognens fyldning ikke så længe ved kilden, køres der med det der er.
|
||||||
ADOutbound.NO_PROGRESS_MS = 15000
|
ADOutbound.NO_PROGRESS_MS = 15000
|
||||||
-- Objektlager der ikke leverer en palle inden så længe regnes som fastlåst (kendt spilfejl).
|
-- Objektlager der ikke leverer en palle inden så længe regnes som fastlåst (kendt spilfejl).
|
||||||
@@ -498,7 +500,8 @@ local function startHold(adEnv, vehicle, run, reasons, sourcesById)
|
|||||||
warnOnce(adEnv, vehicle, "noWait", string.format("'%s' har intet ledigt Wait-punkt med vejnet (run '%s'); holder ved kilden %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(run.name), tostring(marker.name)))
|
warnOnce(adEnv, vehicle, "noWait", string.format("'%s' har intet ledigt Wait-punkt med vejnet (run '%s'); holder ved kilden %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(run.name), tostring(marker.name)))
|
||||||
end
|
end
|
||||||
local previous = holds[vehicle]
|
local previous = holds[vehicle]
|
||||||
holds[vehicle] = {markerId = marker.id, markerName = tostring(marker.name), reasons = reasons, timerMs = 0}
|
-- tilfældig start (0-2 s) så holdende vogne ikke genplanlægger i samme frame (hak hvert 5. s, 26/9)
|
||||||
|
holds[vehicle] = {markerId = marker.id, markerName = tostring(marker.name), reasons = reasons, timerMs = math.random(0, ADOutbound.CHECK_JITTER_MS)}
|
||||||
local message = string.format("'%s' holder ved %s: %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(marker.name), table.concat(reasons, "; "))
|
local message = string.format("'%s' holder ved %s: %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(marker.name), table.concat(reasons, "; "))
|
||||||
if previous == nil or previous.lastLogged ~= message then
|
if previous == nil or previous.lastLogged ~= message then
|
||||||
Logging.info(ADOutbound.LOG_PREFIX .. "%s", message)
|
Logging.info(ADOutbound.LOG_PREFIX .. "%s", message)
|
||||||
|
|||||||
@@ -10,6 +10,13 @@
|
|||||||
|
|
||||||
ADSmartPickup = {}
|
ADSmartPickup = {}
|
||||||
|
|
||||||
|
-- ADCache-opslag der også virker uden adCache (tests)
|
||||||
|
local function cacheGet(bucket, key, ttlMs, fn)
|
||||||
|
if ADCacheGet == nil then return fn() end
|
||||||
|
return ADCacheGet(bucket, key, ttlMs, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
ADSmartPickup.dir = g_currentModDirectory
|
ADSmartPickup.dir = g_currentModDirectory
|
||||||
if ADSmartPickup.dir ~= nil and source ~= nil then
|
if ADSmartPickup.dir ~= nil and source ~= nil then
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupFrame.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupFrame.lua")
|
||||||
@@ -216,14 +223,21 @@ local function getUnloadStationAtMarker(adEnv, wayPoint, farmId)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Station ved et AutoDrive-waypoint (bruges af runs-controlleren og læs-byttet); nil uden station.
|
-- Station ved et AutoDrive-waypoint (bruges af runs-controlleren og læs-byttet); nil uden station.
|
||||||
|
-- Markør -> station er stabil (stationer og markører flytter sig sjældent): caches 10 s (ADCache).
|
||||||
|
ADSmartPickup.STATION_CACHE_MS = 30000
|
||||||
|
|
||||||
function ADSmartPickup.getLoadStationAtWayPointId(adEnv, wayPointId, farmId)
|
function ADSmartPickup.getLoadStationAtWayPointId(adEnv, wayPointId, farmId)
|
||||||
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
return cacheGet("loadStation", tostring(wayPointId) .. ":" .. tostring(farmId), ADSmartPickup.STATION_CACHE_MS, function()
|
||||||
return wayPoint ~= nil and getLoadStationAtMarker(adEnv, wayPoint, farmId) or nil
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
||||||
|
return wayPoint ~= nil and getLoadStationAtMarker(adEnv, wayPoint, farmId) or nil
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ADSmartPickup.getUnloadStationAtWayPointId(adEnv, wayPointId, farmId)
|
function ADSmartPickup.getUnloadStationAtWayPointId(adEnv, wayPointId, farmId)
|
||||||
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
return cacheGet("unloadStation", tostring(wayPointId) .. ":" .. tostring(farmId), ADSmartPickup.STATION_CACHE_MS, function()
|
||||||
return wayPoint ~= nil and getUnloadStationAtMarker(adEnv, wayPoint, farmId) or nil
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
||||||
|
return wayPoint ~= nil and getUnloadStationAtMarker(adEnv, wayPoint, farmId) or nil
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Løst opslag: nærmeste læssestation inden for maxDistance, uanset køreretning og "bag næsen".
|
-- Løst opslag: nærmeste læssestation inden for maxDistance, uanset køreretning og "bag næsen".
|
||||||
@@ -1194,6 +1208,7 @@ function ADSmartPickup:update(dt)
|
|||||||
installHook(adEnv)
|
installHook(adEnv)
|
||||||
isHooked = true
|
isHooked = true
|
||||||
if ADProfiler ~= nil then pcall(ADProfiler.install) end
|
if ADProfiler ~= nil then pcall(ADProfiler.install) end
|
||||||
|
if ADCache ~= nil then ADCache.enabled = true end
|
||||||
if ADRunsController ~= nil then
|
if ADRunsController ~= nil then
|
||||||
pcall(ADRunsController.load, adEnv)
|
pcall(ADRunsController.load, adEnv)
|
||||||
pcall(ADRunsController.installSaveHook)
|
pcall(ADRunsController.installSaveHook)
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
|
|
||||||
ADSources = {}
|
ADSources = {}
|
||||||
|
|
||||||
|
-- ADCache-opslag der også virker uden adCache (tests)
|
||||||
|
local function cacheGet(bucket, key, ttlMs, fn)
|
||||||
|
if ADCacheGet == nil then return fn() end
|
||||||
|
return ADCacheGet(bucket, key, ttlMs, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
ADSources.LOG_PREFIX = "ADSmartPickup: "
|
ADSources.LOG_PREFIX = "ADSmartPickup: "
|
||||||
-- Objektlager tættere end dette på markøren regnes som markørens (samme som triggere).
|
-- Objektlager tættere end dette på markøren regnes som markørens (samme som triggere).
|
||||||
ADSources.MAX_DISTANCE = 40
|
ADSources.MAX_DISTANCE = 40
|
||||||
@@ -136,6 +143,12 @@ end
|
|||||||
|
|
||||||
-- {kind = "factory"|"silo"|"objectStorage", placeable, station|nil, wayPointId, name} eller nil.
|
-- {kind = "factory"|"silo"|"objectStorage", placeable, station|nil, wayPointId, name} eller nil.
|
||||||
function ADSources.identify(adEnv, wayPointId, farmId)
|
function ADSources.identify(adEnv, wayPointId, farmId)
|
||||||
|
return cacheGet("identify", tostring(wayPointId) .. ":" .. tostring(farmId), 10000, function()
|
||||||
|
return ADSources.identifyUncached(adEnv, wayPointId, farmId)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADSources.identifyUncached(adEnv, wayPointId, farmId)
|
||||||
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
||||||
if wayPoint == nil then
|
if wayPoint == nil then
|
||||||
return nil
|
return nil
|
||||||
@@ -552,6 +565,10 @@ end
|
|||||||
|
|
||||||
-- Markører hvis aflæsningsstation er et rigtigt salgssted: { {marker, station} }.
|
-- Markører hvis aflæsningsstation er et rigtigt salgssted: { {marker, station} }.
|
||||||
function ADSources.getSellMarkers(adEnv, farmId)
|
function ADSources.getSellMarkers(adEnv, farmId)
|
||||||
|
return cacheGet("sellMarkers", tostring(farmId), 10000, function() return ADSources.findSellMarkers(adEnv, farmId) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADSources.findSellMarkers(adEnv, farmId)
|
||||||
local result = {}
|
local result = {}
|
||||||
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
||||||
local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId)
|
local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId)
|
||||||
@@ -999,6 +1016,10 @@ end
|
|||||||
-- { {name, kind = "factory"|"silo"|"objectStorage"|"husbandry", entries = {{fillType, name, title, level, capacity, isOutput, count}}} }
|
-- { {name, kind = "factory"|"silo"|"objectStorage"|"husbandry", entries = {{fillType, name, title, level, capacity, isOutput, count}}} }
|
||||||
-- kun gårdens bygninger med indhold; sorteret på slags, dernæst navn.
|
-- kun gårdens bygninger med indhold; sorteret på slags, dernæst navn.
|
||||||
function ADSources.getFarmInventory(adEnv, farmId)
|
function ADSources.getFarmInventory(adEnv, farmId)
|
||||||
|
return cacheGet("inventory", tostring(farmId), 2000, function() return ADSources.buildFarmInventory(adEnv, farmId) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADSources.buildFarmInventory(adEnv, farmId)
|
||||||
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||||||
local result = {}
|
local result = {}
|
||||||
for _, placeable in pairs(placeables) do
|
for _, placeable in pairs(placeables) do
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ end
|
|||||||
-- stående for evigt. Her genoptages ventningen for aktive traktorer i et forsynings-run hvis
|
-- stående for evigt. Her genoptages ventningen for aktive traktorer i et forsynings-run hvis
|
||||||
-- aflæsningsopgave peger på en Wait-markør: hjem = run'ets mål, og reservationen sættes igen.
|
-- aflæsningsopgave peger på en Wait-markør: hjem = run'ets mål, og reservationen sættes igen.
|
||||||
-- Kaldes fra ADSmartPickup:update (throttlet).
|
-- Kaldes fra ADSmartPickup:update (throttlet).
|
||||||
ADUnloadWait.ADOPT_INTERVAL_MS = 5000
|
ADUnloadWait.ADOPT_INTERVAL_MS = 5300
|
||||||
local adoptTimerMs = ADUnloadWait.ADOPT_INTERVAL_MS
|
local adoptTimerMs = ADUnloadWait.ADOPT_INTERVAL_MS
|
||||||
|
|
||||||
function ADUnloadWait.adoptOrphans(adEnv)
|
function ADUnloadWait.adoptOrphans(adEnv)
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
|
|
||||||
ADWaitPool = {}
|
ADWaitPool = {}
|
||||||
|
|
||||||
|
-- ADCache-opslag der også virker uden adCache (tests)
|
||||||
|
local function cacheGet(bucket, key, ttlMs, fn)
|
||||||
|
if ADCacheGet == nil then return fn() end
|
||||||
|
return ADCacheGet(bucket, key, ttlMs, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
ADWaitPool.LOG_PREFIX = "ADSmartPickup: "
|
ADWaitPool.LOG_PREFIX = "ADSmartPickup: "
|
||||||
-- sættes af den der installerer opgave-hooks (task.STATE_DRIVING); 2 er AD's værdi
|
-- sættes af den der installerer opgave-hooks (task.STATE_DRIVING); 2 er AD's værdi
|
||||||
ADWaitPool.STATE_DRIVING = 2
|
ADWaitPool.STATE_DRIVING = 2
|
||||||
@@ -14,6 +21,10 @@ ADWaitPool.claims = setmetatable({}, {__mode = "v"})
|
|||||||
|
|
||||||
-- poolGroup: run'ets Wait-mappe; tom/nil = alle Wait-mapper.
|
-- poolGroup: run'ets Wait-mappe; tom/nil = alle Wait-mapper.
|
||||||
function ADWaitPool.getCandidates(adEnv, poolGroup)
|
function ADWaitPool.getCandidates(adEnv, poolGroup)
|
||||||
|
return cacheGet("waitCandidates", tostring(poolGroup or ""), 10000, function() return ADWaitPool.findCandidates(adEnv, poolGroup) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADWaitPool.findCandidates(adEnv, poolGroup)
|
||||||
local candidates = {}
|
local candidates = {}
|
||||||
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
||||||
local isInPool = poolGroup == nil or poolGroup == "" or marker.group == poolGroup
|
local isInPool = poolGroup == nil or poolGroup == "" or marker.group == poolGroup
|
||||||
@@ -57,20 +68,31 @@ ADWaitPool.OCCUPIED_RADIUS = 6
|
|||||||
|
|
||||||
-- Positioner for ANDRE motoriserede køretøjer (ikke vognen selv eller dens redskaber) — til at se om et
|
-- Positioner for ANDRE motoriserede køretøjer (ikke vognen selv eller dens redskaber) — til at se om et
|
||||||
-- Wait-punkt fysisk er optaget, også uden reservation (fx lige efter en omplanlægning).
|
-- Wait-punkt fysisk er optaget, også uden reservation (fx lige efter en omplanlægning).
|
||||||
|
-- Alle motoriserede køretøjers positioner, delt mellem opslag i 0,5 s (ikke én runde pr. vogn pr. opslag).
|
||||||
|
local function allVehiclePositions()
|
||||||
|
return cacheGet("vehiclePositions", "all", 500, function()
|
||||||
|
local list = {}
|
||||||
|
local system = g_currentMission ~= nil and g_currentMission.vehicleSystem or nil
|
||||||
|
for _, other in pairs(system ~= nil and system.vehicles or {}) do
|
||||||
|
if other.spec_motorized ~= nil and other.rootNode ~= nil then
|
||||||
|
local ok, x, _, z = pcall(getWorldTranslation, other.rootNode)
|
||||||
|
if ok and x ~= nil then table.insert(list, {vehicle = other, x = x, z = z}) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return list
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
local function otherVehiclePositions(vehicle)
|
local function otherVehiclePositions(vehicle)
|
||||||
local positions = {}
|
local positions = {}
|
||||||
local system = g_currentMission ~= nil and g_currentMission.vehicleSystem or nil
|
|
||||||
local own = {}
|
local own = {}
|
||||||
if vehicle.getChildVehicles ~= nil then
|
if vehicle.getChildVehicles ~= nil then
|
||||||
local ok, children = pcall(vehicle.getChildVehicles, vehicle)
|
local ok, children = pcall(vehicle.getChildVehicles, vehicle)
|
||||||
for _, child in pairs(ok and children or {}) do own[child] = true end
|
for _, child in pairs(ok and children or {}) do own[child] = true end
|
||||||
end
|
end
|
||||||
own[vehicle] = true
|
own[vehicle] = true
|
||||||
for _, other in pairs(system ~= nil and system.vehicles or {}) do
|
for _, entry in ipairs(allVehiclePositions()) do
|
||||||
if not own[other] and other.spec_motorized ~= nil and other.rootNode ~= nil then
|
if not own[entry.vehicle] then table.insert(positions, entry) end
|
||||||
local ok, x, _, z = pcall(getWorldTranslation, other.rootNode)
|
|
||||||
if ok and x ~= nil then table.insert(positions, {x = x, z = z}) end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return positions
|
return positions
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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.3.0</version>
|
<version>1.28.0.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<multiplayer supported="true"/>
|
<multiplayer supported="true"/>
|
||||||
<l10n filenamePrefix="l10n/l10n"/>
|
<l10n filenamePrefix="l10n/l10n"/>
|
||||||
<extraSourceFiles>
|
<extraSourceFiles>
|
||||||
|
<sourceFile filename="adCache.lua"/>
|
||||||
<sourceFile filename="adSupplyPlanner.lua"/>
|
<sourceFile filename="adSupplyPlanner.lua"/>
|
||||||
<sourceFile filename="adRuns.lua"/>
|
<sourceFile filename="adRuns.lua"/>
|
||||||
<sourceFile filename="adRunsStorage.lua"/>
|
<sourceFile filename="adRunsStorage.lua"/>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Simuleringer
|
||||||
|
|
||||||
|
`bench_hak.lua` genskaber "hak hvert 5. sekund" uden spillet: falsk verden i brugerens proportioner
|
||||||
|
(13.550 waypoints, 600 markører, 120 stationer, 40 køretøjer, 15 holdende vogne) og modulernes rigtige kode.
|
||||||
|
|
||||||
|
luajit bench/bench_hak.lua FS25_ADSmartPickup # nuværende kode
|
||||||
|
git worktree add /tmp/old <commit>; luajit bench/bench_hak.lua /tmp/old/FS25_ADSmartPickup # sammenlign
|
||||||
|
|
||||||
|
Resultat 26/9: værste frame 124,7 ms (v1.27.3) -> 0,9 ms (v1.28.0), 0 tunge frames efter opvarmning.
|
||||||
|
luajit er hurtigere end spillets Lua: forholdet før/efter er det der tæller, ikke de absolutte ms.
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
-- Simulering af "hak hvert 5. sekund" (kør: luajit bench/bench_hak.lua <mappe med modulerne>)
|
||||||
|
-- Falsk verden i brugerens proportioner: 13.550 waypoints, 600 markører (150 Wait), 120 stationer med 3
|
||||||
|
-- triggere, 40 køretøjer, 15 holdende vogne der genplanlægger hvert 5. s. Kører modulernes RIGTIGE kode
|
||||||
|
-- (Wait-punktsøgning, markør -> station, salgssteder) frame for frame og måler det værste frame.
|
||||||
|
-- luajit er hurtigere end spillets Lua; FORHOLDET mellem før og efter er det der tæller.
|
||||||
|
local dir = arg[1] or "FS25_ADSmartPickup"
|
||||||
|
math.randomseed(42)
|
||||||
|
local function noop() end
|
||||||
|
Logging = {info = noop, warning = noop}
|
||||||
|
MathUtil = {vector2Length = function(x, z) return math.sqrt(x * x + z * z) end}
|
||||||
|
function entityExists() return true end
|
||||||
|
local nodes = {}
|
||||||
|
function getWorldTranslation(node) local p = nodes[node]; return p[1], 0, p[2] end
|
||||||
|
local simTime = 0
|
||||||
|
g_currentMission = {time = 0, getFarmId = function() return 1 end, accessHandler = {canFarmAccess = function() return true end}}
|
||||||
|
function netGetTime() return os.clock() * 1000 end
|
||||||
|
addModEventListener = noop
|
||||||
|
Utils = {prependedFunction = function(a) return a end}
|
||||||
|
|
||||||
|
-- vejnet og markører
|
||||||
|
local wayPoints, markers = {}, {}
|
||||||
|
for id = 1, 13550 do
|
||||||
|
local x, z = (id % 120) * 60 - 3600, math.floor(id / 120) * 60 - 3600
|
||||||
|
wayPoints[id] = {id = id, x = x, z = z, out = {id + 1 <= 13550 and id + 1 or 1}, incoming = {id > 1 and id - 1 or 13550}}
|
||||||
|
end
|
||||||
|
for index = 1, 600 do
|
||||||
|
local id = index * 22
|
||||||
|
table.insert(markers, {id = id, markerIndex = index, name = (index <= 150 and "Wait " or "Marker ") .. index, group = index <= 150 and "Cow Wait" or "All"})
|
||||||
|
end
|
||||||
|
-- stationer ved hver 5. markør, 3 triggere hver
|
||||||
|
local stations = {}
|
||||||
|
for index = 1, 120 do
|
||||||
|
local wp = wayPoints[markers[150 + index].id]
|
||||||
|
local triggers = {}
|
||||||
|
for t = 1, 3 do
|
||||||
|
local node = #nodes + 1
|
||||||
|
nodes[node] = {wp.x + t, wp.z + t}
|
||||||
|
table.insert(triggers, {exactFillRootNode = node})
|
||||||
|
end
|
||||||
|
table.insert(stations, {unloadTriggers = triggers, owningPlaceable = {}, getOwnerFarmId = function() return 1 end, name = "S" .. index,
|
||||||
|
isa = function() return false end, getIsFillTypeAllowed = function() return true end})
|
||||||
|
end
|
||||||
|
g_currentMission.storageSystem = {getUnloadingStations = function() return stations end, getLoadingStations = function() return {} end}
|
||||||
|
g_currentMission.placeableSystem = {placeables = {}}
|
||||||
|
-- køretøjer
|
||||||
|
local vehicles = {}
|
||||||
|
for index = 1, 40 do
|
||||||
|
local node = #nodes + 1
|
||||||
|
nodes[node] = {math.random(-3600, 3600), math.random(-3600, 3600)}
|
||||||
|
table.insert(vehicles, {rootNode = node, spec_motorized = {}, getChildVehicles = function() return {} end})
|
||||||
|
end
|
||||||
|
g_currentMission.vehicleSystem = {vehicles = vehicles}
|
||||||
|
local adEnv = {ADGraphManager = {
|
||||||
|
getMapMarkers = function() return markers end,
|
||||||
|
getWayPointById = function(_, id) return wayPoints[id] end,
|
||||||
|
getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end,
|
||||||
|
getPathTo = function(_, _, id) return {{id = id}} end,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, file in ipairs({"adCache.lua", "adSupplyPlanner.lua", "adWaitPool.lua", "adPrices.lua", "adSmartPickup.lua"}) do
|
||||||
|
local chunk = loadfile(dir .. "/" .. file)
|
||||||
|
if chunk ~= nil then pcall(chunk) end
|
||||||
|
end
|
||||||
|
if ADCache ~= nil then ADCache.enabled = true end
|
||||||
|
ADPrices = ADPrices or {}
|
||||||
|
ADPrices.isSellingStation = ADPrices.isSellingStation or function() return true end
|
||||||
|
|
||||||
|
-- én genplanlægning for en holdende vogn: Wait-punkt + stationsopslag over alle markører (salgssteder/lager)
|
||||||
|
local function replan(vehicle)
|
||||||
|
ADWaitPool.pickFree(adEnv, vehicle, "Cow Wait", function() return true end)
|
||||||
|
for _, marker in ipairs(markers) do
|
||||||
|
ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local t0 = os.clock()
|
||||||
|
replan(vehicles[1])
|
||||||
|
local single = (os.clock() - t0) * 1000
|
||||||
|
|
||||||
|
-- 30 s med 60 fps; 15 holdende vogne. Gammel kode: alle timere starter på 0. Ny: jitter hvis koden har det.
|
||||||
|
local jitter = ADCache ~= nil and 5000 or 0 -- ny kode spreder vognenes genplanlægning over 0-5 s (adOutbound CHECK_JITTER_MS)
|
||||||
|
local timers = {}
|
||||||
|
for index = 1, 15 do timers[index] = jitter > 0 and math.random(0, jitter) or 0 end
|
||||||
|
local worst, steady, frames = 0, 0, 0
|
||||||
|
for frame = 1, 60 * 60 do
|
||||||
|
simTime = simTime + 16.7
|
||||||
|
g_currentMission.time = simTime
|
||||||
|
local started = os.clock()
|
||||||
|
for index = 1, 15 do
|
||||||
|
timers[index] = timers[index] + 16.7
|
||||||
|
if timers[index] >= 5000 then
|
||||||
|
timers[index] = 0
|
||||||
|
replan(vehicles[index])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local spent = (os.clock() - started) * 1000
|
||||||
|
if spent > worst then worst = spent end
|
||||||
|
if simTime > 12000 and spent > steady then steady = spent end
|
||||||
|
if simTime > 12000 and spent > 3 then heavy = (heavy or 0) + 1; heavyAt = heavyAt or {}; if #heavyAt < 8 then table.insert(heavyAt, string.format("%.1fs:%.1fms", simTime / 1000, spent)) end end
|
||||||
|
frames = frames + 1
|
||||||
|
end
|
||||||
|
print(" tunge frames (>3 ms) efter opvarmning:", heavy or 0, table.concat(heavyAt or {}, " "))
|
||||||
|
print(string.format("%-12s én genplanlægning %.2f ms | værste frame (60 s): %.2f ms | værste efter opvarmning: %.2f ms", dir:find("old") and "FØR" or "EFTER", single, worst, steady))
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# AD Smart Pickup — brugermanual
|
# AD Smart Pickup — brugermanual
|
||||||
|
|
||||||
Version 1.27.3 · Farming Simulator 25 · tillæg til AutoDrive
|
Version 1.28.0 · Farming Simulator 25 · tillæg til AutoDrive
|
||||||
|
|
||||||
*English version: [USER_MANUAL.md](USER_MANUAL.md)*
|
*English version: [USER_MANUAL.md](USER_MANUAL.md)*
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Testplan — Markarbejde (fanen Marker), version 1.27.3.0
|
# Testplan — Markarbejde (fanen Marker), version 1.28.0.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.3.0) FS25_ADSmartPickup`.
|
- [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.28.0.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
@@ -1,6 +1,6 @@
|
|||||||
# AD Smart Pickup — user manual
|
# AD Smart Pickup — user manual
|
||||||
|
|
||||||
Version 1.27.3 · Farming Simulator 25 · add-on for AutoDrive
|
Version 1.28.0 · Farming Simulator 25 · add-on for AutoDrive
|
||||||
|
|
||||||
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
|
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
-- Kør: luajit tests/test_adCache.lua (fra repo-roden)
|
||||||
|
local failures = 0
|
||||||
|
local function check(name, actual, expected)
|
||||||
|
if actual == expected then print("OK " .. name) else
|
||||||
|
failures = failures + 1
|
||||||
|
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
g_currentMission = {time = 0}
|
||||||
|
dofile("FS25_ADSmartPickup/adCache.lua")
|
||||||
|
local calls = 0
|
||||||
|
local function lookup() calls = calls + 1; return "station" end
|
||||||
|
check("K1 slået fra: altid opslag", ADCacheGet("b", "k", 1000, lookup) .. ADCacheGet("b", "k", 1000, lookup), "stationstation")
|
||||||
|
check("K1 to opslag", calls, 2)
|
||||||
|
ADCache.enabled = true
|
||||||
|
calls = 0
|
||||||
|
ADCacheGet("b", "k", 1000, lookup); ADCacheGet("b", "k", 1000, lookup)
|
||||||
|
check("K2 genbrugt", calls, 1)
|
||||||
|
g_currentMission.time = 900
|
||||||
|
ADCacheGet("b", "k", 1000, lookup)
|
||||||
|
check("K2 inden for levetid", calls, 1)
|
||||||
|
g_currentMission.time = 1600
|
||||||
|
ADCacheGet("b", "k", 1000, lookup)
|
||||||
|
check("K3 efter levetid + spredning (maks 1500) -> nyt opslag", calls, 2)
|
||||||
|
local nothing = 0
|
||||||
|
ADCacheGet("n", "x", 1000, function() nothing = nothing + 1; return nil end)
|
||||||
|
check("K4 intet fundet huskes", ADCacheGet("n", "x", 1000, function() nothing = nothing + 1; return "ny" end), nil)
|
||||||
|
check("K4 kun ét opslag", nothing, 1)
|
||||||
|
-- spredt udløb: 200 poster lavet samtidig udløber ikke samtidig
|
||||||
|
g_currentMission.time = 10000
|
||||||
|
for i = 1, 200 do ADCacheGet("spread", i, 10000, function() return i end) end
|
||||||
|
local expired = 0
|
||||||
|
g_currentMission.time = 22000
|
||||||
|
for i = 1, 200 do ADCacheGet("spread", i, 10000, function() expired = expired + 1; return i end) end
|
||||||
|
check("K5 kun en del udløbet efter 12 s (spredt 10-15 s)", expired > 20 and expired < 180, true)
|
||||||
|
ADCache.clear()
|
||||||
|
check("K6 ryddet", next(ADCache.buckets), nil)
|
||||||
|
|
||||||
|
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