JuliaGizmos / WebIO.jl

A bridge between Julia and the Web.
https://juliagizmos.github.io/WebIO.jl/latest/
Other
228 stars 64 forks source link

Pushing updates to multiple web clients #71

Closed rdeits closed 6 years ago

rdeits commented 6 years ago

I was investigating whether it's possible to have multiple web views receiving the same updates from Julia. It seems like, at least from the basic demos, this sort of works. That is, pushing an update from Julia to Javascript works, but only one web view receives the update, and which one appears to be random.

To reproduce, I started with the simple clock demo from the readme:

w = Scope()
obs = Observable(w, "clock-value", "")

timestr() = Dates.format(now(), "HH:MM:SS")

# update timestamp every second
@async while true
    sleep(1)
    obs[] = timestr()
end

# on every update to `obs`, replace the text content of #clock
onjs(obs, @js val -> begin
    @var clock = this.dom.querySelector("#clock")
    clock.textContent = val
end)

node = w(
  dom"div#clock"(
    timestr(),
  ),
)

I let IJulia display that final node variable, which results in the ticking clock. I then set up a Mux server to serve the same node:

using Mux
function myapp(req) # an "App" takes a request, returns the output
    node
end

webio_serve(page("/", myapp))

And then I opened localhost:8000 in a browser.

The result is that both clocks keep ticking, but only one of them ticks in a given second, so each individual clock only updates every few seconds.

Is this expected? Do we want to make this work? Or am I just doing it wrong? In MeshCat.jl I manage this by deliberately sending every update to all the connected websockets, and I suspect I could do something similar with WebIO, but only if that's actually desirable.

Thanks!

shashi commented 6 years ago

Nice, that's interesting. I hadn't tested this. Yes, that would be desirable.

rdeits commented 6 years ago

Ok, I understand what's happening now. When I render the node in IJulia, it sets up this async loop to take messages from the scope's outbox and send them to the IJulia connection: https://github.com/JuliaGizmos/WebIO.jl/blob/18ae3d9f06d2ee39b7af2740d4365addd8baf85c/src/connection.jl#L28

However, when I then render the same node in Mux, dispatch() is called again with the WebSocketConnection, which starts another instance of that same loop. The randomly selected updates happen because both tasks are trying to take! from the same channel, so presumably libuv can just choose to process either task arbitrarily.

I could imaging storing a vector of connections associated with each scopeid and then doing:

msg = take!(scope.outbox)
for conn in connections_for_this_scope_id
  send(conn, msg)
end

is that the right idea?

shashi commented 6 years ago

Yup definitely :-)