Threaded BlockState DB-Connection

This commit is contained in:
Jascha Starke 2013-09-25 16:35:41 +02:00
parent 7506a7f1c1
commit e32bbbd105
22 changed files with 1275 additions and 458 deletions

View file

@ -1,5 +1,7 @@
package de.jaschastarke.minecraft.limitedcreative; package de.jaschastarke.minecraft.limitedcreative;
import org.bukkit.event.Listener;
import de.jaschastarke.bukkit.lib.CoreModule; import de.jaschastarke.bukkit.lib.CoreModule;
import de.jaschastarke.bukkit.lib.commands.AliasHelpedCommand; import de.jaschastarke.bukkit.lib.commands.AliasHelpedCommand;
import de.jaschastarke.bukkit.lib.modules.AdditionalBlockBreaks; 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.BlockStateCommand;
import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockStateConfig; import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockStateConfig;
import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel; 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.DependencyListener;
import de.jaschastarke.minecraft.limitedcreative.blockstate.HangingListener; import de.jaschastarke.minecraft.limitedcreative.blockstate.HangingListener;
import de.jaschastarke.minecraft.limitedcreative.blockstate.PlayerListener; 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.minecraft.limitedcreative.blockstate.worldedit.LCEditSessionFactory;
import de.jaschastarke.modularize.IModule; import de.jaschastarke.modularize.IModule;
import de.jaschastarke.modularize.ModuleEntry; import de.jaschastarke.modularize.ModuleEntry;
@ -19,7 +22,6 @@ import de.jaschastarke.modularize.ModuleEntry.ModuleState;
public class ModBlockStates extends CoreModule<LimitedCreative> { public class ModBlockStates extends CoreModule<LimitedCreative> {
private BlockStateConfig config; private BlockStateConfig config;
private FeatureBlockItemSpawn blockDrops; private FeatureBlockItemSpawn blockDrops;
private DBQueries queries;
private BlockStateCommand command; private BlockStateCommand command;
private DBModel model; private DBModel model;
@ -56,8 +58,15 @@ public class ModBlockStates extends CoreModule<LimitedCreative> {
@Override @Override
public void onEnable() { public void onEnable() {
try { try {
queries = new DBQueries(this, getPlugin().getDatabaseConnection()); if (model == null) {
queries.initTable(); 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) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
getLog().warn(plugin.getLocale().trans("block_state.error.sql_connection_failed", getName())); getLog().warn(plugin.getLocale().trans("block_state.error.sql_connection_failed", getName()));
@ -79,7 +88,11 @@ public class ModBlockStates extends CoreModule<LimitedCreative> {
} }
@Override @Override
public void onDisable() { public void onDisable() {
model.onDisable();
super.onDisable(); super.onDisable();
if (model instanceof Listener)
listeners.removeListener((Listener) model);
model = null;
} }
public BlockStateConfig getConfig() { public BlockStateConfig getConfig() {
@ -88,12 +101,7 @@ public class ModBlockStates extends CoreModule<LimitedCreative> {
public FeatureBlockItemSpawn getBlockSpawn() { public FeatureBlockItemSpawn getBlockSpawn() {
return blockDrops; return blockDrops;
} }
public DBQueries getQueries() {
return queries;
}
public DBModel getModel() { public DBModel getModel() {
if (model == null)
model = new DBModel(this);
return model; return model;
} }
} }

View file

@ -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<MetadataValue> 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<MetadataValue> 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;
}
}

View file

@ -1,6 +1,5 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate; package de.jaschastarke.minecraft.limitedcreative.blockstate;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -21,6 +20,7 @@ import org.bukkit.metadata.FixedMetadataValue;
import de.jaschastarke.bukkit.lib.events.BlockDestroyedEvent; import de.jaschastarke.bukkit.lib.events.BlockDestroyedEvent;
import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates;
import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.DBTransaction;
public class BlockListener implements Listener { public class BlockListener implements Listener {
private ModBlockStates mod; private ModBlockStates mod;
@ -30,96 +30,62 @@ public class BlockListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) { public void onBlockBreak(BlockBreakEvent event) {
try { if (mod.getModel().isRestricted(event.getBlock())) {
BlockState s = mod.getModel().getState(event.getBlock()); if (mod.isDebug())
if (s != null) { mod.getLog().debug("Breaking bad, err.. block: " + event.getBlock().getLocation().toString());
if (event.getPlayer().getGameMode() != GameMode.CREATIVE) {
if (mod.isDebug()) if (mod.isDebug())
mod.getLog().debug("Breaking bad, err.. block: " + s.toString()); mod.getLog().debug("... was placed by creative. Drop prevented");
mod.getBlockSpawn().block(event.getBlock(), event.getPlayer());
if (s.isRestricted() && event.getPlayer().getGameMode() != GameMode.CREATIVE) { event.setExpToDrop(0);
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);
} }
} catch (SQLException e) {
mod.getLog().warn("DB-Error while onBlockBreak: "+e.getMessage());
event.setCancelled(true);
} }
mod.getModel().removeState(event.getBlock());
} }
@EventHandler @EventHandler
public void onOtherBlockDestroy(BlockDestroyedEvent event) { public void onOtherBlockDestroy(BlockDestroyedEvent event) {
try { if (mod.getModel().isRestricted(event.getBlock())) {
BlockState s = mod.getModel().getState(event.getBlock()); if (mod.isDebug())
if (s != null) { mod.getLog().debug("Breaking attached block: " + event.getBlock().getLocation().toString());
if (mod.isDebug())
mod.getLog().debug("Breaking attached block: " + s.toString()); if (mod.isDebug())
mod.getLog().debug("... was placed by creative. Drop prevented");
if (s.isRestricted()) { mod.getBlockSpawn().block(event.getBlock());
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());
} }
mod.getModel().removeState(event.getBlock());
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlocksBreakByExplosion(EntityExplodeEvent event) { public void onBlocksBreakByExplosion(EntityExplodeEvent event) {
try { Map<Block, Boolean> states = mod.getModel().getRestrictedStates(event.blockList());
Map<Block, BlockState> states = mod.getModel().getStates(event.blockList()); DBTransaction update = mod.getModel().groupUpdate();
for (Block block : event.blockList()) { for (Block block : event.blockList()) {
BlockState s = states.get(block); if (mod.isDebug())
if (s != null) { mod.getLog().debug("Breaking bad, err.. block: " + block.getLocation().toString());
if (mod.isDebug())
mod.getLog().debug("Breaking bad, err.. block: " + s.toString()); if (states.get(block)) {
if (mod.isDebug())
if (s.isRestricted()) { mod.getLog().debug("... was placed by creative. Drop prevented");
if (mod.isDebug()) mod.getBlockSpawn().block(block);
mod.getLog().debug("... was placed by creative. Drop prevented");
mod.getBlockSpawn().block(block);
}
mod.getModel().removeState(s);
}
} }
} catch (SQLException e) {
mod.getLog().warn("DB-Error while onBlockBreakByExplosion: "+e.getMessage()); update.removeState(block);
event.setCancelled(true);
} }
update.finish();
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) { public void onBlockPlace(BlockPlaceEvent event) {
try { BlockState s = new BlockState();
BlockState s = mod.getModel().getState(event.getBlock()); s.setLocation(event.getBlock().getLocation());
if (s != null) { s.setPlayer(event.getPlayer());
// This shouldn't happen s.setDate(new Date());
if (mod.isDebug()) if (mod.isDebug())
mod.getLog().debug("Replacing current BlockState: " + s.toString()); mod.getLog().debug("Saving BlockState: " + s.toString());
} else {
s = new BlockState(); mod.getModel().setState(s);
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);
}
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -138,25 +104,16 @@ public class BlockListener implements Listener {
source = source.getRelative(event.getDirection()); source = source.getRelative(event.getDirection());
} }
try { if (movedBlocks.size() > 0) {
if (movedBlocks.size() > 0) { DBTransaction update = mod.getModel().groupUpdate();
mod.getQueries().getDB().startTransaction(); for (Block sblock : movedBlocks) {
for (Block sblock : movedBlocks) { Block dest = sblock.getRelative(event.getDirection());
Block dest = sblock.getRelative(event.getDirection()); if (mod.isDebug())
if (mod.isDebug()) mod.getLog().debug("PistionExtend moves "+sblock.getType()+"-Block from "+sblock.getLocation()+" to "+dest.getLocation());
mod.getLog().debug("PistionExtend moves "+sblock.getType()+"-Block from "+sblock.getLocation()+" to "+dest.getLocation());
update.moveState(sblock, dest);
mod.getModel().moveState(sblock, dest);
}
mod.getQueries().getDB().endTransaction();
} }
} catch (SQLException e) { update.finish();
try {
mod.getQueries().getDB().revertTransaction();
} catch (SQLException e1) {
}
mod.getLog().warn("DB-Error while onBlockMove (extend): "+e.getMessage());
//event.setCancelled(true);
} }
} }
@ -167,18 +124,9 @@ public class BlockListener implements Listener {
Block dest = event.getBlock().getRelative(event.getDirection()); Block dest = event.getBlock().getRelative(event.getDirection());
Block source = dest.getRelative(event.getDirection()); Block source = dest.getRelative(event.getDirection());
if (event.isSticky() && source.getType() != Material.AIR) { if (event.isSticky() && source.getType() != Material.AIR) {
try { if (mod.isDebug())
if (mod.isDebug()) mod.getLog().debug("PistionRetract moves "+source.getType()+"-Block from "+source.getLocation()+" to "+dest.getLocation());
mod.getLog().debug("PistionRetract moves "+source.getType()+"-Block from "+source.getLocation()+" to "+dest.getLocation()); mod.getModel().moveState(source, source.getRelative(event.getDirection().getOppositeFace()));
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);
}
} }
} }
} }

View file

@ -1,6 +1,5 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate; package de.jaschastarke.minecraft.limitedcreative.blockstate;
import java.sql.SQLException;
import java.util.Date; import java.util.Date;
import org.bukkit.GameMode; import org.bukkit.GameMode;
@ -25,7 +24,8 @@ import de.jaschastarke.maven.ArchiveDocComments;
import de.jaschastarke.minecraft.lib.permissions.IAbstractPermission; import de.jaschastarke.minecraft.lib.permissions.IAbstractPermission;
import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates;
import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source; 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) * 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() { public void run() {
if (mod.isDebug()) if (mod.isDebug())
mod.getLog().debug("Scheduler: Asynchronous Task run"); mod.getLog().debug("Scheduler: Asynchronous Task run");
DBQueries q = mod.getQueries(); DBTransaction update = mod.getModel().groupUpdate();
try { int count = 0;
q.getDB().startTransaction(); World w = selection.getWorld();
int count = 0;
World w = selection.getWorld(); Cuboid c = new Cuboid();
c.add(min);
Cuboid c = new Cuboid(); c.add(max);
c.add(min); mod.getModel().cacheStates(c);
c.add(max);
mod.getModel().cacheStates(c); BlockState seed = new BlockState();
seed.setPlayer(context.getPlayer());
BlockState seed = new BlockState(); seed.setGameMode(tgm);
seed.setPlayer(context.getPlayer()); seed.setSource(Source.COMMAND);
seed.setGameMode(tgm); seed.setDate(new Date());
seed.setSource(Source.COMMAND); for (int x = min.getBlockX(); x <= max.getBlockX(); x++) {
seed.setDate(new Date()); for (int y = min.getBlockY(); y <= max.getBlockY(); y++) {
for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { Location loc = new Location(w, x, y, z);
for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { if (w.getBlockAt(loc).getType() != Material.AIR && selection.contains(loc)) {
Location loc = new Location(w, x, y, z); seed.setLocation(loc);
if (w.getBlockAt(loc).getType() != Material.AIR && selection.contains(loc)) { update.setState(new BlockState(seed));
seed.setLocation(loc); count++;
mod.getModel().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; return true;

View file

@ -43,6 +43,11 @@ public class BlockStateConfig extends Configuration implements IConfigurationSub
} else { } else {
entry.disable(); 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); 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 * BlockStateTool
* *

View file

@ -1,149 +1,86 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate; package de.jaschastarke.minecraft.limitedcreative.blockstate;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block; 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; public interface DBModel {
import de.jaschastarke.minecraft.limitedcreative.blockstate.DBQueries.Cuboid; public static class Cuboid {
private World w = null;
public class DBModel { private int minx, miny, minz;
private static final String BSMDKEY = "blockstate"; private int maxx, maxy, maxz;
private ModBlockStates mod; public void add(Location loc) {
private DBQueries q; if (w == null) {
w = loc.getWorld();
public DBModel(ModBlockStates mod) { minx = maxx = loc.getBlockX();
this.mod = mod; miny = maxy = loc.getBlockY();
this.q = mod.getQueries(); minz = maxz = loc.getBlockZ();
}
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<Block, BlockState> getStates(List<Block> blocks) throws SQLException {
Map<Block, BlockState> ret = new HashMap<Block, BlockState>();
Cuboid c = new Cuboid();
for (Block block : blocks) {
HasBlockState has = getMetaBlock(block);
if (has.set) {
ret.put(block, has.state);
} else { } 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()) { public int getMinX() {
List<BlockState> dbb = q.findAllIn(c); return minx;
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);
}
}
} }
/*for (Block block : blocks) { public int getMinY() {
if (ret.containsKey(block)) return miny;
ret.put(block, getState(block));
}*/
return ret;
}
public void cacheStates(Cuboid c) throws SQLException {
if (!c.isEmpty()) {
List<BlockState> dbb = q.findAllIn(c);
for (BlockState bs : dbb) {
setMetaBlock(bs.getLocation().getBlock(), bs);
}
} }
} public int getMinZ() {
public BlockState getState(Block block) throws SQLException { return minz;
HasBlockState has = getMetaBlock(block);
if (!has.set) {
BlockState state = q.find(block.getLocation());
setMetaBlock(block, state);
return state;
} }
return has.state; public int getMaxX() {
} return maxx;
public void setState(BlockState state) throws SQLException { }
Block block = state.getLocation().getBlock(); public int getMaxY() {
boolean update = hasMetaBlock(block); return maxy;
boolean store = state.isRestricted() || mod.getConfig().getLogSurvival(); }
public int getMaxZ() {
setMetaBlock(block, store ? state : null); return maxz;
}
if (update) { public World getWorld() {
if (!store) return w;
q.delete(state); }
else if (!q.update(state)) public boolean isEmpty() {
q.insert(state); return w == null;
} else { }
if (store) public String toString() {
q.insert(state); 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) { public void onEnable() throws Exception;
List<MetadataValue> metadata = m.getMetadata(BSMDKEY); public void onDisable();
for (MetadataValue v : metadata) { public void moveState(Block from, Block to);
if (v.value() instanceof BlockState) public void removeState(BlockState state);
return true; public void removeState(Block block);
} public Map<Block, BlockState> getStates(List<Block> blocks);
return false; public Map<Block, Boolean> getRestrictedStates(List<Block> blocks);
} public void cacheStates(DBModel.Cuboid c);
protected void setMetaBlock(Metadatable m, BlockState s) { public BlockState getState(Block block);
if (s == null) public boolean isRestricted(Block block);
m.setMetadata(BSMDKEY, new FixedMetadataValue(mod.getPlugin(), new Boolean(false))); public void setState(BlockState state);
else public DBTransaction groupUpdate();
m.setMetadata(BSMDKEY, new FixedMetadataValue(mod.getPlugin(), s));
}
protected HasBlockState getMetaBlock(Metadatable m) {
HasBlockState has = new HasBlockState();
List<MetadataValue> 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());
}
protected static class HasBlockState { public static interface DBTransaction {
public boolean set = false; public void moveState(Block from, Block to);
public BlockState state = null; public void setState(BlockState state);
public void removeState(Block block);
public void finish();
} }
} }

View file

@ -9,7 +9,6 @@ import java.util.List;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World;
import de.jaschastarke.database.Type; import de.jaschastarke.database.Type;
import de.jaschastarke.database.db.Database; import de.jaschastarke.database.db.Database;
@ -49,7 +48,7 @@ public class DBQueries {
} }
private PreparedStatement findall = null; private PreparedStatement findall = null;
public List<BlockState> findAllIn(Cuboid c) throws SQLException { public List<BlockState> findAllIn(DBModel.Cuboid c) throws SQLException {
if (mod.isDebug()) if (mod.isDebug())
mod.getLog().debug("DBQuery: findAllIn: " + c.toString()); mod.getLog().debug("DBQuery: findAllIn: " + c.toString());
List<BlockState> blocks = new ArrayList<BlockState>(); List<BlockState> blocks = new ArrayList<BlockState>();
@ -268,60 +267,4 @@ public class DBQueries {
public Database getDB() { public Database getDB() {
return db; 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+"}";
}
}
} }

View file

@ -1,6 +1,5 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate; package de.jaschastarke.minecraft.limitedcreative.blockstate;
import java.sql.SQLException;
import java.util.Date; import java.util.Date;
import org.bukkit.GameMode; import org.bukkit.GameMode;
@ -14,7 +13,6 @@ import org.bukkit.event.hanging.HangingPlaceEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent;
import de.jaschastarke.minecraft.limitedcreative.ModBlockStates; import de.jaschastarke.minecraft.limitedcreative.ModBlockStates;
import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source;
public class HangingListener implements Listener { public class HangingListener implements Listener {
private ModBlockStates mod; private ModBlockStates mod;
@ -25,36 +23,26 @@ public class HangingListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (event.getRightClicked() instanceof ItemFrame) { if (event.getRightClicked() instanceof ItemFrame) {
try { if (mod.getModel().isRestricted(event.getRightClicked().getLocation().getBlock())) {
BlockState s = mod.getModel().getState(event.getRightClicked().getLocation().getBlock()); if (mod.isDebug())
if (s != null) { mod.getLog().debug("Modifying hanging: " + event.getRightClicked().getLocation().toString());
if (event.getPlayer().getGameMode() != GameMode.CREATIVE) {
if (mod.isDebug()) if (mod.isDebug())
mod.getLog().debug("Modifying hanging: " + s.toString()); mod.getLog().debug("... was placed by creative. Modify prevented");
event.setCancelled(true);
if ((s.getGameMode() == GameMode.CREATIVE || s.getSource() == Source.EDIT) && event.getPlayer().getGameMode() != GameMode.CREATIVE) { return;
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);
} }
} catch (SQLException e) { } else {
mod.getLog().warn("DB-Error while onHangingInteract: "+e.getMessage()); BlockState s = new BlockState();
event.setCancelled(true); 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) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onHangingBreak(HangingBreakEvent event) { public void onHangingBreak(HangingBreakEvent event) {
if (event.getEntity() instanceof ItemFrame) { if (event.getEntity() instanceof ItemFrame) {
try { if (mod.isDebug())
BlockState s = mod.getModel().getState(event.getEntity().getLocation().getBlock()); mod.getLog().debug("Breaking hanging: " + event.getEntity().getLocation().toString());
if (s != null) {
if (mod.isDebug()) if (mod.getModel().isRestricted(event.getEntity().getLocation().getBlock())) {
mod.getLog().debug("Breaking hanging: " + s.toString()); if (mod.isDebug())
mod.getLog().debug("... was placed by creative. Drop prevented");
if (s.getGameMode() == GameMode.CREATIVE || s.getSource() == Source.EDIT) {
if (mod.isDebug()) mod.getBlockSpawn().block(event.getEntity().getLocation().getBlock().getLocation(), Material.ITEM_FRAME);
mod.getLog().debug("... was placed by creative. Drop prevented"); mod.getBlockSpawn().block(event.getEntity().getLocation().getBlock().getLocation(), ((ItemFrame) event.getEntity()).getItem().getType());
//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);
} }
mod.getModel().removeState(event.getEntity().getLocation().getBlock());
} }
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onHangingPlace(HangingPlaceEvent event) { public void onHangingPlace(HangingPlaceEvent event) {
if (event.getEntity() instanceof ItemFrame) { if (event.getEntity() instanceof ItemFrame) {
try { BlockState s = new BlockState();
BlockState s = mod.getModel().getState(event.getEntity().getLocation().getBlock()); s.setLocation(event.getEntity().getLocation().getBlock().getLocation());
if (s != null) { s.setPlayer(event.getPlayer());
// This shouldn't happen s.setDate(new Date());
if (mod.isDebug()) if (mod.isDebug())
mod.getLog().debug("Replacing current BlockState: " + s.toString()); mod.getLog().debug("Saving BlockState: " + s.toString());
} else {
s = new BlockState(); mod.getModel().setState(s);
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);
}
} }
} }
} }

View file

@ -1,7 +1,5 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate; package de.jaschastarke.minecraft.limitedcreative.blockstate;
import java.sql.SQLException;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
@ -46,40 +44,36 @@ public class PlayerListener implements Listener {
} }
private void showInfo(Player pl, Location loc, Material type) { private void showInfo(Player pl, Location loc, Material type) {
try { BlockState s = mod.getModel().getState(loc.getBlock());
BlockState s = mod.getModel().getState(loc.getBlock()); InGameFormatter f = new InGameFormatter(mod.getPlugin().getLang());
InGameFormatter f = new InGameFormatter(mod.getPlugin().getLang()); String ret = null;
String ret = null; if (s == null || s.getSource() == Source.UNKNOWN) {
if (s == null || s.getSource() == Source.UNKNOWN) { ret = f.formatString(ChatFormattings.ERROR, f.getString("block_state.tool_info.unknown", type.toString()));
ret = f.formatString(ChatFormattings.ERROR, f.getString("block_state.tool_info.unknown", type.toString())); } else {
} else { String k = "block_state.tool_info." + s.getSource().name().toLowerCase();
String k = "block_state.tool_info." + s.getSource().name().toLowerCase(); String gm = "";
String gm = ""; if (s.getGameMode() != null) {
if (s.getGameMode() != null) { switch (s.getGameMode()) {
switch (s.getGameMode()) { case CREATIVE:
case CREATIVE: gm = ChatColor.GOLD + s.getGameMode().toString().toLowerCase() + ChatColor.RESET;
gm = ChatColor.GOLD + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; break;
break; case SURVIVAL:
case SURVIVAL: gm = ChatColor.GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET;
gm = ChatColor.GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; break;
break; case ADVENTURE:
case ADVENTURE: gm = ChatColor.DARK_GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET;
gm = ChatColor.DARK_GREEN + s.getGameMode().toString().toLowerCase() + ChatColor.RESET; break;
break; default:
default: break;
break;
}
} }
ret = f.formatString(ChatFormattings.INFO, f.getString(k, type.toString(),
s.getPlayerName(),
gm,
s.getDate()));
} }
if (ret != null)
pl.sendMessage(ret); ret = f.formatString(ChatFormattings.INFO, f.getString(k, type.toString(),
} catch (SQLException e) { s.getPlayerName(),
mod.getLog().warn("DB-Error while onPlayerInteract: "+e.getMessage()); gm,
s.getDate()));
} }
if (ret != null)
pl.sendMessage(ret);
} }
} }

View file

@ -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<Block, Boolean> getRestrictedStates(List<Block> blocks) {
Map<Block, Boolean> ret = new HashMap<Block, Boolean>();
for (Map.Entry<Block, BlockState> entry : getStates(blocks).entrySet()) {
ret.put(entry.getKey(), entry.getValue().isRestricted());
}
return ret;
}
public Map<Block, BlockState> getStates(List<Block> blocks) {
Map<Block, BlockState> ret = new HashMap<Block, BlockState>();
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<BlockState> 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<BlockState> 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;
}
}
}
}

View file

@ -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<Block, Boolean> getRestrictedStates(List<Block> blocks) {
Map<Block, Boolean> ret = new HashMap<Block, Boolean>();
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<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.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<MetadataValue> 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;
}
}

View file

@ -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);
}

View file

@ -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<Block> knownBlocks = new HashSet<Block>();
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;
}
}

View file

@ -0,0 +1,18 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate.thread;
public abstract class CallableAction<E> 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;
}
}

View file

@ -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<BlockState> {
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();
}
}
}

View file

@ -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<List<BlockState>> {
private Cuboid cuboid;
public FetchCuboidAction(Cuboid cuboid) {
this.cuboid = cuboid;
}
@Override
public void process(ThreadLink link, DBQueries q) {
List<BlockState> 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();
}
}
}

View file

@ -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);
}
}

