L’animazione dell’oscillazione è estremamente lenta

Ho un problema con la mia animazione corrente che sto usando Java Swing. Si tratta di una simulazione di eventi discreti e la simulazione basata sul testo sta funzionando bene, sto solo avendo problemi nel colbind l’output di simulazione a GUI.

Per questo esempio avrò 10 macchine da simulare. Le auto sono rappresentate da JPanels che illustrerò tra qualche istante.

Quindi considera, l’evento process_car_arrival. Ogni volta che questo evento è pianificato per l’esecuzione, aggiungo un object Car a un ArrayList chiamato cars nella mia class Model . La class Car ha i seguenti attributi rilevanti:

 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 

Inoltre ha il seguente metodo move() :

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

Questo è tutto a posto. quindi, in teoria, l’auto percorre una strada diritta da est a ovest mentre invoco il metodo move() per ogni auto che voglio spostare.

Ritorno all’evento process_car_arrival. Dopo aver aggiunto un object Car, invoca un metodo addCarToEast() nella class View . Questo aggiunge un JPanel all’inizio della strada che va da est a ovest.

Andando alla class View ora ho un ** thread ** separato che fa il seguente (il metodo 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++; } } } } 

Quanto sopra muove l’auto da est a ovest senza problemi all’inizio. Ma dopo che ci sono 3-4 macchine in movimento, finisce per essere ESTREMAMENTE lento e quando ho 10 macchine che lo muovono finisce solo per muoversi molto poco.

Giusto per chiarire, al momento nella class Model c’è un ArrayList di oggetti Car , e nella class View c’è anche un ArrayList di oggetti JPanel che rappresentano le auto. Sto cercando di abbinare gli oggetti della JPanels agli JPanels , ma ovviamente sto facendo un grosso lavoro.

Sospetto che sto facendo qualcosa di follemente inefficiente, ma non so cosa. All’inizio pensai che forse stava ArrayList così tanto che immagino lo renderebbe davvero lento.

Qualche suggerimento su cosa posso cambiare per farlo funzionare senza intoppi?

Sulla base di questa risposta precedente, l’esempio seguente simula una flotta di tre cabine che si muovono casualmente su una griglia rettangular. Un javax.swing.Timer guida l’animazione a 5 Hz. Il modello e la vista sono strettamente accoppiati in CabPanel , ma l’animazione può fornire alcuni spunti utili. In particolare, è ansible aumentare il numero di cabine o ridurre il ritardo del timer.

Immagine

 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 actionPerformsd(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 actionPerformsd(ActionEvent e) { Hue h = (Hue) colorBox.getSelectedItem(); CabPanel.this.setBackground(h.getColor()); update(); } }); reset.addActionListener(new ActionListener() { @Override public void actionPerformsd(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(); } }); } } 

Non ho potuto resistere …

inserisci la descrizione dell'immagine qui

Ho avuto 500 macchine in esecuzione sullo schermo con un piccolo rallentamento (non era il più veloce … circa 200-300 era abbastanza buono …

Questo utilizza pannelli per rappresentare ciascun veicolo. Se si desidera ottenere prestazioni migliori, è probabilmente necessario considerare l’utilizzo di un buffer di backup di qualche 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 actionPerformsd(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(); } } } 

Aggiornato con la versione ottimizzata

Ho fatto un po 'di ottimizzazione del codice con la creazione degli oggetti della macchina (c'è ancora spazio per miglioramenti) e ho modificato l'output grafico (fatto sembrare più bello).

Fondamentalmente, ora, quando un'auto lascia lo schermo, viene messa in una piscina. Quando è necessaria un'altra vettura, se ansible, viene tirata fuori dalla piscina, altrimenti viene prodotta una nuova auto. Ciò ha ridotto il sovraccarico di creazione e smistamento di così tanti oggetti (relativamente) di breve durata, rendendo l'utilizzo della memoria un po 'più stabile.

Sul mio schermo con risoluzione 2560x1600 (ingrandito al massimo), sono riuscito a far funzionare 4500 macchine contemporaneamente. Una volta che la creazione dell'object è stata ridotta, ha funzionato in modo relativamente fluido (non funzionerà mai come 10, ma non ha sofferto di una significativa riduzione della velocità).

 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 - Dovrei aggiungere 1- Il mio 10 mesi mi è piaciuto molto 2. Mi ha ricordato la corsa al lavoro: P