Skip to content

Commit

Permalink
Introduce a new hook system (right now only with Hologram support)
Browse files Browse the repository at this point in the history
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, ...).
  • Loading branch information
SpraxDev committed Jan 30, 2024
1 parent d8564d1 commit dfa04e7
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Core/src/main/java/com/craftaro/core/SongodaPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -221,6 +224,8 @@ public final void onDisable() {
} catch (Exception ignored) {
}

this.hookRegistryManager.deactivateAllActiveHooks();

console.sendMessage(ChatColor.GREEN + "=============================");
console.sendMessage(" "); // blank line to separate chatter
}
Expand Down Expand Up @@ -357,4 +362,8 @@ public void setDataManager(DataManager dataManager) {
}
this.dataManager = dataManager;
}

public HookRegistryManager getHookManager() {
return this.hookRegistryManager;
}
}
145 changes: 145 additions & 0 deletions Core/src/main/java/com/craftaro/core/hooks/BaseHookRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package com.craftaro.core.hooks;

import com.craftaro.core.hooks.holograms.Hook;
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));
}
}
}
17 changes: 17 additions & 0 deletions Core/src/main/java/com/craftaro/core/hooks/HookPriority.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.craftaro.core.hooks;

/**
* Some handy constants for hook priorities intended to be used in
* {@link BaseHookRegistry#register(com.craftaro.core.hooks.holograms.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() {
}
}
29 changes: 29 additions & 0 deletions Core/src/main/java/com/craftaro/core/hooks/HookRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.craftaro.core.hooks;

import com.craftaro.core.hooks.holograms.Hook;
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();
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.craftaro.core.hooks.hologram;

import com.craftaro.core.hooks.holograms.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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit dfa04e7

Please sign in to comment.