From fd01165ea8a59e0de6bdc7b016c5e1bc20058ee6 Mon Sep 17 00:00:00 2001 From: Silverwolfg11 Date: Sun, 27 Oct 2024 17:38:00 -0700 Subject: [PATCH] Add MapPlatformObservers API to handle platform reloads appropriately. --- .../maptowny/platform/MapPlatform.java | 21 +++ .../platform/MapPlatformObserver.java | 62 ++++++++ .../bluemap/BlueMapObserverHandler.java | 118 +++++++++++++++ .../platform/bluemap/BlueMapPlatform.java | 55 ++++--- .../dynmap/DynmapObserverHandler.java | 131 +++++++++++++++++ .../platform/dynmap/DynmapPlatform.java | 20 ++- .../pl3xmap/Pl3xMapObserverHandler.java | 139 ++++++++++++++++++ .../platform/pl3xmap/Pl3xMapPlatform.java | 19 +++ .../me/silverwolfg11/maptowny/MapTowny.java | 4 + .../maptowny/managers/TownyLayerManager.java | 63 ++++++-- .../platform/squaremap/SquareMapPlatform.java | 29 ++++ 11 files changed, 628 insertions(+), 33 deletions(-) create mode 100644 maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatformObserver.java create mode 100644 maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapObserverHandler.java create mode 100644 maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapObserverHandler.java create mode 100644 maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapObserverHandler.java diff --git a/maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatform.java b/maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatform.java index c7cd5ac..0abeba9 100644 --- a/maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatform.java +++ b/maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatform.java @@ -51,6 +51,7 @@ public interface MapPlatform { * * * @param callback Callback to execute + * @deprecated 3.0.0+ - Use {@link MapPlatformObserver} instead. */ default void onFirstInitialize(Runnable callback) { callback.run(); @@ -67,11 +68,29 @@ default void onFirstInitialize(Runnable callback) { * * * @param callback Callback to execute. + * @deprecated 3.0.0+ - Use {@link MapPlatformObserver} instead. */ default void onInitialize(Runnable callback) { callback.run(); } + /** + * Register an observer to listen to platform events. + * Registering an already registered observer has no effect + * and will report as a registration failure. + * + * @return if registration was successful. + * @since 3.0.0 + */ + boolean registerObserver(@NotNull MapPlatformObserver observer); + + /** + * Unregister an observer. + * @return if unregistering was successful. + * @since 3.0.0 + */ + boolean unregisterObserver(@NotNull MapPlatformObserver observer); + /** * Check if the map plugin renders the specific world. * @@ -134,6 +153,8 @@ default void onInitialize(Runnable callback) { * Indicate to the platform that it should switch to shutdown mode. * * The platform will still be able to process all operations during this period. + * @deprecated 3.0.0 - Platform cleanup does not need to be split into two phases. + * */ default void startShutdown() { } diff --git a/maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatformObserver.java b/maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatformObserver.java new file mode 100644 index 0000000..31fd853 --- /dev/null +++ b/maptowny-api/src/main/java/me/silverwolfg11/maptowny/platform/MapPlatformObserver.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 Silverwolfg11 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.silverwolfg11.maptowny.platform; + +/** + * Provides an observer interface for map-platform events. + * These functions may be called on any thread. + *
Note: Platforms may not implement all events. + * + * @since 3.0.0 + */ +public interface MapPlatformObserver { + /** + * Run initial logic for the observer. + *
+ * Called immediately if the map-platform is already enabled + * or delayed until the map-platform is enabled for the first time. + * Only runs once after the observer is registered (not re-run on platform re-enable). + */ + default void onObserverSetup() {} + + /** + * Called when the map-platform is enabling. + *

+ * If the map-platform is already enabled when the observer is registered, then this is not called. + * If {@link #onObserverSetup()} has not been called when the platform is enabling, + * then {@link #onObserverSetup()} will be called and this will not. + *

+ * At this stage, some platforms may not have started marker processing + * and may not have process queues, so to be safe avoid + * performing platform processing logic in this event. + */ + default void onPlatformEnabled() {} + + /** + * Called when the map-platform is disabling. + *
+ * At this stage, some platforms may have already stopped marker processing, + * so to be safe, avoid performing platform processing logic in this event. + */ + default void onPlatformDisabled() {} +} diff --git a/maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapObserverHandler.java b/maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapObserverHandler.java new file mode 100644 index 0000000..df40c3b --- /dev/null +++ b/maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapObserverHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 Silverwolfg11 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.silverwolfg11.maptowny.platform.bluemap; + +import de.bluecolored.bluemap.api.BlueMapAPI; +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +public class BlueMapObserverHandler { + private final CopyOnWriteArrayList observers = new CopyOnWriteArrayList<>(); + private final List unsetupObservers = new ArrayList<>(); + + private final Consumer enableConsumer; + private final Consumer disableConsumer; + + public BlueMapObserverHandler() { + enableConsumer = (BlueMapAPI) -> this.onBlueMapEnabled(); + disableConsumer = (BlueMapAPI) -> this.onBlueMapDisabled(); + + BlueMapAPI.onEnable(enableConsumer); + BlueMapAPI.onDisable(disableConsumer); + } + + public boolean registerObserver(MapPlatformObserver observer) { + if (observers.contains(observer)) { + return false; + } + + synchronized (unsetupObservers) { + if (unsetupObservers.contains(observer)) { + return false; + } + } + + setupObserver(observer); + return true; + } + + public boolean unregisterObserver(MapPlatformObserver observer) { + if (observers.remove(observer)) { + return true; + } + + synchronized (unsetupObservers) { + if (unsetupObservers.remove(observer)) { + return true; + } + } + + return false; + } + + public void disableObservers() { + BlueMapAPI.unregisterListener(enableConsumer); + BlueMapAPI.unregisterListener(disableConsumer); + } + + private void setupObserver(MapPlatformObserver observer) { + if (BlueMapAPI.getInstance().isPresent()) { + observer.onObserverSetup(); + observers.add(observer); + } + else { + synchronized (unsetupObservers) { + unsetupObservers.add(observer); + } + } + } + + private void onBlueMapEnabled() { + for (MapPlatformObserver observer : observers) { + observer.onPlatformEnabled(); + } + + List tmpObservers; + synchronized (unsetupObservers) { + tmpObservers = new ArrayList<>(unsetupObservers); + unsetupObservers.clear(); + } + + // Setup first-time observers + for (MapPlatformObserver observer : tmpObservers) { + observer.onObserverSetup(); + } + + observers.addAll(tmpObservers); + } + + private void onBlueMapDisabled() { + for (MapPlatformObserver observer : observers) { + observer.onPlatformDisabled(); + } + } +} diff --git a/maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapPlatform.java b/maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapPlatform.java index 7aa5140..0368b2a 100644 --- a/maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapPlatform.java +++ b/maptowny-bluemap/src/main/java/me/silverwolfg11/maptowny/platform/bluemap/BlueMapPlatform.java @@ -24,27 +24,24 @@ import de.bluecolored.bluemap.api.BlueMapAPI; import me.silverwolfg11.maptowny.platform.MapPlatform; +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; import me.silverwolfg11.maptowny.platform.MapWorld; import me.silverwolfg11.maptowny.platform.bluemap.objects.WorldIdentifier; -import me.silverwolfg11.maptowny.schedulers.MapTownyScheduler; -import me.silverwolfg11.maptowny.schedulers.MapTownySchedulerFactory; import org.bukkit.World; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.image.BufferedImage; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; public class BlueMapPlatform implements MapPlatform { - private final JavaPlugin plugin; private final BlueMapIconMapper iconMapper; + private final BlueMapObserverHandler observerHandler; public BlueMapPlatform(JavaPlugin plugin) { - this.plugin = plugin; this.iconMapper = new BlueMapIconMapper(plugin.getLogger()); + observerHandler = new BlueMapObserverHandler(); } @Override @@ -54,23 +51,38 @@ public BlueMapPlatform(JavaPlugin plugin) { @Override public void onFirstInitialize(Runnable callback) { - // Use the future to unregister the listener - CompletableFuture future = new CompletableFuture<>(); - Consumer apiConsumer = (api) -> { - callback.run(); - // Have to delay it, otherwise it will end up concurrently modifying - // the onEnablers list. - MapTownySchedulerFactory.create(plugin).scheduleTask(() -> future.complete(null)); - }; - future.thenRun(() -> BlueMapAPI.unregisterListener(apiConsumer)); - - BlueMapAPI.onEnable(apiConsumer); + observerHandler.registerObserver(new MapPlatformObserver() { + @Override + public void onObserverSetup() { + callback.run(); + } + }); } @Override public void onInitialize(final Runnable callback) { - // BlueMap API asynchronously initializes after the server starts ticking. - BlueMapAPI.onEnable(api -> callback.run()); + // Implement both setup and enabled to maintain old behavior + observerHandler.registerObserver(new MapPlatformObserver() { + @Override + public void onObserverSetup() { + callback.run(); + } + + @Override + public void onPlatformEnabled() { + callback.run(); + } + }); + } + + @Override + public boolean registerObserver(@NotNull MapPlatformObserver observer) { + return observerHandler.registerObserver(observer); + } + + @Override + public boolean unregisterObserver(@NotNull MapPlatformObserver observer) { + return observerHandler.unregisterObserver(observer); } @Override @@ -107,4 +119,9 @@ public boolean hasIcon(@NotNull String iconKey) { public boolean unregisterIcon(@NotNull String iconKey) { return iconMapper.unregisterIcon(iconKey); } + + @Override + public void shutdown() { + observerHandler.disableObservers(); + } } diff --git a/maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapObserverHandler.java b/maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapObserverHandler.java new file mode 100644 index 0000000..de9fbce --- /dev/null +++ b/maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapObserverHandler.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 Silverwolfg11 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.silverwolfg11.maptowny.platform.dynmap; + +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; +import org.dynmap.DynmapCommonAPI; +import org.dynmap.DynmapCommonAPIListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; + +public class DynmapObserverHandler { + private final CopyOnWriteArrayList observers = new CopyOnWriteArrayList<>(); + private final List unsetupObservers = new ArrayList<>(); + + private final DynmapCommonAPIListener dynmapListener; + private final AtomicReference dynmapApiRef = new AtomicReference<>(); + + public DynmapObserverHandler() { + dynmapListener = new DynmapCommonAPIListener() { + @Override + public void apiEnabled(DynmapCommonAPI dynmapCommonAPI) { + DynmapObserverHandler.this.onDynmapEnabled(dynmapCommonAPI); + } + + @Override + public void apiDisabled(DynmapCommonAPI api) { + DynmapObserverHandler.this.onDynmapDisabled(); + } + }; + + // Listener runs immediately if dynmap api is enabled. + DynmapCommonAPIListener.register(dynmapListener); + } + + public boolean registerObserver(MapPlatformObserver observer) { + if (observers.contains(observer)) { + return false; + } + + synchronized (unsetupObservers) { + if (unsetupObservers.contains(observer)) { + return false; + } + } + + setupObserver(observer); + return true; + } + + public boolean unregisterObserver(MapPlatformObserver observer) { + if (observers.remove(observer)) { + return true; + } + + synchronized (unsetupObservers) { + if (unsetupObservers.remove(observer)) { + return true; + } + } + + return false; + } + + public void disableObservers() { + DynmapCommonAPIListener.unregister(dynmapListener); + } + + private void setupObserver(MapPlatformObserver observer) { + if (dynmapApiRef.get() != null) { + observer.onObserverSetup(); + observers.add(observer); + } + else { + synchronized (unsetupObservers) { + unsetupObservers.add(observer); + } + } + } + + private void onDynmapEnabled(DynmapCommonAPI api) { + dynmapApiRef.set(api); + + for (MapPlatformObserver observer : observers) { + observer.onPlatformEnabled(); + } + + List tmpObservers; + synchronized (unsetupObservers) { + tmpObservers = new ArrayList<>(unsetupObservers); + unsetupObservers.clear(); + } + + // Setup first-time observers + for (MapPlatformObserver observer : tmpObservers) { + observer.onObserverSetup(); + } + + observers.addAll(tmpObservers); + } + + private void onDynmapDisabled() { + dynmapApiRef.set(null); + + for (MapPlatformObserver observer : observers) { + observer.onPlatformDisabled(); + } + } +} diff --git a/maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapPlatform.java b/maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapPlatform.java index 7352169..df5ae66 100644 --- a/maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapPlatform.java +++ b/maptowny-dynmap/src/main/java/me/silverwolfg11/maptowny/platform/dynmap/DynmapPlatform.java @@ -23,11 +23,11 @@ package me.silverwolfg11.maptowny.platform.dynmap; import me.silverwolfg11.maptowny.platform.MapPlatform; +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; import me.silverwolfg11.maptowny.platform.MapWorld; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; import org.dynmap.DynmapAPI; import org.dynmap.markers.MarkerIcon; import org.jetbrains.annotations.NotNull; @@ -41,17 +41,28 @@ public class DynmapPlatform implements MapPlatform { private final DynmapAPI dynmapAPI; + private final DynmapObserverHandler observerHandler; public DynmapPlatform() { dynmapAPI = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap"); + observerHandler = new DynmapObserverHandler(); } - @Override public @NotNull String getPlatformName() { return "dynmap"; } + @Override + public boolean registerObserver(@NotNull MapPlatformObserver observer) { + return observerHandler.registerObserver(observer); + } + + @Override + public boolean unregisterObserver(@NotNull MapPlatformObserver observer) { + return observerHandler.unregisterObserver(observer); + } + @Override public boolean isWorldEnabled(@NotNull World world) { // TODO How would this even be implemented for Dynmap? @@ -108,6 +119,11 @@ public boolean unregisterIcon(@NotNull String iconKey) { return false; } + @Override + public void shutdown() { + observerHandler.disableObservers(); + } + // Logger Accessibility static void logError(String errorMsg) { Plugin plugin = Bukkit.getPluginManager().getPlugin("MapTowny"); diff --git a/maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapObserverHandler.java b/maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapObserverHandler.java new file mode 100644 index 0000000..f2bf5d3 --- /dev/null +++ b/maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapObserverHandler.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024 Silverwolfg11 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.silverwolfg11.maptowny.platform.pl3xmap; + +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; +import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.event.EventHandler; +import net.pl3x.map.core.event.EventListener; +import net.pl3x.map.core.event.server.Pl3xMapDisabledEvent; +import net.pl3x.map.core.event.server.Pl3xMapEnabledEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Pl3xMapObserverHandler { + private final CopyOnWriteArrayList observers = new CopyOnWriteArrayList<>(); + private final List unsetupObservers = new ArrayList<>(); + + private final AtomicBoolean pluginEnabled = new AtomicBoolean(true); + + public Pl3xMapObserverHandler() { + Pl3xMap.api().getEventRegistry().register(new EventListener() { + @EventHandler + public void onPl3xMapEnabled(Pl3xMapEnabledEvent event) { + Pl3xMapObserverHandler.this.onPl3xMapEnabled(); + } + + @EventHandler + public void onPl3xMapDisabled(Pl3xMapDisabledEvent event) { + Pl3xMapObserverHandler.this.onPl3xMapDisabled(); + } + }); + } + + public boolean registerObserver(MapPlatformObserver observer) { + if (observers.contains(observer)) { + return false; + } + + synchronized (unsetupObservers) { + if (unsetupObservers.contains(observer)) { + return false; + } + } + + setupObserver(observer); + return true; + } + + public boolean unregisterObserver(MapPlatformObserver observer) { + if (observers.remove(observer)) { + return true; + } + + synchronized (unsetupObservers) { + if (unsetupObservers.remove(observer)) { + return true; + } + } + + return false; + } + + public void disableObservers() { + pluginEnabled.set(false); + observers.clear(); + synchronized (unsetupObservers) { + unsetupObservers.clear(); + } + } + + private void setupObserver(MapPlatformObserver observer) { + if (Pl3xMap.api().isEnabled()) { + observer.onObserverSetup(); + observers.add(observer); + } + else { + synchronized (unsetupObservers) { + unsetupObservers.add(observer); + } + } + } + + private void onPl3xMapEnabled() { + if (!pluginEnabled.get()) { + return; + } + + for (MapPlatformObserver observer : observers) { + observer.onPlatformEnabled(); + } + + List tmpObservers; + synchronized (unsetupObservers) { + tmpObservers = new ArrayList<>(unsetupObservers); + unsetupObservers.clear(); + } + + // Setup first-time observers + for (MapPlatformObserver observer : tmpObservers) { + observer.onObserverSetup(); + } + + observers.addAll(tmpObservers); + + } + + private void onPl3xMapDisabled() { + if (!pluginEnabled.get()) { + return; + } + + for (MapPlatformObserver observer : observers) { + observer.onPlatformDisabled(); + } + } +} diff --git a/maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapPlatform.java b/maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapPlatform.java index aa53d07..56d08c5 100644 --- a/maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapPlatform.java +++ b/maptowny-pl3xmap/src/main/java/me/silverwolfg11/maptowny/platform/pl3xmap/Pl3xMapPlatform.java @@ -23,6 +23,7 @@ package me.silverwolfg11.maptowny.platform.pl3xmap; import me.silverwolfg11.maptowny.platform.MapPlatform; +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; import me.silverwolfg11.maptowny.platform.MapWorld; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.image.IconImage; @@ -33,6 +34,19 @@ import java.awt.image.BufferedImage; public class Pl3xMapPlatform implements MapPlatform { + + private final Pl3xMapObserverHandler observerHandler = new Pl3xMapObserverHandler(); + + @Override + public boolean registerObserver(@NotNull MapPlatformObserver observer) { + return observerHandler.registerObserver(observer); + } + + @Override + public boolean unregisterObserver(@NotNull MapPlatformObserver observer) { + return observerHandler.unregisterObserver(observer); + } + @Override public @NotNull String getPlatformName() { return "Pl3xMap"; @@ -75,4 +89,9 @@ public boolean hasIcon(@NotNull String iconKey) { public boolean unregisterIcon(@NotNull String iconKey) { return Pl3xMap.api().getIconRegistry().unregister(iconKey) != null; } + + @Override + public void shutdown() { + observerHandler.disableObservers(); + } } diff --git a/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/MapTowny.java b/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/MapTowny.java index c7d492b..2034c4d 100644 --- a/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/MapTowny.java +++ b/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/MapTowny.java @@ -208,4 +208,8 @@ public void reload() throws IOException { public void async(Runnable run) { scheduler.scheduleAsyncTask(run); } + + public MapTownyScheduler getScheduler() { + return scheduler; + } } diff --git a/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/managers/TownyLayerManager.java b/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/managers/TownyLayerManager.java index ce6187c..80eb17b 100644 --- a/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/managers/TownyLayerManager.java +++ b/maptowny-plugin/src/main/java/me/silverwolfg11/maptowny/managers/TownyLayerManager.java @@ -37,7 +37,9 @@ import me.silverwolfg11.maptowny.objects.TownRenderEntry; import me.silverwolfg11.maptowny.platform.MapLayer; import me.silverwolfg11.maptowny.platform.MapPlatform; +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; import me.silverwolfg11.maptowny.platform.MapWorld; +import me.silverwolfg11.maptowny.tasks.RenderTownsTask; import me.silverwolfg11.maptowny.util.PolygonUtil; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; @@ -64,6 +66,7 @@ public class TownyLayerManager implements LayerManager { private final MapTowny plugin; private final TownInfoManager townInfoManager; private final MapPlatform mapPlatform; + private final MapPlatformObserver layerPlatformObserver; private final Map worldProviders = new ConcurrentHashMap<>(); private final Collection renderedTowns = ConcurrentHashMap.newKeySet(); @@ -89,8 +92,9 @@ public TownyLayerManager(MapTowny plugin, MapPlatform platform) { this.plugin = plugin; this.townInfoManager = new TownInfoManager(plugin.getDataFolder(), plugin.getLogger()); this.mapPlatform = platform; - // Schedule initialization - mapPlatform.onFirstInitialize(this::initialize); + + this.layerPlatformObserver = createLayerPlatformObserver(); + this.mapPlatform.registerObserver(layerPlatformObserver); } // Callbacks execute when the LayerManager completes initialization. @@ -104,9 +108,50 @@ public void onInitialize(Runnable runnable) { } } + private MapPlatformObserver createLayerPlatformObserver() { + return new MapPlatformObserver() { + @Override + public void onObserverSetup() { + initialize(); + } + + @Override + public void onPlatformEnabled() { + reloadPlatform(); + } + }; + } + private void initialize() { MapPlatform platform = mapPlatform; + loadWorldProviders(platform); + loadIcons(platform); + + isInitialized = true; + for (Runnable callback : initializerCallbacks) { + callback.run(); + } + + // Allow GC + initializerCallbacks.clear(); + initializerCallbacks = null; + } + + // Assumes platform's marker sets and icons are + // empty. + private void reloadPlatform() { + renderedTowns.clear(); + loadIcons(mapPlatform); + + plugin.getScheduler().scheduleTask(() -> { + loadWorldProviders(mapPlatform); + new RenderTownsTask(plugin).run(); + }); + } + + // Must be called on the Bukkit thread. + private void loadWorldProviders(MapPlatform platform) { // Load world providers for (String worldName : plugin.config().getEnabledWorlds()) { World world = Bukkit.getWorld(worldName); @@ -125,7 +170,9 @@ private void initialize() { MapLayer mapLayer = platform.getWorld(world).registerLayer(LAYER_KEY, layerOptions); worldProviders.put(world.getName(), mapLayer); } + } + private void loadIcons(MapPlatform platform) { // Load icons int iconWidth = plugin.config().getIconSizeX(); int iconHeight = plugin.config().getIconSizeY(); @@ -146,16 +193,6 @@ private void initialize() { else { usingOutposts = false; } - - // Run initialization callbacks - isInitialized = true; - for (Runnable callback : initializerCallbacks) { - callback.run(); - } - - // Allow GC - initializerCallbacks.clear(); - initializerCallbacks = null; } // Only ran synchronous @@ -479,6 +516,8 @@ public void close() { if (mapPlatform.hasIcon(OUTPOST_ICON)) mapPlatform.unregisterIcon(OUTPOST_ICON); + + mapPlatform.unregisterObserver(layerPlatformObserver); } // API Methods diff --git a/maptowny-squaremap/src/main/java/me/silverwolfg11/maptowny/platform/squaremap/SquareMapPlatform.java b/maptowny-squaremap/src/main/java/me/silverwolfg11/maptowny/platform/squaremap/SquareMapPlatform.java index acc6a8f..1773e48 100644 --- a/maptowny-squaremap/src/main/java/me/silverwolfg11/maptowny/platform/squaremap/SquareMapPlatform.java +++ b/maptowny-squaremap/src/main/java/me/silverwolfg11/maptowny/platform/squaremap/SquareMapPlatform.java @@ -23,6 +23,7 @@ package me.silverwolfg11.maptowny.platform.squaremap; import me.silverwolfg11.maptowny.platform.MapPlatform; +import me.silverwolfg11.maptowny.platform.MapPlatformObserver; import me.silverwolfg11.maptowny.platform.MapWorld; import org.bukkit.World; import org.jetbrains.annotations.NotNull; @@ -33,13 +34,36 @@ import xyz.jpenilla.squaremap.api.WorldIdentifier; import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; public class SquareMapPlatform implements MapPlatform { + + private final List platformObservers = new ArrayList<>(); + @Override public @NotNull String getPlatformName() { return "squaremap"; } + @Override + public boolean registerObserver(@NotNull MapPlatformObserver observer) { + if (platformObservers.contains(observer)) { + return false; + } + + // SquareMap doesn't implement a way to monitor enable/disable. + observer.onObserverSetup(); + platformObservers.add(observer); + return true; + } + + @Override + public boolean unregisterObserver(@NotNull MapPlatformObserver observer) { + // Not keeping track of observers + return platformObservers.remove(observer); + } + @Override public boolean isWorldEnabled(@NotNull World world) { final WorldIdentifier worldId = BukkitAdapter.worldIdentifier(world); @@ -78,4 +102,9 @@ public boolean unregisterIcon(@NotNull String iconKey) { SquaremapProvider.get().iconRegistry().unregister(key); return hasKey; } + + @Override + public void shutdown() { + platformObservers.clear(); + } }