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
|
||||
|
||||
plugins {
|
||||
id("java")
|
||||
id("dev.frogmc.phytotelma").version("0.0.1-SNAPSHOT")
|
||||
java
|
||||
id("dev.frogmc.phytotelma") version "0.0.1-SNAPSHOT"
|
||||
id("io.freefair.lombok") version "8.+"
|
||||
}
|
||||
|
||||
group = "dev.frogmc"
|
||||
version = "0.0.1-SNAPSHOT"
|
||||
|
||||
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()
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
@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"
|
||||
frogloader = "0.0.1-SNAPSHOT"
|
||||
|
||||
[libraries]
|
||||
|
|
|
@ -14,4 +14,3 @@ pluginManagement {
|
|||
}
|
||||
|
||||
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