Search code examples
javamultithreadingconcurrencylibgdxfuturetask

Wait for FutureTask completion in game loop


I want to setup a FutureTask for a expensive path finding task. So I created this Callable class.

public class GetPath implements Callable<List<Coordinate>> {

    private Coordinate start;
    private Coordinate end;

    public GetPath(Coordinate start, Coordinate end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public List<Coordinate> call() throws Exception {

        IndexedNodeImplementation location = TestMap.mapgraph.getNodeByCoord(start.x, start.y);
        IndexedNodeImplementation destination = TestMap.mapgraph.getNodeByCoord(end.x, end.y);
        GraphPathImplementation resultPath = new GraphPathImplementation();

        List<Coordinate> path = new ArrayList<Coordinate>();

        TestMap.pathFinder.searchNodePath(location, destination, new HeuristicImplementation(), resultPath);

        if (resultPath.getCount() > 0)
        {
            for (int i = 1; i < resultPath.getCount(); i++)
            {
                path.add(new Coordinate(resultPath.get(i).origin(), true));
            }
        }
        return path;
    }
}

When I execute this in a example I have seen in the constructor it still "waits" until the task is finished. While this works I am still experiencing frame drops as if I would just do it without threading.

executor = Executors.newFixedThreadPool(1);
task = new FutureTask(new GetPath(creature.getLocation(), coordinate));
//TestMap.executor.execute(task);

executor.execute(task);

try {
    path = (List<Coordinate>)task.get();
    } catch (InterruptedException e) {
        System.out.println("Interrupted Exception");
        e.printStackTrace();
    } catch (ExecutionException e) {
        System.out.println("Execution Exception"); // <---
        e.printStackTrace();
    }

    executor.shutdown();
    //Works but is basically the same as just looking up a path in the main thread.

I am aware that it's pretty silly to have a thread pool of one but I'm new to this. Anyway, I tried using the same ExecutorService a larger pool for each of the objects but without success.

Since this method is still holding the program until finished I tried watching the FutureTask in the game loop. And when done I populate the path list and continue with the code.

        if (task.isDone())
        {
            try {
                path = (List<Coordinate>)task.get();
            } catch (InterruptedException e) {
                System.out.println("Interrupted Exception");
                e.printStackTrace();
            } catch (ExecutionException e) {
                System.out.println("Execution Exception");
                e.printStackTrace();
            }
            executor.shutdown();
            //TestMap.executor.shutdown();
        }
        else
        {
            return false;
        }
        //Continue with code and work with path which should now contain elements

But this gives a NullPointerException I am unable to trace. I have checked all variables in my own code and nothing seems to be null. Perhaps I am misinterpreting task.isDone. Or perhaps I should leave the task to the executor to handle and use the executor to determine if the task is done but I am not sure how.

java.util.concurrent.ExecutionException: java.lang.NullPointerException
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:188)
    at com.buckriderstudio.buriedkingdoms.Creatures.Jobs.GoTo.perform(GoTo.java:55)
//...

I am just looking to run some expensive code on another thread without interrupting my main game loop. I don't care how long it takes until it finds a path that is why I rather put it in a separate thread then trying to implement a much better pathfind algorithm like hierarchical A*. I am currently using gdx.ai.pfa.* for my pathfinding.

Upon opening the current program there are 100 units waiting for a path. Each game loop 1 unit gets a path request. I'm not sure if this has any influence on what I am doing here.


Solution

  • If your path finding Runnable is working fine when its run synchronously, but not when it is run asynchronously (your second example). I believe your problem is inside your Runnable, you are trying to access some resource, which is null at the time you access it.

    You basically have a race condition between the line of code that nulls(or disposes) the resource, and the code inside the Runnable which needs to access it. In other words, when you run it synchronously, the Runnable always "gets there first", since the rest of the program is waiting for it to finish. In your case, when its asynchronously, the disposing/nulling code executes before the Runnable gets to the resource.

    You snipped your stack trace, but the last line mentioned a perform method, in GoTo.java

    Your doing something to some of the resources, from outside the Runnable, which means, at the time the runnable is executed, its trying to access something which is null.