Search code examples
javajavafxtask

JavaFX - Using Task<> to run a jobs in the background lags the program


I'm trying to write an implementation of MapPanel by Stepan Rutz in JavaFX.

Stepan's map allows the user to move around the map while the map tiles haven't been loaded and I'm trying to get that same effect.To do it I'm using Task<> to run the painting of the tiles in the background whenever is required (pretty often).

The thing is that whenever a new tile that hasn't been painted before needs to be added to the map the program creates a new worker to do the task of creating the tile (a petition to my server where they are created).

I think that I need to restrain somehow the number of tasks that are being created but I don't know how to accomplish it while having the sensation of smoothness when moving the map.

Here is the code that creates the tiles:

1.First a for loop for every tile that is needed:

    public void paintMap(Point mapPosition, Point scalePosition) {
        double width = this.getWidth();
        double height = this.getHeight();

        int x0 = (int) Math.floor((this.mapPosition.getX()) / TILE_SIZE);
        int y0 = (int) Math.floor((this.mapPosition.getY()) / TILE_SIZE);
        int x1 = (int) Math.ceil((mapPosition.getX() + width) / TILE_SIZE);
        int y1 = (int) Math.ceil((mapPosition.getY() + height) / TILE_SIZE);
        
        int dy = y0 * TILE_SIZE - (int) this.mapPosition.getY();
        for(int y = y0; y < y1; ++y) {
            int dx = x0 * TILE_SIZE - (int) this.mapPosition.getX();
            for(int x = x0; x < x1; ++x) {

                paintTile(dx, dy, x, y);
                dx += TILE_SIZE;
            }
            dy += TILE_SIZE;
        }
    }

  1. The function that paints the tile:
    private void paintTile(int dx, int dy, int x, int y) {
        int xTileCount = 1 << zoom;
        int yTileCount = 1 << zoom;
        boolean tileInBounds = x >= 0 && x < xTileCount && y >= 0 && y < yTileCount;
        if(tileInBounds) {
            TileCache cache = getCache();
            Image image = cache.get(x, y, zoom);
            if(image == null) {
                String tileURL = getTileString(x,y, zoom);
                System.out.println(tileURL);
                invokeTileTaskController(this, tileURL, cache, x, y, dx, dy);

            }
            if(image != null) {
                ImageView iv = new ImageView(image);
                iv.setX(dx);
                iv.setY(dy);
                this.getChildren().add(iv); 
                iv = null;

            }
        }
    }
  1. The function that creates the task:
private void invokeTileTaskController(MapPanel mp, String tileURL, TileCache cache, int x, int y, int dx, int dy) {
        TileTaskController ttc = new TileTaskController(tileURL);
        Thread ttcThread = new Thread(ttc);
        ttcThread.start();
        ttc.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, 
                new EventHandler<WorkerStateEvent>() {
            @Override
            public void handle(WorkerStateEvent t) {
                Image tileImage = ttc.getValue();
                if(tileImage!=null) {
                    cache.put(x, y, zoom, tileImage);
                    ImageView iv = new ImageView(tileImage);
                    iv.setX(dx);
                    iv.setY(dy);
                    mp.getChildren().add(iv);
                }
            }
        });     
    }
  1. And the Task class:
public class TileTaskController extends Task<Image>
{
    private Image tileImage;
    private final String tileURL;
    public TileTaskController(String tileURL) 
    {
        this.tileImage = new Image(getClass().getResourceAsStream("image-error-icon-7.jpg"));
        this.tileURL = tileURL;
    }
    
    @Override
    protected Image call() throws Exception 
    {
        Image tileImage = new Image(this.tileURL);
        updateValue(tileImage);
        return new Image(this.tileURL);
    }

}


Solution

  • As indicated by @vgr no Task<> is needed to avoid lagging and load multiple images simultaneously. Just adding the parameter true to the Image with the url does the trick:

    Image i = new Image(URL, true);
    

    This way JavaFX loads the image in the background and loads it whenever its ready, solving the multitasking problem I had.