Animación de swing muy lenta

Tengo un problema con mi animación actual que estoy ejecutando usando Java Swing. Es una simulación de eventos discretos y la simulación basada en texto está funcionando bien, solo estoy teniendo problemas para conectar la simulación a la salida de GUI.

Para este ejemplo, tendré que simular 10 autos. Los automóviles están representados por JPanels que elaboraré en unos momentos.

Así que considere, el evento process_car_arrival. Cada vez que se progtwig la ejecución de este evento, agrego un objeto Car a una ArrayList llamada cars en mi clase Model . La clase Car tiene los siguientes atributos relevantes:

 Point currentPos; // The current position, initialized in another method when knowing route. double speed; // giving the speed any value still causes the same problem but I have 5 atm. RouteType route; // for this example I only consider one simple route 

Además tiene el siguiente método move() :

 switch (this.route) { case EAST: this.currentPos.x -= speed; return this.currentPos; . . . //only above is relevant in this example 

Esto está todo bien. así que, en teoría, el automóvil atraviesa un camino recto de este a oeste, ya que solo invoco el método move() para cada automóvil que quiero mover.

Volviendo al evento process_car_arrival. Después de agregar un objeto Car, invoca un método addCarToEast() en la clase View . Esto agrega un JPanel al comienzo de la carretera que va de este a oeste.

Yendo a la clase View ahora tengo una secuencia ** separada ** que hace lo siguiente (el método run ()):

 @Override public void run() { while (true) { try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } if (!cars.isEmpty()) { cars.get(i).setLocation( new Point(getModel.getCars().get(i).move())); if (i == cars.size() - 1) { i = 0; } else { i++; } } } } 

Lo anterior mueve el automóvil de este a oeste suavemente al principio. Pero después de que haya 3-4 autos en movimiento, esto acaba siendo MUY lento y cuando tengo 10 autos en movimiento simplemente termina moviéndose muy poco.

Para aclarar, en este momento en la clase Model hay una ArrayList of Car objects, y en la clase View también hay una ArrayList de objetos JPanel que representan los autos. Estoy tratando de JPanels los objetos de Car con los JPanels , pero obviamente estoy haciendo un trabajo de JPanels .

Sospecho que estoy haciendo algo terriblemente ineficiente, pero no sé qué. Inicialmente pensé que tal vez está accediendo tanto a ArrayList lo que supongo que lo haría realmente lento.

¿Alguna sugerencia sobre lo que puedo cambiar para que funcione sin problemas?

Según esta respuesta anterior, el ejemplo siguiente simula una flota de tres cabinas que se mueven aleatoriamente en una cuadrícula rectangular. Un javax.swing.Timer conduce la animación a 5 Hz. El modelo y la vista están estrechamente acoplados en CabPanel , pero la animación puede proporcionar algunas ideas útiles. En particular, puede boost el número de cabinas o disminuir el retraso del temporizador.