View file

@ -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<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.");
while (!shutdown || !updateQueue.isEmpty()) {
try {
List<Action> acts = new LinkedList<Action>();
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<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;
}
}

View file

@ -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<TransactionAction> actions = new LinkedList<TransactionAction>();
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");
}
}
}

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -1,7 +1,6 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate.worldedit; package de.jaschastarke.minecraft.limitedcreative.blockstate.worldedit;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.Date; import java.util.Date;
import org.bukkit.Location; import org.bukkit.Location;
@ -153,24 +152,19 @@ public class LCEditSessionFactory extends EditSessionFactory {
public boolean onBlockEdit(LocalPlayer player, Vector pt, BaseBlock block) { public boolean onBlockEdit(LocalPlayer player, Vector pt, BaseBlock block) {
if (player != null) { if (player != null) {
Location loc = new Location(((BukkitWorld) player.getWorld()).getWorld(), pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); Location loc = new Location(((BukkitWorld) player.getWorld()).getWorld(), pt.getBlockX(), pt.getBlockY(), pt.getBlockZ());
try { BlockState s = mod.getModel().getState(loc.getBlock());
BlockState s = mod.getModel().getState(loc.getBlock()); if (s == null) {
if (s == null) { s = new BlockState();
s = new BlockState(); s.setLocation(loc);
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;
} }
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; return true;
} else { } else {
return false; return false;