Server 2.10.5: far fewer save freezes
Saving makes the game of the asked player freeze for seconds. Joining now asks for a fresh save only if the stored one is older than JoinSaveMaxAgeMinutes (15) and something changed - otherwise the stored save plus the world state; regular saves every 30 minutes (old untouched default 10 is migrated once) and marked not urgent, so the game can wait for a calm moment (60 s deadline). 3 new tests (168). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+24
-6
@@ -140,6 +140,8 @@ public sealed class GameServer
|
||||
}
|
||||
SaveUpload saveUpload;
|
||||
int nextSaveRequestId = 1;
|
||||
/// <summary>Wann der aktuelle Spielstand vom Spiel geschrieben wurde (Datei-Zeit) - bestimmt, ob ein Beitritt frisch speichern lässt.</summary>
|
||||
DateTime lastGameSaveUtc = DateTime.MinValue;
|
||||
double nextSaveSync;
|
||||
readonly List<Client> waitingForSave = new(); // Beigetretene, die auf den frischen Spielstand warten
|
||||
const long MaxUploadBytes = 32L * 1024 * 1024;
|
||||
@@ -204,6 +206,12 @@ public sealed class GameServer
|
||||
return false;
|
||||
}
|
||||
saveFiles = loaded;
|
||||
try
|
||||
{
|
||||
string main = Path.Combine(savePath, "defaultES2File.txt");
|
||||
if (File.Exists(main)) lastGameSaveUtc = File.GetLastWriteTimeUtc(main);
|
||||
}
|
||||
catch { }
|
||||
NewGame = false;
|
||||
Log("Loaded save game (" + saveFiles.Count + " files, " + saveFiles.Sum(f => f.Value.Length) / 1024 + " KB)");
|
||||
return true;
|
||||
@@ -369,7 +377,7 @@ public sealed class GameServer
|
||||
{
|
||||
nextSaveSync = now + config.SaveSyncMinutes * 60.0;
|
||||
// Nur wenn sich seit dem letzten Spielstand etwas geändert hat - Speichern lässt das Spiel kurz hängen
|
||||
if (world.ChangesSinceSave > 0) RequestSave("regular save");
|
||||
if (world.ChangesSinceSave > 0) RequestSave("regular save", urgent: false);
|
||||
}
|
||||
// Hochladen dauert zu lange (Spieler weg, alte Mod ...): Wartende bekommen den bisherigen Spielstand
|
||||
if (saveUpload != null && DateTime.UtcNow > saveUpload.Deadline)
|
||||
@@ -697,8 +705,12 @@ public sealed class GameServer
|
||||
c.Conn.Send(MsgType.AdminStatus, Wire.Build(w => w.Write(isAdmin)));
|
||||
|
||||
// Spielstand schicken; nach dem Laden fragt der Client mit Ready nach dem Weltzustand.
|
||||
// Spielt schon jemand, erst dessen aktuellen Spielstand holen (Schrauben, Teile ... genau wie jetzt im Spiel).
|
||||
if (RequestSave("new player " + c.Name)) waitingForSave.Add(c);
|
||||
// Spielt schon jemand und ist der Spielstand alt, erst dessen aktuellen Spielstand holen (Schrauben, Teile ...
|
||||
// genau wie jetzt im Spiel). Speichern lässt das Spiel dieses Spielers einige Sekunden hängen - bei einem
|
||||
// frischen Spielstand bekommt der Neue ihn plus die seitdem passierten Änderungen (Weltzustand).
|
||||
bool saveIsFresh = world.ChangesSinceSave == 0
|
||||
|| (config.JoinSaveMaxAgeMinutes > 0 && DateTime.UtcNow - lastGameSaveUtc < TimeSpan.FromMinutes(config.JoinSaveMaxAgeMinutes));
|
||||
if (!saveIsFresh && RequestSave("new player " + c.Name)) waitingForSave.Add(c);
|
||||
else SendSave(c);
|
||||
|
||||
Log($"{c.Name} joined (SteamID {c.SteamId}, {c.Conn.RemoteAddress}) - {JoinedCount}/{config.MaxPlayers}");
|
||||
@@ -782,16 +794,22 @@ public sealed class GameServer
|
||||
/// Einen Spieler, der schon im Spiel ist (der am längsten verbundene), um seinen aktuellen Spielstand bitten.
|
||||
/// Gibt false zurück, wenn niemand im Spiel ist - dann ist der gespeicherte Spielstand ohnehin aktuell.
|
||||
/// </summary>
|
||||
bool RequestSave(string why)
|
||||
/// <param name="urgent">false = Routine: das Spiel darf auf einen ruhigen Moment warten (nicht während schneller Fahrt).</param>
|
||||
bool RequestSave(string why, bool urgent = true)
|
||||
{
|
||||
if (saveUpload != null) return true; // läuft schon
|
||||
var source = clients.Values
|
||||
.Where(x => x.State == ClientState.Joined && !x.Conn.IsClosing && x.TrackPositionFrom != DateTime.MaxValue)
|
||||
.OrderBy(x => x.Id).FirstOrDefault();
|
||||
if (source == null) return false;
|
||||
saveUpload = new SaveUpload { Id = nextSaveRequestId++, FromClient = source.Id, Deadline = DateTime.UtcNow.AddSeconds(SaveUploadTimeoutSeconds) };
|
||||
saveUpload = new SaveUpload
|
||||
{
|
||||
Id = nextSaveRequestId++, FromClient = source.Id,
|
||||
Deadline = DateTime.UtcNow.AddSeconds(urgent ? SaveUploadTimeoutSeconds : Math.Max(SaveUploadTimeoutSeconds, 60))
|
||||
};
|
||||
int id = saveUpload.Id;
|
||||
source.Conn.Send(MsgType.SaveRequest, Wire.Build(w => w.Write(id)));
|
||||
// Ältere Mods lesen nur die ID - das zusätzliche Byte stört sie nicht
|
||||
source.Conn.Send(MsgType.SaveRequest, Wire.Build(w => { w.Write(id); w.Write(urgent); }));
|
||||
Log($"Asking {source.Name} for the current save game ({why})");
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user