diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/admin/handler.go | 20 | ||||
| -rw-r--r-- | backend/api/generated.go | 164 | ||||
| -rw-r--r-- | backend/api/handler.go | 21 | ||||
| -rw-r--r-- | backend/db/query.sql.go | 13 | ||||
| -rw-r--r-- | backend/game/hub.go | 296 | ||||
| -rw-r--r-- | backend/game/message.go | 11 | ||||
| -rw-r--r-- | backend/query.sql | 5 |
7 files changed, 291 insertions, 239 deletions
diff --git a/backend/admin/handler.go b/backend/admin/handler.go index 5398107..41eacd4 100644 --- a/backend/admin/handler.go +++ b/backend/admin/handler.go @@ -229,16 +229,6 @@ func (h *Handler) postGameEdit(c echo.Context) error { } } - { - // TODO: - if state != row.State && state == "starting" { - err := h.hubs.StartGame(int(gameID)) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - } - err = h.q.UpdateGame(c.Request().Context(), db.UpdateGameParams{ GameID: int32(gameID), GameType: gameType, @@ -252,5 +242,15 @@ func (h *Handler) postGameEdit(c echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + { + // TODO: + if state != row.State && state == "starting" { + err := h.hubs.StartGame(int(gameID)) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + } + } + return c.Redirect(http.StatusSeeOther, c.Request().URL.Path) } diff --git a/backend/api/generated.go b/backend/api/generated.go index ac536c0..7eb5940 100644 --- a/backend/api/generated.go +++ b/backend/api/generated.go @@ -40,13 +40,23 @@ const ( // Defines values for GamePlayerMessageS2CExecResultPayloadStatus. const ( GamePlayerMessageS2CExecResultPayloadStatusCompileError GamePlayerMessageS2CExecResultPayloadStatus = "compile_error" - GamePlayerMessageS2CExecResultPayloadStatusFailure GamePlayerMessageS2CExecResultPayloadStatus = "failure" GamePlayerMessageS2CExecResultPayloadStatusInternalError GamePlayerMessageS2CExecResultPayloadStatus = "internal_error" + GamePlayerMessageS2CExecResultPayloadStatusRuntimeError GamePlayerMessageS2CExecResultPayloadStatus = "runtime_error" GamePlayerMessageS2CExecResultPayloadStatusSuccess GamePlayerMessageS2CExecResultPayloadStatus = "success" GamePlayerMessageS2CExecResultPayloadStatusTimeout GamePlayerMessageS2CExecResultPayloadStatus = "timeout" GamePlayerMessageS2CExecResultPayloadStatusWrongAnswer GamePlayerMessageS2CExecResultPayloadStatus = "wrong_answer" ) +// Defines values for GamePlayerMessageS2CSubmitResultPayloadStatus. +const ( + GamePlayerMessageS2CSubmitResultPayloadStatusCompileError GamePlayerMessageS2CSubmitResultPayloadStatus = "compile_error" + GamePlayerMessageS2CSubmitResultPayloadStatusInternalError GamePlayerMessageS2CSubmitResultPayloadStatus = "internal_error" + GamePlayerMessageS2CSubmitResultPayloadStatusRuntimeError GamePlayerMessageS2CSubmitResultPayloadStatus = "runtime_error" + GamePlayerMessageS2CSubmitResultPayloadStatusSuccess GamePlayerMessageS2CSubmitResultPayloadStatus = "success" + GamePlayerMessageS2CSubmitResultPayloadStatusTimeout GamePlayerMessageS2CSubmitResultPayloadStatus = "timeout" + GamePlayerMessageS2CSubmitResultPayloadStatusWrongAnswer GamePlayerMessageS2CSubmitResultPayloadStatus = "wrong_answer" +) + // Defines values for GameWatcherMessageS2CExecResultPayloadStatus. const ( GameWatcherMessageS2CExecResultPayloadStatusCompileError GameWatcherMessageS2CExecResultPayloadStatus = "compile_error" @@ -59,12 +69,12 @@ const ( // Defines values for GameWatcherMessageS2CSubmitResultPayloadStatus. const ( - CompileError GameWatcherMessageS2CSubmitResultPayloadStatus = "compile_error" - InternalError GameWatcherMessageS2CSubmitResultPayloadStatus = "internal_error" - RuntimeError GameWatcherMessageS2CSubmitResultPayloadStatus = "runtime_error" - Success GameWatcherMessageS2CSubmitResultPayloadStatus = "success" - Timeout GameWatcherMessageS2CSubmitResultPayloadStatus = "timeout" - WrongAnswer GameWatcherMessageS2CSubmitResultPayloadStatus = "wrong_answer" + GameWatcherMessageS2CSubmitResultPayloadStatusCompileError GameWatcherMessageS2CSubmitResultPayloadStatus = "compile_error" + GameWatcherMessageS2CSubmitResultPayloadStatusInternalError GameWatcherMessageS2CSubmitResultPayloadStatus = "internal_error" + GameWatcherMessageS2CSubmitResultPayloadStatusRuntimeError GameWatcherMessageS2CSubmitResultPayloadStatus = "runtime_error" + GameWatcherMessageS2CSubmitResultPayloadStatusSuccess GameWatcherMessageS2CSubmitResultPayloadStatus = "success" + GameWatcherMessageS2CSubmitResultPayloadStatusTimeout GameWatcherMessageS2CSubmitResultPayloadStatus = "timeout" + GameWatcherMessageS2CSubmitResultPayloadStatusWrongAnswer GameWatcherMessageS2CSubmitResultPayloadStatus = "wrong_answer" ) // Error defines model for Error. @@ -72,17 +82,23 @@ type Error struct { Message string `json:"message"` } +// ExecStep defines model for ExecStep. +type ExecStep struct { + Label string `json:"label"` + TestcaseID nullable.Nullable[int] `json:"testcase_id"` +} + // Game defines model for Game. type Game struct { - DisplayName string `json:"display_name"` - DurationSeconds int `json:"duration_seconds"` - GameID int `json:"game_id"` - GameType GameGameType `json:"game_type"` - Players []User `json:"players"` - Problem Problem `json:"problem"` - StartedAt *int `json:"started_at,omitempty"` - State GameState `json:"state"` - VerificationSteps []VerificationStep `json:"verification_steps"` + DisplayName string `json:"display_name"` + DurationSeconds int `json:"duration_seconds"` + ExecSteps []ExecStep `json:"exec_steps"` + GameID int `json:"game_id"` + GameType GameGameType `json:"game_type"` + Players []User `json:"players"` + Problem Problem `json:"problem"` + StartedAt *int64 `json:"started_at,omitempty"` + State GameState `json:"state"` } // GameGameType defines model for Game.GameType. @@ -136,8 +152,10 @@ type GamePlayerMessageS2CExecResult struct { // GamePlayerMessageS2CExecResultPayload defines model for GamePlayerMessageS2CExecResultPayload. type GamePlayerMessageS2CExecResultPayload struct { - Score nullable.Nullable[int] `json:"score"` - Status GamePlayerMessageS2CExecResultPayloadStatus `json:"status"` + Status GamePlayerMessageS2CExecResultPayloadStatus `json:"status"` + Stderr string `json:"stderr"` + Stdout string `json:"stdout"` + TestcaseID nullable.Nullable[int] `json:"testcase_id"` } // GamePlayerMessageS2CExecResultPayloadStatus defines model for GamePlayerMessageS2CExecResultPayload.Status. @@ -151,9 +169,24 @@ type GamePlayerMessageS2CStart struct { // GamePlayerMessageS2CStartPayload defines model for GamePlayerMessageS2CStartPayload. type GamePlayerMessageS2CStartPayload struct { - StartAt int `json:"start_at"` + StartAt int64 `json:"start_at"` +} + +// GamePlayerMessageS2CSubmitResult defines model for GamePlayerMessageS2CSubmitResult. +type GamePlayerMessageS2CSubmitResult struct { + Data GamePlayerMessageS2CSubmitResultPayload `json:"data"` + Type string `json:"type"` } +// GamePlayerMessageS2CSubmitResultPayload defines model for GamePlayerMessageS2CSubmitResultPayload. +type GamePlayerMessageS2CSubmitResultPayload struct { + Score nullable.Nullable[int] `json:"score"` + Status GamePlayerMessageS2CSubmitResultPayloadStatus `json:"status"` +} + +// GamePlayerMessageS2CSubmitResultPayloadStatus defines model for GamePlayerMessageS2CSubmitResultPayload.Status. +type GamePlayerMessageS2CSubmitResultPayloadStatus string + // GameWatcherMessage defines model for GameWatcherMessage. type GameWatcherMessage struct { union json.RawMessage @@ -202,7 +235,7 @@ type GameWatcherMessageS2CStart struct { // GameWatcherMessageS2CStartPayload defines model for GameWatcherMessageS2CStartPayload. type GameWatcherMessageS2CStartPayload struct { - StartAt int `json:"start_at"` + StartAt int64 `json:"start_at"` } // GameWatcherMessageS2CSubmit defines model for GameWatcherMessageS2CSubmit. @@ -213,8 +246,7 @@ type GameWatcherMessageS2CSubmit struct { // GameWatcherMessageS2CSubmitPayload defines model for GameWatcherMessageS2CSubmitPayload. type GameWatcherMessageS2CSubmitPayload struct { - PlayerID int `json:"player_id"` - PreliminaryScore int `json:"preliminary_score"` + PlayerID int `json:"player_id"` } // GameWatcherMessageS2CSubmitResult defines model for GameWatcherMessageS2CSubmitResult. @@ -226,6 +258,7 @@ type GameWatcherMessageS2CSubmitResult struct { // GameWatcherMessageS2CSubmitResultPayload defines model for GameWatcherMessageS2CSubmitResultPayload. type GameWatcherMessageS2CSubmitResultPayload struct { PlayerID int `json:"player_id"` + Score nullable.Nullable[int] `json:"score"` Status GameWatcherMessageS2CSubmitResultPayloadStatus `json:"status"` } @@ -248,12 +281,6 @@ type User struct { Username string `json:"username"` } -// VerificationStep defines model for VerificationStep. -type VerificationStep struct { - Label string `json:"label"` - TestcaseID nullable.Nullable[int] `json:"testcase_id"` -} - // HeaderAuthorization defines model for header_authorization. type HeaderAuthorization = string @@ -473,6 +500,32 @@ func (t *GamePlayerMessageS2C) MergeGamePlayerMessageS2CExecResult(v GamePlayerM return err } +// AsGamePlayerMessageS2CSubmitResult returns the union data inside the GamePlayerMessageS2C as a GamePlayerMessageS2CSubmitResult +func (t GamePlayerMessageS2C) AsGamePlayerMessageS2CSubmitResult() (GamePlayerMessageS2CSubmitResult, error) { + var body GamePlayerMessageS2CSubmitResult + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromGamePlayerMessageS2CSubmitResult overwrites any union data inside the GamePlayerMessageS2C as the provided GamePlayerMessageS2CSubmitResult +func (t *GamePlayerMessageS2C) FromGamePlayerMessageS2CSubmitResult(v GamePlayerMessageS2CSubmitResult) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeGamePlayerMessageS2CSubmitResult performs a merge with any union data inside the GamePlayerMessageS2C, using the provided GamePlayerMessageS2CSubmitResult +func (t *GamePlayerMessageS2C) MergeGamePlayerMessageS2CSubmitResult(v GamePlayerMessageS2CSubmitResult) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + func (t GamePlayerMessageS2C) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err @@ -1108,33 +1161,34 @@ func (sh *strictHandler) GetToken(ctx echo.Context, params GetTokenParams) error // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/backend/api/handler.go b/backend/api/handler.go index 4150ba6..a0ecd4c 100644 --- a/backend/api/handler.go +++ b/backend/api/handler.go @@ -84,9 +84,9 @@ func (h *Handler) GetGames(ctx context.Context, _ GetGamesRequestObject, user *a } games := make([]Game, len(gameRows)) for i, row := range gameRows { - var startedAt *int + var startedAt *int64 if row.StartedAt.Valid { - startedAtTimestamp := int(row.StartedAt.Time.Unix()) + startedAtTimestamp := row.StartedAt.Time.Unix() startedAt = &startedAtTimestamp } games[i] = Game{ @@ -123,9 +123,9 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use } return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - var startedAt *int + var startedAt *int64 if row.StartedAt.Valid { - startedAtTimestamp := int(row.StartedAt.Time.Unix()) + startedAtTimestamp := row.StartedAt.Time.Unix() startedAt = &startedAtTimestamp } playerRows, err := h.q.ListGamePlayers(ctx, int32(gameID)) @@ -146,12 +146,13 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use if err != nil { return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - verificationSteps := make([]VerificationStep, len(testcaseIDs)+1) - verificationSteps[0] = VerificationStep{ - Label: "Compile", + execSteps := make([]ExecStep, len(testcaseIDs)+1) + execSteps[0] = ExecStep{ + TestcaseID: nullable.NewNullNullable[int](), + Label: "Compile", } for i, testcaseID := range testcaseIDs { - verificationSteps[i+1] = VerificationStep{ + execSteps[i+1] = ExecStep{ TestcaseID: nullable.NewNullableWithValue(int(testcaseID)), Label: fmt.Sprintf("Testcase %d", i+1), } @@ -168,8 +169,8 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use Title: row.Title, Description: row.Description, }, - Players: players, - VerificationSteps: verificationSteps, + Players: players, + ExecSteps: execSteps, } return GetGame200JSONResponse{ Game: game, diff --git a/backend/db/query.sql.go b/backend/db/query.sql.go index 583389e..34b0ae9 100644 --- a/backend/db/query.sql.go +++ b/backend/db/query.sql.go @@ -174,6 +174,19 @@ func (q *Queries) GetGameByID(ctx context.Context, gameID int32) (GetGameByIDRow return i, err } +const getSubmissionCodeSizeByID = `-- name: GetSubmissionCodeSizeByID :one +SELECT code_size FROM submissions +WHERE submission_id = $1 +LIMIT 1 +` + +func (q *Queries) GetSubmissionCodeSizeByID(ctx context.Context, submissionID int32) (int32, error) { + row := q.db.QueryRow(ctx, getSubmissionCodeSizeByID, submissionID) + var code_size int32 + err := row.Scan(&code_size) + return code_size, err +} + const getUserAuthByUsername = `-- name: GetUserAuthByUsername :one SELECT users.user_id, username, display_name, icon_path, is_admin, created_at, user_auth_id, user_auths.user_id, auth_type, password_hash FROM users JOIN user_auths ON users.user_id = user_auths.user_id diff --git a/backend/game/hub.go b/backend/game/hub.go index 97303ec..7c06e05 100644 --- a/backend/game/hub.go +++ b/backend/game/hub.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log" + "regexp" "strings" "time" @@ -86,7 +87,7 @@ func (hub *gameHub) run() { // TODO: assert game state is gaming log.Printf("submit: %v", message.message) code := msg.Data.Code - codeSize := len(code) // TODO: exclude whitespaces. + codeSize := calcCodeSize(code) codeHash := calcHash(code) if err := hub.taskQueue.EnqueueTaskCreateSubmissionRecord( hub.game.gameID, @@ -101,8 +102,7 @@ func (hub *gameHub) run() { hub.broadcastToWatchers(&watcherMessageS2CSubmit{ Type: watcherMessageTypeS2CSubmit, Data: watcherMessageS2CSubmitPayload{ - PlayerID: message.client.playerID, - PreliminaryScore: codeSize, + PlayerID: message.client.playerID, }, }) default: @@ -138,6 +138,55 @@ func (hub *gameHub) run() { } } +func (hub *gameHub) sendExecResultMessage(playerID int, testcaseID nullable.Nullable[int], status string, stdout string, stderr string) { + hub.sendToPlayer(playerID, &playerMessageS2CExecResult{ + Type: playerMessageTypeS2CExecResult, + Data: playerMessageS2CExecResultPayload{ + TestcaseID: testcaseID, + Status: api.GamePlayerMessageS2CExecResultPayloadStatus(status), + Stdout: stdout, + Stderr: stderr, + }, + }) + hub.broadcastToWatchers(&watcherMessageS2CExecResult{ + Type: watcherMessageTypeS2CExecResult, + Data: watcherMessageS2CExecResultPayload{ + PlayerID: playerID, + TestcaseID: testcaseID, + Status: api.GameWatcherMessageS2CExecResultPayloadStatus(status), + Stdout: stdout, + Stderr: stderr, + }, + }) +} + +func (hub *gameHub) sendSubmitResult(playerID int, status string, score nullable.Nullable[int]) { + hub.sendToPlayer(playerID, &playerMessageS2CSubmitResult{ + Type: playerMessageTypeS2CSubmitResult, + Data: playerMessageS2CSubmitResultPayload{ + Status: api.GamePlayerMessageS2CSubmitResultPayloadStatus(status), + Score: score, + }, + }) + hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ + Type: watcherMessageTypeS2CSubmitResult, + Data: watcherMessageS2CSubmitResultPayload{ + PlayerID: playerID, + Status: api.GameWatcherMessageS2CSubmitResultPayloadStatus(status), + Score: score, + }, + }) +} + +func (hub *gameHub) sendToPlayer(playerID int, msg playerMessageS2C) { + for player := range hub.players { + if player.playerID == playerID { + player.s2cMessages <- msg + return + } + } +} + func (hub *gameHub) broadcastToWatchers(msg watcherMessageS2C) { for watcher := range hub.watchers { watcher.s2cMessages <- msg @@ -160,100 +209,52 @@ func (hub *gameHub) processTaskResults() { case *taskqueue.TaskResultCreateSubmissionRecord: err := hub.processTaskResultCreateSubmissionRecord(taskResult) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), - }, - } - } - hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ - Type: watcherMessageTypeS2CSubmitResult, - Data: watcherMessageS2CSubmitResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CSubmitResultPayloadStatus(err.Status), - }, - }) + hub.sendSubmitResult( + taskResult.TaskPayload.UserID(), + err.Status, + nullable.NewNullNullable[int](), + ) } case *taskqueue.TaskResultCompileSwiftToWasm: err := hub.processTaskResultCompileSwiftToWasm(taskResult) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), - }, - } - } - hub.broadcastToWatchers(&watcherMessageS2CExecResult{ - Type: watcherMessageTypeS2CExecResult, - Data: watcherMessageS2CExecResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CExecResultPayloadStatus(err.Status), - Stdout: err.Stdout, - Stderr: err.Stderr, - }, - }) - hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ - Type: watcherMessageTypeS2CSubmitResult, - Data: watcherMessageS2CSubmitResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CSubmitResultPayloadStatus(err.Status), - }, - }) + hub.sendExecResultMessage( + taskResult.TaskPayload.UserID(), + nullable.NewNullNullable[int](), + err.Status, + err.Stdout, + err.Stderr, + ) + hub.sendSubmitResult( + taskResult.TaskPayload.UserID(), + err.Status, + nullable.NewNullNullable[int](), + ) } case *taskqueue.TaskResultCompileWasmToNativeExecutable: err := hub.processTaskResultCompileWasmToNativeExecutable(taskResult) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), - }, - } - } - hub.broadcastToWatchers(&watcherMessageS2CExecResult{ - Type: watcherMessageTypeS2CExecResult, - Data: watcherMessageS2CExecResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CExecResultPayloadStatus(err.Status), - Stdout: err.Stdout, - Stderr: err.Stderr, - }, - }) - hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ - Type: watcherMessageTypeS2CSubmitResult, - Data: watcherMessageS2CSubmitResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CSubmitResultPayloadStatus(err.Status), - }, - }) + hub.sendExecResultMessage( + taskResult.TaskPayload.UserID(), + nullable.NewNullNullable[int](), + err.Status, + err.Stdout, + err.Stderr, + ) + hub.sendSubmitResult( + taskResult.TaskPayload.UserID(), + err.Status, + nullable.NewNullNullable[int](), + ) } else { - hub.broadcastToWatchers(&watcherMessageS2CExecResult{ - Type: watcherMessageTypeS2CExecResult, - Data: watcherMessageS2CExecResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CExecResultPayloadStatus("success"), - // TODO: inherit the command stdout/stderr. - Stdout: "Successfully compiled", - Stderr: "", - }, - }) + hub.sendExecResultMessage( + taskResult.TaskPayload.UserID(), + nullable.NewNullNullable[int](), + "success", + // TODO: inherit the command stdout/stderr. + "Successfully compiled", + "", + ) } case *taskqueue.TaskResultRunTestcase: // FIXME: error handling @@ -269,82 +270,52 @@ func (hub *gameHub) processTaskResults() { Stderr: "", }) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus("internal_error"), - }, - } - } - hub.broadcastToWatchers(&watcherMessageS2CExecResult{ - Type: watcherMessageTypeS2CExecResult, - Data: watcherMessageS2CExecResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - TestcaseID: nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), - Status: api.GameWatcherMessageS2CExecResultPayloadStatus("internal_error"), - // TODO: inherit the command stdout/stderr? - Stdout: "", - Stderr: "internal error", - }, - }) - hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ - Type: watcherMessageTypeS2CSubmitResult, - Data: watcherMessageS2CSubmitResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CSubmitResultPayloadStatus("internal_error"), - }, - }) + hub.sendExecResultMessage( + taskResult.TaskPayload.UserID(), + nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), + "internal_error", + // TODO: inherit the command stdout/stderr? + "", + "internal error", + ) + hub.sendSubmitResult( + taskResult.TaskPayload.UserID(), + "internal_error", + nullable.NewNullNullable[int](), + ) continue } - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(aggregatedStatus), - }, - } - } if err1 != nil { - hub.broadcastToWatchers(&watcherMessageS2CExecResult{ - Type: watcherMessageTypeS2CExecResult, - Data: watcherMessageS2CExecResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - TestcaseID: nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), - Status: api.GameWatcherMessageS2CExecResultPayloadStatus(err1.Status), - Stdout: err1.Stdout, - Stderr: err1.Stderr, - }, - }) + hub.sendExecResultMessage( + taskResult.TaskPayload.UserID(), + nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), + aggregatedStatus, + err1.Stdout, + err1.Stderr, + ) } else { - hub.broadcastToWatchers(&watcherMessageS2CExecResult{ - Type: watcherMessageTypeS2CExecResult, - Data: watcherMessageS2CExecResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - TestcaseID: nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), - Status: api.GameWatcherMessageS2CExecResultPayloadStatus("success"), - // TODO: inherit the command stdout/stderr? - Stdout: "Testcase passed", - Stderr: "", - }, - }) + hub.sendExecResultMessage( + taskResult.TaskPayload.UserID(), + nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), + "success", + // TODO: inherit the command stdout/stderr? + "Testcase passed", + "", + ) } if aggregatedStatus != "running" { - hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ - Type: watcherMessageTypeS2CSubmitResult, - Data: watcherMessageS2CSubmitResultPayload{ - PlayerID: taskResult.TaskPayload.UserID(), - Status: api.GameWatcherMessageS2CSubmitResultPayloadStatus(aggregatedStatus), - }, - }) + var score nullable.Nullable[int] + if aggregatedStatus == "success" { + codeSize, err := hub.q.GetSubmissionCodeSizeByID(hub.ctx, int32(taskResult.TaskPayload.SubmissionID)) + if err == nil { + score = nullable.NewNullableWithValue(int(codeSize)) + } + } + hub.sendSubmitResult( + taskResult.TaskPayload.UserID(), + aggregatedStatus, + score, + ) } default: panic("unexpected task result type") @@ -538,14 +509,14 @@ func (hub *gameHub) startGame() error { player.s2cMessages <- &playerMessageS2CStart{ Type: playerMessageTypeS2CStart, Data: playerMessageS2CStartPayload{ - StartAt: int(startAt.Unix()), + StartAt: startAt.Unix(), }, } } hub.broadcastToWatchers(&watcherMessageS2CStart{ Type: watcherMessageTypeS2CStart, Data: watcherMessageS2CStartPayload{ - StartAt: int(startAt.Unix()), + StartAt: startAt.Unix(), }, }) err := hub.q.UpdateGameStartedAt(hub.ctx, db.UpdateGameStartedAtParams{ @@ -693,3 +664,8 @@ func isTestcaseResultCorrect(expectedStdout, actualStdout string) bool { func calcHash(code string) string { return fmt.Sprintf("%x", md5.Sum([]byte(code))) } + +func calcCodeSize(code string) int { + re := regexp.MustCompile(`\s+`) + return len(re.ReplaceAllString(code, "")) +} diff --git a/backend/game/message.go b/backend/game/message.go index 4877ac4..b535c1d 100644 --- a/backend/game/message.go +++ b/backend/game/message.go @@ -8,10 +8,11 @@ import ( ) const ( - playerMessageTypeS2CStart = "player:s2c:start" - playerMessageTypeS2CExecResult = "player:s2c:execresult" - playerMessageTypeC2SCode = "player:c2s:code" - playerMessageTypeC2SSubmit = "player:c2s:submit" + playerMessageTypeS2CStart = "player:s2c:start" + playerMessageTypeS2CExecResult = "player:s2c:execresult" + playerMessageTypeS2CSubmitResult = "player:s2c:submitresult" + playerMessageTypeC2SCode = "player:c2s:code" + playerMessageTypeC2SSubmit = "player:c2s:submit" ) type playerMessageC2SWithClient struct { @@ -24,6 +25,8 @@ type playerMessageS2CStart = api.GamePlayerMessageS2CStart type playerMessageS2CStartPayload = api.GamePlayerMessageS2CStartPayload type playerMessageS2CExecResult = api.GamePlayerMessageS2CExecResult type playerMessageS2CExecResultPayload = api.GamePlayerMessageS2CExecResultPayload +type playerMessageS2CSubmitResult = api.GamePlayerMessageS2CSubmitResult +type playerMessageS2CSubmitResultPayload = api.GamePlayerMessageS2CSubmitResultPayload type playerMessageC2S = interface{} type playerMessageC2SCode = api.GamePlayerMessageC2SCode diff --git a/backend/query.sql b/backend/query.sql index 408bf2d..25eb4df 100644 --- a/backend/query.sql +++ b/backend/query.sql @@ -78,6 +78,11 @@ INSERT INTO submissions (game_id, user_id, code, code_size, code_hash) VALUES ($1, $2, $3, $4, $5) RETURNING submission_id; +-- name: GetSubmissionCodeSizeByID :one +SELECT code_size FROM submissions +WHERE submission_id = $1 +LIMIT 1; + -- name: ListTestcasesByGameID :many SELECT * FROM testcases WHERE testcases.problem_id = (SELECT problem_id FROM games WHERE game_id = $1) |
