2.10.8: fewer hitches
- Sending on a background thread (the game thread blocked whenever the TCP buffer was full, e.g. while uploading the save); small frames are batched - Server save requests: routine saves wait for a calm moment (not while driving fast) and are announced; urgent flag from server 2.10.5 - Dynamic built-in font for all MSCO GUI (Unity logged hundreds of "Font size and style overrides" warnings with stutters) - Admin VEHICLE tab rescans every 30 s / after actions instead of every 5 s; time/weather FSM search less often - Reused buffers for Build/Frame/Poll (less garbage, fewer GC pauses) - Hitch log: GC and save detection, count of hitches between reports - Show Bolt Size 1.0.1: finds the FPSCamera (Camera.main may be missing), uses MSCO's shared FSM list instead of FindObjectsOfType every 2 s Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+117
-23
@@ -200,7 +200,8 @@ namespace MSCO
|
||||
{
|
||||
if (payload == null) payload = new byte[0];
|
||||
var frame = new byte[5 + payload.Length];
|
||||
BitConverter.GetBytes(payload.Length + 1).CopyTo(frame, 0);
|
||||
int length = payload.Length + 1; // wie BitConverter (Little Endian), ohne zusätzliches Array
|
||||
frame[0] = (byte)length; frame[1] = (byte)(length >> 8); frame[2] = (byte)(length >> 16); frame[3] = (byte)(length >> 24);
|
||||
frame[4] = (byte)type;
|
||||
Buffer.BlockCopy(payload, 0, frame, 5, payload.Length);
|
||||
return frame;
|
||||
@@ -215,16 +216,30 @@ namespace MSCO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>TCP-Verbindung (LAN / IP). Lesen ist nicht-blockierend und wird über Poll() abgefragt.</summary>
|
||||
/// <summary>
|
||||
/// TCP-Verbindung (LAN / IP). Lesen ist nicht-blockierend und wird über Poll() abgefragt. Senden läuft über eine
|
||||
/// Warteschlange und einen eigenen Thread: vorher schrieb das Spiel selbst (blockierend) - war die Leitung kurz voll
|
||||
/// (Spielstand hochladen, schwaches WLAN), stand das ganze Spiel. Kleine Pakete werden dabei zusammengefasst.
|
||||
/// </summary>
|
||||
public class Connection : IConnection
|
||||
{
|
||||
public bool IsClosed { get; private set; }
|
||||
public string CloseReason { get; private set; }
|
||||
volatile bool closed;
|
||||
volatile string closeReason;
|
||||
public bool IsClosed { get { return closed; } }
|
||||
public string CloseReason { get { return closeReason; } }
|
||||
|
||||
// Mehr als das unterwegs = die Gegenseite nimmt nichts mehr an
|
||||
const long MaxQueuedBytes = 96L * 1024 * 1024;
|
||||
const int MaxBatchBytes = 256 * 1024;
|
||||
|
||||
readonly TcpClient client;
|
||||
readonly NetworkStream stream;
|
||||
readonly FrameParser parser = new FrameParser();
|
||||
readonly byte[] readBuffer = new byte[64 * 1024];
|
||||
readonly Queue<byte[]> outgoing = new Queue<byte[]>();
|
||||
readonly object gate = new object();
|
||||
long queuedBytes;
|
||||
readonly System.Threading.Thread writer;
|
||||
|
||||
public Connection(TcpClient client)
|
||||
{
|
||||
@@ -232,11 +247,46 @@ namespace MSCO
|
||||
client.NoDelay = true;
|
||||
client.SendBufferSize = 4 * 1024 * 1024;
|
||||
stream = client.GetStream();
|
||||
writer = new System.Threading.Thread(WriteLoop) { IsBackground = true, Name = "MSCO send" };
|
||||
writer.Start();
|
||||
}
|
||||
|
||||
/// <summary>Sende-Thread: wartet auf Pakete und schreibt sie (gebündelt) in die Verbindung.</summary>
|
||||
void WriteLoop()
|
||||
{
|
||||
var batch = new MemoryStream(MaxBatchBytes);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
batch.SetLength(0);
|
||||
lock (gate)
|
||||
{
|
||||
while (outgoing.Count == 0 && !closed) System.Threading.Monitor.Wait(gate);
|
||||
if (outgoing.Count == 0) return; // geschlossen und nichts mehr zu senden
|
||||
// Mehrere kleine Pakete in einem Rutsch (große einzeln)
|
||||
do
|
||||
{
|
||||
byte[] frame = outgoing.Dequeue();
|
||||
queuedBytes -= frame.Length;
|
||||
batch.Write(frame, 0, frame.Length);
|
||||
} while (outgoing.Count > 0 && batch.Length + outgoing.Peek().Length <= MaxBatchBytes);
|
||||
System.Threading.Monitor.PulseAll(gate); // Close() wartet evtl. aufs Leerwerden
|
||||
}
|
||||
stream.Write(batch.GetBuffer(), 0, (int)batch.Length);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!closed) Close("Network error: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
readonly List<Message> polled = new List<Message>(); // wiederverwendet: der Aufrufer geht sie sofort durch
|
||||
public List<Message> Poll()
|
||||
{
|
||||
var messages = new List<Message>();
|
||||
var messages = polled;
|
||||
messages.Clear();
|
||||
if (IsClosed) return messages;
|
||||
try
|
||||
{
|
||||
@@ -263,23 +313,37 @@ namespace MSCO
|
||||
|
||||
public void Send(MsgType type, byte[] payload)
|
||||
{
|
||||
if (IsClosed) return;
|
||||
try
|
||||
if (closed) return;
|
||||
byte[] frame = FrameParser.Frame(type, payload);
|
||||
bool tooMuch;
|
||||
lock (gate)
|
||||
{
|
||||
byte[] frame = FrameParser.Frame(type, payload);
|
||||
stream.Write(frame, 0, frame.Length); // blockierend, damit große Dateien komplett rausgehen
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Close("Network error: " + e.Message);
|
||||
tooMuch = queuedBytes + frame.Length > MaxQueuedBytes;
|
||||
if (!tooMuch)
|
||||
{
|
||||
outgoing.Enqueue(frame);
|
||||
queuedBytes += frame.Length;
|
||||
System.Threading.Monitor.Pulse(gate);
|
||||
}
|
||||
}
|
||||
if (tooMuch) Close("The connection is too slow - too much data waiting to be sent");
|
||||
}
|
||||
|
||||
public void Close(string reason)
|
||||
{
|
||||
if (IsClosed) return;
|
||||
IsClosed = true;
|
||||
CloseReason = reason;
|
||||
lock (gate)
|
||||
{
|
||||
if (closed) return;
|
||||
// Was schon in der Warteschlange liegt (z.B. eine Abschiedsnachricht), kurz noch rausgehen lassen
|
||||
var until = DateTime.UtcNow.AddMilliseconds(300);
|
||||
while (outgoing.Count > 0 && DateTime.UtcNow < until && System.Threading.Thread.CurrentThread != writer)
|
||||
System.Threading.Monitor.Wait(gate, 50);
|
||||
closed = true;
|
||||
closeReason = reason;
|
||||
outgoing.Clear();
|
||||
queuedBytes = 0;
|
||||
System.Threading.Monitor.PulseAll(gate);
|
||||
}
|
||||
try { client.Close(); } catch { }
|
||||
}
|
||||
}
|
||||
@@ -313,9 +377,11 @@ namespace MSCO
|
||||
lastReceive = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
readonly List<Message> polled = new List<Message>(); // wiederverwendet: der Aufrufer geht sie sofort durch
|
||||
public List<Message> Poll()
|
||||
{
|
||||
var messages = new List<Message>();
|
||||
var messages = polled;
|
||||
messages.Clear();
|
||||
if (IsClosed) return messages;
|
||||
try { parser.Parse(messages); }
|
||||
catch (Exception e) { Close("Network error: " + e.Message); }
|
||||
@@ -419,9 +485,11 @@ namespace MSCO
|
||||
return new NetSession { IsSteam = true, server = new SteamConnection(host), pendingConnected = true };
|
||||
}
|
||||
|
||||
readonly List<NetEvent> polledEvents = new List<NetEvent>(); // wiederverwendet (jedes Frame)
|
||||
public List<NetEvent> Poll()
|
||||
{
|
||||
var events = new List<NetEvent>();
|
||||
var events = polledEvents;
|
||||
events.Clear();
|
||||
if (IsClosed) return events;
|
||||
try
|
||||
{
|
||||
@@ -568,15 +636,41 @@ namespace MSCO
|
||||
try { if (listener != null) listener.Stop(); } catch { }
|
||||
}
|
||||
|
||||
// Puffer zum Bauen von Nachrichten pro Thread wiederverwenden (vorher pro Nachricht neuer Speicherstrom + Schreiber:
|
||||
// bei vielen Nachrichten pro Sekunde viel Speichermüll -> häufiger Garbage Collector -> Ruckler)
|
||||
[ThreadStatic] static MemoryStream buildStream;
|
||||
[ThreadStatic] static BinaryWriter buildWriter;
|
||||
[ThreadStatic] static bool building;
|
||||
|
||||
public static byte[] Build(Action<BinaryWriter> write)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
using (var w = new BinaryWriter(ms))
|
||||
if (building)
|
||||
{
|
||||
write(w);
|
||||
w.Flush();
|
||||
return ms.ToArray();
|
||||
// Build innerhalb von Build: eigener Puffer
|
||||
using (var ms = new MemoryStream())
|
||||
using (var w = new BinaryWriter(ms))
|
||||
{
|
||||
write(w);
|
||||
w.Flush();
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
if (buildStream == null)
|
||||
{
|
||||
buildStream = new MemoryStream(4096);
|
||||
buildWriter = new BinaryWriter(buildStream);
|
||||
}
|
||||
building = true;
|
||||
try
|
||||
{
|
||||
buildStream.SetLength(0);
|
||||
write(buildWriter);
|
||||
buildWriter.Flush();
|
||||
byte[] result = buildStream.ToArray();
|
||||
if (buildStream.Capacity > 1024 * 1024) { buildStream = null; buildWriter = null; } // große Dateien nicht ewig im Speicher halten
|
||||
return result;
|
||||
}
|
||||
finally { building = false; }
|
||||
}
|
||||
|
||||
/// <summary>Stellt der Nachricht die Absender-ID voran (für weitergeleitete Nachrichten).</summary>
|
||||
|
||||
Reference in New Issue
Block a user