问题:创建一个非常准确的挥杆计时器 [英] Issues: Creating a very accurate Swing Timer

查看:22
本文介绍了问题:创建一个非常准确的挥杆计时器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了使 SwingTimer 准确,我喜欢@Tony Docherty 建议的逻辑和示例在 CR 上.这是链接.>

为了突出给定的单词,一次又一次,总会有几微秒的延迟.如果我有话要强调说:你好,你好"并且每个单词的值分别是(延迟):200,300,400 ms,那么计时器所花费的实际时间总是更多.假设不是 200 毫秒,而是需要 216 毫秒.像这样,如果我有很多话..最后,额外的延迟很明显.

我必须突出显示每个字母说:'h''e''l''l''0' 每个应该得到 200/length(i.e 5) = 40 ms approx.设置每个字母后的延迟.

我的逻辑是,就在开始进程之前,取当前时间说 startTime.另外,计算 totalDelay 即 totalDelay+=delay/.length().

现在检查条件:(startTime+totalDelay-System.currentTime)如果这是-ve,则意味着时间消耗更多,因此请跳过该字母.检查直到出现正延迟.这意味着我将添加到现在为止的时间,并用进程启动时所用时间的差异来过度检查它.

这可能会导致跳过以突出显示字母.

但是出了点问题.什么,我很难分辨.循环的东西可能有问题.我已经看到它进入循环(检查时间是否为 -ve )仅两次.但事实并非如此.而且我也不确定如何设置我的下一个延迟.有什么想法吗?

