package transcode import ( flatbuffers "github.com/google/flatbuffers/go" "scrabble/gateway/internal/backendclient" fb "scrabble/pkg/fbs/scrabblefb" ) // The encoders build the FlatBuffers response payloads from the backend's typed // responses. FlatBuffers is built bottom-up: every string and child vector is // created before the table that references it, and no two tables/vectors are // under construction at once. // encodeSession builds a Session payload. func encodeSession(s backendclient.SessionResp) []byte { b := flatbuffers.NewBuilder(128) token := b.CreateString(s.Token) uid := b.CreateString(s.UserID) name := b.CreateString(s.DisplayName) fb.SessionStart(b) fb.SessionAddToken(b, token) fb.SessionAddUserId(b, uid) fb.SessionAddIsGuest(b, s.IsGuest) fb.SessionAddDisplayName(b, name) b.Finish(fb.SessionEnd(b)) return b.FinishedBytes() } // encodeAck builds an Ack payload. func encodeAck(ok bool) []byte { b := flatbuffers.NewBuilder(16) fb.AckStart(b) fb.AckAddOk(b, ok) b.Finish(fb.AckEnd(b)) return b.FinishedBytes() } // encodeProfile builds a Profile payload. func encodeProfile(p backendclient.ProfileResp) []byte { b := flatbuffers.NewBuilder(192) uid := b.CreateString(p.UserID) name := b.CreateString(p.DisplayName) lang := b.CreateString(p.PreferredLanguage) tz := b.CreateString(p.TimeZone) fb.ProfileStart(b) fb.ProfileAddUserId(b, uid) fb.ProfileAddDisplayName(b, name) fb.ProfileAddPreferredLanguage(b, lang) fb.ProfileAddTimeZone(b, tz) fb.ProfileAddHintBalance(b, int32(p.HintBalance)) fb.ProfileAddBlockChat(b, p.BlockChat) fb.ProfileAddBlockFriendRequests(b, p.BlockFriendRequests) fb.ProfileAddIsGuest(b, p.IsGuest) b.Finish(fb.ProfileEnd(b)) return b.FinishedBytes() } // encodeMoveResult builds a MoveResult payload. func encodeMoveResult(r backendclient.MoveResultResp) []byte { b := flatbuffers.NewBuilder(512) move := buildMoveRecord(b, r.Move) game := buildGameView(b, r.Game) fb.MoveResultStart(b) fb.MoveResultAddMove(b, move) fb.MoveResultAddGame(b, game) b.Finish(fb.MoveResultEnd(b)) return b.FinishedBytes() } // encodeState builds a StateView payload. func encodeState(s backendclient.StateResp) []byte { b := flatbuffers.NewBuilder(512) game := buildGameView(b, s.Game) rack := buildStringVector(b, s.Rack, fb.StateViewStartRackVector) fb.StateViewStart(b) fb.StateViewAddGame(b, game) fb.StateViewAddSeat(b, int32(s.Seat)) fb.StateViewAddRack(b, rack) fb.StateViewAddBagLen(b, int32(s.BagLen)) fb.StateViewAddHintsRemaining(b, int32(s.HintsRemaining)) b.Finish(fb.StateViewEnd(b)) return b.FinishedBytes() } // encodeMatch builds a MatchResult payload. func encodeMatch(m backendclient.MatchResp) []byte { b := flatbuffers.NewBuilder(512) matched := m.Matched && m.Game != nil var game flatbuffers.UOffsetT if matched { game = buildGameView(b, *m.Game) } fb.MatchResultStart(b) fb.MatchResultAddMatched(b, matched) if matched { fb.MatchResultAddGame(b, game) } b.Finish(fb.MatchResultEnd(b)) return b.FinishedBytes() } // encodeChat builds a ChatMessage payload. func encodeChat(c backendclient.ChatResp) []byte { b := flatbuffers.NewBuilder(192) id := b.CreateString(c.ID) gid := b.CreateString(c.GameID) sid := b.CreateString(c.SenderID) kind := b.CreateString(c.Kind) body := b.CreateString(c.Body) fb.ChatMessageStart(b) fb.ChatMessageAddId(b, id) fb.ChatMessageAddGameId(b, gid) fb.ChatMessageAddSenderId(b, sid) fb.ChatMessageAddKind(b, kind) fb.ChatMessageAddBody(b, body) fb.ChatMessageAddCreatedAtUnix(b, c.CreatedAtUnix) b.Finish(fb.ChatMessageEnd(b)) return b.FinishedBytes() } // buildGameView builds a GameView table and returns its offset. func buildGameView(b *flatbuffers.Builder, g backendclient.GameResp) flatbuffers.UOffsetT { seatOffs := make([]flatbuffers.UOffsetT, len(g.Seats)) for i, s := range g.Seats { aid := b.CreateString(s.AccountID) fb.SeatViewStart(b) fb.SeatViewAddSeat(b, int32(s.Seat)) fb.SeatViewAddAccountId(b, aid) fb.SeatViewAddScore(b, int32(s.Score)) fb.SeatViewAddHintsUsed(b, int32(s.HintsUsed)) fb.SeatViewAddIsWinner(b, s.IsWinner) seatOffs[i] = fb.SeatViewEnd(b) } fb.GameViewStartSeatsVector(b, len(seatOffs)) for i := len(seatOffs) - 1; i >= 0; i-- { b.PrependUOffsetT(seatOffs[i]) } seats := b.EndVector(len(seatOffs)) id := b.CreateString(g.ID) variant := b.CreateString(g.Variant) dictVer := b.CreateString(g.DictVersion) status := b.CreateString(g.Status) endReason := b.CreateString(g.EndReason) fb.GameViewStart(b) fb.GameViewAddId(b, id) fb.GameViewAddVariant(b, variant) fb.GameViewAddDictVersion(b, dictVer) fb.GameViewAddStatus(b, status) fb.GameViewAddPlayers(b, int32(g.Players)) fb.GameViewAddToMove(b, int32(g.ToMove)) fb.GameViewAddTurnTimeoutSecs(b, int32(g.TurnTimeoutSecs)) fb.GameViewAddMoveCount(b, int32(g.MoveCount)) fb.GameViewAddEndReason(b, endReason) fb.GameViewAddSeats(b, seats) return fb.GameViewEnd(b) } // buildMoveRecord builds a MoveRecord table and returns its offset. func buildMoveRecord(b *flatbuffers.Builder, m backendclient.MoveRecordResp) flatbuffers.UOffsetT { tileOffs := make([]flatbuffers.UOffsetT, len(m.Tiles)) for i, t := range m.Tiles { letter := b.CreateString(t.Letter) fb.TileRecordStart(b) fb.TileRecordAddRow(b, int32(t.Row)) fb.TileRecordAddCol(b, int32(t.Col)) fb.TileRecordAddLetter(b, letter) fb.TileRecordAddBlank(b, t.Blank) tileOffs[i] = fb.TileRecordEnd(b) } fb.MoveRecordStartTilesVector(b, len(tileOffs)) for i := len(tileOffs) - 1; i >= 0; i-- { b.PrependUOffsetT(tileOffs[i]) } tiles := b.EndVector(len(tileOffs)) words := buildStringVector(b, m.Words, fb.MoveRecordStartWordsVector) action := b.CreateString(m.Action) dir := b.CreateString(m.Dir) fb.MoveRecordStart(b) fb.MoveRecordAddPlayer(b, int32(m.Player)) fb.MoveRecordAddAction(b, action) fb.MoveRecordAddDir(b, dir) fb.MoveRecordAddMainRow(b, int32(m.MainRow)) fb.MoveRecordAddMainCol(b, int32(m.MainCol)) fb.MoveRecordAddTiles(b, tiles) fb.MoveRecordAddWords(b, words) fb.MoveRecordAddCount(b, int32(m.Count)) fb.MoveRecordAddScore(b, int32(m.Score)) fb.MoveRecordAddTotal(b, int32(m.Total)) return fb.MoveRecordEnd(b) } // buildStringVector builds a vector of strings using the table-specific // StartXVector function and returns the vector offset. func buildStringVector(b *flatbuffers.Builder, items []string, start func(*flatbuffers.Builder, int) flatbuffers.UOffsetT) flatbuffers.UOffsetT { offs := make([]flatbuffers.UOffsetT, len(items)) for i, s := range items { offs[i] = b.CreateString(s) } start(b, len(offs)) for i := len(offs) - 1; i >= 0; i-- { b.PrependUOffsetT(offs[i]) } return b.EndVector(len(offs)) }