feat(cluster): the spot list size is a setting

The list is a ring buffer that held a thousand spots, and on a busy
evening a thousand arrive in a couple of minutes. So the buffer decided
how long a spot lived, and the spot LIFETIME setting never got the chance
to expire anything: fifteen minutes meant nothing when the oldest spot
was pushed out after two.

Now settable (Preferences → Cluster, beside the lifetime, since between
them they decide the same thing), 100 to 10 000. The ceiling is a real
limit rather than a round number: every spot is matched against the
worked index and the alert rules, and is a row the cluster grid and every
open band map re-render.
This commit is contained in:
2026-08-24 19:15:32 +02:00
parent 4e9b1eebe9
commit 27d0952d01
7 changed files with 103 additions and 9 deletions
+45
View File
@@ -312,6 +312,7 @@ const (
keyClusterSelfSpot = "cluster.self_spot" // "1" → announce ourselves on the cluster as we log
keyClusterSelfSpotMin = "cluster.self_spot_minutes" // shortest gap between two self-spots
keyClusterSpotTTL = "cluster.spot_ttl_min" // drop spots older than this; 0 = keep them
keyClusterSpotMax = "cluster.spot_max" // how many spots the list holds at once
keyScpEnabled = "scp.enabled" // Super Check Partial / N+1 suggestions on
@@ -20294,6 +20295,50 @@ func (a *App) SetKenwoodKeySpeed(wpm int) error {
// and exists only so a mistyped value cannot mean "never".
const spotTTLMax = 720
// The spot list is a ring buffer, and its size decided the lifetime far more
// often than the lifetime setting did: on a busy evening a thousand spots
// arrive in a couple of minutes, so a fifteen-minute lifetime never got the
// chance to expire anything. Hence a setting.
//
// The ceiling is a real limit, not a round number. Every spot is matched against
// the worked index and the alert rules, and each one is a row the cluster grid
// and every open band map re-render; past ten thousand that work starts to show
// on the very evenings the list is worth having.
const (
spotMaxDefault = 1000
spotMaxCeiling = 10000
spotMaxFloor = 100
)
// GetSpotMax returns how many spots the list holds.
func (a *App) GetSpotMax() int {
n, _ := strconv.Atoi(a.settingOr(keyClusterSpotMax, ""))
if n <= 0 {
return spotMaxDefault
}
return clampSpotMax(n)
}
// SetSpotMax sets it. Clamped here as well as in the UI, for the same reason as
// the lifetime: the value decides how much work arrives on every spot, and a
// stale frontend must not be able to widen it past the ceiling.
func (a *App) SetSpotMax(n int) error {
a.setSetting(keyClusterSpotMax, strconv.Itoa(clampSpotMax(n)))
return nil
}
func clampSpotMax(n int) int {
if n < spotMaxFloor {
return spotMaxFloor
}
if n > spotMaxCeiling {
return spotMaxCeiling
}
return n
}
// GetSpotTTLMinutes returns how long a spot stays in the list, in minutes.
// 0 means spots are kept until the count cap pushes them out, which is what
// OpsLog always did.