7
\$\begingroup\$

I have a Game of Life program that has a grid[][] array which is populated by Cell objects. If there's a cell in a grid square, that grid square is green. One can add the cells manually by clicking on the grid squares and turning them green.

When a Start Iterations button is pressed a startIterations() method starts, which makes use of AnimationTimer. It all runs in the single thread, until there are no alive cells left or until 10 seconds pass.

However, I still can interfer with that process by clicking on free squares and turning them green. Also I have a Stop Iterations button that invokes AnimationTimer's stop() method when pressed; I can access that button while the simulations are being run at it works OK. How is that possible, if there's only one thread?

Even though it works OK, I have a feeling it's wrong to have everything inside a single thread. What is the better approach? Put animation in a separate second thread?

enter image description here

Main class:

public class Main extends Application {
private static int gameWidth = 800;
private static int gameHeight = 600;

private static int gridSize = 20;
private static Cell[][] grid = new Cell[gridSize][gridSize];

private static Scene scene;
private static Displayer gameDisplayer;

static SimpleIntegerProperty simulationCount = new SimpleIntegerProperty(0);
static int aliveCellsCount = 0;

private static boolean iterationsStatus = false;
@Override
public void start(Stage primaryStage) throws Exception {
    primaryStage.setTitle("Advanced Game of Life");

    createGrid();

    gameDisplayer = new Displayer(gameWidth, gameHeight, grid);

    scene = gameDisplayer.getGameScene();
    primaryStage.setScene(scene);
    primaryStage.show();

}

public static void main(String[] args) {
    launch(args);
}

startIterations() method:

public static void startIterations() {
    System.out.println("Starting iterations");
    simulationCount.set(0);

    final long startNanoTime = System.nanoTime();

    new AnimationTimer() {
        private long lastUpdateTime = 0;

        public void handle(long currentNanoTime) {
            int currentTime = (int) Math.round((currentNanoTime - startNanoTime) / (double) 1000_000_000L);

            if (currentNanoTime - lastUpdateTime >= 100_000_000) {
                aliveCellsCount=updateGrid();

                simulationCount.set(simulationCount.get() + 1);
                lastUpdateTime = currentNanoTime;
            }

            if (aliveCellsCount == 0 || currentTime > 10 || !iterationsStatus) {
                stop();
                System.out.println("Stopped iterations");
            }
        }
    }.start();

    System.out.println("Finished iterations");
}

updateGrid() method:

public static int updateGrid() {
        System.out.println("Updating grid");

        int livingCount;

        for (int i = 0; i < gridSize; i++) {
            for (int j = 0; j < gridSize; j++) {
                livingCount = 0;

                livingCount += checkGrid(i - 1, j);
                livingCount += checkGrid(i, j - 1);
                livingCount += checkGrid(i + 1, j);
                livingCount += checkGrid(i, j + 1);
                livingCount += checkGrid(i - 1, j - 1);
                livingCount += checkGrid(i + 1, j + 1);
                livingCount += checkGrid(i - 1, j + 1);
                livingCount += checkGrid(i + 1, j - 1);

                if (livingCount == 3) {
                    grid[i][j].setNextStatus(true);
                } else if (livingCount == 2 && grid[i][j].getStatus()) {
                    grid[i][j].setNextStatus(true);
                } else {
                    grid[i][j].setNextStatus(false);
                }

                System.out.print("livingCount for (" + i + ", " + j + "): " + livingCount);
                System.out.println(" => " + grid[i][j].getNextStatus());
            }
        }

        int resultLivingCount = 0;

        for (int i = 0; i < gridSize; i++) {
            for (int j = 0; j < gridSize; j++) {
                grid[i][j].setStatus(grid[i][j].getNextStatus());

                resultLivingCount += grid[i][j].getStatus() ? 1 : 0;
                // System.out.println("grid[" + i + "]["+j + "] is " + grid[i][j].getStatus());
            }
        }

        return resultLivingCount;
    }

handle() method inside Displayer class, that handles buttons clicks:

public void handle(ActionEvent event)  {
        if (event.getSource()==createCellButton)  {
           Main.createCell();
        }
        else if (event.getSource() == deleteCellsButton) {
            Main.deleteCells();
            cellsCountLabel.setText("Cells Count: " + (cellId + 1));
            System.out.println("Cells deleted");
        }
        else if (event.getSource()==randomGridButton)  {
            System.out.println("Calling random grid");

            Main.generateRandomGrid();
        }
        else if (event.getSource() == updateGridButton)  {
            Main.updateGrid();
        }
        else if (event.getSource() == startIterationsButton)  {
            Main.setIterationsStatus(true);
            Main.startIterations();
        }
        else if(event.getSource()==stopIterationsButton)  {
            Main.setIterationsStatus(false);
        }
        else  {
            System.out.println("Unknown button");
        }
    }
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Normal case

Applications with user-interfaces normally work with a single thread, commonly something like this:

loop {
    handleEvents()
    updateWorld()
    renderWorld()
}

It's a bit hard to tell since the code does not seem complete, but from your description it seems to be doing handleEvents()/renderWorld() in one thread (by Application itself) and updateWorld() in another (your AnimationTimer), that will give thread-conflicts.

Two possible solutions (I recommend 1 in this case, 2 is useful when incoming events must be coming through its own thread):

  1. Don't start a separate thread, jack into the update thread of the Application instead, there most likely is one available to jack in to
  2. Create a queue of incoming events in handle() and process the queue in the updateWorld() thread.

If you want multi-threading:

Multithreading is easiest to achieve in the updateWorld() part. This can be done by splitting the work that needs to be done into multiple threads, waiting for all of them to finish their work and then exiting updateWorld() so render() can commence.

Another way would be to run updateWorld() in the background while renderWorld() is running, but the synchronization of that can be tricky to handle.

Multithreading renderWorld() is beyond my capabilities.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.