Features: - ConsistLOD: fast-path Bezier position update for distant cars (PositionAccuracy matched, LocationF/R synced, OnPosition fired, culler sphere kept current) - ConsistFreezer: per-consist auto-freeze replacing stock all-or-nothing optimizer - PhysicsTimer: stacked ring buffers (render, FixedUpdate, Tick, PosCars) + report - PhysicsOverlayGUI: draggable stacked-area chart, reference lines always visible, ON/OFF toggles with persistence, opacity control - Settings: JSON persistence, full UMM settings panel with auto-save - ConsoleCommands: /rpf with freeze/unfreeze/dump/timing/overlay/forceactive/lod
106 lines
3.9 KiB
C#
106 lines
3.9 KiB
C#
using System.Collections.Generic;
|
|
using HarmonyLib;
|
|
using Model;
|
|
using Model.Physics;
|
|
using UnityEngine;
|
|
|
|
namespace RailroaderPhysicsOverhaul;
|
|
|
|
public static class ConsistFreezer
|
|
{
|
|
// Manual freeze — explicit /rpf freeze command
|
|
static readonly HashSet<uint> _frozenSetIds = new();
|
|
public static bool IsFrozen(uint setId) => _frozenSetIds.Contains(setId);
|
|
public static bool Freeze(uint setId) => _frozenSetIds.Add(setId);
|
|
public static bool Unfreeze(uint setId) => _frozenSetIds.Remove(setId);
|
|
public static void ClearAll() => _frozenSetIds.Clear();
|
|
|
|
// Auto-freeze: skip the Verlet tick for consists that are far from the camera
|
|
// and moving slowly enough that physics quality there doesn't matter.
|
|
// This is our integrated replacement for the stock optimizer (AllCarsAtRest only).
|
|
public static bool AutoFreezeEnabled = true;
|
|
public static float AutoFreezeDistance = 200f; // meters
|
|
public static float AutoFreezeSpeedThreshold = 0.3f; // m/s (~0.7 mph)
|
|
|
|
static float AutoFreezeDistanceSq => AutoFreezeDistance * AutoFreezeDistance;
|
|
|
|
// Per-tick frozen-car counters — flushed to Last* by FlushFrameCounts().
|
|
// internal so ShouldSkipTickPatch (same file, different class) can increment them.
|
|
internal static int _frameAtRestCars;
|
|
internal static int _frameDistanceCars;
|
|
public static int LastAtRestCars { get; private set; }
|
|
public static int LastDistanceCars { get; private set; }
|
|
|
|
internal static void FlushFrameCounts()
|
|
{
|
|
LastAtRestCars = _frameAtRestCars;
|
|
LastDistanceCars = _frameDistanceCars;
|
|
_frameAtRestCars = 0;
|
|
_frameDistanceCars = 0;
|
|
}
|
|
|
|
// Called from ShouldSkipTickPatch. Internal so the patch class (same file) can reach it.
|
|
internal static bool ShouldAutoFreeze(IntegrationSet set)
|
|
{
|
|
if (!AutoFreezeEnabled) return false;
|
|
Camera cam = Camera.main;
|
|
if (cam == null) return false;
|
|
Vector3 camPos = cam.transform.position;
|
|
float distSqThresh = AutoFreezeDistanceSq;
|
|
float speedThresh = AutoFreezeSpeedThreshold;
|
|
|
|
foreach (Car car in set.Cars)
|
|
{
|
|
Transform body = car.BodyTransform;
|
|
if (body == null) continue;
|
|
|
|
// If ANY car is within distance → don't freeze the consist.
|
|
if ((body.position - camPos).sqrMagnitude < distSqThresh) return false;
|
|
|
|
// If ANY car is moving above threshold → don't freeze.
|
|
if (Mathf.Abs(car.velocity) >= speedThresh) return false;
|
|
}
|
|
|
|
// Every car is both far and slow — safe to skip the Verlet tick.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Completely replaces the stock ShouldSkipTick getter.
|
|
// Priority order:
|
|
// 1. Manual freeze → always skip
|
|
// 2. ForceActive → never skip (profiling bypass)
|
|
// 3. AllCarsAtRest → stock optimizer behaviour, replicated explicitly
|
|
// 4. Auto-freeze → our distance + speed tier
|
|
[HarmonyPatch(typeof(IntegrationSet), "get_ShouldSkipTick")]
|
|
static class ShouldSkipTickPatch
|
|
{
|
|
static bool Prefix(IntegrationSet __instance, ref bool __result)
|
|
{
|
|
if (ConsistFreezer.IsFrozen(__instance.Id))
|
|
{
|
|
__result = true;
|
|
return false;
|
|
}
|
|
|
|
if (PhysicsTimer.ForceActive)
|
|
{
|
|
__result = false;
|
|
return false;
|
|
}
|
|
|
|
// Replicate stock optimizer behaviour explicitly so it works with or without
|
|
// the stock optimizer mod installed.
|
|
if (__instance.AllCarsAtRest())
|
|
{
|
|
ConsistFreezer._frameAtRestCars += __instance.NumberOfCars;
|
|
__result = true;
|
|
return false;
|
|
}
|
|
|
|
__result = ConsistFreezer.ShouldAutoFreeze(__instance);
|
|
if (__result)
|
|
ConsistFreezer._frameDistanceCars += __instance.NumberOfCars;
|
|
return false; // always override — stock getter never runs
|
|
}
|
|
}
|