Deploying game to server results in strange behaviour
Asked Answered
T

2

6

I developed a breakout alike game with a friend using HTML5 WebSockets and java as backend and recently deployed my game on a Glassfish server that's running on the 20$ Digitalocean droplet (3GB ram, 2cpu's).

When developing the game I worked with IntelliJ and a co-worker with Netbeans, when deploying our WAR file on the Glassfish servers running on our PC everything is working as expected. But when deploying the exact same WAR file on the droplet, the ball seems to be moving 3 times as fast.

I have tried reproducing the issue by installing the same Ubuntu server as the droplet on a virtual machine and executing the same steps I used to install OpenJDK, Glassfish, ... but on the VM it was working fine as well.

Other droplets with 1 CPU (tried ubuntu and centos) produces the same issues. I wonder what might be the cause of this issue that I'm missing?

Below is the code that I'm using for the connection/game:

WebSocket:

@ServerEndpoint("/singleplayer")
public class SingleplayerSocket {

    private static final Set<Session> PLAYERS = Collections.synchronizedSet(new HashSet<Session>());

    private Session session;
    private Gson gson;
    private Game game;

    private void sendMessage(String message) {
        try {
            for (Session player: PLAYERS) {
                if (player == session) {
                    player.getBasicRemote().sendText(message);
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void gameStart() {
        game.start();
        sendMessage("Game started");
    }

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        gson = new Gson();
        PLAYERS.add(session);

        sendMessage("Connection established");
    }

    @OnMessage
    public void onMessage(String message) {
        if (session != null && session.isOpen()) {
            String messageType = gson.fromJson(message, MessageType.class).getMessage();

            switch (messageType) {

                case "gameSetup":
                    gameSetup(message);
                    break;

                case "gameStart":
                    gameStart();
                    break;
            }
        }
    }

    @OnClose
    public void onClose(Session session) {
        PLAYERS.remove(session);
        this.session = null;
    }
}

Game class with the ball move method underneath:

        public class Game implements Runnable {

        private final int TARGET_FPS = 60;
        private final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;

        private volatile boolean gameRunning;
        private volatile boolean gamePaused;

        private Session session;
        private Thread thread;
        private Gson gson;

        public Game(Session session, int width, int height, String difficulty) {
            this.session = session;
            this.WIDTH = width;
            this.HEIGHT = height;
            gson = new Gson();
            timer = new Timer();

            setup(difficulty);
        }

        private void setGameRunning(boolean gameRunning) {
            this.gameRunning = gameRunning;
        }

        private void update(double delta) {
            ball.move(delta);
            collisionDetectionWalls();
            collisionDetectionPaddle();
            collisionDetectionBricks();
        }

        public void start() {
            thread = new Thread(this);
            thread.start();
            setGameRunning(true);
        }

        public void stop() {
            setGameRunning(false);
        }

        private void end(boolean won) {
            updateScore();
            sendGameEnd(won);
            stop();
        }

        private void sendMessage(String message) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void sendGameUpdate() {
            GameUpdateData data = new GameUpdateData(paddle, ball, bricks);
            GameUpdateResponse response = new GameUpdateResponse("gameUpdate", data);
            sendMessage(gson.toJson(response));
        }

        @Override
        public void run() {
            long lastLoopTime = System.nanoTime();
            long lastFpsTime = 0;

            while (gameRunning) {
                long currentTime = System.nanoTime();
                long updateLength = currentTime - lastLoopTime;
                lastLoopTime = currentTime;
                double delta = updateLength / ((double) OPTIMAL_TIME);

                lastFpsTime += updateLength;
                if (lastFpsTime >= 1000000000) {
                    lastFpsTime = 0;
                }

                if (!gamePaused) {
                    update(delta);
                    sendGameUpdate();
                }

                try {
                    long sleepTime = (System.nanoTime() - lastLoopTime + OPTIMAL_TIME) / 1000000;
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                }
            }
        }
    }

public class Ball {
    public void move(double delta) {
        if (isLaunched()){
            double trigoX = Math.cos(angle);
            double trigoY = Math.sin(angle);

            x += trigoX * velocity * delta;
            y += trigoY * velocity * delta;
        }
    }
}
Theomachy answered 10/12, 2017 at 15:11 Comment(1)
Two things. 1: you ignore your interrupted exception. If Thread.sleep happens to wake up early, you'd never know about it. 2: Can you explain why your delta is calculated as a fraction of your elapsed time since the last frame? Looks like it's elapsed time divided by your target FPS in nano seconds. Why not just stick with nano seconds through and through? I may be missing somethingLaryssa
T
2

While working on the multi player game I stumbled on a couple of issues and decided to check a couple of variables with console.log() in Javascript. I noticed the game was being started twice when clicking on the button and solved the issue by adding a server side check to prevent starting the game multiple times when clicking on the 'Play' button.

private void gameStart() {
    if (!game.isGameRunning()) {
        game.start();
        sendMessage("Game started");
    }
}

The ball speed is working fine now.

Theomachy answered 21/12, 2017 at 16:2 Comment(0)
S
1

Maybe you can try System.currentTimeMillis() since System.nanoTime() is not threadsave.

Reference: Is System.nanoTime() consistent across threads?

Scanlon answered 11/12, 2017 at 6:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.