dkforest

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

commit b054fb176a064547e528d3faf3c52e0b9920eb81
parent ad469cc117d3bb6a75bbad2a4eee20c07b2a9a56
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Sat, 17 Jun 2023 23:42:13 -0700

analyze display progress

Diffstat:
Mpkg/web/handlers/chess.go | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mpkg/web/handlers/interceptors/chess.go | 59++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mpkg/web/middlewares/middlewares.go | 1+
3 files changed, 114 insertions(+), 24 deletions(-)

diff --git a/pkg/web/handlers/chess.go b/pkg/web/handlers/chess.go @@ -128,24 +128,76 @@ func ChessGameAnalyzeHandler(c echo.Context) error { if err != nil { return c.Redirect(http.StatusFound, "/") } - if !interceptors.ChessInstance.SetAnalyzing(key) { - return c.String(http.StatusOK, "already analyzing") - } game := g.Game if game.Outcome() == chess.NoOutcome { return c.String(http.StatusOK, "no outcome") } - t := utils.ParseInt64OrDefault(c.QueryParam("t"), 15) - t = utils.Clamp(t, 1, 60) - res, err := interceptors.AnalyzeGame(game.String(), t) - if err != nil { - return c.String(http.StatusOK, err.Error()) + if interceptors.ChessInstance.SetAnalyzing(key) { + go func() { + res, err := interceptors.AnalyzeGame(g, game.String()) + if err != nil { + logrus.Error(err) + return + } + g.DbChessGame.Stats, _ = json.Marshal(res) + g.DbChessGame.AccuracyWhite = res.WhiteAccuracy + g.DbChessGame.AccuracyBlack = res.BlackAccuracy + g.DbChessGame.DoSave(db) + }() + } + + quit := hutils.CloseSignalChan(c) + + if err := usersStreamsManager.Inst.Add(authUser.ID, "analyze_"+key); err != nil { + return nil + } + defer usersStreamsManager.Inst.Remove(authUser.ID, "analyze_"+key) + + c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) + c.Response().WriteHeader(http.StatusOK) + c.Response().Header().Set("Transfer-Encoding", "chunked") + c.Response().Header().Set("Connection", "keep-alive") + + sub := interceptors.ChessAnalyzeProgressPubSub.Subscribe([]string{"chess_analyze_progress_" + key}) + defer sub.Close() + + _, _ = c.Response().Write([]byte(`<style>html, body { background-color: #222; } +#progress { color: #eee; } +</style>`)) + _, _ = c.Response().Write([]byte(`<div id="progress"></div>`)) + progress := g.GetAnalyzeProgress() + _, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>#progress:after { content: "PROGRESS: %d/%d" }</style>`, progress.Step, progress.Total))) + c.Response().Flush() + +Loop: + for { + select { + case <-quit: + break Loop + default: + } + + progress := g.GetAnalyzeProgress() + if progress.Step > 0 && progress.Step == progress.Total { + _, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>#progress:after { content: "PROGRESS: %d/%d" }</style>`, progress.Step, progress.Total))) + break + } + + _, payload, err := sub.ReceiveTimeout2(1*time.Second, quit) + if err != nil { + if err == pubsub.ErrCancelled { + break Loop + } + continue + } + + _, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>#progress:after { content: "PROGRESS: %d/%d" }</style>`, payload.Step, payload.Total))) + c.Response().Flush() } - g.DbChessGame.Stats, _ = json.Marshal(res) - g.DbChessGame.AccuracyWhite = res.WhiteAccuracy - g.DbChessGame.AccuracyBlack = res.BlackAccuracy - g.DbChessGame.DoSave(db) - return c.Redirect(http.StatusFound, c.Request().Referer()) + + _, _ = c.Response().Write([]byte(fmt.Sprintf(`<a href="/chess/%s">Back</a>`, g.Key))) + c.Response().Flush() + return nil } func ChessGameStatsHandler(c echo.Context) error { diff --git a/pkg/web/handlers/interceptors/chess.go b/pkg/web/handlers/interceptors/chess.go @@ -39,6 +39,12 @@ type ChessMove struct { BestMove string } +type ChessAnalyzeProgress struct { + Step int + Total int +} + +var ChessAnalyzeProgressPubSub = pubsub.NewPubSub[ChessAnalyzeProgress]() var ChessPubSub = pubsub.NewPubSub[ChessMove]() type ChessPlayer struct { @@ -49,15 +55,29 @@ type ChessPlayer struct { } type ChessGame struct { - DbChessGame *database.ChessGame - Key string - Game *chess.Game - lastUpdated time.Time - Player1 *ChessPlayer - Player2 *ChessPlayer - CreatedAt time.Time - piecesCache map[chess.Square]string - analyzing bool + DbChessGame *database.ChessGame + Key string + Game *chess.Game + lastUpdated time.Time + Player1 *ChessPlayer + Player2 *ChessPlayer + CreatedAt time.Time + piecesCache map[chess.Square]string + analyzing bool + mtx sync.RWMutex + analyzeProgrss ChessAnalyzeProgress +} + +func (g *ChessGame) SetAnalyzeProgress(progress ChessAnalyzeProgress) { + g.mtx.Lock() + defer g.mtx.Unlock() + g.analyzeProgrss = progress +} + +func (g *ChessGame) GetAnalyzeProgress() ChessAnalyzeProgress { + g.mtx.RLock() + defer g.mtx.RUnlock() + return g.analyzeProgrss } func newChessPlayer(player database.User) *ChessPlayer { @@ -963,10 +983,21 @@ type AnalyzeResult struct { Scores []Score } -func AnalyzeGame(pgn string, t int64) (out AnalyzeResult, err error) { +func AnalyzeGame(gg *ChessGame, pgn string) (out AnalyzeResult, err error) { pgnOpt, _ := chess.PGN(strings.NewReader(pgn)) g := chess.NewGame(pgnOpt) positions := g.Positions() + nbPosition := len(positions) + + pubProgress := func(step int) { + progress := ChessAnalyzeProgress{Step: step, Total: nbPosition} + gg.SetAnalyzeProgress(progress) + pubKey := "chess_analyze_progress_" + gg.Key + ChessAnalyzeProgressPubSub.Pub(pubKey, progress) + } + defer func() { + pubProgress(nbPosition) + }() eng, err := uci.New("stockfish") if err != nil { @@ -982,19 +1013,23 @@ func AnalyzeGame(pgn string, t int64) (out AnalyzeResult, err error) { scores := make([]Score, 0) cps := make([]int, 0) + t := 15 + moveTime := time.Duration((float64(t)/float64(len(positions)-1))*1000) * time.Millisecond + for idx, position := range positions { // First position is the board without any move played if idx == 0 { continue } cmdPos := uci.CmdPosition{Position: position} - cmdGo := uci.CmdGo{Depth: 24} + cmdGo := uci.CmdGo{MoveTime: moveTime} if err := eng.Run(cmdPos, cmdGo); err != nil { logrus.Error(err) mov := g.MoveHistory()[idx-1].Move moveStr := chess.AlgebraicNotation{}.Encode(positions[idx-1], mov) cps = append(cps, 0) scores = append(scores, Score{Move: moveStr}) + pubProgress(idx) continue } res := eng.SearchResults() @@ -1009,6 +1044,8 @@ func AnalyzeGame(pgn string, t int64) (out AnalyzeResult, err error) { bestMoveStr := chess.UCINotation{}.Encode(position, res.BestMove) cps = append(cps, cp) scores = append(scores, Score{Move: moveStr, BestMove: bestMoveStr, CP: cp, Mate: mate}) + + pubProgress(idx) } //fmt.Println(strings.Join(s, ", ")) diff --git a/pkg/web/middlewares/middlewares.go b/pkg/web/middlewares/middlewares.go @@ -31,6 +31,7 @@ var GzipMiddleware = middleware.GzipWithConfig( c.Path() == "/vip/downloads/:filename" || c.Path() == "/vip/challenges/re-1/:filename" || c.Path() == "/chess/:key" || + c.Path() == "/chess/:key/analyze" || c.Path() == "/api/v1/chat/messages/:roomName/stream" || c.Path() == "/uploads/:filename" || c.Path() == "/" {