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;
}
}
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;
}
}
}
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);
}
}
});
}
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);
}
}
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.