0

I'm doing a simulation program in Scala and I'm trying to render the simulation in a JPanel by overriding the paintComponent:

override def paintComponent(g: Graphics2D) = {
  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  super.paintComponent(g)
  tx1 = g.getTransform()
  g.setColor(new Color(0,0,0))
   simulator.getVehicles foreach{vehc =>
    g.translate(vehc.getPos.x,vehc.getPos.y)
    g.draw(new Ellipse2D.Double(-Vehicle.rad, -Vehicle.rad, Vehicle.diam, Vehicle.diam))
    g.drawLine(0,0,(Vehicle.rad*vehc.getDir.x).toInt,(Vehicle.rad*vehc.getDir.y).toInt)
    g.setTransform(tx1)
  }
}

I have the simulation itself running on a different thread:

def run{
//logic loop
 time = System.currentTimeMillis();
 dt = 1000/60
while(loop)
{
  getVehicles.foreach{
  _.move
  }
  collider.solvecollisions()
  Thread.sleep(dt- (time - System.currentTimeMillis()))
  time = System.currentTimeMillis();
}}

GetVehicles returns a Buffer[Vehicle] of all simulated vehicles.

My problem is that there is jittering in the rendering. What I mean is that sometimes some of the vehicles are rendered a timestep later than others. I presume this happens because the simulation loop updates the positions at the same time the render loop fetches the positions, and there is some overlap. i.e. when rendering begins in timestep n,half of the vehicles are rendered and then timestep n+1 happens and the rest of the vehicles are rendered a timestep further. First I thought this was a problem to be solved with double buffering, but since paintComponent already does that, I dont think thats the case. Any ideas how to fix this? I tried simply rendering getVehicles.clone but that didn't help because the references to the vehicles are still the same.

Thanks!

Ou Tsei
  • 470
  • 7
  • 24

1 Answers1

1

It appears that your vehicle models are mutable things (_.move). Then if simulation and painting run in different threads, it is no surprise that you don't get a consistent world view in Swing.

I can see the following solutions, depending on your requirements:

  • run the simulation updates on the event-dispatch-thread. Advantage: no need to change your code at all. Disadvantage: may render the GUI sluggish if simulation is heavy
  • create one global "world" lock to which you synchronize. Advantage: requires very little change to the code. Disadvantage: Unless GUI updates are at low rate, both simulation and rendering block each other. Might be useful if GUI updates are a fraction of simulation rate.
  • adopt an immutable model, and your simulation will then create one consistent updated world in each step. Advantage: rendering and simulations will automatically be consistent. Probably the fastest solution. Disadvantage: You need to rewrite your simulation. Probably the best solution.
  • Change your var mutable state to STM reference cells. Might work nicely if GUI rate is low compared to simulation rate, because then this "optimistic" approach might work with relatively few rollbacks. I'm not sure how this works out with Scala-STM and the renderer doing only read access. Perhaps you need a full multi-versioned STM to avoid rollbacks.

To outline the immutable variant:

trait Vehicle {
  def move: Vehicle // return updated model
}

trait Collisions {
  def solve(in: Seq[Vehicle]): Seq[Vehicle] // return corrected models
}

trait World {
  def vehicles: Seq[Vehicle]
}

trait Simulator {
  protected def coll: Collisions

  // create updated world
  def run(prev: World): World = new World {
    val vehicles = coll.solve(prev.vehicles.map(_.move))
  }
}
0__
  • 66,707
  • 21
  • 171
  • 266
  • The immutable solution sounds feasible, but I wonder how much it affects the performance since you have to create a lot of new objects each step. – Ou Tsei Feb 26 '15 at 20:33
  • 1
    The effort of creating new objects is often overestimated. The JVM is actually quite good handling a lot of short-lived objects that arise from a more functional style of programming. Also mutations of immutable data structures have a high degree of structural sharing, i.e. if you append an element that doesn't imply a complete copy must be made. – 0__ Feb 27 '15 at 10:06