这是一个 SSCCE:

 import java.awt.Color;导入 java.awt.event.ActionEvent;导入 java.awt.event.ActionListener;导入 java.lang.reflect.InvocationTargetException;导入 javax.swing.JFrame;导入 javax.swing.JPanel;导入 javax.swing.JTextPane;导入 javax.swing.SwingUtilities;导入 javax.swing.Timer;导入 javax.swing.text.BadLocationException;导入 javax.swing.text.DefaultStyledDocument;导入 javax.swing.text.StyleConstants;导入 javax.swing.text.StyledDocument;公开课提醒{private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";private static final String[] WORDS = TEXT.split(" ");私人 JFrame 框架;私人定时器定时器;私人 StyledDocument 文档;私人 JTextPane 文本窗格;私有整数 [] 次 = 新整数 [100];private long totalDelay=0,startTime=0;私人 int stringIndex = 0;私有整数索引 = 0;公共无效开始着色(){次数[0]=100;次数[9]=200;次数[10]=200;次数[11]=200;次数[12]=200;次数[1]=400;次数[2]=300;次数[3]=900;次数[4]=1000;次数[5]=600;次数[6]=200;次数[7]=700;次数[8]=700;ActionListener actionListener = new ActionListener() {@覆盖public void actionPerformed(ActionEvent actionEvent){doc.setCharacterAttributes(stringIndex, 1, textpane.getStyle("Red"), true);字符串索引++;尝试 {if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ")|| doc.getText(stringIndex, 1).equals("
")){指数++;}如果(索引

更新:

为了更好地理解:

@Tony Docherty 给出的 EG:

让我们以单词Test"为例,说它需要突出显示 1 秒,因此每个字母突出显示 250 毫秒.以您最初的方式做事,确实意味着您为每个字母设置了 250 毫秒的计时器,但是如果每个周期实际上花费了 260 毫秒,并且假设e"周期花费了 400 毫秒(可能是由于 GC 或其他使用 CPU 周期的原因)单词的结尾你会比你应该多花 180 毫秒.此错误将继续为每个单词构建,直到错误如此之大,突出显示不再在视觉上同步.

我尝试的方式不是反复说这个字母需要突出显示 x 时间,而是计算每个字母相对于序列开头的时间,即 T = 250, e = 500, s =750,t = 1000.

所以要获得实际的时间延迟,您需要加上开始时间并减去当前时间.使用我上面给出的时间运行示例:

StartTime Letter Offset CurrentTime Delay ActualTimeTaken100000 吨 250 100010 240 250100000 e 500 100260 240 400100000 秒 750 100660 90 100100000 吨 1000 100760 240 250

所以您现在应该能够看到每个字母的时间都经过调整,以考虑到前一个字母的任何超时.当然,时间超限可能非常严重,以至于您必须跳过突出显示下一个字母(或者可能超过 1 个),但至少我会保持大致同步.

已编辑的 SSCCE

更新 2

在第一阶段,我为每个单词计时.也就是说,当用户按下 ESC 键时,特定单词的时间被存储(他这样做是因为歌曲在后台播放.)当按下 ESC 键时,当前单词被突出显示,并且当前单词花费的时间word 存储在一个数组中.我继续存储时间.当用户结束时,现在我想按照设定的时间突出显示单词.所以在这里,用户的时机很重要.如果时间快,则突出显示单词,如果时间慢,反之亦然.

新更新:进展

下面的答案有不同的逻辑,但令我惊讶的是,它们的工作原理大致相同.我在所有逻辑(包括我的)中发现的一个非常非常奇怪的问题是,它们似乎可以完美地处理几行,但之后它们获得了速度,这也不是很慢,而是有很大的不同.

另外,如果您认为我应该以不同的方式思考,我们非常感谢您的建议.

解决方案

好的,所以我一直在看一些代码(我在你上一个关于卡拉 OK 计时器的问题中发布的代码)

使用该代码,我通过 System.out.println() 使用 System.nanoTime() 建立了一些测量系统,这将帮助我们了解正在发生的事情:

import java.awt.BorderLayout;导入 java.awt.Color;导入 java.awt.event.ActionEvent;导入 java.awt.event.ActionListener;导入 java.util.ArrayList;导入 javax.swing.AbstractAction;导入 javax.swing.JButton;导入 javax.swing.JFrame;导入 javax.swing.JOptionPane;导入 javax.swing.JTextPane;导入 javax.swing.SwingUtilities;导入 javax.swing.Timer;导入 javax.swing.text.Style;导入 javax.swing.text.StyleConstants;导入 javax.swing.text.StyledDocument;公开课 KaraokeTest {private int[]timingsArray = {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};//单词/字母计时private String[] individualWordsToHighlight = {" 
Hello
", " world
", " Hello", " world", " Hello", " world", " Hello", " world", " Hello", " world"};//要突出显示的每个单词/字母私有整数计数 = 0;私有最终 JTextPane jtp = new JTextPane();private final JButton startButton = new JButton("Start");私有最终 JFrame 框架 = 新 JFrame();//创建单个字母及其时间的数组最终的 ArrayListchars = new ArrayList<>();最终的 ArrayListcharsTiming = new ArrayList<>();公共卡拉OK测试(){初始化组件();}私有无效initComponents(){frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setResizable(false);for (String s : individualWordsToHighlight) {String tmp = jtp.getText();jtp.setText(tmp + s);}jtp.setEditable(false);startButton.addActionListener(new ActionListener() {@覆盖public void actionPerformed(ActionEvent ae) {startButton.setEnabled(false);计数 = 0;charsTiming.clear();字符.clear();for (String s : individualWordsToHighlight) {for (int i = 0; i < s.length(); i++) {chars.add(String.valueOf(s.charAt(i)));//System.out.println(String.valueOf(s.charAt(i)));}}//计算每个字母的时间for (int x = 0; x 

我电脑上的输出是:

<块引用>

开始:10289712615974

字符需要的时间:166

耗时:165

差异 1

...

字符需要的时间:166

耗时:155

差异 11

...

字符需要的时间:166

耗时:5

差异 161

已停止:10299835063084

总共需要的时间:9960

使用每个字母所花费时间的累加器所花费的时间:5542

差异:4418

使用差异所花费的时间(endTime-startTime):10122

差异:-162

因此我的结论是 Swing Timer 实际上运行得比我们预期的要快,因为 TimeractionPerformed 中的代码不一定需要与预期突出显示的字母一样长的时间这当然会导致雪崩效应,即计时器运行得越快/越慢,差异将变得越大/越小,并且 restart(..) 上的计时器下次执行将在不同的时间执行,即更快或者更慢.

在代码中这样做:

//计算下一个字母的开始以突出显示花费的时间和实际花费的时间之间的差异延迟 = (int) (timeToTake - dif);//用新的时间重新启动计时器//((Timer) ae.getSource()).setInitialDelay((int)timeToTake);//定时器通常更快,因此整个高亮会做得太快((Timer) ae.getSource()).setInitialDelay(delay);((Timer) ae.getSource()).restart();

产生更准确的结果(我的最大延迟每个字母快了 4 毫秒):

<块引用>

开始:10813491256556

字符需要的时间:166

耗时:164

差异 2

...

字符需要的时间:166

耗时:164

差异 2

...

字符需要的时间:166

耗时:162

差异 4

已停止:10823452105363

总共需要的时间:9960

使用每个字母所花费时间的累加器所花费的时间:9806

差异:154

使用差异(endTime-startTime)花费的时间:9960

差异:0

In order to make SwingTimer accurate, I like the logic and example suggested by @Tony Docherty On CR. Here is the Link.

In order to highlight the given words, again and again, there is always a few microsecond delays. If I have words to highlight say: "hello how are" and the values for each word are (delays): 200,300,400 ms respectively, then the actual time taken by the timer is always more. Say instead of 200 ms, it takes 216 ms. Like this, if I have many words..in the end, the extra delay is noticeable.

I have to highlight each letter say: 'h''e''l''l''0' each should get 200/length(i.e 5) = 40 ms approx. Set the delay after each letter.

My logic is, take the current time say startTime, just before starting the process. Also, calculate the totalDelay which is totalDelay+=delay/.length().

Now check the condition: (startTime+totalDelay-System.currentTime) if this is -ve, that means the time consumption is more, so skip the letter. Check till there is a positive delay.This means I am adding the timings till now, and overcheck it with the difference in the time taken by the process when it got started.

This may result into skipping to highlight the letters.

But something is wrong. What, it’s difficult for me to make out. It's some problem with the looping thing maybe. I have seen it is entering the loop (to check whether the time is -ve ) just twice. But this should not be the case. And I am also not sure about setting up my next delay. Any ideas?

Here is an SSCCE:

    import java.awt.Color;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.lang.reflect.InvocationTargetException;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JTextPane;
    import javax.swing.SwingUtilities;
    import javax.swing.Timer;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.DefaultStyledDocument;
    import javax.swing.text.StyleConstants;
    import javax.swing.text.StyledDocument;

    public class Reminder {
        private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
        private static final String[] WORDS = TEXT.split(" ");
        private JFrame frame;
        private Timer timer;
        private StyledDocument doc;
        private JTextPane textpane;
        private int[] times = new int[100];
      private long totalDelay=0,startTime=0;

        private int stringIndex = 0;
        private int index = 0;

        public void startColoring() {
              times[0]=100;times[9]=200;times[10]=200;times[11]=200;times[12]=200;
              times[1]=400;times[2]=300;times[3]=900;times[4]=1000;times[5]=600;times[6]=200;times[7]=700;times[8]=700;

      ActionListener actionListener = new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent actionEvent) 
       {

       doc.setCharacterAttributes(stringIndex, 1, textpane.getStyle("Red"), true);
        stringIndex++;

 try {

 if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ")|| doc.getText(stringIndex, 1).equals("
"))
 {
                            index++;
  }
    if (index < WORDS.length) {

       double delay = times[index];
     totalDelay+=delay/WORDS[index].length();

  /*Check if there is no -ve delay, and you are running according to the time*/
  /*The problem is here I think. It's just entered this twice*/
   while(totalDelay+startTime-System.currentTimeMillis()<0)
      { 
      totalDelay+=delay/WORDS[index].length();
      stringIndex++;
     /*this may result into the end of current word, jump to next word.*/
    if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ") || doc.getText(stringIndex, 1).equals("
"))
       {
   index += 1;
   totalDelay+=delay/WORDS[index].length();
       }
      }

     timer.setDelay((int)(totalDelay+startTime-System.currentTimeMillis()));

                        } 
else {
         timer.stop();
    System.err.println("Timer stopped");
       }
                    } catch (BadLocationException e) {
                        e.printStackTrace();
                    }
                }
            };

            startTime=System.currentTimeMillis();
            timer = new Timer(times[index], actionListener);
            timer.setInitialDelay(0);
            timer.start();
        }

        public void initUI() {
            frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JPanel panel = new JPanel();
            doc = new DefaultStyledDocument();
            textpane = new JTextPane(doc);
            textpane.setText(TEXT);
            javax.swing.text.Style style = textpane.addStyle("Red", null);
            StyleConstants.setForeground(style, Color.RED);
            panel.add(textpane);
            frame.add(panel);
            frame.pack();
            frame.setVisible(true);
        }

        public static void main(String args[]) throws InterruptedException, InvocationTargetException {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    Reminder reminder = new Reminder();
                    reminder.initUI();
                    reminder.startColoring();
                }
            });
        }
    }

