4

So I'm writing a boid simulation program as a project for school. My program supports multiple different groups of these boids that don't flock with other groups, they all have different settings which I do by adding a BoxPanel to the main GUI of the program when a new tribe is made, and those BoxPanels have a settings button that opens a new frame with the groups settings.

This works perfectly when I start the program up and add all the pre defined tribes that are in the code. Now I made a new part of the GUI that let's you make new groups of these boids and adds them while the simulation is running, and here is when the problems start for me.

For some weird reason it adds the group just fine, with the right settings in to the simulation but it wont add the BoxPanels to the main GUI. It makes the whole settings bar that I have in the side of my simulation disappear completely. I tested this out and if I add the tribes in the beginning of my calculation thread it does the same thing, so this seems to be a problem with multiple threads and swing. Any ideas what is causing this or how to fix this? I am completely perplexed by this.

tl;dr: The code below for adding tribes works fine when I haven't started the thread but if I try to use it after starting the thread the optionPanel appears empty.

Here's the code that adds the BoxPanels to the main gui:

      def addTribe(tribe: Tribe) = {
        tribeFrames += new TribeSettingFrame(tribe)
        tribeBoxPanels += new TribeBoxPanel(tribe)
        this.refcontents
      }

      private def refcontents = {
        top.optionPanel.contents.clear()
        top.optionPanel.contents += new BoxPanel(Orientation.Vertical) {
          tribeBoxPanels.foreach(contents += _.tribeBoxPanel)
        }
        top.optionPanel.contents += new BoxPanel(Orientation.Horizontal) {
          contents += top.addTribeButton
        }
        top.optionPanel.contents += new BoxPanel(Orientation.Horizontal) {
          contents += top.vectorDebugButton
        }
      }


  new Thread(BoidSimulation).start()

Oh and I tested if it really adds the contents that it should by printing out the sizes of the contents, and everything matches fine, it just won't draw them.

EDIT: After some digging around it really seems to be a thing with updating swing from a Thread. A lot of places suggest to use SwingWorker but from the info I gathered about it I don't think it would fit in my program since it is a continuous simulation and and I would have to keep making new SwingWorkers every frame.

EDIT2: Tried calling the method from the thread like this:

SwingUtilities.invokeLater(new Runnable() {
  override def run() {
    GUI2D.addTribe(tribe)
  }
});

Didn't make any difference. I am starting to think that this is a problem with how I use TribeBoxPanel and TribeSettingFrame. These are objects that both contain only one method that returns the wanted BoxPanel or Frame. Is this implementation bad? If so what is the better way of creating dynamic BoxPanels and Frames?

RusinaRange
  • 341
  • 1
  • 4
  • 18

2 Answers2

0

Swing is not thread-safe.

Repeat after me.

Swing is not thread-safe.

Hear the chorus? Swing is not thread safe There is official documentation.

There is a very simple workaround given as well.

SwingUtilities.invokeLater(new Runnable() {
    @Override public void run() {
        // your stuff
    }
});

In Scala, this is supported as:

Swing.invokeLater(/* your stuff */)
Community
  • 1
  • 1
Bob Dalgleish
  • 8,167
  • 4
  • 32
  • 42
  • Thanks for your answer! That doesn't seem to work though, I tried putting the code that modifies the GUI in there and it still freezes.. I am starting to think this is the way I implemented TribeSettingFrame and TribeBoxPanel, these are just objects with a definition that returns the wanted BoxPanel or Frame, could it be that these aren't processed correctly when in a thread? I am testing this out now. – RusinaRange Mar 06 '15 at 13:48
0

First you should let the UI thread handle all UI manipulation. The simple way should be following Scala-Code:

Swing.onEDT { GUI2D.addTribe(tribe) }

But as you already noted, this won't solve your problem. I had a very similar problem where I only changed the text content of a Swing.Label and it sometimes simply disappeared.

It turned out that it only disappeared, when the text was too long to display it inside the Area which the Label initially reserved for itself. So one way around your Problem could be to give the optionPanel a bigger initial size when you create it.

Swing.onEDT { top.optionPanel.preferredSize = new Dimension(width,height) }

I'm not quite sure whether this has to be set before the component is first drawn (before Frame.open() is called).

lSoleyl
  • 309
  • 1
  • 7