-
-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'staging' into rfb-support
- Loading branch information
Showing
5 changed files
with
282 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
patches/cpw/mods/fml/common/eventhandler/ASMEventHandler.java.patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- ../src-base/minecraft/cpw/mods/fml/common/eventhandler/ASMEventHandler.java | ||
+++ ../src-work/minecraft/cpw/mods/fml/common/eventhandler/ASMEventHandler.java | ||
@@ -36,6 +36,13 @@ | ||
readable = "ASM: " + target + " " + method.getName() + Type.getMethodDescriptor(method); | ||
} | ||
|
||
+ public ASMEventHandler(IEventListener handler, Object target, Method method, ModContainer owner, String readable) { | ||
+ this.handler = handler; | ||
+ this.subInfo = method.getAnnotation(SubscribeEvent.class); | ||
+ this.owner = owner; | ||
+ this.readable = readable; | ||
+ } | ||
+ | ||
@Override | ||
public void invoke(Event event) | ||
{ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
src/main/java/io/github/crucible/api/CrucibleEventBus.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package io.github.crucible.api; | ||
|
||
import cpw.mods.fml.common.FMLCommonHandler; | ||
import cpw.mods.fml.common.eventhandler.*; | ||
import io.github.crucible.CrucibleModContainer; | ||
import io.github.crucible.eventfactory.PluginClassLoaderFactory; | ||
import net.minecraftforge.common.MinecraftForge; | ||
import org.bukkit.plugin.Plugin; | ||
import org.objectweb.asm.Type; | ||
|
||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
public class CrucibleEventBus { | ||
|
||
/** | ||
* Register an object to the specific forge event bus! | ||
* | ||
* | ||
* @param plugin The plugin that is registering the event, can be 'null' | ||
* | ||
* @param bus The event bus to register to: | ||
* - You can use {@link MinecraftForge#EVENT_BUS} | ||
* - You can use {@link FMLCommonHandler#bus()} | ||
* - Any other bus. | ||
* | ||
* @param target Either a {@link Class} or an arbitrary object. | ||
* The object maybe have methods annotated with {@link SubscribeEvent} | ||
* The class maybe have static methods annotated with {@link SubscribeEvent} | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
public static void register(Plugin plugin, EventBus bus, Object target) { | ||
ConcurrentHashMap<Object, ?> listeners = bus.getListeners(); | ||
if (!listeners.containsKey(target)) { | ||
if (target.getClass() == Class.class) { | ||
registerClass(plugin, (Class<?>) target, bus); | ||
} else { | ||
registerObject(plugin, target, bus); | ||
} | ||
} | ||
} | ||
|
||
private static void registerClass(Plugin plugin, final Class<?> clazz, EventBus bus) { | ||
Arrays.stream(clazz.getMethods()). | ||
filter(m -> Modifier.isStatic(m.getModifiers())). | ||
filter(m -> m.isAnnotationPresent(SubscribeEvent.class)). | ||
forEach(m -> registerListener(plugin, clazz, m, m, bus)); | ||
} | ||
|
||
private static void registerObject(Plugin plugin, final Object obj, EventBus bus) { | ||
final HashSet<Class<?>> classes = new HashSet<>(); | ||
typesFor(obj.getClass(), classes); | ||
Arrays.stream(obj.getClass().getMethods()). | ||
filter(m -> !Modifier.isStatic(m.getModifiers())). | ||
forEach(m -> classes.stream(). | ||
map(c -> getDeclMethod(c, m)). | ||
filter(rm -> rm.isPresent() && rm.get().isAnnotationPresent(SubscribeEvent.class)). | ||
findFirst(). | ||
ifPresent(rm -> registerListener(plugin, obj, m, rm.get(), bus))); | ||
} | ||
|
||
private static void registerListener(Plugin plugin, final Object target, final Method method, final Method real, EventBus bus) { | ||
Class<?>[] parameterTypes = method.getParameterTypes(); | ||
if (parameterTypes.length != 1) { | ||
throw new IllegalArgumentException( | ||
"Method " + method + " has @SubscribeEvent annotation. " + | ||
"It has " + parameterTypes.length + " arguments, " + | ||
"but event handler methods require a single argument only." | ||
); | ||
} | ||
|
||
Class<?> eventType = parameterTypes[0]; | ||
|
||
if (!Event.class.isAssignableFrom(eventType)) { | ||
throw new IllegalArgumentException( | ||
"Method " + method + " has @SubscribeEvent annotation, " + | ||
"but takes an argument that is not an Event subtype : " + eventType); | ||
} | ||
|
||
register(plugin, eventType, target, real, bus); | ||
} | ||
|
||
private static void register(Plugin plugin, Class<?> eventType, Object target, Method method, EventBus bus) { | ||
String pluginName = (plugin != null ? plugin.getName() : "null"); | ||
try { | ||
PluginClassLoaderFactory factory = new PluginClassLoaderFactory(); | ||
IEventListener iEventListener = factory.create(method, target); | ||
String readable = "ASM_CRUCIBLE: (Plugin='" + pluginName + "') " + target + " " + method.getName() + Type.getMethodDescriptor(method); | ||
ASMEventHandler asm = new ASMEventHandler(iEventListener, target, method, CrucibleModContainer.instance, readable); | ||
bus.crucible_register(asm, eventType, target, method, CrucibleModContainer.instance); | ||
} catch (Throwable e) { | ||
CrucibleModContainer.logger.error("Error registering event handler for the plugin ({}): {} {}", pluginName, eventType, method, e); | ||
} | ||
} | ||
|
||
// ----------------------------------------------------------------------------------------------------------------- | ||
// The following methods are utilities that come from isnide the EventBus on modern forge. | ||
// ----------------------------------------------------------------------------------------------------------------- | ||
|
||
private static void typesFor(final Class<?> clz, final Set<Class<?>> visited) { | ||
if (clz.getSuperclass() == null) return; | ||
typesFor(clz.getSuperclass(),visited); | ||
Arrays.stream(clz.getInterfaces()).forEach(i->typesFor(i, visited)); | ||
visited.add(clz); | ||
} | ||
|
||
private static Optional<Method> getDeclMethod(final Class<?> clz, final Method in) { | ||
try { | ||
return Optional.of(clz.getDeclaredMethod(in.getName(), in.getParameterTypes())); | ||
} catch (NoSuchMethodException nse) { | ||
return Optional.empty(); | ||
} | ||
} | ||
} |
113 changes: 113 additions & 0 deletions
113
src/main/java/io/github/crucible/eventfactory/PluginClassLoaderFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package io.github.crucible.eventfactory; | ||
|
||
import com.google.common.collect.Maps; | ||
import cpw.mods.fml.common.eventhandler.Event; | ||
import cpw.mods.fml.common.eventhandler.IEventListener; | ||
import org.objectweb.asm.ClassWriter; | ||
import org.objectweb.asm.MethodVisitor; | ||
import org.objectweb.asm.Type; | ||
|
||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.security.ProtectionDomain; | ||
import java.util.HashMap; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
import static org.objectweb.asm.Opcodes.*; | ||
|
||
public class PluginClassLoaderFactory { | ||
private static AtomicInteger IDs = new AtomicInteger(); | ||
private static final HashMap<Method, Class<?>> cache = Maps.newHashMap(); | ||
private static final String HANDLER_DESC = Type.getInternalName(IEventListener.class); | ||
private static final String HANDLER_FUNC_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Event.class)); | ||
|
||
public IEventListener create(Method method, Object target) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { | ||
Class<?> cls = createWrapper(method); | ||
if (Modifier.isStatic(method.getModifiers())) | ||
return (IEventListener)cls.getDeclaredConstructor().newInstance(); | ||
else | ||
return (IEventListener)cls.getConstructor(Object.class).newInstance(target); | ||
} | ||
|
||
public Class<?> createWrapper(Method callback) { | ||
if (cache.containsKey(callback)) { | ||
return cache.get(callback); | ||
} | ||
|
||
ClassWriter cw = new ClassWriter(0); | ||
MethodVisitor mv; | ||
|
||
boolean isStatic = Modifier.isStatic(callback.getModifiers()); | ||
String name = getUniqueName(callback); | ||
String desc = name.replace('.', '/'); | ||
String instType = Type.getInternalName(callback.getDeclaringClass()); | ||
String eventType = Type.getInternalName(callback.getParameterTypes()[0]); | ||
|
||
cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, desc, null, "java/lang/Object", new String[]{HANDLER_DESC}); | ||
|
||
cw.visitSource(".dynamic", null); | ||
{ | ||
if (!isStatic) | ||
cw.visitField(ACC_PUBLIC, "instance", "Ljava/lang/Object;", null, null).visitEnd(); | ||
} | ||
{ | ||
mv = cw.visitMethod(ACC_PUBLIC, "<init>", isStatic ? "()V" : "(Ljava/lang/Object;)V", null, null); | ||
mv.visitCode(); | ||
mv.visitVarInsn(ALOAD, 0); | ||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); | ||
if (!isStatic) { | ||
mv.visitVarInsn(ALOAD, 0); | ||
mv.visitVarInsn(ALOAD, 1); | ||
mv.visitFieldInsn(PUTFIELD, desc, "instance", "Ljava/lang/Object;"); | ||
} | ||
mv.visitInsn(RETURN); | ||
mv.visitMaxs(2, 2); | ||
mv.visitEnd(); | ||
} | ||
{ | ||
mv = cw.visitMethod(ACC_PUBLIC, "invoke", HANDLER_FUNC_DESC, null, null); | ||
mv.visitCode(); | ||
mv.visitVarInsn(ALOAD, 0); | ||
if (!isStatic) { | ||
mv.visitFieldInsn(GETFIELD, desc, "instance", "Ljava/lang/Object;"); | ||
mv.visitTypeInsn(CHECKCAST, instType); | ||
} | ||
mv.visitVarInsn(ALOAD, 1); | ||
mv.visitTypeInsn(CHECKCAST, eventType); | ||
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, instType, callback.getName(), Type.getMethodDescriptor(callback), false); | ||
mv.visitInsn(RETURN); | ||
mv.visitMaxs(2, 2); | ||
mv.visitEnd(); | ||
} | ||
cw.visitEnd(); | ||
byte[] bytes = cw.toByteArray(); | ||
|
||
PluginASMClassLoader LOADER = new PluginASMClassLoader(callback.getDeclaringClass().getClassLoader(), callback.getDeclaringClass().getProtectionDomain()); | ||
Class<?> ret = LOADER.define(name, bytes); | ||
cache.put(callback, ret); | ||
return ret; | ||
} | ||
|
||
public String getUniqueName(Method callback) { | ||
return String.format("%s_%d_%s_%s_%s", | ||
getClass().getName(), | ||
IDs.incrementAndGet(), | ||
callback.getDeclaringClass().getSimpleName(), | ||
callback.getName(), | ||
callback.getParameterTypes()[0].getSimpleName()); | ||
} | ||
|
||
private static class PluginASMClassLoader extends ClassLoader { | ||
private final ProtectionDomain protectionDomain; | ||
|
||
private PluginASMClassLoader(ClassLoader classLoader, ProtectionDomain protectionDomain) { | ||
super(classLoader); | ||
this.protectionDomain = protectionDomain; | ||
} | ||
|
||
public Class<?> define(String name, byte[] data) { | ||
return defineClass(name, data, 0, data.length, protectionDomain); | ||
} | ||
} | ||
} |