Skip to content

Commit d3d299c

Browse files
authored
Merge pull request #766 from maartenbreddels/refactor_widget_fetching
Feature: fetch all widgets in one single comm message using the control channel
2 parents 63a81d1 + fbf017c commit d3d299c

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed

packages/voila/src/manager.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,123 @@ export class WidgetManager extends JupyterLabManager {
194194
});
195195
}
196196

197+
/**
198+
* This is the implementation of building widgets models making use of the
199+
* jupyter.widget.control comm channel
200+
*/
197201
async _build_models(): Promise<{ [key: string]: base.WidgetModel }> {
202+
const models: { [key: string]: base.WidgetModel } = {};
203+
const commId = base.uuid();
204+
const initComm = await this._create_comm(
205+
'jupyter.widget.control',
206+
commId,
207+
{ widgets: null },
208+
{ version: '1.0.0' }
209+
);
210+
211+
// Fetch widget states
212+
let data: any;
213+
let buffers: any;
214+
try {
215+
await new Promise((resolve, reject) => {
216+
initComm.on_msg(msg => {
217+
data = msg['content']['data'];
218+
219+
if (data.method !== 'update_states') {
220+
console.warn(`
221+
Unknown ${data.method} message on the Control channel
222+
`);
223+
return;
224+
}
225+
226+
buffers = (msg.buffers || []).map((b: any) => {
227+
if (b instanceof DataView) {
228+
return b;
229+
} else {
230+
return new DataView(b instanceof ArrayBuffer ? b : b.buffer);
231+
}
232+
});
233+
234+
resolve(null);
235+
});
236+
237+
initComm.on_close(reject);
238+
239+
// Send a states request msg
240+
initComm.send({ method: 'request_states' }, {});
241+
});
242+
} catch (error) {
243+
console.warn(
244+
'Failed to open "jupyter.widget.control" comm channel, fallback to slow fetching of widgets.',
245+
error
246+
);
247+
// Fallback to the old implementation for old ipywidgets versions (<=7.6)
248+
return this._build_models_slow();
249+
}
250+
251+
initComm.close();
252+
253+
const states: any = data.states;
254+
255+
// Extract buffer paths
256+
// Why do we have to do this? Is there another way?
257+
const bufferPaths: any = {};
258+
for (const bufferPath of data.buffer_paths) {
259+
if (!bufferPaths[bufferPath[0]]) {
260+
bufferPaths[bufferPath[0]] = [];
261+
}
262+
bufferPaths[bufferPath[0]].push(bufferPath.slice(1));
263+
}
264+
265+
const widgetPromises: Promise<base.WidgetModel>[] = [];
266+
267+
// Start creating all widgets
268+
for (const [widget_id, state] of Object.entries(states) as any) {
269+
try {
270+
const comm = await this._create_comm('jupyter.widget', widget_id);
271+
272+
// Put binary buffers
273+
if (widget_id in bufferPaths) {
274+
const nBuffers = bufferPaths[widget_id].length;
275+
base.put_buffers(
276+
state,
277+
bufferPaths[widget_id],
278+
buffers.splice(0, nBuffers)
279+
);
280+
}
281+
282+
const modelPromise = this.new_model(
283+
{
284+
model_name: state.model_name,
285+
model_module: state.model_module,
286+
model_module_version: state.model_module_version,
287+
model_id: widget_id,
288+
comm: comm
289+
},
290+
state.state
291+
);
292+
widgetPromises.push(modelPromise);
293+
} catch (error) {
294+
// Failed to create a widget model, we continue creating other models so that
295+
// other widgets can render
296+
console.error(error);
297+
}
298+
}
299+
300+
// Wait for widgets to be created
301+
const widgets = await Promise.all(widgetPromises);
302+
for (const model of widgets) {
303+
models[model.model_id] = model;
304+
}
305+
306+
return models;
307+
}
308+
309+
/**
310+
* This is the old implementation of building widgets models
311+
* We keep it around for supporting old ipywidgets versions (<=7.6)
312+
*/
313+
async _build_models_slow(): Promise<{ [key: string]: base.WidgetModel }> {
198314
const comm_ids = await this._get_comm_info();
199315
const models: { [key: string]: base.WidgetModel } = {};
200316
/**

0 commit comments

Comments
 (0)