raxell
raxell

Reputation: 677

Java javax.swing.Timer duration

I need to execute N calculations (with graphics updates) in X seconds using javax.swing.Timer. What's the best way for do this?

If I set a fixed delay for the timer I exceed the X seconds, because the time required for each execution is delay + calculations.

To work around this I've tried to set the delay of the timer dynamically, but still the time is not accurate.

As a last chance I've tried to set the dalay to 1ms and use Thread.sleep(sleepTime) for control the duration and this work perfectly, without affect the animations.

My question is:

Is this a good solution? Can I use Thread.sleep(sleepTime) inside the javax.swing.Timer?

Edit, here's some code to better understand. I want to say that the graphics is needed only to see if the movements are correct, in the final version i only need to update the game and generate a report of the match result.

Main frame with "game loop":

package engine.test;

import engine.entity.Skill;
import engine.math.Point;
import engine.math.Vector;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.Timer;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public final class EngineFrame extends JFrame implements ActionListener
{
    public static final int SCALE = 6;
    public static final int WIDTH = 100 * SCALE;
    public static final int HEIGHT = 60 * SCALE;

    public static final int DURATION = 2; // animation duration in seconds
    public static final int FPS = 60;
    public static final int SKIP_TICKS = 1000 / FPS;

    private final JSONObject data;
    private final ArrayList<Player> players;
    private PlayersPanel playersPanel;

    public EngineFrame(String title) throws JSONException, FileNotFoundException
    {
        super(title);

        setSize(WIDTH, HEIGHT);
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        createMenu();

        data = new JSONObject(loadData());
        players = buildPlayers(data.getJSONArray("teams").getJSONObject(0).getJSONArray("players"));

        playersPanel = new PlayersPanel(players);
        add(playersPanel);
    }

    public ArrayList<Player> getPlayers()
    {
        return players;
    }

    /**
     * Returns the data required to run a match.
     * 
     * @return a json string representing the match data
     * @throws FileNotFoundException 
     */
    private String loadData() throws FileNotFoundException
    {
        Scanner fileInput = new Scanner(new File(
            "C:\\Users\\packard bell\\Documents\\JavaProjects\\Engine\\src\\resources\\data.json"
        ));
        String jsonText = "";

        while (fileInput.hasNextLine()) {
            jsonText += fileInput.nextLine();
        }

        return jsonText;
    }

    /**
     * Creates and returns the player entities.
     * 
     * @param playersData
     * @return 
     */
    private ArrayList<Player> buildPlayers(JSONArray playersData) throws JSONException
    {
        ArrayList<Player> players = new ArrayList<Player>();
        JSONObject playerData;
        Player player;

        for (int i = 0, l = playersData.length(); i < l; i++) {
            playerData = playersData.getJSONObject(i);
            player = new Player.Builder()
                .setId(playerData.getInt("id"))
                .setFirstName(playerData.getString("first_name"))
                .setLastName(playerData.getString("last_name"))
                .setMass(playerData.getInt("weight"))
                .setSkills(buildSkills(playerData.getJSONObject("skills")))
                .setInitialPosition(new Point(0, i * 10 + 20))
                .setInitialVelocity(new Vector(0, 0))
                .build();

            players.add(player);
        }

        return players;
    }

    /**
     * 
     */
    private Map<Skill, Double> buildSkills(JSONObject skillsData) throws JSONException
    {
        Map<Skill, Double> skills = new HashMap();

        for (Skill skill : Skill.values()) {
            skills.put(
                skill,
                skill.getMinValue() + (skill.getMaxValue() - skill.getMinValue()) * (skillsData.getDouble(skill.getName()) / 100)
            );
        }

        return skills;
    }

    /**
     * 
     */
    private void createMenu()
    {
        JMenu seekMenu = new JMenu("Seek behavior");

        JMenuItem initSeek = new JMenuItem("Init seek");
        initSeek.addActionListener(this);
        JMenuItem runSeek = new JMenuItem("Run seek");
        runSeek.addActionListener(this);
        JMenuItem stopSeek = new JMenuItem("Stop seek");
        stopSeek.addActionListener(this);

        seekMenu.add(initSeek);
        seekMenu.add(runSeek);
        seekMenu.add(stopSeek);

        JMenuBar bar = new JMenuBar();
        bar.add(seekMenu);

        setJMenuBar(bar);
    }

    public static void main(String[] args) throws JSONException, FileNotFoundException, InterruptedException
    {
        EngineFrame frame = new EngineFrame("Engine");
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        String menuString = e.getActionCommand();

        if (menuString.equalsIgnoreCase("init seek")) {
            Player player1 = getPlayers().get(0);
            Player player2 = getPlayers().get(1);

            player1.setPosition(new Point(0, 20));
            player1.setVelocity(new Vector(0, 0));
            player2.setPosition(new Point(0, 30));
            player2.setVelocity(new Vector(0, 0));

            repaint();
        }
        else if (menuString.equalsIgnoreCase("run seek")) {
            Timer t = new Timer(1, new ActionListener() {
                private final long start = System.currentTimeMillis();
                private long nextGameUpdate = start;
                private long sleepTime = 0;
                private int loops = DURATION * 1000 / SKIP_TICKS;

                @Override
                public void actionPerformed(ActionEvent e) {
                    Player player1 = getPlayers().get(0);
                    Player player2 = getPlayers().get(1);

                    //System.out.println("Position: " + player1.getPosition());
                    //System.out.println("Velocity: " + player1.getVelocity());
                    System.out.println();

                    player1.getSteering().seek(new Point(50, 20));
                    player2.getSteering().seek(new Point(50, 30));
                    player1.update();
                    player2.update();
                    repaint();

                    nextGameUpdate += SKIP_TICKS;
                    sleepTime = nextGameUpdate - System.currentTimeMillis();
                    //System.out.println(nextGameUpdate);
                    //System.out.println(sleepTime);
                    loops--;

                    if (sleepTime >= 0) {
                        try {
                            Thread.sleep(sleepTime);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(EngineFrame.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }

                    if (loops <= 0) {
                        ((Timer)e.getSource()).stop();

                        long end = System.currentTimeMillis();
                        // should be 2000ms (equals to DURATION constant)
                        System.out.println("Duration: " + (end - start) + "ms");
                    }
                }
            });
            t.setInitialDelay(0);
            t.start();
        }
    }

    // version without swing timer (it works if called in the main method)
    private void runSeek() throws InterruptedException
    {
        Player player1 = getPlayers().get(0);
        Player player2 = getPlayers().get(1);

        player1.setPosition(new Point(0, 20));
        player2.setPosition(new Point(0, 30));

        // run
        long start = System.currentTimeMillis();
        long nextGameUpdate = start;
        long sleepTime = 0;
        int i = DURATION * 1000 / SKIP_TICKS;
        System.out.println("Loop executions: " + i);
        int steps = 0;
        String stepsCode = "[";
        String velocitiesCode = "[";
        String positionsCode = "[";

        while (i > 0) {
            stepsCode += steps + ", ";
            velocitiesCode += player1.getVelocity().len() + ", ";
            positionsCode += player1.getPosition().toVector().len() + ", ";

            System.out.println("Position: " + player1.getPosition());
            System.out.println("Velocity: " + player1.getVelocity());
            System.out.println();

            player1.getSteering().seek(new Point(50, 20));
            player2.getSteering().seek(new Point(50, 30));
            player1.update();
            player2.update();
            repaint();

            nextGameUpdate += SKIP_TICKS;
            sleepTime = nextGameUpdate - System.currentTimeMillis();
            steps += sleepTime;
            //System.out.println(sleepTime);

            if (sleepTime >= 0) {
                Thread.sleep(sleepTime);
            }

            i--;
        }

        stepsCode = stepsCode.substring(0, stepsCode.length() - 2) + "]";
        velocitiesCode = velocitiesCode.substring(0, velocitiesCode.length() - 2) + "]";
        positionsCode = positionsCode.substring(0, positionsCode.length() - 2) + "]";

        long end = System.currentTimeMillis();

        System.out.println("Duration: " + (end - start) + "ms");

        System.out.println("Steps:");
        System.out.println(stepsCode);
        System.out.println("Positions:");
        System.out.println(positionsCode);
        System.out.println("Velocities:");
        System.out.println(velocitiesCode);
    }
}

Here's the JPanel that draw the entities:

package engine.test;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.ArrayList;
import javax.swing.JPanel;

public class PlayersPanel extends JPanel
{
    private ArrayList<Player> players;

    public PlayersPanel(ArrayList<Player> players)
    {
        this.players = players;
    }

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        for (Player player : players) {
            int x = (int) (player.getPosition().x() * EngineFrame.SCALE);
            int y = (int) (player.getPosition().y() * EngineFrame.SCALE);

            g2.setColor(Color.BLACK);
            g2.fillArc(x, y, 18, 18, 0, 360);
            g2.setColor(new Color(0x11539f));
            g2.fillArc(x + 2, y + 2, 14, 14, 0, 360);
        }
    }
}

Upvotes: 0

Views: 721

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 208944

Is this a good solution? Can I use Thread.sleep(sleepTime) inside the javax.swing.Timer?

No, never do this. Don't worry about changing the delay. Instead consider what it is you want to draw and when you want to draw them. You can create a class for the different object to draw, and have then maintain a delayed state that will determine when they should be drawn. If you want to change an object velocity, then increase the number of pixels you increment it's movement.

Here's an example you may find useful, that shows my first point of maintaining a delayed state. You can see the balls are thrown at different times.

enter image description here

Other than that, you should post some code that show's exactly what you're trying to do. Your question is somewhat vague.

Upvotes: 1

Related Questions