Less garbage, fewer GC hitches: no Joint array per part every 0.5 s, cheap aim check after clicks, cached FPS/ping/chat labels; perf report measures object sync, vehicle fx and part/bolt sync correctly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nico Meister
2026-10-05 16:31:56 +02:00
co-authored by Claude Opus 5.5
parent fe08fa5213
commit d567d5992a
4 changed files with 71 additions and 21 deletions
+12 -5
View File
@@ -167,18 +167,25 @@ namespace MSCO
}
}
static Camera aimCamera;
static int aimCameraFrame = -1;
static bool IsAimedAt(Fsm fsm, out float aimDistance)
{
aimDistance = float.MaxValue;
GameObject go = fsm.GameObject;
Camera cam = Camera.main;
// Läuft im Klick-Fenster für jede FSM, die den Zustand wechselt: Kamera nur einmal pro Bild suchen
if (aimCameraFrame != Time.frameCount) { aimCameraFrame = Time.frameCount; aimCamera = Camera.main; }
Camera cam = aimCamera;
if (go == null || cam == null) return false;
// Erst der billige Abstand, dann die Namen (jeder .name-Zugriff erzeugt einen neuen String)
Transform camT = cam.transform;
Vector3 toObject = go.transform.position - camT.position;
if (toObject.sqrMagnitude > ClickRange * ClickRange || Vector3.Dot(toObject, camT.forward) < 0f) return false;
string root = go.transform.root.name;
if (root == "PLAYER" || root.StartsWith("MSCO_")) return false;
Vector3 toObject = go.transform.position - cam.transform.position;
if (toObject.magnitude > ClickRange || Vector3.Dot(toObject, cam.transform.forward) < 0f) return false;
aimDistance = Vector3.Cross(cam.transform.forward, toObject).magnitude; // Abstand zur Blickachse
aimDistance = Vector3.Cross(camT.forward, toObject).magnitude; // Abstand zur Blickachse
return true;
}
+33 -9
View File
@@ -367,10 +367,22 @@ namespace MSCO
}
// grün ab 50, gelb ab 30, sonst rot
fpsStyle.normal.textColor = fpsValue < 0f ? UiTheme.Muted : fpsValue >= 50f ? UiTheme.Online : fpsValue >= 30f ? UiTheme.Pending : UiTheme.Danger;
var content = new GUIContent(fpsValue < 0f ? "FPS ..." : "FPS " + Mathf.RoundToInt(fpsValue));
float width = fpsStyle.CalcSize(content).x;
GUI.Box(new Rect(rightEdge - width, 8, width, 24), content, fpsStyle);
int shown = fpsValue < 0f ? -1 : Mathf.RoundToInt(fpsValue);
if (shown != fpsShown || fpsContent.text == null)
{
fpsShown = shown;
fpsContent.text = shown < 0 ? "FPS ..." : "FPS " + shown;
fpsWidth = fpsStyle.CalcSize(fpsContent).x;
}
GUI.Box(new Rect(rightEdge - fpsWidth, 8, fpsWidth, 24), fpsContent, fpsStyle);
}
// Anzeige-Texte zwischengespeichert (nur bei Änderung neu gebaut, siehe DrawFps/DrawPing)
readonly GUIContent fpsContent = new GUIContent(), pingContent = new GUIContent();
int fpsShown = int.MinValue;
float fpsWidth;
string pingShown;
float pingTextWidth;
/// <summary>Text für Anzeige und Konsole.</summary>
public string PingText
@@ -379,7 +391,10 @@ namespace MSCO
{
if (session == null) return "not connected";
if (session.IsHost) return "you are the host";
return pingMs < 0f ? "measuring..." : Mathf.RoundToInt(pingMs) + " ms";
if (pingMs < 0f) return "measuring...";
int ms = Mathf.RoundToInt(pingMs);
if (ms != pingTextMs || pingTextCache == null) { pingTextMs = ms; pingTextCache = ms + " ms"; }
return pingTextCache;
}
}
@@ -397,12 +412,20 @@ namespace MSCO
// Farbe nach Qualität: grün < 80 ms, gelb < 150 ms, sonst rot
Color color = pingMs < 0f ? UiTheme.Muted : pingMs < 80f ? UiTheme.Online : pingMs < 150f ? UiTheme.Pending : UiTheme.Danger;
pingStyle.normal.textColor = color;
var content = new GUIContent("PING " + PingText);
float width = pingStyle.CalcSize(content).x;
GUI.Box(new Rect(Screen.width - width - 12, 8, width, 24), content, pingStyle);
string ping = PingText; // ändert sich nur, wenn eine neue Messung da ist
if (ping != pingShown)
{
pingShown = ping;
pingContent.text = "PING " + ping;
pingTextWidth = pingStyle.CalcSize(pingContent).x;
}
float width = pingTextWidth;
GUI.Box(new Rect(Screen.width - width - 12, 8, width, 24), pingContent, pingStyle);
pingWidth = width + 8;
}
float pingWidth; // Platz, den die Ping-Anzeige belegt (FPS steht links daneben)
int pingTextMs = int.MinValue;
string pingTextCache;
// ---------------------------------------------------------------- Cheats (Admin-Menü)
@@ -2170,12 +2193,12 @@ namespace MSCO
PartStart();
try { objects.Update(localPlayer, Time.deltaTime); }
catch (System.Exception e) { ReportError("object sync", e); }
PartStart();
PartEnd("object sync");
PartStart();
try { vehicleFx.Update(objects, SendGame, Time.deltaTime, IsWorldAuthority); }
catch (System.Exception e) { ReportError("vehicle lights/sound", e); }
PartStart();
PartEnd("vehicle lights/sound");
PartStart();
try { worldSync.Update(IsWorldAuthority, SendGame, Time.unscaledDeltaTime); }
catch (System.Exception e) { ReportError("time/weather sync", e); }
PartEnd("time/weather sync");
@@ -2260,6 +2283,7 @@ namespace MSCO
}
// Abgefangene Schraub-/Montage-Aktionen verschicken
PartStart();
try
{
FsmSync.Enabled = ready;
+11 -3
View File
@@ -811,15 +811,23 @@ namespace MSCO
return attached;
}
static bool IsAttached(SyncedBody b)
bool IsAttached(SyncedBody b)
{
if (b.IsVehicle || b.Rb == null) return false;
// Unter einem anderen Rigidbody (z.B. am Auto eingehängt)
for (Transform p = b.Rb.transform.parent; p != null; p = p.parent)
if (p.GetComponent<Rigidbody>() != null) return true;
// Per Gelenk mit etwas verbunden, das nicht der Spieler ist (die Hand hält Items nicht über Gelenke fest)
// Per Gelenk mit etwas verbunden, das nicht der Spieler ist (die Hand hält Items nicht über Gelenke fest).
// Läuft alle 0,5 s für jedes Teil: erst billig prüfen, ob es überhaupt ein Gelenk hat (GetComponents legt
// jedes Mal ein neues Array an - die allermeisten Teile haben keins).
if (b.Rb.GetComponent<Joint>() == null) return false;
Transform playerRoot = Head != null ? Head.root : null;
foreach (Joint j in b.Rb.GetComponents<Joint>())
if (j != null && j.connectedBody != null && j.connectedBody.transform.root.name != "PLAYER") return true;
{
if (j == null || j.connectedBody == null) continue;
Transform root = j.connectedBody.transform.root;
if (playerRoot != null ? root != playerRoot : root.name != "PLAYER") return true;
}
return false;
}
+15 -4
View File
@@ -30,8 +30,13 @@ namespace MSCO
public string Name, Text;
public bool System;
public float Time;
// "Name:" und seine Breite - einmal berechnet statt bei jedem OnGUI-Durchlauf (2-3x pro Bild)
public string NameLabel;
public float NameWidth = -1f;
}
readonly List<ChatLine> visibleChat = new List<ChatLine>();
// Konsole
public bool ConsoleOpen { get; private set; }
readonly List<string> consoleLines = new List<string>();
@@ -211,7 +216,8 @@ namespace MSCO
int maxLines = ChatOpen ? 12 : 6;
// Sichtbare Zeilen: offen = die letzten, geschlossen = nur neue (blenden aus)
var visible = new List<ChatLine>();
var visible = visibleChat;
visible.Clear();
for (int i = chat.Count - 1; i >= 0 && visible.Count < maxLines; i--)
if (ChatOpen || now - chat[i].Time < ChatFadeSeconds) visible.Insert(0, chat[i]);
@@ -233,9 +239,14 @@ namespace MSCO
}
else
{
Vector2 size = chatName.CalcSize(new GUIContent(line.Name + ":"));
GUI.Label(new Rect(rect.x + 12, y, size.x, lineHeight), line.Name + ":", chatName);
GUI.Label(new Rect(rect.x + 16 + size.x, y, width - 28 - size.x, lineHeight), line.Text, chatText);
if (line.NameWidth < 0f)
{
line.NameLabel = line.Name + ":";
line.NameWidth = chatName.CalcSize(new GUIContent(line.NameLabel)).x;
}
float nameWidth = line.NameWidth;
GUI.Label(new Rect(rect.x + 12, y, nameWidth, lineHeight), line.NameLabel, chatName);
GUI.Label(new Rect(rect.x + 16 + nameWidth, y, width - 28 - nameWidth, lineHeight), line.Text, chatText);
}
y += lineHeight;
}