@@ -44,8 +44,16 @@ public final class SchedulerUtil {
4444 private static final ThreadPoolManager THREAD_POOL = new ThreadPoolManager ();
4545 private static final AtomicInteger activeAsyncTasks = new AtomicInteger (0 );
4646 private static final int MAX_CONCURRENT_ASYNC_TASKS = Math .max (4 , AVAILABLE_PROCESSORS );
47+ private static final ConcurrentHashMap <String , BukkitTask > SYNC_DEBOUNCE = new ConcurrentHashMap <>();
48+ private static final ConcurrentHashMap <String , java .util .concurrent .ScheduledFuture <?>> ASYNC_DEBOUNCE = new ConcurrentHashMap <>();
49+ private static final java .util .concurrent .ScheduledThreadPoolExecutor SCHEDULED_EXECUTOR = new java .util .concurrent .ScheduledThreadPoolExecutor (1 , r -> {
50+ Thread t = new Thread (r , "MasterCombat-Scheduler" );
51+ t .setDaemon (true );
52+ return t ;
53+ });
4754
4855 static {
56+ SCHEDULED_EXECUTOR .setRemoveOnCancelPolicy (true );
4957 scheduleCleanupTask ();
5058 }
5159
@@ -138,6 +146,11 @@ public static void setShuttingDown(boolean shuttingDown) {
138146 isShuttingDown .set (shuttingDown );
139147 if (shuttingDown ) {
140148 THREAD_POOL .shutdown ();
149+ SCHEDULED_EXECUTOR .shutdownNow ();
150+ SYNC_DEBOUNCE .values ().forEach (task -> { try { if (task != null && !task .isCancelled ()) task .cancel (); } catch (Exception ignored ) {} });
151+ SYNC_DEBOUNCE .clear ();
152+ ASYNC_DEBOUNCE .values ().forEach (f -> { try { if (f != null ) f .cancel (false ); } catch (Exception ignored ) {} });
153+ ASYNC_DEBOUNCE .clear ();
141154 }
142155 }
143156
@@ -152,6 +165,41 @@ public static void cancelAllTasks(Plugin plugin) {
152165 });
153166 }
154167
168+ public static void debounceSync (Plugin plugin , String key , Runnable task , long delayTicks ) {
169+ if (shouldSkip (plugin )) return ;
170+ BukkitTask existing = SYNC_DEBOUNCE .remove (key );
171+ if (existing != null ) {
172+ try { existing .cancel (); } catch (Exception ignored ) {}
173+ }
174+ Runnable wrapped = wrapTask (() -> {
175+ try { task .run (); } finally { SYNC_DEBOUNCE .remove (key ); }
176+ }, plugin , "debounce-sync" );
177+ BukkitTask newTask ;
178+ if (IS_ARCLIGHT ) {
179+ newTask = Bukkit .getScheduler ().runTaskLater (plugin , wrapArclightTask (wrapped ), delayTicks );
180+ } else {
181+ newTask = Bukkit .getScheduler ().runTaskLater (plugin , wrapped , delayTicks );
182+ }
183+ SYNC_DEBOUNCE .put (key , newTask );
184+ }
185+
186+ public static void debounceAsync (Plugin plugin , String key , Runnable task , long delayTicks ) {
187+ if (shouldSkip (plugin )) return ;
188+ java .util .concurrent .ScheduledFuture <?> prev = ASYNC_DEBOUNCE .remove (key );
189+ if (prev != null ) {
190+ try { prev .cancel (false ); } catch (Exception ignored ) {}
191+ }
192+ long delayMs = Math .max (0L , delayTicks * 50L );
193+ java .util .concurrent .ScheduledFuture <?> future = SCHEDULED_EXECUTOR .schedule (() -> {
194+ try {
195+ runTaskAsync (plugin , task );
196+ } finally {
197+ ASYNC_DEBOUNCE .remove (key );
198+ }
199+ }, delayMs , TimeUnit .MILLISECONDS );
200+ ASYNC_DEBOUNCE .put (key , future );
201+ }
202+
155203 private static boolean shouldSkip (Plugin plugin ) {
156204 return isShuttingDown .get () || plugin == null || !plugin .isEnabled ();
157205 }
@@ -257,11 +305,9 @@ public static BukkitTask runTask(Plugin plugin, Runnable task) {
257305 return null ;
258306 } else if (IS_ARCLIGHT ) {
259307 BukkitTask bukkitTask = Bukkit .getScheduler ().runTask (plugin , wrapArclightTask (wrapped ));
260- trackTask (bukkitTask );
261308 return bukkitTask ;
262309 } else {
263310 BukkitTask bukkitTask = Bukkit .getScheduler ().runTask (plugin , wrapped );
264- trackTask (bukkitTask );
265311 return bukkitTask ;
266312 }
267313 }
@@ -288,11 +334,9 @@ public static BukkitTask runTaskAsync(Plugin plugin, Runnable task) {
288334 return null ;
289335 } else if (IS_ARCLIGHT ) {
290336 BukkitTask bukkitTask = Bukkit .getScheduler ().runTaskAsynchronously (plugin , wrapArclightAsyncTask (wrapped ));
291- trackTask (bukkitTask );
292337 return bukkitTask ;
293338 } else {
294339 BukkitTask bukkitTask = Bukkit .getScheduler ().runTaskAsynchronously (plugin , wrapped );
295- trackTask (bukkitTask );
296340 return bukkitTask ;
297341 }
298342 }
@@ -307,11 +351,9 @@ public static BukkitTask runTaskLater(Plugin plugin, Runnable task, long delay)
307351 return null ;
308352 } else if (IS_ARCLIGHT ) {
309353 BukkitTask bukkitTask = Bukkit .getScheduler ().runTaskLater (plugin , wrapArclightTask (wrapped ), delay );
310- trackTask (bukkitTask );
311354 return bukkitTask ;
312355 } else {
313356 BukkitTask bukkitTask = Bukkit .getScheduler ().runTaskLater (plugin , wrapped , delay );
314- trackTask (bukkitTask );
315357 return bukkitTask ;
316358 }
317359 }
@@ -349,11 +391,9 @@ public static BukkitTask runTaskLaterAsync(Plugin plugin, Runnable task, long de
349391 return null ;
350392 } else if (IS_ARCLIGHT ) {
351393 BukkitTask bukkitTask = Bukkit .getScheduler ().runTaskLaterAsynchronously (plugin , wrapArclightAsyncTask (wrapped ), delay );
352- trackTask (bukkitTask );
353394 return bukkitTask ;
354395 } else {
355396 BukkitTask bukkitTask = Bukkit .getScheduler ().runTaskLaterAsynchronously (plugin , wrapped , delay );
356- trackTask (bukkitTask );
357397 return bukkitTask ;
358398 }
359399 }
0 commit comments