Let's say we've got the following Agents.jl + Makie.jl workflow:
using Agents, Random, AgentsPlots, Makie, Observables
mutable struct BallAgent <: AbstractAgent
id::Int
pos::Tuple{Float64, Float64}
vel::Tuple{Float64, Float64}
mass::Float64
end
function ball_model(; speed = 0.002)
space2d = ContinuousSpace(2; periodic = true, extend = (1, 1))
model = AgentBasedModel(BallAgent, space2d, properties = Dict(:dt => 1e0, :i => Observable(0)))
Random.seed!(1001)
for ind ∈ 1:500
pos = Tuple(rand(Float64, 2))
vel = (sincos(rand(Float64) * 2π) |> reverse) .* speed
add_agent!(pos, model, vel, 1e0)
end
index!(model)
return model
end
agent_step!(agent::BallAgent, model) = move_agent!(agent, model, model.dt)
function AbstractPlotting.:plot!(scene::AbstractPlotting.Plot(AgentBasedModel))
ab_model = scene[1][]
position = Observable([a.pos for a ∈ allagents(ab_model)])
on(ab_model.i) do i
position[] = [a.pos for a ∈ allagents(ab_model)]
end
scatter!(scene, position, markersize = 0.01)
end
function create_animation()
model = ball_model()
scene = plot(model)
display(AbstractPlotting.PlotDisplay(), scene)
for i ∈ 1:600
Agents.step!(model, agent_step!, 1)
model.i[] = i
sleep(1/60)
end
end
Now since AgentsPlot.jl doesn't support Makie, I have to make a recipe for it and currently the way I update the plot is by registering a callback that updates the positions observable, which gets passed to scatter.
The thing is I am registering that callback on an Int observable, which is attached to the AgentBasedModel, that is created specifically for this. It just seems like an ugly way to go about this.
model = AgentBasedModel(BallAgent, space2d, properties = Dict(:dt => 1e0, :i => Observable(0))) i is the observable which we attach the callback to like this:
on(ab_model.i) do i
position[] = [a.pos for a ∈ allagents(ab_model)]
end
and I have to update it here so the callback gets called like so:
for i ∈ 1:600
model.i[] = i