Skip to content

Commit 5546dc6

Browse files
authored
feat(vtklocal): add progress event handling during WASM sync
1 parent 1f0d6b9 commit 5546dc6

5 files changed

Lines changed: 176 additions & 8 deletions

File tree

README.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,49 @@ Running examples
9595
Some example are meant to test and validate WASM rendering.
9696
Some will default for remote rendering but if you want to force them to use WASM just run `export USE_WASM=1` before executing them.
9797

98+
Progress events
99+
----------------------------------------
100+
101+
The client-side VtkLocal component emits a `progress` event while wasm sync is happening.
102+
This can be used to keep a global loader visible until the first sync completes.
103+
104+
.. code-block:: python
105+
106+
from trame.widgets import vtklocal, vuetify
107+
108+
state.app_loading = True
109+
state._vtklocal_seen_active = False
110+
111+
def on_vtklocal_progress(event):
112+
if event.get("active"):
113+
state._vtklocal_seen_active = True
114+
state.app_loading = True
115+
elif state._vtklocal_seen_active:
116+
state.app_loading = False
117+
118+
with vuetify.VOverlay(v_model=("app_loading",), absolute=True):
119+
vuetify.VProgressCircular(indeterminate=True, size=64)
120+
121+
view = vtklocal.LocalView(
122+
render_window,
123+
progress=(on_vtklocal_progress, "[$event]"),
124+
)
125+
126+
If you are using the Vue component directly, you can override the built-in loader
127+
with the `loader` slot.
128+
129+
.. code-block:: html
130+
131+
<vtk-local>
132+
<template #loader="{ progress, wasmLoading, statePercent, hashPercent }">
133+
<div v-if="wasmLoading">Loading wasm...</div>
134+
<div v-else>
135+
States: {{ progress.state.current }}/{{ progress.state.total }}
136+
Blobs: {{ progress.hash.current }}/{{ progress.hash.total }}
137+
</div>
138+
</template>
139+
</vtk-local>
140+
98141
SharedArrayBuffer
99142
----------------------------------------
100143

