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; } }