diff options
Diffstat (limited to 'backend/game/hub.go')
| -rw-r--r-- | backend/game/hub.go | 274 |
1 files changed, 259 insertions, 15 deletions
diff --git a/backend/game/hub.go b/backend/game/hub.go index b51d977..d17ff7c 100644 --- a/backend/game/hub.go +++ b/backend/game/hub.go @@ -4,6 +4,7 @@ import ( "context" "errors" "log" + "strings" "time" "github.com/jackc/pgx/v5/pgtype" @@ -204,36 +205,273 @@ func (hub *gameHub) run() { } } +type codeSubmissionError struct { + Status string + Stdout string + Stderr string +} + +func (err *codeSubmissionError) Error() string { + return err.Stderr +} + func (hub *gameHub) processTaskResults() { for taskResult := range hub.taskResults { switch taskResult := taskResult.(type) { case *taskqueue.TaskResultCreateSubmissionRecord: - // todo + 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), + }, + } + } + // TODO: broadcast to watchers + } case *taskqueue.TaskResultCompileSwiftToWasm: - // todo + 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), + }, + } + } + // TODO: broadcast to watchers + } case *taskqueue.TaskResultCompileWasmToNativeExecutable: - // todo - case *taskqueue.TaskResultRunTestcase: - // todo - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue + 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), + }, + } } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(taskResult.Status), - }, + // TODO: broadcast to watchers + } + case *taskqueue.TaskResultRunTestcase: + err := hub.processTaskResultRunTestcase(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), + }, + } } + // TODO: broadcast to watchers } - // broadcast to watchers + // TODO: aggregate results of testcases default: panic("unexpected task result type") } } } +func (hub *gameHub) processTaskResultCreateSubmissionRecord( + taskResult *taskqueue.TaskResultCreateSubmissionRecord, +) *codeSubmissionError { + if taskResult.Err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: taskResult.Err.Error(), + } + } + + if err := hub.taskQueue.EnqueueTaskCompileSwiftToWasm( + taskResult.TaskPayload.GameID(), + taskResult.TaskPayload.UserID(), + taskResult.TaskPayload.Code(), + taskResult.SubmissionID, + ); err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + return nil +} + +func (hub *gameHub) processTaskResultCompileSwiftToWasm( + taskResult *taskqueue.TaskResultCompileSwiftToWasm, +) *codeSubmissionError { + if taskResult.Err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: taskResult.Err.Error(), + } + } + + if taskResult.Status != "success" { + if err := hub.q.CreateSubmissionResult(hub.ctx, db.CreateSubmissionResultParams{ + SubmissionID: int32(taskResult.TaskPayload.SubmissionID), + Status: taskResult.Status, + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + }); err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + return &codeSubmissionError{ + Status: taskResult.Status, + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + } + } + if err := hub.taskQueue.EnqueueTaskCompileWasmToNativeExecutable( + taskResult.TaskPayload.GameID(), + taskResult.TaskPayload.UserID(), + taskResult.TaskPayload.Code(), + taskResult.TaskPayload.SubmissionID, + ); err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + return nil +} + +func (hub *gameHub) processTaskResultCompileWasmToNativeExecutable( + taskResult *taskqueue.TaskResultCompileWasmToNativeExecutable, +) *codeSubmissionError { + if taskResult.Err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: taskResult.Err.Error(), + } + } + + if taskResult.Status != "success" { + if err := hub.q.CreateSubmissionResult(hub.ctx, db.CreateSubmissionResultParams{ + SubmissionID: int32(taskResult.TaskPayload.SubmissionID), + Status: taskResult.Status, + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + }); err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + return &codeSubmissionError{ + Status: taskResult.Status, + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + } + } + + testcases, err := hub.q.ListTestcasesByGameID(hub.ctx, int32(taskResult.TaskPayload.GameID())) + if err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + if len(testcases) == 0 { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: "no testcases found", + } + } + + for _, testcase := range testcases { + if err := hub.taskQueue.EnqueueTaskRunTestcase( + taskResult.TaskPayload.GameID(), + taskResult.TaskPayload.UserID(), + taskResult.TaskPayload.Code(), + taskResult.TaskPayload.SubmissionID, + int(testcase.TestcaseID), + testcase.Stdin, + testcase.Stdout, + ); err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + } + return nil +} + +func (hub *gameHub) processTaskResultRunTestcase( + taskResult *taskqueue.TaskResultRunTestcase, +) *codeSubmissionError { + if taskResult.Err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: taskResult.Err.Error(), + } + } + + if taskResult.Status != "success" { + if err := hub.q.CreateTestcaseResult(hub.ctx, db.CreateTestcaseResultParams{ + SubmissionID: int32(taskResult.TaskPayload.SubmissionID), + TestcaseID: int32(taskResult.TaskPayload.TestcaseID), + Status: taskResult.Status, + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + }); err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + return &codeSubmissionError{ + Status: taskResult.Status, + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + } + } + if !isTestcaseResultCorrect(taskResult.TaskPayload.Stdout, taskResult.Stdout) { + if err := hub.q.CreateTestcaseResult(hub.ctx, db.CreateTestcaseResultParams{ + SubmissionID: int32(taskResult.TaskPayload.SubmissionID), + TestcaseID: int32(taskResult.TaskPayload.TestcaseID), + Status: "wrong_answer", + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + }); err != nil { + return &codeSubmissionError{ + Status: "internal_error", + Stderr: err.Error(), + } + } + return &codeSubmissionError{ + Status: "wrong_answer", + Stdout: taskResult.Stdout, + Stderr: taskResult.Stderr, + } + } + return nil +} + func (hub *gameHub) startGame() error { for player := range hub.players { player.s2cMessages <- &playerMessageS2CPrepare{ @@ -376,3 +614,9 @@ func (hubs *GameHubs) StartGame(gameID int) error { } return hub.startGame() } + +func isTestcaseResultCorrect(expectedStdout, actualStdout string) bool { + expectedStdout = strings.TrimSpace(expectedStdout) + actualStdout = strings.TrimSpace(actualStdout) + return actualStdout == expectedStdout +} |
