using System; using System.Diagnostics.CodeAnalysis; using ADOFAIRenderer.Core; using HarmonyLib; using UnityEngine; namespace ADOFAIRenderer.Patch.Game; // === [MARKER: FIX_SET_FRAMERATE_BUG START] === /// /// Fixes the in-game and render bug where objects (floors, decorations, planets) disappear on the first frame of /// a SetFramerate event (due to camera Z=0 near clip culling) and where parallax/tiles lag behind on subsequent frames. /// /// Defers the manual camera rendering (scrCamera.UpdateCustomFrameRateScreen) to the very end of the frame /// (execution order int.MaxValue - 1), after all components (scrCamera, scrFloor, /// scrDecorationManager, scrPlanet, scrParallax, and scrController) have finished their /// LateUpdate. /// /// [HarmonyPatch] [SuppressMessage("ReSharper", "InconsistentNaming")] public static class CustomFrameRateDeferPatch { private static bool _isExecutingDeferred; private static scrCamera? _pendingCamera; private static readonly Action? UpdateCustomFrameRateScreenInvoker = AccessTools.MethodDelegate>(AccessTools.Method(typeof(scrCamera), "UpdateCustomFrameRateScreen")); /// /// Checks whether the SetFramerate render timing fix is enabled. /// Active if rendering session has the option enabled, or enabled by default during normal in-game play. /// public static bool IsEnabled { get { if (RenderManager.IsActive && RenderManager.Instance?.Configuration?.AdvancedConfig != null) { return RenderManager.Instance.Configuration.AdvancedConfig.FixCustomFrameRate; } return true; } } /// /// Intercepts manual camera render calls from SetCustomFrameRate (Update phase) or CheckUpdateFrameRateScreen /// (LateUpdate phase). If not currently executing the deferred render pass, records the camera instance and /// suppresses the premature render. /// [HarmonyPrefix] [HarmonyPatch(typeof(scrCamera), "UpdateCustomFrameRateScreen")] private static bool DeferManualRender(scrCamera __instance) { if (!IsEnabled) return true; if (_isExecutingDeferred) return true; _pendingCamera = __instance; return false; } /// /// Ensures that the runner component is attached to the camera when it starts. /// [HarmonyPostfix] [HarmonyPatch(typeof(scrCamera), "Start")] private static void AttachRunner(scrCamera __instance) { if (!__instance) return; if (!__instance.gameObject.GetComponent()) { __instance.gameObject.AddComponent(); } } /// /// Invoked at the very end of LateUpdate (Order int.MaxValue - 1). If a manual render was requested during the frame, /// executes the camera's original UpdateCustomFrameRateScreen once with all objects fully prepared. /// internal static void ExecuteDeferredRender() { if (_pendingCamera == null) return; var cam = _pendingCamera; _pendingCamera = null; if (!cam) return; _isExecutingDeferred = true; try { UpdateCustomFrameRateScreenInvoker?.Invoke(cam); } finally { _isExecutingDeferred = false; } } } /// /// Runner component attached to scrCamera that executes deferred custom frame rate renders at the end of LateUpdate. /// Execution order int.MaxValue - 1 guarantees it runs after all normal game components (including /// scrController at 400), but right before PostRenderHook at int.MaxValue. /// [DefaultExecutionOrder(int.MaxValue - 1)] internal class CustomFrameRateDeferredRunner : MonoBehaviour { private void LateUpdate() { CustomFrameRateDeferPatch.ExecuteDeferredRender(); } } // === [MARKER: FIX_SET_FRAMERATE_BUG END] ===