Separate Logic Thread From Event Dispatch Thread

2019-04-06 07:35发布

问题:

This is the smallest runnable SSCCE,of my project, that I could implement to show you.

  • I've read that calling the game logic from the Event Dispacth Thread is a bad practice, how can I separate them, because as you can see update() and repaint() are related into loop and how can I separate code in a pretty way, I'm getting in trouble with this, trying to find out how to do it.

  • I've posted a similar question regarding and I got an answer,that says to use a Swing Timer,but i have huge task to make and as i read Swing timer isn't ideal for this scenario.This is the question:

    • Event Dispatch Thread divided from logic thread,prevent blocking UI

Main class

    import javax.swing.JFrame;
    import javax.swing.SwingUtilities;
    import javax.swing.UIManager;

    public class Main {

        private static final Main mainFrame = new Main();
        private final JFrame frame;

        private Main() {
        frame = new JFrame();
        frame.setUndecorated(true);
        frame.add(new MyPanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        }

        public static Main getMainFrameInstance() {
        return mainFrame;
        }


        public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
            Main.getMainFrameInstance();
            }
        });
        }

    }

MyPanel Class

        import java.awt.Dimension;
        import java.awt.Graphics;
        import java.awt.Graphics2D;
        import java.awt.RenderingHints;
        import java.awt.image.BufferedImage;

        import javax.swing.JPanel;

        public class MyPanel extends JPanel implements Runnable,KeyListener,MouseListeners {

            private static final long serialVersionUID = 1L;

            // thread and loop
            private Thread thread;
            private boolean running;
            private int FPS = 60;
            private long targetTime = 1000 / FPS;
            private long start;
            private long elapsed;
            private long wait;

            // image
            public BufferedImage image;
            // foo
            private Foo foo;

            private Render render = Render.getRenderManagerInstance();

            public MyPanel() {
            setPreferredSize(new Dimension(700, 700));
            setFocusable(true);
            requestFocus();
            }

            public void addNotify() {
            super.addNotify();
            if (thread == null) {
                    addKeyListeners(this);
                    addMouseListener(this);
                thread = new Thread(this);
                thread.start();
            }
            }


            private  void initGraphic() {
            image = new BufferedImage(700, 700, BufferedImage.TYPE_INT_RGB);
            foo = new Foo();
            running = true;

            }

            public void run() {
            initGraphic();

            // loop
            while (running) {
                start = System.nanoTime();
                foo.update();
                repaint();
                elapsed = System.nanoTime() - start;
                wait = (targetTime - elapsed / 1000000) - 8;
                if (wait <= 0)
                wait = 6;

                try {
                Thread.sleep(wait);
                } catch (Exception e) {
                e.printStackTrace();
                }

            }
            }

           public void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        graphics = image.getGraphics();
        ((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        ((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        render.setRenderState((Graphics2D) graphics);
        graphic.drawImage(image, 0, 0, this);
    // clear graphics resources after use them
    graphic2D.dispose();

      }
         public void keyPressed(KeyEvent keyEvent) {
                   //code not considerable
          }

          public void keyReleased(KeyEvent keyEvent) {
                       //code not considerable

            }

            public void mousePressed(MouseEvent mouseEvent) {
                       //code not considerable

            }

            public void mouseReleased(MouseEvent mouseEvent) {
                      //code not considerable
            }

    }

回答1:

This is how it can look like. You'll need to call the following code somewhere in EDT or through Swing Timer. I'm assuming here that your "huge" task will need to update a text field, but it can be any other UI control as well. All of that, just to demonstrate an idea. Do not treat it as a tested code.

//javax.swing.JTextField jfield; The field that needs to be updated. Take it from your Panel
String text = ""; // just a place holder
Object params [] = new Object []{jfield, text}; 
HugeTaskRunner ht = new HugeTaskRunner(params, new CallBack());

HugeTaskRunner is derived from AbstractTaskRunner, which looks like follows:

public abstract class AbstractTaskRunner extends Thread {


CallBack callBack = null;
Object [] params = new Object[0];

public AbstractTaskRunner (Object [] params, CallBack callBack) {
    this.params = params;
    this.callBack = callBack;

}
public abstract void doTask ();
@Override
public void run() {
    doTask();
    if (callBack != null) {
        callBack.doCall(new Object[]{"DONE"});
    }
}

}

HugeTaskRunner:

public class HugeTaskRunner extends AbstractTaskRunner {

public HugeTaskRunner(Object[] params, CallBack callBack) {
    super(params, callBack);
    // TODO Auto-generated constructor stub
}

@Override
public void doTask() {
    // HERE YOU'LL HAVE TO DO SOME HUGE TASK ACTIONS
    // THEN YOU'LL NEED TO CALL callBack.doCall(params) to update GUI 
    String newText = "Image #1 has been loaded";
    params[params.length -1] = newText; // assuming that the last param is for updated text
    callBack.doCall(params);

}

}

CallBack class:

public class CallBack {
public void doCall (Object [] params) {
    javax.swing.SwingUtilities.invokeLater(new GUIUpdater(params, null));
}
}

GUIUpdater class:

public class GUIUpdater extends AbstractTaskRunner {

public GUIUpdater(Object[] params, CallBack callBack) {
    super(params, callBack);
}

@Override
public void doTask() {
    // UPDATE YOUR GUI HERE TAKING Swing UI objects from params, e.g.
    if (params.length == 1 && params[0].equals("DONE")) {
        // HUGE TASK IS COMPLETED, DO SOMETHING IF YOU NEED TO
    }
    else if (params.length == 2) { // It's a request to update GUI
        javax.swing.JTextField txt = (javax.swing.JTextField) this.params[0];
        txt.setText((String)this.params[1]);
    }
    else {
        // UNKNOWN REQUEST
    }

}

}