Skip to content

Instantly share code, notes, and snippets.

@keijiro
Last active November 5, 2024 06:53
Show Gist options
  • Save keijiro/df2c866ccbdb2ac02fded6e70aaae04a to your computer and use it in GitHub Desktop.
Save keijiro/df2c866ccbdb2ac02fded6e70aaae04a to your computer and use it in GitHub Desktop.
FpsCapper - Limits the frame rate of the Unity Editor in Edit Mode
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment