* NEW: Worker runnables can now have callbacks.

These are executed in the Bukkit main thread, therefore 
allowing to use the Bukkit API in a thread-safe manner.
This commit is contained in:
Prokopyl 2015-03-19 02:01:26 +01:00
parent 7a8ec83d6a
commit 15e5c5457f
5 changed files with 191 additions and 8 deletions

View File

@ -20,7 +20,9 @@ package fr.moribus.imageonmap;
import fr.moribus.imageonmap.commands.Commands;
import fr.moribus.imageonmap.image.ImageIOExecutor;
import fr.moribus.imageonmap.image.ImageRendererExecutor;
import fr.moribus.imageonmap.image.MapInitEvent;
import fr.moribus.imageonmap.map.MapManager;
import java.io.File;
import org.bukkit.plugin.java.JavaPlugin;
@ -62,8 +64,11 @@ public final class ImageOnMap extends JavaPlugin
}
}
//Init all the things !
MetricsLite.startMetrics();
ImageIOExecutor.start();
ImageRendererExecutor.start();
MapManager.init();
Commands.init(this);
getServer().getPluginManager().registerEvents(new MapInitEvent(), this);
}
@ -72,6 +77,8 @@ public final class ImageOnMap extends JavaPlugin
public void onDisable()
{
ImageIOExecutor.stop();
ImageRendererExecutor.stop();
MapManager.exit();
}
}

View File

@ -22,6 +22,8 @@ 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.worker.WorkerCallback;
import java.net.MalformedURLException;
import java.net.URL;
import org.bukkit.entity.Player;
@ -36,7 +38,7 @@ public class NewCommand extends Command
@Override
protected void run() throws CommandException
{
Player player = playerSender();
final Player player = playerSender();
URL url;
if(args.length < 1) throwInvalidArgument("You must give an URL to take the image from.");
@ -55,7 +57,21 @@ public class NewCommand extends Command
}
info("Not implemented.");
info("Working ...");
ImageRendererExecutor.Test(new WorkerCallback()
{
@Override
public void finished(Object... args)
{
player.sendMessage("Long task finished !");
}
@Override
public void errored(Throwable exception)
{
player.sendMessage("Whoops, an error occured !");
}
});
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.image;
import fr.moribus.imageonmap.worker.Worker;
import fr.moribus.imageonmap.worker.WorkerCallback;
import fr.moribus.imageonmap.worker.WorkerRunnable;
public class ImageRendererExecutor extends Worker
{
static private ImageRendererExecutor instance;
static public void start()
{
if(instance != null) stop();
instance = new ImageRendererExecutor();
instance.init();
}
static public void stop()
{
instance.exit();
instance = null;
}
private ImageRendererExecutor()
{
super("Image IO");
}
static public void Test(WorkerCallback callback)
{
instance.submitQuery(new WorkerRunnable()
{
@Override
public void run() throws Throwable
{
Thread.sleep(5000);
}
}, callback);
}
}

View File

@ -42,6 +42,7 @@ public abstract class Worker
PluginLogger.LogWarning("Restarting " + name + " thread.");
exit();
}
callbackManager.init();
thread = createThread();
thread.start();
}
@ -49,6 +50,7 @@ public abstract class Worker
public void exit()
{
thread.interrupt();
callbackManager.exit();
thread = null;
}
@ -74,10 +76,11 @@ public abstract class Worker
try
{
currentRunnable.run();
callbackManager.callback(currentRunnable);
}
catch(Throwable ex)
{
PluginLogger.LogError("Exception from thread " + name, ex);
callbackManager.callback(currentRunnable, ex);
}
}
}
@ -91,6 +94,12 @@ public abstract class Worker
}
}
protected void submitQuery(WorkerRunnable runnable, WorkerCallback callback, Object... args)
{
callbackManager.setupCallback(runnable, callback, args);
submitQuery(runnable);
}
private Thread createThread()
{

View File

@ -18,19 +18,23 @@
package fr.moribus.imageonmap.worker;
import fr.moribus.imageonmap.ImageOnMap;
import java.util.ArrayDeque;
import java.util.HashMap;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
class WorkerCallbackManager implements Runnable
{
static private final int WATCH_LOOP_DELAY = 5;
static private final int WATCH_LOOP_DELAY = 40;
private final HashMap<WorkerRunnable, WorkerCallback> callbacks;
private final ArrayDeque<WorkerCallback> callbackQueue;
private final HashMap<WorkerRunnable, WorkerRunnableInfo> callbacks;
private final ArrayDeque<WorkerRunnableInfo> callbackQueue;
private final String name;
private BukkitTask selfTask;
public WorkerCallbackManager(String name)
{
callbacks = new HashMap<>();
@ -40,16 +44,104 @@ class WorkerCallbackManager implements Runnable
public void init()
{
//Bukkit.getScheduler().runTaskTimer(null, 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)
{
synchronized(callbacks)
{
callbacks.put(runnable, new WorkerRunnableInfo(callback, args));
}
}
public void callback(WorkerRunnable runnable)
{
callback(runnable, null);
}
public void callback(WorkerRunnable runnable, Throwable exception)
{
WorkerRunnableInfo runnableInfo;
synchronized(callbacks)
{
runnableInfo = callbacks.get(runnable);
}
if(runnableInfo == null) return;
runnableInfo.setRunnableException(exception);
enqueueCallback(runnableInfo);
}
public void exit()
{
if(selfTask != null) selfTask.cancel();
}
private void enqueueCallback(WorkerRunnableInfo runnableInfo)
{
synchronized(callbackQueue)
{
callbackQueue.add(runnableInfo);
}
}
@Override
public void run()
{
WorkerRunnableInfo currentRunnableInfo;
synchronized(callbackQueue)
{
if(callbackQueue.isEmpty()) return;
currentRunnableInfo = callbackQueue.pop();
}
currentRunnableInfo.runCallback();
}
private class WorkerRunnableInfo
{
private final WorkerCallback callback;
private final Object[] args;
private Throwable runnableException;
public WorkerRunnableInfo(WorkerCallback callback, Object[] args)
{
this.callback = callback;
this.args = args;
this.runnableException = null;
}
public WorkerCallback getCallback()
{
return callback;
}
public void runCallback()
{
if(runnableCrashed())
{
callback.errored(runnableException);
}
else
{
callback.finished(args);
}
}
public Throwable getRunnableException()
{
return runnableException;
}
public void setRunnableException(Throwable runnableException)
{
this.runnableException = runnableException;
}
public boolean runnableCrashed()
{
return this.runnableException != null;
}
}
}