1

I recently discovered, that my scala swing applications behave very strangely. The following test application draws a line which moves over the screen. When I ran the program the first time I was shocked on how laggy swing seems to be, since the line doesnt move smoothely in the least. BUT as soon as swing seems to recognize events, triggered by for example a mouse-hover or pressed keys, everything seems to run smoothly and as expected. However as soon as the mouse leaves the swing window,or no more keys are pressed swing is laggy again. I am not working on a slow machine and since I dont have similar problems with python or so I still think that the scala.swing library seems to very bad, to me. Is there a major mistake I am not seeing here? Am I using scala.swing incorrectly? What is it, that makes scala.swing so laggy but only ever when there are no events triggered by the user?

Here is the very small test app I have created. Please copy it and try it yourself.

object PerformanceTest {

  def main(args:Array[String]): Unit ={
    var i =0
    val (x1, y1) = (0,0)
    val (x2, y2) = (400,300)

    val frame = new MainFrame{
      title = "performance test"
      centerOnScreen()
      contents = new BorderPanel{
        layout += new Panel{
          override def paint(g:Graphics2D): Unit ={
            g.drawLine(x1+i, y1,x2-i, y2)
          }
        } -> BorderPanel.Position.Center
        listenTo()
      }
      size = new Dimension(400, 300)
      visible = true
    }


    while(true){
      frame.repaint()
      i += 1
      Thread.sleep(20)
    }
  }

}
Julian
  • 525
  • 5
  • 19
  • Did you consider EDT rules (about threading)? This code looks like one glaring EDT violation. – Rekin Feb 16 '16 at 15:15
  • After yout comment i read a little about EDT. Seems like i actually do violate a lot of stuff i dont even know about yet. I have neither invoked the swing ui by any edt methode like invokeLater (or so i found), nor have i used any threaded methode to process the events like akka. According to you can I suggest that this 'laggy' behaviour will most likely be fixed as soon as I learn to use edt right? And if so, can you advice me some tutorial for it? I can only find java specific documentation on edt and methods like invokeLater dont exist in scala... or do they? – Julian Feb 16 '16 at 15:36
  • Well, I know nothing of Scala+Swing, but I've used Scala and rxScala. That library was actually like a godsend, when it comes to threading. I remember just putting a single method "subscribeOnEdt" and everything was set up behind the scenes. Very liberating. I'd hazard a guess that the normal Scala Swing library does provide some wrapper code for the EDT, since it's the very first thing to take care of in the Swing realm. – Rekin Feb 16 '16 at 15:57

1 Answers1

2

Are you by any chance seeing this on Linux? I have discovered a refresh bug in the JDK Linux recently that sounds like the same thing. See this question.

So what I did is call .toolkit.sync(). This is kind of horrible because it syncs the entire windowing system as far as I understand, while you actually just want to sync one individual component. But I haven't found any better workaround yet.

Also note that you should probably just refresh the panel and not the frame. Then you'll have to fill the background rectangle or declare it non-opaque. You should also not create the frame outside of the event dispatch thread. Instead of waiting on the main thread with Thread.sleep, a better approach is to use a Swing timer. With these changes I get:

object PerformanceTest {
  import scala.swing._

  def main(args: Array[String]): Unit = Swing.onEDT /* ! */ {
    var i = 0
    val (x1, y1) = (0,0)
    val (x2, y2) = (400,300)

    val panel = new Panel {
      opaque = false  // !
      override def paint(g: Graphics2D): Unit =
        g.drawLine(x1 + i, y1, x2 - i, y2)
    }

    lazy val frame = new Frame {
      title = "performance test"
      contents = new BorderPanel {
        layout += panel -> BorderPanel.Position.Center
      }
      size = new Dimension(400, 300)
      centerOnScreen()

      override def closeOperation(): Unit = {
        timer.stop()
        dispose()
      }
    }

    lazy val timer: javax.swing.Timer = new javax.swing.Timer(20,
      Swing.ActionListener { _ =>
        i = (i + 1) % 400
        panel.repaint()
        panel.toolkit.sync()  // linux bug?
      }
    )

    timer.start()
    frame.open()
  }
}

PerformanceTest.main(null) // test
0__
  • 66,707
  • 21
  • 171
  • 266
  • yes, my operating system is elementary os that actually really sounds exactly like the problem i am having! – Julian Feb 16 '16 at 17:51