-
Notifications
You must be signed in to change notification settings - Fork 944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Creating threads to update visualization asynchronously #2656
base: main
Are you sure you want to change the base?
Conversation
execution is slower,thread not stopping gracefully
pending- eipstein not rendering prply(check other models) stopping thread(use_thread) graph inaccuracy
implementing bi-directional threads is ensuring synchronization between model execution and visualization, though this is resulting in reduced performance.
takes care of valueError and CancelledError. pending changes:clean code
optimal performance in browser and jupyter
In use_threads, the threads for step and vis may compete for GI leading to slower execution, especially when sharing resources. In contrast, use_task, which is asyncio-based, avoids GIL contention.
for more information, see https://pre-commit.ci
Performance benchmarks:
|
Thanks for the PR and the detailed PR message! I’m on vacation the whole of February, so I will leave the review to other maintainers. Have you also tried using a separate thread for every figure? |
Thanks! Hope you have a great vacation! |
…control This should improve the overall functionality and reliability of the SimulatorController, particularly in scenarios where stopping the execution is necessary.
That's an excellent point. You're right. Another option could be that a new thread is used to draw all figures for a single step. Then the figures for a certain step will be ready at the same time. You would have to guarantee that each frame is displayed for about the same time (or for a minimum duration). It's up to you how complicated you make it in this PR. We could also keep this PR to a minimal, simple implementation and implement further ideas in future PRs. |
Yes, this PR ensures a single thread handles drawing all figures together for a specific step, ensuring synchronization.
Noted, currently even if the play interval is set, the model doesn’t always visualize it correctly, and the user has to manually adjust it leading to poor responsiveness.I'll start working on this and create a new PR to handle this issue. |
You're in luck, my knee hurts a little so I need to take some more breaks between skiing and I quickly looked through the code. I appreciate the detailed approach to decoupling model steps from visualization updates. Separating these concerns should help improve responsiveness during simulations. However, I have some concerns regarding the mix of threading and asyncio constructs, which might complicate maintenance and lead to subtle concurrency issues. My main worries are about the unnecessary use of asyncio event loops within threads and potential race conditions when updating shared model state. The duplicated logic between ModelController and SimulatorController also adds complexity, and I’m not entirely convinced that the current implementation guarantees thread safety—especially when forcing UI updates from background threads. To move forward, please consider the following specific points:
Hope it helps. I think the goal of this PR should be to implement is both simple and scalable, so we have foundation to build on this. Let me know if you have any questions. I'm Sunday in an long distance train for 7 hours, so I can look at it again then. Thanks for your work on it so far! |
Hey, sorry for the late reply! Just to clarify regarding thread safety—are you more concerned about multiple threads accessing model value at the same time (which could cause inconsistent state in model.value.step() and simulator.run_for()), or is the main issue updating the UI from a background thread? For the first one, I haven’t come across any issues where step value gets into an unsafe state with the current code, but if there's a potential risk, I can add a lock, queue, etc. or make it bidirectional (whichever works best) to ensure safety. If it’s about UI updates, I was thinking of using a reactive variable and then triggering updates in use_effect on the main thread instead of updating directly from a background thread. Let me know what you think! |
Thanks for getting back! My comment wasn't primarily about thread safety of model state or UI updates. The main concern is that you're creating asyncio event loops inside threads without using any async/await functionality there. For example, in your step function: def step():
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
while running.value and playing.value:
time.sleep(play_interval.value / 1000)
do_step()
if use_threads.value:
visualization_pause_event.set()
except Exception as e:
print(f"Error in step: {e}")
return
finally:
loop.close() You create and set an asyncio event loop, but then only use regular blocking calls like I'd suggest simplifying by sticking to pure threading. The loop creation, error handling and cleanup could be much simpler that way. cc @quaquel - let us know if you have a take on this. |
Understood, will change it right away. |
Thanks! I will let another maintainer with broader knowledge of the visualisation stack do the final review. |
mesa/visualization/solara_viz.py
Outdated
solara.Checkbox( | ||
label="Use Threads", | ||
value=reactive_use_threads, | ||
on_value=lambda v: reactive_use_threads.set(v), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@HMNS19 Thanks for the PR!
I will leave the more focused review to @quaquel and @Corvince, however if possible I like to avoid lambda
functions in Mesa as they are generally viewed as less readable, harder to read in the traceback and create issues with serialization. As this is part of the visual I appreciate the last 2 are less of a concern
However a simple function is also fairly easy to add
def set_reactive_use_threads(value):
reactive_use_threads.set(value)
solara.Checkbox(
label="Use Threads",
value=reactive_use_threads,
on_value=set_reactive_use_threads,
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback! I’ve replaced the lambda with a named function as you suggested. Let me know if there’s anything else to improve. Also curious—would lambda functions in the play_interval and render_interval sliders have similar concerns?
Summary
This PR introduces a thread-based mechanism to handle model updates and visualization rendering concurrently. It ensures smooth execution of simulations by separating model step execution and visualization updates into independent threads, improving performance and responsiveness during simulations.
Fixes #2604
Motive
Previously, the visualization process could become a bottleneck during rapid simulations, as rendering was tightly coupled with model updates. This caused delays and UI responsiveness issues. By separating these operations into threads, the model execution is no longer hindered by the visualization process.
Implementation
Handles visualization updates triggered by threading.Event (visualization_pause_event) to synchronize rendering with model steps.
Implemented visualization_pause_event to signal the visualization thread after each model step is completed, ensuring rendering happens efficiently without blocking the simulation.
Usage Examples
Play Button Behavior: The play button now ensures the simulation continues smoothly.
Concurrent Rendering: Visualization and simulation run in parallel, reducing delays.
Before

After

Additional Notes