mirror of
https://github.com/zDevelopers/ImageOnMap.git
synced 2024-11-14 06:05:36 +01: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;
|
||||
|
||||
import fr.moribus.imageonmap.PluginLogger;
|
||||
import fr.moribus.imageonmap.commands.Command;
|
||||
import fr.moribus.imageonmap.commands.CommandException;
|
||||
import fr.moribus.imageonmap.commands.CommandInfo;
|
||||
import fr.moribus.imageonmap.commands.Commands;
|
||||
import fr.moribus.imageonmap.image.ImageRendererExecutor;
|
||||
import fr.moribus.imageonmap.map.ImageMap;
|
||||
import fr.moribus.imageonmap.worker.WorkerCallback;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@ -39,6 +41,7 @@ public class NewCommand extends Command
|
||||
protected void run() throws CommandException
|
||||
{
|
||||
final Player player = playerSender();
|
||||
boolean scaling = false;
|
||||
URL url;
|
||||
|
||||
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)
|
||||
{
|
||||
throwInvalidArgument("Invalid URL.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(args.length < 2)
|
||||
if(args.length >= 2)
|
||||
{
|
||||
|
||||
if(args[1].equals("resize")) scaling = true;
|
||||
}
|
||||
|
||||
info("Working ...");
|
||||
ImageRendererExecutor.Test(new WorkerCallback()
|
||||
info("Rendering ...");
|
||||
ImageRendererExecutor.Render(url, scaling, player.getUniqueId(), new WorkerCallback<ImageMap>()
|
||||
{
|
||||
@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
|
||||
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)
|
||||
{
|
||||
instance.submitQuery(new WorkerRunnable()
|
||||
instance.submitQuery(new WorkerRunnable<Void>()
|
||||
{
|
||||
@Override
|
||||
public void run() throws Exception
|
||||
public Void run() throws Exception
|
||||
{
|
||||
BufferedImage image = ImageIO.read(file);
|
||||
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;
|
||||
|
||||
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.WorkerCallback;
|
||||
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
|
||||
{
|
||||
@ -41,19 +53,132 @@ public class ImageRendererExecutor extends Worker
|
||||
|
||||
private ImageRendererExecutor()
|
||||
{
|
||||
super("Image IO");
|
||||
super("Image IO", true);
|
||||
}
|
||||
|
||||
static public void Test(WorkerCallback callback)
|
||||
{
|
||||
instance.submitQuery(new WorkerRunnable()
|
||||
instance.submitQuery(new WorkerRunnable<Void>()
|
||||
{
|
||||
@Override
|
||||
public void run() throws Throwable
|
||||
public Void run() throws Throwable
|
||||
{
|
||||
Thread.sleep(5000);
|
||||
return null;
|
||||
}
|
||||
}, 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 HEIGHT = 128;
|
||||
|
||||
private BufferedImage originalImage;
|
||||
private BufferedImage[] cutImages;
|
||||
private int lines;
|
||||
private int columns;
|
||||
@ -36,15 +37,16 @@ public class PosterImage
|
||||
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
|
||||
*/
|
||||
public PosterImage(BufferedImage originalImage)
|
||||
{
|
||||
splitImages(originalImage);
|
||||
this.originalImage = originalImage;
|
||||
calculateDimensions();
|
||||
}
|
||||
|
||||
private void splitImages(BufferedImage originalImage)
|
||||
private void calculateDimensions()
|
||||
{
|
||||
int originalWidth = originalImage.getWidth();
|
||||
int originalHeight = originalImage.getHeight();
|
||||
@ -59,6 +61,10 @@ public class PosterImage
|
||||
if(remainderY > 0) lines++;
|
||||
|
||||
cutImagesCount = columns * lines;
|
||||
}
|
||||
|
||||
public void splitImages()
|
||||
{
|
||||
cutImages = new BufferedImage[cutImagesCount];
|
||||
|
||||
int imageX;
|
||||
@ -73,6 +79,8 @@ public class PosterImage
|
||||
}
|
||||
imageY += HEIGHT;
|
||||
}
|
||||
|
||||
originalImage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@
|
||||
package fr.moribus.imageonmap.image;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.map.MapCanvas;
|
||||
import org.bukkit.map.MapRenderer;
|
||||
@ -35,6 +36,19 @@ public class Renderer extends MapRenderer
|
||||
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)
|
||||
{
|
||||
Renderer renderer = new Renderer();
|
||||
|
@ -21,8 +21,11 @@ package fr.moribus.imageonmap.map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public abstract class ImageMap implements ConfigurationSerializable
|
||||
{
|
||||
@ -48,6 +51,16 @@ public abstract class ImageMap implements ConfigurationSerializable
|
||||
public abstract short[] getMapsIDs();
|
||||
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 ====== */
|
||||
|
||||
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
|
||||
{
|
||||
this(userUUID, mapType);
|
||||
this.imageName = getFieldValue(map, "name");
|
||||
this.imageName = getNullableFieldValue(map, "name");
|
||||
}
|
||||
|
||||
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.put("type", mapType.toString());
|
||||
map.put("name", imageName);
|
||||
this.postSerialize(map);
|
||||
return map;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -19,6 +19,7 @@
|
||||
package fr.moribus.imageonmap.map;
|
||||
|
||||
import fr.moribus.imageonmap.ImageOnMap;
|
||||
import fr.moribus.imageonmap.image.PosterImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Bukkit;
|
||||
@ -37,8 +38,8 @@ abstract public class MapManager
|
||||
|
||||
static public void exit()
|
||||
{
|
||||
playerMaps.clear();
|
||||
save();
|
||||
playerMaps.clear();
|
||||
if(autosaveTask != null) autosaveTask.cancel();
|
||||
}
|
||||
|
||||
@ -54,6 +55,43 @@ abstract public class MapManager
|
||||
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)
|
||||
{
|
||||
getPlayerMapStore(playerUUID).notifyModification();
|
||||
|
@ -54,6 +54,12 @@ public class PlayerMapStore implements ConfigurationSerializable
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addMap(ImageMap map)
|
||||
{
|
||||
mapList.add(map);
|
||||
notifyModification();
|
||||
}
|
||||
|
||||
/* ===== Getters & Setters ===== */
|
||||
|
||||
public UUID getUUID()
|
||||
@ -104,7 +110,7 @@ public class PlayerMapStore implements ConfigurationSerializable
|
||||
}
|
||||
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.LogInfo("Saving maps file for " + playerUUID.toString());
|
||||
modified = false;
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class PosterMap extends ImageMap
|
||||
|
||||
columnCount = getFieldValue(map, "columns");
|
||||
rowCount = getFieldValue(map, "rows");
|
||||
mapsIDs = getFieldValue(map, "mapIDs");
|
||||
mapsIDs = getFieldValue(map, "mapsIDs");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,7 +49,8 @@ public class SingleMap extends ImageMap
|
||||
public SingleMap(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException
|
||||
{
|
||||
super(map, userUUID, Type.SINGLE);
|
||||
mapID = getFieldValue(map, "mapID");
|
||||
int _mapID = getFieldValue(map, "mapID");
|
||||
mapID = (short) _mapID;//Meh
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,6 +20,8 @@ package fr.moribus.imageonmap.worker;
|
||||
|
||||
import fr.moribus.imageonmap.PluginLogger;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public abstract class Worker
|
||||
{
|
||||
@ -27,12 +29,19 @@ public abstract class Worker
|
||||
private final ArrayDeque<WorkerRunnable> runQueue = new ArrayDeque<>();
|
||||
|
||||
private final WorkerCallbackManager callbackManager;
|
||||
private final WorkerMainThreadExecutor mainThreadExecutor;
|
||||
private Thread thread;
|
||||
|
||||
protected Worker(String name)
|
||||
{
|
||||
this(name, false);
|
||||
}
|
||||
|
||||
protected Worker(String name, boolean runMainThreadExecutor)
|
||||
{
|
||||
this.name = name;
|
||||
this.callbackManager = new WorkerCallbackManager(name);
|
||||
this.mainThreadExecutor = runMainThreadExecutor ? new WorkerMainThreadExecutor(name) : null;
|
||||
}
|
||||
|
||||
public void init()
|
||||
@ -43,6 +52,7 @@ public abstract class Worker
|
||||
exit();
|
||||
}
|
||||
callbackManager.init();
|
||||
if(mainThreadExecutor != null) mainThreadExecutor.init();
|
||||
thread = createThread();
|
||||
thread.start();
|
||||
}
|
||||
@ -51,6 +61,7 @@ public abstract class Worker
|
||||
{
|
||||
thread.interrupt();
|
||||
callbackManager.exit();
|
||||
if(mainThreadExecutor != null) mainThreadExecutor.exit();
|
||||
thread = null;
|
||||
}
|
||||
|
||||
@ -75,12 +86,11 @@ public abstract class Worker
|
||||
|
||||
try
|
||||
{
|
||||
currentRunnable.run();
|
||||
callbackManager.callback(currentRunnable);
|
||||
callbackManager.callback(currentRunnable, currentRunnable.run());
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
protected <T> Future<T> submitToMainThread(Callable<T> callable)
|
||||
{
|
||||
if(mainThreadExecutor != null) return mainThreadExecutor.submit(callable);
|
||||
return null;
|
||||
}
|
||||
|
||||
private Thread createThread()
|
||||
{
|
||||
|
@ -17,8 +17,8 @@
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
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 ArrayDeque<WorkerRunnableInfo> callbackQueue;
|
||||
@ -47,28 +47,29 @@ class WorkerCallbackManager implements Runnable
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
runnableInfo = callbacks.get(runnable);
|
||||
}
|
||||
if(runnableInfo == null) return;
|
||||
runnableInfo.setRunnableException(exception);
|
||||
runnableInfo.setResult(result);
|
||||
|
||||
enqueueCallback(runnableInfo);
|
||||
}
|
||||
@ -99,16 +100,15 @@ class WorkerCallbackManager implements Runnable
|
||||
currentRunnableInfo.runCallback();
|
||||
}
|
||||
|
||||
private class WorkerRunnableInfo
|
||||
private class WorkerRunnableInfo<T>
|
||||
{
|
||||
private final WorkerCallback callback;
|
||||
private final Object[] args;
|
||||
private final WorkerCallback<T> callback;
|
||||
private T result;
|
||||
private Throwable runnableException;
|
||||
|
||||
public WorkerRunnableInfo(WorkerCallback callback, Object[] args)
|
||||
public WorkerRunnableInfo(WorkerCallback callback)
|
||||
{
|
||||
this.callback = callback;
|
||||
this.args = args;
|
||||
this.runnableException = null;
|
||||
}
|
||||
|
||||
@ -125,9 +125,14 @@ class WorkerCallbackManager implements Runnable
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.finished(args);
|
||||
callback.finished(result);
|
||||
}
|
||||
}
|
||||
|
||||
public void setResult(T result)
|
||||
{
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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