commit b054fb176a064547e528d3faf3c52e0b9920eb81
parent ad469cc117d3bb6a75bbad2a4eee20c07b2a9a56
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Sat, 17 Jun 2023 23:42:13 -0700
analyze display progress
Diffstat:
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() == "/" {