commit 9e1e51d7128e3a99a94a54f3b77da9ac5c77df10
parent 8cc15252f11b9bd7d2cbdd189d65b3655f546fdf
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Sun, 17 Dec 2023 07:39:43 -0500
mtx experiment
Diffstat:
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()