MosswartOverlord/go-services/tracker-go/share.go
Erik 5b2db439a3 feat(go-services): tracker share_* handlers (complete ingest) + shadow tuning
- share.go: cross-machine vital sharing (share_subscribe/unsubscribe/share_*),
  faithful port of the peer-state snapshot + plugin fan-out + /vital-sharing/peers.
  The last ingest handler — the Go tracker now handles every plugin event type.
- shadow consumer: drop the outbound keepalive ping (the firehose is never idle)
  and tighten the read-deadline watchdog to 12s for faster reconnect after the
  upstream's periodic eviction (full-firehose browser clients get evicted ~every
  90s; the watchdog recovers it, ~90% duty cycle). Production-bound /ws/position
  is unaffected (plugins connect to us; no eviction).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:27:25 +02:00

111 lines
3.4 KiB
Go

package main
// Cross-machine vital sharing (share_*), a faithful port of main.py:3658-3703 +
// _update_vital_sharing_peer_state / _broadcast_share_to_plugin_clients.
// Memory-only: subscriber set + last-known peer snapshot, fanned out to other
// opted-in plugin clients and to browsers. In shadow mode there are no plugin
// connections, so the fan-out is a no-op; the peer state still drives
// /vital-sharing/peers.
func (i *Ingestor) handleShareSubscribe(data map[string]any) {
char := toStr(data["character_name"])
if char == "" {
return
}
i.mu.Lock()
i.vitalSubscribers[char] = true
entry := i.vitalPeerEntry(char)
if tags, ok := data["tags"].([]any); ok {
entry["tags"] = tags
}
entry["connected"] = true
i.mu.Unlock()
}
func (i *Ingestor) handleShareUnsubscribe(data map[string]any) {
char := toStr(data["character_name"])
if char == "" {
return
}
i.mu.Lock()
delete(i.vitalSubscribers, char)
delete(i.vitalPeerState, char)
i.mu.Unlock()
if i.broadcast != nil {
i.broadcast(map[string]any{"type": "share_peer_removed", "character_name": char})
}
}
func (i *Ingestor) handleShareUpdate(msgType string, data map[string]any) {
origin := toStr(data["character_name"])
i.mu.Lock()
i.updateVitalPeerState(msgType, data)
// Snapshot subscribers for the fan-out.
subs := make(map[string]bool, len(i.vitalSubscribers))
for k := range i.vitalSubscribers {
subs[k] = true
}
i.mu.Unlock()
// Fan out to other opted-in plugin clients (no-op when no plugins connected).
if i.plugins != nil && len(subs) > 0 {
i.plugins.fanoutShare(data, origin, subs)
}
}
// vitalPeerEntry returns (creating if needed) the peer snapshot for char. Caller
// holds i.mu.
func (i *Ingestor) vitalPeerEntry(char string) map[string]any {
entry, ok := i.vitalPeerState[char]
if !ok {
entry = map[string]any{
"character_name": char, "tags": []any{}, "vitals": nil,
"position": nil, "items": nil, "connected": true, "last_update": nil,
}
i.vitalPeerState[char] = entry
}
return entry
}
// updateVitalPeerState mirrors _update_vital_sharing_peer_state. Caller holds i.mu.
func (i *Ingestor) updateVitalPeerState(msgType string, data map[string]any) {
char := toStr(data["character_name"])
if char == "" {
return
}
entry := i.vitalPeerEntry(char)
entry["last_update"] = data["timestamp"]
if tags, ok := data["tags"].([]any); ok {
entry["tags"] = tags
}
switch msgType {
case "share_vital_update":
entry["vitals"] = map[string]any{
"current_health": data["current_health"], "max_health": data["max_health"],
"current_stamina": data["current_stamina"], "max_stamina": data["max_stamina"],
"current_mana": data["current_mana"], "max_mana": data["max_mana"],
}
case "share_position_update":
entry["position"] = map[string]any{
"ew": data["ew"], "ns": data["ns"], "z": data["z"], "heading": data["heading"],
}
case "share_item_update":
entry["items"] = data["items"]
}
}
// vitalSharingPeers returns the peer list for /vital-sharing/peers (main.py:1800).
func (i *Ingestor) vitalSharingPeers() ([]map[string]any, int) {
i.mu.RLock()
defer i.mu.RUnlock()
peers := make([]map[string]any, 0, len(i.vitalPeerState))
for char, entry := range i.vitalPeerState {
p := make(map[string]any, len(entry)+2)
for k, v := range entry {
p[k] = v
}
p["subscribed"] = i.vitalSubscribers[char]
p["plugin_connected"] = i.plugins != nil && i.plugins.isConnected(char)
peers = append(peers, p)
}
return peers, len(i.vitalSubscribers)
}