Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.bencodez.advancedcore.bukkit.runtime;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;

import com.bencodez.advancedcore.AdvancedCorePlugin;
import com.bencodez.advancedcore.api.item.FullInventoryHandler;
import com.bencodez.advancedcore.api.javascript.JavascriptEngineHandler;
import com.bencodez.advancedcore.api.time.TimeChecker;
import com.bencodez.advancedcore.api.user.UserStorage;
import com.bencodez.advancedcore.core.platform.RuntimePlatform;

/** Bukkit services for the shared executor lifecycle; no duplicate owners are created. */
public final class BukkitRuntimePlatform implements RuntimePlatform {
private final AdvancedCorePlugin plugin;

public BukkitRuntimePlatform(AdvancedCorePlugin plugin) {
this.plugin = Objects.requireNonNull(plugin, "plugin");
}

@Override public ScheduledExecutorService getTimer() { return plugin.getTimer(); }
@Override public ScheduledExecutorService getLoginTimer() { return plugin.getLoginTimer(); }
@Override public ScheduledExecutorService getInventoryTimer() { return plugin.getInventoryTimer(); }
@Override public ScheduledExecutorService getTimeTimer() {
TimeChecker checker = plugin.getTimeChecker();
return checker == null ? null : checker.getTimer();
}

@Override public List<Cleanup> beforeExecutorShutdown() {
return List.of(
new Cleanup("Javascript engine", () -> {
if (plugin.getOptions() != null && plugin.getOptions().isJavascriptEngineEnabled()) {
plugin.getLogger().info("Shutting down Javascript engine");
JavascriptEngineHandler.getInstance().clearCachedEngine();
}
}),
new Cleanup("MySQL", () -> {
if (plugin.isLoadUserData() && plugin.getOptions() != null
&& UserStorage.MYSQL.equals(plugin.getOptions().getStorageType()) && plugin.getMysql() != null) {
plugin.getMysql().close();
}
}),
new Cleanup("server data timestamp", () -> {
if (plugin.getServerDataFile() != null) plugin.getServerDataFile().setLastUpdated();
}));
}

@Override public List<Cleanup> afterExecutorGrace() {
return List.of(new Cleanup("reward handler", () -> {
if (plugin.getRewardHandler() != null) plugin.getRewardHandler().shutdown();
}));
}

@Override public List<Cleanup> afterExecutorShutdown() {
return List.of(
new Cleanup("plugin unload hook", plugin::onUnLoad),
new Cleanup("skull cache", () -> {
if (plugin.getSkullCacheHandler() != null) plugin.getSkullCacheHandler().close();
}),
new Cleanup("full inventory handler", () -> {
FullInventoryHandler handler = plugin.getFullInventoryHandler();
if (handler != null) {
handler.shutdown();
handler.save();
}
}),
new Cleanup("hologram handler", () -> {
if (plugin.getHologramHandler() != null) plugin.getHologramHandler().onShutDown();
}),
new Cleanup("permission handler", () -> {
if (plugin.getPermissionHandler() != null) plugin.getPermissionHandler().shutDown();
}),
new Cleanup("dialog service", () -> {
if (plugin.getDialogService() != null) plugin.getDialogService().unregister();
}));
}

@Override public void info(String message) { plugin.getLogger().info(message); }
@Override public void cleanupFailed(String component, Throwable failure) {
plugin.getLogger().warning("Failed to shut down " + component + ": " + failure.getMessage());
plugin.debug(failure);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.bencodez.advancedcore.core.platform;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;

/** Supplies existing executor owners and platform cleanup without exposing game APIs. */
public interface RuntimePlatform {
record Cleanup(String name, Runnable action) {
public Cleanup {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(action, "action");
}
}

ScheduledExecutorService getTimer();
ScheduledExecutorService getLoginTimer();
ScheduledExecutorService getInventoryTimer();
ScheduledExecutorService getTimeTimer();
List<Cleanup> beforeExecutorShutdown();
List<Cleanup> afterExecutorGrace();
List<Cleanup> afterExecutorShutdown();
void info(String message);
void cleanupFailed(String component, Throwable failure);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.bencodez.advancedcore.core.runtime;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.bencodez.advancedcore.core.platform.RuntimePlatform;
import com.bencodez.advancedcore.core.platform.RuntimePlatform.Cleanup;

/**
* First shared runtime slice: executor creation and existing shutdown sequencing.
* It creates no second cache/service registry, and does not replace an entity or
* region scheduler with a global executor. The platform serializes lifecycle
* calls; cleanup hooks are synchronous and retain their existing failure policy.
*/
public final class AdvancedCoreRuntime {
private final RuntimePlatform platform;

public AdvancedCoreRuntime(RuntimePlatform platform) {
this.platform = Objects.requireNonNull(platform, "platform");
}

/** The same three independent executor owners used by the Bukkit plugin. */
public record ExecutorGroup(ScheduledExecutorService timer, ScheduledExecutorService loginTimer,
ScheduledExecutorService inventoryTimer) { }

public static ExecutorGroup createExecutors() {
ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
ScheduledExecutorService loginTimer = null;
try {
loginTimer = Executors.newSingleThreadScheduledExecutor();
return new ExecutorGroup(timer, loginTimer, Executors.newSingleThreadScheduledExecutor());
} catch (RuntimeException | Error failure) {
timer.shutdownNow();
if (loginTimer != null) loginTimer.shutdownNow();
throw failure;
}
}

public void shutdown() {
clean(platform.beforeExecutorShutdown());

// Resolve the time-checker timer once, after the pre-shutdown actions,
// just as the old lifecycle did. Other getters retain their lookup order.
ScheduledExecutorService timeTimer = platform.getTimeTimer();
shutdown(platform.getLoginTimer());
shutdown(platform.getTimer());
shutdown(timeTimer);
shutdown(platform.getInventoryTimer());

platform.info("Allowing background tasks to finish before shutdown");
await(platform.getLoginTimer(), 2, TimeUnit.SECONDS);
await(platform.getTimer(), 2, TimeUnit.SECONDS);
await(timeTimer, 2, TimeUnit.SECONDS);
await(platform.getInventoryTimer(), 1, TimeUnit.SECONDS);

clean(platform.afterExecutorGrace());
shutdownNow(platform.getLoginTimer());
shutdownNow(platform.getTimer());
shutdownNow(timeTimer);
shutdownNow(platform.getInventoryTimer());
clean(platform.afterExecutorShutdown());
}

private void clean(List<Cleanup> actions) {
for (Cleanup action : actions) {
try {
action.action().run();
} catch (Throwable failure) {
platform.cleanupFailed(action.name(), failure);
}
}
}

public static void shutdown(ScheduledExecutorService executor) {
if (executor != null && !executor.isShutdown()) executor.shutdown();
}
public static void shutdownNow(ScheduledExecutorService executor) {
if (executor != null && !executor.isTerminated()) executor.shutdownNow();
}
public static void await(ScheduledExecutorService executor, long timeout, TimeUnit unit) {
if (executor == null) return;
try {
executor.awaitTermination(timeout, unit);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
}
}
}
Loading
Loading