mirror of
https://github.com/zDevelopers/ImageOnMap.git
synced 2024-06-27 07:04:45 +02:00
Added image-to-map rendering.
* NEW: Implemented the RendererExecutor for splitted and scaled images. * NEW: Implemented image saving. * NEW: Worker callbacks now take as a parameter the return value of the Runnable. * NEW: Worker runnables can now request data from the main thread using the Future API. * BUG: null is now a valid value for the "name" field. * BUG: Fix map loading for Posters and Single maps. * OPT: The PosterImage class now only makes the primary calculations when instanciated. The splitting is now a separate method. * OPT: When a map data entry is invalid, only the error message is shown in console rather than the full backtrace.
This commit is contained in:
parent
5c369c65cd
commit
5c9feddd6e
|
@ -18,11 +18,13 @@
|
||||||
|
|
||||||
package fr.moribus.imageonmap.commands.maptool;
|
package fr.moribus.imageonmap.commands.maptool;
|
||||||
|
|
||||||
|
import fr.moribus.imageonmap.PluginLogger;
|
||||||
import fr.moribus.imageonmap.commands.Command;
|
import fr.moribus.imageonmap.commands.Command;
|
||||||
import fr.moribus.imageonmap.commands.CommandException;
|
import fr.moribus.imageonmap.commands.CommandException;
|
||||||
import fr.moribus.imageonmap.commands.CommandInfo;
|
import fr.moribus.imageonmap.commands.CommandInfo;
|
||||||
import fr.moribus.imageonmap.commands.Commands;
|
import fr.moribus.imageonmap.commands.Commands;
|
||||||
import fr.moribus.imageonmap.image.ImageRendererExecutor;
|
import fr.moribus.imageonmap.image.ImageRendererExecutor;
|
||||||
|
import fr.moribus.imageonmap.map.ImageMap;
|
||||||
import fr.moribus.imageonmap.worker.WorkerCallback;
|
import fr.moribus.imageonmap.worker.WorkerCallback;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -39,6 +41,7 @@ public class NewCommand extends Command
|
||||||
protected void run() throws CommandException
|
protected void run() throws CommandException
|
||||||
{
|
{
|
||||||
final Player player = playerSender();
|
final Player player = playerSender();
|
||||||
|
boolean scaling = false;
|
||||||
URL url;
|
URL url;
|
||||||
|
|
||||||
if(args.length < 1) throwInvalidArgument("You must give an URL to take the image from.");
|
if(args.length < 1) throwInvalidArgument("You must give an URL to take the image from.");
|
||||||
|
@ -50,26 +53,29 @@ public class NewCommand extends Command
|
||||||
catch(MalformedURLException ex)
|
catch(MalformedURLException ex)
|
||||||
{
|
{
|
||||||
throwInvalidArgument("Invalid URL.");
|
throwInvalidArgument("Invalid URL.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(args.length < 2)
|
if(args.length >= 2)
|
||||||
{
|
{
|
||||||
|
if(args[1].equals("resize")) scaling = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
info("Working ...");
|
info("Rendering ...");
|
||||||
ImageRendererExecutor.Test(new WorkerCallback()
|
ImageRendererExecutor.Render(url, scaling, player.getUniqueId(), new WorkerCallback<ImageMap>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void finished(Object... args)
|
public void finished(ImageMap result)
|
||||||
{
|
{
|
||||||
player.sendMessage("Long task finished !");
|
player.sendMessage("§7Rendering finished !");
|
||||||
|
result.give(player.getInventory());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void errored(Throwable exception)
|
public void errored(Throwable exception)
|
||||||
{
|
{
|
||||||
player.sendMessage("Whoops, an error occured !");
|
player.sendMessage("§cMap rendering failed : " + exception.getMessage());
|
||||||
|
PluginLogger.LogWarning("Rendering from '" + player.getName() + "' failed", exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,14 +48,28 @@ public class ImageIOExecutor extends Worker
|
||||||
|
|
||||||
static public void loadImage(final File file, final Renderer mapRenderer)
|
static public void loadImage(final File file, final Renderer mapRenderer)
|
||||||
{
|
{
|
||||||
instance.submitQuery(new WorkerRunnable()
|
instance.submitQuery(new WorkerRunnable<Void>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Exception
|
public Void run() throws Exception
|
||||||
{
|
{
|
||||||
BufferedImage image = ImageIO.read(file);
|
BufferedImage image = ImageIO.read(file);
|
||||||
mapRenderer.setImage(image);
|
mapRenderer.setImage(image);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public void saveImage(final File file, final BufferedImage image)
|
||||||
|
{
|
||||||
|
instance.submitQuery(new WorkerRunnable<Void>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Void run() throws Throwable
|
||||||
|
{
|
||||||
|
ImageIO.write(image, "png", file);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,21 @@
|
||||||
|
|
||||||
package fr.moribus.imageonmap.image;
|
package fr.moribus.imageonmap.image;
|
||||||
|
|
||||||
|
import fr.moribus.imageonmap.ImageOnMap;
|
||||||
|
import fr.moribus.imageonmap.PluginLogger;
|
||||||
|
import fr.moribus.imageonmap.map.ImageMap;
|
||||||
|
import fr.moribus.imageonmap.map.MapManager;
|
||||||
import fr.moribus.imageonmap.worker.Worker;
|
import fr.moribus.imageonmap.worker.Worker;
|
||||||
import fr.moribus.imageonmap.worker.WorkerCallback;
|
import fr.moribus.imageonmap.worker.WorkerCallback;
|
||||||
import fr.moribus.imageonmap.worker.WorkerRunnable;
|
import fr.moribus.imageonmap.worker.WorkerRunnable;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
public class ImageRendererExecutor extends Worker
|
public class ImageRendererExecutor extends Worker
|
||||||
{
|
{
|
||||||
|
@ -41,19 +53,132 @@ public class ImageRendererExecutor extends Worker
|
||||||
|
|
||||||
private ImageRendererExecutor()
|
private ImageRendererExecutor()
|
||||||
{
|
{
|
||||||
super("Image IO");
|
super("Image IO", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void Test(WorkerCallback callback)
|
static public void Test(WorkerCallback callback)
|
||||||
{
|
{
|
||||||
instance.submitQuery(new WorkerRunnable()
|
instance.submitQuery(new WorkerRunnable<Void>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Throwable
|
public Void run() throws Throwable
|
||||||
{
|
{
|
||||||
Thread.sleep(5000);
|
Thread.sleep(5000);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public void Render(final URL url, final boolean scaling, final UUID playerUUID, WorkerCallback<ImageMap> callback)
|
||||||
|
{
|
||||||
|
instance.submitQuery(new WorkerRunnable<ImageMap>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ImageMap run() throws Throwable
|
||||||
|
{
|
||||||
|
final BufferedImage image = ImageIO.read(url);
|
||||||
|
if(image == null) throw new IOException("The given URL is not a valid image");
|
||||||
|
|
||||||
|
if(scaling) return RenderSingle(image, playerUUID);
|
||||||
|
else return RenderPoster(image, playerUUID);
|
||||||
|
}
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
static private ImageMap RenderSingle(final BufferedImage image, final UUID playerUUID) throws Throwable
|
||||||
|
{
|
||||||
|
final short mapID = instance.submitToMainThread(new Callable<Short>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Short call() throws Exception
|
||||||
|
{
|
||||||
|
return MapManager.getNewMapsIds(1)[0];
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
final BufferedImage finalImage = ResizeImage(image, ImageMap.WIDTH, ImageMap.HEIGHT);
|
||||||
|
|
||||||
|
ImageIOExecutor.saveImage(ImageOnMap.getPlugin().getImageFile(mapID), finalImage);
|
||||||
|
|
||||||
|
final ImageMap newMap = instance.submitToMainThread(new Callable<ImageMap>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ImageMap call() throws Exception
|
||||||
|
{
|
||||||
|
Renderer.installRenderer(finalImage, mapID);
|
||||||
|
return MapManager.createMap(playerUUID, mapID);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
return newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private ImageMap RenderPoster(final BufferedImage image, final UUID playerUUID) throws Throwable
|
||||||
|
{
|
||||||
|
final PosterImage poster = new PosterImage(image);
|
||||||
|
final int mapCount = poster.getImagesCount();
|
||||||
|
|
||||||
|
final Future<short[]> futureMapsIds = instance.submitToMainThread(new Callable<short[]>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public short[] call() throws Exception
|
||||||
|
{
|
||||||
|
return MapManager.getNewMapsIds(mapCount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
poster.splitImages();
|
||||||
|
|
||||||
|
final short[] mapsIDs = futureMapsIds.get();
|
||||||
|
|
||||||
|
for(short mapID : mapsIDs)
|
||||||
|
{
|
||||||
|
ImageIOExecutor.saveImage(ImageOnMap.getPlugin().getImageFile(mapID), image);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ImageMap newMap = instance.submitToMainThread(new Callable<ImageMap>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ImageMap call() throws Exception
|
||||||
|
{
|
||||||
|
Renderer.installRenderer(poster, mapsIDs);
|
||||||
|
return MapManager.createMap(poster, playerUUID, mapsIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
return newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private BufferedImage ResizeImage(BufferedImage source, int destinationW, int destinationH)
|
||||||
|
{
|
||||||
|
float ratioW = (float)destinationW / (float)source.getWidth();
|
||||||
|
float ratioH = (float)destinationH / (float)source.getHeight();
|
||||||
|
int finalW, finalH;
|
||||||
|
|
||||||
|
if(ratioW < ratioH)
|
||||||
|
{
|
||||||
|
finalW = destinationW;
|
||||||
|
finalH = (int)(source.getHeight() * ratioW);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
finalW = (int)(source.getWidth() * ratioH);
|
||||||
|
finalH = destinationH;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
x = (destinationW - finalW) / 2;
|
||||||
|
y = (destinationH - finalH) / 2;
|
||||||
|
PluginLogger.LogInfo(finalW + " " + finalH + " : " + x + " " + y);
|
||||||
|
|
||||||
|
BufferedImage newImage = new BufferedImage(destinationW, destinationH, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
Graphics graphics = newImage.getGraphics();
|
||||||
|
graphics.drawImage(source, x, y, finalW, finalH, null);
|
||||||
|
graphics.dispose();
|
||||||
|
return newImage;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ public class PosterImage
|
||||||
static private final int WIDTH = 128;
|
static private final int WIDTH = 128;
|
||||||
static private final int HEIGHT = 128;
|
static private final int HEIGHT = 128;
|
||||||
|
|
||||||
|
private BufferedImage originalImage;
|
||||||
private BufferedImage[] cutImages;
|
private BufferedImage[] cutImages;
|
||||||
private int lines;
|
private int lines;
|
||||||
private int columns;
|
private int columns;
|
||||||
|
@ -36,15 +37,16 @@ public class PosterImage
|
||||||
private int remainderX, remainderY;
|
private int remainderX, remainderY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and splits a new Poster from an entire image
|
* Creates a new Poster from an entire image
|
||||||
* @param originalImage the original image
|
* @param originalImage the original image
|
||||||
*/
|
*/
|
||||||
public PosterImage(BufferedImage originalImage)
|
public PosterImage(BufferedImage originalImage)
|
||||||
{
|
{
|
||||||
splitImages(originalImage);
|
this.originalImage = originalImage;
|
||||||
|
calculateDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void splitImages(BufferedImage originalImage)
|
private void calculateDimensions()
|
||||||
{
|
{
|
||||||
int originalWidth = originalImage.getWidth();
|
int originalWidth = originalImage.getWidth();
|
||||||
int originalHeight = originalImage.getHeight();
|
int originalHeight = originalImage.getHeight();
|
||||||
|
@ -59,6 +61,10 @@ public class PosterImage
|
||||||
if(remainderY > 0) lines++;
|
if(remainderY > 0) lines++;
|
||||||
|
|
||||||
cutImagesCount = columns * lines;
|
cutImagesCount = columns * lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void splitImages()
|
||||||
|
{
|
||||||
cutImages = new BufferedImage[cutImagesCount];
|
cutImages = new BufferedImage[cutImagesCount];
|
||||||
|
|
||||||
int imageX;
|
int imageX;
|
||||||
|
@ -73,6 +79,8 @@ public class PosterImage
|
||||||
}
|
}
|
||||||
imageY += HEIGHT;
|
imageY += HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalImage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package fr.moribus.imageonmap.image;
|
package fr.moribus.imageonmap.image;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.map.MapCanvas;
|
import org.bukkit.map.MapCanvas;
|
||||||
import org.bukkit.map.MapRenderer;
|
import org.bukkit.map.MapRenderer;
|
||||||
|
@ -35,6 +36,19 @@ public class Renderer extends MapRenderer
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public void installRenderer(PosterImage image, short[] mapsIds)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < mapsIds.length; i++)
|
||||||
|
{
|
||||||
|
installRenderer(image.getImageAt(i), mapsIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void installRenderer(BufferedImage image, short mapID)
|
||||||
|
{
|
||||||
|
installRenderer(Bukkit.getMap(mapID)).setImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
static public Renderer installRenderer(MapView map)
|
static public Renderer installRenderer(MapView map)
|
||||||
{
|
{
|
||||||
Renderer renderer = new Renderer();
|
Renderer renderer = new Renderer();
|
||||||
|
|
|
@ -21,8 +21,11 @@ package fr.moribus.imageonmap.map;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.configuration.InvalidConfigurationException;
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
public abstract class ImageMap implements ConfigurationSerializable
|
public abstract class ImageMap implements ConfigurationSerializable
|
||||||
{
|
{
|
||||||
|
@ -48,6 +51,16 @@ public abstract class ImageMap implements ConfigurationSerializable
|
||||||
public abstract short[] getMapsIDs();
|
public abstract short[] getMapsIDs();
|
||||||
public abstract boolean managesMap(short mapID);
|
public abstract boolean managesMap(short mapID);
|
||||||
|
|
||||||
|
public void give(Inventory inventory)
|
||||||
|
{
|
||||||
|
short[] mapsIDs = getMapsIDs();
|
||||||
|
for(short mapID : mapsIDs)
|
||||||
|
{
|
||||||
|
ItemStack itemMap = new ItemStack(Material.MAP, 1, mapID);
|
||||||
|
inventory.addItem(itemMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ====== Serialization methods ====== */
|
/* ====== Serialization methods ====== */
|
||||||
|
|
||||||
static public ImageMap fromConfig(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException
|
static public ImageMap fromConfig(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException
|
||||||
|
@ -73,7 +86,7 @@ public abstract class ImageMap implements ConfigurationSerializable
|
||||||
protected ImageMap(Map<String, Object> map, UUID userUUID, Type mapType) throws InvalidConfigurationException
|
protected ImageMap(Map<String, Object> map, UUID userUUID, Type mapType) throws InvalidConfigurationException
|
||||||
{
|
{
|
||||||
this(userUUID, mapType);
|
this(userUUID, mapType);
|
||||||
this.imageName = getFieldValue(map, "name");
|
this.imageName = getNullableFieldValue(map, "name");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void postSerialize(Map<String, Object> map);
|
protected abstract void postSerialize(Map<String, Object> map);
|
||||||
|
@ -84,10 +97,18 @@ public abstract class ImageMap implements ConfigurationSerializable
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
map.put("type", mapType.toString());
|
map.put("type", mapType.toString());
|
||||||
map.put("name", imageName);
|
map.put("name", imageName);
|
||||||
|
this.postSerialize(map);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
static protected <T> T getFieldValue(Map<String, Object> map, String fieldName) throws InvalidConfigurationException
|
static protected <T> T getFieldValue(Map<String, Object> map, String fieldName) throws InvalidConfigurationException
|
||||||
|
{
|
||||||
|
T value = getNullableFieldValue(map, fieldName);
|
||||||
|
if(value == null) throw new InvalidConfigurationException("Field value not found for \"" + fieldName + "\"");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static protected <T> T getNullableFieldValue(Map<String, Object> map, String fieldName) throws InvalidConfigurationException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package fr.moribus.imageonmap.map;
|
package fr.moribus.imageonmap.map;
|
||||||
|
|
||||||
import fr.moribus.imageonmap.ImageOnMap;
|
import fr.moribus.imageonmap.ImageOnMap;
|
||||||
|
import fr.moribus.imageonmap.image.PosterImage;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
@ -37,8 +38,8 @@ abstract public class MapManager
|
||||||
|
|
||||||
static public void exit()
|
static public void exit()
|
||||||
{
|
{
|
||||||
playerMaps.clear();
|
|
||||||
save();
|
save();
|
||||||
|
playerMaps.clear();
|
||||||
if(autosaveTask != null) autosaveTask.cancel();
|
if(autosaveTask != null) autosaveTask.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +55,43 @@ abstract public class MapManager
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public ImageMap createMap(UUID playerUUID, short mapID)
|
||||||
|
{
|
||||||
|
ImageMap newMap = new SingleMap(playerUUID, mapID);
|
||||||
|
addMap(newMap, playerUUID);
|
||||||
|
return newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public ImageMap createMap(PosterImage image, UUID playerUUID, short[] mapsIDs)
|
||||||
|
{
|
||||||
|
ImageMap newMap;
|
||||||
|
if(image.getImagesCount() == 1)
|
||||||
|
{
|
||||||
|
newMap = new SingleMap(playerUUID, mapsIDs[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newMap = new PosterMap(playerUUID, mapsIDs, image.getColumns(), image.getLines());
|
||||||
|
}
|
||||||
|
addMap(newMap, playerUUID);
|
||||||
|
return newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public short[] getNewMapsIds(int amount)
|
||||||
|
{
|
||||||
|
short[] mapsIds = new short[amount];
|
||||||
|
for(int i = 0; i < amount; i++)
|
||||||
|
{
|
||||||
|
mapsIds[i] = Bukkit.createMap(Bukkit.getWorlds().get(0)).getId();
|
||||||
|
}
|
||||||
|
return mapsIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void addMap(ImageMap map, UUID playerUUID)
|
||||||
|
{
|
||||||
|
getPlayerMapStore(playerUUID).addMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
static public void notifyModification(UUID playerUUID)
|
static public void notifyModification(UUID playerUUID)
|
||||||
{
|
{
|
||||||
getPlayerMapStore(playerUUID).notifyModification();
|
getPlayerMapStore(playerUUID).notifyModification();
|
||||||
|
|
|
@ -54,6 +54,12 @@ public class PlayerMapStore implements ConfigurationSerializable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addMap(ImageMap map)
|
||||||
|
{
|
||||||
|
mapList.add(map);
|
||||||
|
notifyModification();
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Getters & Setters ===== */
|
/* ===== Getters & Setters ===== */
|
||||||
|
|
||||||
public UUID getUUID()
|
public UUID getUUID()
|
||||||
|
@ -104,7 +110,7 @@ public class PlayerMapStore implements ConfigurationSerializable
|
||||||
}
|
}
|
||||||
catch(InvalidConfigurationException ex)
|
catch(InvalidConfigurationException ex)
|
||||||
{
|
{
|
||||||
PluginLogger.LogWarning("Could not load map data", ex);
|
PluginLogger.LogWarning("Could not load map data : " + ex.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,6 +151,7 @@ public class PlayerMapStore implements ConfigurationSerializable
|
||||||
{
|
{
|
||||||
PluginLogger.LogError("Could not save maps file for player " + playerUUID.toString(), ex);
|
PluginLogger.LogError("Could not save maps file for player " + playerUUID.toString(), ex);
|
||||||
}
|
}
|
||||||
|
PluginLogger.LogInfo("Saving maps file for " + playerUUID.toString());
|
||||||
modified = false;
|
modified = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class PosterMap extends ImageMap
|
||||||
|
|
||||||
columnCount = getFieldValue(map, "columns");
|
columnCount = getFieldValue(map, "columns");
|
||||||
rowCount = getFieldValue(map, "rows");
|
rowCount = getFieldValue(map, "rows");
|
||||||
mapsIDs = getFieldValue(map, "mapIDs");
|
mapsIDs = getFieldValue(map, "mapsIDs");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -49,7 +49,8 @@ public class SingleMap extends ImageMap
|
||||||
public SingleMap(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException
|
public SingleMap(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException
|
||||||
{
|
{
|
||||||
super(map, userUUID, Type.SINGLE);
|
super(map, userUUID, Type.SINGLE);
|
||||||
mapID = getFieldValue(map, "mapID");
|
int _mapID = getFieldValue(map, "mapID");
|
||||||
|
mapID = (short) _mapID;//Meh
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,6 +20,8 @@ package fr.moribus.imageonmap.worker;
|
||||||
|
|
||||||
import fr.moribus.imageonmap.PluginLogger;
|
import fr.moribus.imageonmap.PluginLogger;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
public abstract class Worker
|
public abstract class Worker
|
||||||
{
|
{
|
||||||
|
@ -27,12 +29,19 @@ public abstract class Worker
|
||||||
private final ArrayDeque<WorkerRunnable> runQueue = new ArrayDeque<>();
|
private final ArrayDeque<WorkerRunnable> runQueue = new ArrayDeque<>();
|
||||||
|
|
||||||
private final WorkerCallbackManager callbackManager;
|
private final WorkerCallbackManager callbackManager;
|
||||||
|
private final WorkerMainThreadExecutor mainThreadExecutor;
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
|
||||||
protected Worker(String name)
|
protected Worker(String name)
|
||||||
|
{
|
||||||
|
this(name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Worker(String name, boolean runMainThreadExecutor)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.callbackManager = new WorkerCallbackManager(name);
|
this.callbackManager = new WorkerCallbackManager(name);
|
||||||
|
this.mainThreadExecutor = runMainThreadExecutor ? new WorkerMainThreadExecutor(name) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init()
|
public void init()
|
||||||
|
@ -43,6 +52,7 @@ public abstract class Worker
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
callbackManager.init();
|
callbackManager.init();
|
||||||
|
if(mainThreadExecutor != null) mainThreadExecutor.init();
|
||||||
thread = createThread();
|
thread = createThread();
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
@ -51,6 +61,7 @@ public abstract class Worker
|
||||||
{
|
{
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
callbackManager.exit();
|
callbackManager.exit();
|
||||||
|
if(mainThreadExecutor != null) mainThreadExecutor.exit();
|
||||||
thread = null;
|
thread = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,12 +86,11 @@ public abstract class Worker
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
currentRunnable.run();
|
callbackManager.callback(currentRunnable, currentRunnable.run());
|
||||||
callbackManager.callback(currentRunnable);
|
|
||||||
}
|
}
|
||||||
catch(Throwable ex)
|
catch(Throwable ex)
|
||||||
{
|
{
|
||||||
callbackManager.callback(currentRunnable, ex);
|
callbackManager.callback(currentRunnable, null, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,12 +104,17 @@ public abstract class Worker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void submitQuery(WorkerRunnable runnable, WorkerCallback callback, Object... args)
|
protected void submitQuery(WorkerRunnable runnable, WorkerCallback callback)
|
||||||
{
|
{
|
||||||
callbackManager.setupCallback(runnable, callback, args);
|
callbackManager.setupCallback(runnable, callback);
|
||||||
submitQuery(runnable);
|
submitQuery(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected <T> Future<T> submitToMainThread(Callable<T> callable)
|
||||||
|
{
|
||||||
|
if(mainThreadExecutor != null) return mainThreadExecutor.submit(callable);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Thread createThread()
|
private Thread createThread()
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
*/
|
*/
|
||||||
package fr.moribus.imageonmap.worker;
|
package fr.moribus.imageonmap.worker;
|
||||||
|
|
||||||
public interface WorkerCallback
|
public interface WorkerCallback<T>
|
||||||
{
|
{
|
||||||
public void finished(Object... args);
|
public void finished(T result);
|
||||||
public void errored(Throwable exception);
|
public void errored(Throwable exception);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
class WorkerCallbackManager implements Runnable
|
class WorkerCallbackManager implements Runnable
|
||||||
{
|
{
|
||||||
static private final int WATCH_LOOP_DELAY = 40;
|
static private final int WATCH_LOOP_DELAY = 5;
|
||||||
|
|
||||||
private final HashMap<WorkerRunnable, WorkerRunnableInfo> callbacks;
|
private final HashMap<WorkerRunnable, WorkerRunnableInfo> callbacks;
|
||||||
private final ArrayDeque<WorkerRunnableInfo> callbackQueue;
|
private final ArrayDeque<WorkerRunnableInfo> callbackQueue;
|
||||||
|
@ -47,28 +47,29 @@ class WorkerCallbackManager implements Runnable
|
||||||
selfTask = Bukkit.getScheduler().runTaskTimer(ImageOnMap.getPlugin(), this, 0, WATCH_LOOP_DELAY);
|
selfTask = Bukkit.getScheduler().runTaskTimer(ImageOnMap.getPlugin(), this, 0, WATCH_LOOP_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupCallback(WorkerRunnable runnable, WorkerCallback callback, Object[] args)
|
public void setupCallback(WorkerRunnable runnable, WorkerCallback callback)
|
||||||
{
|
{
|
||||||
synchronized(callbacks)
|
synchronized(callbacks)
|
||||||
{
|
{
|
||||||
callbacks.put(runnable, new WorkerRunnableInfo(callback, args));
|
callbacks.put(runnable, new WorkerRunnableInfo(callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void callback(WorkerRunnable runnable)
|
public <T> void callback(WorkerRunnable<T> runnable, T result)
|
||||||
{
|
{
|
||||||
callback(runnable, null);
|
callback(runnable, result, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void callback(WorkerRunnable runnable, Throwable exception)
|
public <T> void callback(WorkerRunnable<T> runnable, T result, Throwable exception)
|
||||||
{
|
{
|
||||||
WorkerRunnableInfo runnableInfo;
|
WorkerRunnableInfo<T> runnableInfo;
|
||||||
synchronized(callbacks)
|
synchronized(callbacks)
|
||||||
{
|
{
|
||||||
runnableInfo = callbacks.get(runnable);
|
runnableInfo = callbacks.get(runnable);
|
||||||
}
|
}
|
||||||
if(runnableInfo == null) return;
|
if(runnableInfo == null) return;
|
||||||
runnableInfo.setRunnableException(exception);
|
runnableInfo.setRunnableException(exception);
|
||||||
|
runnableInfo.setResult(result);
|
||||||
|
|
||||||
enqueueCallback(runnableInfo);
|
enqueueCallback(runnableInfo);
|
||||||
}
|
}
|
||||||
|
@ -99,16 +100,15 @@ class WorkerCallbackManager implements Runnable
|
||||||
currentRunnableInfo.runCallback();
|
currentRunnableInfo.runCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WorkerRunnableInfo
|
private class WorkerRunnableInfo<T>
|
||||||
{
|
{
|
||||||
private final WorkerCallback callback;
|
private final WorkerCallback<T> callback;
|
||||||
private final Object[] args;
|
private T result;
|
||||||
private Throwable runnableException;
|
private Throwable runnableException;
|
||||||
|
|
||||||
public WorkerRunnableInfo(WorkerCallback callback, Object[] args)
|
public WorkerRunnableInfo(WorkerCallback callback)
|
||||||
{
|
{
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.args = args;
|
|
||||||
this.runnableException = null;
|
this.runnableException = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,9 +125,14 @@ class WorkerCallbackManager implements Runnable
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
callback.finished(args);
|
callback.finished(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResult(T result)
|
||||||
|
{
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
public Throwable getRunnableException()
|
public Throwable getRunnableException()
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Moribus
|
||||||
|
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fr.moribus.imageonmap.worker;
|
||||||
|
|
||||||
|
import fr.moribus.imageonmap.ImageOnMap;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
class WorkerMainThreadExecutor implements Runnable
|
||||||
|
{
|
||||||
|
static private final int WATCH_LOOP_DELAY = 1;
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final ArrayDeque<WorkerFuture> mainThreadQueue = new ArrayDeque<>();
|
||||||
|
private BukkitTask mainThreadTask;
|
||||||
|
|
||||||
|
public WorkerMainThreadExecutor(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init()
|
||||||
|
{
|
||||||
|
mainThreadTask = Bukkit.getScheduler().runTaskTimer(ImageOnMap.getPlugin(), this, 0, WATCH_LOOP_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exit()
|
||||||
|
{
|
||||||
|
mainThreadTask.cancel();
|
||||||
|
mainThreadTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Future<T> submit(Callable<T> callable)
|
||||||
|
{
|
||||||
|
WorkerFuture<T> future = new WorkerFuture<T>(callable);
|
||||||
|
synchronized(mainThreadQueue)
|
||||||
|
{
|
||||||
|
mainThreadQueue.add(future);
|
||||||
|
}
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
WorkerFuture currentFuture;
|
||||||
|
synchronized(mainThreadQueue)
|
||||||
|
{
|
||||||
|
if(mainThreadQueue.isEmpty()) return;
|
||||||
|
currentFuture = mainThreadQueue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFuture.runCallable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WorkerFuture<T> implements Future<T>
|
||||||
|
{
|
||||||
|
private final Callable<T> callable;
|
||||||
|
private boolean isCancelled;
|
||||||
|
private boolean isDone;
|
||||||
|
private Exception executionException;
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
public WorkerFuture(Callable<T> callable)
|
||||||
|
{
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runCallable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = callable.call();
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
executionException = ex;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
isDone = true;
|
||||||
|
synchronized(this){this.notifyAll();}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cancel(boolean mayInterruptIfRunning)
|
||||||
|
{
|
||||||
|
if(this.isCancelled || this.isDone) return false;
|
||||||
|
this.isCancelled = true;
|
||||||
|
this.isDone = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled()
|
||||||
|
{
|
||||||
|
return this.isCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDone()
|
||||||
|
{
|
||||||
|
return this.isDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get() throws InterruptedException, ExecutionException
|
||||||
|
{
|
||||||
|
waitForCompletion();
|
||||||
|
if(executionException != null) throw new ExecutionException(executionException);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||||
|
{
|
||||||
|
waitForCompletion(timeout, unit);
|
||||||
|
if(executionException != null) throw new ExecutionException(executionException);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForCompletion(long timeout) throws InterruptedException, TimeoutException
|
||||||
|
{
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
long remainingTime;
|
||||||
|
long timeoutTime = System.currentTimeMillis() + timeout;
|
||||||
|
while(!isDone)
|
||||||
|
{
|
||||||
|
remainingTime = timeoutTime - System.currentTimeMillis();
|
||||||
|
if(remainingTime <= 0) throw new TimeoutException();
|
||||||
|
this.wait(remainingTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForCompletion() throws InterruptedException
|
||||||
|
{
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
while(!isDone) this.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForCompletion(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
|
||||||
|
{
|
||||||
|
long millis = 0;
|
||||||
|
switch(unit)
|
||||||
|
{
|
||||||
|
case NANOSECONDS:
|
||||||
|
millis = timeout / 10^6;
|
||||||
|
break;
|
||||||
|
case MICROSECONDS:
|
||||||
|
millis = timeout / 10^3;
|
||||||
|
break;
|
||||||
|
case MILLISECONDS:
|
||||||
|
millis = timeout;
|
||||||
|
break;
|
||||||
|
case SECONDS:
|
||||||
|
millis = timeout * 10^3;
|
||||||
|
break;
|
||||||
|
case MINUTES:
|
||||||
|
millis = timeout * 10^3 * 60;
|
||||||
|
break;
|
||||||
|
case HOURS:
|
||||||
|
millis = timeout * 10^3 * 3600;
|
||||||
|
break;
|
||||||
|
case DAYS:
|
||||||
|
millis = timeout * 10^3 * 3600 * 24;
|
||||||
|
}
|
||||||
|
waitForCompletion(millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
package fr.moribus.imageonmap.worker;
|
package fr.moribus.imageonmap.worker;
|
||||||
|
|
||||||
public interface WorkerRunnable
|
public interface WorkerRunnable<T>
|
||||||
{
|
{
|
||||||
public void run() throws Throwable;
|
public T run() throws Throwable;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user