views:

118

answers:

2

The program I have visualizes a physics simulation (basically). Right now, it works, but can get very unresponsive, and I think I know why - too much (read:all) computation is done on the event thread.

When the 'play' button is pressed, a Swing Timer is created that wakes up periodically and calls updateTime() - so far so good. The problem is that updateTime() iterates through every time-dependent object and tells it to propagate itself forwards in time by the appropriate amount (either the real time elapsed, or an arbitrary time unit each tick). These calculations, and the subsequent GUI updates, are all on the event dispatch thread.

So, I'd like to offload as much of this computation as possible, and I think SwingWorkers are the way to go, but I'm not sure how to work them into the existing code. I can't have the existing classes extend SwingWorker since many of them already extend other classes.

My best idea so far is to create an interface for each time-dependent object to implement. The interface would specify 2 methods, calcUpdateTime() and drawUpdateTime(). I would split up each of their current updateTime() methods into the physics calculations (into calc_) and GUI updates (into draw_). Then I would create only one class of SwingWorker that takes a TimeDependant object in the constructor, and its doInBackground would call calcUpdateTime and done would call drawUpdateTime. So then I can replace all occurrences of

myObj.updateTime(currentTime);

with

new MySwingWorker(myObj, currentTime).execute();

I wanted to run this idea by SO because it doesn't feel quite right, and it would like to avoid refactoring the entire project just to find out I started out with a bad idea. Also, isn't it probably a bad idea to create potentially dozens of MySwingWorkers each tick?

Thanks for reading this far.

A: 

I've had poor experiences (performance-wise) using the Swing timer. Since all events across Swing use the same thread, it seems to have unexpected delays quite a lot.

I'd also suggest to trust your instincts about multiple swing worker instances every tick: it doesn't sound right to me either. (And according to the SwingWorker docs, it's designed to only be run once, so you can't necessarily reuse it safely).

A timer that might do what you need is the java.util.Timer. This has quite a lot of options for specifying timeouts, although depending on the fidelity of your timing simulation even this may not be appropriate. (For example: if you want it to run in real time, but your calculations actually take longer than real time, what should it do? Run slowly, or start skipping time steps?)

So, not knowing exactly what your calc/draw routines involve, I'd tentatively suggest trying a java.util.Timer. When it expires (after whatever timeout period you deem appropriate based on your "realtime multiplier"), run through all the calcs, and then throw the results back to the EDT thread to do the drawing (for example, by wrapping them up in a SwingUtilities.invokeLater()).

Of course, this maybe introduce locking issues if the EDT wants to references the same objects as the calculation thread. Ideally if the calc thread can pass immuatable results to the EDT, it would save having to introduce locks.

(A disclaimer: none of the above really takes into account multiple cores/processors either, other than one for GUI, one for calculations. If you want to parallelise the application, it's probably not an appropriate solution).

Ash
That looks promising, I'll try this and see how it works out. Re:multiple cores/processors, I didn't think the JVM took advantage of them (at least it didn't on my machine the last time I checked)
drhorrible
I've mainly used Sun's Windows JVM, and it will spread threads across multiple cores (not sure about processors though).
Ash
+2  A: 

You're right, it's not necessary to call SwingWorker.execute() for every worker for every tick, because you'll be creating and destroying a lot of threads that you really don't need.

However, using a SwingWorker is still a good idea, just because it gives you an easy way to separate the code that needs to be run in the background (your implementation of SwingWorker.doInBackground()) from the code that needs to be run in Swing to update the GUI afterwards (your implementation of SwingWorker.done()).

Rather than using javax.swing.Timer or java.util.Timer, I would recommend using java.util.concurrent.ScheduledThreadPoolExecutor. Basically, it can do everything that a java.util.Timer can do, except that it also gives you the opportunity to control how many threads are working in the background, how to handle exceptions that are thrown in background threads, and so forth.

Joe Carnahan