Skip to content

Commit bce3284

Browse files
maartenbreddelsmartinRenou
authored andcommitted
Performance: Fetch all widgets models in one comm message
Through the new control comm target
1 parent 41ad079 commit bce3284

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

packages/voila/src/manager.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,125 @@ 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 {
243+
console.warn(
244+
'Failed to open "jupyter.widget.control" comm channel, fallback to slow fetching of widgets.'
245+
);
246+
// Fallback to the old implementation for old ipywidgets versions (<=7.6)
247+
return this._build_models_slow();
248+
}
249+
250+
initComm.close();
251+
252+
const states: any = data.states;
253+
254+
// Extract buffer paths
255+
// Why do we have to do this? Is there another way?
256+
const bufferPaths: any = {};
257+
for (const bufferPath of data.buffer_paths) {
258+
if (!bufferPaths[bufferPath[0]]) {
259+
bufferPaths[bufferPath[0]] = [];
260+
}
261+
bufferPaths[bufferPath[0]].push(bufferPath.slice(1));
262+
}
263+
264+
const widgetPromises: Promise<base.WidgetModel>[] = [];
265+
266+
// Start creating all widgets
267+
for (const widget_id in states) {
268+
const state = states[widget_id];
269+
270+
try {
271+
const comm = await this._create_comm('jupyter.widget', widget_id);
272+
273+
// Put binary buffers
274+
if (widget_id in bufferPaths) {
275+
const nBuffers = bufferPaths[widget_id].length;
276+
base.put_buffers(
277+
state,
278+
bufferPaths[widget_id],
279+
buffers.slice(0, nBuffers)
280+
);
281+
buffers = buffers.slice(nBuffers);
282+
}
283+
284+
const modelPromise = this.new_model(
285+
{
286+
model_name: state.model_name,
287+
model_module: state.model_module,
288+
model_module_version: state.model_module_version,
289+
model_id: widget_id,
290+
comm: comm
291+
},
292+
state.state
293+
);
294+
widgetPromises.push(modelPromise);
295+
} catch (error) {
296+
// Failed to create a widget model, we continue creating other models so that
297+
// other widgets can render
298+
console.error(error);
299+
}
300+
}
301+
302+
// Wait for widgets to be created
303+
const widgets = await Promise.all(widgetPromises);
304+
for (const model of widgets) {
305+
models[model.model_id] = model;
306+
}
307+
308+
return models;
309+
}
310+
311+
/**
312+
* This is the old implementation of building widgets models
313+
* We keep it around for supporting old ipywidgets versions (<=7.6)
314+
*/
315+
async _build_models_slow(): Promise<{ [key: string]: base.WidgetModel }> {
198316
const comm_ids = await this._get_comm_info();
199317
const models: { [key: string]: base.WidgetModel } = {};
200318
/**

0 commit comments

Comments
 (0)