Skip to content

Commit f3ebb53

Browse files
milanglacierkarthink
authored andcommitted
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.
1 parent 9ad5167 commit f3ebb53

File tree

2 files changed

+77
-31
lines changed

2 files changed

+77
-31
lines changed

README.org

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ To reset =func=:
4545
By default a debounced function returns =nil= at call time. To change this, run:
4646
#+begin_src emacs-lisp
4747
(timeout-debounce 'func 0.5 'some-return-value)
48-
#+end_src
48+
#+end_src
4949

5050
Instead of advising =func=, you can also create new throttled or debounced versions of it with =timeout-throttle= and =timeout-debounce=:
5151

@@ -59,3 +59,21 @@ These return anonymous functions which you can bind to a symbol with =defalias=
5959
(defalias 'throttled-func (timeout-throttled-func 'func 2.0))
6060
(fset 'throttled-func (timeout-throttled-func 'func 2.0))
6161
#+end_src
62+
63+
*** Dynamic duration
64+
65+
All timeout functions support dynamic duration by passing a symbol or function instead of a number:
66+
67+
#+begin_src emacs-lisp
68+
;; Using a variable for dynamic timeout
69+
(defvar my-timeout 1.5)
70+
(timeout-throttle 'func 'my-timeout) ; uses value of my-timeout
71+
72+
;; Using a function for conditional behavior
73+
(timeout-throttle 'func (lambda () (if busy-p 0.1 2.0)))
74+
75+
;; The duration is evaluated at runtime, so you can change it dynamically
76+
(setq my-timeout 3.0) ; throttle duration is now 3 seconds
77+
#+end_src
78+
79+
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.

timeout.el

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
;;
3232
;; To throttle a function FUNC to run no more than once every 2 seconds, run
3333
;; (timeout-throttle 'func 2.0)
34-
;;
34+
;;
3535
;; To debounce a function FUNC to run after a delay of 0.3 seconds, run
3636
;; (timeout-debounce 'func 0.3)
3737
;;
@@ -45,6 +45,13 @@
4545
;; (defalias 'throttled-func (timeout-throttled-func 'func 2.0))
4646
;; (fset 'throttled-func (timeout-throttled-func 'func 2.0))
4747
;;
48+
;; Dynamic duration is supported by passing a symbol or function instead of
49+
;; a number:
50+
;;
51+
;; (defvar my-timeout 1.5)
52+
;; (timeout-throttle 'func 'my-timeout) ; uses value of my-timeout
53+
;; (timeout-throttle 'func (lambda () (if busy-p 0.1 2.0))) ; conditional
54+
;;
4855
;; The interactive spec and documentation of FUNC is carried over to the new
4956
;; function.
5057

@@ -54,17 +61,27 @@
5461
(define-obsolete-function-alias 'timeout-throttle! 'timeout-throttle "v2.0")
5562
(define-obsolete-function-alias 'timeout-debounce! 'timeout-debounce "v2.0")
5663

64+
(defsubst timeout--eval-value (value)
65+
"Eval a VALUE.
66+
If value is a function (either lambda or a callable symbol), eval the
67+
function (with no argument) and return the result. Else if value is a
68+
symbol, return its value. Else return itself."
69+
(cond ((numberp value) value)
70+
((functionp value) (funcall value))
71+
((and (symbolp value) (boundp value)) (symbol-value value))
72+
(t (error "Invalid value %s" value))))
73+
5774
(defun timeout--throttle-advice (&optional timeout)
5875
"Return a function that throttles its argument function.
5976
60-
TIMEOUT defaults to 1 second.
77+
For the meaning of TIMEOUT see `timeout-throttle'.
6178
6279
When FUNC does not run because of the throttle, the result from the
6380
previous successful call is returned.
6481
6582
This is intended for use as function advice."
6683
(let ((throttle-timer)
67-
(timeout (or timeout 1.0))
84+
(timeout-value (or timeout 1.0))
6885
(result))
6986
(lambda (orig-fn &rest args)
7087
"Throttle calls to this function."
@@ -73,29 +90,31 @@ This is intended for use as function advice."
7390
(setq result (apply orig-fn args))
7491
(setq throttle-timer
7592
(run-with-timer
76-
timeout nil
93+
(timeout--eval-value timeout-value) nil
7794
(lambda ()
7895
(cancel-timer throttle-timer)
7996
(setq throttle-timer nil)))))))))
8097

8198
(defun timeout--debounce-advice (&optional delay default)
8299
"Return a function that debounces its argument function.
83100
84-
DELAY defaults to 0.50 seconds. The function returns immediately with
85-
value DEFAULT when called the first time. On future invocations, the
86-
result from the previous call is returned.
101+
For the meaning of DELAY see `timeout-debounce'.
102+
103+
The function returns immediately with value DEFAULT when called the
104+
first time. On future invocations, the result from the previous call is
105+
returned.
87106
88107
This is intended for use as function advice."
89108
(let ((debounce-timer nil)
90-
(delay (or delay 0.50)))
109+
(delay-value (or delay 0.50)))
91110
(lambda (orig-fn &rest args)
92111
"Debounce calls to this function."
93112
(prog1 default
94113
(if (timerp debounce-timer)
95-
(timer-set-idle-time debounce-timer delay)
114+
(timer-set-idle-time debounce-timer (timeout--eval-value delay-value))
96115
(setq debounce-timer
97116
(run-with-idle-timer
98-
delay nil
117+
(timeout--eval-value delay-value) nil
99118
(lambda (buf)
100119
(cancel-timer debounce-timer)
101120
(setq debounce-timer nil)
@@ -114,13 +133,15 @@ This advises FUNC, when called (interactively or from code), to
114133
run after DELAY seconds. If FUNC is called again within this time,
115134
the timer is reset.
116135
117-
DELAY defaults to 0.5 seconds. Using a delay of 0 removes any
118-
debounce advice.
136+
DELAY defaults to 0.5 seconds. DELAY can be a number, a symbol (whose
137+
value is a number), or a function (that evaluates to a number). When
138+
passed a symbol or function, it is evaluated at runtime for dynamic
139+
duration. Using a delay of 0 removes any debounce advice.
119140
120141
The function returns immediately with value DEFAULT when called the
121142
first time. On future invocations, the result from the previous call is
122143
returned."
123-
(if (and delay (= delay 0))
144+
(if (and delay (eq delay 0))
124145
(advice-remove func 'debounce)
125146
(advice-add func :around (timeout--debounce-advice delay default)
126147
'((name . debounce)
@@ -130,12 +151,14 @@ returned."
130151
(defun timeout-throttle (func &optional throttle)
131152
"Make FUNC run no more frequently than once every THROTTLE seconds.
132153
133-
THROTTLE defaults to 1 second. Using a throttle of 0 removes any
134-
throttle advice.
154+
THROTTLE defaults to 1 second. THROTTLE can be a number, a symbol (whose
155+
value is a number), or a function (that evaluates to a number). When
156+
passed a symbol or function, it is evaluated at runtime for dynamic
157+
duration. Using a throttle of 0 removes any throttle advice.
135158
136159
When FUNC does not run because of the throttle, the result from the
137160
previous successful call is returned."
138-
(if (and throttle (= throttle 0))
161+
(if (and throttle (eq throttle 0))
139162
(advice-remove func 'throttle)
140163
(advice-add func :around (timeout--throttle-advice throttle)
141164
'((name . throttle)
@@ -145,28 +168,31 @@ previous successful call is returned."
145168
"Return a throttled version of function FUNC.
146169
147170
The throttled function runs no more frequently than once every THROTTLE
148-
seconds. THROTTLE defaults to 1 second.
171+
seconds. THROTTLE defaults to 1 second. THROTTLE can be a number, a
172+
symbol (whose value is a number), or a function (that evaluates to a
173+
number). When passed a symbol or function, it is evaluated at runtime
174+
for dynamic duration.
149175
150176
When FUNC does not run because of the throttle, the result from the
151177
previous successful call is returned."
152178
(let ((throttle-timer nil)
153-
(throttle (or throttle 1))
179+
(throttle-value (or throttle 1))
154180
(result))
155181
(if (commandp func)
156182
;; INTERACTIVE version
157183
(lambda (&rest args)
158184
(:documentation
159185
(concat
160186
(documentation func)
161-
(format "\n\nThrottle calls to this function by %f seconds" throttle)))
187+
"\n\nThrottle calls to this function"))
162188
(interactive (advice-eval-interactive-spec
163189
(cadr (interactive-form func))))
164190
(prog1 result
165191
(unless (and throttle-timer (timerp throttle-timer))
166192
(setq result (apply func args))
167193
(setq throttle-timer
168194
(run-with-timer
169-
throttle nil
195+
(timeout--eval-value throttle-value) nil
170196
(lambda ()
171197
(cancel-timer throttle-timer)
172198
(setq throttle-timer nil)))))))
@@ -175,13 +201,13 @@ previous successful call is returned."
175201
(:documentation
176202
(concat
177203
(documentation func)
178-
(format "\n\nThrottle calls to this function by %f seconds" throttle)))
204+
"\n\nThrottle calls to this function"))
179205
(prog1 result
180206
(unless (and throttle-timer (timerp throttle-timer))
181207
(setq result (apply func args))
182208
(setq throttle-timer
183209
(run-with-timer
184-
throttle nil
210+
(timeout--eval-value throttle-value) nil
185211
(lambda ()
186212
(cancel-timer throttle-timer)
187213
(setq throttle-timer nil))))))))))
@@ -190,28 +216,30 @@ previous successful call is returned."
190216
"Return a debounced version of function FUNC.
191217
192218
The debounced function runs DELAY seconds after it is called. DELAY
193-
defaults to 0.5 seconds.
219+
defaults to 0.5 seconds. DELAY can be a number, a symbol (whose value
220+
is a number), or a function (that evaluates to a number). When passed
221+
a symbol or function, it is evaluated at runtime for dynamic duration.
194222
195223
The function returns immediately with value DEFAULT when called the
196224
first time. On future invocations, the result from the previous call is
197225
returned."
198226
(let ((debounce-timer nil)
199-
(delay (or delay 0.50)))
227+
(delay-value (or delay 0.50)))
200228
(if (commandp func)
201229
;; INTERACTIVE version
202230
(lambda (&rest args)
203231
(:documentation
204232
(concat
205233
(documentation func)
206-
(format "\n\nDebounce calls to this function by %f seconds" delay)))
234+
"\n\nDebounce calls to this function"))
207235
(interactive (advice-eval-interactive-spec
208236
(cadr (interactive-form func))))
209237
(prog1 default
210238
(if (timerp debounce-timer)
211-
(timer-set-idle-time debounce-timer delay)
239+
(timer-set-idle-time debounce-timer (timeout--eval-value delay-value))
212240
(setq debounce-timer
213241
(run-with-idle-timer
214-
delay nil
242+
(timeout--eval-value delay-value) nil
215243
(lambda (buf)
216244
(cancel-timer debounce-timer)
217245
(setq debounce-timer nil)
@@ -226,13 +254,13 @@ returned."
226254
(:documentation
227255
(concat
228256
(documentation func)
229-
(format "\n\nDebounce calls to this function by %f seconds" delay)))
257+
"\n\nDebounce calls to this function"))
230258
(prog1 default
231259
(if (timerp debounce-timer)
232-
(timer-set-idle-time debounce-timer delay)
260+
(timer-set-idle-time debounce-timer (timeout--eval-value delay-value))
233261
(setq debounce-timer
234262
(run-with-idle-timer
235-
delay nil
263+
(timeout--eval-value delay-value) nil
236264
(lambda (buf)
237265
(cancel-timer debounce-timer)
238266
(setq debounce-timer nil)

0 commit comments

Comments
 (0)