using System; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using UnityEngine; namespace VRC.SDKBase.Editor { internal class DebouncedCall { private bool _debounced; private DateTime _lastCall; private readonly TimeSpan _delay; private readonly Action _action; private readonly ExecuteMode _mode; /// /// Debounce execution mode /// internal enum ExecuteMode { /// /// Always execute on the first call. Consecutive calls are debounced /// Start, /// /// Only execute after debounce delay /// End } /// /// Creates a Debounced action wrapper /// /// How long to wait until executing the call. The actual will always be executed after the specified from the first invocation instead of being delayed more /// Action to call /// Execution mode internal DebouncedCall(TimeSpan delay, Action action, ExecuteMode executeMode) { _delay = delay; _action = action; _mode = executeMode; } internal void Invoke() { if (_debounced) return; // If we haven't called the action yet or the delay has passed, call the action if (_mode == ExecuteMode.Start && _lastCall.Add(_delay) <= DateTime.Now) { _lastCall = DateTime.Now; _action(); return; } _debounced = true; // Wait full delay before calling the action _lastCall = DateTime.Now; // Wait for the delay to pass before calling the action Task.Run(async () => { await UniTask.WaitUntil(() => _lastCall.Add(_delay) < DateTime.Now); await UniTask.SwitchToMainThread(); _debounced = false; _lastCall = DateTime.Now; _action(); }) .ConfigureAwait(false); } } }