add extensions, add event system including asm crimes
This commit is contained in:
parent
577386681d
commit
f2cf3b501c
|
@ -2,30 +2,46 @@ import dev.frogmc.phytotelma.ext.minecraft
|
||||||
import dev.frogmc.phytotelma.ext.loader
|
import dev.frogmc.phytotelma.ext.loader
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("java")
|
java
|
||||||
id("dev.frogmc.phytotelma").version("0.0.1-SNAPSHOT")
|
id("dev.frogmc.phytotelma") version "0.0.1-SNAPSHOT"
|
||||||
|
id("io.freefair.lombok") version "8.+"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.frogmc"
|
group = "dev.frogmc"
|
||||||
version = "0.0.1-SNAPSHOT"
|
version = "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
|
||||||
name = "FrogMC Maven Releases"
|
|
||||||
url = uri("https://maven.frogmc.dev/releases")
|
|
||||||
}
|
|
||||||
maven {
|
|
||||||
name = "FrogMC Maven Snapshots"
|
|
||||||
url = uri("https://maven.frogmc.dev/snapshots")
|
|
||||||
}
|
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
minecraft(libs.versions.minecraft).loader(libs.versions.frogloader)
|
ext.set("mcVer", libs.versions.minecraft)
|
||||||
|
ext.set("loaderVer", libs.versions.frogloader)
|
||||||
|
|
||||||
|
minecraft(project.libs.versions.minecraft).loader(project.libs.versions.frogloader)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
|
||||||
java {
|
}
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun setUpLibrary(project: Project) {
|
||||||
|
with(project) {
|
||||||
|
println("Configuring: ${project.name}")
|
||||||
|
apply(plugin = "dev.frogmc.phytotelma")
|
||||||
|
|
||||||
|
group = "dev.frogmc"
|
||||||
|
version = "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
minecraft(rootProject.ext.get("mcVer") as Provider<String>).loader(rootProject.ext.get("loaderVer") as Provider<String>)
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,3 @@
|
||||||
|
|
||||||
minecraft = "1.20.6"
|
minecraft = "1.20.6"
|
||||||
frogloader = "0.0.1-SNAPSHOT"
|
frogloader = "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
[libraries]
|
|
||||||
|
|
|
@ -14,4 +14,3 @@ pluginManagement {
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "froglib"
|
rootProject.name = "froglib"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.frogmc.froglib.entrypoints;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
|
||||||
|
public interface ClientExtension {
|
||||||
|
|
||||||
|
void onClientInit(ModProperties mod);
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.frogmc.froglib.entrypoints;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.FrogLoader;
|
||||||
|
|
||||||
|
public class ExtensionLauncher {
|
||||||
|
|
||||||
|
public static void invokeClient() {
|
||||||
|
System.out.println("Starting client extension");
|
||||||
|
FrogLoader.getInstance().getMods().forEach(mod -> {
|
||||||
|
mod.extensions().runIfPresent("client", ClientExtension.class, ext -> ext.onClientInit(mod));
|
||||||
|
mod.extensions().runIfPresent("init", MainExtension.class, ext -> ext.onInit(mod));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void invokeServer() {
|
||||||
|
System.out.println("Starting server extension");
|
||||||
|
FrogLoader.getInstance().getMods().forEach(mod -> {
|
||||||
|
mod.extensions().runIfPresent("server", ServerExtension.class, ext -> ext.onServerInit(mod));
|
||||||
|
mod.extensions().runIfPresent("init", MainExtension.class, ext -> ext.onInit(mod));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.frogmc.froglib.entrypoints;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
|
||||||
|
public interface MainExtension {
|
||||||
|
|
||||||
|
void onInit(ModProperties mod);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.frogmc.froglib.entrypoints;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
|
||||||
|
public interface ServerExtension {
|
||||||
|
|
||||||
|
void onServerInit(ModProperties mod);
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package dev.frogmc.froglib.entrypoints.mixin.client;
|
||||||
|
|
||||||
|
import dev.frogmc.froglib.entrypoints.ExtensionLauncher;
|
||||||
|
import net.minecraft.client.main.Main;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Main.class)
|
||||||
|
public class MainMixin {
|
||||||
|
|
||||||
|
@Inject(method = "main", at = @At(value = "INVOKE", target = "Ljava/lang/Thread;setName(Ljava/lang/String;)V"))
|
||||||
|
private static void initializeClient(String[] args, CallbackInfo ci) {
|
||||||
|
ExtensionLauncher.invokeClient();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package dev.frogmc.froglib.entrypoints.mixin.server;
|
||||||
|
|
||||||
|
import dev.frogmc.froglib.entrypoints.ExtensionLauncher;
|
||||||
|
import net.minecraft.server.Main;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Main.class)
|
||||||
|
public class MinecraftServerMixin {
|
||||||
|
|
||||||
|
@Inject(method = "main", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;spin(Ljava/util/function/Function;)Lnet/minecraft/server/MinecraftServer;"))
|
||||||
|
private static void initializeServer(String[] args, CallbackInfo ci) {
|
||||||
|
ExtensionLauncher.invokeServer();
|
||||||
|
}
|
||||||
|
}
|
25
src/main/java/dev/frogmc/froglib/events/api/Event.java
Normal file
25
src/main/java/dev/frogmc/froglib/events/api/Event.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package dev.frogmc.froglib.events.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import dev.frogmc.froglib.events.impl.EventImpl;
|
||||||
|
|
||||||
|
public interface Event<T> {
|
||||||
|
|
||||||
|
static <T> Event<T> of(T listener){
|
||||||
|
return EventImpl.of(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Event<T> of(Class<T> clazz){
|
||||||
|
return EventImpl.of(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Event<T> of(Function<List<T>, T> invokerFactory){
|
||||||
|
return EventImpl.of(invokerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void register(T listener);
|
||||||
|
|
||||||
|
T invoker();
|
||||||
|
}
|
9
src/main/java/dev/frogmc/froglib/events/api/Events.java
Normal file
9
src/main/java/dev/frogmc/froglib/events/api/Events.java
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package dev.frogmc.froglib.events.api;
|
||||||
|
|
||||||
|
import dev.frogmc.froglib.events.api.types.ClientStartupEvent;
|
||||||
|
import dev.frogmc.froglib.events.api.types.ServerStartupEvent;
|
||||||
|
|
||||||
|
public class Events {
|
||||||
|
public static final Event<ClientStartupEvent> CLIENT_STARTUP = Event.of(ClientStartupEvent.class);
|
||||||
|
public static final Event<ServerStartupEvent> SERVER_STARTUP = Event.of(ServerStartupEvent.class);
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package dev.frogmc.froglib.events.api.types;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.main.GameConfig;
|
||||||
|
|
||||||
|
public interface ClientStartupEvent {
|
||||||
|
|
||||||
|
void onClientInit(Minecraft client, GameConfig config);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.frogmc.froglib.events.api.types;
|
||||||
|
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
|
||||||
|
public interface ServerStartupEvent {
|
||||||
|
void onServerInit(DedicatedServer server);
|
||||||
|
}
|
230
src/main/java/dev/frogmc/froglib/events/impl/EventGenerator.java
Normal file
230
src/main/java/dev/frogmc/froglib/events/impl/EventGenerator.java
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
package dev.frogmc.froglib.events.impl;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.impl.launch.MixinClassLoader;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.objectweb.asm.*;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class EventGenerator {
|
||||||
|
|
||||||
|
private static final MethodHandle defineClass;
|
||||||
|
private static final Map<String, Integer> classNameMap = new HashMap<>();
|
||||||
|
private static final boolean DEBUG_EXPORT;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
defineClass = MethodHandles.privateLookupIn(MixinClassLoader.class, MethodHandles.lookup()).findVirtual(MixinClassLoader.class, "defineClass", MethodType.methodType(Class.class, String.class, byte[].class, int.class, int.class));
|
||||||
|
DEBUG_EXPORT = Boolean.getBoolean("froglib.events.export_invokers");
|
||||||
|
if (DEBUG_EXPORT){
|
||||||
|
Files.deleteIfExists(Paths.get(".frogmc/generated"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodHandle generateInvoker(Class<?> handlerClass) throws Throwable {
|
||||||
|
|
||||||
|
Method functionalMethod = null;
|
||||||
|
|
||||||
|
for (Method m : handlerClass.getMethods()) {
|
||||||
|
int modifier = m.getModifiers();
|
||||||
|
if (Modifier.isPublic(modifier) && Modifier.isAbstract(modifier) && m.getReturnType() == void.class) {
|
||||||
|
if (functionalMethod != null) {
|
||||||
|
throw new IllegalArgumentException("Not a functional interface");
|
||||||
|
}
|
||||||
|
functionalMethod = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (functionalMethod == null) {
|
||||||
|
throw new IllegalArgumentException("Not a functional interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
String pkg = "dev/frogmc/froglib/events/generated/";
|
||||||
|
String name = pkg + "FrogEventInvoker$" + Math.abs(functionalMethod.hashCode())+"$"+classNameMap.compute(handlerClass.getName(), (s, i) -> i == null ? 1 : i+1);
|
||||||
|
ClassNode node = new ClassNode();
|
||||||
|
node.visit(Opcodes.V21, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, name, null, "java/lang/Object", null);
|
||||||
|
|
||||||
|
MethodType functional = MethodHandles.lookup().unreflect(functionalMethod).type();
|
||||||
|
String funcDesc = functional.descriptorString().substring(functional.descriptorString().indexOf(';') + 1, functional.descriptorString().length() - 2);
|
||||||
|
MethodVisitor methodVisitor;
|
||||||
|
String type = "L" + handlerClass.getName().replace(".", "/") + ";";
|
||||||
|
Class<?>[] parameterArray = functional.parameterArray();
|
||||||
|
{
|
||||||
|
methodVisitor = node.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "invoker",
|
||||||
|
"(Ljava/util/List;)" + type,
|
||||||
|
"(Ljava/util/List<" + type + ">;)" + type,
|
||||||
|
null);
|
||||||
|
methodVisitor.visitCode();
|
||||||
|
Label label0 = new Label();
|
||||||
|
methodVisitor.visitLabel(label0);
|
||||||
|
methodVisitor.visitLineNumber(2, label0);
|
||||||
|
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
methodVisitor.visitInvokeDynamicInsn(functionalMethod.getName(), "(Ljava/util/List;)" + type,
|
||||||
|
new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
|
||||||
|
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;" +
|
||||||
|
"Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
|
||||||
|
false), Type.getType("(" + funcDesc + ")V"),
|
||||||
|
new Handle(Opcodes.H_INVOKESTATIC, name, "lambda$invoker$1",
|
||||||
|
"(Ljava/util/List;" + funcDesc + ")V", false),
|
||||||
|
Type.getType("(" + funcDesc + ")V"));
|
||||||
|
methodVisitor.visitInsn(Opcodes.ARETURN);
|
||||||
|
Label label1 = new Label();
|
||||||
|
methodVisitor.visitLabel(label1);
|
||||||
|
methodVisitor.visitLocalVariable("list", "Ljava/util/List;",
|
||||||
|
"Ljava/util/List<" + type + ">;", label0, label1, 0);
|
||||||
|
methodVisitor.visitMaxs(1, 1);
|
||||||
|
methodVisitor.visitEnd();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
methodVisitor = node.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
|
||||||
|
"lambda$invoker$1", "(Ljava/util/List;" + funcDesc + ")V", null, null);
|
||||||
|
methodVisitor.visitCode();
|
||||||
|
Label label0 = new Label();
|
||||||
|
methodVisitor.visitLabel(label0);
|
||||||
|
methodVisitor.visitLineNumber(2, label0);
|
||||||
|
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
for (int i = 0; i < parameterArray.length - 1; i++) {
|
||||||
|
methodVisitor.visitVarInsn(Opcodes.ALOAD, i + 1);
|
||||||
|
}
|
||||||
|
methodVisitor.visitInvokeDynamicInsn("accept", "(" + funcDesc + ")Ljava/util/function/Consumer;",
|
||||||
|
new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
|
||||||
|
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;" +
|
||||||
|
"Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
|
||||||
|
false), Type.getType("(Ljava/lang/Object;)V"),
|
||||||
|
new Handle(Opcodes.H_INVOKESTATIC, name, "lambda$invoker$0",
|
||||||
|
"(" + funcDesc + type + ")V", false),
|
||||||
|
Type.getType("(" + type + ")V"));
|
||||||
|
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "forEach", "(Ljava/util/function/Consumer;)V", true);
|
||||||
|
methodVisitor.visitInsn(Opcodes.RETURN);
|
||||||
|
Label label1 = new Label();
|
||||||
|
methodVisitor.visitLabel(label1);
|
||||||
|
methodVisitor.visitLocalVariable("list", "Ljava/util/List;", null, label0, label1, 0);
|
||||||
|
Set<String> usedParamNames = new HashSet<>();
|
||||||
|
usedParamNames.add("list");
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < parameterArray.length; i++) {
|
||||||
|
Class<?> param = parameterArray[i];
|
||||||
|
methodVisitor.visitLocalVariable(getParamName(param, usedParamNames), param.descriptorString(), null, label0, label1, i);
|
||||||
|
}
|
||||||
|
methodVisitor.visitMaxs(i + 1, i + 1);
|
||||||
|
methodVisitor.visitEnd();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
methodVisitor = node.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
|
||||||
|
"lambda$invoker$0", "(" + funcDesc + type + ")V", null, null);
|
||||||
|
methodVisitor.visitCode();
|
||||||
|
Label label0 = new Label();
|
||||||
|
methodVisitor.visitLabel(label0);
|
||||||
|
methodVisitor.visitLineNumber(2, label0);
|
||||||
|
methodVisitor.visitVarInsn(Opcodes.ALOAD, parameterArray.length - 1);
|
||||||
|
for (int i = 0; i < parameterArray.length - 1; i++) {
|
||||||
|
methodVisitor.visitVarInsn(Opcodes.ALOAD, i);
|
||||||
|
}
|
||||||
|
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, handlerClass.getName().replace(".", "/"), functionalMethod.getName(),
|
||||||
|
"(" + funcDesc + ")V", true);
|
||||||
|
methodVisitor.visitInsn(Opcodes.RETURN);
|
||||||
|
Label label1 = new Label();
|
||||||
|
methodVisitor.visitLabel(label1);
|
||||||
|
Set<String> usedLocalNames = new HashSet<>();
|
||||||
|
usedLocalNames.add("r");
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < parameterArray.length - 1; i++) {
|
||||||
|
Class<?> param = parameterArray[i + 1];
|
||||||
|
methodVisitor.visitLocalVariable(getParamName(param, usedLocalNames), param.descriptorString(), null, label0, label1, i);
|
||||||
|
}
|
||||||
|
methodVisitor.visitLocalVariable("r", type, null, label0, label1, i);
|
||||||
|
methodVisitor.visitMaxs(i + 1, i + 1);
|
||||||
|
methodVisitor.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
node.visitEnd();
|
||||||
|
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||||
|
node.accept(writer);
|
||||||
|
|
||||||
|
byte[] bytes = writer.toByteArray();
|
||||||
|
if (DEBUG_EXPORT) {
|
||||||
|
Path file = Paths.get(".frogmc/generated/" + name + ".class");
|
||||||
|
Files.createDirectories(file.getParent());
|
||||||
|
Files.write(file, bytes);
|
||||||
|
}
|
||||||
|
Class<?> generated = defineClass(name.replace("/", "."), bytes);
|
||||||
|
|
||||||
|
return MethodHandles.lookup().findStatic(generated, "invoker", MethodType.methodType(handlerClass, List.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> Class<T> defineClass(String name, byte[] bytes) {
|
||||||
|
try {
|
||||||
|
return (Class<T>) defineClass.invoke(EventGenerator.class.getClassLoader(), name, bytes, 0, bytes.length);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.warn("Failed to generate class:", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getParamName(Class<?> cls, Set<String> usedLocalNames) {
|
||||||
|
String newName = convertDescriptor(cls.descriptorString());
|
||||||
|
int count = 1;
|
||||||
|
String nameCopy = newName;
|
||||||
|
|
||||||
|
while (usedLocalNames.contains(newName)) {
|
||||||
|
newName = nameCopy + ++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedLocalNames.add(newName);
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertDescriptor(String descriptor) {
|
||||||
|
return switch (descriptor.charAt(0)) {
|
||||||
|
case 'B' -> "b";
|
||||||
|
case 'C' -> "c";
|
||||||
|
case 'D' -> "d";
|
||||||
|
case 'F' -> "f";
|
||||||
|
case 'I' -> "i";
|
||||||
|
case 'J' -> "l";
|
||||||
|
case 'S' -> "s";
|
||||||
|
case 'Z' -> "bl";
|
||||||
|
case '[' -> {
|
||||||
|
Type type = Type.getType(descriptor).getElementType();
|
||||||
|
yield type.getSort() == 10 ? convertDescriptor(type.getInternalName()) + "s" : type.getClassName() + "s";
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
String name = cleanType(descriptor);
|
||||||
|
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||||
|
yield name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String cleanType(String type) {
|
||||||
|
String cleaned = type;
|
||||||
|
if (type.indexOf(60) != -1) {
|
||||||
|
cleaned = type.substring(0, type.indexOf(60));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleaned.indexOf(47) != -1) {
|
||||||
|
cleaned = cleaned.substring(cleaned.lastIndexOf(47) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String var3 = cleaned.replaceAll("\\$\\d+;", "");
|
||||||
|
if (var3.indexOf(36) != -1) {
|
||||||
|
var3 = var3.substring(var3.lastIndexOf(36) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return var3.replaceAll("\\[]", "").replaceAll("(.*)\\d", "$1").replace(";", "");
|
||||||
|
}
|
||||||
|
}
|
72
src/main/java/dev/frogmc/froglib/events/impl/EventImpl.java
Normal file
72
src/main/java/dev/frogmc/froglib/events/impl/EventImpl.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package dev.frogmc.froglib.events.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import dev.frogmc.froglib.events.api.Event;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class EventImpl<T> implements Event<T> {
|
||||||
|
|
||||||
|
private final List<T> listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private final Function<List<T>, T> invokerFactory;
|
||||||
|
private T cachedInvoker;
|
||||||
|
|
||||||
|
private EventImpl(Function<List<T>, T> invokerFactory) {
|
||||||
|
this.invokerFactory = invokerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <B> EventImpl<B> of(Function<List<B>, B> invokerFactory){
|
||||||
|
return new EventImpl<>(invokerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <B> EventImpl<B> of(Class<B> clazz) {
|
||||||
|
return of(list -> generateInvoker(list, clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <B> EventImpl<B> of (B listener) {
|
||||||
|
EventImpl<B> event = of((Class<B>) listener.getClass().getInterfaces()[0]);
|
||||||
|
event.register(listener);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor is only allowed if you can be sure that there is <strong>always</strong>
|
||||||
|
* at least one listener to this event (before the invoker method is called)
|
||||||
|
* @return a newly created Event
|
||||||
|
* @param <B> the type of this event
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <B> EventImpl<B> of(){
|
||||||
|
return of(list -> generateInvoker(list, (Class<B>) list.getFirst().getClass().getInterfaces()[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EventImpl<Runnable> simple() {
|
||||||
|
return of(l -> () -> l.forEach(Runnable::run));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <B> B generateInvoker(List<B> list, Class<B> clazz) {
|
||||||
|
try {
|
||||||
|
return (B) EventGenerator.generateInvoker(clazz).invoke(list);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(T listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T invoker() {
|
||||||
|
return cachedInvoker == null ? cachedInvoker = invokerFactory.apply(listeners) : cachedInvoker;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package dev.frogmc.froglib.events.impl.mixin.client;
|
||||||
|
|
||||||
|
import dev.frogmc.froglib.events.api.Events;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.main.GameConfig;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
|
public class MinecraftMixin {
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("TAIL"))
|
||||||
|
private void postClientInit(GameConfig gameConfig, CallbackInfo ci) {
|
||||||
|
Events.CLIENT_STARTUP.invoker().onClientInit((Minecraft) (Object) this, gameConfig);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package dev.frogmc.froglib.events.impl.mixin.server;
|
||||||
|
|
||||||
|
import dev.frogmc.froglib.events.api.Events;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(DedicatedServer.class)
|
||||||
|
public class DedicatedServerMixin {
|
||||||
|
|
||||||
|
@Inject(method = "initServer", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", ordinal = 0))
|
||||||
|
private void initializeServer(CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
Events.SERVER_STARTUP.invoker().onServerInit((DedicatedServer) (Object) this);
|
||||||
|
}
|
||||||
|
}
|
23
src/main/resources/frog.mod.toml
Normal file
23
src/main/resources/frog.mod.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[frog]
|
||||||
|
format_version = "1.0.0"
|
||||||
|
|
||||||
|
[frog.mod]
|
||||||
|
id = "froglib"
|
||||||
|
name = "FrogLib"
|
||||||
|
version = "0.0.1"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
credits = [
|
||||||
|
{ name = "FrogMC Team", roles = ["author"] }
|
||||||
|
]
|
||||||
|
|
||||||
|
[frog.dependencies]
|
||||||
|
depends = [
|
||||||
|
{ id = "minecraft", versions = "~1.20.6", name = "Minecraft", link = "https://frogmc.dev" }
|
||||||
|
]
|
||||||
|
|
||||||
|
[frog.extensions]
|
||||||
|
mixin_config = [
|
||||||
|
"froglib.entrypoints.mixins.json",
|
||||||
|
"froglib.events.mixins.json"
|
||||||
|
]
|
||||||
|
|
15
src/main/resources/froglib.entrypoints.mixins.json
Normal file
15
src/main/resources/froglib.entrypoints.mixins.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "dev.frogmc.froglib.entrypoints.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_21",
|
||||||
|
"server": [
|
||||||
|
"server.MinecraftServerMixin"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"client.MainMixin"
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
18
src/main/resources/froglib.events.mixins.json
Normal file
18
src/main/resources/froglib.events.mixins.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "dev.frogmc.froglib.events.impl.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_21",
|
||||||
|
"mixins": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"server": [
|
||||||
|
"server.DedicatedServerMixin"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"client.MinecraftMixin"
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue