mirror of
https://github.com/songoda/SongodaCore.git
synced 2024-10-22 12:20:26 +02:00
Introduce a new hook system (right now only with Hologram support)
My plans with this hook system are to make it easier to understand and maintain – Hopefully it is also more flexible Hooks now have a dedicated activation-life-cycle – I think this allows it to contain all the logic to do what it needs to do but also provide some helper methods like `#canBeActivated`. The activation should be used by the hooks to reduce the memory and performancec impact when not used. The de-activation also allows hooks to clean up themselves and not rely on the plugins used by the hook or the plugin using the hook to clean up. For example: You can see that the `DecentHologramsHook` has few class variables and they are kept as small as possible and reasonable when not activated. In `#deactivate` I call `#removeAll` to remove all holograms that still exist and I call `ArrayList#trimToSize` to reduce it's size again. This has a similar effect to setting the class varaible null or to a new List but with this I can make that field *final*. The exact implementation details and capabilities vary on the third-party plugins being supported by hooks, so it needs to be flexible and easy to understand, so we can easily add support for more plugins and especially new plugin categories like Maps (dynmap, BlueMap, PlexMap, ...).
This commit is contained in:
parent
d8564d1c1c
commit
638b793040
@ -7,6 +7,7 @@ import com.craftaro.core.database.DatabaseType;
|
||||
import com.craftaro.core.dependency.Dependency;
|
||||
import com.craftaro.core.dependency.DependencyLoader;
|
||||
import com.craftaro.core.dependency.Relocation;
|
||||
import com.craftaro.core.hooks.HookRegistryManager;
|
||||
import com.craftaro.core.locale.Locale;
|
||||
import com.craftaro.core.utils.Metrics;
|
||||
import com.craftaro.core.verification.CraftaroProductVerification;
|
||||
@ -39,6 +40,8 @@ public abstract class SongodaPlugin extends JavaPlugin {
|
||||
private boolean licensePreventedPluginLoad = false;
|
||||
private boolean emergencyStop = false;
|
||||
|
||||
private final HookRegistryManager hookRegistryManager = new HookRegistryManager(this);
|
||||
|
||||
static {
|
||||
MinecraftVersion.getLogger().setLevel(Level.WARNING);
|
||||
MinecraftVersion.disableUpdateCheck();
|
||||
@ -221,6 +224,8 @@ public abstract class SongodaPlugin extends JavaPlugin {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
this.hookRegistryManager.deactivateAllActiveHooks();
|
||||
|
||||
console.sendMessage(ChatColor.GREEN + "=============================");
|
||||
console.sendMessage(" "); // blank line to separate chatter
|
||||
}
|
||||
@ -357,4 +362,8 @@ public abstract class SongodaPlugin extends JavaPlugin {
|
||||
}
|
||||
this.dataManager = dataManager;
|
||||
}
|
||||
|
||||
public HookRegistryManager getHookManager() {
|
||||
return this.hookRegistryManager;
|
||||
}
|
||||
}
|
||||
|
144
Core/src/main/java/com/craftaro/core/hooks/BaseHookRegistry.java
Normal file
144
Core/src/main/java/com/craftaro/core/hooks/BaseHookRegistry.java
Normal file
@ -0,0 +1,144 @@
|
||||
package com.craftaro.core.hooks;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This hook registry makes use of priorities to automatically activate the highest priority hook that is available if no hook has been activated programmatically.
|
||||
*/
|
||||
public abstract class BaseHookRegistry<T extends Hook> extends HookRegistry<T> {
|
||||
private final Plugin plugin;
|
||||
|
||||
private final Map<T, Integer> hooksWithPriority = new HashMap<>();
|
||||
protected T activeHook = null;
|
||||
|
||||
public BaseHookRegistry(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public abstract void registerDefaultHooks();
|
||||
|
||||
public Optional<T> getActive() {
|
||||
if (this.activeHook == null) {
|
||||
T hook = findFirstAvailableHook();
|
||||
if (hook != null) {
|
||||
setActive(hook);
|
||||
this.plugin.getLogger().info("Activated hook '" + hook.getName() + "'");
|
||||
}
|
||||
|
||||
checkDependenciesOfAllHooksAndLogMissingOnes();
|
||||
}
|
||||
return Optional.ofNullable(this.activeHook);
|
||||
}
|
||||
|
||||
public void setActive(@Nullable T hook) {
|
||||
if (this.activeHook == hook) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeHook != null) {
|
||||
this.activeHook.deactivate();
|
||||
}
|
||||
|
||||
this.activeHook = hook;
|
||||
if (this.activeHook != null) {
|
||||
this.activeHook.activate(this.plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T get(String name) {
|
||||
for (T hook : this.hooksWithPriority.keySet()) {
|
||||
if (hook.getName().equalsIgnoreCase(name)) {
|
||||
return hook;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<T> getAll() {
|
||||
// Use List.copyOf() when we upgrade to Java 10+
|
||||
return Collections.unmodifiableList(new ArrayList<>(this.hooksWithPriority.keySet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> getAllNames() {
|
||||
return this.hooksWithPriority
|
||||
.keySet()
|
||||
.stream()
|
||||
.map(Hook::getName)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(@NotNull T hook) {
|
||||
register(hook, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HookPriority
|
||||
*/
|
||||
public void register(@NotNull T hook, int priority) {
|
||||
if (get(hook.getName()) != null) {
|
||||
throw new IllegalArgumentException("Hook with name '" + hook.getName() + "' already registered");
|
||||
}
|
||||
this.hooksWithPriority.put(hook, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NotNull T hook) {
|
||||
if (this.activeHook == hook) {
|
||||
this.activeHook = null;
|
||||
hook.deactivate();
|
||||
}
|
||||
this.hooksWithPriority.remove(hook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.hooksWithPriority.clear();
|
||||
}
|
||||
|
||||
protected @Nullable T findFirstAvailableHook() {
|
||||
return this.hooksWithPriority
|
||||
.entrySet()
|
||||
.stream()
|
||||
.sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue()))
|
||||
.filter((entry) -> entry.getKey().canBeActivated())
|
||||
.map(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected void checkDependenciesOfAllHooksAndLogMissingOnes() {
|
||||
List<String> missingDependencies = new ArrayList<>(0);
|
||||
|
||||
for (T hook : getAll()) {
|
||||
for (String pluginName : hook.getPluginDependencies()) {
|
||||
if (this.plugin.getDescription().getDepend().contains(pluginName)) {
|
||||
continue;
|
||||
}
|
||||
if (this.plugin.getDescription().getSoftDepend().contains(pluginName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
missingDependencies.add(pluginName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!missingDependencies.isEmpty()) {
|
||||
this.plugin.getLogger().warning("Nag author(s): Plugin accesses hooks that it does not declare dependance on: " + String.join(", ", missingDependencies));
|
||||
}
|
||||
}
|
||||
}
|
24
Core/src/main/java/com/craftaro/core/hooks/Hook.java
Normal file
24
Core/src/main/java/com/craftaro/core/hooks/Hook.java
Normal file
@ -0,0 +1,24 @@
|
||||
package com.craftaro.core.hooks;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Hook {
|
||||
String getName();
|
||||
|
||||
@NotNull String[] getPluginDependencies();
|
||||
|
||||
default boolean canBeActivated() {
|
||||
for (String pluginName : getPluginDependencies()) {
|
||||
if (!Bukkit.getPluginManager().isPluginEnabled(pluginName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void activate(Plugin plugin);
|
||||
|
||||
void deactivate();
|
||||
}
|
17
Core/src/main/java/com/craftaro/core/hooks/HookPriority.java
Normal file
17
Core/src/main/java/com/craftaro/core/hooks/HookPriority.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.craftaro.core.hooks;
|
||||
|
||||
/**
|
||||
* Some handy constants for hook priorities intended to be used in
|
||||
* {@link BaseHookRegistry#register(Hook, int)}
|
||||
*/
|
||||
public final class HookPriority {
|
||||
public static final int HIGHEST = 100;
|
||||
public static final int HIGHER = 50;
|
||||
public static final int HIGH = 10;
|
||||
public static final int NORMAL = 0;
|
||||
public static final int LOW = -10;
|
||||
public static final int LOWER = -50;
|
||||
|
||||
private HookPriority() {
|
||||
}
|
||||
}
|
28
Core/src/main/java/com/craftaro/core/hooks/HookRegistry.java
Normal file
28
Core/src/main/java/com/craftaro/core/hooks/HookRegistry.java
Normal file
@ -0,0 +1,28 @@
|
||||
package com.craftaro.core.hooks;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class HookRegistry<T extends Hook> {
|
||||
public abstract Optional<T> getActive();
|
||||
|
||||
public abstract void setActive(@Nullable T hook);
|
||||
|
||||
public abstract @NotNull List<String> getAllNames();
|
||||
|
||||
public abstract void register(@NotNull T hook);
|
||||
|
||||
public abstract void unregister(@NotNull T hook);
|
||||
|
||||
public abstract void clear();
|
||||
|
||||
@ApiStatus.Internal
|
||||
public abstract @Nullable T get(String name);
|
||||
|
||||
@ApiStatus.Internal
|
||||
public abstract @NotNull List<T> getAll();
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.craftaro.core.hooks;
|
||||
|
||||
import com.craftaro.core.hooks.hologram.HologramHook;
|
||||
import com.craftaro.core.hooks.hologram.HologramHookRegistry;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class HookRegistryManager {
|
||||
private final Plugin plugin;
|
||||
|
||||
private HologramHookRegistry hologramRegistry;
|
||||
|
||||
public HookRegistryManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public Optional<HologramHook> holograms() {
|
||||
return getHologramRegistry().getActive();
|
||||
}
|
||||
|
||||
public HologramHookRegistry getHologramRegistry() {
|
||||
if (this.hologramRegistry == null) {
|
||||
this.hologramRegistry = new HologramHookRegistry(this.plugin);
|
||||
this.hologramRegistry.registerDefaultHooks();
|
||||
}
|
||||
|
||||
return this.hologramRegistry;
|
||||
}
|
||||
|
||||
public void deactivateAllActiveHooks() {
|
||||
if (this.hologramRegistry != null) {
|
||||
this.hologramRegistry.setActive(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package com.craftaro.core.hooks.hologram;
|
||||
|
||||
import com.craftaro.core.hooks.Hook;
|
||||
import com.craftaro.core.utils.LocationUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class HologramHook implements Hook {
|
||||
public abstract boolean exists(@NotNull String id);
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if the hologram already exists
|
||||
* @see #createOrUpdateText(String, Location, List)
|
||||
*/
|
||||
public abstract void create(@NotNull String id, @NotNull Location location, @NotNull List<String> lines);
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if the hologram does not exist
|
||||
*/
|
||||
public abstract void update(@NotNull String id, @NotNull List<String> lines);
|
||||
|
||||
public abstract void updateBulk(@NotNull Map<String, List<String>> hologramData);
|
||||
|
||||
public abstract void remove(@NotNull String id);
|
||||
|
||||
public abstract void removeAll();
|
||||
|
||||
/**
|
||||
* @see #create(String, Location, List)
|
||||
*/
|
||||
public void create(@NotNull String id, @NotNull Location location, @NotNull String text) {
|
||||
create(id, location, Collections.singletonList(text));
|
||||
}
|
||||
|
||||
public void createOrUpdateText(@NotNull String id, @NotNull Location location, @NotNull List<String> lines) {
|
||||
if (exists(id)) {
|
||||
update(id, lines);
|
||||
return;
|
||||
}
|
||||
|
||||
create(id, location, lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #createOrUpdateText(String, Location, List)
|
||||
*/
|
||||
public void createOrUpdateText(@NotNull String id, @NotNull Location location, @NotNull String text) {
|
||||
createOrUpdateText(id, location, Collections.singletonList(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #update(String, List)
|
||||
*/
|
||||
public void update(@NotNull String id, @NotNull String text) {
|
||||
update(id, Collections.singletonList(text));
|
||||
}
|
||||
|
||||
protected double getYOffset() {
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
protected @NotNull Location getNormalizedLocation(Location location) {
|
||||
return LocationUtils
|
||||
.getCenter(location)
|
||||
.add(0, getYOffset(), 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.craftaro.core.hooks.hologram;
|
||||
|
||||
import com.craftaro.core.hooks.BaseHookRegistry;
|
||||
import com.craftaro.core.hooks.HookPriority;
|
||||
import com.craftaro.core.hooks.hologram.adapter.DecentHologramsHook;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class HologramHookRegistry extends BaseHookRegistry<HologramHook> {
|
||||
public HologramHookRegistry(Plugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDefaultHooks() {
|
||||
register(new DecentHologramsHook(), HookPriority.HIGH);
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package com.craftaro.core.hooks.hologram.adapter;
|
||||
|
||||
import com.craftaro.core.hooks.hologram.HologramHook;
|
||||
import eu.decentsoftware.holograms.api.DHAPI;
|
||||
import eu.decentsoftware.holograms.api.holograms.Hologram;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DecentHologramsHook extends HologramHook {
|
||||
private static final String DECENT_HOLOGRAMS = "DecentHolograms";
|
||||
|
||||
private final ArrayList<String> ourHologramIds = new ArrayList<>(0);
|
||||
private String hologramNamePrefix;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return DECENT_HOLOGRAMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String[] getPluginDependencies() {
|
||||
return new String[] {DECENT_HOLOGRAMS};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate(Plugin plugin) {
|
||||
this.hologramNamePrefix = plugin.getClass().getName().replace('.', '_') + "-";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
removeAll();
|
||||
this.hologramNamePrefix = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(@NotNull String id) {
|
||||
return DHAPI.getHologram(getHologramName(id)) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(@NotNull String id, @NotNull Location location, @NotNull List<String> lines) {
|
||||
if (exists(id)) {
|
||||
throw new IllegalStateException("Cannot create hologram that already exists: " + getHologramName(id));
|
||||
}
|
||||
|
||||
DHAPI.createHologram(getHologramName(id), getNormalizedLocation(location), lines);
|
||||
this.ourHologramIds.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull String id, @NotNull List<String> lines) {
|
||||
Hologram hologram = DHAPI.getHologram(getHologramName(id));
|
||||
if (hologram == null) {
|
||||
throw new IllegalStateException("Cannot update hologram that does not exist: " + getHologramName(id));
|
||||
}
|
||||
|
||||
DHAPI.setHologramLines(hologram, lines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBulk(@NotNull Map<String, List<String>> hologramData) {
|
||||
for (Map.Entry<String, List<String>> entry : hologramData.entrySet()) {
|
||||
update(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@Nullable String id) {
|
||||
DHAPI.removeHologram(getHologramName(id));
|
||||
this.ourHologramIds.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll() {
|
||||
for (String id : this.ourHologramIds) {
|
||||
DHAPI.removeHologram(getHologramName(id));
|
||||
}
|
||||
this.ourHologramIds.clear();
|
||||
this.ourHologramIds.trimToSize();
|
||||
}
|
||||
|
||||
private String getHologramName(String id) {
|
||||
if (this.hologramNamePrefix == null) {
|
||||
throw new IllegalStateException("Hook has not been activated yet");
|
||||
}
|
||||
return this.hologramNamePrefix + id;
|
||||
}
|
||||
}
|
@ -25,4 +25,17 @@ public class LocationUtils {
|
||||
location.getY() >= y1 && location.getY() <= y2 &&
|
||||
location.getZ() >= z1 && location.getZ() <= z2;
|
||||
}
|
||||
|
||||
public static Location getCenter(Location location) {
|
||||
double xOffset = location.getBlockX() > 0 ? 0.5 : -0.5;
|
||||
double zOffset = location.getBlockZ() > 0 ? 0.5 : -0.5;
|
||||
return new Location(
|
||||
location.getWorld(),
|
||||
location.getBlockX() + xOffset,
|
||||
location.getBlockY(),
|
||||
location.getBlockZ() + zOffset,
|
||||
0,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user