imagen

 import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Point; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.Timer; /** * @see https://stackoverflow.com/a/14887457/230513 * @see https://stackoverflow.com/questions/5617027 */ public class FleetPanel extends JPanel { private static final Random random = new Random(); private final MapPanel map = new MapPanel(); private final JPanel control = new JPanel(); private final List fleet = new ArrayList(); private final Timer timer = new Timer(200, null); public FleetPanel() { super(new BorderLayout()); fleet.add(new CabPanel("Cab #1", Hue.Cyan)); fleet.add(new CabPanel("Cab #2", Hue.Magenta)); fleet.add(new CabPanel("Cab #3", Hue.Yellow)); control.setLayout(new GridLayout(0, 1)); for (CabPanel cp : fleet) { control.add(cp); timer.addActionListener(cp.listener); } this.add(map, BorderLayout.CENTER); this.add(control, BorderLayout.SOUTH); } public void start() { timer.start(); } private class CabPanel extends JPanel { private static final String format = "000000"; private final DecimalFormat df = new DecimalFormat(format); private JLabel name = new JLabel("", JLabel.CENTER); private Point point = new Point(); private JLabel position = new JLabel(toString(point), JLabel.CENTER); private int blocks; private JLabel odometer = new JLabel(df.format(0), JLabel.CENTER); private final JComboBox colorBox = new JComboBox(); private final JButton reset = new JButton("Reset"); private final ActionListener listener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int ds = random.nextInt(3) - 1; if (random.nextBoolean()) { point.x += ds; } else { point.y += ds; } blocks += Math.abs(ds); update(); } }; public CabPanel(String s, Hue hue) { super(new GridLayout(1, 0)); name.setText(s); this.setBackground(hue.getColor()); this.add(map, BorderLayout.CENTER); for (Hue h : Hue.values()) { colorBox.addItem(h); } colorBox.setSelectedIndex(hue.ordinal()); colorBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Hue h = (Hue) colorBox.getSelectedItem(); CabPanel.this.setBackground(h.getColor()); update(); } }); reset.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { point.setLocation(0, 0); blocks = 0; update(); } }); this.add(name); this.add(odometer); this.add(position); this.add(colorBox); this.add(reset); } private void update() { position.setText(CabPanel.this.toString(point)); odometer.setText(df.format(blocks)); map.repaint(); } private String toString(Point p) { StringBuilder sb = new StringBuilder(); sb.append(Math.abs(px)); sb.append(px < 0 ? " W" : " E"); sb.append(", "); sb.append(Math.abs(py)); sb.append(py < 0 ? " N" : " S"); return sb.toString(); } } private class MapPanel extends JPanel { private static final int SIZE = 16; public MapPanel() { this.setPreferredSize(new Dimension(32 * SIZE, 32 * SIZE)); this.setBackground(Color.lightGray); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int w = this.getWidth(); int h = this.getHeight(); g2d.setColor(Color.gray); for (int col = SIZE; col <= w; col += SIZE) { g2d.drawLine(col, 0, col, h); } for (int row = SIZE; row <= h; row += SIZE) { g2d.drawLine(0, row, w, row); } for (CabPanel cp : fleet) { Point p = cp.point; int x = SIZE * (px + w / 2 / SIZE) - SIZE / 2; int y = SIZE * (py + h / 2 / SIZE) - SIZE / 2; g2d.setColor(cp.getBackground()); g2d.fillOval(x, y, SIZE, SIZE); } } } public enum Hue { Cyan(Color.cyan), Magenta(Color.magenta), Yellow(Color.yellow), Red(Color.red), Green(Color.green), Blue(Color.blue), Orange(Color.orange), Pink(Color.pink); private final Color color; private Hue(Color color) { this.color = color; } public Color getColor() { return color; } } private static void display() { JFrame f = new JFrame("Dispatch"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); FleetPanel fp = new FleetPanel(); f.add(fp); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); fp.start(); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { display(); } }); } } 

No pude resistir …

enter image description here

