From 86efd781317f3c0f824aed9f9b406484924d1d8a Mon Sep 17 00:00:00 2001 From: Elecast Date: Sat, 14 Dec 2024 17:49:16 -0300 Subject: [PATCH] Use own ticker, prevents blocking bukkit threads --- .../xxmicloxx/NoteBlockAPI/NoteBlockAPI.java | 8 +- .../NoteBlockAPI/songplayer/SongPlayer.java | 293 +++++++++--------- .../NoteBlockAPI/songplayer/SongTicker.java | 42 +++ .../utils/NamedThreadFactory.java | 20 ++ 4 files changed, 210 insertions(+), 153 deletions(-) create mode 100644 src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongTicker.java create mode 100644 src/main/java/com/xxmicloxx/NoteBlockAPI/utils/NamedThreadFactory.java diff --git a/src/main/java/com/xxmicloxx/NoteBlockAPI/NoteBlockAPI.java b/src/main/java/com/xxmicloxx/NoteBlockAPI/NoteBlockAPI.java index abc593c..e9c693c 100644 --- a/src/main/java/com/xxmicloxx/NoteBlockAPI/NoteBlockAPI.java +++ b/src/main/java/com/xxmicloxx/NoteBlockAPI/NoteBlockAPI.java @@ -1,6 +1,7 @@ package com.xxmicloxx.NoteBlockAPI; import com.xxmicloxx.NoteBlockAPI.songplayer.SongPlayer; +import com.xxmicloxx.NoteBlockAPI.songplayer.SongTicker; import com.xxmicloxx.NoteBlockAPI.utils.MathUtils; import com.xxmicloxx.NoteBlockAPI.utils.Updater; import org.bstats.bukkit.Metrics; @@ -135,7 +136,7 @@ public static void setSongPlayersByPlayer(UUID player, ArrayList son @Override public void onEnable() { plugin = this; - + for (Plugin pl : getServer().getPluginManager().getPlugins()){ if (pl.getDescription().getDepend().contains("NoteBlockAPI") || pl.getDescription().getSoftDepend().contains("NoteBlockAPI")){ dependentPlugins.put(pl, false); @@ -212,16 +213,13 @@ public void onDisable() { worker.getThread().interrupt(); } NoteBlockPlayerMain.plugin.onDisable(); + SongTicker.shutdown(); } public void doSync(Runnable runnable) { getServer().getScheduler().runTask(this, runnable); } - public void doAsync(Runnable runnable) { - getServer().getScheduler().runTaskAsynchronously(this, runnable); - } - public boolean isDisabling() { return disabling; } diff --git a/src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongPlayer.java b/src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongPlayer.java index 8f4424e..8d1c214 100644 --- a/src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongPlayer.java +++ b/src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongPlayer.java @@ -309,172 +309,168 @@ public void setEnable10Octave(boolean enable10Octave) { * Starts this SongPlayer */ private void start() { - plugin.doAsync(() -> { - while (!destroyed) { - long startTime = System.currentTimeMillis(); - lock.lock(); - try { - if (destroyed || NoteBlockAPI.getAPI().isDisabling()){ - break; + SongTicker.register(this); + } + + private long lastTickTime = System.currentTimeMillis(); + + public void updateTick() { + lock.lock(); + try { + if (destroyed || NoteBlockAPI.getAPI().isDisabling()){ + return; + } + + long delayMillis = (long) (song.getDelay() * 50); + + long currentTime = System.currentTimeMillis(); + if (currentTime - lastTickTime < delayMillis) { + return; + } + + lastTickTime = currentTime; + + if (playing || fading) { + if (fadeTemp != null){ + if (fadeTemp.isDone()) { + fadeTemp = null; + fading = false; + if (!playing) { + SongStoppedEvent event = new SongStoppedEvent(this); + plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); + volume = fadeIn.getFadeTarget(); + return; + } + }else { + int fade = fadeTemp.calculateFade(); + if (fade != -1){ + volume = (byte) fade; + } + } + } else if (tick < fadeIn.getFadeDuration()){ + int fade = fadeIn.calculateFade(); + if (fade != -1){ + volume = (byte) fade; } + CallUpdate("fadeDone", fadeIn.getFadeDone()); + } else if (tick >= song.getLength() - fadeOut.getFadeDuration()){ + int fade = fadeOut.calculateFade(); + if (fade != -1){ + volume = (byte) fade; + } + } - if (playing || fading) { - if (fadeTemp != null){ - if (fadeTemp.isDone()) { - fadeTemp = null; - fading = false; - if (!playing) { - SongStoppedEvent event = new SongStoppedEvent(this); - plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - volume = fadeIn.getFadeTarget(); - continue; - } - }else { - int fade = fadeTemp.calculateFade(); - if (fade != -1){ - volume = (byte) fade; + tick++; + if (tick > song.getLength()) { + tick = -1; + fadeIn.setFadeDone(0); + CallUpdate("fadeDone", fadeIn.getFadeDone()); + fadeOut.setFadeDone(0); + volume = fadeIn.getFadeTarget(); + if (repeat == RepeatMode.ONE){ + SongLoopEvent event = new SongLoopEvent(this); + plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); + + if (!event.isCancelled()) { + return; + } + } else { + if (random) { + songQueue.put(song, true); + checkPlaylistQueue(); + ArrayList left = new ArrayList<>(); + for (Song s : songQueue.keySet()) { + if (!songQueue.get(s)) { + left.add(s); } } - } else if (tick < fadeIn.getFadeDuration()){ - int fade = fadeIn.calculateFade(); - if (fade != -1){ - volume = (byte) fade; - } - CallUpdate("fadeDone", fadeIn.getFadeDone()); - } else if (tick >= song.getLength() - fadeOut.getFadeDuration()){ - int fade = fadeOut.calculateFade(); - if (fade != -1){ - volume = (byte) fade; - } - } - - tick++; - if (tick > song.getLength()) { - tick = -1; - fadeIn.setFadeDone(0); - CallUpdate("fadeDone", fadeIn.getFadeDone()); - fadeOut.setFadeDone(0); - volume = fadeIn.getFadeTarget(); - if (repeat == RepeatMode.ONE){ - SongLoopEvent event = new SongLoopEvent(this); - plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - if (!event.isCancelled()) { - continue; + if (left.size() == 0) { + left.addAll(songQueue.keySet()); + for (Song s : songQueue.keySet()) { + songQueue.put(s, false); } - } else { - if (random) { - songQueue.put(song, true); - checkPlaylistQueue(); - ArrayList left = new ArrayList<>(); - for (Song s : songQueue.keySet()) { - if (!songQueue.get(s)) { - left.add(s); - } - } + song = left.get(rng.nextInt(left.size())); + actualSong = playlist.getIndex(song); + CallUpdate("song", song); + if (repeat == RepeatMode.ALL) { + SongLoopEvent event = new SongLoopEvent(this); + plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - if (left.size() == 0) { - left.addAll(songQueue.keySet()); - for (Song s : songQueue.keySet()) { - songQueue.put(s, false); - } - song = left.get(rng.nextInt(left.size())); - actualSong = playlist.getIndex(song); - CallUpdate("song", song); - if (repeat == RepeatMode.ALL) { - SongLoopEvent event = new SongLoopEvent(this); - plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - - if (!event.isCancelled()) { - continue; - } - } - } else { - song = left.get(rng.nextInt(left.size())); - actualSong = playlist.getIndex(song); - - CallUpdate("song", song); - SongNextEvent event = new SongNextEvent(this); - plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - continue; - } - } else { - if (playlist.hasNext(actualSong)) { - actualSong++; - song = playlist.get(actualSong); - CallUpdate("song", song); - SongNextEvent event = new SongNextEvent(this); - plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - continue; - } else { - actualSong = 0; - song = playlist.get(actualSong); - CallUpdate("song", song); - if (repeat == RepeatMode.ALL) { - SongLoopEvent event = new SongLoopEvent(this); - plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - - if (!event.isCancelled()) { - continue; - } - } + if (!event.isCancelled()) { + return; } } + } else { + song = left.get(rng.nextInt(left.size())); + actualSong = playlist.getIndex(song); + + CallUpdate("song", song); + SongNextEvent event = new SongNextEvent(this); + plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); + return; } - playing = false; - SongEndEvent event = new SongEndEvent(this); - plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - if (autoDestroy) { - destroy(); - } - continue; - } - CallUpdate("tick", tick); - - plugin.doSync(() -> { - try { - for (UUID uuid : playerList.keySet()) { - Player player = Bukkit.getPlayer(uuid); - if (player == null) { - // offline... - continue; + } else { + if (playlist.hasNext(actualSong)) { + actualSong++; + song = playlist.get(actualSong); + CallUpdate("song", song); + SongNextEvent event = new SongNextEvent(this); + plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); + return; + } else { + actualSong = 0; + song = playlist.get(actualSong); + CallUpdate("song", song); + if (repeat == RepeatMode.ALL) { + SongLoopEvent event = new SongLoopEvent(this); + plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); + + if (!event.isCancelled()) { + return; } - playTick(player, tick); } - } catch (Exception e){ - Bukkit.getLogger().severe("An error occurred during the playback of song " - + (song != null ? - song.getPath() + " (" + song.getAuthor() + " - " + song.getTitle() + ")" - : "null")); - e.printStackTrace(); } - }); + } } - } catch (Exception e) { - Bukkit.getLogger().severe("An error occurred during the playback of song " - + (song != null ? - song.getPath() + " (" + song.getAuthor() + " - " + song.getTitle() + ")" - : "null")); - e.printStackTrace(); - } finally { - lock.unlock(); - } - - if (destroyed) { - break; + playing = false; + SongEndEvent event = new SongEndEvent(this); + plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); + if (autoDestroy) { + destroy(); + } + return; } + CallUpdate("tick", tick); - long duration = System.currentTimeMillis() - startTime; - float delayMillis = song.getDelay() * 50; - if (duration < delayMillis) { + plugin.doSync(() -> { try { - Thread.sleep((long) (delayMillis - duration)); - } catch (InterruptedException e) { - // do nothing + for (UUID uuid : playerList.keySet()) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) { + // offline... + continue; + } + playTick(player, tick); + } + } catch (Exception e){ + Bukkit.getLogger().severe("An error occurred during the playback of song " + + (song != null ? + song.getPath() + " (" + song.getAuthor() + " - " + song.getTitle() + ")" + : "null")); + e.printStackTrace(); } - } + }); } - }); + } catch (Exception e) { + Bukkit.getLogger().severe("An error occurred during the playback of song " + + (song != null ? + song.getPath() + " (" + song.getAuthor() + " - " + song.getTitle() + ")" + : "null")); + e.printStackTrace(); + } finally { + lock.unlock(); + } } private void checkPlaylistQueue(){ @@ -600,13 +596,13 @@ public void destroy() { try { SongDestroyingEvent event = new SongDestroyingEvent(this); plugin.doSync(() -> Bukkit.getPluginManager().callEvent(event)); - //Bukkit.getScheduler().cancelTask(threadId); if (event.isCancelled()) { return; } destroyed = true; playing = false; setTick((short) -1); + SongTicker.unregister(this); CallUpdate("destroyed", destroyed); CallUpdate("playing", playing); } finally { @@ -614,6 +610,7 @@ public void destroy() { } } + /** * Returns whether the SongPlayer is actively playing * @return is playing diff --git a/src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongTicker.java b/src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongTicker.java new file mode 100644 index 0000000..2f385a2 --- /dev/null +++ b/src/main/java/com/xxmicloxx/NoteBlockAPI/songplayer/SongTicker.java @@ -0,0 +1,42 @@ +package com.xxmicloxx.NoteBlockAPI.songplayer; + +import com.xxmicloxx.NoteBlockAPI.utils.NamedThreadFactory; +import org.bukkit.Bukkit; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class SongTicker { + private static final ScheduledExecutorService ticker = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("NoteBlockAPI")); + private static final Set activeSongPlayers = ConcurrentHashMap.newKeySet(); + + static { + ticker.scheduleAtFixedRate(SongTicker::tick, 0, 10, TimeUnit.MILLISECONDS); + } + + private static void tick() { + for (SongPlayer player : activeSongPlayers) { + try { + player.updateTick(); + } catch (Exception e) { + Bukkit.getLogger().severe("Error updating song player: " + player); + e.printStackTrace(); + } + } + } + + public static void register(SongPlayer songPlayer) { + activeSongPlayers.add(songPlayer); + } + + public static void unregister(SongPlayer songPlayer) { + activeSongPlayers.remove(songPlayer); + } + + public static void shutdown() { + ticker.shutdown(); + } +} diff --git a/src/main/java/com/xxmicloxx/NoteBlockAPI/utils/NamedThreadFactory.java b/src/main/java/com/xxmicloxx/NoteBlockAPI/utils/NamedThreadFactory.java new file mode 100644 index 0000000..fc4a0c6 --- /dev/null +++ b/src/main/java/com/xxmicloxx/NoteBlockAPI/utils/NamedThreadFactory.java @@ -0,0 +1,20 @@ +package com.xxmicloxx.NoteBlockAPI.utils; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class NamedThreadFactory implements ThreadFactory { + private final String baseName; + private final AtomicInteger threadCount = new AtomicInteger(1); + + public NamedThreadFactory(String baseName) { + this.baseName = baseName; + } + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName(baseName + "-Thread-" + threadCount.getAndIncrement()); + return thread; + } +}