How to: LiveView + WebGL?

What is considered the correct or idiomatic approach to using LiveView with a WebGL frontend? I’m building a charting dashboard, and I’m not sure how I should sync the frontend with the backend in a LiveView app. If I have a redraw function in JS, should I use LiveView to push JSON and invoke this redraw function somehow?

Apologies if this is covered in a tutorial somewhere.

I guess the answer is to use hooks?

I would set clear responsibilities here.

WebGL should own the whole visual spectrum as well as business logic in the front-end. There shouldn’t be a race with LiveView JS side to who is going to handle that event and weird patches to DOM.

If you need to push, fetch data I would recommend phoenix sockets with the WebGL. I wouldn’t get LiveView involved into charting or WGL.

I say this because hooks have events defined and coding is sporadic. With the JS sockets you can have clear methods to manage your DOM and data transfer whilst still using the same backend.

1 Like

Hooks are definitely the way to go. Use the handleEvent method on the hook to receive server-side events. Here’s an example–

To render a client-side chart, first render an element in your template where the chart will live:

# lib/dashboard_live/index.html.leex
<div id="my-chart" phx-hook="Chart" phx-update="ignore">
  <!-- The chart will be rendered here -->
</div>

Note: The article you linked is a bit outdated. You can apply phx-hook and phx-update=“ignore” to the same element and the hook will still fire when attributes change– only its children are ignored.

Then, provide a hook to the LiveSocket:

# assets/js/app.js
Hooks.Chart = {
  mounted(){
    this.handleEvent("points", ({payload}) => {
      let points = payload[this.el.id]
      if(points){ WebGLChartLib.addPoints(points) }
    })
  }
}

Next, let’s assume that your LiveView process receives chart data periodically from an outside source. When this occurs, you trigger the “points” event on the client-side via push_event/3:

# lib/dashboard_live/index.ex
def handle_info({:points, "my-chart", data}, socket) do
  {:noreply, push_event(socket, "points", %{"my-chart" => data})}
end

Note: the event is triggered for all active hooks that have subscribed, so you either need to namespace the event name or namespace the data if you are going to re-use the same hook on multiple elements.

Fwiw this is almost exactly what we do to draw the charts for LiveDashboard. I say almost because push_event/handleEvent didn’t exist at the time so we actually render the datapoints into attributes of hidden elements and then read them back out in the hook.

6 Likes

I personally find it very annoying when people respond “don’t do X” to “how to do X” questions, so I’m going to try to present this as a logical sequence, and you can tell me which assumption I got wrong. (Context: I have been doing lots of WebGL2 in Rust via wasm).

  1. I’m going to go ahead and assume that on the WebGL side, you have a Vector of tuples of the form
Vec<(ShaderProgram, TextureSetup, UniformSetup, VertexBufferObjects)>

where basically, on every frame, for every tuple in the list, we want to bind the ShaderProgram, the TextureSetup, the UniformSetup, and make calls to glDrawElement … // then for efficiency sake we sort by ShaderProgram to minimize calls to glUseProgram

  1. Assuming (1) is true, what you really want from server side is an efficient way to update this
Vec<(ShaderProgram, TextureSetup, UniformSetup, VertexBufferObject)>
  1. At this point, on the WebGL side, I would use a representation like:
HashMap<u64, (ShaderProgram, TextureSetup, UniformSetup, VertexBufferObject)>

so the server side can send commands like “drop the tuple associated with this u64” or “change the TextureSetup assocaited with this u64” or “apply this diff to the VertexBufferObject of this u64”

  1. Assuming 1-2-3 are close what you want, you probably don’t want liveview, and instead probably want some hand rolled protocol to send ‘diffs’ to this hashmap of u64 → (ShaderProgram, TextureSetup, …) tuple.

Wow, this approach seems really interesting, but very different from what I’ve been doing. I’m drawing pie charts and stock charts, so it’s mostly drawing rectangles, lines, and arcs. I don’t fully understand where shaders are applicable for what I’m doing, I thought shaders were for 3D graphics.

I haven’t tried WebGL with rust yet, I’ve been using PixiJS.

I misread your problem. I (incorrectly) thought you were making raw WebGL calls. I wasn’t aware you were using some library that used WebGL.