@perspective-dev/viewer 4.5.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +20 -1
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/esm/extensions.d.ts +22 -3
- package/dist/esm/perspective-viewer.d.ts +9 -0
- package/dist/esm/perspective-viewer.inline.js +20 -1
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +20 -1
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +71 -5
- package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
- package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
- package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
- package/dist/esm/ts-rs/Layout.d.ts +11 -0
- package/dist/esm/ts-rs/Orientation.d.ts +4 -0
- package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +41 -0
- package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
- package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +8 -2
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +9 -0
- package/dist/esm/ts-rs/WindowAggregate.d.ts +1 -0
- package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
- package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +46 -0
- package/dist/esm/ts-rs/Windows.d.ts +10 -0
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
- package/dist/wasm/perspective-viewer.d.ts +299 -78
- package/dist/wasm/perspective-viewer.js +544 -186
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
- package/dist/wasm/snippets/perspective-viewer-8a8c7d5d696b43ae/inline0.js +6 -0
- package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-8a8c7d5d696b43ae/inline4.js} +5 -0
- package/package.json +3 -2
- package/src/css/_context_menu.css +14 -0
- package/src/css/column-selector.css +1 -0
- package/src/css/column-settings-panel.css +68 -3
- package/src/css/column-style.css +4 -3
- package/src/css/config-selector.css +22 -1
- package/src/css/containers/context-menu.css +107 -0
- package/src/css/containers/tabs.css +2 -2
- package/src/css/panel-tab.css +193 -0
- package/src/css/plugin-selector.css +7 -10
- package/src/css/status-bar.css +75 -81
- package/src/css/viewer.css +188 -70
- package/src/rust/components/column_dropdown.rs +40 -30
- package/src/rust/components/column_selector/active_column.rs +28 -45
- package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
- package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
- package/src/rust/components/column_selector/config_selector.rs +40 -82
- package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
- package/src/rust/components/column_selector/filter_column.rs +5 -15
- package/src/rust/components/column_selector/inactive_column.rs +11 -9
- package/src/rust/components/column_selector/sort_column.rs +3 -6
- package/src/rust/components/column_selector.rs +25 -14
- package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
- package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
- package/src/rust/components/column_settings_sidebar.rs +163 -16
- package/src/rust/components/containers/dragdrop_list.rs +73 -3
- package/src/rust/components/containers/sidebar.rs +99 -22
- package/src/rust/components/containers/split_panel.rs +181 -62
- package/src/rust/components/context_menu.rs +130 -0
- package/src/rust/components/editable_header.rs +2 -0
- package/src/rust/components/form/debug.rs +3 -0
- package/src/rust/components/global_filter_bar.rs +76 -0
- package/src/rust/components/main_panel/frame_theme.rs +186 -0
- package/src/rust/components/main_panel/msg.rs +62 -0
- package/src/rust/components/main_panel/presize.rs +220 -0
- package/src/rust/components/main_panel/reconcile.rs +224 -0
- package/src/rust/components/main_panel/render.rs +385 -0
- package/src/rust/components/main_panel/update.rs +184 -0
- package/src/rust/components/main_panel.rs +316 -71
- package/src/rust/components/mod.rs +5 -0
- package/src/rust/components/panel_menu.rs +493 -0
- package/src/rust/components/panel_tab.rs +510 -0
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/portal.rs +7 -0
- package/src/rust/components/settings_panel.rs +13 -5
- package/src/rust/components/status_bar.rs +110 -178
- package/src/rust/components/status_indicator.rs +6 -9
- package/src/rust/components/style/mod.rs +5 -0
- package/src/rust/components/style/style_provider.rs +2 -0
- package/src/rust/components/viewer/filters.rs +133 -0
- package/src/rust/components/viewer/msg.rs +182 -0
- package/src/rust/components/viewer/panels.rs +326 -0
- package/src/rust/components/viewer/render.rs +255 -0
- package/src/rust/components/viewer/settings.rs +434 -0
- package/src/rust/components/viewer/snapshots.rs +134 -0
- package/src/rust/components/viewer/wiring.rs +505 -0
- package/src/rust/components/viewer.rs +245 -756
- package/src/rust/components/window_editor.rs +1013 -0
- package/src/rust/config/export_method.rs +1 -1
- package/src/rust/config/mod.rs +4 -0
- package/src/rust/config/options.rs +71 -0
- package/src/rust/config/viewer_config.rs +24 -2
- package/src/rust/config/workspace_config.rs +92 -0
- package/src/rust/custom_elements/copy_dropdown.rs +3 -1
- package/src/rust/custom_elements/debug_plugin.rs +20 -1
- package/src/rust/custom_elements/export_dropdown.rs +3 -1
- package/src/rust/custom_elements/viewer.rs +1015 -286
- package/src/rust/custom_events.rs +212 -61
- package/src/rust/js/mod.rs +2 -0
- package/src/rust/js/plugin.rs +297 -10
- package/src/rust/js/regular_layout.rs +258 -0
- package/src/rust/lib.rs +24 -2
- package/src/rust/presentation/column_locator.rs +16 -1
- package/src/rust/presentation.rs +66 -33
- package/src/rust/queries/columns_iter_set.rs +7 -1
- package/src/rust/queries/get_viewer_config.rs +29 -11
- package/src/rust/queries/plugin_column_styles.rs +2 -2
- package/src/rust/renderer/activate.rs +41 -14
- package/src/rust/renderer/dispatch.rs +463 -0
- package/src/rust/renderer/plugin_config.rs +495 -0
- package/src/rust/renderer/registry.rs +18 -6
- package/src/rust/renderer/render_timer.rs +29 -10
- package/src/rust/renderer.rs +384 -838
- package/src/rust/session/column_defaults_update.rs +26 -8
- package/src/rust/session/drag_drop_update.rs +18 -0
- package/src/rust/session/metadata.rs +66 -9
- package/src/rust/session/replace_expression_update.rs +1 -0
- package/src/rust/session/view_subscription.rs +30 -15
- package/src/rust/session.rs +755 -264
- package/src/rust/tasks/apply_global_filters.rs +89 -0
- package/src/rust/tasks/auto_pause.rs +171 -0
- package/src/rust/tasks/copy_export.rs +29 -19
- package/src/rust/tasks/create_panel.rs +243 -0
- package/src/rust/tasks/dismiss_render_warning.rs +3 -1
- package/src/rust/tasks/edit_expression.rs +4 -4
- package/src/rust/tasks/edit_window.rs +155 -0
- package/src/rust/tasks/eject.rs +64 -23
- package/src/rust/tasks/mod.rs +30 -4
- package/src/rust/tasks/pipeline.rs +411 -0
- package/src/rust/tasks/presize_panels.rs +324 -0
- package/src/rust/tasks/reset_all.rs +22 -12
- package/src/rust/tasks/resize_observer.rs +124 -87
- package/src/rust/tasks/restore_and_render.rs +80 -86
- package/src/rust/tasks/restore_panel.rs +145 -0
- package/src/rust/tasks/send_column_config.rs +5 -2
- package/src/rust/tasks/send_plugin_config.rs +5 -2
- package/src/rust/tasks/set_edit_mode.rs +59 -0
- package/src/rust/tasks/sync_update_panels.rs +152 -0
- package/src/rust/tasks/table_lifecycle.rs +158 -0
- package/src/rust/tasks/update_theme.rs +56 -12
- package/src/rust/utils/browser/dragdrop.rs +12 -0
- package/src/rust/utils/browser/tests/debounce.rs +49 -0
- package/src/rust/utils/completion.rs +56 -0
- package/src/rust/utils/custom_element.rs +5 -0
- package/src/rust/utils/debounce.rs +189 -17
- package/src/rust/utils/mod.rs +6 -0
- package/src/rust/utils/pending_effects.rs +62 -0
- package/src/rust/utils/spawn.rs +33 -0
- package/src/rust/utils/weak_scope.rs +0 -10
- package/src/rust/workspace.rs +846 -0
- package/src/svg/active-indicator.svg +3 -0
- package/src/svg/pin-icon.svg +5 -0
- package/src/themes/botanical.css +5 -2
- package/src/themes/defaults.css +54 -2
- package/src/themes/dracula.css +6 -3
- package/src/themes/gruvbox-dark.css +5 -2
- package/src/themes/gruvbox.css +5 -2
- package/src/themes/icons.css +5 -2
- package/src/themes/intl/de.css +7 -0
- package/src/themes/intl/es.css +7 -0
- package/src/themes/intl/fr.css +7 -0
- package/src/themes/intl/ja.css +7 -0
- package/src/themes/intl/pt.css +7 -0
- package/src/themes/intl/zh.css +7 -0
- package/src/themes/intl.css +10 -1
- package/src/themes/monokai.css +5 -2
- package/src/themes/phosphor.css +5 -2
- package/src/themes/pro-dark.css +5 -2
- package/src/themes/pro.css +2 -1
- package/src/themes/solarized-dark.css +5 -2
- package/src/themes/solarized.css +5 -2
- package/src/themes/vaporwave.css +5 -2
- package/src/ts/bootstrap.ts +8 -7
- package/src/ts/extensions.ts +30 -2
- package/src/ts/perspective-viewer.ts +10 -0
- package/src/ts/plugin.ts +72 -5
- package/src/ts/ts-rs/ClientOptions.ts +7 -0
- package/src/ts/ts-rs/ExportMethod.ts +3 -0
- package/src/ts/ts-rs/ExportOptions.ts +8 -0
- package/src/ts/ts-rs/GetClientOptions.ts +7 -0
- package/src/ts/ts-rs/GetTableOptions.ts +7 -0
- package/src/ts/ts-rs/Layout.ts +9 -0
- package/src/ts/ts-rs/Orientation.ts +6 -0
- package/src/ts/ts-rs/PanelOptions.ts +7 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +16 -0
- package/src/ts/ts-rs/SplitLayout.ts +5 -0
- package/src/ts/ts-rs/TabLayout.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +7 -2
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +9 -0
- package/src/ts/ts-rs/WindowAggregate.ts +3 -0
- package/src/ts/ts-rs/WindowSort.ts +9 -0
- package/src/ts/ts-rs/WindowSortDir.ts +8 -0
- package/src/ts/ts-rs/WindowSpec.ts +46 -0
- package/src/ts/ts-rs/Windows.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
- package/src/rust/tasks/intersection_observer.rs +0 -98
- package/src/rust/tasks/update_and_render.rs +0 -110
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline0.js → perspective-viewer-8a8c7d5d696b43ae/inline1.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-8a8c7d5d696b43ae/inline2.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-8a8c7d5d696b43ae/inline3.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-8a8c7d5d696b43ae/inline5.js} +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use std::cell::RefCell;
|
|
14
|
+
use std::rc::Rc;
|
|
15
|
+
|
|
16
|
+
use perspective_client::utils::PerspectiveResultExt;
|
|
17
|
+
use perspective_client::{Client, clone};
|
|
18
|
+
use perspective_js::utils::{ApiFuture, ApiResult, LocalPollLoop};
|
|
19
|
+
use wasm_bindgen::JsValue;
|
|
20
|
+
|
|
21
|
+
use super::pipeline::RunOrigin;
|
|
22
|
+
use super::restore_and_render::restore_and_render;
|
|
23
|
+
use crate::config::ViewerConfigUpdate;
|
|
24
|
+
use crate::presentation::Presentation;
|
|
25
|
+
use crate::utils::{AddListener, Subscription};
|
|
26
|
+
use crate::workspace::{Panel, Workspace};
|
|
27
|
+
|
|
28
|
+
/// The per-client hosted-tables-update subscriptions this element holds, for
|
|
29
|
+
/// removal at `delete()`.
|
|
30
|
+
pub(crate) type HostedTableSubs = Rc<RefCell<Vec<(Client, u32)>>>;
|
|
31
|
+
|
|
32
|
+
/// Wire reactive table binding for an element: subscribe every registered
|
|
33
|
+
/// [`Client`]'s hosted-tables updates (current and future registrations) to
|
|
34
|
+
/// [`sweep_table_bindings`]. Returns the registration listener (owned for the
|
|
35
|
+
/// element's lifetime) and the accumulated client subscriptions (removed at
|
|
36
|
+
/// element `delete()`).
|
|
37
|
+
pub(crate) fn wire_table_lifecycle(
|
|
38
|
+
workspace: &Workspace,
|
|
39
|
+
presentation: &Presentation,
|
|
40
|
+
) -> (Subscription, HostedTableSubs) {
|
|
41
|
+
let subs: HostedTableSubs = Rc::default();
|
|
42
|
+
let sub = workspace.client_registered().add_listener({
|
|
43
|
+
clone!(workspace, presentation, subs);
|
|
44
|
+
move |client: Client| {
|
|
45
|
+
clone!(workspace, presentation, subs);
|
|
46
|
+
ApiFuture::spawn(async move {
|
|
47
|
+
let poll_loop = LocalPollLoop::new({
|
|
48
|
+
clone!(workspace, presentation);
|
|
49
|
+
move |()| {
|
|
50
|
+
clone!(workspace, presentation);
|
|
51
|
+
ApiFuture::spawn(async move {
|
|
52
|
+
sweep_table_bindings(&workspace, &presentation).await
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
Ok(JsValue::UNDEFINED)
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
let id = client
|
|
60
|
+
.on_hosted_tables_update(move || {
|
|
61
|
+
let poll_loop = poll_loop.clone();
|
|
62
|
+
async move {
|
|
63
|
+
poll_loop.poll(()).await;
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
.await?;
|
|
67
|
+
|
|
68
|
+
subs.borrow_mut().push((client, id));
|
|
69
|
+
|
|
70
|
+
// A table created between this client's registration and the
|
|
71
|
+
// subscription above fired no callback — sweep NOW, after
|
|
72
|
+
// subscribing, so no creation can be missed.
|
|
73
|
+
sweep_table_bindings(&workspace, &presentation).await
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
(sub, subs)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// One reconciliation pass over every panel's table binding vs. the loaded
|
|
82
|
+
/// clients' hosted sets: releases first (a delete and a re-create in the same
|
|
83
|
+
/// update settle as suspend-then-rebind), then completes pending binds.
|
|
84
|
+
/// Per-panel failures are logged, never aborting the sweep.
|
|
85
|
+
pub(crate) async fn sweep_table_bindings(
|
|
86
|
+
workspace: &Workspace,
|
|
87
|
+
presentation: &Presentation,
|
|
88
|
+
) -> ApiResult<()> {
|
|
89
|
+
for panel in workspace.panels() {
|
|
90
|
+
if let Some(name) = panel.session.get_table().map(|t| t.get_name().to_owned())
|
|
91
|
+
&& let Some(client) = panel.session.get_client()
|
|
92
|
+
&& let Ok(hosted) = client.get_hosted_table_names().await
|
|
93
|
+
&& !hosted.iter().any(|n| n == &name)
|
|
94
|
+
{
|
|
95
|
+
suspend_panel(&panel).await.unwrap_or_log();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for panel in workspace.panels() {
|
|
100
|
+
if panel.session.pending_table().is_some() {
|
|
101
|
+
bind_pending(&panel, workspace, presentation)
|
|
102
|
+
.await
|
|
103
|
+
.unwrap_or_log();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Ok(())
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Suspend one panel's binding under its draw lock (see
|
|
111
|
+
/// [`Session::suspend_table`]) and blank its display. The suspend re-derives
|
|
112
|
+
/// the bound name inside the lock — a panel that rebound or ejected since the
|
|
113
|
+
/// sweep's observation suspends its CURRENT state or no-ops.
|
|
114
|
+
async fn suspend_panel(panel: &Panel) -> ApiResult<()> {
|
|
115
|
+
clone!(panel.session, panel.renderer);
|
|
116
|
+
renderer
|
|
117
|
+
.clone()
|
|
118
|
+
.render_task(|_guard| async move {
|
|
119
|
+
if let Some(reset) = session.suspend_table() {
|
|
120
|
+
reset.await?;
|
|
121
|
+
if let Some(plugin) = renderer.active_plugin() {
|
|
122
|
+
plugin.clear().await?;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Ok(())
|
|
127
|
+
})
|
|
128
|
+
.await
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Complete a PENDING panel's bind through the shared restore pipeline: the
|
|
132
|
+
/// committed view config is untouched, the pending name re-derived inside the
|
|
133
|
+
/// locked run (capture-free), the client federated exactly as a `restore`
|
|
134
|
+
/// would. A name still un-hosted (the sweep raced a delete) simply re-pends.
|
|
135
|
+
async fn bind_pending(
|
|
136
|
+
panel: &Panel,
|
|
137
|
+
workspace: &Workspace,
|
|
138
|
+
presentation: &Presentation,
|
|
139
|
+
) -> ApiResult<()> {
|
|
140
|
+
restore_and_render(
|
|
141
|
+
&panel.session,
|
|
142
|
+
&panel.renderer,
|
|
143
|
+
presentation,
|
|
144
|
+
RunOrigin::Internal,
|
|
145
|
+
ViewerConfigUpdate::default(),
|
|
146
|
+
{
|
|
147
|
+
clone!(panel.session, workspace);
|
|
148
|
+
async move {
|
|
149
|
+
let Some(name) = session.pending_table() else {
|
|
150
|
+
return Ok(());
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
super::restore_panel::bind_table_task(&session, &workspace, name).await
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
.await
|
|
158
|
+
}
|
|
@@ -10,31 +10,63 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
//! Theme reset / set task.
|
|
14
|
-
//! reached into `Session::get_view()` for the post-update restyle.
|
|
13
|
+
//! Theme reset / set task.
|
|
15
14
|
|
|
15
|
+
use futures::future::join_all;
|
|
16
16
|
use perspective_js::utils::*;
|
|
17
17
|
|
|
18
18
|
use crate::presentation::Presentation;
|
|
19
19
|
use crate::renderer::Renderer;
|
|
20
|
-
use crate::
|
|
20
|
+
use crate::workspace::Workspace;
|
|
21
21
|
|
|
22
|
-
///
|
|
22
|
+
/// Re-seed every panel renderer's cached registry default theme from the
|
|
23
|
+
/// (awaited, so initialized) theme registry, returning the default name.
|
|
24
|
+
/// Every consumer of `Renderer::needs_restyle` after a registry-affecting
|
|
25
|
+
/// change must run this first — a cold cache compares against `None`.
|
|
26
|
+
pub(crate) async fn seed_default_themes(
|
|
27
|
+
presentation: &Presentation,
|
|
28
|
+
workspace: &Workspace,
|
|
29
|
+
) -> Option<String> {
|
|
30
|
+
let default = presentation.get_default_theme_name().await;
|
|
31
|
+
for panel in workspace.panels() {
|
|
32
|
+
panel.renderer.set_default_theme(default.clone());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
default
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Apply a theme change and restyle the affected panel's view.
|
|
23
39
|
///
|
|
24
40
|
/// `theme = None` resets to the first available theme; `theme = Some(name)`
|
|
25
|
-
/// sets the named theme.
|
|
26
|
-
///
|
|
27
|
-
///
|
|
41
|
+
/// sets the named theme. The `theme` is recorded on the active `renderer`
|
|
42
|
+
/// (per-panel state) and mirrored onto the host `theme` attribute (driving
|
|
43
|
+
/// the shared chrome). Components dispatch this task instead of reading
|
|
28
44
|
/// `Session::get_view()` themselves.
|
|
45
|
+
///
|
|
46
|
+
/// Every panel's plugin carries its own stamped `theme` attribute (effective
|
|
47
|
+
/// theme = its own, else the registry default — see `Renderer::stamp_theme`),
|
|
48
|
+
/// and the theme rules provide a COMPLETE var set for `perspective-viewer
|
|
49
|
+
/// [theme="X"]` descendants (no inheritance from the host attribute), so a
|
|
50
|
+
/// pick changes ONLY the picked panel's effective theme — the host flip
|
|
51
|
+
/// restyles nothing else. Restyle (an expensive full `restyle_all`, which
|
|
52
|
+
/// also redraws) is therefore scoped to panels whose captured CSS is
|
|
53
|
+
/// actually STALE (`Renderer::needs_restyle` — effective theme vs. the one
|
|
54
|
+
/// stamped at the plugin's last capture): the picked panel when the value
|
|
55
|
+
/// is genuinely new, and never a panel that has yet to first-paint.
|
|
29
56
|
pub fn update_theme(
|
|
30
|
-
session: &Session,
|
|
31
57
|
renderer: &Renderer,
|
|
32
58
|
presentation: &Presentation,
|
|
59
|
+
workspace: &Workspace,
|
|
33
60
|
theme: Option<String>,
|
|
34
61
|
) {
|
|
35
|
-
|
|
36
|
-
|
|
62
|
+
// Per-panel: record the theme on the (active) renderer so this panel keeps
|
|
63
|
+
// it independent of which panel is active. `set_theme_name` below mirrors
|
|
64
|
+
// the same value onto the host `theme` attribute (driving the chrome), and
|
|
65
|
+
// MainPanel inlines this renderer's theme on its frame only when it
|
|
66
|
+
renderer.set_theme_stamped(theme.clone());
|
|
67
|
+
|
|
37
68
|
let presentation = presentation.clone();
|
|
69
|
+
let workspace = workspace.clone();
|
|
38
70
|
ApiFuture::spawn(async move {
|
|
39
71
|
match theme {
|
|
40
72
|
Some(name) => {
|
|
@@ -45,7 +77,19 @@ pub fn update_theme(
|
|
|
45
77
|
},
|
|
46
78
|
}
|
|
47
79
|
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
seed_default_themes(&presentation, &workspace).await;
|
|
81
|
+
let panels = workspace.panels();
|
|
82
|
+
join_all(panels.iter().map(|panel| async move {
|
|
83
|
+
if panel.renderer.needs_restyle() {
|
|
84
|
+
panel.renderer.restyle_all().await?;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ApiResult::<()>::Ok(())
|
|
88
|
+
}))
|
|
89
|
+
.await
|
|
90
|
+
.into_iter()
|
|
91
|
+
.collect::<ApiResult<Vec<_>>>()?;
|
|
92
|
+
|
|
93
|
+
Ok(())
|
|
50
94
|
});
|
|
51
95
|
}
|
|
@@ -17,6 +17,18 @@ pub enum DragTarget {
|
|
|
17
17
|
SplitBy,
|
|
18
18
|
Sort,
|
|
19
19
|
Filter,
|
|
20
|
+
WindowSource,
|
|
21
|
+
WindowOrderBy,
|
|
22
|
+
WindowPartitionBy,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl DragTarget {
|
|
26
|
+
pub fn is_staged(&self) -> bool {
|
|
27
|
+
matches!(
|
|
28
|
+
self,
|
|
29
|
+
Self::WindowSource | Self::WindowOrderBy | Self::WindowPartitionBy
|
|
30
|
+
)
|
|
31
|
+
}
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
@@ -86,6 +86,55 @@ pub async fn test_lock_seq() {
|
|
|
86
86
|
assert_eq!(cell.get(), 10);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/// A dedicated [`DebounceSlot`]'s task must RUN even when the default slot
|
|
90
|
+
/// has a parked runner — coalescing is scoped per slot. Pre-slot (one
|
|
91
|
+
/// shared parked flag), `t4` would resolve via `t2`'s settle without its
|
|
92
|
+
/// closure ever running: the resize-eaten-by-parked-update bug.
|
|
93
|
+
#[wasm_bindgen_test]
|
|
94
|
+
pub async fn test_slot_isolated_from_default_coalescing() {
|
|
95
|
+
let debounce_mutex = DebounceMutex::default();
|
|
96
|
+
let slot = debounce_mutex.slot();
|
|
97
|
+
let defaults: Rc<Cell<u32>> = Rc::new(Cell::new(0));
|
|
98
|
+
let slots: Rc<Cell<u32>> = Rc::new(Cell::new(0));
|
|
99
|
+
|
|
100
|
+
let t1 = debounce_mutex.lock(async {
|
|
101
|
+
set_timeout(10).await.unwrap();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
let t2 = {
|
|
105
|
+
let defaults = defaults.clone();
|
|
106
|
+
debounce_mutex.debounce(async move {
|
|
107
|
+
defaults.set(defaults.get() + 1);
|
|
108
|
+
Ok(())
|
|
109
|
+
})
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
let t3 = {
|
|
113
|
+
let defaults = defaults.clone();
|
|
114
|
+
debounce_mutex.debounce(async move {
|
|
115
|
+
defaults.set(defaults.get() + 100);
|
|
116
|
+
Ok(())
|
|
117
|
+
})
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
let t4 = {
|
|
121
|
+
let slots = slots.clone();
|
|
122
|
+
slot.debounce_with(|_| async move {
|
|
123
|
+
slots.set(slots.get() + 1);
|
|
124
|
+
Ok(())
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
let (_, r2, r3, r4) = futures::join!(t1, t2, t3, t4);
|
|
129
|
+
r2.unwrap();
|
|
130
|
+
r3.unwrap();
|
|
131
|
+
r4.unwrap();
|
|
132
|
+
|
|
133
|
+
// `t3` coalesced onto the parked `t2`; `t4` ran its own closure.
|
|
134
|
+
assert_eq!(defaults.get(), 1);
|
|
135
|
+
assert_eq!(slots.get(), 1);
|
|
136
|
+
}
|
|
137
|
+
|
|
89
138
|
#[wasm_bindgen_test]
|
|
90
139
|
pub async fn test_debounce_seq() {
|
|
91
140
|
let debounce_mutex = DebounceMutex::default();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use std::future::Future;
|
|
14
|
+
|
|
15
|
+
use futures::channel::oneshot;
|
|
16
|
+
use perspective_js::utils::*;
|
|
17
|
+
|
|
18
|
+
/// A completion handle for message-based public API methods (invariant I6 —
|
|
19
|
+
/// see `SESSION_CONFIG_COHERENCE_PLAN.md`): its ONLY resolution API is
|
|
20
|
+
/// [`Completion::resolve_after`], which takes a run future — so resolving a
|
|
21
|
+
/// public method's promise at message-handling time (before the renders it
|
|
22
|
+
/// caused have drawn) is unwritable, not merely discouraged. Dropping an
|
|
23
|
+
/// unresolved `Completion` rejects the caller as cancelled.
|
|
24
|
+
pub struct Completion(Option<oneshot::Sender<ApiResult<()>>>);
|
|
25
|
+
|
|
26
|
+
impl Completion {
|
|
27
|
+
#[allow(clippy::new_ret_no_self)]
|
|
28
|
+
pub fn new() -> (Self, oneshot::Receiver<ApiResult<()>>) {
|
|
29
|
+
let (sender, receiver) = oneshot::channel();
|
|
30
|
+
(Self(Some(sender)), receiver)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Resolve this completion with `run`'s result when it settles. The
|
|
34
|
+
/// caller relinquishes the handle — one run, one resolution.
|
|
35
|
+
pub fn resolve_after(mut self, run: impl Future<Output = ApiResult<()>> + 'static) {
|
|
36
|
+
let sender = self.0.take().unwrap();
|
|
37
|
+
ApiFuture::spawn(async move {
|
|
38
|
+
let _ = sender.send(run.await);
|
|
39
|
+
Ok(())
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl std::fmt::Debug for Completion {
|
|
45
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
46
|
+
f.debug_struct("Completion").finish()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl Drop for Completion {
|
|
51
|
+
fn drop(&mut self) {
|
|
52
|
+
if let Some(sender) = self.0.take() {
|
|
53
|
+
let _ = sender.send(Err(ApiError::new("Cancelled")));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -16,6 +16,11 @@ use wasm_bindgen::prelude::*;
|
|
|
16
16
|
|
|
17
17
|
#[wasm_bindgen(inline_js = r#"
|
|
18
18
|
export function bootstrap(psp, name, clsname, statics) {
|
|
19
|
+
if (customElements.get(name)) {
|
|
20
|
+
console.warn(`Custom element "${name}" is already registered; skipping duplicate registration (Perspective was loaded more than once on this page).`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
const cls = psp[clsname];
|
|
20
25
|
const proto = cls.prototype;
|
|
21
26
|
class x extends HTMLElement {
|
|
@@ -17,44 +17,216 @@ use std::rc::Rc;
|
|
|
17
17
|
use async_lock::Mutex;
|
|
18
18
|
use perspective_js::utils::ApiResult;
|
|
19
19
|
|
|
20
|
+
use super::pubsub::PubSub;
|
|
21
|
+
|
|
22
|
+
/// Proof that the bearer is executing inside a [`DebounceMutex`]-locked task.
|
|
23
|
+
///
|
|
24
|
+
/// Every plugin-dispatching function (`draw_view`, `activate_plugin`, the
|
|
25
|
+
/// restyle/resize/export wrappers) and the snapshot pipeline
|
|
26
|
+
/// (`bind_snapshot`) require `&RenderGuard`, so an unlocked plugin call or
|
|
27
|
+
/// out-of-pipeline render is a missing witness — a compile error, not a
|
|
28
|
+
/// review rule. Constructed ONLY by [`DebounceMutex::lock_with`] /
|
|
29
|
+
/// [`DebounceMutex::debounce_with`], after the lock is acquired; it is not
|
|
30
|
+
/// `Clone`, so it cannot be stashed for use outside the task that received
|
|
31
|
+
/// it.
|
|
32
|
+
pub struct RenderGuard {
|
|
33
|
+
_private: (),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// The per-slot debounce bookkeeping: one running + one parked evaluation,
|
|
37
|
+
/// with coalesced callers awaiting the parked runner's settle. Kept
|
|
38
|
+
/// per-[`DebounceSlot`] so coalescing is only expressible WITHIN a kind —
|
|
39
|
+
/// a caller can never be absorbed by a parked runner of a different kind
|
|
40
|
+
/// whose work does not subsume it (the resize-eaten-by-parked-update bug).
|
|
20
41
|
#[derive(Default)]
|
|
21
|
-
struct
|
|
42
|
+
struct SlotState {
|
|
22
43
|
id: Cell<u64>,
|
|
23
|
-
|
|
44
|
+
last: Cell<u64>,
|
|
45
|
+
parked: Cell<bool>,
|
|
46
|
+
settled_id: Cell<u64>,
|
|
47
|
+
on_settle: PubSub<()>,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[derive(Default)]
|
|
51
|
+
struct DebounceMutexData {
|
|
52
|
+
held: Cell<bool>,
|
|
53
|
+
mutex: Mutex<()>,
|
|
54
|
+
default_slot: Rc<SlotState>,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Clears the `held` flag on drop, so cancellation of a locked task (its
|
|
58
|
+
/// future dropped mid-await) can't leave [`DebounceMutex::is_held`] stuck
|
|
59
|
+
/// `true`.
|
|
60
|
+
struct HeldFlag<'a>(&'a Cell<bool>);
|
|
61
|
+
|
|
62
|
+
impl<'a> HeldFlag<'a> {
|
|
63
|
+
fn set(cell: &'a Cell<bool>) -> Self {
|
|
64
|
+
cell.set(true);
|
|
65
|
+
Self(cell)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl Drop for HeldFlag<'_> {
|
|
70
|
+
fn drop(&mut self) {
|
|
71
|
+
self.0.set(false);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// RAII for the parked debounce runner: clears `parked` and broadcasts the
|
|
76
|
+
/// runner's settle on drop. Running through `Drop` (not a happy-path call)
|
|
77
|
+
/// makes the release unconditional — a runner cancelled mid-park or
|
|
78
|
+
/// mid-run still frees the parked slot and releases its coalesced waiters,
|
|
79
|
+
/// so they can never be stranded awaiting a settle that no task will emit.
|
|
80
|
+
struct SettleGuard<'a> {
|
|
81
|
+
state: &'a SlotState,
|
|
82
|
+
next: u64,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl<'a> SettleGuard<'a> {
|
|
86
|
+
fn park(state: &'a SlotState, next: u64) -> Self {
|
|
87
|
+
state.parked.set(true);
|
|
88
|
+
Self { state, next }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl Drop for SettleGuard<'_> {
|
|
93
|
+
fn drop(&mut self) {
|
|
94
|
+
self.state.parked.set(false);
|
|
95
|
+
if self.state.settled_id.get() < self.next {
|
|
96
|
+
self.state.settled_id.set(self.next);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
self.state.on_settle.emit(());
|
|
100
|
+
}
|
|
24
101
|
}
|
|
25
102
|
|
|
26
103
|
/// An async `Mutex` type specialized for Perspective's rendering, which
|
|
27
|
-
/// debounces calls in addition to providing exclusivity. Calling
|
|
28
|
-
///
|
|
29
|
-
///
|
|
104
|
+
/// debounces calls in addition to providing exclusivity. Calling
|
|
105
|
+
/// [`Self::debounce`] resolves only after at least one complete evaluation
|
|
106
|
+
/// of a call that began no earlier than this one — either by running the
|
|
107
|
+
/// caller's own future, or by coalescing onto the single parked trailing
|
|
108
|
+
/// runner. At most TWO debounce evaluations are ever outstanding (one
|
|
109
|
+
/// running, one parked); every additional concurrent caller resolves
|
|
110
|
+
/// without queueing on the lock.
|
|
30
111
|
#[derive(Clone, Default)]
|
|
31
112
|
pub struct DebounceMutex(Rc<DebounceMutexData>);
|
|
32
113
|
|
|
33
114
|
impl DebounceMutex {
|
|
115
|
+
/// `true` while a locked task is executing. Used by lock-acquiring
|
|
116
|
+
/// public API methods to emit a debug-build warning when they are about
|
|
117
|
+
/// to queue behind an in-flight run — legitimate for app callers, a
|
|
118
|
+
/// guaranteed deadlock when reached synchronously from a plugin's render
|
|
119
|
+
/// (the render-callable contract on `js::plugin` forbids it).
|
|
120
|
+
pub fn is_held(&self) -> bool {
|
|
121
|
+
self.0.held.get()
|
|
122
|
+
}
|
|
123
|
+
|
|
34
124
|
/// Lock like a normal `Mutex`.
|
|
35
125
|
pub async fn lock<T>(&self, f: impl Future<Output = T>) -> T {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
126
|
+
self.lock_with(|_| f).await
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Lock, passing a [`RenderGuard`] witness into the task builder. The
|
|
130
|
+
/// task future is CONSTRUCTED after the lock is acquired — a guard can
|
|
131
|
+
/// never exist outside a locked section.
|
|
132
|
+
pub async fn lock_with<T, F, Fut>(&self, f: F) -> T
|
|
133
|
+
where
|
|
134
|
+
F: FnOnce(RenderGuard) -> Fut,
|
|
135
|
+
Fut: Future<Output = T>,
|
|
136
|
+
{
|
|
137
|
+
let guard = self.0.mutex.lock().await;
|
|
138
|
+
let held = HeldFlag::set(&self.0.held);
|
|
139
|
+
let result = f(RenderGuard { _private: () }).await;
|
|
140
|
+
drop(held);
|
|
141
|
+
drop(guard);
|
|
40
142
|
result
|
|
41
143
|
}
|
|
42
144
|
|
|
43
145
|
/// Lock and also debounce `f`, which should be cancellable.
|
|
44
146
|
pub async fn debounce(&self, f: impl Future<Output = ApiResult<()>>) -> ApiResult<()> {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
147
|
+
self.debounce_with(|_| f).await
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// [`Self::debounce`] with a [`RenderGuard`] witness (see
|
|
151
|
+
pub async fn debounce_with<T, F, Fut>(&self, f: F) -> ApiResult<T>
|
|
152
|
+
where
|
|
153
|
+
T: Default,
|
|
154
|
+
F: FnOnce(RenderGuard) -> Fut,
|
|
155
|
+
Fut: Future<Output = ApiResult<T>>,
|
|
156
|
+
{
|
|
157
|
+
DebounceSlot {
|
|
158
|
+
mutex: self.clone(),
|
|
159
|
+
state: self.0.default_slot.clone(),
|
|
160
|
+
}
|
|
161
|
+
.debounce_with(f)
|
|
162
|
+
.await
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// A NEW debounce slot over this mutex: its tasks serialize against
|
|
166
|
+
/// every other locked task, but coalesce only among themselves.
|
|
167
|
+
pub fn slot(&self) -> DebounceSlot {
|
|
168
|
+
DebounceSlot {
|
|
169
|
+
mutex: self.clone(),
|
|
170
|
+
state: Default::default(),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// One debounce KIND over a shared [`DebounceMutex`]. Execution is
|
|
176
|
+
/// exclusive across the whole mutex; coalescing (a caller resolving via a
|
|
177
|
+
/// parked runner instead of queueing) is scoped to the slot. A coalesced
|
|
178
|
+
/// caller's work must be subsumable by any same-slot runner that runs no
|
|
179
|
+
/// earlier than the call — which is why parameterized tasks sharing a slot
|
|
180
|
+
/// must read their parameters at RUN time (e.g. the [`Renderer`] geometry
|
|
181
|
+
/// command cell), never capture them at call time.
|
|
182
|
+
#[derive(Clone)]
|
|
183
|
+
pub struct DebounceSlot {
|
|
184
|
+
mutex: DebounceMutex,
|
|
185
|
+
state: Rc<SlotState>,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
impl DebounceSlot {
|
|
189
|
+
pub async fn debounce_with<T, F, Fut>(&self, f: F) -> ApiResult<T>
|
|
190
|
+
where
|
|
191
|
+
T: Default,
|
|
192
|
+
F: FnOnce(RenderGuard) -> Fut,
|
|
193
|
+
Fut: Future<Output = ApiResult<T>>,
|
|
194
|
+
{
|
|
195
|
+
let state = &self.state;
|
|
196
|
+
let next = state.id.get() + 1;
|
|
197
|
+
if state.parked.get() {
|
|
198
|
+
self.await_settled(next).await;
|
|
199
|
+
return Ok(T::default());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let settle = SettleGuard::park(state, next);
|
|
203
|
+
let guard = self.mutex.0.mutex.lock().await;
|
|
204
|
+
state.parked.set(false);
|
|
205
|
+
let result = if state.last.get() < next {
|
|
206
|
+
let next = state.id.get() + 1;
|
|
207
|
+
state.id.set(next);
|
|
208
|
+
let held = HeldFlag::set(&self.mutex.0.held);
|
|
209
|
+
let result = f(RenderGuard { _private: () }).await;
|
|
210
|
+
drop(held);
|
|
51
211
|
if result.is_ok() {
|
|
52
|
-
|
|
212
|
+
state.last.set(next);
|
|
53
213
|
}
|
|
54
214
|
|
|
55
215
|
result
|
|
56
216
|
} else {
|
|
57
|
-
Ok(())
|
|
217
|
+
Ok(T::default())
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
drop(guard);
|
|
221
|
+
drop(settle);
|
|
222
|
+
result
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async fn await_settled(&self, next: u64) {
|
|
226
|
+
while self.state.settled_id.get() < next {
|
|
227
|
+
if self.state.on_settle.read_next().await.is_err() {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
58
230
|
}
|
|
59
231
|
}
|
|
60
232
|
}
|
package/src/rust/utils/mod.rs
CHANGED
|
@@ -17,27 +17,33 @@
|
|
|
17
17
|
//! reference other `crate::utils` modules when it helps reduce boiler-plate.
|
|
18
18
|
|
|
19
19
|
mod browser;
|
|
20
|
+
mod completion;
|
|
20
21
|
mod custom_element;
|
|
21
22
|
mod datetime;
|
|
22
23
|
mod debounce;
|
|
23
24
|
mod hooks;
|
|
24
25
|
mod modal_position;
|
|
25
26
|
mod number_format;
|
|
27
|
+
mod pending_effects;
|
|
26
28
|
mod ptr_eq_rc;
|
|
27
29
|
mod pubsub;
|
|
30
|
+
mod spawn;
|
|
28
31
|
mod weak_scope;
|
|
29
32
|
|
|
30
33
|
#[cfg(test)]
|
|
31
34
|
mod tests;
|
|
32
35
|
|
|
33
36
|
pub use browser::*;
|
|
37
|
+
pub use completion::*;
|
|
34
38
|
pub use custom_element::*;
|
|
35
39
|
pub use datetime::*;
|
|
36
40
|
pub use debounce::*;
|
|
37
41
|
pub use hooks::*;
|
|
38
42
|
pub use modal_position::*;
|
|
39
43
|
pub use number_format::*;
|
|
44
|
+
pub use pending_effects::*;
|
|
40
45
|
pub use perspective_client::clone;
|
|
41
46
|
pub use ptr_eq_rc::*;
|
|
42
47
|
pub use pubsub::*;
|
|
48
|
+
pub use spawn::*;
|
|
43
49
|
pub use weak_scope::*;
|