package de.jaschastarke.minecraft.limitedcreative.blockstate; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; import org.bukkit.Chunk; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.MetadataValue; import org.bukkit.metadata.Metadatable; import de.jaschastarke.database.DatabaseConfigurationException; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; import de.jaschastarke.minecraft.limitedcreative.blockstate.thread.CallableAction; import de.jaschastarke.minecraft.limitedcreative.blockstate.thread.ThreadLink; import de.jaschastarke.minecraft.limitedcreative.blockstate.thread.Transaction; public class ThreadedModel extends AbstractModel implements DBModel, Listener { private ModBlockStates mod; private ThreadLink threads; private MetadataValue metadataSet; private MetadataValue metadataSetRestricted; public ThreadedModel(ModBlockStates mod) { super(mod.getPlugin()); this.mod = mod; metadataSet = new FixedMetadataValue(mod.getPlugin(), new Boolean(true)); metadataSetRestricted = new FixedMetadataValue(mod.getPlugin(), new Object()); } @Override public void onEnable() throws SQLException, DatabaseConfigurationException { DBQueries queries = new DBQueries(mod, mod.getPlugin().getDatabaseConnection()); queries.initTable(); threads = new ThreadLink(this, queries); // We don't keep any reference to queries, because it contains the DB-Connection, which should be only used // from the thread from now on (as SQLite may not threadsafe) for (World w : mod.getPlugin().getServer().getWorlds()) { if (!mod.getConfig().getIgnoredWorlds().containsIgnoreCase(w.getName())) { for (Chunk chunk : w.getLoadedChunks()) { threads.queueChunkLoad(chunk); } } } threads.start(); } @Override public void onDisable() { try { threads.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); mod.getLog().severe("Failed to clean end Database-Thread, maybe BlockStates haven't been saved"); } } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onChunkLoad(ChunkLoadEvent event) { if (!mod.getConfig().getIgnoredWorlds().containsIgnoreCase(event.getWorld().getName())) { threads.queueChunkLoad(event.getChunk()); } } @Override public void moveState(Block from, Block to) { threads.queueMetaMove(from.getLocation(), to.getLocation()); moveMetaState(from, to); } @Override public void removeState(BlockState state) { removeState(state.getLocation().getBlock()); } @Override public void removeState(Block block) { setMetaBlock(block, null); threads.queueUpdate(block); } @Override public Map<Block, Boolean> getRestrictedStates(List<Block> blocks) { Map<Block, Boolean> ret = new HashMap<Block, Boolean>(); for (Block block : blocks) { HasBlockState has = getMetaBlock(block); ret.put(block, has.isRestricted()); } return ret; } @Override public Map<Block, BlockState> getStates(List<Block> blocks) { Map<Block, BlockState> ret = new HashMap<Block, BlockState>(); Cuboid c; do { c = new Cuboid(); for (Block block : blocks) { HasBlockState has = getMetaBlock(block); if (has.getState() != null || has.isNull()) { ret.put(block, has.getState()); } else { c.add(block.getLocation()); ret.put(block, null); } } if (!c.isEmpty()) threads.callUpdate(c); } while(!c.isEmpty()); return ret; } @Override public void cacheStates(Cuboid c) { threads.callUpdate(c); } @Override public BlockState getState(Block block) { HasBlockState has = getMetaBlock(block); if (has.getState() == null && !has.isNull()) { // The DB-Entry isn't set // and the entry doesn't tell us that it knows that it isn't set // (while using the threaded model, even having no Metadata entry, tells us there is no one in DB) return threads.callUpdate(block); } return has.getState(); } @Override public boolean isRestricted(Block block) { return getMetaBlock(block).isRestricted(); } @Override public void setState(BlockState state) { Block block = state.getLocation().getBlock(); boolean store = state.isRestricted() || mod.getConfig().getLogSurvival(); setMetaBlock(block, store ? state : null); threads.queueUpdate(block); } @Override public DBTransaction groupUpdate() { return new GroupUpdate(threads); } private class GroupUpdate extends Transaction { public GroupUpdate(ThreadLink threads) { super(threads); } @Override public void moveState(Block from, Block to) { moveMetaState(from, to); super.moveState(from, to); } @Override public void setState(BlockState state) { Block block = state.getLocation().getBlock(); boolean store = state.isRestricted() || mod.getConfig().getLogSurvival(); setMetaBlock(block, store ? state : null); super.setState(state); } @Override public void removeState(Block block) { setMetaBlock(block, null); super.setState(block); } } /** * Metadata-Interface for the Thread-Link */ public HasBlockState getMetaState(Block block) { return getMetaBlock(block); } /** * Metadata-Interface for the Thread-Link */ public void setMetaState(Block block, BlockState state) { super.setMetaBlock(block, state); } public void setSimpleMetaDataState(Block block, BlockState state) { if (state == null) super.setMetaBlock(block, null); else if (state.isRestricted()) block.setMetadata(BSMDKEY, metadataSetRestricted); else block.setMetadata(BSMDKEY, metadataSet); } protected HasBlockState getMetaBlock(Metadatable m) { HasBlockState has = null; List<MetadataValue> metadata = m.getMetadata(BSMDKEY); for (MetadataValue v : metadata) { if (v.value() instanceof BlockState) { // The actual DB-entry is in Metadata (requires more memory) has = new HasBlockState((BlockState) v.value()); break; } else if (v.getOwningPlugin() == mod.getPlugin()) { if (v == metadataNull) { // Metadata knows, that there is no entry in DB has = new HasBlockState(true); break; } else if (v == metadataSet) { // Metadata knows, that there is survival-entry in DB has = new HasBlockState(true, false); break; } else if (v == metadataSetRestricted) { // Metadata knows, that there is creative-entry in DB has = new HasBlockState(true, true); } break; } } if (has == null) return new HasBlockState(false); else return has; } public static class HasBlockState extends AbstractModel.HasBlockState { private boolean restricted = false; private boolean isNull = false; public HasBlockState(BlockState state) { super(state); restricted = state.isRestricted(); } public HasBlockState(boolean isSet) { super(isSet); isNull = true; } public HasBlockState(boolean isSet, boolean isRestricted) { super(isSet); restricted = isRestricted; } public boolean isRestricted() { return restricted; } public boolean isNull() { return isNull; } } public ModBlockStates getModel() { return mod; } @Override public int cleanUp(final Cleanup target) { return threads.call(new CallableAction<Integer>() { @Override public void process(ThreadLink link, DBQueries q) { this.returnSet = true; try { this.returnValue = q.cleanup(target); } catch (SQLException e) { this.returnValue = -1; mod.getLog().severe(e.getMessage()); mod.getLog().warn("Failed to cleanup BlockState-DB"); } } }); } }