diff options
| author | Milan Glacier <dev@milanglacier.com> | 2025-09-03 14:28:45 -0400 |
|---|---|---|
| committer | Karthik Chikmagalur <karthikchikmagalur@gmail.com> | 2025-09-07 20:34:30 -0700 |
| commit | f3ebb53ef15aaa302d9c92863cb4a5d99419e89d (patch) | |
| tree | 578bfeb899d1a860bb9e93b29b35029d5fcdeb46 | |
| parent | 9ad5167024f96bea45669acd862615ca379feddf (diff) | |
timeout: Allow dynamic delays for throttle and debounce
* timeout.el (timeout--eval-value, timeout--throttle-advice,
timeout--debounce-advice, timeout-debounce, timeout-throttle,
timeout-throttled-func, timeout-debounced-func): Allow debounce
and throttle delays to be dynamic, supplied via a symbol (whose
value is used) or a function (whose result is used).
* README.org: Update instructions for dynamic delay.
| -rw-r--r-- | README.org | 20 | ||||
| -rw-r--r-- | timeout.el | 88 |
2 files changed, 77 insertions, 31 deletions
@@ -45,7 +45,7 @@ To reset =func=: By default a debounced function returns =nil= at call time. To change this, run: #+begin_src emacs-lisp (timeout-debounce 'func 0.5 'some-return-value) -#+end_src +#+end_src Instead of advising =func=, you can also create new throttled or debounced versions of it with =timeout-throttle= and =timeout-debounce=: @@ -59,3 +59,21 @@ These return anonymous functions which you can bind to a symbol with =defalias= (defalias 'throttled-func (timeout-throttled-func 'func 2.0)) (fset 'throttled-func (timeout-throttled-func 'func 2.0)) #+end_src + +*** Dynamic duration + +All timeout functions support dynamic duration by passing a symbol or function instead of a number: + +#+begin_src emacs-lisp +;; Using a variable for dynamic timeout +(defvar my-timeout 1.5) +(timeout-throttle 'func 'my-timeout) ; uses value of my-timeout + +;; Using a function for conditional behavior +(timeout-throttle 'func (lambda () (if busy-p 0.1 2.0))) + +;; The duration is evaluated at runtime, so you can change it dynamically +(setq my-timeout 3.0) ; throttle duration is now 3 seconds +#+end_src + +When passed a symbol, its value is used as the duration. When passed a function, it is called with no arguments to get the duration. This allows for adaptive timeouts based on system state or user preferences. @@ -31,7 +31,7 @@ ;; ;; To throttle a function FUNC to run no more than once every 2 seconds, run ;; (timeout-throttle 'func 2.0) -;; +;; ;; To debounce a function FUNC to run after a delay of 0.3 seconds, run ;; (timeout-debounce 'func 0.3) ;; @@ -45,6 +45,13 @@ ;; (defalias 'throttled-func (timeout-throttled-func 'func 2.0)) ;; (fset 'throttled-func (timeout-throttled-func 'func 2.0)) ;; +;; Dynamic duration is supported by passing a symbol or function instead of +;; a number: +;; +;; (defvar my-timeout 1.5) +;; (timeout-throttle 'func 'my-timeout) ; uses value of my-timeout +;; (timeout-throttle 'func (lambda () (if busy-p 0.1 2.0))) ; conditional +;; ;; The interactive spec and documentation of FUNC is carried over to the new ;; function. @@ -54,17 +61,27 @@ (define-obsolete-function-alias 'timeout-throttle! 'timeout-throttle "v2.0") (define-obsolete-function-alias 'timeout-debounce! 'timeout-debounce "v2.0") +(defsubst timeout--eval-value (value) + "Eval a VALUE. +If value is a function (either lambda or a callable symbol), eval the +function (with no argument) and return the result. Else if value is a +symbol, return its value. Else return itself." + (cond ((numberp value) value) + ((functionp value) (funcall value)) + ((and (symbolp value) (boundp value)) (symbol-value value)) + (t (error "Invalid value %s" value)))) + (defun timeout--throttle-advice (&optional timeout) "Return a function that throttles its argument function. -TIMEOUT defaults to 1 second. +For the meaning of TIMEOUT see `timeout-throttle'. When FUNC does not run because of the throttle, the result from the previous successful call is returned. This is intended for use as function advice." (let ((throttle-timer) - (timeout (or timeout 1.0)) + (timeout-value (or timeout 1.0)) (result)) (lambda (orig-fn &rest args) "Throttle calls to this function." @@ -73,7 +90,7 @@ This is intended for use as function advice." (setq result (apply orig-fn args)) (setq throttle-timer (run-with-timer - timeout nil + (timeout--eval-value timeout-value) nil (lambda () (cancel-timer throttle-timer) (setq throttle-timer nil))))))))) @@ -81,21 +98,23 @@ This is intended for use as function advice." (defun timeout--debounce-advice (&optional delay default) "Return a function that debounces its argument function. -DELAY defaults to 0.50 seconds. The function returns immediately with -value DEFAULT when called the first time. On future invocations, the -result from the previous call is returned. +For the meaning of DELAY see `timeout-debounce'. + +The function returns immediately with value DEFAULT when called the +first time. On future invocations, the result from the previous call is +returned. This is intended for use as function advice." (let ((debounce-timer nil) - (delay (or delay 0.50))) + (delay-value (or delay 0.50))) (lambda (orig-fn &rest args) "Debounce calls to this function." (prog1 default (if (timerp debounce-timer) - (timer-set-idle-time debounce-timer delay) + (timer-set-idle-time debounce-timer (timeout--eval-value delay-value)) (setq debounce-timer (run-with-idle-timer - delay nil + (timeout--eval-value delay-value) nil (lambda (buf) (cancel-timer debounce-timer) (setq debounce-timer nil) @@ -114,13 +133,15 @@ This advises FUNC, when called (interactively or from code), to run after DELAY seconds. If FUNC is called again within this time, the timer is reset. -DELAY defaults to 0.5 seconds. Using a delay of 0 removes any -debounce advice. +DELAY defaults to 0.5 seconds. DELAY can be a number, a symbol (whose +value is a number), or a function (that evaluates to a number). When +passed a symbol or function, it is evaluated at runtime for dynamic +duration. Using a delay of 0 removes any debounce advice. The function returns immediately with value DEFAULT when called the first time. On future invocations, the result from the previous call is returned." - (if (and delay (= delay 0)) + (if (and delay (eq delay 0)) (advice-remove func 'debounce) (advice-add func :around (timeout--debounce-advice delay default) '((name . debounce) @@ -130,12 +151,14 @@ returned." (defun timeout-throttle (func &optional throttle) "Make FUNC run no more frequently than once every THROTTLE seconds. -THROTTLE defaults to 1 second. Using a throttle of 0 removes any -throttle advice. +THROTTLE defaults to 1 second. THROTTLE can be a number, a symbol (whose +value is a number), or a function (that evaluates to a number). When +passed a symbol or function, it is evaluated at runtime for dynamic +duration. Using a throttle of 0 removes any throttle advice. When FUNC does not run because of the throttle, the result from the previous successful call is returned." - (if (and throttle (= throttle 0)) + (if (and throttle (eq throttle 0)) (advice-remove func 'throttle) (advice-add func :around (timeout--throttle-advice throttle) '((name . throttle) @@ -145,12 +168,15 @@ previous successful call is returned." "Return a throttled version of function FUNC. The throttled function runs no more frequently than once every THROTTLE -seconds. THROTTLE defaults to 1 second. +seconds. THROTTLE defaults to 1 second. THROTTLE can be a number, a +symbol (whose value is a number), or a function (that evaluates to a +number). When passed a symbol or function, it is evaluated at runtime +for dynamic duration. When FUNC does not run because of the throttle, the result from the previous successful call is returned." (let ((throttle-timer nil) - (throttle (or throttle 1)) + (throttle-value (or throttle 1)) (result)) (if (commandp func) ;; INTERACTIVE version @@ -158,7 +184,7 @@ previous successful call is returned." (:documentation (concat (documentation func) - (format "\n\nThrottle calls to this function by %f seconds" throttle))) + "\n\nThrottle calls to this function")) (interactive (advice-eval-interactive-spec (cadr (interactive-form func)))) (prog1 result @@ -166,7 +192,7 @@ previous successful call is returned." (setq result (apply func args)) (setq throttle-timer (run-with-timer - throttle nil + (timeout--eval-value throttle-value) nil (lambda () (cancel-timer throttle-timer) (setq throttle-timer nil))))))) @@ -175,13 +201,13 @@ previous successful call is returned." (:documentation (concat (documentation func) - (format "\n\nThrottle calls to this function by %f seconds" throttle))) + "\n\nThrottle calls to this function")) (prog1 result (unless (and throttle-timer (timerp throttle-timer)) (setq result (apply func args)) (setq throttle-timer (run-with-timer - throttle nil + (timeout--eval-value throttle-value) nil (lambda () (cancel-timer throttle-timer) (setq throttle-timer nil)))))))))) @@ -190,28 +216,30 @@ previous successful call is returned." "Return a debounced version of function FUNC. The debounced function runs DELAY seconds after it is called. DELAY -defaults to 0.5 seconds. +defaults to 0.5 seconds. DELAY can be a number, a symbol (whose value +is a number), or a function (that evaluates to a number). When passed +a symbol or function, it is evaluated at runtime for dynamic duration. The function returns immediately with value DEFAULT when called the first time. On future invocations, the result from the previous call is returned." (let ((debounce-timer nil) - (delay (or delay 0.50))) + (delay-value (or delay 0.50))) (if (commandp func) ;; INTERACTIVE version (lambda (&rest args) (:documentation (concat (documentation func) - (format "\n\nDebounce calls to this function by %f seconds" delay))) + "\n\nDebounce calls to this function")) (interactive (advice-eval-interactive-spec (cadr (interactive-form func)))) (prog1 default (if (timerp debounce-timer) - (timer-set-idle-time debounce-timer delay) + (timer-set-idle-time debounce-timer (timeout--eval-value delay-value)) (setq debounce-timer (run-with-idle-timer - delay nil + (timeout--eval-value delay-value) nil (lambda (buf) (cancel-timer debounce-timer) (setq debounce-timer nil) @@ -226,13 +254,13 @@ returned." (:documentation (concat (documentation func) - (format "\n\nDebounce calls to this function by %f seconds" delay))) + "\n\nDebounce calls to this function")) (prog1 default (if (timerp debounce-timer) - (timer-set-idle-time debounce-timer delay) + (timer-set-idle-time debounce-timer (timeout--eval-value delay-value)) (setq debounce-timer (run-with-idle-timer - delay nil + (timeout--eval-value delay-value) nil (lambda (buf) (cancel-timer debounce-timer) (setq debounce-timer nil) |
