package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; import java.lang.Thread.UncaughtExceptionHandler; import java.util.LinkedList; import java.util.List; import java.util.Stack; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.block.Block; import de.jaschastarke.bukkit.lib.ModuleLogger; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState; import de.jaschastarke.minecraft.limitedcreative.blockstate.AbstractModel.HasBlockState; import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.Cuboid; import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; import de.jaschastarke.minecraft.limitedcreative.blockstate.ThreadedModel; public class ThreadLink { private static final int BATCH_ACTION_LENGTH = 25; private static final int QUEUE_ACCESS_WARNING_DURATION = 5; // ms private static final int COUNT_WARNING_QUEUE = 5; private static final int COUNT_ERROR_QUEUE = 20; private static final int QUEUE_TIMING_DURATION = 500; // ms private static final int STARTUP_TIMING = 30000; // ms private long lastTimeout; private Stack<Action> updateQueue = new Stack<Action>(); private boolean shutdown = false; private ModuleLogger log; private ThreadedModel model; private Thread thread; public ThreadLink(ThreadedModel threadedModel, DBQueries queries) { model = threadedModel; log = threadedModel.getModel().getLog(); /* * In theory we could add multiple threads, e.g. 1 write and 2 read threads. */ thread = new DBThread(queries); thread.setName("LC BlockState DB-Thread"); thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable e) { e.printStackTrace(); log.severe("Thread " + thread.getName() + " encoutered an uncaught Exception: " + e.getMessage()); } }); } private class DBThread extends Thread { private DBQueries q; public DBThread(DBQueries queries) { super(); this.q = queries; } public void run() { if (getModule().isDebug()) log.debug("DB-Thread '" + Thread.currentThread().getName() + "' started."); lastTimeout = System.currentTimeMillis() + STARTUP_TIMING; while (!shutdown || !updateQueue.isEmpty()) { try { List<Action> acts = new LinkedList<Action>(); synchronized (updateQueue) { while (updateQueue.isEmpty() && !shutdown) updateQueue.wait(); if (updateQueue.size() > (BATCH_ACTION_LENGTH * COUNT_ERROR_QUEUE)) { if (System.currentTimeMillis() - lastTimeout > QUEUE_TIMING_DURATION) { getLog().warn("Extrem large DB-Queue in " + Thread.currentThread().getName() + ": " + updateQueue.size()); lastTimeout = System.currentTimeMillis(); } } else if (updateQueue.size() > (BATCH_ACTION_LENGTH * COUNT_WARNING_QUEUE)) { if (System.currentTimeMillis() - lastTimeout > QUEUE_TIMING_DURATION) { getLog().info("Large DB-Queue in " + Thread.currentThread().getName() + ": " + updateQueue.size()); lastTimeout = System.currentTimeMillis(); } } else if (updateQueue.size() <= BATCH_ACTION_LENGTH) { lastTimeout = System.currentTimeMillis(); } for (int i = 0; i < BATCH_ACTION_LENGTH && !updateQueue.isEmpty(); i++) { acts.add(updateQueue.pop()); } } long t = 0; if (getModule().isDebug()) { t = System.currentTimeMillis(); log.debug("DB-Thread '" + Thread.currentThread().getName() + "' run: " + acts.size()); } for (Action act : acts) { if (!shutdown || !(act instanceof CacheChunkAction)) { if (act instanceof CallableAction) { synchronized (act) { act.process(ThreadLink.this, this.q); act.notify(); } } else { act.process(ThreadLink.this, this.q); } } } if (getModule().isDebug()) log.debug("DB-Thread '" + Thread.currentThread().getName() + "' execution time: " + (System.currentTimeMillis() - t) + "ms"); } catch (InterruptedException e) { e.printStackTrace(); log.severe("DB-Thread '" + Thread.currentThread().getName() + "' was harmfull interupted"); } Thread.yield(); } if (getModule().isDebug()) log.debug("DB-Thread " + Thread.currentThread().getName() + " finished."); } } public void start() { if (!thread.isAlive()) thread.start(); } public void queueUpdate(Block block) { long l = System.currentTimeMillis(); synchronized (updateQueue) { updateQueue.add(new UpdateBlockStateAction(block)); updateQueue.notify(); } long l2 = System.currentTimeMillis(); if (l2 - l > QUEUE_ACCESS_WARNING_DURATION) { getLog().warn("queueUpdate-action took to long: " + (l - 2) + "ms"); } } public BlockState callUpdate(Block block) { FetchBlockStateAction action = new FetchBlockStateAction(block); synchronized (updateQueue) { updateQueue.push(action); updateQueue.notify(); } return action.getValue(); } public void queue(Action act) { synchronized (updateQueue) { updateQueue.add(act); updateQueue.notify(); } } public <T> T call(CallableAction<T> act) { synchronized (updateQueue) { updateQueue.push(act); updateQueue.notify(); } return act.getValue(); } public List<BlockState> callUpdate(Cuboid c) { FetchCuboidAction action = new FetchCuboidAction(c); synchronized (updateQueue) { updateQueue.push(action); updateQueue.notify(); } return action.getValue(); } public void queueMetaMove(Location from, Location to) { synchronized (updateQueue) { updateQueue.add(new MoveBlockStateAction(from, to)); updateQueue.notify(); } } public void queueChunkLoad(Chunk chunk) { synchronized (updateQueue) { updateQueue.add(new CacheChunkAction(chunk)); updateQueue.notify(); } } public void queueTransaction(Transaction transaction) { synchronized (updateQueue) { updateQueue.add(transaction); updateQueue.notify(); } } public void shutdown() throws InterruptedException { synchronized (updateQueue) { shutdown = true; updateQueue.notify(); } thread.join(); } public HasBlockState getMetaState(Block block) { return model.getMetaState(block); } public void setMetaState(Block block, BlockState state) { model.setMetaState(block, state); } public void setSimpleMetaState(Block block, BlockState state) { model.setSimpleMetaDataState(block, state); } public ModBlockStates getModule() { return model.getModel(); } public ModuleLogger getLog() { return log; } }