diff --git a/build.gradle.kts b/build.gradle.kts index e3a92fd0..624ef7a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "io.tofpu.speedbridge2" -version = "1.0.4" +version = "1.0.5" tasks { compileJava { @@ -24,6 +24,8 @@ tasks { relocate("net.kyori.adventure", "io.tofpu.speedbridge2.lib.adventure") relocate("com.zaxxer.HikariCP", "io.tofpu.speedbridge2.lib.hikaricp") relocate("org.bstats", "io.tofpu.speedbridge2.lib.bstats") + relocate("com.github.benmanes.caffeine", "io.tofpu.speedbridge2.lib.caffeine") + relocate("org.apache.commons", "io.tofpu.speedbridge2.lib.commons") } exclude("META-INF/**") @@ -57,7 +59,7 @@ repositories { dependencies { compileOnly("org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT") - implementation("org.xerial:sqlite-jdbc:3.36.0.3") + compileOnly("org.xerial:sqlite-jdbc:3.36.0.3") compileOnly("com.sk89q:worldedit:6.0.0-SNAPSHOT") @@ -74,11 +76,14 @@ dependencies { implementation("org.spongepowered:configurate-hocon:4.1.2") implementation("commons-lang:commons-lang:2.6") + implementation("commons-io:commons-io:2.11.0") implementation("org.bstats:bstats-bukkit:3.0.0") compileOnly("me.clip:placeholderapi:2.10.10") + implementation("com.github.ben-manes.caffeine:caffeine:3.0.5") + implementation("com.github.cryptomorin:XSeries:8.6.1") implementation("com.github.tofpu.MultiWorldEdit:multiworldedit-api:f9ad4ce832") { exclude("de.schlichtherle", "truezip") diff --git a/src/main/java/io/tofpu/speedbridge2/SpeedBridge.java b/src/main/java/io/tofpu/speedbridge2/SpeedBridge.java index e5c2eeff..4e80880b 100644 --- a/src/main/java/io/tofpu/speedbridge2/SpeedBridge.java +++ b/src/main/java/io/tofpu/speedbridge2/SpeedBridge.java @@ -36,6 +36,11 @@ public SpeedBridge(final JavaPlugin javaPlugin) { } public void load() { + // reset the world, in-case it does exist + SchematicManager.INSTANCE.resetWorld(); + } + + public void enable() { adventure = BukkitAudiences.create(javaPlugin); new Metrics(javaPlugin, 14597); @@ -61,7 +66,7 @@ public void load() { } log("Loading the `speedbridge2` world..."); - SchematicManager.INSTANCE.load(javaPlugin); + SchematicManager.INSTANCE.load(); IslandSetupManager.INSTANCE.load(); @@ -80,7 +85,7 @@ public void load() { BridgeUtil.whenComplete(islandService.load(), () -> { log("Loading the global/session leaderboard..."); - BridgeUtil.whenComplete(Leaderboard.INSTANCE.load(), () -> { + BridgeUtil.whenComplete(Leaderboard.INSTANCE.loadAsync(), () -> { log("Loading the island leaderboard..."); // when the global leaderboard is complete, load the per-island @@ -132,7 +137,8 @@ public void shutdown() { PlayerService.INSTANCE.shutdown(); log("Unloading the `speedbridge2` world..."); - Bukkit.unloadWorld("speedbridge2", false); + SchematicManager.INSTANCE.unloadWorld(); + SchematicManager.INSTANCE.resetWorld(); log("Complete."); } diff --git a/src/main/java/io/tofpu/speedbridge2/command/subcommand/SpeedBridgeCommand.java b/src/main/java/io/tofpu/speedbridge2/command/subcommand/SpeedBridgeCommand.java index 5dd50244..8bf1df2f 100644 --- a/src/main/java/io/tofpu/speedbridge2/command/subcommand/SpeedBridgeCommand.java +++ b/src/main/java/io/tofpu/speedbridge2/command/subcommand/SpeedBridgeCommand.java @@ -121,7 +121,7 @@ public void onPlayerReset(final CommonBridgePlayer bridgePlayer, final @Argum BridgePlayer target = PlayerService.INSTANCE.get(uuidResult); if (target == null) { try { - PlayerService.INSTANCE.load(uuidResult) + PlayerService.INSTANCE.loadAsync(uuidResult) .get(); } catch (InterruptedException | ExecutionException e) { BridgeUtil.sendMessage(bridgePlayer, INSTANCE.somethingWentWrong); @@ -349,7 +349,6 @@ public void pluginReload(final CommonBridgePlayer player) { @CommandMethod("speedbridge|sb") @CommandDescription("Shows a list of commands") - @CommandPermission("speedbridge.help") @Hidden public void onNoArgument(final CommonBridgePlayer bridgePlayer) { final CommandSender player = bridgePlayer.getPlayer(); diff --git a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/Leaderboard.java b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/Leaderboard.java index 7362e19a..308288a6 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/Leaderboard.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/Leaderboard.java @@ -1,7 +1,7 @@ package io.tofpu.speedbridge2.domain.extra.leaderboard; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.LoadingCache; +import com.github.benmanes.caffeine.cache.AsyncLoadingCache; +import com.github.benmanes.caffeine.cache.Caffeine; import io.tofpu.speedbridge2.domain.common.PluginExecutor; import io.tofpu.speedbridge2.domain.common.config.manager.ConfigurationManager; import io.tofpu.speedbridge2.domain.common.database.wrapper.DatabaseQuery; @@ -17,6 +17,7 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public final class Leaderboard { @@ -25,31 +26,30 @@ public final class Leaderboard { private final LeaderboardMap globalMap; private final Map sessionalMap; - private final LoadingCache playerCache; - private final LoadingCache islandPositionMap; + private final AsyncLoadingCache playerCache; + private final AsyncLoadingCache islandPositionMap; private Leaderboard() { this.globalMap = new LeaderboardMap(); this.sessionalMap = new ConcurrentHashMap<>(); // player's personal global position - this.playerCache = CacheBuilder.newBuilder() + this.playerCache = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.SECONDS) - .build(PersonalBoardLoader.INSTANCE); + .buildAsync(PersonalBoardLoader.INSTANCE); // player's global position that based on an island - this.islandPositionMap = CacheBuilder.newBuilder() + this.islandPositionMap = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.SECONDS) - .build(IslandLoader.INSTANCE); + .buildAsync(IslandLoader.INSTANCE); } /** * Load the leaderboard from the database * - * @param javaPlugin The plugin that is calling this method. * @return Nothing. */ - public CompletableFuture load() { + public CompletableFuture loadAsync() { final CompletableFuture loadFuture = new CompletableFuture<>(); BridgeUtil.runBukkitAsync(() -> { @@ -107,7 +107,7 @@ public CompletableFuture load() { // per-player based position operation for (final UUID uuid : playerCache.asMap() .keySet()) { - this.playerCache.refresh(uuid); + this.playerCache.synchronous().refresh(uuid); } // update the global leaderboard @@ -161,16 +161,15 @@ public CompletableFuture load() { * @return A CompletableFuture */ public CompletableFuture retrieve(final UUID uniqueId) { - final BoardPlayer player = playerCache.asMap() + final CompletableFuture completableFuture = playerCache.asMap() .get(uniqueId); - // if the board player is found, return the completed value - if (player != null) { - return CompletableFuture.completedFuture(player); + if (completableFuture == null) { + // loading the board player + return playerCache.get(uniqueId); } - // otherwise, attempt to load the player board async - return CompletableFuture.supplyAsync(() -> playerCache.getUnchecked(uniqueId)); + return completableFuture; } /** @@ -200,8 +199,8 @@ public BoardPlayer retrieve(final LeaderboardRetrieveType leaderboardRetrieveTyp * @return The IslandBoard object. */ public CompletableFuture retrieve(final UUID uniqueId, final int islandSlot) { - final IslandBoardPlayer player = islandPositionMap.asMap() - .get(uniqueId); + final IslandBoardPlayer player = + islandPositionMap.synchronous().getIfPresent(uniqueId); final IslandBoardPlayer.IslandBoard islandBoard = player == null ? null : player.findDefault(islandSlot); @@ -211,8 +210,13 @@ public CompletableFuture retrieve(final UUID uniq } // otherwise, attempt to retrieve the board async - return PluginExecutor.supply(() -> islandPositionMap.getUnchecked(uniqueId) - .retrieve(islandSlot)); + return PluginExecutor.supply(() -> { + try { + return islandPositionMap.get(uniqueId).get().retrieve(islandSlot); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + }); } /** diff --git a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/IslandLoader.java b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/IslandLoader.java index 03a1ebdc..1acd64ff 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/IslandLoader.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/IslandLoader.java @@ -1,16 +1,17 @@ package io.tofpu.speedbridge2.domain.extra.leaderboard.loader; -import com.google.common.cache.CacheLoader; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; +import com.github.benmanes.caffeine.cache.CacheLoader; import io.tofpu.speedbridge2.domain.common.util.BridgeUtil; import io.tofpu.speedbridge2.domain.extra.leaderboard.meta.BoardRetrieve; import io.tofpu.speedbridge2.domain.extra.leaderboard.wrapper.IslandBoardPlayer; import org.jetbrains.annotations.NotNull; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; -public final class IslandLoader extends CacheLoader implements BoardRetrieve { +public final class IslandLoader implements CacheLoader, + BoardRetrieve { public static final IslandLoader INSTANCE = new IslandLoader(); private IslandLoader() {} @@ -22,14 +23,20 @@ private IslandLoader() {} } @Override - public ListenableFuture reload(final @NotNull UUID key, final @NotNull IslandBoardPlayer oldValue) { + public CompletableFuture asyncReload(final UUID key, final IslandBoardPlayer oldValue, final Executor executor) throws Exception { BridgeUtil.debug("attempting to reload " + key); - return Futures.immediateFuture(retrieve(key)); + return retrieveAsync(key, executor); } @Override - public @NotNull IslandBoardPlayer retrieve(final @NotNull UUID key) { + public IslandBoardPlayer retrieve(final @NotNull UUID uniqueId) { + return new IslandBoardPlayer(uniqueId); + } + + @Override + public @NotNull CompletableFuture retrieveAsync(final @NotNull UUID key, + final @NotNull Executor executor) { BridgeUtil.debug("retrieving " + key); - return new IslandBoardPlayer(key); + return CompletableFuture.supplyAsync(() -> retrieve(key), executor); } } diff --git a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/PersonalBoardLoader.java b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/PersonalBoardLoader.java index fe89a6f6..b1e0128d 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/PersonalBoardLoader.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/loader/PersonalBoardLoader.java @@ -1,9 +1,8 @@ package io.tofpu.speedbridge2.domain.extra.leaderboard.loader; -import com.google.common.cache.CacheLoader; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; +import com.github.benmanes.caffeine.cache.CacheLoader; import io.tofpu.speedbridge2.domain.common.PlayerNameCache; +import io.tofpu.speedbridge2.domain.common.PluginExecutor; import io.tofpu.speedbridge2.domain.common.database.wrapper.DatabaseQuery; import io.tofpu.speedbridge2.domain.common.database.wrapper.DatabaseSet; import io.tofpu.speedbridge2.domain.common.util.BridgeUtil; @@ -16,60 +15,86 @@ import org.jetbrains.annotations.Nullable; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; -public final class PersonalBoardLoader extends CacheLoader implements BoardRetrieve { +public final class PersonalBoardLoader implements CacheLoader, BoardRetrieve { public static final PersonalBoardLoader INSTANCE = new PersonalBoardLoader(); private static final String GLOBAL_POSITION = "SELECT DISTINCT 1 + COUNT(*) AS " + "position FROM scores WHERE score < (SELECT score FROM scores WHERE uid = ?)"; private PersonalBoardLoader() {} + @Override - public @Nullable BoardPlayer load(final @NotNull UUID key) throws Exception { + public @Nullable BoardPlayer load(final UUID key) throws Exception { return retrieve(key); } @Override - public ListenableFuture reload(final @NotNull UUID key, final @NotNull BoardPlayer oldValue) { - return Futures.immediateFuture(retrieve(key)); + public CompletableFuture asyncLoad(final UUID key, final Executor executor) { + return retrieveAsync(key, executor); } @Override - public @Nullable BoardPlayer retrieve(final @NotNull UUID key) { - BridgeUtil.debug("PersonalBoardLoader#retrieve(): key: " + key); - try (final DatabaseQuery databaseQuery = DatabaseQuery.query(GLOBAL_POSITION)) { - databaseQuery.setString(key.toString()); - - final AtomicReference boardPlayer = new AtomicReference<>(); - databaseQuery.executeQuery(resultSet -> { - BridgeUtil.debug("PersonalBoardLoader#retrieve(): executeQuery:"); - if (!resultSet.next()) { - System.out.println("PersonalBoardLoader#retrieve(): next: " + "false"); - return; - } + public CompletableFuture asyncReload(final UUID key, final BoardPlayer oldValue, final Executor executor) throws Exception { + return retrieveAsync(key, executor); + } - BridgeUtil.debug("PersonalBoardLoader#retrieve(): next: " + "true"); - BoardPlayer value = toBoardPlayer(key, resultSet); - final BridgePlayer player = PlayerService.INSTANCE.get(key); - if (player != null && player.getScores().isEmpty()) { - value = new BoardPlayer(value.getName(), 0, key, value.getScore()); - } + @Override + public BoardPlayer retrieve(final @NotNull UUID uniqueId) { + final CompletableFuture future = retrieveAsync(uniqueId, PluginExecutor.INSTANCE); + if (!future.isDone()) { + return null; + } - boardPlayer.set(value); - }); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } - final BoardPlayer player = boardPlayer.get(); + @Override + public @NotNull CompletableFuture retrieveAsync(final @NotNull UUID key, + final @NotNull Executor executor) { + return CompletableFuture.supplyAsync(() -> { + BridgeUtil.debug("PersonalBoardLoader#retrieve(): key: " + key); + try (final DatabaseQuery databaseQuery = DatabaseQuery.query(GLOBAL_POSITION)) { + databaseQuery.setString(key.toString()); + + final AtomicReference boardPlayer = new AtomicReference<>(); + databaseQuery.executeQuery(resultSet -> { + BridgeUtil.debug("PersonalBoardLoader#retrieve(): executeQuery:"); + if (!resultSet.next()) { + System.out.println("PersonalBoardLoader#retrieve(): next: " + "false"); + return; + } + + BridgeUtil.debug("PersonalBoardLoader#retrieve(): next: " + "true"); + BoardPlayer value = toBoardPlayer(key, resultSet); + final BridgePlayer player = PlayerService.INSTANCE.get(key); + if (player != null && player.getScores().isEmpty()) { + value = new BoardPlayer(value.getName(), 0, key, value.getScore()); + } + + boardPlayer.set(value); + }); - BridgeUtil.debug("PersonalBoardLoader#retrieve(): player: " + player); - if (player == null) { - return new BoardPlayer(PlayerNameCache.INSTANCE.getOrDefault(key), - 0, key, new Score(-1, -1)); + final BoardPlayer player = boardPlayer.get(); + + BridgeUtil.debug("PersonalBoardLoader#retrieve(): player: " + player); + if (player == null) { + return new BoardPlayer(PlayerNameCache.INSTANCE.getOrDefault(key), + 0, key, new Score(-1, -1)); + } + return player; + } catch (final Exception e) { + throw new IllegalStateException(e); } - return player; - } catch (final Exception e) { - throw new IllegalStateException(e); - } + }, executor); } public BoardPlayer toBoardPlayer(final UUID uid, final DatabaseSet databaseSet) { diff --git a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/meta/BoardRetrieve.java b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/meta/BoardRetrieve.java index 56355dc3..df3a26a0 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/meta/BoardRetrieve.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/extra/leaderboard/meta/BoardRetrieve.java @@ -3,7 +3,11 @@ import org.jetbrains.annotations.NotNull; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; public interface BoardRetrieve { T retrieve(final @NotNull UUID uniqueId); + CompletableFuture retrieveAsync(final @NotNull UUID uniqueId, + final @NotNull Executor executor); } diff --git a/src/main/java/io/tofpu/speedbridge2/domain/island/schematic/SchematicManager.java b/src/main/java/io/tofpu/speedbridge2/domain/island/schematic/SchematicManager.java index da4a89ae..a8403a09 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/island/schematic/SchematicManager.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/island/schematic/SchematicManager.java @@ -1,20 +1,24 @@ package io.tofpu.speedbridge2.domain.island.schematic; import com.sk89q.worldedit.WorldEditException; +import io.tofpu.speedbridge2.domain.common.config.manager.ConfigurationManager; import io.tofpu.speedbridge2.domain.common.util.BridgeUtil; import io.tofpu.speedbridge2.domain.island.object.Island; import io.tofpu.speedbridge2.domain.island.object.extra.GameIsland; import io.tofpu.speedbridge2.domain.island.plot.IslandPlot; import io.tofpu.speedbridge2.domain.player.object.GamePlayer; +import org.apache.commons.io.FileUtils; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.WorldCreator; +import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; -import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.io.IOException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -25,21 +29,19 @@ public final class SchematicManager { new HashMap<>(); private static final AtomicInteger COUNTER = new AtomicInteger(); - private File worldFile; + private File worldDirectory; private @Nullable World world; private SchematicManager() {} - public void load(final @NotNull Plugin plugin) { + public void load() { World world = Bukkit.getWorld("speedbridge2"); if (world == null) { world = Bukkit.createWorld(WorldCreator.name("speedbridge2") .generator(new EmptyChunkGenerator())); } - this.world = world; - worldFile = new File(plugin.getDataFolder() - .getParentFile(), "speedbridge2"); + this.world = world; protectWorld(world); } @@ -126,6 +128,18 @@ private IslandPlot createIslandPlot(final Collection islandPlots, fi return islandPlot; } + public void resetWorld() { + final File worldFile = getWorldDirectory(); + if (worldFile != null && worldFile.exists()) { + try { + // delete the world outright + FileUtils.forceDelete(worldFile); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + public Collection retrieve(final int slot) { return ISLAND_PLOTS.getOrDefault(slot, new ArrayList<>()); } @@ -134,8 +148,39 @@ public void clearPlot(final int slot) { ISLAND_PLOTS.remove(slot); } - public File getWorldFile() { - return worldFile; + public File getWorldDirectory() { + if (worldDirectory == null || !worldDirectory.exists()) { + this.worldDirectory = new File( "speedbridge2"); + } + return worldDirectory; + } + + public void unloadWorld() { + if (world == null) { + return; + } + + movePlayersFromIslandWorld(); + + Bukkit.unloadWorld(world, false); + } + + private void movePlayersFromIslandWorld() { + for (final Player player : Bukkit.getOnlinePlayers()) { + if (!player.getWorld() + .getName() + .equals(world.getName())) { + continue; + } + Location lobbyLocation = ConfigurationManager.INSTANCE.getLobbyCategory() + .getLobbyLocation(); + + if (lobbyLocation == null) { + lobbyLocation = Bukkit.getServer().getWorlds().get(0).getSpawnLocation(); + } + + player.teleport(lobbyLocation); + } } public static final class EmptyChunkGenerator extends ChunkGenerator { diff --git a/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetup.java b/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetup.java index 700c8f0e..b3946e32 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetup.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetup.java @@ -8,6 +8,7 @@ import io.tofpu.speedbridge2.domain.island.plot.IslandPlot; import io.tofpu.speedbridge2.domain.player.object.BridgePlayer; import io.tofpu.speedbridge2.support.worldedit.CuboidRegion; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; @@ -58,7 +59,6 @@ public boolean finish() { return false; } this.removed = true; - playerEditor.toggleSetup(); final Location absoluteLocation = islandPlot.getPlotLocation() .subtract(this.playerSpawnPoint); @@ -67,6 +67,8 @@ public boolean finish() { island.setAbsoluteLocation(absoluteLocation); + resetState(); + // teleporting the player to the lobby location playerEditor.getPlayer() .teleport(ConfigurationManager.INSTANCE.getLobbyCategory() @@ -139,7 +141,7 @@ public void cancel() { this.removed = true; IslandSetupManager.INSTANCE.invalidate(this); - playerEditor.toggleSetup(); + resetState(); // teleporting the player to the lobby location playerEditor.getPlayer() @@ -149,6 +151,13 @@ public void cancel() { resetPlot(); } + private void resetState() { + playerEditor.toggleSetup(); + + // setting the player's gamemode back to survival + playerEditor.getPlayer().setGameMode(GameMode.SURVIVAL); + } + /** * Returns the UUID of the player that is currently editing this object * diff --git a/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetupManager.java b/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetupManager.java index 8d379830..c1e6e09e 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetupManager.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/island/setup/IslandSetupManager.java @@ -5,6 +5,7 @@ import io.tofpu.speedbridge2.domain.island.plot.IslandPlot; import io.tofpu.speedbridge2.domain.player.object.BridgePlayer; import org.bukkit.Bukkit; +import org.bukkit.GameMode; import org.bukkit.World; import java.util.HashMap; @@ -32,6 +33,7 @@ public boolean startSetup(final BridgePlayer bridgePlayer, final Island island) return false; } bridgePlayer.toggleSetup(); + bridgePlayer.getPlayer().setGameMode(GameMode.CREATIVE); final IslandSetup islandSetup = create(bridgePlayer, island); final IslandPlot islandPlot = islandSetup.getIslandPlot(); diff --git a/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerHandler.java b/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerHandler.java index 2290ed2d..cb63ee9a 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerHandler.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerHandler.java @@ -1,13 +1,13 @@ package io.tofpu.speedbridge2.domain.player; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.LoadingCache; -import io.tofpu.speedbridge2.domain.common.PluginExecutor; +import com.github.benmanes.caffeine.cache.AsyncLoadingCache; +import com.github.benmanes.caffeine.cache.Caffeine; import io.tofpu.speedbridge2.domain.common.database.Databases; import io.tofpu.speedbridge2.domain.island.setup.IslandSetupManager; import io.tofpu.speedbridge2.domain.player.loader.PlayerLoader; import io.tofpu.speedbridge2.domain.player.object.BridgePlayer; import io.tofpu.speedbridge2.domain.player.object.GamePlayer; +import io.tofpu.speedbridge2.domain.player.object.extra.DummyBridgePlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,15 +15,14 @@ import java.util.Collections; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; public final class PlayerHandler { - private final @NotNull LoadingCache playerMap; + private final @NotNull AsyncLoadingCache playerMap; public PlayerHandler() { - this.playerMap = CacheBuilder.newBuilder() - .expireAfterAccess(5, TimeUnit.MINUTES) - .build(PlayerLoader.INSTANCE); + this.playerMap = Caffeine.newBuilder() + .expireAfter(PlayerLoader.PlayerRemovalListener.INSTANCE) + .buildAsync(PlayerLoader.INSTANCE); } /** @@ -33,11 +32,8 @@ public PlayerHandler() { * @param uniqueId The unique ID of the player. * @return A CompletableFuture */ - public CompletableFuture load(final UUID uniqueId) { - return PluginExecutor.supply(() -> { - // for loading purposes - return this.playerMap.getUnchecked(uniqueId); - }); + public CompletableFuture loadAsync(final UUID uniqueId) { + return this.playerMap.get(uniqueId); } /** @@ -47,7 +43,7 @@ public CompletableFuture load(final UUID uniqueId) { * @return A BridgePlayer object. */ public @Nullable BridgePlayer get(final UUID uniqueId) { - return this.playerMap.asMap().get(uniqueId); + return this.playerMap.synchronous().getIfPresent(uniqueId); } /** @@ -57,33 +53,38 @@ public CompletableFuture load(final UUID uniqueId) { * @return A BridgePlayer object. */ public @NotNull BridgePlayer getOrDefault(final UUID uniqueId) { - return this.playerMap.asMap().getOrDefault(uniqueId, BridgePlayer.of(uniqueId)); + final BridgePlayer bridgePlayer = get(uniqueId); + if (bridgePlayer == null) { + return DummyBridgePlayer.of(uniqueId); + } + return bridgePlayer; } /** - * Remove a player from the player map + * Remove a player from the player map if present * * @param uniqueId The unique ID of the player to remove. - * @return The BridgePlayer object that was removed from the map. */ - public @Nullable BridgePlayer remove(final UUID uniqueId) { - return this.playerMap.asMap().remove(uniqueId); + public void remove(final UUID uniqueId) { + if (uniqueId == null) { + return; + } + this.playerMap.synchronous().invalidate(uniqueId); } /** * If the player is in the - * database, update the name and refresh the player + * database, update the name and refresh the player instance * * @param name The name of the player. * @param uniqueId The unique ID of the player. - * @return A BridgePlayer object. */ - public @Nullable BridgePlayer internalRefresh(final String name, + public void internalRefresh(final String name, final UUID uniqueId) { final BridgePlayer bridgePlayer = get(uniqueId); if (bridgePlayer == null) { - load(uniqueId); - return null; + loadAsync(uniqueId); + return; } if (!bridgePlayer.getName().equals(name)) { @@ -91,8 +92,6 @@ public CompletableFuture load(final UUID uniqueId) { } bridgePlayer.internalRefresh(uniqueId); - - return bridgePlayer; } /** @@ -108,6 +107,7 @@ public CompletableFuture load(final UUID uniqueId) { return null; } bridgePlayer.invalidatePlayer(); + playerMap.asMap().compute(uniqueId, (uuid, bridgePlayerCompletableFuture) -> CompletableFuture.completedFuture(bridgePlayer)); IslandSetupManager.INSTANCE.invalidate(uniqueId); @@ -120,7 +120,8 @@ public CompletableFuture load(final UUID uniqueId) { * @return A collection of all the players in the game. */ public Collection getBridgePlayers() { - return Collections.unmodifiableCollection(playerMap.asMap().values()); + return Collections.unmodifiableCollection(playerMap.synchronous().asMap() + .values()); } /** diff --git a/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerService.java b/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerService.java index feac8c03..21f2c430 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerService.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/player/PlayerService.java @@ -25,8 +25,8 @@ public PlayerService() { * @param uid The unique ID of the player. * @return A CompletableFuture */ - public CompletableFuture load(final UUID uid) { - return playerHandler.load(uid); + public CompletableFuture loadAsync(final UUID uid) { + return playerHandler.loadAsync(uid); } /** @@ -50,25 +50,23 @@ public CompletableFuture load(final UUID uid) { } /** - * Remove a player from the player map + * Remove a player from the player map if present * * @param uniqueId The unique ID of the player to remove. - * @return The BridgePlayer object that was removed from the map. */ - public @Nullable BridgePlayer remove(final @NotNull UUID uniqueId) { - return playerHandler.remove(uniqueId); + public void remove(final @NotNull UUID uniqueId) { + playerHandler.remove(uniqueId); } /** * If the player is in the - * database, update the name and refresh the player + * database, update the name and refresh the player instance * * @param name The name of the player. * @param uniqueId The unique ID of the player. - * @return A BridgePlayer object. */ - public @Nullable BridgePlayer internalRefresh(final @NotNull Player player) { - return playerHandler.internalRefresh(player.getName(), player.getUniqueId()); + public void internalRefresh(final @NotNull Player player) { + playerHandler.internalRefresh(player.getName(), player.getUniqueId()); } /** diff --git a/src/main/java/io/tofpu/speedbridge2/domain/player/loader/PlayerLoader.java b/src/main/java/io/tofpu/speedbridge2/domain/player/loader/PlayerLoader.java index c33ddc45..c09ad88f 100644 --- a/src/main/java/io/tofpu/speedbridge2/domain/player/loader/PlayerLoader.java +++ b/src/main/java/io/tofpu/speedbridge2/domain/player/loader/PlayerLoader.java @@ -1,21 +1,34 @@ package io.tofpu.speedbridge2.domain.player.loader; -import com.google.common.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Expiry; +import io.tofpu.speedbridge2.domain.common.PluginExecutor; import io.tofpu.speedbridge2.domain.common.database.Databases; import io.tofpu.speedbridge2.domain.common.util.BridgeUtil; import io.tofpu.speedbridge2.domain.extra.leaderboard.meta.BoardRetrieve; import io.tofpu.speedbridge2.domain.player.object.BridgePlayer; +import org.checkerframework.checker.index.qual.NonNegative; import org.jetbrains.annotations.NotNull; +import java.time.Duration; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; -public final class PlayerLoader extends CacheLoader implements BoardRetrieve { +public final class PlayerLoader implements BoardRetrieve, CacheLoader { public static final PlayerLoader INSTANCE = new PlayerLoader(); @Override public BridgePlayer load(final @NotNull UUID key) throws Exception { - return retrieve(key); + return this.retrieve(key); + } + + @Override + public CompletableFuture asyncLoad(final UUID key, final Executor executor) { + // TODO: maybe pass down the executor + return retrieveAsync(key, executor); } @Override @@ -25,8 +38,7 @@ public BridgePlayer retrieve(final @NotNull UUID uniqueId) { try { BridgeUtil.debug("attempting to load " + uniqueId + " player's data!"); - bridgePlayer = Databases.PLAYER_DATABASE.getStoredPlayer(uniqueId) - .get(); + bridgePlayer = retrieveAsync(uniqueId, PluginExecutor.INSTANCE).get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); @@ -35,4 +47,40 @@ public BridgePlayer retrieve(final @NotNull UUID uniqueId) { } return bridgePlayer; } + + @Override + public CompletableFuture retrieveAsync(final @NotNull UUID uniqueId, + final @NotNull Executor executor) { + BridgeUtil.debug("attempting to load " + uniqueId + " player's data!"); + return Databases.PLAYER_DATABASE.getStoredPlayer(uniqueId); + } + + public static final class PlayerRemovalListener implements Expiry { + private static final long EXPIRY_DURATION = TimeUnit.MINUTES.toNanos(5); + public static final PlayerRemovalListener INSTANCE = new PlayerRemovalListener(); + + @Override + public long expireAfterCreate(final UUID key, final BridgePlayer value, final long currentTime) { + return Long.MAX_VALUE; + } + + @Override + public long expireAfterUpdate(final UUID key, final BridgePlayer value, final long currentTime, + @NonNegative final long currentDuration) { + System.out.println("expireAfterUpdate - Start"); + System.out.println(Duration.ofNanos(currentDuration).getSeconds()); + if (value.getPlayer() == null) { + System.out.println("expiring the bridge player after 5 minutes"); + return EXPIRY_DURATION; + } + System.out.println("expireAfterUpdate - End"); + return Long.MAX_VALUE; + } + + @Override + public long expireAfterRead(final UUID key, final BridgePlayer value, final long currentTime, + @NonNegative final long currentDuration) { + return currentDuration; + } + } } diff --git a/src/main/java/io/tofpu/speedbridge2/plugin/SpeedBridgePlugin.java b/src/main/java/io/tofpu/speedbridge2/plugin/SpeedBridgePlugin.java index dadc5a77..6680c09f 100644 --- a/src/main/java/io/tofpu/speedbridge2/plugin/SpeedBridgePlugin.java +++ b/src/main/java/io/tofpu/speedbridge2/plugin/SpeedBridgePlugin.java @@ -11,10 +11,15 @@ public SpeedBridgePlugin() { } @Override - public void onEnable() { + public void onLoad() { speedBridge.load(); } + @Override + public void onEnable() { + speedBridge.enable(); + } + @Override public void onDisable() { // Plugin shutdown logic diff --git a/src/main/java/io/tofpu/speedbridge2/support/placeholderapi/expansion/expansions/LeaderboardExpansion.java b/src/main/java/io/tofpu/speedbridge2/support/placeholderapi/expansion/expansions/LeaderboardExpansion.java index ca5614e9..88ab68c6 100644 --- a/src/main/java/io/tofpu/speedbridge2/support/placeholderapi/expansion/expansions/LeaderboardExpansion.java +++ b/src/main/java/io/tofpu/speedbridge2/support/placeholderapi/expansion/expansions/LeaderboardExpansion.java @@ -35,11 +35,9 @@ public boolean passedRequirement(final BridgePlayer bridgePlayer, @Override public String runAction(final BridgePlayer bridgePlayer, final GamePlayer gamePlayer, final String[] args) { - final int position; - try { - position = Integer.parseInt(args[2]); - } catch (final NumberFormatException exception) { - return "invalid placeholder"; + final int position = parse(args, 2); + if (position == -1) { + return "Invalid Placeholder"; } final BoardPlayer boardPlayer; @@ -53,7 +51,11 @@ public String runAction(final BridgePlayer bridgePlayer, return BridgeUtil.translateMiniMessageLegacy(Message.INSTANCE.emptySessionLeaderboard); } } else { - final Island island = IslandService.INSTANCE.findIslandBy(Integer.parseInt(args[1])); + final int slot = parse(args, 1); + if (slot == -1) { + return "Invalid Placeholder"; + } + final Island island = IslandService.INSTANCE.findIslandBy(slot); if (island == null) { return ""; @@ -77,4 +79,12 @@ public String runAction(final BridgePlayer bridgePlayer, .replace("%name%", boardPlayer.getName()) .replace("%score%", BridgeUtil.formatNumber(bestScore.getScore()))); } + + public int parse(final String[] args, final int index) { + try { + return Integer.parseInt(args[index]); + } catch (final NumberFormatException exception) { + return -1; + } + } }