From 8384c47e53328acdd5d1ea44eac628f0dcacd88c Mon Sep 17 00:00:00 2001 From: Nate Mortensen Date: Thu, 10 Nov 2016 10:50:38 -0700 Subject: [PATCH] Create Lifetime system for managing Listeners with a defined duration. This commit introduces a Component system focused around Games, which is completely backwards compatible, as well as designed to be flexible enough for later improvements such as dependency injection. Each GameState is associated with a phase of the PhasedLifetime that each Game has. Components can be registered with a specific phased or the entirety of the Lifetime. Refer to the javadocs for Lifetime and PhasedLifetime for more details. Currently the main two Component types are ICommand and ListenerComponent. This commit includes the first refactoring into using this system, which is replacing the Wizards minigame's usage of Miniplugin with a Lifetimed Component, allowing for the shop to be appropriately registered and unregistered. This change allows for Wizards to be run more than once on the same server instance. Previously, attempting to register the Miniplugin twice would result in the minigame failing to start after the initial registration. This commit additionally includes slight refactoring within GameCreationManager as required for the Lifetime system to function correctly. These changes ensure that Games are only disabled once, whereas before they could be repeatedly disabled. The previous implementation of disable(), along with the classes that override it, functioned correctly despite being called several times. Finally, this commit adds in changes to the pom to allow for unit testing. --- Plugins/Mineplex.Core/pom.xml | 5 + .../src/mineplex/core/MiniPlugin.java | 22 +- .../src/mineplex/core/command/ICommand.java | 15 +- .../mineplex/core/lifetimes/Component.java | 21 ++ .../src/mineplex/core/lifetimes/Lifetime.java | 29 ++ .../mineplex/core/lifetimes/Lifetimed.java | 19 ++ .../core/lifetimes/ListenerComponent.java | 54 ++++ .../core/lifetimes/PhasedComponent.java | 7 + .../core/lifetimes/PhasedLifetime.java | 289 ++++++++++++++++++ .../core/lifetimes/SimpleLifetime.java | 96 ++++++ .../src/mineplex/core/shop/ShopBase.java | 67 ++-- .../mineplex/core/shop/page/ShopPageBase.java | 20 +- .../core/lifetimes/LoggingComponent.java | 32 ++ .../core/lifetimes/PhasedLifetimeTest.java | 99 ++++++ .../core/lifetimes/SimpleLifetimeTest.java | 41 +++ .../src/nautilus/game/arcade/game/Game.java | 22 +- .../game/arcade/game/GameComponent.java | 31 ++ .../game/games/gladiators/Gladiators.java | 1 + .../game/games/wizards/WizardSpellMenu.java | 13 +- .../arcade/game/games/wizards/Wizards.java | 2 +- .../arcade/managers/GameCreationManager.java | 19 +- Plugins/pom.xml | 14 + 22 files changed, 847 insertions(+), 71 deletions(-) create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Component.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetime.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetimed.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/lifetimes/ListenerComponent.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedComponent.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedLifetime.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/lifetimes/SimpleLifetime.java create mode 100644 Plugins/Mineplex.Core/test/mineplex/core/lifetimes/LoggingComponent.java create mode 100644 Plugins/Mineplex.Core/test/mineplex/core/lifetimes/PhasedLifetimeTest.java create mode 100644 Plugins/Mineplex.Core/test/mineplex/core/lifetimes/SimpleLifetimeTest.java create mode 100644 Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/GameComponent.java diff --git a/Plugins/Mineplex.Core/pom.xml b/Plugins/Mineplex.Core/pom.xml index a2d2f6e3e..afbd561ec 100644 --- a/Plugins/Mineplex.Core/pom.xml +++ b/Plugins/Mineplex.Core/pom.xml @@ -60,6 +60,11 @@ core 3.3.0 + + junit + junit + test + diff --git a/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java b/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java index 631c05202..9f76c5201 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java @@ -7,6 +7,9 @@ import mineplex.core.common.util.NautHashMap; import mineplex.core.common.util.UtilServer; import mineplex.core.common.util.UtilTime; import mineplex.core.common.util.UtilTime.TimeUnit; +import mineplex.core.lifetimes.Lifetime; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.SimpleLifetime; import mineplex.core.thread.ThreadPool; import org.bukkit.Bukkit; @@ -27,12 +30,16 @@ import org.bukkit.scheduler.BukkitTask; *

