Compare commits

...
Author SHA1 Message Date
admin e00ff5f1a5 Report Eagle cache load rejection reasons 2026-06-09 16:47:52 -07:00
admin 29b0a460e7 Log and persist Eagle cache resume counts 2026-06-09 16:38:45 -07:00
4 changed files with 99 additions and 17 deletions
@@ -276,7 +276,13 @@ namespace eagle {
private void RestoreCachedStateIfAvailable(FactionId? factionId) {
var cachedState = EagleGameStateDiskCache.Load(GameId, factionId);
if (cachedState == null) { return; }
if (cachedState == null) {
_connectionLogger.LogLine(
$"[STATE_CACHE] Cache miss game={GameId} " +
$"player={factionId?.ToString() ?? "spectator"} " +
$"reason={EagleGameStateDiskCache.LastLoadDiagnostic}");
return;
}
_currentModel.GsView = cachedState.GameStateView;
_currentModel.BattalionTypes = cachedState.GameStateView.BattalionTypes.ToDictionary(
@@ -289,16 +295,17 @@ namespace eagle {
}
_lastAppliedUnfilteredCount = cachedState.UnfilteredResultCount;
RestoreCachedShardokStates(cachedState.ShardokGames);
var restoredShardokCount = RestoreCachedShardokStates(cachedState.ShardokGames);
_connectionLogger.LogLine(
$"[STATE_CACHE] Restored game {GameId} at count " +
$"{cachedState.UnfilteredResultCount} with " +
$"{cachedState.ShardokGames.Count} cached Shardok game(s)");
$"{restoredShardokCount}/{cachedState.ShardokGames.Count} cached Shardok game(s)");
}
private void RestoreCachedShardokStates(
private int RestoreCachedShardokStates(
List<EagleGameStateDiskCache.CachedShardokGameState> cachedShardokStates) {
int restoredCount = 0;
foreach (var cachedShardokState in cachedShardokStates) {
var model = MakeGameModel(cachedShardokState.ShardokGameId);
if (model == null) { continue; }
@@ -311,7 +318,9 @@ namespace eagle {
_currentModel.ShardokGameModels[cachedShardokState.ShardokGameId] = model;
_shardokResultCounts[cachedShardokState.ShardokGameId] =
cachedShardokState.ResultCount;
restoredCount++;
}
return restoredCount;
}
private ShardokGameModel MakeGameModel(ShardokGameId shardokGameId) {
@@ -423,8 +432,13 @@ namespace eagle {
_connectionLogger.LogLine(
"[UPDATE] Processed update and invoked UpdateAction");
} else {
RecordAppliedUnfilteredCount(
updateItem.ActionResultResponse.UnfilteredResultCountAfter);
SaveCurrentStateToCache(
updateItem.ActionResultResponse.UnfilteredResultCountAfter);
_connectionLogger.LogLine(
"[UPDATE] SKIPPED - no results, has commands, token matches");
"[UPDATE] SKIPPED - no results, has commands, token matches; " +
"persisted accepted count");
}
// Check for turn state mismatch after processing update
@@ -736,20 +750,32 @@ namespace eagle {
}
if (countAfter > _lastAppliedUnfilteredCount) {
_lastAppliedUnfilteredCount = countAfter;
RecordAppliedUnfilteredCount(countAfter);
}
SaveCurrentStateToCache(countAfter);
}
private void RecordAppliedUnfilteredCount(int countAfter) {
if (countAfter <= _lastAppliedUnfilteredCount) { return; }
_connectionLogger.LogLine(
$"[STATE_CACHE] Accepted Eagle count {_lastAppliedUnfilteredCount} -> {countAfter}");
_lastAppliedUnfilteredCount = countAfter;
}
private void SaveCurrentStateToCache(int unfilteredResultCount) {
if (unfilteredResultCount <= 0 || _currentModel.GsView == null) { return; }
var cachedShardokStates = CachedShardokGameStates();
EagleGameStateDiskCache.Save(
GameId,
_currentModel.PlayerId,
unfilteredResultCount,
_currentModel.GsView,
CachedShardokGameStates());
cachedShardokStates);
_connectionLogger.LogLine(
$"[STATE_CACHE] Saved game={GameId} count={unfilteredResultCount} " +
$"shardokGames={cachedShardokStates.Count}");
}
private void SaveCurrentStateToCache() {
@@ -43,25 +43,66 @@ namespace eagle {
}
private static string _cacheDirectory;
public static string LastLoadDiagnostic { get; private set; }
public static CachedGameState Load(long gameId, int? playerId) {
EnsureCacheDirectoryInitialized();
var path = CachePath(gameId, playerId);
if (!File.Exists(path)) { return null; }
LastLoadDiagnostic = $"path={path}";
if (!File.Exists(path)) {
LastLoadDiagnostic = $"file not found at {path}";
return null;
}
try {
var cache = JsonUtility.FromJson<CacheFile>(File.ReadAllText(path));
if (cache == null || cache.schemaVersion != CurrentSchemaVersion) { return null; }
if (cache.gameId != gameId) { return null; }
if (cache.hasPlayerId != playerId.HasValue) { return null; }
if (cache.hasPlayerId && cache.playerId != playerId.Value) { return null; }
if (cache.unfilteredResultCount <= 0) { return null; }
if (String.IsNullOrEmpty(cache.gameStateViewBase64)) { return null; }
if (cache == null) {
LastLoadDiagnostic = $"cache JSON parsed to null at {path}";
return null;
}
if (cache.schemaVersion != CurrentSchemaVersion) {
LastLoadDiagnostic =
$"schema mismatch at {path}: cache={cache.schemaVersion} current={CurrentSchemaVersion}";
return null;
}
if (cache.gameId != gameId) {
LastLoadDiagnostic =
$"game id mismatch at {path}: cache={cache.gameId} requested={gameId}";
return null;
}
if (cache.hasPlayerId != playerId.HasValue) {
LastLoadDiagnostic =
$"player presence mismatch at {path}: cacheHas={cache.hasPlayerId} requestedHas={playerId.HasValue}";
return null;
}
if (cache.hasPlayerId && cache.playerId != playerId.Value) {
LastLoadDiagnostic =
$"player id mismatch at {path}: cache={cache.playerId} requested={playerId.Value}";
return null;
}
if (cache.unfilteredResultCount <= 0) {
LastLoadDiagnostic =
$"invalid unfiltered count at {path}: {cache.unfilteredResultCount}";
return null;
}
if (String.IsNullOrEmpty(cache.gameStateViewBase64)) {
LastLoadDiagnostic = $"missing GameStateView payload at {path}";
return null;
}
var bytes = Convert.FromBase64String(cache.gameStateViewBase64);
var gameStateView = GameStateView.Parser.ParseFrom(bytes);
if (gameStateView.GameId != gameId) { return null; }
if (gameStateView.GameId == 0) {
gameStateView.GameId = gameId;
} else if (gameStateView.GameId != gameId) {
LastLoadDiagnostic =
$"GameStateView game id mismatch at {path}: view={gameStateView.GameId} requested={gameId}";
return null;
}
LastLoadDiagnostic =
$"loaded {path} count={cache.unfilteredResultCount} shardokGames={cache.shardokGames?.Count ?? 0}";
return new CachedGameState {
GameStateView = gameStateView,
@@ -69,6 +110,7 @@ namespace eagle {
ShardokGames = LoadShardokGames(cache.shardokGames)
};
} catch (Exception e) {
LastLoadDiagnostic = $"exception loading {path}: {e}";
Debug.LogWarning($"Failed to load Eagle game state cache for game {gameId}: {e}");
TryDelete(path);
return null;
@@ -285,10 +285,15 @@ namespace eagle {
lock (_pendingSubscriptionAcks) { _pendingSubscriptionAcks[gameId] = ackTcs; }
var shardokCount = shardokStatuses.Count();
var streamingTextStatuses = streamGameRequest.StreamingTextStatuses;
var completeTextCount = streamingTextStatuses.Count(status => status.KnownComplete);
var incompleteTextCount = streamingTextStatuses.Count - completeTextCount;
LogFlow($"SUBSCRIBE game={gameId} unfilteredCount={subscriber.LastUnfilteredResultCount} shardokGames={shardokCount}");
_remoteEagleClientLogger.LogLine(
$"[SUBSCRIBE] Sending StreamGameRequest for game {gameId}, " +
$"unfilteredCount={subscriber.LastUnfilteredResultCount}");
$"unfilteredCount={subscriber.LastUnfilteredResultCount}, " +
$"shardokGames={shardokCount}, completeTexts={completeTextCount}, " +
$"incompleteTexts={incompleteTextCount}");
var sendSuccess = await DoWithStreamingCall(async (sc) => {
await sc.RequestStream.WriteAsync(request);
@@ -1104,10 +1104,19 @@ final case class GameController(
0
} else knownResultCount
val knownCompleteTextIds = streamingTextStatuses
val knownCompleteTextIds = streamingTextStatuses
.filter(_.knownComplete)
.map(_.llmIdentifier)
.toSet
val knownIncompleteTextCount = streamingTextStatuses.count(!_.knownComplete)
SimpleTimedLogger.printLogger.logLine(
s"[CLIENT-CACHE] game=$gameId user=$userId clientId=$clientId " +
s"reportedCount=$knownResultCount acceptedCount=$safeKnownResultCount " +
s"serverCount=${fullHistory.count} delta=${fullHistory.count - safeKnownResultCount} " +
s"shardokStatuses=${knownShardokResultCounts.size} " +
s"knownCompleteTexts=${knownCompleteTextIds.size} " +
s"knownIncompleteTexts=$knownIncompleteTextCount"
)
this.withHumanClient(
clientTextStore