Merge pull request #2720 from davotoula/feat/log-lambda-overloads
chore: convert interpolated Log calls to lambda overload + restore throwables in Marmot catch blocks
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
---
|
||||
name: find-non-lambda-logs
|
||||
description: Use when auditing or migrating Log calls to lambda overloads, after adding new logging, or checking for string interpolation in Log.d/i/w/e calls that waste allocations when the log level is filtered out
|
||||
description: Use when auditing or migrating Log calls — flags both interpolated Log.d/i/w/e that should use the lambda overload (allocation hygiene) and catch-block Log.w/e that interpolate ${e.message} but drop the throwable (lost stack traces)
|
||||
---
|
||||
|
||||
# Find Non-Lambda Log Calls
|
||||
|
||||
## Overview
|
||||
|
||||
Locates `Log.d/i/w/e` calls that use string interpolation without the lambda overload, wasting string allocation when the log level is filtered out in release builds.
|
||||
Two related logging hygiene issues:
|
||||
|
||||
1. **Lambda overload missing.** `Log.d/i/w/e` calls that use string interpolation without the lambda overload waste string allocation when the log level is filtered out in release builds.
|
||||
2. **Throwable dropped in catch blocks.** `Log.w/e` calls inside `catch (e: ...)` blocks that interpolate `${e.message}` but don't pass `e` lose the stack trace, and log nothing useful when `e.message` is null (NPE, IOException with no message, etc.).
|
||||
|
||||
## When to Use
|
||||
|
||||
@@ -60,7 +63,22 @@ type: kotlin
|
||||
|
||||
Then **manually exclude** lines where a throwable is passed as third argument (ending with `, e)`, `, throwable)`, etc.). Check the actual line — a catch block catching `e` doesn't mean `e` is passed to the Log call.
|
||||
|
||||
### Step 3: Verify no android.util.Log leakage
|
||||
### Step 3: Find catch-block Log.w/e that drop the throwable
|
||||
|
||||
Among the Step 2 hits, the calls that interpolate `${e.message}` (or `${t.message}`, `${throwable.message}`) but do not pass the exception itself are a separate bug — they lose the stack trace AND log a useless empty-ish line whenever the exception's message is null.
|
||||
|
||||
Quick filter:
|
||||
|
||||
```
|
||||
pattern: Log\.(w|e)\([^)]*\$\{(e|t|throwable|cause)\.message\}[^)]*\)$
|
||||
type: kotlin
|
||||
```
|
||||
|
||||
Then for each hit, open the file and confirm the line is **inside a `catch (e: ...)` block** and **does not pass `e` (or the matching name) as a third argument**. False positives: extension functions / helpers that accept an `e: SomeError` parameter and forward it elsewhere.
|
||||
|
||||
Both Step 2 and Step 3 may flag the same line — handle Step 3 first (different fix), then apply Step 2 to whatever remains.
|
||||
|
||||
### Step 4: Verify no android.util.Log leakage
|
||||
|
||||
```
|
||||
pattern: android\.util\.Log\.(d|i|w|e|v)\(
|
||||
@@ -69,7 +87,9 @@ type: kotlin
|
||||
|
||||
These bypass the `Log.minLevel` filter entirely. Exclude `PlatformLog.android.kt` which is the wrapper implementation.
|
||||
|
||||
## Fix Pattern
|
||||
## Fix Patterns
|
||||
|
||||
### Lambda overload (Step 1 + Step 2)
|
||||
|
||||
```kotlin
|
||||
// Before
|
||||
@@ -79,8 +99,27 @@ Log.d("Tag", "Processing event ${event.id} from ${relay.url}")
|
||||
Log.d("Tag") { "Processing event ${event.id} from ${relay.url}" }
|
||||
```
|
||||
|
||||
### Throwable overload (Step 3)
|
||||
|
||||
Switch to `(tag, msg, throwable)` — the lambda overload does **not** accept a throwable, so this case must use the eager-string form. Drop the redundant `${e.message}` from the message text since the throwable already carries it.
|
||||
|
||||
```kotlin
|
||||
// Before — stack trace lost, prints "...failed: null" if e.message is null
|
||||
try { groupManager.clearAllState() } catch (e: Exception) {
|
||||
Log.w("MarmotManager") { "clearAllState failed: ${e.message}" }
|
||||
}
|
||||
|
||||
// After — full stack trace logged
|
||||
try { groupManager.clearAllState() } catch (e: Exception) {
|
||||
Log.w("MarmotManager", "clearAllState failed", e)
|
||||
}
|
||||
```
|
||||
|
||||
Trade-off: the message string is allocated eagerly even when warn is filtered, but warn-level catch logs are rare-event paths so this cost is negligible compared to losing diagnostic detail.
|
||||
|
||||
## Do NOT Convert
|
||||
|
||||
- Calls passing a `Throwable` parameter - the lambda overload `(tag) { message }` has no throwable parameter
|
||||
- Static string calls with no `$` interpolation - no allocation benefit
|
||||
- Commented-out log calls
|
||||
- **To lambda:** calls passing a `Throwable` parameter — the lambda overload `(tag) { message }` has no throwable parameter.
|
||||
- Static string calls with no `$` interpolation — no allocation benefit.
|
||||
- Commented-out log calls.
|
||||
- Informational/intentional log of `e.message` *outside* a catch block (rare; usually means the exception was already handled and only the message is meaningful).
|
||||
|
||||
@@ -180,7 +180,7 @@ class CallMediaManager(
|
||||
}
|
||||
|
||||
override fun onCameraSwitchError(error: String?) {
|
||||
Log.e(TAG, "Camera switch failed: $error")
|
||||
Log.e(TAG) { "Camera switch failed: $error" }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -211,7 +211,7 @@ class WebRtcCallSession(
|
||||
}
|
||||
|
||||
override fun onCreateFailure(error: String?) {
|
||||
Log.e(TAG, "Create offer failed: $error")
|
||||
Log.e(TAG) { "Create offer failed: $error" }
|
||||
error?.let { onError("Create offer failed: $it") }
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ class WebRtcCallSession(
|
||||
}
|
||||
|
||||
override fun onCreateFailure(error: String?) {
|
||||
Log.e(TAG, "Create answer failed: $error")
|
||||
Log.e(TAG) { "Create answer failed: $error" }
|
||||
error?.let { onError("Create answer failed: $it") }
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ class WebRtcCallSession(
|
||||
}
|
||||
|
||||
override fun onSetFailure(error: String?) {
|
||||
Log.e(TAG, "setRemoteDescription FAILED: $error (type=${sdp.type})")
|
||||
Log.e(TAG) { "setRemoteDescription FAILED: $error (type=${sdp.type})" }
|
||||
error?.let { onError("SDP error: $it") }
|
||||
}
|
||||
},
|
||||
@@ -314,7 +314,7 @@ class WebRtcCallSession(
|
||||
}
|
||||
|
||||
override fun onCreateFailure(error: String?) {
|
||||
Log.e(TAG, "ICE restart offer creation failed: $error")
|
||||
Log.e(TAG) { "ICE restart offer creation failed: $error" }
|
||||
onError("Connection failed - check network")
|
||||
onDisconnected()
|
||||
}
|
||||
@@ -364,7 +364,7 @@ class WebRtcCallSession(
|
||||
}
|
||||
|
||||
override fun onSetFailure(error: String?) {
|
||||
Log.e(TAG, "Rollback FAILED: $error")
|
||||
Log.e(TAG) { "Rollback FAILED: $error" }
|
||||
error?.let { onError("Rollback failed: $it") }
|
||||
}
|
||||
},
|
||||
@@ -386,7 +386,7 @@ class WebRtcCallSession(
|
||||
}
|
||||
|
||||
override fun onCreateFailure(error: String?) {
|
||||
Log.e(TAG, "$label onCreateFailure: $error")
|
||||
Log.e(TAG) { "$label onCreateFailure: $error" }
|
||||
error?.let { onError("SDP error: $it") }
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ class WebRtcCallSession(
|
||||
}
|
||||
|
||||
override fun onSetFailure(error: String?) {
|
||||
Log.e(TAG, "$label onSetFailure: $error")
|
||||
Log.e(TAG) { "$label onSetFailure: $error" }
|
||||
error?.let { onError("SDP error: $it") }
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -142,7 +142,7 @@ class MarmotManager(
|
||||
|
||||
if (result is WelcomeResult.Joined) {
|
||||
subscriptionManager.subscribeGroup(result.nostrGroupId)
|
||||
Log.d("MarmotManager", "Joined group ${result.nostrGroupId}")
|
||||
Log.d("MarmotManager") { "Joined group ${result.nostrGroupId}" }
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -370,17 +370,17 @@ class MarmotManager(
|
||||
try {
|
||||
groupManager.clearAllState()
|
||||
} catch (e: Exception) {
|
||||
Log.w("MarmotManager", "resetAllState(): groupManager.clearAllState failed: ${e.message}")
|
||||
Log.w("MarmotManager", "resetAllState(): groupManager.clearAllState failed", e)
|
||||
}
|
||||
try {
|
||||
keyPackageRotationManager.clearAllState()
|
||||
} catch (e: Exception) {
|
||||
Log.w("MarmotManager", "resetAllState(): keyPackageRotationManager.clearAllState failed: ${e.message}")
|
||||
Log.w("MarmotManager", "resetAllState(): keyPackageRotationManager.clearAllState failed", e)
|
||||
}
|
||||
try {
|
||||
subscriptionManager.clear()
|
||||
} catch (e: Exception) {
|
||||
Log.w("MarmotManager", "resetAllState(): subscriptionManager.clear failed: ${e.message}")
|
||||
Log.w("MarmotManager", "resetAllState(): subscriptionManager.clear failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ class MarmotManager(
|
||||
try {
|
||||
messageStore?.delete(nostrGroupId)
|
||||
} catch (e: Exception) {
|
||||
Log.w("MarmotManager", "Failed to delete persisted messages for $nostrGroupId: ${e.message}")
|
||||
Log.w("MarmotManager", "Failed to delete persisted messages for $nostrGroupId", e)
|
||||
}
|
||||
return outboundEvent
|
||||
}
|
||||
@@ -423,7 +423,7 @@ class MarmotManager(
|
||||
try {
|
||||
messageStore?.appendMessage(nostrGroupId, innerEventJson)
|
||||
} catch (e: Exception) {
|
||||
Log.w("MarmotManager", "Failed to persist Marmot message for $nostrGroupId: ${e.message}")
|
||||
Log.w("MarmotManager", "Failed to persist Marmot message for $nostrGroupId", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +435,7 @@ class MarmotManager(
|
||||
try {
|
||||
messageStore?.loadMessages(nostrGroupId) ?: emptyList()
|
||||
} catch (e: Exception) {
|
||||
Log.w("MarmotManager", "Failed to load persisted messages for $nostrGroupId: ${e.message}")
|
||||
Log.w("MarmotManager", "Failed to load persisted messages for $nostrGroupId", e)
|
||||
emptyList()
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -103,7 +103,7 @@ class KeyPackageRotationManager(
|
||||
try {
|
||||
store.load()
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "Failed to load persisted KeyPackages: ${e.message}")
|
||||
Log.w("KeyPackageRotationManager", "Failed to load persisted KeyPackages", e)
|
||||
null
|
||||
} ?: return
|
||||
try {
|
||||
@@ -117,7 +117,7 @@ class KeyPackageRotationManager(
|
||||
try {
|
||||
store.delete()
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "Failed to delete legacy snapshot: ${e.message}")
|
||||
Log.w("KeyPackageRotationManager", "Failed to delete legacy snapshot", e)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -133,7 +133,7 @@ class KeyPackageRotationManager(
|
||||
try {
|
||||
store.delete()
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "Failed to delete stale snapshot: ${e.message}")
|
||||
Log.w("KeyPackageRotationManager", "Failed to delete stale snapshot", e)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -154,7 +154,7 @@ class KeyPackageRotationManager(
|
||||
"${decoded.namedSlotDTags.size} named slot d-tag(s)"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "Failed to decode persisted KeyPackages: ${e.message}")
|
||||
Log.w("KeyPackageRotationManager", "Failed to decode persisted KeyPackages", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class KeyPackageRotationManager(
|
||||
try {
|
||||
store.delete()
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "clearAllState(): failed to delete snapshot: ${e.message}")
|
||||
Log.w("KeyPackageRotationManager", "clearAllState(): failed to delete snapshot", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ class KeyPackageRotationManager(
|
||||
try {
|
||||
store.save(snapshotBytesUnlocked())
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "Failed to persist KeyPackages: ${e.message}")
|
||||
Log.w("KeyPackageRotationManager", "Failed to persist KeyPackages", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -60,7 +60,7 @@ object ChessStateReconstructor {
|
||||
events: JesterGameEvents,
|
||||
viewerPubkey: String,
|
||||
): ReconstructionResult {
|
||||
Log.d("chessdebug", "[Reconstructor] reconstruct() called: viewerPubkey=${viewerPubkey.take(8)}, startEvent=${events.startEvent?.id?.take(8)}, moveCount=${events.moves.size}")
|
||||
Log.d("chessdebug") { "[Reconstructor] reconstruct() called: viewerPubkey=${viewerPubkey.take(8)}, startEvent=${events.startEvent?.id?.take(8)}, moveCount=${events.moves.size}" }
|
||||
|
||||
// Step 1: Get start event (required for player info)
|
||||
val startEvent =
|
||||
@@ -75,7 +75,7 @@ object ChessStateReconstructor {
|
||||
val challengerColor = startEvent.playerColor() ?: Color.WHITE
|
||||
val challengedPubkey = startEvent.opponentPubkey()
|
||||
|
||||
Log.d("chessdebug", "[Reconstructor] startEventId=${startEventId.take(8)}, challenger=${challengerPubkey.take(8)}, challengerColor=$challengerColor, challengedPubkey=${challengedPubkey?.take(8)}")
|
||||
Log.d("chessdebug") { "[Reconstructor] startEventId=${startEventId.take(8)}, challenger=${challengerPubkey.take(8)}, challengerColor=$challengerColor, challengedPubkey=${challengedPubkey?.take(8)}" }
|
||||
|
||||
// In Jester, we determine opponent from the p-tag or from move events
|
||||
// For open challenges, we need to find who made the first move
|
||||
@@ -85,7 +85,7 @@ object ChessStateReconstructor {
|
||||
?.pubKey
|
||||
|
||||
val actualOpponent = challengedPubkey ?: opponentFromMoves
|
||||
Log.d("chessdebug", "[Reconstructor] opponentFromMoves=${opponentFromMoves?.take(8)}, actualOpponent=${actualOpponent?.take(8)}")
|
||||
Log.d("chessdebug") { "[Reconstructor] opponentFromMoves=${opponentFromMoves?.take(8)}, actualOpponent=${actualOpponent?.take(8)}" }
|
||||
|
||||
// Determine players based on challenger's color choice
|
||||
val (whitePubkey, blackPubkey) =
|
||||
@@ -117,7 +117,7 @@ object ChessStateReconstructor {
|
||||
ViewerRole.SPECTATOR -> blackPubkey ?: "" // For spectators, "opponent" is black
|
||||
}
|
||||
|
||||
Log.d("chessdebug", "[Reconstructor] white=${whitePubkey?.take(8)}, black=${blackPubkey?.take(8)}, viewerRole=$viewerRole, playerColor=$playerColor")
|
||||
Log.d("chessdebug") { "[Reconstructor] white=${whitePubkey?.take(8)}, black=${blackPubkey?.take(8)}, viewerRole=$viewerRole, playerColor=$playerColor" }
|
||||
|
||||
// Check if game is pending (no moves yet AND viewer is the challenger)
|
||||
// If viewer accepted someone else's challenge, the game is NOT pending
|
||||
@@ -129,9 +129,9 @@ object ChessStateReconstructor {
|
||||
val latestMove = events.latestMove()
|
||||
val history = latestMove?.history() ?: emptyList()
|
||||
|
||||
Log.d("chessdebug", "[Reconstructor] latestMove=${latestMove?.id?.take(8)}, historySize=${history.size}, isPendingChallenge=$isPendingChallenge")
|
||||
Log.d("chessdebug") { "[Reconstructor] latestMove=${latestMove?.id?.take(8)}, historySize=${history.size}, isPendingChallenge=$isPendingChallenge" }
|
||||
if (history.isNotEmpty()) {
|
||||
Log.d("chessdebug", "[Reconstructor] history: ${history.joinToString(" ")}")
|
||||
Log.d("chessdebug") { "[Reconstructor] history: ${history.joinToString(" ")}" }
|
||||
}
|
||||
|
||||
// Track applied moves
|
||||
@@ -147,10 +147,10 @@ object ChessStateReconstructor {
|
||||
} else {
|
||||
// Move failed - game might be desynced
|
||||
isDesynced = true
|
||||
Log.d("chessdebug", "[Reconstructor] DESYNC: move #$moveNumber '$san' failed: ${result.error}")
|
||||
Log.d("chessdebug") { "[Reconstructor] DESYNC: move #$moveNumber '$san' failed: ${result.error}" }
|
||||
// Try to recover by loading the FEN from latest move if available
|
||||
latestMove?.fen()?.let { fen ->
|
||||
Log.d("chessdebug", "[Reconstructor] Recovering with FEN: $fen")
|
||||
Log.d("chessdebug") { "[Reconstructor] Recovering with FEN: $fen" }
|
||||
engine.loadFen(fen)
|
||||
}
|
||||
break
|
||||
@@ -162,7 +162,7 @@ object ChessStateReconstructor {
|
||||
val currentFen = engine.getFen()
|
||||
if (!fenPositionsMatch(currentFen, expectedFen)) {
|
||||
isDesynced = true
|
||||
Log.d("chessdebug", "[Reconstructor] FEN MISMATCH: current=$currentFen, expected=$expectedFen")
|
||||
Log.d("chessdebug") { "[Reconstructor] FEN MISMATCH: current=$currentFen, expected=$expectedFen" }
|
||||
engine.loadFen(expectedFen)
|
||||
}
|
||||
}
|
||||
@@ -173,13 +173,13 @@ object ChessStateReconstructor {
|
||||
when {
|
||||
gameResult != null -> {
|
||||
val result = parseGameResult(gameResult)
|
||||
Log.d("chessdebug", "[Reconstructor] Game finished from result tag: $gameResult -> $result")
|
||||
Log.d("chessdebug") { "[Reconstructor] Game finished from result tag: $gameResult -> $result" }
|
||||
GameStatus.Finished(result)
|
||||
}
|
||||
|
||||
engine.isCheckmate() -> {
|
||||
val winner = engine.getSideToMove().opposite()
|
||||
Log.d("chessdebug", "[Reconstructor] Checkmate detected, winner=$winner")
|
||||
Log.d("chessdebug") { "[Reconstructor] Checkmate detected, winner=$winner" }
|
||||
GameStatus.Finished(
|
||||
if (winner == Color.WHITE) GameResult.WHITE_WINS else GameResult.BLACK_WINS,
|
||||
)
|
||||
@@ -219,7 +219,7 @@ object ChessStateReconstructor {
|
||||
timeControl = null, // Not supported in Jester
|
||||
)
|
||||
|
||||
Log.d("chessdebug", "[Reconstructor] Result: status=$gameStatus, appliedMoves=${appliedMoveNumbers.size}, isDesynced=$isDesynced, headEvent=${headEventId.take(8)}")
|
||||
Log.d("chessdebug") { "[Reconstructor] Result: status=$gameStatus, appliedMoves=${appliedMoveNumbers.size}, isDesynced=$isDesynced, headEvent=${headEventId.take(8)}" }
|
||||
|
||||
return ReconstructionResult.Success(state, engine)
|
||||
}
|
||||
|
||||
+11
-11
@@ -126,15 +126,15 @@ class LiveChessGameState(
|
||||
to: String,
|
||||
promotion: PieceType? = null,
|
||||
): ChessMoveEvent? {
|
||||
Log.d("chessdebug", "[LiveGame] makeMove: game=${startEventId.take(8)}, from=$from, to=$to, promotion=$promotion, isPlayerTurn=${isPlayerTurn()}, status=${_gameStatus.value}")
|
||||
Log.d("chessdebug") { "[LiveGame] makeMove: game=${startEventId.take(8)}, from=$from, to=$to, promotion=$promotion, isPlayerTurn=${isPlayerTurn()}, status=${_gameStatus.value}" }
|
||||
if (!isPlayerTurn()) {
|
||||
Log.d("chessdebug", "[LiveGame] makeMove REJECTED: not player's turn (sideToMove=${engine.getSideToMove()}, playerColor=$playerColor)")
|
||||
Log.d("chessdebug") { "[LiveGame] makeMove REJECTED: not player's turn (sideToMove=${engine.getSideToMove()}, playerColor=$playerColor)" }
|
||||
_lastError.value = "Not your turn"
|
||||
return null
|
||||
}
|
||||
|
||||
if (_gameStatus.value != GameStatus.InProgress) {
|
||||
Log.d("chessdebug", "[LiveGame] makeMove REJECTED: game not in progress (${_gameStatus.value})")
|
||||
Log.d("chessdebug") { "[LiveGame] makeMove REJECTED: game not in progress (${_gameStatus.value})" }
|
||||
_lastError.value = "Game is not in progress"
|
||||
return null
|
||||
}
|
||||
@@ -175,7 +175,7 @@ class LiveChessGameState(
|
||||
* @param newHeadEventId The ID of the newly published move event
|
||||
*/
|
||||
fun updateHeadEventId(newHeadEventId: String) {
|
||||
Log.d("chessdebug", "[LiveGame] updateHeadEventId: game=${startEventId.take(8)}, old=${_headEventId.value.take(8)}, new=${newHeadEventId.take(8)}")
|
||||
Log.d("chessdebug") { "[LiveGame] updateHeadEventId: game=${startEventId.take(8)}, old=${_headEventId.value.take(8)}, new=${newHeadEventId.take(8)}" }
|
||||
_headEventId.value = newHeadEventId
|
||||
}
|
||||
|
||||
@@ -209,10 +209,10 @@ class LiveChessGameState(
|
||||
fen: String,
|
||||
moveNumber: Int? = null,
|
||||
): Boolean {
|
||||
Log.d("chessdebug", "[LiveGame] applyOpponentMove: game=${startEventId.take(8)}, san=$san, moveNumber=$moveNumber, expectedMove=${_moveHistory.value.size + 1}")
|
||||
Log.d("chessdebug") { "[LiveGame] applyOpponentMove: game=${startEventId.take(8)}, san=$san, moveNumber=$moveNumber, expectedMove=${_moveHistory.value.size + 1}" }
|
||||
// Check for duplicate move
|
||||
if (moveNumber != null && receivedMoveNumbers.contains(moveNumber)) {
|
||||
Log.d("chessdebug", "[LiveGame] applyOpponentMove: DUPLICATE move #$moveNumber, ignoring")
|
||||
Log.d("chessdebug") { "[LiveGame] applyOpponentMove: DUPLICATE move #$moveNumber, ignoring" }
|
||||
// Already processed this move, ignore
|
||||
return true
|
||||
}
|
||||
@@ -220,7 +220,7 @@ class LiveChessGameState(
|
||||
// Check for out-of-order move
|
||||
val expectedMoveNumber = _moveHistory.value.size + 1
|
||||
if (moveNumber != null && moveNumber > expectedMoveNumber) {
|
||||
Log.d("chessdebug", "[LiveGame] applyOpponentMove: OUT-OF-ORDER move #$moveNumber (expected #$expectedMoveNumber), queuing")
|
||||
Log.d("chessdebug") { "[LiveGame] applyOpponentMove: OUT-OF-ORDER move #$moveNumber (expected #$expectedMoveNumber), queuing" }
|
||||
// Store for later processing
|
||||
pendingMoves[moveNumber] = san to fen
|
||||
return true
|
||||
@@ -246,13 +246,13 @@ class LiveChessGameState(
|
||||
if (currentFen != fen) {
|
||||
// Positions don't match - desync detected
|
||||
_isDesynced.value = true
|
||||
Log.d("chessdebug", "[LiveGame] applyOpponentMove: FEN MISMATCH after $san - current=$currentFen, expected=$fen")
|
||||
Log.d("chessdebug") { "[LiveGame] applyOpponentMove: FEN MISMATCH after $san - current=$currentFen, expected=$fen" }
|
||||
_lastError.value = "Position mismatch - syncing to opponent's position"
|
||||
// Load the opponent's FEN to stay in sync
|
||||
engine.loadFen(fen)
|
||||
} else {
|
||||
_isDesynced.value = false
|
||||
Log.d("chessdebug", "[LiveGame] applyOpponentMove: SUCCESS - $san applied, totalMoves=${_moveHistory.value.size + 1}")
|
||||
Log.d("chessdebug") { "[LiveGame] applyOpponentMove: SUCCESS - $san applied, totalMoves=${_moveHistory.value.size + 1}" }
|
||||
}
|
||||
|
||||
_currentPosition.value = engine.getPosition()
|
||||
@@ -271,7 +271,7 @@ class LiveChessGameState(
|
||||
|
||||
return true
|
||||
} else {
|
||||
Log.d("chessdebug", "[LiveGame] applyOpponentMove: INVALID move $san - ${result.error}")
|
||||
Log.d("chessdebug") { "[LiveGame] applyOpponentMove: INVALID move $san - ${result.error}" }
|
||||
_lastError.value = "Invalid opponent move: $san"
|
||||
return false
|
||||
}
|
||||
@@ -524,7 +524,7 @@ class LiveChessGameState(
|
||||
* Used when loading a game from cache that already has an end event.
|
||||
*/
|
||||
fun markAsFinished(result: GameResult) {
|
||||
Log.d("chessdebug", "[LiveGame] markAsFinished: game=${startEventId.take(8)}, result=$result")
|
||||
Log.d("chessdebug") { "[LiveGame] markAsFinished: game=${startEventId.take(8)}, result=$result" }
|
||||
_gameStatus.value = GameStatus.Finished(result)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -155,7 +155,7 @@ class BleMeshManager(
|
||||
val role = assignRole(transport.deviceUuid, peerUuid)
|
||||
val peer = BlePeer(peerUuid, role, platformHandle)
|
||||
|
||||
Log.d("BleMeshManager", "Discovered peer $peerUuid, my role: $role")
|
||||
Log.d("BleMeshManager") { "Discovered peer $peerUuid, my role: $role" }
|
||||
|
||||
when (role) {
|
||||
BleRole.CLIENT -> {
|
||||
@@ -239,7 +239,7 @@ class BleMeshManager(
|
||||
peer: BlePeer?,
|
||||
error: String,
|
||||
) {
|
||||
Log.e("BleMeshManager", "BLE error for peer ${peer?.deviceUuid}: $error")
|
||||
Log.e("BleMeshManager") { "BLE error for peer ${peer?.deviceUuid}: $error" }
|
||||
listener.onError(peer, error)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ class BleNostrClient(
|
||||
if (success) {
|
||||
currentChunkIndex++
|
||||
} else {
|
||||
Log.e("BleNostrClient", "Failed to write chunk $currentChunkIndex to ${peer.deviceUuid}")
|
||||
Log.e("BleNostrClient") { "Failed to write chunk $currentChunkIndex to ${peer.deviceUuid}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ class BleNostrServer(
|
||||
if (success) {
|
||||
currentChunkIndex++
|
||||
} else {
|
||||
Log.e("BleNostrServer", "Failed to notify chunk $currentChunkIndex to ${peer.deviceUuid}")
|
||||
Log.e("BleNostrServer") { "Failed to notify chunk $currentChunkIndex to ${peer.deviceUuid}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user