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>
This commit is contained in:
2026-02-14 13:06:53 -08:00
co-authored by Claude Opus 4.5
parent bec455ebd2
commit 1371fc7be3
@@ -160,18 +160,9 @@ object PersistedHistory {
.sortBy(_.dropRight(persistence.eagleExtension.length).toInt)
.lastOption
val shardokKeys = allKeys.filter(_.endsWith(persistence.shardokExtension))
val shardokStartTime = System.currentTimeMillis()
val shardokGames =
shardokKeys
.map(persister.retrieveAsStream)
.flatMap(_.toOption)
.flatMap(inputStream =>
ProtoParser
.parseZipped[ShardokResults](inputStream)(using ShardokResults)
)
val shardokDuration = System.currentTimeMillis() - shardokStartTime
val allShardokKeys = allKeys.filter(_.endsWith(persistence.shardokExtension))
// Load e0a file first to determine which shardok battles are outstanding
val e0aStartTime = System.currentTimeMillis()
val e0aResult = lastKey
.map(persister.retrieveAsStream)
@@ -201,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,
@@ -210,18 +226,35 @@ object PersistedHistory {
persister = persister
)
}
val e0aDuration = System.currentTimeMillis() - e0aStartTime
println(
s"[GameLoad] gameId=0x${gameId.toHexString}: " +
s"e0a files=${e0aKeys.size} (loaded last 1 in ${e0aDuration}ms), " +
s"e0s files=${shardokKeys.size} loaded in ${shardokDuration}ms, " +
s"total=${e0aDuration + shardokDuration}ms"
)
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(
persister: Persister,
startingIndex: Int,