-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: use_render_queue, use_liveness_scope, use_table_listener docs #1044
Changes from 1 commit
4dd2145
5e83bf0
db6294f
e1bb8d0
3b421e5
aa0f999
b5244fb
59a5734
e192458
1c160fc
2bdd05e
c40ad86
31a49ed
2599607
8705101
c40e24e
c26c284
20179a0
a962ad1
250dbbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# use_liveness_scope | ||
|
||
`use_liveness_scope` allows your to interact with the [liveness scope](https://deephaven.io/core/docs/conceptual/liveness-scope-concept/) for a component. Some functions which interact with a component will create live objects that need to be managed by the component to ensure they are kept active. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The primary use case for this is when creating tables outside the component's own function, and passing them as state for the component's next update. If the table is not kept alive by the component, it will be garbage collected and the component will not be able to update with the new data. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Example | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```python | ||
from deephaven import ui, time_table | ||
|
||
|
||
@ui.component | ||
def ui_resetable_table(): | ||
table, set_table = ui.use_state(lambda: time_table("PT1s")) | ||
handle_press = ui.use_liveness_scope(lambda _: set_table(time_table("PT1s")), []) | ||
return [ | ||
ui.action_button( | ||
"Reset", | ||
on_press=handle_press, | ||
), | ||
table, | ||
] | ||
|
||
|
||
resetable_table = ui_resetable_table() | ||
``` | ||
|
||
## UI recommendations | ||
|
||
1. **Avoid using `use_liveness_scope` unless necessary**: This is an advanced feature that should only be used when you need to manage the liveness of objects outside of the component's own function. Prefer instead to derive a live component based on state rather than setting a live component within state. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
2. **Use `use_liveness_scope` to manage live objects**: If you need to manage the liveness of objects created outside of the component's own function, use `use_liveness_scope` to ensure they are kept alive. For more information on liveness scopes and why they are needed, see the [liveness scope documentation](https://deephaven.io/core/docs/conceptual/liveness-scope-concept/). | ||
|
||
## Refactoring to avoid liveness scope | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
In the above example, we could refactor the component to avoid using `use_liveness_scope` by deriving the table from state instead of setting it directly: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which is the better approach? If this is better than using a liveness scope, then why present the first example at all? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's kind of an escape hatch if you need it. Ideally you refactor such that you don't need to use it. |
||
|
||
```python | ||
from deephaven import ui, time_table | ||
|
||
|
||
@ui.component | ||
def ui_resetable_table(): | ||
iteration, set_iteration = ui.use_state(0) | ||
table = ui.use_memo(lambda: time_table("PT1s"), [iteration]) | ||
return [ | ||
ui.action_button( | ||
"Reset", | ||
on_press=lambda: set_iteration(iteration + 1), | ||
), | ||
table, | ||
] | ||
|
||
|
||
resetable_table = ui_resetable_table() | ||
``` | ||
|
||
## API Reference | ||
|
||
```{eval-rst} | ||
.. dhautofunction:: deephaven.ui.use_liveness_scope | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# use_render_queue | ||
|
||
`use_render_queue` lets you use the render queue in your component. This is useful when you want to queue updates on the render thread from a background thread. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Example | ||
|
||
```python | ||
from deephaven import time_table | ||
from deephaven import ui | ||
|
||
_source = time_table("PT5S").update("X = i").tail(5) | ||
|
||
|
||
@ui.component | ||
def toast_table(t): | ||
render_queue = ui.use_render_queue() | ||
|
||
def listener_function(update, is_replay): | ||
data_added = update.added()["X"][0] | ||
render_queue(lambda: ui.toast(f"added {data_added}", timeout=5000)) | ||
|
||
ui.use_table_listener(t, listener_function, []) | ||
return t | ||
|
||
|
||
my_toast_table = toast_table(_source) | ||
``` | ||
|
||
The above example listens to table updates and displays a toast message when the table updates. The `toast` function must be triggered on the render thread, whereas the listener is not fired on the render thread. Therefore, you must use the render queue to trigger the toast. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## UI recommendations | ||
|
||
1. **Use the render queue to trigger toasts**: When you need to trigger a toast from a background thread, use the render queue to ensure the toast is triggered on the render thread. Otherwise, an exception will be raised. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
2. **Use the render queue to batch UI updates from a background thread**: By default, setter functions from the `use_state` hook are already fired on the render thread. However, if you have multiple updates to make to the UI from a background thread, you can use the render queue to batch them together. | ||
|
||
## Batching updates | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Setter functions from the `use_state` hook are fired on the render thread, so if you call a series of updates from a callback on the render thread, they will be batched together. Consider the following, which will increment states `a` and `b` in the callback from pressing on "Update values": | ||
|
||
```python | ||
from deephaven import ui | ||
import time | ||
|
||
|
||
@ui.component | ||
def ui_batch_example(): | ||
a, set_a = ui.use_state(0) | ||
b, set_b = ui.use_state(0) | ||
|
||
ui.toast( | ||
f"Values are {a} and {b}", | ||
variant="negative" if a != b else "neutral", | ||
timeout=5000, | ||
) | ||
|
||
def do_work(): | ||
set_a(lambda new_a: new_a + 1) | ||
# Introduce a bit of delay between updates | ||
time.sleep(0.1) | ||
set_b(lambda new_b: new_b + 1) | ||
|
||
return ui.button("Update values", on_press=do_work) | ||
|
||
|
||
batch_example = ui_batch_example() | ||
``` | ||
|
||
Because `do_work` is called from the render thread (in response to the `on_press` ), `set_a` and `set_b` will queue their updates on the render thread and they will be batched together. This means that the toast will only show once, with the updated values of `a` and `b` and they will always be the same value when the component re-renders. | ||
|
||
If we instead put `do_work` in a background thread, the updates are not guaranteed to be batched together: | ||
|
||
```python | ||
from deephaven import ui | ||
import threading | ||
import time | ||
|
||
|
||
@ui.component | ||
def ui_batch_example(): | ||
a, set_a = ui.use_state(0) | ||
b, set_b = ui.use_state(0) | ||
|
||
ui.toast( | ||
f"Values are {a} and {b}", | ||
variant="negative" if a != b else "neutral", | ||
timeout=5000, | ||
) | ||
|
||
def do_work(): | ||
set_a(lambda new_a: new_a + 1) | ||
# Introduce a bit of delay between updates | ||
time.sleep(0.1) | ||
set_b(lambda new_b: new_b + 1) | ||
|
||
def start_background_thread(): | ||
threading.Thread(target=do_work).start() | ||
|
||
return ui.button("Update values", on_press=start_background_thread) | ||
|
||
|
||
batch_example = ui_batch_example() | ||
``` | ||
|
||
When running the above example, we'll see _two_ toasts with each press of the button: a red one where `a != b` (as `a` gets updated first), then a neutral one where `a == b` (as `b` gets updated second). We can use the `use_render_queue` hook to ensure the updates are always batched together when working with a background thread: | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```python | ||
from deephaven import ui | ||
import threading | ||
import time | ||
|
||
|
||
@ui.component | ||
def ui_batch_example(): | ||
render_queue = ui.use_render_queue() | ||
a, set_a = ui.use_state(0) | ||
b, set_b = ui.use_state(0) | ||
|
||
ui.toast( | ||
f"Values are {a} and {b}", | ||
variant="negative" if a != b else "neutral", | ||
timeout=5000, | ||
) | ||
|
||
def do_work(): | ||
def update_state(): | ||
set_a(lambda new_a: new_a + 1) | ||
# Introduce a bit of delay between updates | ||
time.sleep(0.1) | ||
set_b(lambda new_b: new_b + 1) | ||
|
||
render_queue(update_state) | ||
|
||
def start_background_thread(): | ||
threading.Thread(target=do_work).start() | ||
|
||
return ui.button("Update values", on_press=start_background_thread) | ||
|
||
|
||
batch_example = ui_batch_example() | ||
``` | ||
|
||
Now when we run this example and press the button, we'll see only one toast with the updated values of `a` and `b`, and they will always be the same value when the component re-renders (since the updates are batched together on the render thread). | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## API Reference | ||
|
||
```{eval-rst} | ||
.. dhautofunction:: deephaven.ui.use_render_queue | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# use_table_listener | ||
|
||
`use_table_listener` lets you listen to a table for updates. This is useful when you want to listen to a table and perform an action when the table updates. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Example | ||
|
||
```python | ||
from deephaven import time_table, ui | ||
from deephaven.table import Table | ||
|
||
_source = time_table("PT1s").update("X = i") | ||
|
||
|
||
@ui.component | ||
def ui_table_monitor(t: Table): | ||
def listener_function(update, is_replay): | ||
print(f"Table updated: {update}, is_replay: {is_replay}") | ||
|
||
ui.use_table_listener(t, listener_function, []) | ||
return t | ||
|
||
|
||
table_monitor = ui_table_monitor(_source) | ||
``` | ||
|
||
## UI recommendations | ||
|
||
1. **Use table data hooks instead when possible**: `use_table_listener` is an advanced feature, requiring understanding of how the [table listeners](https://deephaven.io/core/docs/how-to-guides/table-listeners-python/) work, and limitations of running code while the Update Graph is running. Most usages of this are more appropriate to implement with [the table data hooks](./overview.md#data-hooks). | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Display the last updated row | ||
|
||
Here's an example that listens to table updates and will display the last update as a header above the table. This is a simple example to demonstrate how to use `use_table_listener` to listen to table updates and update state in your component. | ||
|
||
```python | ||
from deephaven import time_table, ui | ||
from deephaven.table import Table | ||
|
||
|
||
@ui.component | ||
def ui_show_last_changed(t: Table): | ||
last_change, set_last_change = ui.use_state("No changes yet.") | ||
|
||
def listener_function(update, is_replay): | ||
set_last_change(f"{update.added()['X'][0]} was added") | ||
|
||
ui.use_table_listener(t, listener_function, []) | ||
return [ui.heading(f"Last change: {last_change}"), t] | ||
|
||
|
||
_source = time_table("PT5s").update("X = i") | ||
show_last_changed = ui_show_last_changed(_source) | ||
``` | ||
|
||
## Display a toast | ||
|
||
Here's an example that listens to table updates and will display a toast message when the table updates. This is a simple example to demonstrate how to use `use_table_listener` to listen to table updates and display a toast message. Note you must use a [render queue](./use_render_queue.md) to trigger the toast, as the listener is not fired on the render thread. | ||
mofojed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```python | ||
from deephaven import time_table | ||
from deephaven import ui | ||
|
||
_source = time_table("PT5S").update("X = i").tail(5) | ||
|
||
|
||
@ui.component | ||
def toast_table(t): | ||
render_queue = ui.use_render_queue() | ||
|
||
def listener_function(update, is_replay): | ||
data_added = update.added()["X"][0] | ||
render_queue(lambda: ui.toast(f"added {data_added}", timeout=5000)) | ||
|
||
ui.use_table_listener(t, listener_function, [t]) | ||
return t | ||
|
||
|
||
my_toast_table = toast_table(_source) | ||
``` | ||
|
||
## API Reference | ||
|
||
```{eval-rst} | ||
.. dhautofunction:: deephaven.ui.use_table_listener | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question for @dsmmcken: When Salmon goes live, will these headings have to change the docs syntax:
And if so, is there a transition tool that will make changing them quick?