2a6dc5a304
Add the WalletOrderRequest / WalletOrderResponse FlatBuffers messages and the wallet.order Connect op: the gateway decodes the order request, forwards it to the backend POST /wallet/order and returns the created order id plus the provider launch URL the client opens. Regenerate the committed Go and TS FlatBuffers code.
650 lines
22 KiB
Go
650 lines
22 KiB
Go
package transcode
|
|
|
|
import (
|
|
flatbuffers "github.com/google/flatbuffers/go"
|
|
|
|
"scrabble/gateway/internal/backendclient"
|
|
fb "scrabble/pkg/fbs/scrabblefb"
|
|
"scrabble/pkg/wire"
|
|
)
|
|
|
|
// 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()
|
|
}
|
|
|
|
// encodeWalletOrder builds a WalletOrderResponse payload: the created order id and the provider
|
|
// launch URL the client opens.
|
|
func encodeWalletOrder(o backendclient.WalletOrderResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
oid := b.CreateString(o.OrderID)
|
|
url := b.CreateString(o.RedirectURL)
|
|
fb.WalletOrderResponseStart(b)
|
|
fb.WalletOrderResponseAddOrderId(b, oid)
|
|
fb.WalletOrderResponseAddRedirectUrl(b, url)
|
|
b.Finish(fb.WalletOrderResponseEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeDeleteRequestResult builds an AccountDeleteRequestResult payload reporting which
|
|
// deletion step-up the account uses ("email" | "phrase").
|
|
func encodeDeleteRequestResult(method string) []byte {
|
|
b := flatbuffers.NewBuilder(32)
|
|
m := b.CreateString(method)
|
|
fb.AccountDeleteRequestResultStart(b)
|
|
fb.AccountDeleteRequestResultAddMethod(b, m)
|
|
b.Finish(fb.AccountDeleteRequestResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeConfirmLinkResult builds an EmailConfirmLinkResult payload, embedding the
|
|
// minted Session for a login. All strings and the nested Session table are built
|
|
// before the result table is opened.
|
|
func encodeConfirmLinkResult(r backendclient.ConfirmLinkResp) []byte {
|
|
b := flatbuffers.NewBuilder(160)
|
|
purpose := b.CreateString(r.Purpose)
|
|
status := b.CreateString(r.Status)
|
|
var session flatbuffers.UOffsetT
|
|
if r.Session != nil {
|
|
token := b.CreateString(r.Session.Token)
|
|
uid := b.CreateString(r.Session.UserID)
|
|
name := b.CreateString(r.Session.DisplayName)
|
|
fb.SessionStart(b)
|
|
fb.SessionAddToken(b, token)
|
|
fb.SessionAddUserId(b, uid)
|
|
fb.SessionAddIsGuest(b, r.Session.IsGuest)
|
|
fb.SessionAddDisplayName(b, name)
|
|
session = fb.SessionEnd(b)
|
|
}
|
|
fb.EmailConfirmLinkResultStart(b)
|
|
fb.EmailConfirmLinkResultAddPurpose(b, purpose)
|
|
fb.EmailConfirmLinkResultAddStatus(b, status)
|
|
if r.Session != nil {
|
|
fb.EmailConfirmLinkResultAddSession(b, session)
|
|
}
|
|
b.Finish(fb.EmailConfirmLinkResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeProfile builds a Profile payload, including the advertising-banner block
|
|
// when the backend marked the viewer eligible.
|
|
// encodeWallet builds the Wallet payload: the visible chip segments and the context-applicable
|
|
// benefits. Each WalletSegment table (and its source string) is built before the segments vector
|
|
// is opened, per FlatBuffers' rule against a nested table while another is under construction.
|
|
func encodeWallet(w backendclient.WalletResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
segs := make([]flatbuffers.UOffsetT, len(w.Segments))
|
|
for i, seg := range w.Segments {
|
|
src := b.CreateString(seg.Source)
|
|
fb.WalletSegmentStart(b)
|
|
fb.WalletSegmentAddSource(b, src)
|
|
fb.WalletSegmentAddChips(b, int32(seg.Chips))
|
|
fb.WalletSegmentAddSpendable(b, seg.Spendable)
|
|
segs[i] = fb.WalletSegmentEnd(b)
|
|
}
|
|
fb.WalletStartSegmentsVector(b, len(segs))
|
|
for i := len(segs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(segs[i])
|
|
}
|
|
segVec := b.EndVector(len(segs))
|
|
fb.WalletStart(b)
|
|
fb.WalletAddSegments(b, segVec)
|
|
fb.WalletAddAdsForever(b, w.AdsForever)
|
|
fb.WalletAddAdsPaidUntilMs(b, w.AdsPaidUntil)
|
|
fb.WalletAddHints(b, int32(w.Hints))
|
|
b.Finish(fb.WalletEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeCatalog builds a Catalog payload: the storefront products for the caller's context, each
|
|
// with its atom composition (built bottom-up — every product's atoms vector is finished before
|
|
// its product table, and every product before the products vector).
|
|
func encodeCatalog(cat backendclient.CatalogResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
prods := make([]flatbuffers.UOffsetT, len(cat.Products))
|
|
for i, p := range cat.Products {
|
|
atoms := make([]flatbuffers.UOffsetT, len(p.Atoms))
|
|
for j, a := range p.Atoms {
|
|
at := b.CreateString(a.AtomType)
|
|
fb.CatalogAtomStart(b)
|
|
fb.CatalogAtomAddAtomType(b, at)
|
|
fb.CatalogAtomAddQuantity(b, int32(a.Quantity))
|
|
atoms[j] = fb.CatalogAtomEnd(b)
|
|
}
|
|
fb.CatalogProductStartAtomsVector(b, len(atoms))
|
|
for j := len(atoms) - 1; j >= 0; j-- {
|
|
b.PrependUOffsetT(atoms[j])
|
|
}
|
|
atomsVec := b.EndVector(len(atoms))
|
|
|
|
kind := b.CreateString(p.Kind)
|
|
pid := b.CreateString(p.ProductID)
|
|
title := b.CreateString(p.Title)
|
|
cur := b.CreateString(p.MoneyCurrency)
|
|
fb.CatalogProductStart(b)
|
|
fb.CatalogProductAddKind(b, kind)
|
|
fb.CatalogProductAddProductId(b, pid)
|
|
fb.CatalogProductAddTitle(b, title)
|
|
fb.CatalogProductAddChips(b, int32(p.Chips))
|
|
fb.CatalogProductAddMoneyAmount(b, p.MoneyAmount)
|
|
fb.CatalogProductAddMoneyCurrency(b, cur)
|
|
fb.CatalogProductAddAtoms(b, atomsVec)
|
|
prods[i] = fb.CatalogProductEnd(b)
|
|
}
|
|
fb.CatalogStartProductsVector(b, len(prods))
|
|
for i := len(prods) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(prods[i])
|
|
}
|
|
prodVec := b.EndVector(len(prods))
|
|
fb.CatalogStart(b)
|
|
fb.CatalogAddProducts(b, prodVec)
|
|
b.Finish(fb.CatalogEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
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)
|
|
awayStart := b.CreateString(p.AwayStart)
|
|
awayEnd := b.CreateString(p.AwayEnd)
|
|
email := b.CreateString(p.Email)
|
|
// Build the banner table (and its children) before opening Profile: FlatBuffers
|
|
// forbids a nested table while another is under construction.
|
|
var banner flatbuffers.UOffsetT
|
|
if p.Banner != nil {
|
|
banner = encodeBanner(b, *p.Banner)
|
|
}
|
|
prefs := buildStringVector(b, p.VariantPreferences, fb.ProfileStartVariantPreferencesVector)
|
|
dictVersions := encodeDictVersions(b, p.DictVersions)
|
|
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)
|
|
fb.ProfileAddAwayStart(b, awayStart)
|
|
fb.ProfileAddAwayEnd(b, awayEnd)
|
|
fb.ProfileAddNotificationsInAppOnly(b, p.NotificationsInAppOnly)
|
|
fb.ProfileAddVariantPreferences(b, prefs)
|
|
fb.ProfileAddEmail(b, email)
|
|
fb.ProfileAddTelegramLinked(b, p.TelegramLinked)
|
|
fb.ProfileAddVkLinked(b, p.VkLinked)
|
|
if dictVersions != 0 {
|
|
fb.ProfileAddDictVersions(b, dictVersions)
|
|
}
|
|
if p.Banner != nil {
|
|
fb.ProfileAddBanner(b, banner)
|
|
}
|
|
b.Finish(fb.ProfileEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeDictVersions builds the Profile's dict_versions vector — one DictVersion table per
|
|
// variant/version pair — and returns its offset, or 0 when there are none (the field is then
|
|
// omitted). Every child table and its strings are created before the vector is opened, per the
|
|
// FlatBuffers rule against nesting a table under one still being built; the caller invokes it
|
|
// before ProfileStart for the same reason.
|
|
func encodeDictVersions(b *flatbuffers.Builder, dvs []backendclient.DictVersion) flatbuffers.UOffsetT {
|
|
if len(dvs) == 0 {
|
|
return 0
|
|
}
|
|
offsets := make([]flatbuffers.UOffsetT, len(dvs))
|
|
for i, dv := range dvs {
|
|
variant := b.CreateString(dv.Variant)
|
|
version := b.CreateString(dv.Version)
|
|
fb.DictVersionStart(b)
|
|
fb.DictVersionAddVariant(b, variant)
|
|
fb.DictVersionAddVersion(b, version)
|
|
offsets[i] = fb.DictVersionEnd(b)
|
|
}
|
|
fb.ProfileStartDictVersionsVector(b, len(offsets))
|
|
for i := len(offsets) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(offsets[i])
|
|
}
|
|
return b.EndVector(len(offsets))
|
|
}
|
|
|
|
// optString creates a FlatBuffers string for a non-empty value, or 0 to omit the
|
|
// optional field (the client then reads it as absent).
|
|
func optString(b *flatbuffers.Builder, s string) flatbuffers.UOffsetT {
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
return b.CreateString(s)
|
|
}
|
|
|
|
// addOptString adds an optional string slot only when it was created (a 0 offset
|
|
// leaves the field absent). add is the generated BannerCampaignAdd* setter.
|
|
func addOptString(b *flatbuffers.Builder, add func(*flatbuffers.Builder, flatbuffers.UOffsetT), off flatbuffers.UOffsetT) {
|
|
if off != 0 {
|
|
add(b, off)
|
|
}
|
|
}
|
|
|
|
// encodeBanner builds a BannerInfo table from the resolved banner block and
|
|
// returns its offset. It is bottom-up: each campaign's messages vector and table
|
|
// are built first, then the campaigns vector, then the BannerInfo table. The
|
|
// caller must invoke it with no table under construction.
|
|
func encodeBanner(b *flatbuffers.Builder, banner backendclient.BannerResp) flatbuffers.UOffsetT {
|
|
campOffsets := make([]flatbuffers.UOffsetT, len(banner.Campaigns))
|
|
for i, c := range banner.Campaigns {
|
|
msgOffsets := make([]flatbuffers.UOffsetT, len(c.Messages))
|
|
for j, m := range c.Messages {
|
|
msgOffsets[j] = b.CreateString(m)
|
|
}
|
|
fb.BannerCampaignStartMessagesVector(b, len(msgOffsets))
|
|
for j := len(msgOffsets) - 1; j >= 0; j-- {
|
|
b.PrependUOffsetT(msgOffsets[j])
|
|
}
|
|
msgs := b.EndVector(len(msgOffsets))
|
|
// The optional colour strings must be created before the table starts; an
|
|
// empty colour is omitted (offset 0), so the client reads it as absent.
|
|
obg := optString(b, c.OverrideBg)
|
|
ofg := optString(b, c.OverrideFg)
|
|
olink := optString(b, c.OverrideLink)
|
|
obgD := optString(b, c.OverrideBgDark)
|
|
ofgD := optString(b, c.OverrideFgDark)
|
|
olinkD := optString(b, c.OverrideLinkDark)
|
|
fb.BannerCampaignStart(b)
|
|
fb.BannerCampaignAddWeight(b, int32(c.Weight))
|
|
fb.BannerCampaignAddMessages(b, msgs)
|
|
addOptString(b, fb.BannerCampaignAddOverrideBg, obg)
|
|
addOptString(b, fb.BannerCampaignAddOverrideFg, ofg)
|
|
addOptString(b, fb.BannerCampaignAddOverrideLink, olink)
|
|
addOptString(b, fb.BannerCampaignAddOverrideBgDark, obgD)
|
|
addOptString(b, fb.BannerCampaignAddOverrideFgDark, ofgD)
|
|
addOptString(b, fb.BannerCampaignAddOverrideLinkDark, olinkD)
|
|
campOffsets[i] = fb.BannerCampaignEnd(b)
|
|
}
|
|
fb.BannerInfoStartCampaignsVector(b, len(campOffsets))
|
|
for i := len(campOffsets) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(campOffsets[i])
|
|
}
|
|
camps := b.EndVector(len(campOffsets))
|
|
t := banner.Timings
|
|
fb.BannerInfoStart(b)
|
|
fb.BannerInfoAddCampaigns(b, camps)
|
|
fb.BannerInfoAddHoldMs(b, int32(t.HoldMs))
|
|
fb.BannerInfoAddEdgePauseMs(b, int32(t.EdgePauseMs))
|
|
fb.BannerInfoAddScrollPxPerSec(b, int32(t.ScrollPxPerSec))
|
|
fb.BannerInfoAddFadeOutMs(b, int32(t.FadeOutMs))
|
|
fb.BannerInfoAddGapMs(b, int32(t.GapMs))
|
|
fb.BannerInfoAddFadeInMs(b, int32(t.FadeInMs))
|
|
return fb.BannerInfoEnd(b)
|
|
}
|
|
|
|
// encodeBlockStatus builds a BlockStatus payload.
|
|
func encodeBlockStatus(s backendclient.BlockStatusResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
until := b.CreateString(s.Until)
|
|
reason := b.CreateString(s.Reason)
|
|
fb.BlockStatusStart(b)
|
|
fb.BlockStatusAddBlocked(b, s.Blocked)
|
|
fb.BlockStatusAddPermanent(b, s.Permanent)
|
|
fb.BlockStatusAddUntil(b, until)
|
|
fb.BlockStatusAddReason(b, reason)
|
|
b.Finish(fb.BlockStatusEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeLinkResult builds a LinkResult payload. A switched-session token
|
|
// (a guest initiator whose durable counterpart won) is carried as a nested Session
|
|
// for the client to adopt; it is omitted otherwise.
|
|
func encodeLinkResult(r backendclient.LinkResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
status := b.CreateString(r.Status)
|
|
secID := b.CreateString(r.SecondaryUserID)
|
|
secName := b.CreateString(r.SecondaryName)
|
|
hasSession := r.Token != "" && r.Profile != nil
|
|
var sess flatbuffers.UOffsetT
|
|
if hasSession {
|
|
token := b.CreateString(r.Token)
|
|
uid := b.CreateString(r.Profile.UserID)
|
|
name := b.CreateString(r.Profile.DisplayName)
|
|
fb.SessionStart(b)
|
|
fb.SessionAddToken(b, token)
|
|
fb.SessionAddUserId(b, uid)
|
|
fb.SessionAddIsGuest(b, r.Profile.IsGuest)
|
|
fb.SessionAddDisplayName(b, name)
|
|
sess = fb.SessionEnd(b)
|
|
}
|
|
fb.LinkResultStart(b)
|
|
fb.LinkResultAddStatus(b, status)
|
|
fb.LinkResultAddSecondaryUserId(b, secID)
|
|
fb.LinkResultAddSecondaryDisplayName(b, secName)
|
|
fb.LinkResultAddSecondaryGames(b, int32(r.SecondaryGames))
|
|
fb.LinkResultAddSecondaryFriends(b, int32(r.SecondaryFriends))
|
|
if hasSession {
|
|
fb.LinkResultAddSession(b, sess)
|
|
}
|
|
b.Finish(fb.LinkResultEnd(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)
|
|
rackBytes := make([]byte, len(r.Rack))
|
|
for i, v := range r.Rack {
|
|
rackBytes[i] = byte(v)
|
|
}
|
|
rack := b.CreateByteVector(rackBytes)
|
|
fb.MoveResultStart(b)
|
|
fb.MoveResultAddMove(b, move)
|
|
fb.MoveResultAddGame(b, game)
|
|
fb.MoveResultAddRack(b, rack)
|
|
fb.MoveResultAddBagLen(b, int32(r.BagLen))
|
|
b.Finish(fb.MoveResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeState builds a StateView payload. The rack is a vector of alphabet indices and the
|
|
// alphabet display table is included only when the backend returned it (the
|
|
// client requests it on a per-variant cache miss).
|
|
func encodeState(s backendclient.StateResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
b.Finish(wire.BuildStateView(b, toWireState(s)))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// toWireState maps a StateResp to the shared wire.StateView.
|
|
func toWireState(s backendclient.StateResp) wire.StateView {
|
|
alphabet := make([]wire.AlphabetEntry, len(s.Alphabet))
|
|
for i, e := range s.Alphabet {
|
|
alphabet[i] = wire.AlphabetEntry{Index: e.Index, Letter: e.Letter, Value: e.Value}
|
|
}
|
|
return wire.StateView{
|
|
Game: toWireGame(s.Game),
|
|
Seat: s.Seat,
|
|
Rack: s.Rack,
|
|
BagLen: s.BagLen,
|
|
HintsRemaining: s.HintsRemaining,
|
|
WalletBalance: s.WalletBalance,
|
|
HintUnlockLeftSeconds: s.HintUnlockLeftSeconds,
|
|
Alphabet: alphabet,
|
|
}
|
|
}
|
|
|
|
// encodeMatch builds a MatchResult payload.
|
|
func encodeMatch(m backendclient.MatchResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
// Enqueue always lands the caller in a game; an open game awaiting an opponent reports
|
|
// matched=false but still carries it, so encode the game whenever it is present (else the
|
|
// client never receives it and cannot navigate in).
|
|
hasGame := m.Game != nil
|
|
var game flatbuffers.UOffsetT
|
|
if hasGame {
|
|
game = buildGameView(b, *m.Game)
|
|
}
|
|
fb.MatchResultStart(b)
|
|
fb.MatchResultAddMatched(b, m.Matched)
|
|
if hasGame {
|
|
fb.MatchResultAddGame(b, game)
|
|
}
|
|
b.Finish(fb.MatchResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// buildChatMessage builds a ChatMessage table and returns its offset.
|
|
func buildChatMessage(b *flatbuffers.Builder, c backendclient.ChatResp) flatbuffers.UOffsetT {
|
|
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)
|
|
return fb.ChatMessageEnd(b)
|
|
}
|
|
|
|
// encodeChat builds a ChatMessage payload.
|
|
func encodeChat(c backendclient.ChatResp) []byte {
|
|
b := flatbuffers.NewBuilder(192)
|
|
b.Finish(buildChatMessage(b, c))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeHintResult builds a HintResult payload.
|
|
func encodeHintResult(r backendclient.HintResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
move := buildMoveRecord(b, r.Move)
|
|
fb.HintResultStart(b)
|
|
fb.HintResultAddMove(b, move)
|
|
fb.HintResultAddHintsRemaining(b, int32(r.HintsRemaining))
|
|
fb.HintResultAddWalletBalance(b, int32(r.WalletBalance))
|
|
b.Finish(fb.HintResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeEvalResult builds an EvalResult payload.
|
|
func encodeEvalResult(r backendclient.EvalResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
words := buildStringVector(b, r.Words, fb.EvalResultStartWordsVector)
|
|
dir := b.CreateString(r.Dir)
|
|
fb.EvalResultStart(b)
|
|
fb.EvalResultAddLegal(b, r.Legal)
|
|
fb.EvalResultAddScore(b, int32(r.Score))
|
|
fb.EvalResultAddWords(b, words)
|
|
fb.EvalResultAddDir(b, dir)
|
|
b.Finish(fb.EvalResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeWordCheck builds a WordCheckResult payload.
|
|
func encodeWordCheck(r backendclient.WordCheckResp) []byte {
|
|
b := flatbuffers.NewBuilder(64)
|
|
word := b.CreateString(r.Word)
|
|
fb.WordCheckResultStart(b)
|
|
fb.WordCheckResultAddWord(b, word)
|
|
fb.WordCheckResultAddLegal(b, r.Legal)
|
|
b.Finish(fb.WordCheckResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeDraftView builds a DraftView payload wrapping the player's composition JSON. The
|
|
// string is empty for the save acknowledgement (the client ignores that payload).
|
|
func encodeDraftView(jsonStr string) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
j := b.CreateString(jsonStr)
|
|
fb.DraftViewStart(b)
|
|
fb.DraftViewAddJson(b, j)
|
|
b.Finish(fb.DraftViewEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeHistory builds a History payload (the decoded move journal).
|
|
func encodeHistory(r backendclient.HistoryResp) []byte {
|
|
b := flatbuffers.NewBuilder(1024)
|
|
moveOffs := make([]flatbuffers.UOffsetT, len(r.Moves))
|
|
for i, m := range r.Moves {
|
|
moveOffs[i] = buildMoveRecord(b, m)
|
|
}
|
|
fb.HistoryStartMovesVector(b, len(moveOffs))
|
|
for i := len(moveOffs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(moveOffs[i])
|
|
}
|
|
moves := b.EndVector(len(moveOffs))
|
|
gid := b.CreateString(r.GameID)
|
|
fb.HistoryStart(b)
|
|
fb.HistoryAddGameId(b, gid)
|
|
fb.HistoryAddMoves(b, moves)
|
|
b.Finish(fb.HistoryEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeGameList builds a GameList payload (the caller's games).
|
|
func encodeGameList(r backendclient.GameListResp) []byte {
|
|
b := flatbuffers.NewBuilder(1024)
|
|
gameOffs := make([]flatbuffers.UOffsetT, len(r.Games))
|
|
for i, g := range r.Games {
|
|
gameOffs[i] = buildGameView(b, g)
|
|
}
|
|
fb.GameListStartGamesVector(b, len(gameOffs))
|
|
for i := len(gameOffs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(gameOffs[i])
|
|
}
|
|
games := b.EndVector(len(gameOffs))
|
|
fb.GameListStart(b)
|
|
fb.GameListAddGames(b, games)
|
|
fb.GameListAddAtGameLimit(b, r.AtGameLimit)
|
|
b.Finish(fb.GameListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeChatList builds a ChatList payload (a game's chat history).
|
|
func encodeChatList(r backendclient.ChatListResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
msgOffs := make([]flatbuffers.UOffsetT, len(r.Messages))
|
|
for i, m := range r.Messages {
|
|
msgOffs[i] = buildChatMessage(b, m)
|
|
}
|
|
fb.ChatListStartMessagesVector(b, len(msgOffs))
|
|
for i := len(msgOffs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(msgOffs[i])
|
|
}
|
|
msgs := b.EndVector(len(msgOffs))
|
|
fb.ChatListStart(b)
|
|
fb.ChatListAddMessages(b, msgs)
|
|
b.Finish(fb.ChatListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeFeedbackState builds a FeedbackState payload, nesting the reply when present.
|
|
func encodeFeedbackState(st backendclient.FeedbackStateResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
var reply flatbuffers.UOffsetT
|
|
if st.Reply != nil {
|
|
body := b.CreateString(st.Reply.Body)
|
|
fb.FeedbackReplyStart(b)
|
|
fb.FeedbackReplyAddBody(b, body)
|
|
fb.FeedbackReplyAddRepliedAtUnix(b, st.Reply.RepliedAtUnix)
|
|
reply = fb.FeedbackReplyEnd(b)
|
|
}
|
|
reason := b.CreateString(st.BlockedReason)
|
|
fb.FeedbackStateStart(b)
|
|
fb.FeedbackStateAddCanSend(b, st.CanSend)
|
|
fb.FeedbackStateAddBlockedReason(b, reason)
|
|
if st.Reply != nil {
|
|
fb.FeedbackStateAddReply(b, reply)
|
|
}
|
|
b.Finish(fb.FeedbackStateEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeFeedbackUnread builds a FeedbackUnread payload.
|
|
func encodeFeedbackUnread(u backendclient.FeedbackUnreadResp) []byte {
|
|
b := flatbuffers.NewBuilder(16)
|
|
fb.FeedbackUnreadStart(b)
|
|
fb.FeedbackUnreadAddReplyUnread(b, u.ReplyUnread)
|
|
b.Finish(fb.FeedbackUnreadEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// buildGameView builds a GameView table and returns its offset.
|
|
func buildGameView(b *flatbuffers.Builder, g backendclient.GameResp) flatbuffers.UOffsetT {
|
|
return wire.BuildGameView(b, toWireGame(g))
|
|
}
|
|
|
|
// toWireGame maps a GameResp to the shared wire.GameView.
|
|
func toWireGame(g backendclient.GameResp) wire.GameView {
|
|
seats := make([]wire.SeatView, len(g.Seats))
|
|
for i, s := range g.Seats {
|
|
seats[i] = wire.SeatView{
|
|
Seat: s.Seat,
|
|
AccountID: s.AccountID,
|
|
Score: s.Score,
|
|
HintsUsed: s.HintsUsed,
|
|
IsWinner: s.IsWinner,
|
|
DisplayName: s.DisplayName,
|
|
}
|
|
}
|
|
return wire.GameView{
|
|
ID: g.ID,
|
|
Variant: g.Variant,
|
|
DictVersion: g.DictVersion,
|
|
Status: g.Status,
|
|
Players: g.Players,
|
|
ToMove: g.ToMove,
|
|
TurnTimeoutSecs: g.TurnTimeoutSecs,
|
|
MultipleWordsPerTurn: g.MultipleWordsPerTurn,
|
|
VsAI: g.VsAI,
|
|
MoveCount: g.MoveCount,
|
|
EndReason: g.EndReason,
|
|
Seats: seats,
|
|
LastActivityUnix: g.LastActivityUnix,
|
|
UnreadChat: g.UnreadChat,
|
|
UnreadMessages: g.UnreadMessages,
|
|
}
|
|
}
|
|
|
|
// buildMoveRecord builds a MoveRecord table and returns its offset.
|
|
func buildMoveRecord(b *flatbuffers.Builder, m backendclient.MoveRecordResp) flatbuffers.UOffsetT {
|
|
tiles := make([]wire.TileRecord, len(m.Tiles))
|
|
for i, t := range m.Tiles {
|
|
tiles[i] = wire.TileRecord{Row: t.Row, Col: t.Col, Letter: t.Letter, Blank: t.Blank}
|
|
}
|
|
return wire.BuildMoveRecord(b, wire.MoveRecord{
|
|
Player: m.Player,
|
|
Action: m.Action,
|
|
Dir: m.Dir,
|
|
MainRow: m.MainRow,
|
|
MainCol: m.MainCol,
|
|
Tiles: tiles,
|
|
Words: m.Words,
|
|
Count: m.Count,
|
|
Score: m.Score,
|
|
Total: m.Total,
|
|
})
|
|
}
|
|
|
|
// 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))
|
|
}
|