Skip to content

Commit

Permalink
Merge pull request #63 from milospi/improve/batch-answer-submission
Browse files Browse the repository at this point in the history
Improve/batch answer submission
  • Loading branch information
mostafas authored Apr 21, 2026
2 parents b95a7ca + 9f2029d commit 303f251
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ public void submitAnswer(String sessionId, int roundIndex, String playerId, Simp
sessionRepository.submitAnswer(sessionId, roundIndex, playerId, callback);
}

@Override
public void submitAnswerAndScore(String sessionId, int roundIndex, String playerId, int score, SimpleCallback callback) {
sessionRepository.submitAnswerAndScore(sessionId, roundIndex, playerId, score, callback);
}

@Override
public void listenToRoundAnswerCount(String sessionId, int roundIndex, AnswerCountCallback callback) {
sessionRepository.listenToRoundAnswerCount(sessionId, roundIndex, callback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,41 @@ public void submitAnswer(
.addOnFailureListener(callback::onFailure);
}

public void submitAnswerAndScore(
String sessionId,
int roundIndex,
String playerId,
int score,
FirebaseGateway.SimpleCallback callback
) {
Map<String, Object> answerData = new HashMap<>();
answerData.put("roundIndex", roundIndex);
answerData.put("playerId", playerId);

Map<String, Object> scoreData = new HashMap<>();
scoreData.put("score", score);

WriteBatch batch = firestore.batch();
batch.set(
firestore.collection(SESSIONS)
.document(sessionId)
.collection("Answers")
.document(roundIndex + "_" + playerId),
answerData
);
batch.update(
firestore.collection(SESSIONS)
.document(sessionId)
.collection(PLAYERS)
.document(playerId),
scoreData
);

batch.commit()
.addOnSuccessListener(unused -> callback.onSuccess())
.addOnFailureListener(callback::onFailure);
}

public void listenToRoundAnswerCount(
String sessionId,
int roundIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public interface LobbySessionCallback {

public LobbyController(BeatBattle game) {
this.game = game;
// Fire a no-op Firestore query on startup to pre-warm the gRPC connection.
// By the time the user navigates to the join screen and types a pin, the
// connection is fully established, eliminating the cold-start latency penalty.
game.getFirebaseGateway().gamePinExists("____", new FirebaseGateway.GamePinExistsCallback() {
@Override public void onResult(boolean exists) {}
@Override public void onFailure(Exception exception) {}
});
}

public void onCreateGame() {
Expand Down Expand Up @@ -125,13 +132,16 @@ public void onFailure(Exception exception) {

view.setStatusMessage("Joining session...");

final long p2StartMs = System.currentTimeMillis(); // P2 — remove after measuring
lobbyService.joinSession(
playerName,
gamePin,
new LobbyService.JoinSessionCallback() {
@Override
public void onSuccess(SessionJoinResult result) {
Gdx.app.postRunnable(() -> {
// P2 — remove after measuring
Gdx.app.log("P2_LATENCY", "join latency: " + (System.currentTimeMillis() - p2StartMs) + " ms");
game.setLocalPlayer(result.getPlayerId(), result.getPlayerName());
game.setLocalSession(result.getSessionId(), false);
game.setLocalGamePin(result.getGamePin());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public interface SessionCancelledCallback {
private int activePlayerCount;
private int currentAnswerCount = 0;

// P1 latency test — remove after measuring
private static int p1SubmissionCount = 0;
private long p1StartMs = 0;

public RoundController(BeatBattle game, Question question, GameSession session) {
this.game = game;
this.question = question;
Expand Down Expand Up @@ -116,26 +120,28 @@ public void onFailure(Exception exception) {
}

public void onAnswerSubmitted(String answer) {
p1StartMs = System.currentTimeMillis(); // P1 — remove after measuring
float answerTime = getTotalTime() - getTimeRemaining();
Player localPlayer = findLocalPlayer();
int score = 0;
if (localPlayer != null) {
AnswerSubmission submission = AnswerSubmission.create(localPlayer.getId(), answer, answerTime, question);
localPlayer.addScore(submission.getPointsAwarded());
submitScoreToFirebase(localPlayer.getId(), localPlayer.getScore());
score = localPlayer.getScore();
}

playerAnswered = true;
recordAnswerInFirebase();
submitAnswerAndScoreToFirebase(localPlayer != null ? localPlayer.getId() : null, score);
}

/** Called when the round timer runs out. Submits a no-answer if the player never responded, then transitions to the leaderboard. */
public void onRoundExpired() {
if (!playerAnswered) {
Player localPlayer = findLocalPlayer();
if (localPlayer != null) {
submitScoreToFirebase(localPlayer.getId(), localPlayer.getScore());
}
recordAnswerInFirebase(); // count as answered so listener is consistent
submitAnswerAndScoreToFirebase(
localPlayer != null ? localPlayer.getId() : null,
localPlayer != null ? localPlayer.getScore() : 0
);
}
transitionToLeaderboard();
}
Expand Down Expand Up @@ -274,19 +280,24 @@ private void maybeAdvanceToLeaderboard() {
}
}

private void recordAnswerInFirebase() {
private void submitAnswerAndScoreToFirebase(String playerId, int score) {
String sessionId = game.getLocalSessionId();
String playerId = game.getLocalPlayerId();
if (sessionId == null || sessionId.isBlank() || playerId == null) return;

game.getFirebaseGateway().submitAnswer(
game.getFirebaseGateway().submitAnswerAndScore(
sessionId,
question.getRoundIndex(),
playerId,
score,
new FirebaseGateway.SimpleCallback() {
@Override public void onSuccess() {}
@Override public void onSuccess() {
// P1 latency test — remove after measuring
long ms = System.currentTimeMillis() - p1StartMs;
p1SubmissionCount++;
Gdx.app.log("P1_LATENCY", "submission " + p1SubmissionCount + ": " + ms + " ms");
}
@Override public void onFailure(Exception e) {
Gdx.app.error("RoundController", "submitAnswer failed: " + e.getMessage());
Gdx.app.error("RoundController", "submitAnswerAndScore failed: " + e.getMessage());
}
}
);
Expand All @@ -305,24 +316,6 @@ private Player findLocalPlayer() {
return null;
}

private void submitScoreToFirebase(String playerId, int score) {
String sessionId = game.getLocalSessionId();
if (sessionId == null || sessionId.isBlank()) return;

game.getFirebaseGateway().updatePlayerScore(
sessionId,
playerId,
score,
new FirebaseGateway.SimpleCallback() {
@Override public void onSuccess() {}
@Override public void onFailure(Exception exception) {
Gdx.app.error("RoundController",
"Failed to submit score: " + exception.getMessage());
}
}
);
}

private void transitionToLeaderboard() {
if (transitioned) return;
transitioned = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ void deleteSession(
/** Records that a player has submitted an answer for the given round. */
void submitAnswer(String sessionId, int roundIndex, String playerId, SimpleCallback callback);

/** Atomically records the answer and updates the player score in a single round-trip. */
void submitAnswerAndScore(String sessionId, int roundIndex, String playerId, int score, SimpleCallback callback);

/** Fires with the current answer count whenever a new answer is submitted for a round. */
void listenToRoundAnswerCount(String sessionId, int roundIndex, AnswerCountCallback callback);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public void submitAnswer(String sessionId, int roundIndex, String playerId, Simp
callback.onFailure(new UnsupportedOperationException("Firebase is not available on this platform"));
}

@Override
public void submitAnswerAndScore(String sessionId, int roundIndex, String playerId, int score, SimpleCallback callback) {
callback.onFailure(new UnsupportedOperationException("Firebase is not available on this platform"));
}

@Override
public void listenToRoundAnswerCount(String sessionId, int roundIndex, AnswerCountCallback callback) {
callback.onFailure(new UnsupportedOperationException("Firebase is not available on this platform"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@ public void updatePlayerScore(String sessionId, String playerId, int score,
callback.onSuccess();
}

@Override
public void submitAnswerAndScore(String sessionId, int roundIndex, String playerId,
int score, SimpleCallback callback) {
updatePlayerScore(sessionId, playerId, score, new SimpleCallback() {
@Override public void onSuccess() {}
@Override public void onFailure(Exception e) {}
});
submitAnswer(sessionId, roundIndex, playerId, callback);
}

@Override
public void submitAnswer(String sessionId, int roundIndex, String playerId,
SimpleCallback callback) {
Expand Down

0 comments on commit 303f251

Please sign in to comment.