diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/ModBlockStates.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/ModBlockStates.java index b901bf0..fcd8515 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/ModBlockStates.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/ModBlockStates.java @@ -1,5 +1,7 @@ package de.jaschastarke.minecraft.limitedcreative; +import org.bukkit.event.Listener; + import de.jaschastarke.bukkit.lib.CoreModule; import de.jaschastarke.bukkit.lib.commands.AliasHelpedCommand; import de.jaschastarke.bukkit.lib.modules.AdditionalBlockBreaks; @@ -7,10 +9,11 @@ import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockListener; import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockStateCommand; import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockStateConfig; import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel; -import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; import de.jaschastarke.minecraft.limitedcreative.blockstate.DependencyListener; import de.jaschastarke.minecraft.limitedcreative.blockstate.HangingListener; import de.jaschastarke.minecraft.limitedcreative.blockstate.PlayerListener; +import de.jaschastarke.minecraft.limitedcreative.blockstate.SyncronizedModel; +import de.jaschastarke.minecraft.limitedcreative.blockstate.ThreadedModel; import de.jaschastarke.minecraft.limitedcreative.blockstate.worldedit.LCEditSessionFactory; import de.jaschastarke.modularize.IModule; import de.jaschastarke.modularize.ModuleEntry; @@ -19,7 +22,6 @@ import de.jaschastarke.modularize.ModuleEntry.ModuleState; public class ModBlockStates extends CoreModule { private BlockStateConfig config; private FeatureBlockItemSpawn blockDrops; - private DBQueries queries; private BlockStateCommand command; private DBModel model; @@ -56,8 +58,15 @@ public class ModBlockStates extends CoreModule { @Override public void onEnable() { try { - queries = new DBQueries(this, getPlugin().getDatabaseConnection()); - queries.initTable(); + if (model == null) { + if (config.getUseThreading()) + model = new ThreadedModel(this); + else + model = new SyncronizedModel(this); + } + if (model instanceof Listener) + listeners.addListener((Listener) model); + model.onEnable(); } catch (Exception e) { e.printStackTrace(); getLog().warn(plugin.getLocale().trans("block_state.error.sql_connection_failed", getName())); @@ -79,7 +88,11 @@ public class ModBlockStates extends CoreModule { } @Override public void onDisable() { + model.onDisable(); super.onDisable(); + if (model instanceof Listener) + listeners.removeListener((Listener) model); + model = null; } public BlockStateConfig getConfig() { @@ -88,12 +101,7 @@ public class ModBlockStates extends CoreModule { public FeatureBlockItemSpawn getBlockSpawn() { return blockDrops; } - public DBQueries getQueries() { - return queries; - } public DBModel getModel() { - if (model == null) - model = new DBModel(this); return model; } } diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/AbstractModel.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/AbstractModel.java new file mode 100644 index 0000000..432d75a --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/AbstractModel.java @@ -0,0 +1,72 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate; + +import java.util.List; + +import org.bukkit.block.Block; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.metadata.Metadatable; +import org.bukkit.plugin.Plugin; + +public abstract class AbstractModel { + protected static final String BSMDKEY = "blockstate"; + protected MetadataValue metadataNull; + private Plugin plugin; + + protected AbstractModel(Plugin plugin) { + this.plugin = plugin; + metadataNull = new FixedMetadataValue(plugin, new Boolean(null)); + } + + protected void moveMetaState(Block from, Block to) { + HasBlockState metaBlock = getMetaBlock(from); + if (metaBlock.set && metaBlock.state != null) { + BlockState state = metaBlock.state; + state.setLocation(to.getLocation()); + setMetaBlock(to, state); + } else { + removeMetaBlock(to); + } + setMetaBlock(from, null); + } + + protected boolean hasMetaBlock(Metadatable m) { + List metadata = m.getMetadata(BSMDKEY); + for (MetadataValue v : metadata) { + if (v.value() instanceof BlockState) + return true; + } + return false; + } + protected void setMetaBlock(Metadatable m, BlockState s) { + if (s == null) + m.setMetadata(BSMDKEY, metadataNull); + else + m.setMetadata(BSMDKEY, new FixedMetadataValue(plugin, s)); + } + protected HasBlockState getMetaBlock(Metadatable m) { + HasBlockState has = new HasBlockState(); + List metadata = m.getMetadata(BSMDKEY); + for (MetadataValue v : metadata) { + if (v.value() instanceof BlockState) { + has.set = true; + has.state = (BlockState) v.value(); + break; + } else if (v == metadataNull) { + has.set = true; + has.state = null; + break; + } + } + return has; + } + protected void removeMetaBlock(Metadatable m) { + m.removeMetadata(BSMDKEY, plugin); + } + + public static class HasBlockState { + public boolean set = false; + public BlockState state = null; + } + +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockListener.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockListener.java index 3470b27..7d0635f 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockListener.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockListener.java @@ -1,6 +1,5 @@ package de.jaschastarke.minecraft.limitedcreative.blockstate; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -21,6 +20,7 @@ import org.bukkit.metadata.FixedMetadataValue; import de.jaschastarke.bukkit.lib.events.BlockDestroyedEvent; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.DBTransaction; public class BlockListener implements Listener { private ModBlockStates mod; @@ -30,96 +30,62 @@ public class BlockListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { - try { - BlockState s = mod.getModel().getState(event.getBlock()); - if (s != null) { + if (mod.getModel().isRestricted(event.getBlock())) { + if (mod.isDebug()) + mod.getLog().debug("Breaking bad, err.. block: " + event.getBlock().getLocation().toString()); + + if (event.getPlayer().getGameMode() != GameMode.CREATIVE) { if (mod.isDebug()) - mod.getLog().debug("Breaking bad, err.. block: " + s.toString()); - - if (s.isRestricted() && event.getPlayer().getGameMode() != GameMode.CREATIVE) { - if (mod.isDebug()) - mod.getLog().debug("... was placed by creative. Drop prevented"); - mod.getBlockSpawn().block(event.getBlock(), event.getPlayer()); - event.setExpToDrop(0); - } - - mod.getModel().removeState(s); + mod.getLog().debug("... was placed by creative. Drop prevented"); + mod.getBlockSpawn().block(event.getBlock(), event.getPlayer()); + event.setExpToDrop(0); } - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onBlockBreak: "+e.getMessage()); - event.setCancelled(true); } + mod.getModel().removeState(event.getBlock()); } @EventHandler public void onOtherBlockDestroy(BlockDestroyedEvent event) { - try { - BlockState s = mod.getModel().getState(event.getBlock()); - if (s != null) { - if (mod.isDebug()) - mod.getLog().debug("Breaking attached block: " + s.toString()); - - if (s.isRestricted()) { - if (mod.isDebug()) - mod.getLog().debug("... was placed by creative. Drop prevented"); - mod.getBlockSpawn().block(event.getBlock()); - } - - mod.getModel().removeState(s); - } - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onBlockBreak: "+e.getMessage()); + if (mod.getModel().isRestricted(event.getBlock())) { + if (mod.isDebug()) + mod.getLog().debug("Breaking attached block: " + event.getBlock().getLocation().toString()); + + if (mod.isDebug()) + mod.getLog().debug("... was placed by creative. Drop prevented"); + mod.getBlockSpawn().block(event.getBlock()); } + mod.getModel().removeState(event.getBlock()); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlocksBreakByExplosion(EntityExplodeEvent event) { - try { - Map states = mod.getModel().getStates(event.blockList()); - for (Block block : event.blockList()) { - BlockState s = states.get(block); - if (s != null) { - if (mod.isDebug()) - mod.getLog().debug("Breaking bad, err.. block: " + s.toString()); - - if (s.isRestricted()) { - if (mod.isDebug()) - mod.getLog().debug("... was placed by creative. Drop prevented"); - mod.getBlockSpawn().block(block); - } - - mod.getModel().removeState(s); - } + Map states = mod.getModel().getRestrictedStates(event.blockList()); + DBTransaction update = mod.getModel().groupUpdate(); + for (Block block : event.blockList()) { + if (mod.isDebug()) + mod.getLog().debug("Breaking bad, err.. block: " + block.getLocation().toString()); + + if (states.get(block)) { + if (mod.isDebug()) + mod.getLog().debug("... was placed by creative. Drop prevented"); + mod.getBlockSpawn().block(block); } - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onBlockBreakByExplosion: "+e.getMessage()); - event.setCancelled(true); + + update.removeState(block); } + update.finish(); } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBlockPlace(BlockPlaceEvent event) { - try { - BlockState s = mod.getModel().getState(event.getBlock()); - if (s != null) { - // This shouldn't happen - if (mod.isDebug()) - mod.getLog().debug("Replacing current BlockState: " + s.toString()); - } else { - s = new BlockState(); - s.setLocation(event.getBlock().getLocation()); - } - s.setPlayer(event.getPlayer()); - s.setDate(new Date()); - if (mod.isDebug()) - mod.getLog().debug("Saving BlockState: " + s.toString()); - - mod.getModel().setState(s); - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onBlockPlace: "+e.getMessage()); - event.setCancelled(true); - } + BlockState s = new BlockState(); + s.setLocation(event.getBlock().getLocation()); + s.setPlayer(event.getPlayer()); + s.setDate(new Date()); + if (mod.isDebug()) + mod.getLog().debug("Saving BlockState: " + s.toString()); + + mod.getModel().setState(s); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -138,25 +104,16 @@ public class BlockListener implements Listener { source = source.getRelative(event.getDirection()); } - try { - if (movedBlocks.size() > 0) { - mod.getQueries().getDB().startTransaction(); - for (Block sblock : movedBlocks) { - Block dest = sblock.getRelative(event.getDirection()); - if (mod.isDebug()) - mod.getLog().debug("PistionExtend moves "+sblock.getType()+"-Block from "+sblock.getLocation()+" to "+dest.getLocation()); - - mod.getModel().moveState(sblock, dest); - } - mod.getQueries().getDB().endTransaction(); + if (movedBlocks.size() > 0) { + DBTransaction update = mod.getModel().groupUpdate(); + for (Block sblock : movedBlocks) { + Block dest = sblock.getRelative(event.getDirection()); + if (mod.isDebug()) + mod.getLog().debug("PistionExtend moves "+sblock.getType()+"-Block from "+sblock.getLocation()+" to "+dest.getLocation()); + + update.moveState(sblock, dest); } - } catch (SQLException e) { - try { - mod.getQueries().getDB().revertTransaction(); - } catch (SQLException e1) { - } - mod.getLog().warn("DB-Error while onBlockMove (extend): "+e.getMessage()); - //event.setCancelled(true); + update.finish(); } } @@ -167,18 +124,9 @@ public class BlockListener implements Listener { Block dest = event.getBlock().getRelative(event.getDirection()); Block source = dest.getRelative(event.getDirection()); if (event.isSticky() && source.getType() != Material.AIR) { - try { - if (mod.isDebug()) - mod.getLog().debug("PistionRetract moves "+source.getType()+"-Block from "+source.getLocation()+" to "+dest.getLocation()); - mod.getModel().moveState(source, source.getRelative(event.getDirection().getOppositeFace())); - } catch (SQLException e) { - try { - mod.getQueries().getDB().revertTransaction(); - } catch (SQLException e1) { - } - mod.getLog().warn("DB-Error while onBlockMove (retract): "+e.getMessage()); - //event.setCancelled(true); - } + if (mod.isDebug()) + mod.getLog().debug("PistionRetract moves "+source.getType()+"-Block from "+source.getLocation()+" to "+dest.getLocation()); + mod.getModel().moveState(source, source.getRelative(event.getDirection().getOppositeFace())); } } } diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateCommand.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateCommand.java index e3df99f..bdd3632 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateCommand.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateCommand.java @@ -1,6 +1,5 @@ package de.jaschastarke.minecraft.limitedcreative.blockstate; -import java.sql.SQLException; import java.util.Date; import org.bukkit.GameMode; @@ -25,7 +24,8 @@ import de.jaschastarke.maven.ArchiveDocComments; import de.jaschastarke.minecraft.lib.permissions.IAbstractPermission; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source; -import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries.Cuboid; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.Cuboid; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.DBTransaction; /** * LimitedCreative-BlockState-Command: modify blockstate database to prevent drops of selected blocks (requires WorldEdit) @@ -130,45 +130,35 @@ public class BlockStateCommand extends BukkitCommand implements IHelpDescribed { public void run() { if (mod.isDebug()) mod.getLog().debug("Scheduler: Asynchronous Task run"); - DBQueries q = mod.getQueries(); - try { - q.getDB().startTransaction(); - int count = 0; - World w = selection.getWorld(); - - Cuboid c = new Cuboid(); - c.add(min); - c.add(max); - mod.getModel().cacheStates(c); - - BlockState seed = new BlockState(); - seed.setPlayer(context.getPlayer()); - seed.setGameMode(tgm); - seed.setSource(Source.COMMAND); - seed.setDate(new Date()); - for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { - for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { - for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { - Location loc = new Location(w, x, y, z); - if (w.getBlockAt(loc).getType() != Material.AIR && selection.contains(loc)) { - seed.setLocation(loc); - mod.getModel().setState(new BlockState(seed)); - count++; - } + DBTransaction update = mod.getModel().groupUpdate(); + int count = 0; + World w = selection.getWorld(); + + Cuboid c = new Cuboid(); + c.add(min); + c.add(max); + mod.getModel().cacheStates(c); + + BlockState seed = new BlockState(); + seed.setPlayer(context.getPlayer()); + seed.setGameMode(tgm); + seed.setSource(Source.COMMAND); + seed.setDate(new Date()); + for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + Location loc = new Location(w, x, y, z); + if (w.getBlockAt(loc).getType() != Material.AIR && selection.contains(loc)) { + seed.setLocation(loc); + update.setState(new BlockState(seed)); + count++; } } } - q.getDB().endTransaction(); - - context.response(L("command.blockstate.command_updated", count)); - } catch (SQLException e) { - try { - q.getDB().revertTransaction(); - } catch (SQLException e1) { - } - mod.getLog().warn("Failed to update blocks in region: " + e.getMessage()); - context.response(L("command.blockstate.command_failed")); } + update.finish(); + + context.response(L("command.blockstate.command_updated", count)); } }); return true; diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateConfig.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateConfig.java index b50fa4b..947e2b5 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateConfig.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/BlockStateConfig.java @@ -43,6 +43,11 @@ public class BlockStateConfig extends Configuration implements IConfigurationSub } else { entry.disable(); } + } else if (node.getName().equals("useThreading")) { + if (entry.getState() == ModuleState.ENABLED) { + entry.disable(); + entry.enable(); + } } } @@ -77,6 +82,20 @@ public class BlockStateConfig extends Configuration implements IConfigurationSub return config.getBoolean("enabled", false); } + /** + * BlockStateThreading + * + * This experimental variant of the experimental Feature uses Threading to minimize lag. This fully relies on + * Bukkit metadata implementation. You only should need this, if there are often plays more then 10 players at once + * on your server. Be aware that this requires more memory. + * + * default: false + */ + @IsConfigurationNode(order = 150) + public boolean getUseThreading() { + return config.getBoolean("useThreading", false); + } + /** * BlockStateTool * diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBModel.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBModel.java index ed1fcfa..196bfab 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBModel.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBModel.java @@ -1,149 +1,86 @@ 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.Location; +import org.bukkit.World; import org.bukkit.block.Block; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.MetadataValue; -import org.bukkit.metadata.Metadatable; -import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; -import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries.Cuboid; - -public class DBModel { - private static final String BSMDKEY = "blockstate"; - private ModBlockStates mod; - private DBQueries q; - - public DBModel(ModBlockStates mod) { - this.mod = mod; - this.q = mod.getQueries(); - } - - public void moveState(Block from, Block to) throws SQLException { - q.delete(to.getLocation()); - q.move(from.getLocation(), to.getLocation()); - HasBlockState metaBlock = getMetaBlock(from); - if (metaBlock.set && metaBlock.state != null) { - BlockState state = metaBlock.state; - state.setLocation(to.getLocation()); - setMetaBlock(to, state); - } else { - removeMetaBlock(to); - } - setMetaBlock(from, null); - } - public void removeState(BlockState state) throws SQLException { - //removeMetaBlock(state.getLocation().getBlock()); - setMetaBlock(state.getLocation().getBlock(), null); - q.delete(state); - } - public Map getStates(List blocks) throws SQLException { - Map ret = new HashMap(); - - Cuboid c = new Cuboid(); - for (Block block : blocks) { - HasBlockState has = getMetaBlock(block); - if (has.set) { - ret.put(block, has.state); +public interface DBModel { + public static class Cuboid { + private World w = null; + private int minx, miny, minz; + private int maxx, maxy, maxz; + public void add(Location loc) { + if (w == null) { + w = loc.getWorld(); + minx = maxx = loc.getBlockX(); + miny = maxy = loc.getBlockY(); + minz = maxz = loc.getBlockZ(); } else { - c.add(block.getLocation()); + if (w != loc.getWorld()) + throw new IllegalArgumentException("Point is from a different world"); + if (minx > loc.getBlockX()) + minx = loc.getBlockX(); + if (maxx < loc.getBlockX()) + maxx = loc.getBlockX(); + if (miny > loc.getBlockY()) + miny = loc.getBlockY(); + if (maxy < loc.getBlockY()) + maxy = loc.getBlockY(); + if (minz > loc.getBlockZ()) + minz = loc.getBlockZ(); + if (maxz < loc.getBlockZ()) + maxz = loc.getBlockZ(); } } - if (!c.isEmpty()) { - List dbb = q.findAllIn(c); - for (BlockState bs : dbb) { - setMetaBlock(bs.getLocation().getBlock(), bs); - if (blocks.contains(bs.getLocation().getBlock())) - ret.put(bs.getLocation().getBlock(), bs); - } - for (Block block : blocks) { - if (!ret.containsKey(block)) { - ret.put(block, null); - setMetaBlock(block, null); - } - } + public int getMinX() { + return minx; } - /*for (Block block : blocks) { - if (ret.containsKey(block)) - ret.put(block, getState(block)); - }*/ - return ret; - } - public void cacheStates(Cuboid c) throws SQLException { - if (!c.isEmpty()) { - List dbb = q.findAllIn(c); - for (BlockState bs : dbb) { - setMetaBlock(bs.getLocation().getBlock(), bs); - } + public int getMinY() { + return miny; } - } - public BlockState getState(Block block) throws SQLException { - HasBlockState has = getMetaBlock(block); - if (!has.set) { - BlockState state = q.find(block.getLocation()); - setMetaBlock(block, state); - return state; + public int getMinZ() { + return minz; } - return has.state; - } - public void setState(BlockState state) throws SQLException { - Block block = state.getLocation().getBlock(); - boolean update = hasMetaBlock(block); - boolean store = state.isRestricted() || mod.getConfig().getLogSurvival(); - - setMetaBlock(block, store ? state : null); - - if (update) { - if (!store) - q.delete(state); - else if (!q.update(state)) - q.insert(state); - } else { - if (store) - q.insert(state); + public int getMaxX() { + return maxx; + } + public int getMaxY() { + return maxy; + } + public int getMaxZ() { + return maxz; + } + public World getWorld() { + return w; + } + public boolean isEmpty() { + return w == null; + } + public String toString() { + return "Cuboid{world="+w.getName()+", min_x="+minx+", max_x="+maxx+", min_y="+miny+", max_y="+maxy+", min_z="+minz+", max_z="+maxz+"}"; } } - protected boolean hasMetaBlock(Metadatable m) { - List metadata = m.getMetadata(BSMDKEY); - for (MetadataValue v : metadata) { - if (v.value() instanceof BlockState) - return true; - } - return false; - } - protected void setMetaBlock(Metadatable m, BlockState s) { - if (s == null) - m.setMetadata(BSMDKEY, new FixedMetadataValue(mod.getPlugin(), new Boolean(false))); - else - m.setMetadata(BSMDKEY, new FixedMetadataValue(mod.getPlugin(), s)); - } - protected HasBlockState getMetaBlock(Metadatable m) { - HasBlockState has = new HasBlockState(); - List metadata = m.getMetadata(BSMDKEY); - for (MetadataValue v : metadata) { - if (v.value() instanceof BlockState) { - has.set = true; - has.state = (BlockState) v.value(); - break; - } else if (v.getOwningPlugin() == mod.getPlugin()) { - has.set = true; - has.state = null; - break; - } - } - return has; - } - protected void removeMetaBlock(Metadatable m) { - m.removeMetadata(BSMDKEY, mod.getPlugin()); - } + public void onEnable() throws Exception; + public void onDisable(); + public void moveState(Block from, Block to); + public void removeState(BlockState state); + public void removeState(Block block); + public Map getStates(List blocks); + public Map getRestrictedStates(List blocks); + public void cacheStates(DBModel.Cuboid c); + public BlockState getState(Block block); + public boolean isRestricted(Block block); + public void setState(BlockState state); + public DBTransaction groupUpdate(); - protected static class HasBlockState { - public boolean set = false; - public BlockState state = null; + public static interface DBTransaction { + public void moveState(Block from, Block to); + public void setState(BlockState state); + public void removeState(Block block); + public void finish(); } } diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBQueries.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBQueries.java index 64d4598..525ca1e 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBQueries.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/DBQueries.java @@ -9,7 +9,6 @@ import java.util.List; import org.bukkit.GameMode; import org.bukkit.Location; -import org.bukkit.World; import de.jaschastarke.database.Type; import de.jaschastarke.database.db.Database; @@ -49,7 +48,7 @@ public class DBQueries { } private PreparedStatement findall = null; - public List findAllIn(Cuboid c) throws SQLException { + public List findAllIn(DBModel.Cuboid c) throws SQLException { if (mod.isDebug()) mod.getLog().debug("DBQuery: findAllIn: " + c.toString()); List blocks = new ArrayList(); @@ -268,60 +267,4 @@ public class DBQueries { public Database getDB() { return db; } - - public static class Cuboid { - private World w = null; - private int minx, miny, minz; - private int maxx, maxy, maxz; - public void add(Location loc) { - if (w == null) { - w = loc.getWorld(); - minx = maxx = loc.getBlockX(); - miny = maxy = loc.getBlockY(); - minz = maxz = loc.getBlockZ(); - } else { - if (w != loc.getWorld()) - throw new IllegalArgumentException("Point is from a different world"); - if (minx > loc.getBlockX()) - minx = loc.getBlockX(); - if (maxx < loc.getBlockX()) - maxx = loc.getBlockX(); - if (miny > loc.getBlockY()) - miny = loc.getBlockY(); - if (maxy < loc.getBlockY()) - maxy = loc.getBlockY(); - if (minz > loc.getBlockZ()) - minz = loc.getBlockZ(); - if (maxz < loc.getBlockZ()) - maxz = loc.getBlockZ(); - } - } - public int getMinX() { - return minx; - } - public int getMinY() { - return miny; - } - public int getMinZ() { - return minz; - } - public int getMaxX() { - return maxx; - } - public int getMaxY() { - return maxy; - } - public int getMaxZ() { - return maxz; - } - public World getWorld() { - return w; - } - public boolean isEmpty() { - return w == null; - } - public String toString() { - return "Cuboid{world="+w.getName()+", min_x="+minx+", max_x="+maxx+", min_y="+miny+", max_y="+maxy+", min_z="+minz+", max_z="+maxz+"}"; - } - } } diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/HangingListener.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/HangingListener.java index 06cc36f..5ca97ec 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/HangingListener.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/HangingListener.java @@ -1,6 +1,5 @@ package de.jaschastarke.minecraft.limitedcreative.blockstate; -import java.sql.SQLException; import java.util.Date; import org.bukkit.GameMode; @@ -14,7 +13,6 @@ import org.bukkit.event.hanging.HangingPlaceEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; -import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source; public class HangingListener implements Listener { private ModBlockStates mod; @@ -25,36 +23,26 @@ public class HangingListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { if (event.getRightClicked() instanceof ItemFrame) { - try { - BlockState s = mod.getModel().getState(event.getRightClicked().getLocation().getBlock()); - if (s != null) { + if (mod.getModel().isRestricted(event.getRightClicked().getLocation().getBlock())) { + if (mod.isDebug()) + mod.getLog().debug("Modifying hanging: " + event.getRightClicked().getLocation().toString()); + + if (event.getPlayer().getGameMode() != GameMode.CREATIVE) { if (mod.isDebug()) - mod.getLog().debug("Modifying hanging: " + s.toString()); - - if ((s.getGameMode() == GameMode.CREATIVE || s.getSource() == Source.EDIT) && event.getPlayer().getGameMode() != GameMode.CREATIVE) { - if (mod.isDebug()) - mod.getLog().debug("... was placed by creative. Modify prevented"); - event.setCancelled(true); - return; - } else { - s.setPlayer(event.getPlayer()); - s.setDate(new Date()); - mod.getModel().setState(s); - } - } else { - s = new BlockState(); - s.setLocation(event.getRightClicked().getLocation().getBlock().getLocation()); - s.setPlayer(event.getPlayer()); - s.setDate(new Date()); - - if (mod.isDebug()) - mod.getLog().debug("Saving BlockState: " + s.toString()); - - mod.getModel().setState(s); + mod.getLog().debug("... was placed by creative. Modify prevented"); + event.setCancelled(true); + return; } - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onHangingInteract: "+e.getMessage()); - event.setCancelled(true); + } else { + BlockState s = new BlockState(); + s.setLocation(event.getRightClicked().getLocation().getBlock().getLocation()); + s.setPlayer(event.getPlayer()); + s.setDate(new Date()); + + if (mod.isDebug()) + mod.getLog().debug("Saving BlockState: " + s.toString()); + + mod.getModel().setState(s); } } } @@ -62,52 +50,32 @@ public class HangingListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onHangingBreak(HangingBreakEvent event) { if (event.getEntity() instanceof ItemFrame) { - try { - BlockState s = mod.getModel().getState(event.getEntity().getLocation().getBlock()); - if (s != null) { - if (mod.isDebug()) - mod.getLog().debug("Breaking hanging: " + s.toString()); - - if (s.getGameMode() == GameMode.CREATIVE || s.getSource() == Source.EDIT) { - if (mod.isDebug()) - mod.getLog().debug("... was placed by creative. Drop prevented"); - //mod.getBlockSpawn().block(event.getEntity().getLocation().getBlock().getLocation()); - mod.getBlockSpawn().block(event.getEntity().getLocation().getBlock().getLocation(), Material.ITEM_FRAME); - mod.getBlockSpawn().block(event.getEntity().getLocation().getBlock().getLocation(), ((ItemFrame) event.getEntity()).getItem().getType()); - } - - mod.getModel().removeState(s); - } - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onHangingBreak: "+e.getMessage()); - event.setCancelled(true); + if (mod.isDebug()) + mod.getLog().debug("Breaking hanging: " + event.getEntity().getLocation().toString()); + + if (mod.getModel().isRestricted(event.getEntity().getLocation().getBlock())) { + if (mod.isDebug()) + mod.getLog().debug("... was placed by creative. Drop prevented"); + + mod.getBlockSpawn().block(event.getEntity().getLocation().getBlock().getLocation(), Material.ITEM_FRAME); + mod.getBlockSpawn().block(event.getEntity().getLocation().getBlock().getLocation(), ((ItemFrame) event.getEntity()).getItem().getType()); } + + mod.getModel().removeState(event.getEntity().getLocation().getBlock()); } } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onHangingPlace(HangingPlaceEvent event) { if (event.getEntity() instanceof ItemFrame) { - try { - BlockState s = mod.getModel().getState(event.getEntity().getLocation().getBlock()); - if (s != null) { - // This shouldn't happen - if (mod.isDebug()) - mod.getLog().debug("Replacing current BlockState: " + s.toString()); - } else { - s = new BlockState(); - s.setLocation(event.getEntity().getLocation().getBlock().getLocation()); - } - s.setPlayer(event.getPlayer()); - s.setDate(new Date()); - if (mod.isDebug()) - mod.getLog().debug("Saving BlockState: " + s.toString()); - - mod.getModel().setState(s); - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onHangingPlace: "+e.getMessage()); - event.setCancelled(true); - } + BlockState s = new BlockState(); + s.setLocation(event.getEntity().getLocation().getBlock().getLocation()); + s.setPlayer(event.getPlayer()); + s.setDate(new Date()); + if (mod.isDebug()) + mod.getLog().debug("Saving BlockState: " + s.toString()); + + mod.getModel().setState(s); } } } diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/PlayerListener.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/PlayerListener.java index 5b87cee..1eadfc2 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/PlayerListener.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/PlayerListener.java @@ -1,7 +1,5 @@ package de.jaschastarke.minecraft.limitedcreative.blockstate; -import java.sql.SQLException; - import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; @@ -46,40 +44,36 @@ public class PlayerListener implements Listener { } private void showInfo(Player pl, Location loc, Material type) { - try { - BlockState s = mod.getModel().getState(loc.getBlock()); - InGameFormatter f = new InGameFormatter(mod.getPlugin().getLang()); - String ret = null; - if (s == null || s.getSource() == Source.UNKNOWN) { - ret = f.formatString(ChatFormattings.ERROR, f.getString("block_state.tool_info.unknown", type.toString())); - } else { - String k = "block_state.tool_info." + s.getSource().name().toLowerCase(); - String gm = ""; - if (s.getGameMode() != null) { - switch (s.getGameMode()) { - case CREATIVE: - gm = ChatColor.GOLD + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; - break; - case SURVIVAL: - gm = ChatColor.GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; - break; - case ADVENTURE: - gm = ChatColor.DARK_GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; - break; - default: - break; - } + BlockState s = mod.getModel().getState(loc.getBlock()); + InGameFormatter f = new InGameFormatter(mod.getPlugin().getLang()); + String ret = null; + if (s == null || s.getSource() == Source.UNKNOWN) { + ret = f.formatString(ChatFormattings.ERROR, f.getString("block_state.tool_info.unknown", type.toString())); + } else { + String k = "block_state.tool_info." + s.getSource().name().toLowerCase(); + String gm = ""; + if (s.getGameMode() != null) { + switch (s.getGameMode()) { + case CREATIVE: + gm = ChatColor.GOLD + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; + break; + case SURVIVAL: + gm = ChatColor.GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; + break; + case ADVENTURE: + gm = ChatColor.DARK_GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; + break; + default: + break; } - - ret = f.formatString(ChatFormattings.INFO, f.getString(k, type.toString(), - s.getPlayerName(), - gm, - s.getDate())); } - if (ret != null) - pl.sendMessage(ret); - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onPlayerInteract: "+e.getMessage()); + + ret = f.formatString(ChatFormattings.INFO, f.getString(k, type.toString(), + s.getPlayerName(), + gm, + s.getDate())); } + if (ret != null) + pl.sendMessage(ret); } } diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/SyncronizedModel.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/SyncronizedModel.java new file mode 100644 index 0000000..16de438 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/SyncronizedModel.java @@ -0,0 +1,207 @@ +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.block.Block; + +import de.jaschastarke.database.DatabaseConfigurationException; +import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; + +/** + * @internal I'm not happy with the error-handling here, especially in the Transaction, but I'll focus on the asynchronous + * variant, where all errors have to be handled in a separate thread. + */ +public class SyncronizedModel extends AbstractModel implements DBModel { + private ModBlockStates mod; + private DBQueries q; + + public SyncronizedModel(ModBlockStates mod) throws DatabaseConfigurationException { + super(mod.getPlugin()); + this.mod = mod; + this.q = new DBQueries(mod, mod.getPlugin().getDatabaseConnection()); + } + + @Override + public void onEnable() throws SQLException { + this.q.initTable(); + } + @Override + public void onDisable() { + } + + public void moveState(Block from, Block to) { + try { + q.delete(to.getLocation()); + q.move(from.getLocation(), to.getLocation()); + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + mod.getLog().warn("Failed to move BlockState in DB from " + from.getLocation().toString() + " to " + to.getLocation().toString()); + } + moveMetaState(from, to); + } + public void removeState(BlockState state) { + removeState(state.getLocation().getBlock()); + } + + @Override + public void removeState(Block block) { + setMetaBlock(block, null); + try { + q.delete(block.getLocation()); + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + mod.getLog().warn("Failed to delete BlockState in DB from " + block.getLocation().toString()); + } + } + + @Override + public Map getRestrictedStates(List blocks) { + Map ret = new HashMap(); + for (Map.Entry entry : getStates(blocks).entrySet()) { + ret.put(entry.getKey(), entry.getValue().isRestricted()); + } + return ret; + } + public Map getStates(List blocks) { + Map ret = new HashMap(); + + Cuboid c = new Cuboid(); + for (Block block : blocks) { + HasBlockState has = getMetaBlock(block); + if (has.set) { + ret.put(block, has.state); + } else { + c.add(block.getLocation()); + } + } + if (!c.isEmpty()) { + try { + List dbb = q.findAllIn(c); + for (BlockState bs : dbb) { + setMetaBlock(bs.getLocation().getBlock(), bs); + if (blocks.contains(bs.getLocation().getBlock())) + ret.put(bs.getLocation().getBlock(), bs); + } + for (Block block : blocks) { + if (!ret.containsKey(block)) { + ret.put(block, null); + setMetaBlock(block, null); + } + } + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + mod.getLog().warn("Failed to fetch BlockState from DB in " + c.toString()); + } + } + return ret; + } + public void cacheStates(Cuboid c) { + if (!c.isEmpty()) { + try { + List dbb = q.findAllIn(c); + for (BlockState bs : dbb) { + setMetaBlock(bs.getLocation().getBlock(), bs); + } + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + mod.getLog().warn("Failed to fetch BlockState (for caching) from DB in " + c.toString()); + } + } + } + + @Override + public boolean isRestricted(Block block) { + BlockState state = getState(block); + return state != null ? state.isRestricted() : null; + } + + public BlockState getState(Block block) { + HasBlockState has = getMetaBlock(block); + if (!has.set) { + try { + BlockState state = q.find(block.getLocation()); + setMetaBlock(block, state); + return state; + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + mod.getLog().warn("Failed to fetch BlockState (for caching) from DB at " + block.getLocation().toString()); + return null; + } + } + return has.state; + } + public void setState(BlockState state) { + Block block = state.getLocation().getBlock(); + boolean update = hasMetaBlock(block); + boolean store = state.isRestricted() || mod.getConfig().getLogSurvival(); + + setMetaBlock(block, store ? state : null); + + try { + if (update) { + if (!store) + q.delete(state); + else if (!q.update(state)) + q.insert(state); + } else { + if (store) + q.insert(state); + } + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + mod.getLog().warn("Failed to update BlockState in DB at " + block.getLocation().toString()); + } + } + + @Override + public DBTransaction groupUpdate() { + return new Transaction(); + } + + private class Transaction implements DBTransaction { + private boolean finished = false; + private Transaction() { + try { + q.getDB().startTransaction(); + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + finished = true; + } + } + + @Override + public void moveState(Block from, Block to) { + if (finished) + throw new IllegalAccessError("Transaction already ended"); + SyncronizedModel.this.moveState(from, to); + } + + @Override + public void setState(BlockState state) { + if (finished) + throw new IllegalAccessError("Transaction already ended"); + SyncronizedModel.this.setState(state); + } + + @Override + public void removeState(Block block) { + if (finished) + throw new IllegalAccessError("Transaction already ended"); + SyncronizedModel.this.removeState(block); + } + + @Override + public void finish() { + try { + q.getDB().endTransaction(); + } catch (SQLException e) { + mod.getLog().severe(e.getMessage()); + } finally { + finished = true; + } + } + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/ThreadedModel.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/ThreadedModel.java new file mode 100644 index 0000000..629f1a0 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/ThreadedModel.java @@ -0,0 +1,252 @@ +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.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()) { + 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) { + 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 getRestrictedStates(List blocks) { + Map ret = new HashMap(); + Cuboid c; + do { + c = new Cuboid(); + for (Block block : blocks) { + HasBlockState has = getMetaBlock(block); + if (has.set) { + ret.put(block, has.restricted); + } else { + c.add(block.getLocation()); + ret.put(block, null); + } + } + if (!c.isEmpty()) + threads.callUpdate(c); + } while(!c.isEmpty()); + return ret; + } + + @Override + public Map getStates(List blocks) { + Map ret = new HashMap(); + Cuboid c; + do { + c = new Cuboid(); + for (Block block : blocks) { + HasBlockState has = getMetaBlock(block); + if (has.set) { + ret.put(block, has.state); + } 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.set || (has.dbSet && has.state == null)) { + return threads.callUpdate(block); + } + return has.state; + } + @Override + public boolean isRestricted(Block block) { + HasBlockState has = getMetaBlock(block); + if (!has.set) { + BlockState state = threads.callUpdate(block); + return state != null ? state.isRestricted() : false; + } + return getMetaBlock(block).restricted; + } + + @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 = new HasBlockState(); + List metadata = m.getMetadata(BSMDKEY); + for (MetadataValue v : metadata) { + if (v.value() instanceof BlockState) { + has.set = true; + has.state = (BlockState) v.value(); + has.dbSet = true; + has.restricted = has.state.isRestricted(); + break; + } else if (v.getOwningPlugin() == mod.getPlugin()) { + if (v == metadataNull) { + has.set = true; + has.state = null; + } else if (v == metadataSet) { + has.set = true; + has.state = null; + has.dbSet = true; + } else if (v == metadataSetRestricted) { + has.set = true; + has.state = null; + has.dbSet = true; + has.restricted = true; + } + break; + } + } + return has; + } + public static class HasBlockState extends AbstractModel.HasBlockState { + public boolean dbSet = false; + public boolean restricted = false; + } + + public ModBlockStates getModel() { + return mod; + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/Action.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/Action.java new file mode 100644 index 0000000..1fb62ea --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/Action.java @@ -0,0 +1,11 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +public interface Action { + + /** + * @internal Executed from asynchronous Thread. Only Thread-Safe methods should be called. + */ + void process(ThreadLink link, DBQueries q); +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/CacheChunkAction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/CacheChunkAction.java new file mode 100644 index 0000000..3f13e03 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/CacheChunkAction.java @@ -0,0 +1,60 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; + +import org.bukkit.Chunk; +import org.bukkit.block.Block; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.Cuboid; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +public class CacheChunkAction implements Action { + private static final int CHUNK_SIZE = 16; + + private Chunk chunk; + + public CacheChunkAction(Chunk chunk) { + this.chunk = chunk; + } + + @Override + public void process(ThreadLink link, DBQueries q) { + if (!chunk.isLoaded()) + return; + Set knownBlocks = new HashSet(); + try { + for (BlockState state : q.findAllIn(getBlocks())) { + Block b = state.getLocation().getBlock(); + knownBlocks.add(b); + link.setSimpleMetaState(b, state); + } + /*int h = chunk.getWorld().getMaxHeight(); + for (int y = 0; y < h; y++) { + for (int x = 0; x < CHUNK_SIZE; x++) { + for (int z = 0; z < CHUNK_SIZE; z++) { + Block b = chunk.getBlock(x, y, z); + if (!knownBlocks.contains(b) && b.getType() != Material.AIR) { + link.setSimpleMetaState(b, null); + link.blockCount++; + } + } + } + }*/ + } catch (SQLException e) { + link.getLog().severe(e.getMessage()); + link.getLog().warn("Thread " + Thread.currentThread().getName() + " failed to load BlockStates for Chunk " + chunk.getX() + "/" + chunk.getZ()); + } + } + + protected Cuboid getBlocks() { + Cuboid c = new Cuboid(); + c.add(chunk.getBlock(0, 0, 0).getLocation()); + + int h = chunk.getWorld().getMaxHeight(); + c.add(chunk.getBlock(CHUNK_SIZE - 1, h - 1, CHUNK_SIZE - 1).getLocation()); + return c; + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/CallableAction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/CallableAction.java new file mode 100644 index 0000000..d7e5abd --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/CallableAction.java @@ -0,0 +1,18 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +public abstract class CallableAction implements Action { + protected boolean returnSet = false; + protected E returnValue = null; + + public E getValue() { + synchronized (this) { + try { + while (!returnSet) + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return returnValue; + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/FetchBlockStateAction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/FetchBlockStateAction.java new file mode 100644 index 0000000..2afd01f --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/FetchBlockStateAction.java @@ -0,0 +1,34 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import java.sql.SQLException; + +import org.bukkit.block.Block; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +public class FetchBlockStateAction extends CallableAction { + private Block block; + + public FetchBlockStateAction(Block block) { + this.block = block; + } + + @Override + public void process(ThreadLink link, DBQueries q) { + BlockState state = null; + try { + state = q.find(block.getLocation()); + link.setMetaState(block, state); + } catch (SQLException e) { + link.getLog().severe(e.getMessage()); + link.getLog().warn("Thread " + Thread.currentThread().getName() + " failed to fetch BlockState from DB: " + block.getLocation()); + return; + } + synchronized (this) { + returnValue = state; + returnSet = true; + this.notify(); + } + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/FetchCuboidAction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/FetchCuboidAction.java new file mode 100644 index 0000000..d5450c2 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/FetchCuboidAction.java @@ -0,0 +1,36 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import java.sql.SQLException; +import java.util.List; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.Cuboid; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +public class FetchCuboidAction extends CallableAction> { + private Cuboid cuboid; + + public FetchCuboidAction(Cuboid cuboid) { + this.cuboid = cuboid; + } + + @Override + public void process(ThreadLink link, DBQueries q) { + List states = null; + try { + states = q.findAllIn(cuboid); + for (BlockState bs : states) { + link.setMetaState(bs.getLocation().getBlock(), bs); + } + } catch (SQLException e) { + link.getLog().severe(e.getMessage()); + link.getLog().warn("Thread " + Thread.currentThread().getName() + " failed to fetch BlockState from DB: " + cuboid); + return; + } + synchronized (this) { + returnValue = states; + returnSet = true; + this.notify(); + } + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/MoveBlockStateAction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/MoveBlockStateAction.java new file mode 100644 index 0000000..d03bc23 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/MoveBlockStateAction.java @@ -0,0 +1,34 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import java.sql.SQLException; + +import org.bukkit.Location; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +public class MoveBlockStateAction extends TransactionAction implements Action { + private Location from; + private Location to; + + public MoveBlockStateAction(Location from, Location to) { + this.from = from; + this.to = to; + } + + @Override + public void process(ThreadLink link, DBQueries q) { + try { + processInTransaction(link, q); + } catch (SQLException e) { + link.getLog().severe(e.getMessage()); + link.getLog().warn("Thread " + Thread.currentThread().getName() + " failed to move BlockState in DB from " + from.toString() + " to " + to.toString()); + } + } + + @Override + public void processInTransaction(ThreadLink link, DBQueries q) throws SQLException { + q.delete(to); + q.move(from, to); + } + +} \ No newline at end of file diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/ThreadLink.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/ThreadLink.java new file mode 100644 index 0000000..dc37a19 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/ThreadLink.java @@ -0,0 +1,168 @@ +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 = 10; + private Stack updateQueue = new Stack(); + + 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."); + while (!shutdown || !updateQueue.isEmpty()) { + try { + List acts = new LinkedList(); + synchronized (updateQueue) { + while (updateQueue.isEmpty() && !shutdown) + updateQueue.wait(); + for (int i = 0; i < BATCH_ACTION_LENGTH && !updateQueue.isEmpty(); i++) { + acts.add(updateQueue.pop()); + } + } + if (getModule().isDebug()) + 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); + } + } + } + } 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) { + synchronized (updateQueue) { + updateQueue.add(new UpdateBlockStateAction(block)); + updateQueue.notify(); + } + } + + public BlockState callUpdate(Block block) { + FetchBlockStateAction action = new FetchBlockStateAction(block); + synchronized (updateQueue) { + updateQueue.push(action); + updateQueue.notify(); + } + return action.getValue(); + } + + public List 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; + } +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/Transaction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/Transaction.java new file mode 100644 index 0000000..162b59f --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/Transaction.java @@ -0,0 +1,74 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; + +import org.bukkit.block.Block; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.DBTransaction; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +abstract public class Transaction implements DBTransaction, Action { + protected boolean finished = false; + private List actions = new LinkedList(); + private ThreadLink link; + + public Transaction(ThreadLink link) { + this.link = link; + } + + @Override + public void moveState(Block from, Block to) { + if (finished) + throw new IllegalAccessError("Transaction already ended"); + + actions.add(new MoveBlockStateAction(from.getLocation(), to.getLocation())); + } + + @Override + public void setState(BlockState state) { + if (finished) + throw new IllegalAccessError("Transaction already ended"); + + Block block = state.getLocation().getBlock(); + actions.add(new UpdateBlockStateAction(block)); + } + + public void setState(Block block) { + if (finished) + throw new IllegalAccessError("Transaction already ended"); + + actions.add(new UpdateBlockStateAction(block)); + } + + @Override + public void finish() { + if (finished) + return; + link.queueTransaction(this); + } + + /** + * @internal Executed from asynchronous Thread. Only Thread-Safe methods should be called. + */ + @Override + public void process(ThreadLink link, DBQueries q) { + if (actions.isEmpty()) + return; + try { + q.getDB().startTransaction(); + for (TransactionAction act : actions) { + act.processInTransaction(link, q); + } + q.getDB().endTransaction(); + } catch (SQLException e) { + try { + q.getDB().revertTransaction(); + } catch (SQLException e1) {} + link.getLog().severe(e.getMessage()); + link.getLog().warn("Thread " + Thread.currentThread().getName() + " failed to write Transaction to the Database"); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/TransactionAction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/TransactionAction.java new file mode 100644 index 0000000..f49fdc5 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/TransactionAction.java @@ -0,0 +1,9 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import java.sql.SQLException; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +abstract public class TransactionAction implements Action { + abstract public void processInTransaction(ThreadLink link, DBQueries q) throws SQLException; +} diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/UpdateBlockStateAction.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/UpdateBlockStateAction.java new file mode 100644 index 0000000..9c16172 --- /dev/null +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/thread/UpdateBlockStateAction.java @@ -0,0 +1,41 @@ +package de.jaschastarke.minecraft.limitedcreative.blockstate.thread; + +import java.sql.SQLException; + +import org.bukkit.block.Block; + +import de.jaschastarke.minecraft.limitedcreative.blockstate.AbstractModel.HasBlockState; +import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries; + +public class UpdateBlockStateAction extends TransactionAction implements Action { + private Block block; + public UpdateBlockStateAction(Block block) { + this.block = block; + } + + @Override + public void process(ThreadLink link, DBQueries q) { + HasBlockState state = link.getMetaState(block); + if (state.set) { + try { + q.delete(block.getLocation()); + if (state.state != null) + q.insert(state.state); + } catch (SQLException e) { + link.getLog().severe(e.getMessage()); + link.getLog().warn("Thread " + Thread.currentThread().getName() + " failed to save BlockState to DB: " + state.state); + } + } + } + + @Override + public void processInTransaction(ThreadLink link, DBQueries q) throws SQLException { + HasBlockState state = link.getMetaState(block); + if (state.set) { + q.delete(block.getLocation()); + if (state.state != null) + q.insert(state.state); + } + } + +} \ No newline at end of file diff --git a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/worldedit/LCEditSessionFactory.java b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/worldedit/LCEditSessionFactory.java index 1fd8dd3..04bd949 100644 --- a/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/worldedit/LCEditSessionFactory.java +++ b/src/main/java/de/jaschastarke/minecraft/limitedcreative/blockstate/worldedit/LCEditSessionFactory.java @@ -1,7 +1,6 @@ package de.jaschastarke.minecraft.limitedcreative.blockstate.worldedit; import java.lang.reflect.InvocationTargetException; -import java.sql.SQLException; import java.util.Date; import org.bukkit.Location; @@ -153,24 +152,19 @@ public class LCEditSessionFactory extends EditSessionFactory { public boolean onBlockEdit(LocalPlayer player, Vector pt, BaseBlock block) { if (player != null) { Location loc = new Location(((BukkitWorld) player.getWorld()).getWorld(), pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); - try { - BlockState s = mod.getModel().getState(loc.getBlock()); - if (s == null) { - s = new BlockState(); - s.setLocation(loc); - } - s.setGameMode(null); - s.setPlayerName(player.getName()); - s.setDate(new Date()); - s.setSource(Source.EDIT); - if (mod.isDebug()) - mod.getLog().debug("Saving BlockState: " + s.toString()); - - mod.getModel().setState(s); - } catch (SQLException e) { - mod.getLog().warn("DB-Error while onBlockEdit: "+e.getMessage()); - return false; + BlockState s = mod.getModel().getState(loc.getBlock()); + if (s == null) { + s = new BlockState(); + s.setLocation(loc); } + s.setGameMode(null); + s.setPlayerName(player.getName()); + s.setDate(new Date()); + s.setSource(Source.EDIT); + if (mod.isDebug()) + mod.getLog().debug("Saving BlockState: " + s.toString()); + + mod.getModel().setState(s); return true; } else { return false;