Skip to content

Commit 5fa7a77

Browse files
committed
Widgetsnbextension: Use control comm channel
1 parent db67129 commit 5fa7a77

File tree

1 file changed

+163
-41
lines changed

1 file changed

+163
-41
lines changed

widgetsnbextension/src/manager.js

Lines changed: 163 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ var embedWidgets = require("./embed_widgets");
1111

1212
var MIME_TYPE = 'application/vnd.jupyter.widget-view+json';
1313

14+
var CONTROL_COMM_TARGET = 'jupyter.widget.control';
15+
var CONTROL_COMM_VERSION = '1.0.0';
16+
1417

1518
function polyfill_new_comm_buffers(manager, target_name, data, callbacks, metadata, comm_id, buffers) {
1619
/**
@@ -75,45 +78,9 @@ export class WidgetManager extends base.ManagerBase {
7578

7679
// Attempt to reconstruct any live comms by requesting them from the back-end (2).
7780
var that = this;
78-
this._get_comm_info().then(function(comm_ids) {
7981

80-
// Create comm class instances from comm ids (2).
81-
var comm_promises = Object.keys(comm_ids).map(function(comm_id) {
82-
return that._create_comm(that.comm_target_name, comm_id);
83-
});
84-
85-
// Send a state request message out for each widget comm and wait
86-
// for the responses (2).
87-
return Promise.all(comm_promises).then(function(comms) {
88-
return Promise.all(comms.map(function(comm) {
89-
var update_promise = new Promise(function(resolve, reject) {
90-
comm.on_msg(function (msg) {
91-
base.put_buffers(msg.content.data.state, msg.content.data.buffer_paths, msg.buffers);
92-
// A suspected response was received, check to see if
93-
// it's a state update. If so, resolve.
94-
if (msg.content.data.method === 'update') {
95-
resolve({
96-
comm: comm,
97-
msg: msg
98-
});
99-
}
100-
});
101-
});
102-
comm.send({
103-
method: 'request_state'
104-
}, that.callbacks());
105-
return update_promise;
106-
}));
107-
}).then(function(widgets_info) {
108-
return Promise.all(widgets_info.map(function(widget_info) {
109-
return that.new_model({
110-
model_name: widget_info.msg.content.data.state._model_name,
111-
model_module: widget_info.msg.content.data.state._model_module,
112-
model_module_version: widget_info.msg.content.data.state._model_module_version,
113-
comm: widget_info.comm,
114-
}, widget_info.msg.content.data.state);
115-
}));
116-
}).then(function() {
82+
this._loadFromKernel()
83+
.then(function () {
11784
// Now that we have mirrored any widgets from the kernel...
11885
// Restore any widgets from saved state that are not live (3)
11986
if (widget_md && widget_md['application/vnd.jupyter.widget-state+json']) {
@@ -132,7 +99,6 @@ export class WidgetManager extends base.ManagerBase {
13299
}
133100
});
134101
});
135-
});
136102

137103
// Create the actions and menu
138104
this._init_actions();
@@ -159,6 +125,162 @@ export class WidgetManager extends base.ManagerBase {
159125
}
160126
}
161127

128+
/**
129+
* Fetch all widgets states using the control comm channel, or fallback to `_loadFromKernelSlow`
130+
* if the backend does not implement the control comm.
131+
*/
132+
_loadFromKernel() {
133+
var that = this;
134+
135+
const commId = base.uuid();
136+
return this._create_comm(
137+
CONTROL_COMM_TARGET,
138+
commId,
139+
{ widgets: null },
140+
{ version: CONTROL_COMM_VERSION }
141+
).then(function (initComm) {
142+
// Try fetching all widget states through the control comm
143+
let data;
144+
let buffers;
145+
try {
146+
var controlCommPromise = new Promise(function (resolve, reject) {
147+
initComm.on_msg(function (msg) {
148+
data = msg['content']['data'];
149+
150+
if (data.method !== 'update_states') {
151+
console.warn(`
152+
Unknown ${data.method} message on the Control channel
153+
`);
154+
return;
155+
}
156+
157+
buffers = (msg.buffers || []).map(function (b) {
158+
if (b instanceof DataView) {
159+
return b;
160+
} else {
161+
return new DataView(b instanceof ArrayBuffer ? b : b.buffer);
162+
}
163+
});
164+
165+
resolve(null);
166+
});
167+
168+
initComm.on_close(reject);
169+
170+
// Send a states request msg
171+
initComm.send({ method: 'request_states' }, {});
172+
});
173+
174+
return controlCommPromise.then(function () {
175+
initComm.close();
176+
177+
const states = data.states;
178+
179+
// Extract buffer paths
180+
const bufferPaths = {};
181+
for (const bufferPath of data.buffer_paths) {
182+
if (!bufferPaths[bufferPath[0]]) {
183+
bufferPaths[bufferPath[0]] = [];
184+
}
185+
bufferPaths[bufferPath[0]].push(bufferPath.slice(1));
186+
}
187+
188+
// Start creating all widgets
189+
return Promise.all(
190+
Object.keys(states).map(function (widget_id) {
191+
const state = states[widget_id];
192+
193+
try {
194+
return that._create_comm('jupyter.widget', widget_id)
195+
.then(function (comm) {
196+
// Put binary buffers
197+
if (widget_id in bufferPaths) {
198+
const nBuffers = bufferPaths[widget_id].length;
199+
base.put_buffers(
200+
state,
201+
bufferPaths[widget_id],
202+
buffers.splice(0, nBuffers)
203+
);
204+
}
205+
206+
return that.new_model(
207+
{
208+
model_name: state.model_name,
209+
model_module: state.model_module,
210+
model_module_version: state.model_module_version,
211+
model_id: widget_id,
212+
comm: comm,
213+
},
214+
state.state
215+
);
216+
});
217+
} catch (error) {
218+
// Failed to create a widget model, we continue creating other models so that
219+
// other widgets can render
220+
console.error(error);
221+
}
222+
})
223+
);
224+
});
225+
} catch (error) {
226+
console.warn(
227+
'Failed to open "jupyter.widget.control" comm channel, fallback to slow fetching of widgets.',
228+
error
229+
);
230+
// Fallback to the old implementation for old ipywidgets backend versions (<=7.6)
231+
return this._loadFromKernelSlow();
232+
}
233+
});
234+
}
235+
236+
/**
237+
* Old implementation of fetching widgets one by one using
238+
* the request_state message on each comm.
239+
*/
240+
_loadFromKernelSlow() {
241+
var that = this;
242+
return this._get_comm_info().then(function(comm_ids) {
243+
244+
// Create comm class instances from comm ids (2).
245+
var comm_promises = Object.keys(comm_ids).map(function(comm_id) {
246+
return that._create_comm(that.comm_target_name, comm_id);
247+
});
248+
249+
// Send a state request message out for each widget comm and wait
250+
// for the responses (2).
251+
return Promise.all(comm_promises).then(function(comms) {
252+
return Promise.all(comms.map(function(comm) {
253+
var update_promise = new Promise(function(resolve, reject) {
254+
comm.on_msg(function (msg) {
255+
base.put_buffers(msg.content.data.state, msg.content.data.buffer_paths, msg.buffers);
256+
// A suspected response was received, check to see if
257+
// it's a state update. If so, resolve.
258+
if (msg.content.data.method === 'update') {
259+
resolve({
260+
comm: comm,
261+
msg: msg
262+
});
263+
}
264+
});
265+
});
266+
comm.send({
267+
method: 'request_state'
268+
}, that.callbacks());
269+
return update_promise;
270+
}));
271+
}).then(function(widgets_info) {
272+
return Promise.all(widgets_info.map(function(widget_info) {
273+
return that.new_model({
274+
model_name: widget_info.msg.content.data.state._model_name,
275+
model_module: widget_info.msg.content.data.state._model_module,
276+
model_module_version: widget_info.msg.content.data.state._model_module_version,
277+
comm: widget_info.comm,
278+
}, widget_info.msg.content.data.state);
279+
}));
280+
});
281+
});
282+
}
283+
162284
/**
163285
* Registers manager level actions with the notebook actions list
164286
*/
@@ -336,7 +458,7 @@ export class WidgetManager extends base.ManagerBase {
336458
}
337459

338460
/**
339-
* List of widget managers in *reverse* order
461+
* List of widget managers in *reverse* order
340462
* (_managers[0] is the most recent)
341463
*/
342-
WidgetManager._managers = [];
464+
WidgetManager._managers = [];

0 commit comments

Comments
 (0)