jupyter / notebook

Jupyter Interactive Notebook
https://jupyter-notebook.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
11.75k stars 4.99k forks source link

How to archive real time communication between Python and JS ? #3533

Open nukadelic opened 6 years ago

nukadelic commented 6 years ago

My goal is to run JS canvas-game within a notebook and get continues data stream between itself and Python. First i have tried to execute notebook kernel from JS, 60 times per second, with an animation frame request hook. But then it hit me, this process is working on a single kernel. Here is my initial attempt:

%%javascript
window.onEnterFrame = e => IPython.notebook.kernel.execute(
`sharedVariable = "${ window.getGameVar() }"  `
);

So that resulted in non changing sharedVariable value during the cell execution

import time
result_1 = sharedVariable
time.sleep( 1 )
result_2 = sharedVariable
print( result_1 == result_2 ) # output is always True

After which i have decided to try and use the COMM, so I made a simple one that will replay with a string value after 500ms of delay.

%%javascript
Jupyter.notebook.kernel.comm_manager.register_target('my_comm_id', (comm, msg)=> {
    comm.on_msg(_=> { // on msg received 
        setTimeout( _=> { // wait 500ms
            comm.send(  window.getGameVar()  ); // output data
        }, 500 );
    });
});

And a matching Python COMM:

my_comm = Comm( target_name='my_comm_id' )
@my_comm.on_msg
def _recv(msg):
    global sharedVariable
    sharedVariable = msg['content']['data']

But that didn't help either, as apparently the messaged were getting received only after the cell had completed it's execution

my_comm.send( "" )
time.sleep( 1 )
result_1 = sharedVariable
my_comm.send( "" )
time.sleep( 1 )
result_2 = sharedVariable
print( result_1 == result_2 ) # output is always True

Also i have tried creating a separate async function

import asyncio

async def getData2(future):
    global my_comm, sharedVariable
    my_comm.send( "" )
    await asyncio.sleep(1)
    future.set_result( sharedVariable )

def getData():
    asyncio.set_event_loop(asyncio.new_event_loop()) # set new global event loop
    loop = asyncio.get_event_loop()
    future = asyncio.Future()
    asyncio.ensure_future( getData2(future) )
    loop.run_until_complete(future)
    result = future.result()
    loop.close()
    return result

But the problem didn't go away

result_1 = getData()
result_2 = getData()
print( result_1 == result_2 ) # output is always True

I had a brief overview of ipywidgets and was able to get one way communication to work like so:

from ipywidgets import FloatSlider
from IPython.display import display
slider = FloatSlider()
display(slider)
for i in range( 11 ):
    slider.value = i * 10    
    time.sleep( 1 )

But the widgets won't work if you change the slider by dragging it

for i in range( 10 ):
    print( slider.value )  # Will always output slider initial value
    time.sleep( 1 ) 
kkyon commented 6 years ago

You need to find some widgets has internal envent loop . Then be able to be added callback function to update/refresh data. Aboves you tried seem do "push" data work . It only can work when buld yourself widgets.

see below some widgets. bokeh and https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Asynchronous.html