package app import ( "encoding/json" "io" "log" "net/http" "strconv" "time" "github.com/andyleap/hnh-map/internal/app/store" "go.etcd.io/bbolt" ) func (a *App) updatePositions(rw http.ResponseWriter, req *http.Request) { defer req.Body.Close() craws := map[string]struct { Name string GridID string Coords struct { X, Y int } Type string }{} buf, err := io.ReadAll(req.Body) if err != nil { log.Println("Error reading position update json: ", err) return } err = json.Unmarshal(buf, &craws) if err != nil { log.Println("Error decoding position update json: ", err) log.Println("Original json: ", string(buf)) return } // Read grid data first (inside db.View), then update characters (with chmu only). // Avoid holding db.View and chmu simultaneously to prevent deadlock. gridDataByID := make(map[string]GridData) a.db.View(func(tx *bbolt.Tx) error { grids := tx.Bucket(store.BucketGrids) if grids == nil { return nil } for _, craw := range craws { grid := grids.Get([]byte(craw.GridID)) if grid != nil { var gd GridData if json.Unmarshal(grid, &gd) == nil { gridDataByID[craw.GridID] = gd } } } return nil }) a.chmu.Lock() defer a.chmu.Unlock() for id, craw := range craws { gd, ok := gridDataByID[craw.GridID] if !ok { continue } idnum, _ := strconv.Atoi(id) c := Character{ Name: craw.Name, ID: idnum, Map: gd.Map, Position: Position{ X: craw.Coords.X + (gd.Coord.X * 100), Y: craw.Coords.Y + (gd.Coord.Y * 100), }, Type: craw.Type, updated: time.Now(), } old, ok := a.characters[id] if !ok { a.characters[id] = c } else { if old.Type == "player" { if c.Type == "player" { a.characters[id] = c } else { old.Position = c.Position a.characters[id] = old } } else if old.Type != "unknown" { if c.Type != "unknown" { a.characters[id] = c } else { old.Position = c.Position a.characters[id] = old } } else { a.characters[id] = c } } } }