Renderloop updates in GLMakie.jl?

Context:
I’m trying to recreate this demo Collide force | D3 by Observable in Makie.jl with GraphPlayground.jl (and do other demos from there too…)

Issue:
What’s are different ways of driving the update loop in GLMakie.jl ?

Things I’ve tried:

  1. Create an async task to update the positions
  2. Buttons to start/stop the async task based on Updating Makie plot based on button action - #7 by jules
  3. Create a new screen and use render_tick to get notified when there is a new frame.
  4. Figure out the current screen for the (scene.current_screens[1].render_tick) event.

The downsides to 1 and 2 are that I have to manage tasks.

The downside to 3 is that I have to start a new window.

The downside to 4 is that it seems really slow. Also, I have to call display on the scene first to get current_screens to instantiate.

Are there other ways I should think about handling this?

Thanks!
David

My solution right now for 3 is below. (4 is basically the same, but we don’t have to create a new screen and use scene.current_screens[1] after calling display…)

using GLMakie
using StableRNGs, GeometryBasics, GraphPlayground, GLMakie

# ## Setup the simulation 
rng = StableRNG(1)
n = 200
width = 564 
k = width/n
radiusdist = k:4k
radius = rand(rng, radiusdist, n)
#radius[1] = 10 # make the first point a fixed point
pos = [Point2f0(rand(rng, 0:width), rand(rng, 0:width)) for _ in 1:n]
pos = pos .- sum(pos) / length(pos) 
sim = ForceSimulation(pos, eachindex(pos); 
  position=PositionForce(;strength=0.01),
  collide=CollisionForce(;radius=radius.+1,iterations=3),
  charge=ManyBodyForce(strength=(i) -> i==1 ? -width*2/3 : 0.0, theta2=0.82),
  alpha=GraphPlayground.CoolingStepper(alpha_target=0.3),
  velocity_decay=0.9,)

# ## Setup the screen and scene 

screen2 = GLMakie.Screen(framerate=60.0, vsync=true, render_on_demand=false, title="GLMakie Playground")
scene = Scene(camera = campixel!, size = (width, width))  
GLMakie.display_scene!(screen2, scene)

# ## setup links between all the display elements and simulation 
pos2 = sim.positions[2:end] 
posobs = Observable(pos2 .+ Point2f0(width/2, width/2))
f = scatter!(scene, posobs, markersize=pi*radius[2:end]/1.11, markerspace=:pixel, strokewidth=0, strokecolor=:white, 
  )  
velobs = Observable(sim.velocities[2:end])
vscale = Observable(1.0)
# g = arrows!(scene, posobs, velobs; lengthscale = vscale)  

mppos = Observable(Point2f0(0,0))
scatter!(scene, mppos, markersize=pi*100/1.11, color=:grey, strokewidth=0, markerspace=:pixel)


on(events(scene).mouseposition) do mp
  if mp[1] < 0 || mp[1] > width || mp[2] < 0 || mp[2] > width
    sim.alpha.alpha_target = 0.0
  else
    sim.alpha.alpha_target = 0.3
  end
  fixnode!(sim, 1, mp .- Point2f0(width/2, width/2))
  notify(posobs)
end  

# ## Add the simulation update loop on render_tick. 
on(screen2.render_tick) do _
  #println("Frame")
  step!(sim)
  posobs[] = sim.positions[2:end] .+ Point2f0(width/2, width/2)
  velobs[] = sim.velocities[2:end]
  vscale[] = 10*sim.alpha.alpha
  mppos[] = sim.positions[1] .+ Point2f0(width/2, width/2)
end

display(scene)

## Old code using my own task to set updates... 
# This was an older approach where I just had a loop that tried to update at 60fps and changed the positions
# with render-on-demand. 
# But then I have to manage my own update task, which is annoying... 
#=
task, close = GraphPlayground.updateloop(scene) do dt
  step!(sim)
  posobs[] = sim.positions[2:end] .+ Point2f0(width/2, width/2)
  velobs[] = sim.velocities[2:end]
  vscale[] = 10*sim.alpha.alpha
  mppos[] = sim.positions[1] .+ Point2f0(width/2, width/2)
  return true 
end 

=#