* This way, we can reflectively create them during {@link #require} when they do not exist, leading to much cleaner code */ -public abstract class MiniPlugin implements Listener +public abstract class MiniPlugin implements Listener, Lifetimed { + // As MiniPlugins can technically be disabled at any time, each + // has its own unique Lifetime. If MiniPlugins are declared + // to never be able to be disabled, then a "Singleton" Lifetime + // could be shared between all of them. + private final SimpleLifetime _lifetime = new SimpleLifetime(); protected String _moduleName = "Default"; protected JavaPlugin _plugin; - protected NautHashMap _commands; - + protected long _initializedTime; public MiniPlugin(String moduleName) @@ -47,8 +54,7 @@ public abstract class MiniPlugin implements Listener _initializedTime = System.currentTimeMillis(); - _commands = new NautHashMap<>(); - + _lifetime.start(); onEnable(); registerEvents(this); @@ -250,4 +256,10 @@ public abstract class MiniPlugin implements Listener { return Managers.require(clazz); } + + @Override + public Lifetime getLifetime() + { + return _lifetime; + } } diff --git a/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java b/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java index 463a7631f..becbdfee5 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java @@ -5,10 +5,11 @@ import java.util.List; import mineplex.core.common.Rank; +import mineplex.core.lifetimes.Component; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -public interface ICommand +public interface ICommand extends Component { void SetCommandCenter(CommandCenter commandCenter); void Execute(Player caller, String[] args); @@ -21,4 +22,16 @@ public interface ICommand Rank[] GetSpecificRanks(); List onTabComplete(CommandSender sender, String commandLabel, String[] args); + + @Override + default void activate() + { + CommandCenter.Instance.addCommand(this); + } + + @Override + default void deactivate() + { + CommandCenter.Instance.removeCommand(this); + } } diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Component.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Component.java new file mode 100644 index 000000000..cee7da78f --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Component.java @@ -0,0 +1,21 @@ +package mineplex.core.lifetimes; + +/** + * A Component defines behavior that can exist within a Lifetime. Components + * should have no impact upon the game while not active. + */ +public interface Component +{ + /** + * Activates the Component, performing any sort of required initialization. + * Components may be activated and deactivated multiple times, however a component + * will not be activated more than once without subsequent calls to deactivate. + */ + void activate(); + + /** + * Deactivates the Component, disabling any sort of functionality it provides + * and performing clean up. A Component may be subsequently reactivated. + */ + void deactivate(); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetime.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetime.java new file mode 100644 index 000000000..1cb578bc0 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetime.java @@ -0,0 +1,29 @@ +package mineplex.core.lifetimes; + +/** + * A Lifetime represents a duration for which a collection of Components + * will be active. While Lifetime does contain a method for registering + * instantiated Components, individual Lifetimes may have unique + * strategies for creating Components and activating them. Lifetime + * doesn't provide any guarantee of Component activation or deactivation + * order. Implementations of Lifetime, however, may. + *

+ * Lifetime doesn't provide mechanisms for beginning or ending a Lifetime. + * This is provided by the various implementations of Lifetime, as it varies + * between the implementations and is functionality that most consumers of + * Lifetimes will not need. + */ +public interface Lifetime +{ + /** + * Registers the provided component with this Lifetime. If the Lifetime + * is currently active, then the Component will be immediately activated. + * @param component the component to register + */ + void register(Component component); + /** + * Gets whether the Lifetime is currently active. + * @return true if the Lifetime is active + */ + boolean isActive(); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetimed.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetimed.java new file mode 100644 index 000000000..fa08dd533 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetimed.java @@ -0,0 +1,19 @@ +package mineplex.core.lifetimes; + +/** + * Represents an object that is associated with a specific lifetime. + * Multiple Lifetimed objects may be associated with the same Lifetime. + * As a roughly generalized explanation, any time functionality should + * be enabled(whether its a command, listener, etc.) it should be registered + * as a Component of a Lifetime. Any object wishing to enable functionality + * that is associated with this Lifetimed object should do so with the Lifetime + * returned by {@link #getLifetime()}. + */ +public interface Lifetimed +{ + /** + * Gets the Lifetime associated with this Lifetimed object. + * @return non-null Lifetime + */ + Lifetime getLifetime(); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/ListenerComponent.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/ListenerComponent.java new file mode 100644 index 000000000..d6b664fb4 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/ListenerComponent.java @@ -0,0 +1,54 @@ +package mineplex.core.lifetimes; + +import mineplex.core.common.util.UtilServer; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +/** + * A convenience class for Components that are a Listener. ListenerComponent + * can either be used as a wrapper class for a specific Listener, or can be + * extended by another Component to provide event registration. + */ +public class ListenerComponent implements Component, Listener +{ + private final Listener _listener; + + /** + * Creates a ListenerComponent that registers the provided Listener when + * activated and unregisters it when deactivated. The newly created + * ListenerComponent will not be registered as a Listener. When a + * ListenerComponent is created with this constructor, it is effectively + * a wrapper to bind a Listener to a specific lifetime. + * + * @param listener non-null listener to wrap + * @throws IllegalArgumentException if listener is null + */ + public ListenerComponent(Listener listener) throws IllegalArgumentException + { + Validate.notNull(listener); + _listener = listener; + } + + /** + * Creates a ListenerComponent that registers itself when activated and + * unregisters itself when deactivated. + */ + public ListenerComponent() + { + _listener = this; + } + + @Override + public void activate() + { + Bukkit.getPluginManager().registerEvents(_listener, UtilServer.getPlugin()); + } + + @Override + public void deactivate() + { + HandlerList.unregisterAll(_listener); + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedComponent.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedComponent.java new file mode 100644 index 000000000..417bf56b1 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedComponent.java @@ -0,0 +1,7 @@ +package mineplex.core.lifetimes; + +public interface PhasedComponent extends Component +{ + + void setPhase(T phase); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedLifetime.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedLifetime.java new file mode 100644 index 000000000..b9760e761 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedLifetime.java @@ -0,0 +1,289 @@ +package mineplex.core.lifetimes; + +import com.google.common.base.Preconditions; +import mineplex.core.common.util.UtilServer; +import org.apache.commons.lang.Validate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A PhasedLifetime is a lifetime that is composed of several + * smaller lifetimes, referred to as phases. This class is provided + * in order to support a system in which Components may exist within multiple + * Lifetimes. PhasedLifetime is not thread-safe. + *

+ * Registering a Component will register it for the entirety of this Lifetime, + * unless registered with {@link #register(Component, Iterable)}. Special behavior + * is provided for instances of {@link PhasedComponent}. See {@link #register(Component, + * Iterable)} for more information. Components registered using {@link + * #register(Component)} are registered for the entire duration of the Lifetime. + */ +public class PhasedLifetime implements Lifetime +{ + private final Map> _phases = new HashMap<>(); + private final List _global = new ArrayList<>(); + private boolean _active = false; + private T _current; + + /** + * Registers the Component for all phases within the provided Iterable, + * and creates a Lifetime that is active during all those phases, and + * therefore identical to when the provided Component is active. If a change + * occurs from a Phase that the Component is active in to another phase that + * the component is active in, it will not be disabled. When this + * Lifetime ends, all Lifetimes created by this Lifetime also end, as all + * phases are considered over. + *

+ * If the Component is an instance of PhasedComponent, then any phase change + * in which one of the phases is within the provided Iterable of phases will + * result in an invocation of {@link PhasedComponent#setPhase(Object)}. This + * should not be used as a mechanism to detect when the component is being + * disabled, but rather as a means to provide specific behavior when that + * phase change occurs. If a phase change drastically changes the behavior + * of a Component such that not all functionality is active for some phases + * that a Component is registered for, you should consider refactoring that + * Component into two separate Components. + *

+ * As an example, assume that we have a PhasedLifetime with phases A-F, + * and a PhasedComponent is registered for phases A, B, and E. The chain + * of events would be as followed(italic indicates an event call to the + * PhasedComponent, an activation, or deactivation). + *

+ *

+ * If phases contains no elements, then the Component will not be + * registered. + * @param component non-null Component being registered + * @param phases non-null Iterable of phases to register the Component for + * @return a Lifetime corresponding to when the Component is active + * @throws IllegalArgumentException if component or phases is null + */ + public Lifetime register(Component component, Iterable phases) throws IllegalArgumentException { + Validate.notNull(component, "Component cannot be null"); + Validate.notNull(phases, "Phases cannot be null"); + RegisteredComponent rComponent = new RegisteredComponent(component); + for (T phase : phases) + { + _phases.computeIfAbsent(phase, (p) -> new ArrayList<>()).add(rComponent); + if (Objects.equals(phase, _current)) + { + rComponent.start(); + if (component instanceof PhasedComponent) + { + ((PhasedComponent) component).setPhase(phase); + } + } + } + return rComponent; + } + + /** + * Starts the Lifetime, activating all components that are active for + * the entire lifetime, and then activating all components that are part + * of the provided phase. + * @param phase non-null phase to start + * @throws IllegalArgumentException if phase is null + * @throws IllegalStateException if the Lifetime is currently active + */ + public void start(T phase) throws IllegalArgumentException, IllegalStateException + { + Validate.notNull(phase, "phase cannot be null"); + Preconditions.checkState(!_active, "Lifetime already started"); + _active = true; + _global.forEach(PhasedLifetime::active); + setPhase(phase); + } + + /** + * Ends the Lifetime, deactivating all components in the current phase + * and then deactivating all components that are active for the entire Lifetime. + * A Lifetime may be subsequently reactivated after it has ended. + * @throws IllegalStateException if the lifetime isn't active + */ + public void end() throws IllegalStateException + { + Preconditions.checkState(_active, "Lifetime not active"); + List toDisable = _phases.get(getPhase()); + if (toDisable != null) + { + toDisable.forEach(RegisteredComponent::end); + } + _global.forEach(PhasedLifetime::deactive); + _active = false; + _current = null; + } + + /** + * Sets the current phase to the provided value, activating components + * that are active in the phase and not currently active, and deactiving + * components that were previously active and are not registered for the + * provided phase. + * @param phase non-null + * @throws IllegalStateException if the Lifetime isn't active + * @throws IllegalArgumentException if the phase equals the current phase + * or is null + */ + public void setPhase(T phase) throws IllegalStateException, IllegalArgumentException + { + Preconditions.checkState(_active, "Lifetime not active"); + Validate.isTrue(!Objects.equals(phase, _current), "Can't set the phase to the current phase"); + Validate.notNull(phase, "the phase cannot be null"); + T oldPhase = getPhase(); + _current = phase; + List old = _phases.get(oldPhase); + List nextPhase = _phases.get(phase); + + for (Component c : _global) + { + if (c instanceof PhasedComponent) + { + ((PhasedComponent) c).setPhase(phase); + } + } + // Disable components that were active in the last phase but not in the next phase. + if (old != null) + { + List toDisable = new ArrayList<>(); + toDisable.addAll(old); + // Components that are in the next phase shouldn't be disabled. + if (nextPhase != null) + { + toDisable.removeAll(nextPhase); + } + + // Ensure that all old ones get a setPhase call before disabling them. + for (RegisteredComponent r : toDisable) + { + if (r.getComponent() instanceof PhasedComponent) + { + ((PhasedComponent) r.getComponent()).setPhase(phase); + } + } + toDisable.forEach(RegisteredComponent::end); + } + if (nextPhase != null) + { + // New but not old + List toActivate = new ArrayList<>(); + toActivate.addAll(nextPhase); + // Ensure that all components from last phase don't end up getting activated again. + if (old != null) + { + toActivate.removeAll(old); + } + // Start all the new ones + toActivate.forEach(RegisteredComponent::start); + // Give every remaining component a call to setPhase + for (RegisteredComponent r : nextPhase) + { + if (r.getComponent() instanceof PhasedComponent) + { + ((PhasedComponent) r.getComponent()).setPhase(phase); + } + } + } + } + + private static void active(Component component) + { + try + { + component.activate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to activate component: " + component); + } + } + private static void deactive(Component component) + { + try + { + component.deactivate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to deactivate component: " + component); + } + } + /** + * Gets the current Phase. If this PhasedLifetime isn't active then + * the current phase is null. + * @return the current phase, null if not active + */ + public T getPhase() + { + return _current; + } + + /** + * {@inheritDoc} + * If the Component is an instance of PhasedComponent, it will receive + * notifications of phase changes prior to any components registered + * with specific phases. Global components will also be deactivated last. + * @param component the component to register + */ + @Override + public void register(Component component) + { + _global.add(component); + if (this.isActive()) + { + component.activate(); + } + } + + @Override + public boolean isActive() + { + return _active; + } + + private static class RegisteredComponent extends SimpleLifetime implements Lifetime { + private Component _component; + + public RegisteredComponent(Component component) + { + this._component = component; + } + + public Component getComponent() + { + return _component; + } + + @Override + public void start() throws IllegalStateException + { + PhasedLifetime.active(_component); + super.start(); + } + + @Override + public void end() throws IllegalStateException + { + super.end(); + PhasedLifetime.deactive(_component); + } + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/SimpleLifetime.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/SimpleLifetime.java new file mode 100644 index 000000000..28dcfe45e --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/SimpleLifetime.java @@ -0,0 +1,96 @@ +package mineplex.core.lifetimes; + +import mineplex.core.common.util.UtilServer; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * A Lifetime that is not thread-safe and is essentially a list of Components. + * All components are activated in the order that they are registered. All + * components are deactivated in the reverse order that they are registered. + * Components may not be registered during invocations of start or end. If a + * Component throws an exception during activation it will be logged, however the + * Component will still only be disabled once all other Components are disabled. + */ +public class SimpleLifetime implements Lifetime +{ + protected final List _components = new ArrayList<>(); + protected boolean _active = false; + @Override + public void register(Component component) + { + this._components.add(component); + if (this.isActive()) + { + try + { + component.activate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to active component: " + component); + } + } + } + + @Override + public boolean isActive() + { + return _active; + } + + /** + * Starts the SimpleLifetime, activating all components in the order that + * they were registered. A SimpleLifetime may be started multiple times, + * so long that every invocation of start after the first is preceded by + * an invocation of {@link #end()}. If a Component throws an exception + * during activation the exception will be logged, however no specific + * error-handling mechanism is provided. The Component will still be + * considered active and will be deactivated with all other Components. + * @throws IllegalStateException if currently active + */ + public void start() throws IllegalStateException + { + Validate.isTrue(!_active); + _active = true; + for (Component component : _components) + { + try + { + component.activate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to active component: " + component); + } + } + } + + /** + * Deactivates all components in the reverse order that they were registered. + * Any exception thrown by a Component while being deactivated will be logged, + * however no exception handling mechanism is provided. + * @throws IllegalStateException if not currently active + */ + public void end() throws IllegalStateException + { + Validate.isTrue(_active); + _active = false; + ListIterator reverseIterator = _components.listIterator(_components.size()); + while (reverseIterator.hasPrevious()) + { + Component component = reverseIterator.previous(); + try + { + component.deactivate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to deactivate component: " + component); + } + } + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java b/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java index e9d26ad8e..6ee09a020 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java @@ -1,13 +1,15 @@ package mineplex.core.shop; -import mineplex.core.MiniPlugin; import mineplex.core.account.CoreClientManager; import mineplex.core.common.util.NautHashMap; import mineplex.core.donation.DonationManager; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.ListenerComponent; import mineplex.core.npc.event.NpcDamageByEntityEvent; import mineplex.core.npc.event.NpcInteractEntityEvent; import mineplex.core.shop.page.ShopPageBase; import net.minecraft.server.v1_8_R3.EntityPlayer; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_8_R3.event.CraftEventFactory; @@ -16,7 +18,6 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; @@ -29,7 +30,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -public abstract class ShopBase implements Listener +public abstract class ShopBase extends ListenerComponent { private NautHashMap _errorThrottling; private NautHashMap _purchaseBlock; @@ -52,7 +53,7 @@ public abstract class ShopBase implements Listene _errorThrottling = new NautHashMap(); _purchaseBlock = new NautHashMap(); - _plugin.registerEvents(this); + _plugin.getLifetime().register(this); } @EventHandler(priority = EventPriority.LOWEST) @@ -150,14 +151,7 @@ public abstract class ShopBase implements Listene { if (_playerPageMap.containsKey(event.getPlayer().getUniqueId()) && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle() != null && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle().equalsIgnoreCase(event.getInventory().getTitle())) { - _playerPageMap.get(event.getPlayer().getUniqueId()).playerClosed(); - _playerPageMap.get(event.getPlayer().getUniqueId()).dispose(); - - _playerPageMap.remove(event.getPlayer().getUniqueId()); - - closeShopForPlayer((Player) event.getPlayer()); - - _openedShop.remove(event.getPlayer().getUniqueId()); + removePlayer((Player) event.getPlayer()); } } @@ -169,17 +163,39 @@ public abstract class ShopBase implements Listene if (_playerPageMap.containsKey(event.getPlayer().getUniqueId()) && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle() != null && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle().equalsIgnoreCase(event.getInventory().getTitle())) { - _playerPageMap.get(event.getPlayer().getUniqueId()).playerClosed(); - _playerPageMap.get(event.getPlayer().getUniqueId()).dispose(); - - _playerPageMap.remove(event.getPlayer().getUniqueId()); - - closeShopForPlayer((Player) event.getPlayer()); - - _openedShop.remove(event.getPlayer().getUniqueId()); + removePlayer((Player) event.getPlayer()); } } - + + protected void removePlayer(Player player) + { + _playerPageMap.get(player.getUniqueId()).playerClosed(); + _playerPageMap.get(player.getUniqueId()).dispose(); + + _playerPageMap.remove(player.getUniqueId()); + + closeShopForPlayer(player); + + _openedShop.remove(player.getUniqueId()); + } + + @Override + public void deactivate() + { + super.deactivate(); + _playerPageMap.entrySet().stream().map(Map.Entry::getKey).map(Bukkit::getPlayer).forEach(p -> { + if (_playerPageMap.get(p.getName()).getTitle().equals(p.getOpenInventory().getTitle())) + { + p.closeInventory(); + } + removePlayer(p); + }); + _playerPageMap.clear(); + _openedShop.clear(); + _purchaseBlock.clear(); + _errorThrottling.clear(); + } + protected boolean canOpenShop(Player player) { return true; @@ -194,15 +210,8 @@ public abstract class ShopBase implements Listene { if (_playerPageMap.containsKey(event.getPlayer().getUniqueId())) { - _playerPageMap.get(event.getPlayer().getUniqueId()).playerClosed(); - _playerPageMap.get(event.getPlayer().getUniqueId()).dispose(); - + removePlayer(event.getPlayer()); event.getPlayer().closeInventory(); - closeShopForPlayer(event.getPlayer()); - - _playerPageMap.remove(event.getPlayer().getUniqueId()); - - _openedShop.remove(event.getPlayer().getUniqueId()); } } diff --git a/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java b/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java index 7f1a8c525..086506dfd 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java @@ -1,5 +1,14 @@ package mineplex.core.shop.page; + +import mineplex.core.account.CoreClient; +import mineplex.core.account.CoreClientManager; +import mineplex.core.common.util.NautHashMap; +import mineplex.core.common.util.UtilInv; +import mineplex.core.donation.DonationManager; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.shop.ShopBase; +import mineplex.core.shop.item.IButton; import org.bukkit.Sound; import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftInventoryCustom; @@ -11,16 +20,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import mineplex.core.MiniPlugin; -import mineplex.core.account.CoreClient; -import mineplex.core.account.CoreClientManager; -import mineplex.core.common.util.NautHashMap; -import mineplex.core.common.util.UtilInv; -import mineplex.core.donation.DonationManager; -import mineplex.core.shop.ShopBase; -import mineplex.core.shop.item.IButton; - -public abstract class ShopPageBase> extends CraftInventoryCustom implements Listener +public abstract class ShopPageBase> extends CraftInventoryCustom implements Listener { protected PluginType _plugin; protected CoreClientManager _clientManager; diff --git a/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/LoggingComponent.java b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/LoggingComponent.java new file mode 100644 index 000000000..d2f61d538 --- /dev/null +++ b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/LoggingComponent.java @@ -0,0 +1,32 @@ +package mineplex.core.lifetimes; + +import java.util.List; + +public class LoggingComponent implements PhasedComponent { + private final List _events; + private final String _name; + + public LoggingComponent(List events, String name) + { + _events = events; + this._name = name; + } + + @Override + public void activate() + { + _events.add(this._name + " activated"); + } + + @Override + public void deactivate() + { + _events.add(this._name + " deactivated"); + } + + @Override + public void setPhase(Object phase) + { + _events.add(this._name + " setPhase " + phase); + } +} \ No newline at end of file diff --git a/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/PhasedLifetimeTest.java b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/PhasedLifetimeTest.java new file mode 100644 index 000000000..3d62001c8 --- /dev/null +++ b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/PhasedLifetimeTest.java @@ -0,0 +1,99 @@ +package mineplex.core.lifetimes; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class PhasedLifetimeTest +{ + PhasedLifetime _lifetime = new PhasedLifetime<>(); + List _events = new ArrayList<>(); + @Test + public void testTwoPhaseComponent() + { + Assert.assertFalse(_lifetime.isActive()); + _lifetime.register(new LoggingComponent(_events, "component"), Arrays.asList(Phase.A, Phase.B)); + _lifetime.start(Phase.A); + Assert.assertTrue(_lifetime.isActive()); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A"), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B"), _events); + _lifetime.setPhase(Phase.C); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component setPhase C", "component deactivated"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component setPhase C", "component deactivated"), _events); + Assert.assertFalse(_lifetime.isActive()); + } + @Test + public void testGlobalComponent() + { + _lifetime.register(new LoggingComponent(_events, "component")); + _lifetime.start(Phase.A); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A"), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component deactivated"), _events); + } + + @Test + public void testLateRegistration() + { + _lifetime.start(Phase.A); + _lifetime.register(new LoggingComponent(_events, "component"), Arrays.asList(Phase.A, Phase.B)); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A"), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component deactivated"), _events); + } + @Test + public void testSinglePhase() + { + _lifetime.register(new LoggingComponent(_events, "component"), Collections.singletonList(Phase.B)); + _lifetime.start(Phase.A); + Assert.assertEquals(Collections.emptyList(), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B"), _events); + _lifetime.setPhase(Phase.C); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B", "component setPhase C", "component deactivated"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B", "component setPhase C", "component deactivated"), _events); + } + @Test + public void testComponentLifetimes() + { + _lifetime.register(new LoggingComponent(_events, "component"), Collections.singletonList(Phase.B)).register(new LoggingComponent(_events, "child")); + _lifetime.start(Phase.A); + Assert.assertEquals(Collections.emptyList(), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "child activated", "component setPhase B"), _events); + _lifetime.setPhase(Phase.C); + Assert.assertEquals(Arrays.asList("component activated", "child activated","component setPhase B", "component setPhase C", "child deactivated", "component deactivated"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "child activated", "component setPhase B", "component setPhase C", "child deactivated", "component deactivated"), _events); + } + @Test + public void testEarlyShutdown() + { + _lifetime.register(new LoggingComponent(_events, "component"), Arrays.asList(Phase.B, Phase.C)); + _lifetime.start(Phase.A); + Assert.assertEquals(Collections.emptyList(), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B", "component deactivated"), _events); + + } + enum Phase + { + A, + B, + C, + ; + } +} diff --git a/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/SimpleLifetimeTest.java b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/SimpleLifetimeTest.java new file mode 100644 index 000000000..061a1decc --- /dev/null +++ b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/SimpleLifetimeTest.java @@ -0,0 +1,41 @@ +package mineplex.core.lifetimes; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SimpleLifetimeTest +{ + private final SimpleLifetime _lifetime = new SimpleLifetime(); + private final List _events = new ArrayList<>(); + @Test + public void testAddition() + { + _lifetime.register(new LoggingComponent(_events,"a")); + _lifetime.start(); + _lifetime.end(); + Assert.assertEquals(_events, Arrays.asList("a activated", "a deactivated")); + } + @Test + public void testLateAddition() + { + _lifetime.start(); + _lifetime.register(new LoggingComponent(_events,"a")); + _lifetime.end(); + Assert.assertEquals(_events, Arrays.asList("a activated", "a deactivated")); + } + @Test + public void testActivationOrder() + { + _lifetime.register(new LoggingComponent(_events,"a")); + _lifetime.register(new LoggingComponent(_events,"b")); + _lifetime.start(); + _lifetime.end(); + Assert.assertEquals(_events, Arrays.asList("a activated", "b activated", "b deactivated", "a deactivated")); + } + + +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java index d2864e5e5..c008a6ae6 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java @@ -13,6 +13,9 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.ListenerComponent; +import mineplex.core.lifetimes.PhasedLifetime; import org.apache.commons.lang3.tuple.Triple; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -109,9 +112,8 @@ import nautilus.game.arcade.world.WorldData; import net.minecraft.server.v1_8_R3.EntityItem; import net.minecraft.server.v1_8_R3.PacketPlayInUseEntity; -public abstract class Game implements Listener +public abstract class Game extends ListenerComponent implements Lifetimed { - private final static int MAX_TICK_SPEED_MEASUREMENT = 40; public long getGameLiveTime() @@ -135,6 +137,7 @@ public abstract class Game implements Listener private GameType _gameType; protected String[] _gameDesc; + private PhasedLifetime _lifetime = new PhasedLifetime<>(); // Map private HashMap> _files; @@ -387,6 +390,7 @@ public abstract class Game implements Listener { Manager = manager; + _lifetime.register(this); // Player List UtilTabTitle.broadcastHeaderAndFooter(C.cGold + C.Bold + gameType.GetName(), "Visit " + C.cGreen + "www.mineplex.com" + ChatColor.RESET + " for News, Forums and Shop"); @@ -760,6 +764,9 @@ public abstract class Game implements Listener for (Player player : UtilServer.getPlayers()) player.leaveVehicle(); + + _lifetime.setPhase(state); + // Event GameStateChangeEvent stateEvent = new GameStateChangeEvent(this, state); UtilServer.getServer().getPluginManager().callEvent(stateEvent); @@ -2347,7 +2354,11 @@ public abstract class Game implements Listener public void disable() { + cleanupModules(); + cleanupCommands(); Managers.get(AntiHack.class).resetIgnoredChecks(); + getLifetime().end(); + getStatTrackers().forEach(HandlerList::unregisterAll); } @EventHandler @@ -2455,5 +2466,10 @@ public abstract class Game implements Listener { return clazz.cast(_modules.get(clazz)); } - + + @Override + public PhasedLifetime getLifetime() + { + return _lifetime; + } } diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/GameComponent.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/GameComponent.java new file mode 100644 index 000000000..00118f80c --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/GameComponent.java @@ -0,0 +1,31 @@ +package nautilus.game.arcade.game; + +import mineplex.core.lifetimes.Lifetime; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.ListenerComponent; + +import java.util.Arrays; + +public class GameComponent extends ListenerComponent implements Lifetimed +{ + private final Lifetime _lifetime; + + public GameComponent(Game game, Game.GameState...active) + { + if (active == null || active.length == 0) + { + // Active for the entire duration of the game. + _lifetime = game.getLifetime(); + _lifetime.register(this); + } else + { + _lifetime = game.getLifetime().register(this, Arrays.asList(active)); + } + } + + @Override + public Lifetime getLifetime() + { + return _lifetime; + } +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java index 59dd19d6c..907c62858 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java @@ -1115,6 +1115,7 @@ public class Gladiators extends SoloGame @Override public void disable() { + super.disable(); _hotbarEditor.deregisterSelf(); // De-register as listener _hotbarEditor.onDisable(); // Fully disable } diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java index 6a384727d..f9f5a9236 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java @@ -1,13 +1,14 @@ package nautilus.game.arcade.game.games.wizards; -import mineplex.core.MiniPlugin; import mineplex.core.common.util.C; import mineplex.core.common.util.UtilInv; import mineplex.core.common.util.UtilServer; import mineplex.core.itemstack.ItemBuilder; +import mineplex.core.lifetimes.Lifetime; import nautilus.game.arcade.events.GameStateChangeEvent; import nautilus.game.arcade.game.Game.GameState; +import nautilus.game.arcade.game.GameComponent; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -20,18 +21,17 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.java.JavaPlugin; -public class WizardSpellMenu extends MiniPlugin +public class WizardSpellMenu extends GameComponent { private Wizards _wizards; private WizardSpellMenuShop _wizardShop; private ItemStack _wizardSpells = new ItemBuilder(Material.ENCHANTED_BOOK).setTitle(C.cGold + "Wizard Spells") .addLore(C.cGray + "Right click with this to view the spells").build(); - public WizardSpellMenu(String moduleName, JavaPlugin plugin, Wizards wizards) + public WizardSpellMenu(Wizards wizards) { - super("Wizard Spell Menu", plugin); + super(wizards); _wizardShop = new WizardSpellMenuShop(this, wizards.getArcadeManager().GetClients(), wizards.getArcadeManager() .GetDonation(), wizards); _wizards = wizards; @@ -49,7 +49,7 @@ public class WizardSpellMenu extends MiniPlugin @EventHandler public void onDeath(final PlayerDeathEvent event) { - Bukkit.getScheduler().scheduleSyncDelayedTask(_plugin, new Runnable() + Bukkit.getScheduler().scheduleSyncDelayedTask(UtilServer.getPlugin(), new Runnable() { public void run() { @@ -131,5 +131,4 @@ public class WizardSpellMenu extends MiniPlugin } } } - } diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java index c4463ea18..5401f66d1 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java @@ -153,7 +153,7 @@ public class Wizards extends SoloGame new KitWitchDoctor(manager) }); - _wizard = new WizardSpellMenu("Wizard Spell Menu", getArcadeManager().getPlugin(), this); + _wizard = new WizardSpellMenu(this); AnnounceStay = false; BlockBreak = true; diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java index 39ed239e9..b0c6ae5f9 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java @@ -87,8 +87,7 @@ public class GameCreationManager implements Listener { if (Manager.GetGame().GetState() == GameState.Dead) { - HandlerList.unregisterAll(Manager.GetGame()); - + Manager.GetGame().disable(); //Schedule Cleanup _ended.add(Manager.GetGame()); @@ -105,15 +104,6 @@ public class GameCreationManager implements Listener while (gameIterator.hasNext()) { Game game = gameIterator.next(); - - game.cleanupModules(); - game.cleanupCommands(); - game.disable(); - - HandlerList.unregisterAll(game); - - for (StatTracker tracker : game.getStatTrackers()) - HandlerList.unregisterAll(tracker); TimingManager.start("GameCreationManager - Attempting Removal - " + game.GetName()); @@ -123,8 +113,7 @@ public class GameCreationManager implements Listener gameIterator.remove(); } else - { - + { boolean removedPlayers = false; if (UtilTime.elapsed(game.GetStateTime(), 20000)) { @@ -154,7 +143,7 @@ public class GameCreationManager implements Listener game.WorldData.Uninitialize(); game.WorldData = null; gameIterator.remove(); - + TimingManager.stop("GameCreationManager - Uninit World - " + game.GetName()); }; } @@ -275,7 +264,7 @@ public class GameCreationManager implements Listener TimingManager.stop("DisplayNext"); TimingManager.start("registerEvents"); - UtilServer.getServer().getPluginManager().registerEvents(Manager.GetGame(), Manager.getPlugin()); + Manager.GetGame().getLifetime().start(GameState.Loading); TimingManager.stop("registerEvents"); } diff --git a/Plugins/pom.xml b/Plugins/pom.xml index 137620d6a..dbc3f82c8 100644 --- a/Plugins/pom.xml +++ b/Plugins/pom.xml @@ -134,11 +134,25 @@ 2.8.1 compile + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + ${project.name} + ${project.basedir}/test ${project.basedir}/src