dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 9e1e51d7128e3a99a94a54f3b77da9ac5c77df10
parent 8cc15252f11b9bd7d2cbdd189d65b3655f546fdf
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Sun, 17 Dec 2023 07:39:43 -0500

mtx experiment

Diffstat:
Mpkg/utils/utils.go | 25+++++++++++++++++++++++++
Mpkg/web/handlers/poker/poker.go | 264++++++++++++++++++++++++++++++++++++++++---------------------------------------
2 files changed, 159 insertions(+), 130 deletions(-)

diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go @@ -1185,3 +1185,28 @@ func (_ CryptoRandSource) Int63() int64 { } func (_ CryptoRandSource) Seed(_ int64) {} + +type RWMtx[T any] struct { + sync.RWMutex + v T +} + +func NewRWMtx[T any](v T) RWMtx[T] { + return RWMtx[T]{v: v} +} + +func (m *RWMtx[T]) Val() *T { + return &m.v +} + +func (m *RWMtx[T]) RWith(clb func(v *T)) { + m.RLock() + defer m.RUnlock() + clb(&m.v) +} + +func (m *RWMtx[T]) With(clb func(v *T)) { + m.Lock() + defer m.Unlock() + clb(&m.v) +} diff --git a/pkg/web/handlers/poker/poker.go b/pkg/web/handlers/poker/poker.go @@ -57,7 +57,7 @@ func (p *Poker) GetOrCreateGame(db *database.DkfDB, roomID RoomID, pokerTableID PokerTableMinBet: pokerTableMinBet, PokerTableIsTest: pokerTableIsTest, PlayersEventCh: make(chan PlayerEvent), - Players: make([]*SeatedPlayer, NbPlayers), + Players: utils.NewRWMtx(make([]*SeatedPlayer, NbPlayers)), DealerSeatIdx: atomic.Int32{}, } g.DealerSeatIdx.Store(-1) @@ -89,28 +89,29 @@ type PlayerEvent struct { var PokerInstance = NewPoker() type Ongoing struct { - Deck []string - Players []*PokerPlayer - Events []PokerEvent - EventsMtx sync.RWMutex - LogEvents []LogEvent - LogEventsMtx sync.RWMutex - CommunityCards []string - WaitTurnEvent PokerWaitTurnEvent - WaitTurnEventMtx sync.RWMutex - CreatedAt time.Time - mainPot atomic.Uint64 + Deck []string + Players []*PokerPlayer + Events utils.RWMtx[[]PokerEvent] + LogEvents utils.RWMtx[[]LogEvent] + CommunityCards []string + WaitTurnEvent utils.RWMtx[PokerWaitTurnEvent] + CreatedAt time.Time + mainPot atomic.Uint64 } type SeatedPlayer struct { SeatIdx int UserID database.UserID Username database.Username - Cash database.PokerChip + Cash utils.RWMtx[database.PokerChip] LastActionTS time.Time } -func (p *SeatedPlayer) GetCash() database.PokerChip { return p.Cash } +func (p *SeatedPlayer) GetCash() database.PokerChip { + p.Cash.RLock() + defer p.Cash.RUnlock() + return *p.Cash.Val() +} // Return either or not a player is eligible to play a game func (p *SeatedPlayer) isEligible(pokerTableMinBet database.PokerChip) bool { @@ -119,17 +120,20 @@ func (p *SeatedPlayer) isEligible(pokerTableMinBet database.PokerChip) bool { type PokerPlayer struct { *SeatedPlayer - GameBet database.PokerChip - Bet database.PokerChip - AllInMaxGain database.PokerChip - Cards []PlayerCard - CardsMtx sync.RWMutex + Bet utils.RWMtx[database.PokerChip] + Cards utils.RWMtx[[]PlayerCard] Folded atomic.Bool Unsit atomic.Bool + GameBet database.PokerChip + AllInMaxGain database.PokerChip countChancesToAction int } -func (p *PokerPlayer) GetBet() database.PokerChip { return p.Bet } +func (p *PokerPlayer) GetBet() database.PokerChip { + p.Bet.RLock() + defer p.Bet.RUnlock() + return *p.Bet.Val() +} func (p *PokerPlayer) isAllIn() bool { return p.GetCash() == 0 @@ -138,27 +142,27 @@ func (p *PokerPlayer) isAllIn() bool { func (p *PokerPlayer) refundPartialBet(db *database.DkfDB, pokerTableID int64, diff database.PokerChip) { _ = db.PokerTableAccountRefundPartialBet(p.UserID, pokerTableID, diff) p.GameBet -= diff - p.Bet -= diff - p.Cash += diff + p.Bet.With(func(v *database.PokerChip) { *v -= diff }) + p.Cash.With(func(cash *database.PokerChip) { *cash += diff }) } func (p *PokerPlayer) doBet(db *database.DkfDB, pokerTableID int64, bet database.PokerChip) { _ = db.PokerTableAccountBet(p.UserID, pokerTableID, bet) p.GameBet += bet - p.Bet += bet - p.Cash -= bet + p.Bet.With(func(v *database.PokerChip) { *v += bet }) + p.Cash.With(func(cash *database.PokerChip) { *cash -= bet }) } func (p *PokerPlayer) gain(db *database.DkfDB, pokerTableID int64, gain database.PokerChip) { _ = db.PokerTableAccountGain(p.UserID, pokerTableID, gain) - p.Cash += gain - p.Bet = 0 + p.Cash.With(func(cash *database.PokerChip) { *cash += gain }) + p.Bet.With(func(bet *database.PokerChip) { *bet = 0 }) } func (p *PokerPlayer) resetBet() { // Do not track in database // DB keeps track of what was bet during the whole (1 hand) game - p.Bet = 0 + p.Bet.With(func(bet *database.PokerChip) { *bet = 0 }) } func (p *PokerPlayer) refundBet(db *database.DkfDB, pokerTableID int64) { @@ -182,8 +186,7 @@ type PokerGame struct { PokerTableMinBet database.PokerChip PokerTableIsTest bool PlayersEventCh chan PlayerEvent - Players []*SeatedPlayer - PlayersMtx sync.RWMutex + Players utils.RWMtx[[]*SeatedPlayer] Ongoing *Ongoing DealerSeatIdx atomic.Int32 IsGameStarted atomic.Bool @@ -227,10 +230,10 @@ func (g *Ongoing) computeWinners() (winner []GameResult) { continue } - p.CardsMtx.RLock() - playerCard1 := p.Cards[0].Name - playerCard2 := p.Cards[1].Name - p.CardsMtx.RUnlock() + p.Cards.RLock() + playerCard1 := (*p.Cards.Val())[0].Name + playerCard2 := (*p.Cards.Val())[1].Name + p.Cards.RUnlock() if len(g.CommunityCards) != 5 { return []GameResult{} @@ -275,9 +278,9 @@ func sortGameResults(arr []GameResult) { } func (g *Ongoing) AddEvent(evts ...PokerEvent) { - g.EventsMtx.Lock() - defer g.EventsMtx.Unlock() - g.Events = append(g.Events, evts...) + g.Events.With(func(events *[]PokerEvent) { + *events = append(*events, evts...) + }) } func (g *Ongoing) GetDeckStr() string { @@ -332,7 +335,7 @@ func (g *Ongoing) GetPlayer(player database.Username) *PokerPlayer { } func (g *PokerGame) getPlayer(username database.Username) (out *SeatedPlayer) { - for _, p := range g.Players { + for _, p := range *g.Players.Val() { if p != nil && p.Username == username { return p } @@ -341,8 +344,8 @@ func (g *PokerGame) getPlayer(username database.Username) (out *SeatedPlayer) { } func (g *PokerGame) IsSeated(player database.Username) bool { - g.PlayersMtx.RLock() - defer g.PlayersMtx.RUnlock() + g.Players.RLock() + defer g.Players.RUnlock() return g.isSeated(player) } @@ -400,8 +403,8 @@ func (g *PokerGame) UnSitPlayer(username database.Username) error { } } - g.PlayersMtx.Lock() - defer g.PlayersMtx.Unlock() + g.Players.Lock() + defer g.Players.Unlock() if p := g.getPlayer(username); p != nil { return g.UnSitPlayer1(p) } @@ -437,29 +440,29 @@ func (g *PokerGame) UnSitPlayer1(seatedPlayer *SeatedPlayer) error { default: } player.Folded.Store(true) - player.CardsMtx.RLock() - for _, card := range player.Cards { - evt := PokerEvent{ID: "card" + itoa(card.Idx), Name: "", Idx: card.Idx, Top: BurnStackY, Left: BurnStackX, Angle: "0deg", Reveal: false} - PokerPubSub.Pub(roomTopic, evt) - ongoing.AddEvent(evt) - } - player.CardsMtx.RUnlock() + player.Cards.RWith(func(playerCards *[]PlayerCard) { + for _, card := range *playerCards { + evt := PokerEvent{ID: "card" + itoa(card.Idx), Name: "", Idx: card.Idx, Top: BurnStackY, Left: BurnStackX, Angle: "0deg", Reveal: false} + PokerPubSub.Pub(roomTopic, evt) + ongoing.AddEvent(evt) + } + }) } } - g.Players[seatedPlayer.SeatIdx] = nil + (*g.Players.Val())[seatedPlayer.SeatIdx] = nil return nil } func (g *PokerGame) SitPlayer(authUser *database.User, pos int, chips database.PokerChip) error { - g.PlayersMtx.Lock() - defer g.PlayersMtx.Unlock() + g.Players.Lock() + defer g.Players.Unlock() if g.isSeated(authUser.Username) { return errors.New("player already seated") } - if g.Players[pos] != nil { + if (*g.Players.Val())[pos] != nil { return errors.New("seat already taken") } - g.Players[pos] = &SeatedPlayer{SeatIdx: pos, UserID: authUser.ID, Username: authUser.Username, Cash: chips, LastActionTS: time.Now()} + (*g.Players.Val())[pos] = &SeatedPlayer{SeatIdx: pos, UserID: authUser.ID, Username: authUser.Username, Cash: utils.NewRWMtx(chips), LastActionTS: time.Now()} return nil } @@ -474,15 +477,15 @@ func NewOngoing(g *PokerGame) *Ongoing { utils.Shuffle1(r, deck) players := make([]*PokerPlayer, 0) - g.PlayersMtx.RLock() - for _, p := range g.Players { + g.Players.RLock() + for _, p := range *g.Players.Val() { if p.isEligible(g.PokerTableMinBet) { players = append(players, &PokerPlayer{SeatedPlayer: p}) } } - g.PlayersMtx.RUnlock() + g.Players.RUnlock() - return &Ongoing{Deck: deck, Players: players, WaitTurnEvent: PokerWaitTurnEvent{Idx: -1}, CreatedAt: time.Now()} + return &Ongoing{Deck: deck, Players: players, WaitTurnEvent: utils.NewRWMtx(PokerWaitTurnEvent{Idx: -1}), CreatedAt: time.Now()} } func (g *PokerGame) newLogEvent(msg string) { @@ -490,9 +493,9 @@ func (g *PokerGame) newLogEvent(msg string) { logEvt := LogEvent{Message: msg} PokerPubSub.Pub(g.RoomID.LogsTopic(), logEvt) if ongoing != nil { - ongoing.LogEventsMtx.Lock() - ongoing.LogEvents = append(ongoing.LogEvents, logEvt) - ongoing.LogEventsMtx.Unlock() + ongoing.LogEvents.With(func(v *[]LogEvent) { + *v = append(*v, logEvt) + }) } } @@ -501,10 +504,10 @@ func showCards(g *PokerGame, seats []Seat) { roomTopic := g.RoomID.Topic() for _, p := range ongoing.Players { if !p.Folded.Load() { - p.CardsMtx.RLock() - firstCard := p.Cards[0] - secondCard := p.Cards[1] - p.CardsMtx.RUnlock() + p.Cards.RLock() + firstCard := (*p.Cards.Val())[0] + secondCard := (*p.Cards.Val())[1] + p.Cards.RUnlock() seatData := seats[p.SeatIdx] if p.SeatIdx == 0 { seatData.Left -= 30 @@ -528,9 +531,9 @@ func setWaitTurn(g *PokerGame, seatIdx int) { evt := PokerWaitTurnEvent{Idx: seatIdx, CreatedAt: time.Now()} PokerPubSub.Pub(roomTopic, evt) - ongoing.WaitTurnEventMtx.Lock() - ongoing.WaitTurnEvent = evt - ongoing.WaitTurnEventMtx.Unlock() + ongoing.WaitTurnEvent.With(func(v *PokerWaitTurnEvent) { + *v = evt + }) } const ( @@ -569,10 +572,10 @@ func execBettingRound(g *PokerGame, skip int, minBet database.PokerChip) bool { foldPlayer := func(p *PokerPlayer) { p.Folded.Store(true) - p.CardsMtx.RLock() - evt1 := PokerEvent{ID: "card" + itoa(p.Cards[0].Idx), Name: "", Idx: p.Cards[0].Idx, Top: BurnStackY, Left: BurnStackX, Angle: "0deg", Reveal: false} - evt2 := PokerEvent{ID: "card" + itoa(p.Cards[1].Idx), Name: "", Idx: p.Cards[1].Idx, Top: BurnStackY, Left: BurnStackX, Angle: "0deg", Reveal: false} - p.CardsMtx.RUnlock() + p.Cards.RLock() + evt1 := PokerEvent{ID: "card" + itoa((*p.Cards.Val())[0].Idx), Name: "", Idx: (*p.Cards.Val())[0].Idx, Top: BurnStackY, Left: BurnStackX, Angle: "0deg", Reveal: false} + evt2 := PokerEvent{ID: "card" + itoa((*p.Cards.Val())[1].Idx), Name: "", Idx: (*p.Cards.Val())[1].Idx, Top: BurnStackY, Left: BurnStackX, Angle: "0deg", Reveal: false} + p.Cards.RUnlock() PokerPubSub.Pub(roomTopic, evt1) PokerPubSub.Pub(roomTopic, evt2) ongoing.AddEvent(evt1, evt2) @@ -943,9 +946,9 @@ func dealPlayersCards(g *PokerGame, seats []Seat, idx *int) { PokerPubSub.Pub(roomTopic, evt) PokerPubSub.Pub(roomUserTopic, evt1) - p.CardsMtx.Lock() - p.Cards = append(p.Cards, PlayerCard{Idx: *idx, Name: card}) - p.CardsMtx.Unlock() + p.Cards.With(func(pCards *[]PlayerCard) { + *pCards = append(*pCards, PlayerCard{Idx: *idx, Name: card}) + }) ongoing.AddEvent(evt, evt1) } @@ -1118,9 +1121,9 @@ func autoUnsitInactivePlayers(g *PokerGame) { ongoing := g.Ongoing pokerTableMinBet := g.PokerTableMinBet roomTopic := g.RoomID.Topic() - g.PlayersMtx.Lock() - defer g.PlayersMtx.Unlock() - for _, p := range g.Players { + g.Players.Lock() + defer g.Players.Unlock() + for _, p := range *g.Players.Val() { if p != nil { playerShallBeBooted := false if !p.isEligible(pokerTableMinBet) { @@ -1312,9 +1315,9 @@ func (g *PokerGame) Deal(roomID RoomID, authUser *database.User) { } func (g *PokerGame) CountEligibleSeated() (count int) { - g.PlayersMtx.RLock() - defer g.PlayersMtx.RUnlock() - for _, p := range g.Players { + g.Players.RLock() + defer g.Players.RUnlock() + for _, p := range *g.Players.Val() { if p.isEligible(g.PokerTableMinBet) { count++ } @@ -1323,9 +1326,9 @@ func (g *PokerGame) CountEligibleSeated() (count int) { } func (g *PokerGame) CountSeated() (count int) { - g.PlayersMtx.RLock() - defer g.PlayersMtx.RUnlock() - for _, p := range g.Players { + g.Players.RLock() + defer g.Players.RUnlock() + for _, p := range *g.Players.Val() { if p != nil { count++ } @@ -1653,20 +1656,21 @@ func buildBaseHtml(g *PokerGame, authUser *database.User, playerBuyIn database.P html += buildCountdownsHtml() if ongoing != nil { - ongoing.WaitTurnEventMtx.RLock() - html += drawCountDownStyle(ongoing.WaitTurnEvent) - ongoing.WaitTurnEventMtx.RUnlock() - - ongoing.EventsMtx.RLock() - for _, evt := range ongoing.Events { - if evt.Player == "" { - html += getPokerEventHtml(evt, "0s") - } - if evt.Player == authUser.Username { - html += getPokerEventHtml(evt, "0s") + + ongoing.WaitTurnEvent.RWith(func(v *PokerWaitTurnEvent) { + html += drawCountDownStyle(*v) + }) + + ongoing.Events.RWith(func(v *[]PokerEvent) { + for _, evt := range *v { + if evt.Player == "" { + html += getPokerEventHtml(evt, "0s") + } + if evt.Player == authUser.Username { + html += getPokerEventHtml(evt, "0s") + } } - } - ongoing.EventsMtx.RUnlock() + }) } return } @@ -1824,13 +1828,13 @@ func buildActionsDiv(roomID RoomID) (html string) { } func buildSeatsHtml(g *PokerGame, authUser *database.User, playerBuyIn database.PokerChip) (html string) { - g.PlayersMtx.RLock() - defer g.PlayersMtx.RUnlock() - for i := range g.Players { + g.Players.RLock() + defer g.Players.RUnlock() + for i := range *g.Players.Val() { html += `<div id="seat` + itoa(i+1) + `Pot" class="seatPot"></div>` } html += `<div>` - for i := range g.Players { + for i := range *g.Players.Val() { html += `<div class="seat" id="seat` + itoa(i+1) + `"> <iframe src="/poker/` + g.RoomID.String() + `/sit/` + itoa(i+1) + `?buy-in=` + itoa2(playerBuyIn) + `" class="takeSeat takeSeat` + itoa(i+1) + `"></iframe>` html += ` <div class="inner"></div>` @@ -1846,32 +1850,32 @@ func drawSeatsStyle(authUser *database.User, g *PokerGame) string { ongoing := g.Ongoing html := "<style>" seated := g.IsSeated(authUser.Username) - g.PlayersMtx.RLock() - defer g.PlayersMtx.RUnlock() - for i, p := range g.Players { - if p != nil || seated { - html += `.takeSeat` + itoa(i+1) + ` { display: none; }` - } else { - html += `.takeSeat` + itoa(i+1) + ` { display: block; }` - } - if p != nil { - pUsername := p.Username - if pUsername == authUser.Username { - html += `#seat` + itoa(i+1) + ` { border: 2px solid #0d1b8f; }` + g.Players.RWith(func(players *[]*SeatedPlayer) { + for i, p := range *players { + if p != nil || seated { + html += `.takeSeat` + itoa(i+1) + ` { display: none; }` + } else { + html += `.takeSeat` + itoa(i+1) + ` { display: block; }` } - html += `#seat` + itoa(i+1) + ` .inner:before { content: "` + pUsername.String() + `"; }` - html += `#seat` + itoa(i+1) + `_cash:before { content: "` + itoa2(p.GetCash()) + `"; }` - if ongoing != nil { - if op := ongoing.GetPlayer(pUsername); op != nil && op.GetBet() > 0 { - html += `#seat` + itoa(i+1) + `Pot:before { content: "` + itoa2(op.GetBet()) + `"; }` + if p != nil { + pUsername := p.Username + if pUsername == authUser.Username { + html += `#seat` + itoa(i+1) + ` { border: 2px solid #0d1b8f; }` } + html += `#seat` + itoa(i+1) + ` .inner:before { content: "` + pUsername.String() + `"; }` + html += `#seat` + itoa(i+1) + `_cash:before { content: "` + itoa2(p.GetCash()) + `"; }` + if ongoing != nil { + if op := ongoing.GetPlayer(pUsername); op != nil && op.GetBet() > 0 { + html += `#seat` + itoa(i+1) + `Pot:before { content: "` + itoa2(op.GetBet()) + `"; }` + } + } + } else { + html += `#seat` + itoa(i+1) + ` { border: 1px solid #333; }` + html += `#seat` + itoa(i+1) + ` .inner:before { content: ""; }` + html += `#seat` + itoa(i+1) + `_cash:before { content: ""; }` } - } else { - html += `#seat` + itoa(i+1) + ` { border: 1px solid #333; }` - html += `#seat` + itoa(i+1) + ` .inner:before { content: ""; }` - html += `#seat` + itoa(i+1) + `_cash:before { content: ""; }` } - } + }) html += "</style>" return html } @@ -1912,10 +1916,10 @@ func drawGameStartedEvent(evt GameStartedEvent, authUser *database.User) (html s } func drawGameIsDoneHtml(g *PokerGame, evt GameIsDoneEvent) (html string) { - g.PlayersMtx.RLock() - defer g.PlayersMtx.RUnlock() + g.Players.RLock() + defer g.Players.RUnlock() html += `<style>` - for i, p := range g.Players { + for i, p := range *g.Players.Val() { if p != nil { html += `#seat` + itoa(i+1) + `_cash:before { content: "` + itoa2(p.GetCash()) + `"; }` } @@ -2265,11 +2269,11 @@ func PokerLogsHandler(c echo.Context) error { send(hutils.HtmlCssReset) send(`<style>body { background-color: #ccc; }</style><div style="display:flex;flex-direction:column-reverse;">`) if ongoing != nil { - ongoing.LogEventsMtx.RLock() - for _, evt := range ongoing.LogEvents { - send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) - } - ongoing.LogEventsMtx.RUnlock() + ongoing.LogEvents.RWith(func(v *[]LogEvent) { + for _, evt := range *v { + send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) + } + }) } c.Response().Flush()