aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/admin/handler.go20
-rw-r--r--backend/api/generated.go164
-rw-r--r--backend/api/handler.go21
-rw-r--r--backend/db/query.sql.go13
-rw-r--r--backend/game/hub.go296
-rw-r--r--backend/game/message.go11
-rw-r--r--backend/query.sql5
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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",
+ "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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)