src/trame_vtklocal/widgets/vtklocal.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ class LocalView(HtmlElement):
9898
camera (event):
9999
Event emitted when any camera is changed. The actual state of
100100
the camera is passed as arg.
101+
progress (event):
102+
Event emitted during wasm sync. Payload includes active flag and
103+
current/total counts for states and blobs.
101104
102105
"""
103106

@@ -142,6 +145,7 @@ def __init__(self, render_window, throttle_rate=10, **kwargs):
142145
("memory_vtk", "memory-vtk"),
143146
("memory_arrays", "memory-arrays"),
144147
("invoke_response", "invoke-response"),
148+
"progress",
145149
]
146150

147151
# Generate throttle update function

vue-components/package-lock.json

Lines changed: 11 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vue-components/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
"vue": "^2.7.0 || >=3.0.0"
1515
},
1616
"dependencies": {
17-
"jszip": "3.10.1",
18-
"@kitware/vtk-wasm": "^1.1.0"
17+
"@kitware/vtk-wasm": "^1.5.0",
18+
"jszip": "3.10.1"
1919
},
2020
"devDependencies": {
2121
"@eslint/js": "^9.22.0",

vue-components/src/components/VtkLocal.js

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
2+
computed,
23
inject,
34
ref,
5+
reactive,
46
unref,
57
onMounted,
68
onBeforeUnmount,
@@ -34,6 +36,7 @@ export default {
3436
"memory-arrays",
3537
"camera",
3638
"invoke-response",
39+
"progress",
3740
],
3841
props: {
3942
useHandler: {
@@ -105,6 +108,62 @@ export default {
105108
? WASM_HANDLERS[props.useHandler]
106109
: new RemoteSession();
107110
let subscription = null;
111+
const progress = reactive({
112+
active: false,
113+
state: {
114+
current: 0,
115+
total: 0,
116+
},
117+
hash: {
118+
current: 0,
119+
total: 0,
120+
},
121+
});
122+
const wasmLoading = ref(!wasmManager.loaded);
123+
const showLoading = computed(() => wasmLoading.value || progress.active);
124+
const statePercent = computed(() => {
125+
if (!progress.state.total) {
126+
return 0;
127+
}
128+
return Math.min(
129+
100,
130+
Math.floor((progress.state.current / progress.state.total) * 100),
131+
);
132+
});
133+
const hashPercent = computed(() => {
134+
if (!progress.hash.total) {
135+
return 0;
136+
}
137+
return Math.min(
138+
100,
139+
Math.floor((progress.hash.current / progress.hash.total) * 100),
140+
);
141+
});
142+
function updateProgress(payload) {
143+
if (!payload) {
144+
return;
145+
}
146+
progress.active = !!payload.active;
147+
progress.state.current = payload.state?.current || 0;
148+
progress.state.total = payload.state?.total || 0;
149+
progress.hash.current = payload.hash?.current || 0;
150+
progress.hash.total = payload.hash?.total || 0;
151+
emit("progress", {
152+
active: progress.active,
153+
state: {
154+
current: progress.state.current,
155+
total: progress.state.total,
156+
},
157+
hash: {
158+
current: progress.hash.current,
159+
total: progress.hash.total,
160+
},
161+
});
162+
}
163+
let removeProgressCallback = null;
164+
if (wasmManager.addProgressCallback) {
165+
removeProgressCallback = wasmManager.addProgressCallback(updateProgress);
166+
}
108167

109168
// network connector ------------------------------------------------------
110169

@@ -227,7 +286,12 @@ export default {
227286
// console.log("vtkLocal::mounted");
228287
wasmManager.bindNetwork(netFetchState, netFetchBlob, netFetchStatus);
229288
if (!wasmManager.loaded) {
230-
await wasmManager.load(wasmURL, props.config, wasmBaseName);
289+
wasmLoading.value = true;
290+
try {
291+
await wasmManager.load(wasmURL, props.config, wasmBaseName);
292+
} finally {
293+
wasmLoading.value = false;
294+
}
231295
}
232296
const selector = wasmManager.bindCanvasToDOM(
233297
props.renderWindow,
@@ -371,6 +435,10 @@ export default {
371435
if (subscription) {
372436
unsubscribe();
373437
}
438+
if (removeProgressCallback) {
439+
removeProgressCallback();
440+
removeProgressCallback = null;
441+
}
374442

375443
// Old/New API - detection
376444
const removeObserverMethodName = wasmManager.sceneManager.removeObserver
@@ -429,7 +497,53 @@ export default {
429497
printSceneManagerInformation,
430498
detachHandler,
431499
getVtkObject,
500+
progress,
501+
showLoading,
502+
statePercent,
503+
hashPercent,
504+
wasmLoading,
432505
};
433506
},
434-
template: `<div ref="container" style="position: relative; width: 100%; height: 100%;"></div>`,
507+
template: `<div ref="container" style="position: relative; width: 100%; height: 100%;">
508+
<slot
509+
v-if="showLoading"
510+
name="loader"
511+
:progress="progress"
512+
:wasm-loading="wasmLoading"
513+
:state-percent="statePercent"
514+
:hash-percent="hashPercent"
515+
:show-loading="showLoading"
516+
>
517+
<div style="position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(10, 10, 10, 0.45); z-index: 2;">
518+
<div style="min-width: 220px; max-width: 320px; padding: 12px 14px; border-radius: 8px; background: rgba(20, 20, 20, 0.9); color: #f5f5f5;">
519+
<div style="font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px;">
520+
{{ wasmLoading ? "Loading VTK WASM" : "Syncing VTK Data" }}
521+
</div>
522+
<div v-if="wasmLoading" style="font-size: 12px; opacity: 0.85;">
523+
Fetching WebAssembly bundle...
524+
</div>
525+
<div v-else>
526+
<div style="margin-bottom: 10px;">
527+
<div style="display: flex; justify-content: space-between; font-size: 11px; margin-bottom: 4px; opacity: 0.8;">
528+
<span>States</span>
529+
<span>{{ progress.state.current }}/{{ progress.state.total }}</span>
530+
</div>
531+
<div style="height: 6px; background: rgba(255, 255, 255, 0.15); border-radius: 4px; overflow: hidden;">
532+
<div :style="{ width: statePercent + '%', height: '100%', background: '#4aa3ff' }"></div>
533+
</div>
534+
</div>
535+
<div>
536+
<div style="display: flex; justify-content: space-between; font-size: 11px; margin-bottom: 4px; opacity: 0.8;">
537+
<span>Blobs</span>
538+
<span>{{ progress.hash.current }}/{{ progress.hash.total }}</span>
539+
</div>
540+
<div style="height: 6px; background: rgba(255, 255, 255, 0.15); border-radius: 4px; overflow: hidden;">
541+
<div :style="{ width: hashPercent + '%', height: '100%', background: '#f5c542' }"></div>
542+
</div>
543+
</div>
544+
</div>
545+
</div>
546+
</div>
547+
</slot>
548+
</div>`,
435549
};

0 commit comments

Comments
 (0)