RusinaRange
RusinaRange

Reputation: 431

Scala.swing freezes when updating

So I'm actually not sure of what the problem really is. It propably relates to the way I'm handling my threads but I don't know how to fix this.

The program is a simple boid simulation with different "tribed" boids, and I want to have it so that when I make a new tribe it has it's own panel in the GUI. Now this works perfectly before I start the thread but after that it always freezes. I know that Swing is not thread safe but I'm not sure how to fix this problem.

Here's the code for adding the panels:

  val tribeBoxPanels = Buffer.empty[TribeBoxPanel]
  val tribeFrames = Buffer.empty[TribeSettingFrame]
  val addTribeFrame = ChooseTribeFrame.frame
  val addFlockingFrame = AddFlockingFrame.frame

  def addTribe(tribe: Tribe) = {
    pause()
    tribeFrames += new TribeSettingFrame(tribe)
    tribeBoxPanels += new TribeBoxPanel(tribe)
    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
    }
    pause()
  }

And heres the code for the (runnable) thread:

  private var running = true

  def pause() = {
    if (running) {
      running = false
      t.stop()
    }
    else {
      running = true
      t = new Thread(BoidSimulation)
      t.start()
    }
  }

  var t = new Thread(BoidSimulation)
  t.start()

Im trying to stop the thread while adding the tribe but that does not seem to work, the GUI still freezes. I also tried t.interrupt() (because that's the better way) but that didn't work also.

EDIT: Could my problem be that I am trying to call the method AddTribe from another object that is not GUI2D (the object the method is in). Maybe if I pasted all of my code to the GUI2D class it would work?

EDIT 2: Tried calling the method like this:

def invoke(tribe: Tribe) = new Runnable() { def run() = addTribe(tribe)  }

Didn't help though, the bar that I am trying to add stuff to still freezes.

I also tried printing out the thread that is calling the method and this is what I got:

Thread[Thread-2,5,main]                  <- println(t)

Thread[AWT-EventQueue-0,6,main]          <- This call works

Thread[AWT-EventQueue-0,6,main]          <- This call works

Thread[AWT-EventQueue-0,6,main]          <- This call works

Thread[AWT-EventQueue-0,6,main]          <- After this it freezes.

So the method is being called from the AWT thread but it still freezes the GUI. So the threading is not my problem?

Edit 3: I think I found my problem!! It's quite backwards actually. Because the method is called at a press of a button Swing was trying to complete code that is meant for the calculation thread, that is what caused it to freeze. Now I need to find out the excact opposite of SwingUtilities.invokelater()

Edit 4: Tried creating a new runnable that would run that code but it is somehow still called from the AWT thread. What is the cause of this and how do I get the code to run from the calculation thread?

class AddTribe(dist: Int, maxSpeed: Int, boidMass: Int, color: Color, behavior: Behavior, boidAmount: Int) extends Runnable {
    println(Thread.currentThread())
    def run() = BoidSimulation.addTribe(dist: Int, maxSpeed: Int, boidMass: Int, color: Color, behavior: Behavior, boidAmount: Int)
  }

case ButtonClicked(e) if (e == addButton) => {
        new AddTribe(distSlider.value, maxSpdSlider.value / 10, massSlider.value / 10, color, new Flocking(separationSlider.value, alignmentSlider.value, cohesionSlider.value), boidSlider.value).run()

Upvotes: 0

Views: 134

Answers (2)

lmm
lmm

Reputation: 17431

Calling .run() on a Runnable directly will just run it on the current thread. The simplest way to run it separately is to create a new Thread and start() it:

case ButtonClicked(e) if (e == addButton) =>
    new Thread(new AddTribeRunnable(...)).start()

Starting a new thread for each time the button is pressed is somewhat inefficient though; you might prefer to e.g. use a ThreadPoolExecutorService to manage a pool of threads. Or for a more advanced/high-performance technique you could look at using akka Actors.

Upvotes: 0

DNA
DNA

Reputation: 42597

The keys things to remember about Swing threads are that:

  • You should not do long-running calculations on the special AWT/Swing thread, or the GUI will freeze (because the Swing thread never gets a chance to process GUI events). Any activity triggered from the GUI (e.g. by an event handler on a button etc) will be running on the Swing thread by default. Put such activities into background threads if they will take a long time.
  • You must only update the GUI from the Swing thread. This typically means that background threads should use SwingUtilities.invokeLater() to add work to the queue for the Swing thread, or use the SwingWorker helper class.

See also the Java tutorials on these topics:

Update:

You can check what thread is running any given bit of code by adding debug statements such as:

println(Thread.currentThread)

or using debugger tools. It helps to give readable names to any threads that you create yourself, using the appropriate constructor.

Upvotes: 3

Related Questions