using UnityEditor; using UnityEngine; using UnityEngine.LowLevel; using System.Linq; using System.Threading; namespace EditorUtils { // // Serializable settings // [FilePath("UserSettings/FpsCapperSettings.asset", FilePathAttribute.Location.ProjectFolder)] public sealed class FpsCapperSettings : ScriptableSingleton<FpsCapperSettings> { public bool enable = false; public int targetFrameRate = 60; public void Save() => Save(true); void OnDisable() => Save(); } // // Settings GUI // sealed class FpsCapperSettingsProvider : SettingsProvider { public FpsCapperSettingsProvider() : base("Project/FPS Capper", SettingsScope.Project) {} public override void OnGUI(string search) { var settings = FpsCapperSettings.instance; var enable = settings.enable; var fps = settings.targetFrameRate; EditorGUI.BeginChangeCheck(); enable = EditorGUILayout.Toggle("Enable", enable); fps = EditorGUILayout.IntField("Target Frame Rate", fps); if (EditorGUI.EndChangeCheck()) { settings.enable = enable; settings.targetFrameRate = fps; settings.Save(); } } [SettingsProvider] public static SettingsProvider CreateCustomSettingsProvider() => new FpsCapperSettingsProvider(); } // // Player loop system // [UnityEditor.InitializeOnLoad] sealed class FpsCapperSystem { // Synchronization object static AutoResetEvent _sync; // Interval in milliseconds static int IntervalMsec; // Interval thread function static void IntervalThread() { _sync = new AutoResetEvent(true); while (true) { Thread.Sleep(Mathf.Max(1, IntervalMsec)); _sync.Set(); } } // Custom system update function static void UpdateSystem() { var cfg = FpsCapperSettings.instance; // Property update IntervalMsec = 1000 / Mathf.Max(5, cfg.targetFrameRate); // Rejection cases if (_sync == null) return; // Not ready if (!cfg.enable) return; // Not enabled if (cfg.targetFrameRate < 1) return; // Wrong FPS value if (Time.captureDeltaTime != 0) return; // Recording // Synchronization with the interval thread _sync.WaitOne(); } // Static constructor (custom system installation) static FpsCapperSystem() { // Interval thread launch new Thread(IntervalThread).Start(); // Custom system definition var system = new PlayerLoopSystem() { type = typeof(FpsCapperSystem), updateDelegate = UpdateSystem }; // Custom system insertion var playerLoop = PlayerLoop.GetCurrentPlayerLoop(); for (var i = 0; i < playerLoop.subSystemList.Length; i++) { ref var phase = ref playerLoop.subSystemList[i]; if (phase.type == typeof(UnityEngine.PlayerLoop.EarlyUpdate)) { phase.subSystemList = phase.subSystemList.Concat(new[]{system}).ToArray(); break; } } PlayerLoop.SetPlayerLoop(playerLoop); } } } // namespace EditorUtils