UPDATE:

For better understanding:

The EG given by @Tony Docherty :

Lets take the word "Test" and say it needs to be highlighted for 1 second, therefore each letter is highlighted for 250ms. Doing things the way you originally, did meant that you set a timer for 250ms for each letter but if each cycle actually took 260ms and lets say the 'e' cycle took 400ms (maybe due to GC or something else using CPU cycles) by the end of the word you would have taken 180ms more than you should have. This error will continue to build for each word until the error is so large highlighting is no longer visually in sync.

The way I am trying, is rather than repeatedly saying this letter needs to be highlighted for x amount of time, calculate the time for each letter relative to the beginning of the sequence ie T = 250, e = 500, s = 750, t = 1000.

So to get the actual time delay you need to add the start time and subtract the current time. To run through the example using the timings I gave above:

StartTime   Letter   Offset    CurrentTime    Delay  ActualTimeTaken   
100000         T       250       100010        240      250  
100000         e       500       100260        240      400  
100000         s       750       100660         90      100  
100000         t      1000       100760        240      250  

So you should be able to see now that the timing for each letter is adjusted to take account of any overrun of time from the previous letter. Of course it is possible that a timing overrun is so great that you have to skip highlighting the next letter (or maybe more than 1) but at least I will remaining broadly in sync.

