diff --git a/src/main/java/eu/mhsl/minecraft/pixelblocks/Main.java b/src/main/java/eu/mhsl/minecraft/pixelblocks/Main.java index e2e2665..291e44b 100644 --- a/src/main/java/eu/mhsl/minecraft/pixelblocks/Main.java +++ b/src/main/java/eu/mhsl/minecraft/pixelblocks/Main.java @@ -15,10 +15,9 @@ import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; -import java.sql.SQLException; -import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Logger; public final class Main extends JavaPlugin { @@ -28,7 +27,7 @@ public final class Main extends JavaPlugin { private static TaskChainFactory taskFactory; - public final static List pixelBlocks = new ArrayList<>(); + public final static List pixelBlocks = new CopyOnWriteArrayList<>(); public static TaskChain sharedChain(String name) { return taskFactory.newSharedChain(name); @@ -94,11 +93,7 @@ public final class Main extends JavaPlugin { @Override public void onDisable() { Bukkit.getOnlinePlayers().forEach(QuitWhileInPixelBlockListener::kickPlayerOutOfWorld); - try { - database.close(); - } catch(SQLException e) { - throw new RuntimeException("Failed disabling", e); - } + database.close(); } public static Main plugin() { diff --git a/src/main/java/eu/mhsl/minecraft/pixelblocks/PixelBlockDatabase.java b/src/main/java/eu/mhsl/minecraft/pixelblocks/PixelBlockDatabase.java index 9ba530f..8d6ffd5 100644 --- a/src/main/java/eu/mhsl/minecraft/pixelblocks/PixelBlockDatabase.java +++ b/src/main/java/eu/mhsl/minecraft/pixelblocks/PixelBlockDatabase.java @@ -7,14 +7,38 @@ import org.bukkit.Location; import java.sql.*; import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; public class PixelBlockDatabase { private final Connection db; + // Alle Statement-Zugriffe nach dem Startup laufen ausschließlich auf diesem Thread, + // da die geteilten PreparedStatements nicht threadsicher sind. + private final ExecutorService executor = + Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "PixelBlocks-Database")); + private final PreparedStatement getAllPixelBlocks; private final PreparedStatement deletePixelBlock; private final PreparedStatement insertOrReplacePixelBlock; + private record PixelBlockRow( + String uuid, + String owner, + String worldName, + double x, + double y, + double z, + String entryWorldName, + double entryX, + double entryY, + double entryZ, + String direction + ) { + } + public PixelBlockDatabase(String url) { try { Class.forName("org.sqlite.JDBC"); @@ -50,52 +74,78 @@ public class PixelBlockDatabase { } } - public void close() throws SQLException { - deletePixelBlock.close(); - getAllPixelBlocks.close(); - insertOrReplacePixelBlock.close(); - db.close(); + public void close() { + this.executor.shutdown(); + try { + if(!this.executor.awaitTermination(10, TimeUnit.SECONDS)) { + Main.logger().warning("Database executor did not terminate in time, forcing shutdown"); + this.executor.shutdownNow(); + } + } catch(InterruptedException e) { + Thread.currentThread().interrupt(); + this.executor.shutdownNow(); + } + + try { + this.deletePixelBlock.close(); + this.getAllPixelBlocks.close(); + this.insertOrReplacePixelBlock.close(); + this.db.close(); + } catch(SQLException e) { + Main.logger().log(Level.SEVERE, "Failed closing the database", e); + } } public void deletePixelBlock(PixelBlock pixelBlock) { - Bukkit.getScheduler().runTaskAsynchronously(Main.plugin(), () -> { + String uuid = pixelBlock.getBlockUUID().toString(); + this.executor.execute(() -> { try { - this.deletePixelBlock.setString(1, pixelBlock.getBlockUUID().toString()); + this.deletePixelBlock.setString(1, uuid); this.deletePixelBlock.executeUpdate(); } catch(SQLException e) { - throw new RuntimeException("Failed to delete PixelBlock from the database", e); + Main.logger().log(Level.SEVERE, String.format("Failed to delete PixelBlock '%s' from the database", uuid), e); } }); } public void savePixelBlock(PixelBlock pixelBlock) { - Bukkit.getScheduler().runTask(Main.plugin(), () -> { + Location blockLocation = pixelBlock.getPixelBlockLocation(); + Location entryLocation = pixelBlock.hasLastEntryLocation() ? pixelBlock.getLastEntryLocation() : blockLocation; + + PixelBlockRow row = new PixelBlockRow( + pixelBlock.getBlockUUID().toString(), + pixelBlock.getOwnerUUID().toString(), + blockLocation.getWorld().getName(), + blockLocation.getX(), + blockLocation.getY(), + blockLocation.getZ(), + entryLocation.getWorld().getName(), + entryLocation.getX(), + entryLocation.getY(), + entryLocation.getZ(), + pixelBlock.getFacingDirection().toString() + ); + + this.executor.execute(() -> { try { - this.insertOrReplacePixelBlock.setString(1, pixelBlock.getBlockUUID().toString()); - this.insertOrReplacePixelBlock.setString(2, pixelBlock.getOwnerUUID().toString()); + this.insertOrReplacePixelBlock.setString(1, row.uuid()); + this.insertOrReplacePixelBlock.setString(2, row.owner()); - this.insertOrReplacePixelBlock.setString(3, pixelBlock.getPixelBlockLocation().getWorld().getName()); - this.insertOrReplacePixelBlock.setDouble(4, pixelBlock.getPixelBlockLocation().getX()); - this.insertOrReplacePixelBlock.setDouble(5, pixelBlock.getPixelBlockLocation().getY()); - this.insertOrReplacePixelBlock.setDouble(6, pixelBlock.getPixelBlockLocation().getZ()); + this.insertOrReplacePixelBlock.setString(3, row.worldName()); + this.insertOrReplacePixelBlock.setDouble(4, row.x()); + this.insertOrReplacePixelBlock.setDouble(5, row.y()); + this.insertOrReplacePixelBlock.setDouble(6, row.z()); - if(pixelBlock.hasLastEntryLocation()) { - this.insertOrReplacePixelBlock.setString(7, pixelBlock.getLastEntryLocation().getWorld().getName()); - this.insertOrReplacePixelBlock.setDouble(8, pixelBlock.getLastEntryLocation().getX()); - this.insertOrReplacePixelBlock.setDouble(9, pixelBlock.getLastEntryLocation().getY()); - this.insertOrReplacePixelBlock.setDouble(10, pixelBlock.getLastEntryLocation().getZ()); - } else { - this.insertOrReplacePixelBlock.setString(7, pixelBlock.getPixelBlockLocation().getWorld().getName()); - this.insertOrReplacePixelBlock.setDouble(8, pixelBlock.getPixelBlockLocation().getX()); - this.insertOrReplacePixelBlock.setDouble(9, pixelBlock.getPixelBlockLocation().getY()); - this.insertOrReplacePixelBlock.setDouble(10, pixelBlock.getPixelBlockLocation().getZ()); - } + this.insertOrReplacePixelBlock.setString(7, row.entryWorldName()); + this.insertOrReplacePixelBlock.setDouble(8, row.entryX()); + this.insertOrReplacePixelBlock.setDouble(9, row.entryY()); + this.insertOrReplacePixelBlock.setDouble(10, row.entryZ()); - this.insertOrReplacePixelBlock.setString(11, pixelBlock.getFacingDirection().toString()); + this.insertOrReplacePixelBlock.setString(11, row.direction()); this.insertOrReplacePixelBlock.executeUpdate(); } catch(SQLException e) { - throw new RuntimeException("Failed to create or update PixelBlock in the database", e); + Main.logger().log(Level.SEVERE, String.format("Failed to create or update PixelBlock '%s' in the database", row.uuid()), e); } }); } @@ -105,27 +155,32 @@ public class PixelBlockDatabase { ResultSet allPixelBlocks = this.getAllPixelBlocks.executeQuery(); while(allPixelBlocks.next()) { - Location blockLocation = new Location( - Bukkit.getWorld(allPixelBlocks.getString("locationWorldName")), - allPixelBlocks.getDouble("locationX"), - allPixelBlocks.getDouble("locationY"), - allPixelBlocks.getDouble("locationZ") - ); + String uuid = allPixelBlocks.getString("uuid"); + try { + Location blockLocation = new Location( + Bukkit.getWorld(allPixelBlocks.getString("locationWorldName")), + allPixelBlocks.getDouble("locationX"), + allPixelBlocks.getDouble("locationY"), + allPixelBlocks.getDouble("locationZ") + ); - Location entryLocation = new Location( - Bukkit.getWorld(allPixelBlocks.getString("entryLocationWorldName")), - allPixelBlocks.getDouble("entryLocationX"), - allPixelBlocks.getDouble("entryLocationY"), - allPixelBlocks.getDouble("entryLocationZ") - ); + Location entryLocation = new Location( + Bukkit.getWorld(allPixelBlocks.getString("entryLocationWorldName")), + allPixelBlocks.getDouble("entryLocationX"), + allPixelBlocks.getDouble("entryLocationY"), + allPixelBlocks.getDouble("entryLocationZ") + ); - Main.pixelBlocks.add(PixelBlock.fromExisting( - UUID.fromString(allPixelBlocks.getString("uuid")), - UUID.fromString(allPixelBlocks.getString("owner")), - blockLocation, - Direction.valueOf(allPixelBlocks.getString("direction")), - entryLocation - )); + Main.pixelBlocks.add(PixelBlock.fromExisting( + UUID.fromString(uuid), + UUID.fromString(allPixelBlocks.getString("owner")), + blockLocation, + Direction.valueOf(allPixelBlocks.getString("direction")), + entryLocation + )); + } catch(Exception e) { + Main.logger().log(Level.SEVERE, String.format("Failed initializing existing pixelblock '%s'", uuid), e); + } } } catch(SQLException e) { throw new RuntimeException("Failed loading PixelBlocks from the database", e); diff --git a/src/main/java/eu/mhsl/minecraft/pixelblocks/pixelblock/PixelBlock.java b/src/main/java/eu/mhsl/minecraft/pixelblocks/pixelblock/PixelBlock.java index 018ed2f..0582258 100644 --- a/src/main/java/eu/mhsl/minecraft/pixelblocks/pixelblock/PixelBlock.java +++ b/src/main/java/eu/mhsl/minecraft/pixelblocks/pixelblock/PixelBlock.java @@ -118,12 +118,8 @@ public class PixelBlock { this.scheduleEntityUpdate(); - this.getBlockTaskChain() - .async(() -> { - Main.database().savePixelBlock(this); - Main.pixelBlocks.add(this); - }) - .execute(); + Main.database().savePixelBlock(this); + Main.pixelBlocks.add(this); this.getBlockTaskChain().sync(() -> this.isAccessible = true).execute(); } @@ -139,9 +135,9 @@ public class PixelBlock { } this.lastEntryLocation = player.getLocation(); + Main.database().savePixelBlock(this); getBlockTaskChain() - .async(() -> Main.database().savePixelBlock(this)) .sync(() -> { if(!this.isAccessible) return; player.teleport(this.pixelWorld.getSpawnLocation()); @@ -204,11 +200,10 @@ public class PixelBlock { world.playSound(this.pixelBlockLocation, Sound.BLOCK_COPPER_BULB_BREAK, 1.0F, 30); world.dropItem(this.pixelBlockLocation.add(new Vector(0.5, 0.5, 0.5)), PixelBlockItem.getBlockAsItem(this)); }) - .async(() -> { - Main.database().deletePixelBlock(this); - Main.pixelBlocks.remove(this); - }) .execute(); + + Main.database().deletePixelBlock(this); + Main.pixelBlocks.remove(this); } private void removeEntities() {