import type * as p from "@bokehjs/core/properties" import {ModelChangedEvent} from "@bokehjs/document" import {View} from "@bokehjs/core/view" import {Model} from "@bokehjs/model" import {Message} from "@bokehjs/protocol/message" import {Receiver} from "@bokehjs/protocol/receiver" import {Buffer} from "@bokehjs/core/serialization/buffer" import type {Patch, DocumentChangedEvent} from "@bokehjs/document" import {isArray, isPlainObject} from "@bokehjs/core/util/types" import {keys} from "@bokehjs/core/util/object" export const comm_settings: any = { debounce: true, } export class CommManagerView extends View { declare model: CommManager } export namespace CommManager { export type Attrs = p.AttrsOf export type Props = Model.Props & { plot_id: p.Property comm_id: p.Property client_comm_id: p.Property timeout: p.Property debounce: p.Property } } export interface CommManager extends CommManager.Attrs {} export class CommManager extends Model { declare properties: CommManager.Props declare __view_type__: CommManagerView ns: any _receiver: Receiver _client_comm: any _event_buffer: DocumentChangedEvent[] _timeout: number _blocked: boolean constructor(attrs?: Partial) { super(attrs) } override initialize(): void { super.initialize() this._receiver = new Receiver() this._event_buffer = [] this._blocked = false this._timeout = Date.now() if (((window as any).PyViz == undefined) || (!(window as any).PyViz.comm_manager)) { console.warn("Could not find comm manager on window.PyViz, ensure the extension is loaded.") } else { this.ns = (window as any).PyViz this.ns.comm_manager.register_target(this.plot_id, this.comm_id, (msg: any) => { for (const view of this.ns.shared_views.get(this.plot_id)) { if (view !== this) { view.msg_handler(msg) } } try { this.msg_handler(msg) } catch (e) { console.error(e) } }) this._client_comm = this.ns.comm_manager.get_client_comm(this.plot_id, this.client_comm_id, (msg: any) => this.on_ack(msg)) if (this.ns.shared_views == null) { this.ns.shared_views = new Map() } if (this.ns.shared_views.has(this.plot_id)) { this.ns.shared_views.get(this.plot_id).push(this) } else { this.ns.shared_views.set(this.plot_id, [this]) } } } protected _document_listener: (event: DocumentChangedEvent) => void = (event) => this._document_changed(event) protected override _doc_attached(): void { super._doc_attached() if (this.document != null) { this.document.on_change(this._document_listener) } } protected _document_changed(event: DocumentChangedEvent): void { // Filter out changes to attributes that aren't server-visible if (event instanceof ModelChangedEvent && !event.model.properties[event.attr].syncable) { return } this._event_buffer.push(event) if (!comm_settings.debounce) { this.process_events() } else if ((!this._blocked || (Date.now() > this._timeout))) { setTimeout(() => this.process_events(), this.debounce) this._blocked = true this._timeout = Date.now()+this.timeout } } protected _extract_buffers(value: unknown, buffers: ArrayBuffer[]): any { if (isArray(value)) { for (const val of value) { this._extract_buffers(val, buffers) } } else if (value instanceof Map) { for (const key of value.keys()) { const v = value.get(key) this._extract_buffers(v, buffers) } } else if (value instanceof Buffer) { const {buffer} = value const id = buffers.length buffers.push(buffer) return {id} } else if (isPlainObject(value)) { for (const key of keys(value)) { const replaced = this._extract_buffers(value[key], buffers) if (replaced != null) { value[key] = replaced } } } } process_events() { if ((this.document == null) || (this._client_comm == null)) { return } const patch = this.document.create_json_patch(this._event_buffer) this._event_buffer = [] const message = {...Message.create("PATCH-DOC", {}, patch)} const buffers: ArrayBuffer[] = [] this._extract_buffers(message.content, buffers) this._client_comm.send(message, {}, buffers) for (const view of this.ns.shared_views.get(this.plot_id)) { if (view !== this && view.document != null) { view.document.apply_json_patch(patch, [], this.id) } } } override disconnect_signals(): void { super.disconnect_signals() this.ns.shared_views.shared_views.delete(this.plot_id) } on_ack(msg: any) { // Receives acknowledgement from Python, processing event // and unblocking Comm if event queue empty const metadata = msg.metadata if (this._event_buffer.length > 0) { this._blocked = true this._timeout = Date.now()+this.timeout this.process_events() } else { this._blocked = false } if ((metadata.msg_type == "Ready") && metadata.content) { // eslint-disable-next-line no-console console.log("Python callback returned following output:", metadata.content) } else if (metadata.msg_type == "Error") { console.warn("Python failed with the following traceback:", metadata.traceback) } } msg_handler(msg: any) { const metadata = msg.metadata const buffers = msg.buffers const content = msg.content.data const plot_id = this.plot_id if ((metadata.msg_type == "Ready")) { if (metadata.content) { // eslint-disable-next-line no-console console.log("Python callback returned following output:", metadata.content) } else if (metadata.msg_type == "Error") { console.warn("Python failed with the following traceback:", metadata.traceback) } } else if (plot_id != null) { let plot = null if ((plot_id in this.ns.plot_index) && (this.ns.plot_index[plot_id] != null)) { plot = this.ns.plot_index[plot_id] } else if (((window as any).Bokeh !== undefined) && (plot_id in (window as any).Bokeh.index)) { plot = (window as any).Bokeh.index[plot_id] } if (plot == null) { return } if (content.length) { this._receiver.consume(content) } else if ((buffers != undefined) && (buffers.length > 0)) { this._receiver.consume(buffers[0].buffer) } else { return } const comm_msg = this._receiver.message if ((comm_msg != null) && (Object.keys(comm_msg.content as Patch).length > 0) && this.document != null) { const patch = comm_msg.content as Patch this.document.apply_json_patch(patch, comm_msg.buffers) } } } static override __module__ = "panel.models.comm_manager" static { this.prototype.default_view = CommManagerView this.define(({Int, Str, Nullable}) => ({ plot_id: [ Nullable(Str), null ], comm_id: [ Nullable(Str), null ], client_comm_id: [ Nullable(Str), null ], timeout: [ Int, 5000 ], debounce: [ Int, 50 ], })) } }