Skip to content

Commit

Permalink
Modularize plugin compatibility as subsystem
Browse files Browse the repository at this point in the history
* Add compatibility for Train Carts
* Other misc for future TODO and consistency
  • Loading branch information
yoshiweegee committed Apr 23, 2024
1 parent c122b60 commit 05a8029
Show file tree
Hide file tree
Showing 13 changed files with 430 additions and 73 deletions.
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pluginManagement {
includeBuild("build-logic")
repositories {
gradlePluginPortal()
maven("https://repo.papermc.io/repository/maven-public/")
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.compatibility;

import com.mojang.logging.LogUtils;
import org.bukkit.Bukkit;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;

public abstract class BasePluginCompatibility {

private final String[] pluginNames;

private final AtomicBoolean reflectionResolutionAttempted = new AtomicBoolean(false);

protected BasePluginCompatibility(String[] pluginNames) {
this.pluginNames = pluginNames;
}

@Nullable
private Map<String, ClassLoader> collectPluginClassLoaders() {
var pluginManager = Bukkit.getPluginManager();
HashMap<java.lang.String, java.lang.ClassLoader> pluginClassLoaders = new HashMap<>(this.pluginNames.length, 1.0f);
for (var pluginName : this.pluginNames) {
var plugin = pluginManager.getPlugin(pluginName);
if (plugin == null) {
return null;
}
pluginClassLoaders.put(pluginName, plugin.getClass().getClassLoader());
}
return pluginClassLoaders;
}

private void onPluginClassesNotFound(ReflectiveOperationException e) {
//TODO: move repeated text to generalized function
//TODO: Get mod name for prefix
LogUtils.getLogger().error(
"[Nitori] FAILED TO LOCATE CLASSES FOR `" + this.getClass().getName() + "`! I'll assume the related plugin(s) will otherwise hang and always run on main. Please update my intel!\n" +
"[Nitori] Exception follows:\n" +
e.toString()
);
}

private void onReferenceUseFailure(CompletionException e) {
//TODO: move repeated text to generalized function
//TODO: Get mod name for prefix
LogUtils.getLogger().error(
"[Nitori] FAILED TO USE REFLECTED REFERENCES FOR `" + this.getClass().getName() + "`! I'll assume the related plugin(s) will otherwise hang and always run on main. Please update my intel!\n" +
"[Nitori] Exception follows:\n" +
e.toString()
);
}

protected abstract void collectReferences(@Nonnull Map<String, ClassLoader> pluginClassLoaders) throws ReflectiveOperationException;

boolean completePluginCompatibilityCondition(BooleanSupplier conditionCallback) {
synchronized (this) { // compatibility classes are singletons so syncing on itself is safe
if (!this.reflectionResolutionAttempted.getAcquire()) {
var thisClassName = this.getClass().getName();
var pluginClassLoaders = collectPluginClassLoaders();
if (pluginClassLoaders == null) {
return false;
}
try {
//TODO: move repeated text to generalized function
//TODO: Get mod name for prefix
LogUtils.getLogger().info("[Nitori] Resolving reflection references for " + thisClassName + ".");
collectReferences(pluginClassLoaders);
//TODO: move repeated text to generalized function
//TODO: Get mod name for prefix
LogUtils.getLogger().info("[Nitori] Resolved reflection references for " + thisClassName + ".");
} catch (ReflectiveOperationException e) {
onPluginClassesNotFound(e);
return true;
} finally {
this.reflectionResolutionAttempted.setRelease(true);
}
}
}
try {
return conditionCallback.getAsBoolean();
} catch (IllegalStateException e) {
//TODO: move to generalized function
//TODO: Get mod name for prefix
LogUtils.getLogger().error("[Nitori] Hey, could you not forget to tell me how to resolve the reflection references??");
throw e;
} catch (CompletionException e) {
onReferenceUseFailure(e);
return true;
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.compatibility;

import net.gensokyoreimagined.nitori.compatibility.reflection.ClassReflectionReferenceResolver;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerPlayer;

import javax.annotation.Nonnull;
import java.util.Map;

public final class PluginCompatibilityCitizens extends BasePluginCompatibility {
private static final String CITIZENS_PLUGIN_NAME = "Citizens";

private static final ClassReflectionReferenceResolver citizensPluginCitizensEntityTrackerClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R3.util.CitizensEntityTracker");

private static final ClassReflectionReferenceResolver citizensPluginEntityHumanNPCClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R3.entity.EntityHumanNPC");

PluginCompatibilityCitizens() {
super(new String[]{CITIZENS_PLUGIN_NAME});
}

@Override
protected void collectReferences(@Nonnull Map<String, ClassLoader> pluginClassLoaders) throws ReflectiveOperationException {
var citizensClassLoader = pluginClassLoaders.get(CITIZENS_PLUGIN_NAME);
citizensPluginCitizensEntityTrackerClassResolver.accept(citizensClassLoader);
citizensPluginEntityHumanNPCClassResolver.accept(citizensClassLoader);
}

public boolean shouldRedirectToMainThread(ChunkMap.TrackedEntity trackedEntity, ServerPlayer serverPlayer) {
return completePluginCompatibilityCondition(
() -> citizensPluginCitizensEntityTrackerClassResolver.getResolution().isInstance(trackedEntity)
&& !citizensPluginEntityHumanNPCClassResolver.getResolution().isInstance(serverPlayer)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.compatibility;

public class PluginCompatibilityRegistry {

public static final PluginCompatibilityCitizens CITIZENS = new PluginCompatibilityCitizens();

public static final PluginCompatibilityTrainCarts TRAIN_CARTS = new PluginCompatibilityTrainCarts();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.compatibility;

import net.gensokyoreimagined.nitori.compatibility.reflection.ClassReflectionReferenceResolver;
import net.gensokyoreimagined.nitori.compatibility.reflection.FieldReflectionReferenceResolver;
import net.gensokyoreimagined.nitori.compatibility.reflection.MethodReflectionReferenceResolver;
import net.minecraft.server.level.ChunkMap;

import javax.annotation.Nonnull;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.CompletionException;

public final class PluginCompatibilityTrainCarts extends BasePluginCompatibility {
private static final String BK_COMMON_LIB_PLUGIN_NAME = "BKCommonLib";
private static final String TRAIN_CARTS_PLUGIN_NAME = "Train_Carts";

private static final FieldReflectionReferenceResolver bkCommonLibPluginEntityTypingHandler_INSTANCEFieldResolver = new FieldReflectionReferenceResolver(
"com.bergerkiller.bukkit.common.internal.logic.EntityTypingHandler",
"INSTANCE"
);

private static final MethodReflectionReferenceResolver bkCommonLibPluginEntityTypingHandler_getEntityTrackerEntryHookMethodResolver = new MethodReflectionReferenceResolver(
"com.bergerkiller.bukkit.common.internal.logic.EntityTypingHandler",
"getEntityTrackerEntryHook",
Object.class
);

private static final MethodReflectionReferenceResolver bkCommonLibPluginEntityTrackerEntryHook_getControllerMethodResolver = new MethodReflectionReferenceResolver(
"com.bergerkiller.bukkit.common.internal.hooks.EntityTrackerEntryHook",
"getController"
);

private static final ClassReflectionReferenceResolver trainCartsMinecartMemberNetworkClassResolver = new ClassReflectionReferenceResolver("com.bergerkiller.bukkit.tc.controller.MinecartMemberNetwork");

PluginCompatibilityTrainCarts() {
super(new String[]{BK_COMMON_LIB_PLUGIN_NAME, TRAIN_CARTS_PLUGIN_NAME});
}

@Override
protected void collectReferences(@Nonnull Map<String, ClassLoader> pluginClassLoaders) throws ReflectiveOperationException {
var bkCommonLibClassLoader = pluginClassLoaders.get(BK_COMMON_LIB_PLUGIN_NAME);
var trainCartsClassLoader = pluginClassLoaders.get(TRAIN_CARTS_PLUGIN_NAME);
bkCommonLibPluginEntityTypingHandler_INSTANCEFieldResolver.accept(bkCommonLibClassLoader);
bkCommonLibPluginEntityTypingHandler_getEntityTrackerEntryHookMethodResolver.accept(bkCommonLibClassLoader);
bkCommonLibPluginEntityTrackerEntryHook_getControllerMethodResolver.accept(bkCommonLibClassLoader);
trainCartsMinecartMemberNetworkClassResolver.accept(trainCartsClassLoader);
}

public boolean shouldRedirectToMainThread(ChunkMap.TrackedEntity trackedEntity) {
return completePluginCompatibilityCondition(
() -> {
try {
/*com.bergerkiller.bukkit.common.internal.logic.EntityTypingHandler*/var bkCommonLibEntityTypingHandlerInstance = bkCommonLibPluginEntityTypingHandler_INSTANCEFieldResolver.getResolution().get(null);
/*com.bergerkiller.bukkit.common.internal.hooks.EntityTrackerEntryHook*/var serverEntityBkCommonLibEntityTrackerEntryHook = bkCommonLibPluginEntityTypingHandler_getEntityTrackerEntryHookMethodResolver.getResolution().invoke(bkCommonLibEntityTypingHandlerInstance, trackedEntity);
if (serverEntityBkCommonLibEntityTrackerEntryHook == null) {
return false;
}
/*com.bergerkiller.bukkit.common.controller.EntityNetworkController*/var serverEntityBkCommonLibUncastedNetworkController = bkCommonLibPluginEntityTrackerEntryHook_getControllerMethodResolver.getResolution().invoke(serverEntityBkCommonLibEntityTrackerEntryHook);
return trainCartsMinecartMemberNetworkClassResolver.getResolution().isInstance(serverEntityBkCommonLibUncastedNetworkController);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new CompletionException(e);
}
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.compatibility.reflection;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public abstract class BaseReflectionReferenceResolver<T> {
@Nullable
private T resolution;

@Nonnull
protected abstract T resolve(ClassLoader classLoader) throws ReflectiveOperationException;

public void accept(ClassLoader classLoader) throws ReflectiveOperationException {
this.resolution = resolve(classLoader);
}

@Nonnull
public T getResolution() {
if (this.resolution == null) {
throw new IllegalStateException("Reflection reference has not been resolved!");
}
return this.resolution;
}

protected static Class<?> resolveClass(ClassLoader classLoader, String classPath) throws ClassNotFoundException {
return Class.forName(classPath, false, classLoader);
}
}
Loading

0 comments on commit 05a8029

Please sign in to comment.