Compare commits

...
Author SHA1 Message Date
adminandClaude Opus 4.5 1371fc7be3 Only load outstanding shardok battles on game load
Previously, we loaded ALL .e0s shardok battle files when loading a game,
even though most battles are completed. For a game with 71 battles, this
was loading ~70 unnecessary files.

Now we:
1. Load the e0a file first and reconstruct the GameState
2. Extract the outstanding battle IDs from outstandingBattles
3. Only load .e0s files for battles that are still in progress
4. Use parallel loading (via Futures) if > 4 files need loading

This should significantly reduce game load time for games with many
completed battles.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-14 13:06:53 -08:00
adminandClaude Opus 4.5 bec455ebd2 Add timing logs for game file loading diagnostics
Logs how many .e0a and .e0s files exist and how long each takes to load.
This helps diagnose why some games take 1-1.5s to load on first connection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-14 12:56:03 -08:00
@@ -155,22 +155,16 @@ object PersistedHistory {
}.toVector
)
val lastKey = allKeys
.filter(_.endsWith(persistence.eagleExtension))
val e0aKeys = allKeys.filter(_.endsWith(persistence.eagleExtension))
val lastKey = e0aKeys
.sortBy(_.dropRight(persistence.eagleExtension.length).toInt)
.lastOption
val shardokKeys = allKeys.filter(_.endsWith(persistence.shardokExtension))
val shardokGames =
shardokKeys
.map(persister.retrieveAsStream)
.flatMap(_.toOption)
.flatMap(inputStream =>
ProtoParser
.parseZipped[ShardokResults](inputStream)(using ShardokResults)
)
val allShardokKeys = allKeys.filter(_.endsWith(persistence.shardokExtension))
lastKey
// Load e0a file first to determine which shardok battles are outstanding
val e0aStartTime = System.currentTimeMillis()
val e0aResult = lastKey
.map(persister.retrieveAsStream)
.flatMap(_.toOption)
.flatMap(inputStream => ProtoParser.parseZipped[PartialGame](inputStream)(using PartialGame))
@@ -198,6 +192,31 @@ object PersistedHistory {
chunkHistory ++ recoveredHistory
} else chunkHistory
val e0aDuration = System.currentTimeMillis() - e0aStartTime
// Get the final game state to determine outstanding battles
val finalState = recentHistory.lastOption
.map(_.scalaGameState)
.getOrElse(GameStateConverter.fromProto(pg.startingState.get))
// Only load shardok files for outstanding battles
val outstandingBattleIds = finalState.outstandingBattles.map(_.shardokGameId).toSet
val neededShardokKeys = allShardokKeys.filter { key =>
val shardokId = key.dropRight(persistence.shardokExtension.length)
outstandingBattleIds.contains(shardokId)
}
val shardokStartTime = System.currentTimeMillis()
val shardokGames = loadShardokFiles(persister, neededShardokKeys)
val shardokDuration = System.currentTimeMillis() - shardokStartTime
println(
s"[GameLoad] gameId=0x${gameId.toHexString}: " +
s"e0a files=${e0aKeys.size} (loaded last 1 in ${e0aDuration}ms), " +
s"e0s files=${neededShardokKeys.size}/${allShardokKeys.size} outstanding loaded in ${shardokDuration}ms, " +
s"total=${e0aDuration + shardokDuration}ms"
)
PersistedHistory(
startingState = pg.startingState.get,
directory = directory,
@@ -207,6 +226,33 @@ object PersistedHistory {
persister = persister
)
}
e0aResult
}
private def loadShardokFiles(
persister: Persister,
keys: Vector[String]
): Vector[ShardokResults] = {
import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.*
def loadOne(key: String): Option[ShardokResults] =
persister
.retrieveAsStream(key)
.toOption
.flatMap(inputStream => ProtoParser.parseZipped[ShardokResults](inputStream)(using ShardokResults))
if keys.size > 4 then {
// Load in parallel for many files
val futures = keys.map(key => Future(loadOne(key)))
Await
.result(Future.sequence(futures), 30.seconds)
.flatten
} else {
keys.flatMap(loadOne)
}
}
def save(