Tengo 500 autos corriendo en la pantalla con poca velocidad (no fue el más rápido … aproximadamente 200-300 fue bastante bueno …

Esto usa paneles para representar cada vehículo. Si desea obtener un mejor rendimiento, es probable que deba considerar el uso de un buffer de respaldo de algún tipo.

 public class TestAnimation10 { public static void main(String[] args) { new TestAnimation10(); } public TestAnimation10() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ex) { } final TrackPane trackPane = new TrackPane(); JSlider slider = new JSlider(1, 500); slider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { trackPane.setCongestion(((JSlider)e.getSource()).getValue()); } }); slider.setValue(5); JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(trackPane); frame.add(slider, BorderLayout.SOUTH); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TrackPane extends JPanel { private List cars; private int maxCars = 1; private List points; private Ellipse2D areaOfEffect; public TrackPane() { points = new ArrayList<>(25); cars = new ArrayList<>(25); setLayout(null); Timer timer = new Timer(40, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Rectangle bounds = areaOfEffect.getBounds(); List tmp = new ArrayList<>(cars); for (Car car : tmp) { car.move(); if (!bounds.intersects(car.getBounds())) { remove(car); cars.remove(car); } } updatePool(); repaint(); } }); timer.setRepeats(true); timer.setCoalesce(true); timer.start(); updateAreaOfEffect(); } protected void updateAreaOfEffect() { double radius = Math.max(getWidth(), getHeight()) * 1.5d; double x = (getWidth() - radius) / 2d; double y = (getHeight() - radius) / 2d; areaOfEffect = new Ellipse2D.Double(x, y, radius, radius); } @Override public void invalidate() { super.invalidate(); updateAreaOfEffect(); } protected void updatePool() { while (cars.size() < maxCars) { // if (cars.size() < maxCars) { Car car = new Car(); double direction = car.getDirection(); double startAngle = direction - 180; double radius = areaOfEffect.getWidth(); Point2D startPoint = getPointAt(radius, startAngle); int cx = getWidth() / 2; int cy = getHeight() / 2; double x = cx + (startPoint.getX() - car.getWidth() / 2); double y = cy + (startPoint.getY() - car.getHeight() / 2); car.setLocation((int)x, (int)y); Point2D targetPoint = getPointAt(radius, direction); points.add(new Point2D[]{startPoint, targetPoint}); add(car); cars.add(car); } } @Override public void paint(Graphics g) { super.paint(g); Font font = g.getFont(); font = font.deriveFont(Font.BOLD, 48f); FontMetrics fm = g.getFontMetrics(font); g.setFont(font); g.setColor(Color.RED); String text = Integer.toString(maxCars); int x = getWidth() - fm.stringWidth(text); int y = getHeight() - fm.getHeight() + fm.getAscent(); g.drawString(text, x, y); text = Integer.toString(getComponentCount()); x = getWidth() - fm.stringWidth(text); y -= fm.getHeight(); g.drawString(text, x, y); text = Integer.toString(cars.size()); x = getWidth() - fm.stringWidth(text); y -= fm.getHeight(); g.drawString(text, x, y); } @Override public Dimension getPreferredSize() { return new Dimension(400, 400); } public void setCongestion(int value) { maxCars = value; } } protected static Point2D getPointAt(double radius, double angle) { double x = Math.round(radius / 2d); double y = Math.round(radius / 2d); double rads = Math.toRadians(-angle); double fullLength = Math.round((radius / 2d)); double xPosy = (Math.cos(rads) * fullLength); double yPosy = (Math.sin(rads) * fullLength); return new Point2D.Double(xPosy, yPosy); } public class Car extends JPanel { private double direction; private double speed; private BufferedImage background; public Car() { setOpaque(false); direction = Math.random() * 360; speed = 5 + (Math.random() * 10); int image = 1 + (int) Math.round(Math.random() * 5); try { String name = "/Car0" + image + ".png"; background = ImageIO.read(getClass().getResource(name)); } catch (IOException ex) { ex.printStackTrace(); } setSize(getPreferredSize()); // setBorder(new LineBorder(Color.RED)); } public void setDirection(double direction) { this.direction = direction; revalidate(); repaint(); } public double getDirection() { return direction; } public void move() { Point at = getLocation(); at.x += (int)(speed * Math.cos(Math.toRadians(-direction))); at.y += (int)(speed * Math.sin(Math.toRadians(-direction))); setLocation(at); } @Override public Dimension getPreferredSize() { Dimension size = super.getPreferredSize(); if (background != null) { double radian = Math.toRadians(direction); double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian)); int w = background.getWidth(), h = background.getHeight(); int neww = (int) Math.floor(w * cos + h * sin); int newh = (int) Math.floor(h * cos + w * sin); size = new Dimension(neww, newh); } return size; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); int x = (getWidth() - background.getWidth()) / 2; int y = (getHeight() - background.getHeight()) / 2; g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2); g2d.drawImage(background, x, y, this); g2d.dispose(); // Debug graphics... // int cx = getWidth() / 2; // int cy = getHeight() / 2; // // g2d = (Graphics2D) g.create(); // g2d.setColor(Color.BLUE); // double radius = Math.min(getWidth(), getHeight()); // Point2D pointAt = getPointAt(radius, direction); // g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius)); // // double xo = cx; // double yo = cy; // double xPos = cx + pointAt.getX(); // double yPos = cy + pointAt.getY(); // // g2d.draw(new Line2D.Double(xo, yo, xPos, yPos)); // g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4)); // g2d.dispose(); } } } 

Actualizado con versión optimizada

Hice un poco de optimización de código con la creación de los objetos del automóvil (todavía hay margen de mejora) y supere la salida de gráficos (hizo que se vea mejor).

Básicamente, ahora, cuando un automóvil deja la pantalla, se coloca en una piscina. Cuando se requiere otro automóvil, si es posible, se saca de la piscina; de lo contrario, se fabrica un automóvil nuevo. Esto ha reducido la sobrecarga de crear y desechar tantos objetos (relativly) de corta duración, lo que hace que el uso de la memoria sea un poco más estable.

En mi pantalla de resolución 2560x1600 (corriendo maximizada), pude hacer que 4500 autos funcionaran simultáneamente. Una vez que se redujo la creación de objetos, se ejecutó con relativa facilidad (nunca se ejecutará tan bien como 10, pero no sufrió una reducción significativa de la velocidad).

 public class TestAnimation10 { public static void main(String[] args) { new TestAnimation10(); } public TestAnimation10() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ex) { } final TrackPane trackPane = new TrackPane(); JSlider slider = new JSlider(1, 5000); slider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { trackPane.setCongestion(((JSlider) e.getSource()).getValue()); } }); slider.setValue(5); JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(trackPane); frame.add(slider, BorderLayout.SOUTH); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TrackPane extends JPanel { private List activeCarList; private List carPool; private int maxCars = 1; private List points; private Ellipse2D areaOfEffect; public TrackPane() { points = new ArrayList<>(25); activeCarList = new ArrayList<>(25); carPool = new ArrayList<>(25); setLayout(null); Timer timer = new Timer(40, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Rectangle bounds = areaOfEffect.getBounds(); List tmp = new ArrayList<>(activeCarList); for (Car car : tmp) { car.move(); if (!bounds.intersects(car.getBounds())) { remove(car); activeCarList.remove(car); carPool.add(car); } } updatePool(); repaint(); } }); timer.setRepeats(true); timer.setCoalesce(true); timer.start(); updateAreaOfEffect(); } protected void updateAreaOfEffect() { double radius = Math.max(getWidth(), getHeight()) * 1.5d; double x = (getWidth() - radius) / 2d; double y = (getHeight() - radius) / 2d; areaOfEffect = new Ellipse2D.Double(x, y, radius, radius); } @Override public void invalidate() { // super.invalidate(); updateAreaOfEffect(); } protected void updatePool() { if (activeCarList.size() < maxCars) { int count = Math.min(maxCars - activeCarList.size(), 10); for (int index = 0; index < count; index++) { Car car = null; if (carPool.isEmpty()) { car = new Car(); } else { car = carPool.remove(0); } double direction = car.getDirection(); double startAngle = direction - 180; double radius = areaOfEffect.getWidth(); Point2D startPoint = getPointAt(radius, startAngle); int cx = getWidth() / 2; int cy = getHeight() / 2; double x = cx + (startPoint.getX() - car.getWidth() / 2); double y = cy + (startPoint.getY() - car.getHeight() / 2); car.setLocation((int) x, (int) y); Point2D targetPoint = getPointAt(radius, direction); points.add(new Point2D[]{startPoint, targetPoint}); add(car); activeCarList.add(car); } } } @Override public void paint(Graphics g) { super.paint(g); Font font = g.getFont(); font = font.deriveFont(Font.BOLD, 48f); FontMetrics fm = g.getFontMetrics(font); g.setFont(font); g.setColor(Color.RED); String text = Integer.toString(maxCars); int x = getWidth() - fm.stringWidth(text); int y = getHeight() - fm.getHeight() + fm.getAscent(); g.drawString(text, x, y); text = Integer.toString(getComponentCount()); x = getWidth() - fm.stringWidth(text); y -= fm.getHeight(); g.drawString(text, x, y); text = Integer.toString(activeCarList.size()); x = getWidth() - fm.stringWidth(text); y -= fm.getHeight(); g.drawString(text, x, y); text = Integer.toString(carPool.size()); x = getWidth() - fm.stringWidth(text); y -= fm.getHeight(); g.drawString(text, x, y); } @Override public Dimension getPreferredSize() { return new Dimension(400, 400); } public void setCongestion(int value) { maxCars = value; } @Override public void validate() { } @Override public void revalidate() { } // @Override // public void repaint(long tm, int x, int y, int width, int height) { // } // // @Override // public void repaint(Rectangle r) { // } // public void repaint() { // } @Override protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { System.out.println(propertyName); // // Strings get interned... // if (propertyName == "text" // || propertyName == "labelFor" // || propertyName == "displayedMnemonic" // || ((propertyName == "font" || propertyName == "foreground") // && oldValue != newValue // && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) { // // super.firePropertyChange(propertyName, oldValue, newValue); // } } @Override public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { } } protected static Point2D getPointAt(double radius, double angle) { double x = Math.round(radius / 2d); double y = Math.round(radius / 2d); double rads = Math.toRadians(-angle); double fullLength = Math.round((radius / 2d)); double xPosy = (Math.cos(rads) * fullLength); double yPosy = (Math.sin(rads) * fullLength); return new Point2D.Double(xPosy, yPosy); } public class Car extends JPanel { private double direction; private double speed; private BufferedImage background; public Car() { setOpaque(false); direction = Math.random() * 360; speed = 5 + (Math.random() * 10); int image = 1 + (int) Math.round(Math.random() * 5); try { String name = "/Car0" + image + ".png"; background = ImageIO.read(getClass().getResource(name)); } catch (IOException ex) { ex.printStackTrace(); } setSize(getPreferredSize()); // setBorder(new LineBorder(Color.RED)); } public void setDirection(double direction) { this.direction = direction; revalidate(); repaint(); } public double getDirection() { return direction; } public void move() { Point at = getLocation(); at.x += (int) (speed * Math.cos(Math.toRadians(-direction))); at.y += (int) (speed * Math.sin(Math.toRadians(-direction))); setLocation(at); } @Override public Dimension getPreferredSize() { Dimension size = super.getPreferredSize(); if (background != null) { double radian = Math.toRadians(direction); double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian)); int w = background.getWidth(), h = background.getHeight(); int neww = (int) Math.floor(w * cos + h * sin); int newh = (int) Math.floor(h * cos + w * sin); size = new Dimension(neww, newh); } return size; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); int x = (getWidth() - background.getWidth()) / 2; int y = (getHeight() - background.getHeight()) / 2; g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2); g2d.drawImage(background, x, y, this); g2d.dispose(); // Debug graphics... // int cx = getWidth() / 2; // int cy = getHeight() / 2; // // g2d = (Graphics2D) g.create(); // g2d.setColor(Color.BLUE); // double radius = Math.min(getWidth(), getHeight()); // Point2D pointAt = getPointAt(radius, direction); // g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius)); // // double xo = cx; // double yo = cy; // double xPos = cx + pointAt.getX(); // double yPos = cy + pointAt.getY(); // // g2d.draw(new Line2D.Double(xo, yo, xPos, yPos)); // g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4)); // g2d.dispose(); } @Override public void invalidate() { } @Override public void validate() { } @Override public void revalidate() { } @Override public void repaint(long tm, int x, int y, int width, int height) { } @Override public void repaint(Rectangle r) { } @Override public void repaint() { } @Override protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { // System.out.println(propertyName); // // Strings get interned... // if (propertyName == "text" // || propertyName == "labelFor" // || propertyName == "displayedMnemonic" // || ((propertyName == "font" || propertyName == "foreground") // && oldValue != newValue // && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) { // // super.firePropertyChange(propertyName, oldValue, newValue); // } } @Override public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { } } } 

ps - Debo añadir 1- Mi hijo de 10 meses me encantó 2- Me recordó la carrera al trabajo: P