Skip to content

Commit 2aadad6

Browse files
authored
feat: Add progress monitoring for RemoteSession downloads
1 parent d11e4d5 commit 2aadad6

File tree

2 files changed

+93
-10
lines changed

2 files changed

+93
-10
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ import { createVtkObjectProxy, createNamespace } from "@kitware/vtk-wasm/vtk"
4242
import { ExportViewer, createViewer } from "@kitware/vtk-wasm/viewer"
4343
```
4444

45+
### RemoteSession progress
46+
47+
```js
48+
import { RemoteSession } from "@kitware/vtk-wasm/remote"
49+
50+
const session = new RemoteSession()
51+
const removeProgress = session.addProgressCallback(({ active, state, hash }) => {
52+
// state: { current, total }, hash: { current, total }
53+
// active is true while states/blobs are being fetched
54+
})
55+
56+
// Later, to stop listening:
57+
removeProgress()
58+
```
59+
4560
### UMD imports
4661

4762
```js

src/remote.js

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export class RemoteSession {
2929
this.networkFetchStatus = null;
3030
this.cameraIds = new Set();
3131
this.stateCache = {};
32+
this.progressCallbacks = new Set();
33+
this.progressState = null;
3234
// FIXME - remove when VTK>=9.5
3335
this.renderWindowIds = new Set();
3436
this.renderWindowIdToInteractorId = new Map();
@@ -116,6 +118,52 @@ export class RemoteSession {
116118
this.networkFetchStatus = fetchStatus;
117119
}
118120

121+
/**
122+
* Register a callback to monitor download progress.
123+
*
124+
* @param {Function} callback
125+
* @returns {Function} cleanup function
126+
*/
127+
addProgressCallback(callback) {
128+
if (callback) {
129+
this.progressCallbacks.add(callback);
130+
}
131+
return () => {
132+
this.progressCallbacks.delete(callback);
133+
};
134+
}
135+
136+
emitProgress() {
137+
if (!this.progressCallbacks.size || !this.progressState) {
138+
return;
139+
}
140+
const { active, state, hash } = this.progressState;
141+
const payload = {
142+
active,
143+
state: {
144+
current: state.current,
145+
total: state.total,
146+
},
147+
hash: {
148+
current: hash.current,
149+
total: hash.total,
150+
},
151+
};
152+
this.progressCallbacks.forEach((cb) => cb(payload));
153+
}
154+
155+
incrementProgress(kind) {
156+
if (!this.progressState) {
157+
return;
158+
}
159+
const bucket = this.progressState[kind];
160+
if (!bucket) {
161+
return;
162+
}
163+
bucket.current = Math.min(bucket.current + 1, bucket.total);
164+
this.emitProgress();
165+
}
166+
119167
/**
120168
* Free old blobs if they go beyond the allowed cache size in Bytes
121169
* starting with the older ones.
@@ -164,7 +212,9 @@ export class RemoteSession {
164212
*/
165213
async fetchState(vtkId) {
166214
const serverState = await this.networkFetchState(vtkId);
167-
return this.patchState(serverState);
215+
const patchedState = this.patchState(serverState);
216+
this.incrementProgress("state");
217+
return patchedState;
168218
}
169219

170220
/**
@@ -221,17 +271,19 @@ export class RemoteSession {
221271
* @returns typed array matching blob content
222272
*/
223273
async fetchHash(hash) {
274+
let array;
224275
// pendingArray only filled via pushHash
225276
if (this.pendingArrays[hash]) {
226277
await this.pendingArrays[hash];
227278
this.hashesMTime[hash] = this.currentMTime;
228279
delete this.pendingArrays[hash];
229-
return;
280+
} else {
281+
// regular network call
282+
array = await this.networkFetchHash(hash);
283+
this.sceneManager.registerBlob(hash, array);
284+
this.hashesMTime[hash] = this.currentMTime;
230285
}
231-
// regular network call
232-
const array = await this.networkFetchHash(hash);
233-
this.sceneManager.registerBlob(hash, array);
234-
this.hashesMTime[hash] = this.currentMTime;
286+
this.incrementProgress("hash");
235287
return array;
236288
}
237289

@@ -274,8 +326,8 @@ export class RemoteSession {
274326

275327
try {
276328
const serverStatus = await this.networkFetchStatus(vtkId);
277-
const pendingHashes = [];
278-
const pendingStates = [];
329+
const hashesToFetch = [];
330+
const statesToFetch = [];
279331

280332
// Handle forcepush if any
281333
const resetIds = serverStatus.force_push || [];
@@ -286,18 +338,29 @@ export class RemoteSession {
286338
// Fetch any state that needs update
287339
serverStatus.ids.forEach(([vtkId, mtime]) => {
288340
if (!this.stateMTimes[vtkId] || this.stateMTimes[vtkId] < mtime) {
289-
pendingStates.push(this.fetchState(vtkId));
341+
statesToFetch.push(vtkId);
290342
}
291343
});
292344

293345
// Fetch any blob that is missing
294346
serverStatus.hashes.forEach((hash) => {
295347
if (!this.hashesMTime[hash]) {
296-
pendingHashes.push(this.fetchHash(hash));
348+
hashesToFetch.push(hash);
297349
}
298350
this.hashesMTime[hash] = this.currentMTime;
299351
});
300352

353+
this.progressState = {
354+
active: !!(statesToFetch.length + hashesToFetch.length),
355+
state: { current: 0, total: statesToFetch.length },
356+
hash: { current: 0, total: hashesToFetch.length },
357+
};
358+
this.emitProgress();
359+
const pendingStates = statesToFetch.map((stateId) =>
360+
this.fetchState(stateId),
361+
);
362+
const pendingHashes = hashesToFetch.map((hash) => this.fetchHash(hash));
363+
301364
// Capture cameras
302365
serverStatus.cameras.forEach((v) => this.cameraIds.add(Number(v)));
303366

@@ -344,6 +407,11 @@ export class RemoteSession {
344407
} catch (e) {
345408
console.error("Error in update", e);
346409
} finally {
410+
if (this.progressState) {
411+
this.progressState.active = false;
412+
this.emitProgress();
413+
this.progressState = null;
414+
}
347415
this.updateInProgress--;
348416
if (this.updateInProgress) {
349417
this.updateInProgress = 0;

0 commit comments

Comments
 (0)