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] ===