using System;
using HarmonyLib;
using S3.Core;
using UnityEngine;
namespace S3.Modules.PhysicsOptimizer;
///
/// S³ module wrapping the physics-CPU optimizations (LOD fast-path + auto-freeze)
/// plus the profiler overlay and debug visualizer.
///
/// Owns a Harmony instance keyed "S3.physics" and patches only its own types, so
/// the patches exist only while the module is enabled. is
/// a static instance the overlay/visualizer read directly.
///
public sealed class PhysicsOptimizerModule : IModule
{
private const string SettingsFile = "S3.physics.json";
public static PhysicsSettings Settings { get; private set; } = new();
private static Harmony? _harmony;
// Attribute-annotated patch classes the module owns. PosCarsTimerPatch is NOT
// here — it has no [HarmonyPatch] attribute and is applied dynamically by
// PhysicsTimer.TryPatchPositionCars (method name varies by game version).
private static readonly Type[] PatchTypes =
{
typeof(PositionWheelBoundsFrontLODPatch),
typeof(TrainControllerFixedUpdateCounterPatch),
typeof(ShouldSkipTickPatch),
typeof(TickTimerPatch),
typeof(FixedUpdateTimerPatch),
typeof(ConsoleCommandPatch),
};
public PhysicsOptimizerModule() => Settings = SettingsStore.Load(SettingsFile);
public string Id => "physics";
public string DisplayName => "Physics Optimizer";
public string Description =>
"Cuts CPU time spent on train physics (LOD fast-path + auto-freeze for far/slow consists). " +
"Includes a profiler overlay and debug car tinting. Console: /rpf";
public bool Enabled
{
get => Settings.enabled;
set => Settings.enabled = value;
}
public void OnEnable()
{
ApplySettingsToStatics();
_harmony = new Harmony("S3.physics");
foreach (Type t in PatchTypes)
_harmony.CreateClassProcessor(t).Patch();
PhysicsTimer.TryPatchPositionCars(_harmony, Main.ModEntry.Logger);
var go = new GameObject("S3.PhysicsOptimizer.Host");
UnityEngine.Object.DontDestroyOnLoad(go);
PhysicsOverlayGUI overlay = go.AddComponent();
overlay.Visible = Settings.ShowOverlay;
overlay.Opacity = Settings.OverlayOpacity;
go.AddComponent();
}
public void OnDisable()
{
// Reserved for live toggling. Today modules apply on next launch, so this
// is not called; when it is, unpatch via _harmony.UnpatchAll("S3.physics")
// and destroy the host GameObject.
_harmony?.UnpatchAll("S3.physics");
_harmony = null;
}
public void SaveSettings() => Persist();
internal static void Persist() => SettingsStore.Save(SettingsFile, Settings);
public void DrawSettings() => PhysicsSettingsUI.Draw();
private static void ApplySettingsToStatics()
{
ConsistLOD.Enabled = Settings.OptimizerEnabled;
ConsistLOD.DistanceThreshold = Settings.DistanceThreshold;
ConsistLOD.ResyncInterval = Settings.ResyncInterval;
ConsistLOD.BlacklistEnabled = Settings.BlacklistEnabled;
ConsistLOD.IsBlacklist = Settings.IsBlacklist;
ConsistLOD.RoadNumberList.Clear();
foreach (string name in Settings.RoadNumberList)
ConsistLOD.RoadNumberList.Add(name);
ConsistLOD.ExcludeLocomotives = Settings.ExcludeLocosFromLOD;
ConsistFreezer.ExcludeLocomotives = Settings.ExcludeLocosFromFreeze;
ConsistFreezer.AutoFreezeEnabled = Settings.AutoFreezeEnabled;
ConsistFreezer.AutoFreezeDistance = Settings.AutoFreezeDistance;
ConsistFreezer.AutoFreezeSpeedThreshold = Settings.AutoFreezeSpeedThreshold;
}
}