diff --git a/api/src/main/java/dev/plex/api/scheduler/SchedulerApi.java b/api/src/main/java/dev/plex/api/scheduler/SchedulerApi.java index c20d007..b8d1c01 100644 --- a/api/src/main/java/dev/plex/api/scheduler/SchedulerApi.java +++ b/api/src/main/java/dev/plex/api/scheduler/SchedulerApi.java @@ -13,10 +13,26 @@ import org.jetbrains.annotations.Nullable; public interface SchedulerApi { + /** + * Executor backed by Paper's async scheduler. + * Use it for blocking I/O and CPU work that does not touch Bukkit world, + * entity, inventory, or command state. + */ Executor asyncExecutor(); + /** + * Executes work on the global region. + * Use the global region for state owned by Folia's global region, such as + * console command dispatch, world time, weather, game rules, and plugin-level + * coordination work. + */ void executeGlobal(Runnable task); + /** + * Runs work on the next global-region tick. + * + * @see #executeGlobal(Runnable) + */ ScheduledTask runGlobal(Consumer task); default ScheduledTask runGlobal(Runnable task) @@ -24,6 +40,11 @@ public interface SchedulerApi return runGlobal(scheduledTask -> task.run()); } + /** + * Runs work on the global region after a tick delay. + * + * @see #executeGlobal(Runnable) + */ ScheduledTask runGlobalLater(Consumer task, long delayTicks); default ScheduledTask runGlobalLater(Runnable task, long delayTicks) @@ -31,6 +52,11 @@ public interface SchedulerApi return runGlobalLater(scheduledTask -> task.run(), delayTicks); } + /** + * Runs repeating work on the global region. + * + * @see #executeGlobal(Runnable) + */ ScheduledTask runGlobalTimer(Consumer task, long delayTicks, long periodTicks); default ScheduledTask runGlobalTimer(Runnable task, long delayTicks, long periodTicks) @@ -38,6 +64,10 @@ public interface SchedulerApi return runGlobalTimer(scheduledTask -> task.run(), delayTicks, periodTicks); } + /** + * Runs work on Paper's async scheduler. + * Do not touch Bukkit world, entity, inventory, or command state here. + */ ScheduledTask runAsync(Consumer task); default ScheduledTask runAsync(Runnable task) @@ -45,6 +75,11 @@ public interface SchedulerApi return runAsync(scheduledTask -> task.run()); } + /** + * Runs async work after a wall-clock delay. + * + * @see #runAsync(Consumer) + */ ScheduledTask runAsyncLater(Consumer task, long delay, TimeUnit unit); default ScheduledTask runAsyncLater(Runnable task, long delay, TimeUnit unit) @@ -52,6 +87,11 @@ public interface SchedulerApi return runAsyncLater(scheduledTask -> task.run(), delay, unit); } + /** + * Runs repeating async work on a wall-clock interval. + * + * @see #runAsync(Consumer) + */ ScheduledTask runAsyncTimer(Consumer task, long delay, long period, TimeUnit unit); default ScheduledTask runAsyncTimer(Runnable task, long delay, long period, TimeUnit unit) @@ -59,10 +99,23 @@ public interface SchedulerApi return runAsyncTimer(scheduledTask -> task.run(), delay, period, unit); } + /** + * Executes work on the region that owns the supplied location. + * Use this for block, chunk, and location-bound world access. + */ void executeRegion(Location location, Runnable task); + /** + * Executes work on the region that owns the supplied chunk. + * Use this for block, chunk, and location-bound world access. + */ void executeRegion(World world, int chunkX, int chunkZ, Runnable task); + /** + * Runs work on the next tick of the region that owns the supplied location. + * + * @see #executeRegion(Location, Runnable) + */ ScheduledTask runRegion(Location location, Consumer task); default ScheduledTask runRegion(Location location, Runnable task) @@ -70,6 +123,11 @@ public interface SchedulerApi return runRegion(location, scheduledTask -> task.run()); } + /** + * Runs work on the next tick of the region that owns the supplied chunk. + * + * @see #executeRegion(World, int, int, Runnable) + */ ScheduledTask runRegion(World world, int chunkX, int chunkZ, Consumer task); default ScheduledTask runRegion(World world, int chunkX, int chunkZ, Runnable task) @@ -77,6 +135,11 @@ public interface SchedulerApi return runRegion(world, chunkX, chunkZ, scheduledTask -> task.run()); } + /** + * Runs work on a location's owning region after a tick delay. + * + * @see #executeRegion(Location, Runnable) + */ ScheduledTask runRegionLater(Location location, Consumer task, long delayTicks); default ScheduledTask runRegionLater(Location location, Runnable task, long delayTicks) @@ -84,6 +147,11 @@ public interface SchedulerApi return runRegionLater(location, scheduledTask -> task.run(), delayTicks); } + /** + * Runs work on a chunk's owning region after a tick delay. + * + * @see #executeRegion(World, int, int, Runnable) + */ ScheduledTask runRegionLater(World world, int chunkX, int chunkZ, Consumer task, long delayTicks); default ScheduledTask runRegionLater(World world, int chunkX, int chunkZ, Runnable task, long delayTicks) @@ -91,6 +159,11 @@ public interface SchedulerApi return runRegionLater(world, chunkX, chunkZ, scheduledTask -> task.run(), delayTicks); } + /** + * Runs repeating work on a location's owning region. + * + * @see #executeRegion(Location, Runnable) + */ ScheduledTask runRegionTimer(Location location, Consumer task, long delayTicks, long periodTicks); default ScheduledTask runRegionTimer(Location location, Runnable task, long delayTicks, long periodTicks) @@ -98,6 +171,11 @@ public interface SchedulerApi return runRegionTimer(location, scheduledTask -> task.run(), delayTicks, periodTicks); } + /** + * Runs repeating work on a chunk's owning region. + * + * @see #executeRegion(World, int, int, Runnable) + */ ScheduledTask runRegionTimer(World world, int chunkX, int chunkZ, Consumer task, long delayTicks, long periodTicks); default ScheduledTask runRegionTimer(World world, int chunkX, int chunkZ, Runnable task, long delayTicks, long periodTicks) @@ -105,6 +183,15 @@ public interface SchedulerApi return runRegionTimer(world, chunkX, chunkZ, scheduledTask -> task.run(), delayTicks, periodTicks); } + /** + * Executes work on the region that currently owns the entity. + * Use this for player and entity state access, inventory changes, kicks, + * teleports, passengers, potion effects, and other entity-bound work. + * Paper runs the retired callback if the entity is removed before the task + * can execute. + * + * @return true if Paper accepted the task + */ boolean executeEntity(Entity entity, Runnable task, @Nullable Runnable retired, long delayTicks); default boolean executeEntity(Entity entity, Runnable task, long delayTicks) @@ -112,6 +199,11 @@ public interface SchedulerApi return executeEntity(entity, task, null, delayTicks); } + /** + * Runs work on the next tick of the entity's owning region. + * + * @see #executeEntity(Entity, Runnable, Runnable, long) + */ @Nullable ScheduledTask runEntity(Entity entity, Consumer task, @Nullable Runnable retired); @@ -120,6 +212,11 @@ public interface SchedulerApi return runEntity(entity, scheduledTask -> task.run(), null); } + /** + * Runs work on the entity's owning region after a tick delay. + * + * @see #executeEntity(Entity, Runnable, Runnable, long) + */ @Nullable ScheduledTask runEntityLater(Entity entity, Consumer task, @Nullable Runnable retired, long delayTicks); @@ -128,6 +225,11 @@ public interface SchedulerApi return runEntityLater(entity, scheduledTask -> task.run(), null, delayTicks); } + /** + * Runs repeating work on the entity's owning region. + * + * @see #executeEntity(Entity, Runnable, Runnable, long) + */ @Nullable ScheduledTask runEntityTimer(Entity entity, Consumer task, @Nullable Runnable retired, long delayTicks, long periodTicks); @@ -136,7 +238,13 @@ public interface SchedulerApi return runEntityTimer(entity, scheduledTask -> task.run(), null, delayTicks, periodTicks); } + /** + * Cancels all global-region tasks owned by Plex. + */ void cancelGlobalTasks(); + /** + * Cancels all async tasks owned by Plex. + */ void cancelAsyncTasks(); } diff --git a/server/src/main/java/dev/plex/PlexLibraryManager.java b/server/src/main/java/dev/plex/PlexLibraryManager.java index 482d4c3..4e56195 100644 --- a/server/src/main/java/dev/plex/PlexLibraryManager.java +++ b/server/src/main/java/dev/plex/PlexLibraryManager.java @@ -25,23 +25,33 @@ import org.jetbrains.annotations.NotNull; @SuppressWarnings("UnstableApiUsage") public class PlexLibraryManager implements PluginLoader { + private static final List MAVEN_CENTRAL_URLS = List.of( + "https://repo1.maven.org/maven2", + "http://repo1.maven.org/maven2", + "https://repo.maven.apache.org/maven2", + "http://repo.maven.apache.org/maven2" + ); + @Override public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) { - MavenLibraryResolver resolver = new MavenLibraryResolver(); PluginLibraries pluginLibraries = load(); - var repositoryUrls = new HashSet(); - pluginLibraries.asRepositories().forEach(repository -> + List pluginRepositories = pluginLibraries.asRepositories().toList(); + List moduleRepositories = loadModuleRepositories().toList(); + List moduleDependencies = loadModuleDependencies().toList(); + + if (!moduleDependencies.isEmpty()) { - repositoryUrls.add(repository.getUrl()); - resolver.addRepository(repository); - }); - loadModuleRepositories() - .filter(repository -> repositoryUrls.add(repository.getUrl())) - .forEach(resolver::addRepository); - pluginLibraries.asDependencies().forEach(resolver::addDependency); - loadModuleDependencies().forEach(resolver::addDependency); - classpathBuilder.addLibrary(resolver); + MavenLibraryResolver moduleResolver = new MavenLibraryResolver(); + addRepositories(moduleResolver, Stream.concat(moduleRepositories.stream(), pluginRepositories.stream())); + moduleDependencies.forEach(moduleResolver::addDependency); + classpathBuilder.addLibrary(moduleResolver); + } + + MavenLibraryResolver pluginResolver = new MavenLibraryResolver(); + addRepositories(pluginResolver, pluginRepositories.stream()); + pluginLibraries.asDependencies().forEach(pluginResolver::addDependency); + classpathBuilder.addLibrary(pluginResolver); } public PluginLibraries load() @@ -115,7 +125,7 @@ public class PlexLibraryManager implements PluginLoader return repositories.getKeys(false).stream() .map(id -> Map.entry(id, repositories.getString(id, ""))) .filter(entry -> !entry.getValue().isBlank()) - .map(entry -> new RemoteRepository.Builder(entry.getKey(), "default", entry.getValue()).build()); + .flatMap(entry -> runtimeRepository(entry.getKey(), entry.getValue())); } private YamlConfiguration readModuleYml(File moduleFile) @@ -149,7 +159,29 @@ public class PlexLibraryManager implements PluginLoader public Stream asRepositories() { - return repositories.entrySet().stream().map(e -> new RemoteRepository.Builder(e.getKey(), "default", e.getValue()).build()); + return repositories.entrySet().stream().flatMap(e -> runtimeRepository(e.getKey(), e.getValue())); } } + + private static Stream runtimeRepository(String id, String url) + { + String runtimeUrl = MAVEN_CENTRAL_URLS.stream().anyMatch(url::startsWith) + ? MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR + : url; + + if (!runtimeUrl.startsWith("https://") && !runtimeUrl.startsWith("http://")) + { + return Stream.empty(); + } + + return Stream.of(new RemoteRepository.Builder(id, "default", runtimeUrl).build()); + } + + private static void addRepositories(MavenLibraryResolver resolver, Stream repositories) + { + var repositoryUrls = new HashSet(); + repositories + .filter(repository -> repositoryUrls.add(repository.getUrl())) + .forEach(resolver::addRepository); + } }