EDITED SSCCE

Update2

In first phase, I take the timings for each word. That is, when the user hits ESC key, the time is stored for a particular word (he does it as the song is played in background.) When the ESC key is pressed, the current word is highlighted and the time spent on the current word is stored in an array. I keep on storing the timings. When the user ends, now I would like to highlight the words as per the set timings. So here, the timing by the user is important. If the timings are fast, so is the highlighting of words or if slow, vice-versa.

New update: progress

The answers below have different logic, but to my surprise, they work more or less the same. A very very weird problem I have found out with all the logic (including mine) is that they seem to work perfectly for few lines, but after that they gain speed, that's also not slowly, but with a huge difference.

Also if you think I should think in a different way, your suggestions are highly appreciated.

解决方案

Okay so I have been looking at the some code (the code I posted in your last question about Karaoke timer)

Using that code I put up some measuring system using System.nanoTime() via System.out.println() which will help us to see what is happening:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

public class KaraokeTest {

    private int[] timingsArray = {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};//word/letters timings
    private String[] individualWordsToHighlight = {" 
Hello
", " world
", " Hello", " world", " Hello", " world", " Hello", " world", " Hello", " world"};//each individual word/letters to highlight
    private int count = 0;
    private final JTextPane jtp = new JTextPane();
    private final JButton startButton = new JButton("Start");
    private final JFrame frame = new JFrame();
    //create Arrays of individual letters and their timings
    final ArrayList<String> chars = new ArrayList<>();
    final ArrayList<Long> charsTiming = new ArrayList<>();

    public KaraokeTest() {
        initComponents();
    }

    private void initComponents() {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);

        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        jtp.setEditable(false);

        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                startButton.setEnabled(false);
                count = 0;
                charsTiming.clear();
                chars.clear();

                for (String s : individualWordsToHighlight) {
                    for (int i = 0; i < s.length(); i++) {
                        chars.add(String.valueOf(s.charAt(i)));
                        //System.out.println(String.valueOf(s.charAt(i)));
                    }
                }

                //calculate each letters timings
                for (int x = 0; x < timingsArray.length; x++) {
                    for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
                        individualWordsToHighlight[x] = individualWordsToHighlight[x].replace("
", " ").replace("
", " ");//replace line breaks
                        charsTiming.add((long) (timingsArray[x] / individualWordsToHighlight[x].trim().length()));//dont count spaces
                        //System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
                    }
                }

                Timer t = new Timer(1, new AbstractAction() {
                    long startTime = 0;
                    long acum = 0;
                    long timeItTookTotal = 0;
                    long dif = 0, timeItTook = 0, timeToTake = 0;
                    int delay = 0;

                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        if (count < charsTiming.size()) {

                            if (count == 0) {
                                startTime = System.nanoTime();
                                System.out.println("Started: " + startTime);
                            }

                            timeToTake = charsTiming.get(count);
                            acum += timeToTake;

                            //highlight the next word
                            highlightNextWord();

                            //System.out.println("Acum " + acum);
                            timeItTook = (acum - ((System.nanoTime() - startTime) / 1000000));
                            timeItTookTotal += timeItTook;
                            //System.out.println("Elapsed since start: " + (System.nanoTime() - startTime));
                            System.out.println("Time the char should take: " + timeToTake);
                            System.out.println("Time it took: " + timeItTook);
                            dif = (timeToTake - timeItTook);
                            System.out.println("Difference: " + dif);
                            //System.out.println("Difference2 " + (timeToTake - dif));

                            //calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
                            delay = (int) (timeToTake - dif);

                            if (delay < 1) {
                                delay = 1;
                            }

                            //restart timer with new timings
                            ((Timer) ae.getSource()).setInitialDelay((int) timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
                            //((Timer) ae.getSource()).setInitialDelay(delay);
                            ((Timer) ae.getSource()).restart();

                        } else {//we are at the end of the array
                            long timeStopped = System.nanoTime();
                            System.out.println("Stopped: " + timeStopped);
                            System.out.println("Time it should take in total: " + acum);
                            System.out.println("Time it took using accumulator of time taken for each letter: " + timeItTookTotal
                                    + "
Difference: " + (acum - timeItTookTotal));
                            long timeItTookUsingNanoTime = ((timeStopped - startTime) / 1000000);
                            System.out.println("Time it took using difference (endTime-startTime): " + timeItTookUsingNanoTime
                                    + "
Difference: " + (acum - timeItTookUsingNanoTime));
                            reset();
                            ((Timer) ae.getSource()).stop();//stop the timer
                        }
                        count++;//increment counter
                    }
                });
                t.setRepeats(false);
                t.start();
            }
        });

