3

Invoke javax.swing.Timer#start() same time,

7u25 is not problem.

enter image description hereenter image description here

but 7u40 is big problem.

enter image description hereenter image description here

Too laggy invoke ActionListener#actionPerformed. (basically same time invoke u25)

Totally different move between u25 and u40. (I use Windows 8) I bug report but still not add bug tracking system. Oracle crushing swing apps?

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class TimerProblem extends JComponent {

        int red = 0;

        TimerProblem(final long startMs) {
                setPreferredSize(new Dimension(10, 10));

                Timer t = new Timer(16, new ActionListener() {

                        @Override
                        public void actionPerformed(ActionEvent e) {
                                red = (int)(System.currentTimeMillis() - startMs) % 255;
                                repaint();
                        }

                });
                t.setInitialDelay(1000);
                t.start();
        }

        @Override
        protected void paintComponent(Graphics g) {
                g.setColor(new Color(red, 255 - red, 0));
                g.fillRect(0, 0, getWidth(), getHeight());
        }

        public static void main(String[] args) {
                JFrame f = new JFrame();
                Container c = f.getContentPane();

                c.setLayout(new GridLayout(10, 10));
                long startMs = System.currentTimeMillis();
                for (int i = 0; i < 100; i++) {
                        c.add(new TimerProblem(startMs));
                }
                f.pack();
                f.setVisible(true);
        }

}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • 1
    +1 Oracle crushing swing apps? looks like as there are two important things security whatever and prerelease for java8 then nobody knows now whats is or isn't, whats bug or feature, btw can you please [to test if caused on your PC(I haven't issue with)](http://stackoverflow.com/questions/18918367/java-jpopupmenu-bug) – mKorbel Sep 21 '13 at 15:08
  • better is to test with standard refresh 25/33/40/50 – mKorbel Sep 21 '13 at 15:11
  • Just tested your code. Same issue. I even have problems to run one of my JavaFX application's. – Branislav Lazic Sep 21 '13 at 16:19
  • Runs fine in 1.7.0_40 in Linux, for what that's worth. Have you tried enclosing the entire body of the main method in `EventQueue.invokeLater` and have you tried adding `super.paintComponent(g);` to the start of your paintComponent method? Also, in Swing painting, you are supposed to restore the Graphics to its entry state when exiting paintComponent. – VGR Sep 21 '13 at 16:51
  • See also [*Initial Threads*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod Sep 21 '13 at 17:55
  • 1
    What makes you conclude it is a slowdown in the `Timer` ? It might also be a different execution of those multiple `repaint` calls you are doing, for which there were never any documented guarantees about timing (or not that I read at least) – Robin Sep 21 '13 at 18:09
  • I suspect that 100 instances of `Timer` are saturating the shared thread. – trashgod Sep 21 '13 at 19:05

2 Answers2

1

Several issues arise in your example:

  • Swing GUI objects should be constructed and manipulated only on the event dispatch thread.

  • All Swing Timer instances share a common thread, which is being saturated.

Depending on the goal, some alternatives are possible:

  • Use a single Timer instance, and select some fraction for update at a proportionally higher rate. The example below randomly selects N of the components and updates them every 100 ms.

  • Use TexturePaint, as shown here.

image

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.*;

/** @see https://stackoverflow.com/a/18936444/230513 */
public class BlinkenLights {

    private static final int S = 24;
    private static final int N = 10;
    private static final Random r = new Random();
    private static final List<MyComponent> list = new ArrayList<MyComponent>();

    private static final class MyComponent extends JComponent {

        public MyComponent() {
            this.setOpaque(true);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(S, S);
        }

        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(Color.getHSBColor(r.nextFloat() / 6, 1, 1));
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    private static JPanel createPanel() {
        final JPanel p = new JPanel();
        p.setLayout(new GridLayout(N, N));
        for (int i = 0; i < N * N; i++) {
            MyComponent c = new MyComponent();
            p.add(c);
            list.add(c);
        }
        Timer t = new Timer(1000 / N, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Collections.shuffle(list, r);
                for (int i = 0; i < N; i++) {
                    list.get(i).repaint();
                }
            }
        });
        t.start();
        return p;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.add(createPanel());
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • +1 for workaround, but there is really diffence betweens these two JDKs, image added to OPs (no rep point to add images) question are just my testing scenario (Win7 instead of OPs Win8) – mKorbel Sep 21 '13 at 20:27
  • @mKorbel: Good point; the user should [profile](http://stackoverflow.com/q/2064427/230513) to see if the EDT is getting saturated. – trashgod Sep 21 '13 at 20:45
  • we have internal command :-) to update to JRE 1.6/7_...40 with manually un_install previous version or not update and waiting for "stable edition", not my concern in this company .... – mKorbel Sep 21 '13 at 20:48
  • good point, really, not sure how to ...., but there could be an issue that repaint(by default invoke an EDT) isn't invoked, good, good, good – mKorbel Sep 21 '13 at 21:00
  • Thank you very much Mr.mKorbel. Screen shot added is good idea. – Hironori Narushima Sep 22 '13 at 01:11
  • http://pastebin.com/vn6dJR3D there is better example. u25 is smooth animation. but u40 is too laggy slow. I'm really disgusting. Oracle no check UI performance issue and release public jre? Totally different behavior ignore u25 and u40? crazy. – Hironori Narushima Sep 22 '13 at 01:14
  • @HironoriNarushima: I see the same 100 instances of `Timer`. What did [profiling](http://stackoverflow.com/q/2064427/230513) show? – trashgod Sep 22 '13 at 15:10
0

Finally I write DIY repaint management class .. :(

import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import javax.swing.Timer;

/**
 * EffectTimer
 */
public class EffectTimer {

    /**
     * All of effect timers in instance of this class.
     */
    static class GlobalTimer implements ActionListener {

        List<EffectTimer> registeredEffects = new ArrayList<>();

        Timer timer = new Timer(16, this);

        public void start(final EffectTimer t) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalStart(t);
                }

            });
        }

        void internalStart(EffectTimer t) {
            int initialDelay = Math.max(0, (int) (t.getEffectStartTime() - System.currentTimeMillis()));
            if(timer.getInitialDelay() >= initialDelay) {
                timer.setInitialDelay(initialDelay);
            }
            if(!registeredEffects.contains(t)) {
                registeredEffects.add(t);
                if(registeredEffects.size() == 1) {
                    timer.start();
                }
            }
        }

        void stop(final EffectTimer t) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    registeredEffects.remove(t);
                    checkStop();
                }

            });
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            long now = e.getWhen();

            Iterator<EffectTimer> iter = registeredEffects.iterator();
            while(iter.hasNext()) {
                EffectTimer t = iter.next();
                long elapsedMs = now - t.getEffectStartTime();

                if(elapsedMs > 0) {
                    float p = elapsedMs / (float)t.getEffectLengthMs();
                    if(p >= 1.0f) {
                        iter.remove();
                        t.stop();
                    } else {
                        if(t.isReversed()) {
                            p = 1.0f - p;
                        }
                        t.progressChanged(p);
                    }
                }
            }

            checkStop();
        }

        void checkStop() {
            if(registeredEffects.isEmpty()) {
                timer.stop();
            }
        }

        public int getRunningTimerCount() {
            return registeredEffects.size();
        }

        public void stopAll() {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    registeredEffects.clear();
                    checkStop();
                }

            });
        }

    }

    static final GlobalTimer GTIMER = new GlobalTimer();

    int effectLengthMs = -1;

    long effectStartMs = -1;

    float progress = 0.0f;

    boolean reversed = true;

    public long getEffectStartTime() {
        return effectStartMs;
    }

    public int getEffectLengthMs() {
        return effectLengthMs;
    }

    public void start(int lengthMs) {
        start(lengthMs, System.currentTimeMillis());
    }

    public void start(int lengthMs, long startMs) {
        effectLengthMs = lengthMs;
        effectStartMs = startMs;

        reversed = false;
        progress = 0.0f;
        GTIMER.start(this);
    }

    public boolean isReversed() {
        return reversed;
    }

    public void reverse(final int lengthMs) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                internalReverse(lengthMs);
            }

        });
    }

    void internalReverse(int lengthMs) {
        reversed = !reversed;

        effectLengthMs = lengthMs;
        int adjust = reversed ? (int)(lengthMs * (1.0f - progress)) : (int)(lengthMs * progress);
        effectStartMs = System.currentTimeMillis() - adjust;

        GTIMER.start(this);
    }

    final public void progressChanged(float p) {
        progress = p;
        run(p);
    }

    /**
     * 0.0f to 1.0f effect progress.
     * <code>Float.compare(progress, 1.0f) >= 0</code> to end progress.
     */
    protected void run(float p) {}

    public void stop() {
        progress = reversed ? 0.0f : 1.0f;
        GTIMER.stop(this);
    }

    public boolean isRunning() {
        return 0.0f < progress && progress < 1.0f;
    }

    public float getProgress() {
        return progress;
    }

    public static int getRunningTimerCount() {
        return GTIMER.getRunningTimerCount();
    }

    public static void stopAll() {
        GTIMER.stopAll();
    }

}