Skip to content

Commit

Permalink
Add debouncing to superzip
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsievert committed Apr 12, 2024
1 parent 0f701b3 commit befebca
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 1 deletion.
4 changes: 3 additions & 1 deletion examples/superzip/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ipywidgets
import pandas as pd
from faicons import icon_svg
from ratelimit import debounce
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
from utils import col_numeric, create_map, density_plot, heatmap_gradient

Expand Down Expand Up @@ -173,6 +174,7 @@ def _():
with reactive.isolate():
current_bounds.set(bb)

@debounce(0.3)
@reactive.calc
def zips_in_bounds():
bb = req(current_bounds())
Expand Down Expand Up @@ -326,4 +328,4 @@ def _on_click(**kwargs):
return m


app = App(app_ui, server)
app = App(app_ui, server, static_assets=app_dir / "www")
103 changes: 103 additions & 0 deletions examples/superzip/ratelimit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# From https://gist.github.com/jcheng5/427de09573816c4ce3a8c6ec1839e7c0
import functools
import time

from shiny import reactive


def debounce(delay_secs):
def wrapper(f):
when = reactive.Value(None)
trigger = reactive.Value(0)

@reactive.Calc
def cached():
"""
Just in case f isn't a reactive calc already, wrap it in one. This ensures
that f() won't execute any more than it needs to.
"""
return f()

@reactive.Effect(priority=102)
def primer():
"""
Whenever cached() is invalidated, set a new deadline for when to let
downstream know--unless cached() invalidates again
"""
try:
cached()
except Exception:
...
finally:
when.set(time.time() + delay_secs)

@reactive.Effect(priority=101)
def timer():
"""
Watches changes to the deadline and triggers downstream if it's expired; if
not, use invalidate_later to wait the necessary time and then try again.
"""
deadline = when()
if deadline is None:
return
time_left = deadline - time.time()
if time_left <= 0:
# The timer expired
with reactive.isolate():
when.set(None)
trigger.set(trigger() + 1)
else:
reactive.invalidate_later(time_left)

@reactive.Calc
@reactive.event(trigger, ignore_none=False)
@functools.wraps(f)
def debounced():
return cached()

return debounced

return wrapper


def throttle(delay_secs):
def wrapper(f):
last_signaled = reactive.Value(None)
last_triggered = reactive.Value(None)
trigger = reactive.Value(0)

@reactive.Calc
def cached():
return f()

@reactive.Effect(priority=102)
def primer():
try:
cached()
except Exception:
...
finally:
last_signaled.set(time.time())

@reactive.Effect(priority=101)
def timer():
if last_triggered() is not None and last_signaled() < last_triggered():
return

now = time.time()
if last_triggered() is None or (now - last_triggered()) >= delay_secs:
last_triggered.set(now)
with reactive.isolate():
trigger.set(trigger() + 1)
else:
reactive.invalidate_later(delay_secs - (now - last_triggered()))

@reactive.Calc
@reactive.event(trigger, ignore_none=False)
@functools.wraps(f)
def throttled():
return cached()

return throttled

return wrapper

0 comments on commit befebca

Please sign in to comment.