        frame.add(jtp, BorderLayout.CENTER);
        frame.add(startButton, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    private void reset() {
        startButton.setEnabled(true);
        jtp.setText("");
        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        JOptionPane.showMessageDialog(frame, "Done");
    }

    private void highlightNextWord() {
        //we still have words to highlight
        int sp = 0;
        for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
            sp += 1;
        }

        while (chars.get(sp - 1).equals(" ")) {
            sp += 1;
            count++;
        }

        //highlight words
        Style style = jtp.addStyle("RED", null);
        StyleConstants.setForeground(style, Color.RED);
        ((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new KaraokeTest();
            }
        });
    }
}

The output on my PC is:

Started: 10289712615974

Time the char should take: 166

Time it took: 165

Difference 1

...

Time the char should take: 166

Time it took: 155

Difference 11

...

Time the char should take: 166

Time it took: 5

Difference 161

Stopped: 10299835063084

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 5542

Difference: 4418

Time it took using difference (endTime-startTime): 10122

Difference: -162

Thus my conclusion is the Swing Timer is actually running faster than we expect as the code in the Timers actionPerformed will not necessarily take as long as the letters expected highlighting time this of course causes an avalanche effect i.e the faster/slower the timer runs the greater/less the difference will become and timers next execution on restart(..) will take be at a different time i.e faster or slower.

in the code do this:

//calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
delay = (int) (timeToTake - dif);


//restart timer with new timings
//((Timer) ae.getSource()).setInitialDelay((int)timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
((Timer) ae.getSource()).setInitialDelay(delay);
((Timer) ae.getSource()).restart();

Produces a more accurate result (maximum latency Ive had is 4ms faster per letter):

Started: 10813491256556

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 162

Difference 4

Stopped: 10823452105363

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 9806

Difference: 154

Time it took using difference (endTime-startTime): 9960

Difference: 0

这篇关于问题:创建一个非常准确的挥杆计时器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