2

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.

Tim B
  • 40,716
  • 16
  • 83
  • 128
Madmenyo
  • 8,389
  • 7
  • 52
  • 99
  • `path = (List)task.get();` blocks until the future completes; the code you have here is equivalent to just running it in the same thread (except that you are creating another thread as well). – Andy Turner Dec 04 '15 at 13:32
  • BTW, declare task as `FutureTask>`, then you don't need to cast the raw return type. – Andy Turner Dec 04 '15 at 13:33
  • @AndyTurner How would I run this Asynchronously? – Madmenyo Dec 04 '15 at 13:34
  • What about using a callback instead of waiting for the task.get(); Instead of returning the "path" you call the callback mechanism passing it the path. Then there is no need for the wait. – Mike Murphy Dec 04 '15 at 13:45
  • @MikeMurphy like a listener? But that would be running on the other thread then right? – Madmenyo Dec 04 '15 at 13:49
  • Not a listener, just an class object with a method you call when the path is found. The method then triggers an event or whatever you need in your game loop. Think "what do I need to happen when the path is found". – Mike Murphy Dec 04 '15 at 13:52
  • 1
    It's not at all silly to have a single thread thread pool. In fact, there's even a special API call just for that use-case: `executors.newSingleThreadedExecutor()`. Suppose you have something that generates a sequence of tasks. Suppose that the tasks must be performed one after the other in strict order. But, suppose the whole sequence can be performed "in the background"... – Solomon Slow Dec 04 '15 at 13:57

2 Answers2

2

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.

JustDanyul
  • 13,813
  • 7
  • 53
  • 71
  • This is probably whats going on. `TestMap.pathFinder.searchNodePath(location, destination, new HeuristicImplementation(), resultPath);` will change. I will try to implement this in the runnable. – Madmenyo Dec 04 '15 at 14:04
  • That indeed got rid of the error. It's just not functioning properly anymore but I am on that. But what if the map graph changes? I should probably have a deep copy of that in the runnable as well. – Madmenyo Dec 04 '15 at 14:06
  • I indeed got it working. Since I used the same pathfinder for each thread this was causing issues. I probably need to generate a node graph in my `Runnable` as well since the map will be dynamic. But this is another expensive calculation that is only necessary when the the map changes on the created path. Thanks! – Madmenyo Dec 04 '15 at 14:15
  • Glad to hear you got it running so quickly, concurrency problems can be tricky as heck to debug :D – JustDanyul Dec 04 '15 at 14:19
  • Well I am not done with it and have read about the horrors so I might hit some problems further down the road ;). – Madmenyo Dec 04 '15 at 14:22
1

If you look at the Javadoc for Future you will see that get waits until the task has completes. If you instead check isDone in your game loop and only call get once that returns true then you will run asynchronously. This is why the second code block runs effectively single-threaded.

In the next example it is better and will run asynchronously but you are calling that each time the loop, this means you shut down the ExecutorService but the task is still done so you then call isDone and get again. That second time is probably what is generating the NullPointerException. You most likely need something to stop it getting called repeatedly, for example setting task to null and checking task!=null && task.isDone().

You should also just create one ExecutorService at the start of the program and then use that repeatedly rather than constantly creating one and then shutting it down.

You need to think about this a little more as well, for example what happens if you try and calculate a new path before the old one is complete? Or the pathing changes?

Tim B
  • 40,716
  • 16
  • 83
  • 128
  • Yeah I will use a single Executor eventually. But in my example I only shut down the executor when `task.isDone()`. – Madmenyo Dec 04 '15 at 13:51
  • Which is true every time you go around the loop after the first time it's done. I've added still more info. – Tim B Dec 04 '15 at 13:52
  • JustDanyul gave the answer. I was trying to use the same pathfinder for each thread. Thanks for looking in to it! – Madmenyo Dec 04 '15 at 14:17