Skip to content

Commit

Permalink
wow much simpler runtime code generation!
Browse files Browse the repository at this point in the history
  • Loading branch information
Progames723 committed Jan 29, 2025
1 parent 551c883 commit 97155bf
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 8 deletions.
1 change: 0 additions & 1 deletion common/src/main/java/dev/progames723/hmmm/HmmmLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
public class HmmmLibrary {
public static final String MOD_ID = "hmmm";
public static final Logger LOGGER = LoggerFactory.getLogger("HmmmLibrary");
public static final Marker NATIVE = MarkerFactory.getMarker("Native");
public static final Marker TEST = MarkerFactory.getMarker("Test");
public static final Marker REFLECT = MarkerFactory.getMarker("Reflection");
public static final boolean TEST_ARG;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.progames723.hmmm.code_gen;

import java.util.Map;

public record AnnotationDefinition(
String descriptor,
Map<String, Object> values
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.progames723.hmmm.code_gen;

import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;

import java.util.List;

public record ClassDefinition(
String className,
@Nullable String superName, //set to null if you dont have a super class or just extend Object
String[] interfaces,
int version,
int access,
List<AnnotationDefinition> annotations,
List<ClassDefinition> innerClasses,
List<FieldDefinition> fields,
List<MethodDefinition> methods,
boolean createDefaultConstructor
) {
public ClassDefinition(
String className,
@Nullable String superName, //same here
String[] interfaces,
int access,
List<AnnotationDefinition> annotations,
List<ClassDefinition> innerClasses,
List<FieldDefinition> fields,
List<MethodDefinition> methods,
boolean createDefaultConstructor
) {
this(className, superName, interfaces, Opcodes.V1_8, access, annotations, innerClasses, fields, methods, createDefaultConstructor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.progames723.hmmm.code_gen;

import java.util.List;

public record FieldDefinition(
String name,
String descriptor,
int access,
List<AnnotationDefinition> annotations,
Object initialFieldValue
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.progames723.hmmm.code_gen;

import org.objectweb.asm.MethodVisitor;

import java.util.List;
import java.util.function.Consumer;

public record MethodDefinition(
String name,
String descriptor,
int access,
List<AnnotationDefinition> annotations,
String[] exceptions,
Consumer<MethodVisitor> instructions
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package dev.progames723.hmmm.code_gen;

import org.objectweb.asm.*;
import org.objectweb.asm.util.CheckClassAdapter;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class RuntimeClassGenerator {
private static boolean DEBUG = false;//set manually or through reflection, dont leave as true in production

private static final GeneratedClassLoader loader = new GeneratedClassLoader();

private static final class GeneratedClassLoader extends ClassLoader {
public Class<?> defineClass(String className, byte[] data) {return defineClass(className, data, 0, data.length);}
}

public static Class<?> generateClass(ClassDefinition clazz) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

//the class thing uhh idfk what im doing so i will now pray for it to start working correctly on my first try
cw.visit(
clazz.version(),
clazz.access(),
clazz.className(),
null,
clazz.superName() != null ? clazz.superName() : "java/lang/Object",
clazz.interfaces()
);

//class annotation things
for (AnnotationDefinition annotation : clazz.annotations()) {
AnnotationVisitor av = cw.visitAnnotation(
annotation.descriptor(),
true
);
annotation.values().forEach(av::visit);
av.visitEnd();
}

//fields yeah
for (FieldDefinition field : clazz.fields()) {
generateField(cw, field);
}

//default constructor lol
if (clazz.createDefaultConstructor()) {
generateDefaultConstructor(cw, clazz.superName());
}

//methods lol
for (MethodDefinition method : clazz.methods()) {
generateMethod(cw, method);
}

cw.visitEnd();
byte[] bytes = cw.toByteArray();
if (DEBUG) {
ClassReader classReader = new ClassReader(bytes);
classReader.accept(new CheckClassAdapter(null), 0);
Path path = Paths.get(classReader.getClassName() + ".class");
try {
Files.createDirectories(path.getParent());
Files.write(path, bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return loader.defineClass(clazz.className().replace('/', '.'), bytes);
}

private static void generateField(ClassWriter cw, FieldDefinition field) {
FieldVisitor fv = cw.visitField(
field.access(),
field.name(),
field.descriptor(),
null,
field.initialFieldValue()
);
for (AnnotationDefinition annotation : field.annotations()) {
AnnotationVisitor av = fv.visitAnnotation(
annotation.descriptor(),
true
);
annotation.values().forEach(av::visit);
av.visitEnd();
}
fv.visitEnd();
}

private static void generateDefaultConstructor(ClassWriter cw, String superName) {
MethodVisitor mv = cw.visitMethod(
Opcodes.ACC_PUBLIC,
"<init>",
"()V",
null,
null
);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
if (superName == null) superName = "java/lang/Object";
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0); //method code reuse
mv.visitEnd();
}

private static void generateMethod(ClassWriter cw, MethodDefinition method) {
MethodVisitor mv = cw.visitMethod(
method.access(),
method.name(),
method.descriptor(),
null,
method.exceptions()
);
for (AnnotationDefinition annotation : method.annotations()) {
AnnotationVisitor av = mv.visitAnnotation(
annotation.descriptor(),
true
);
annotation.values().forEach(av::visit);
av.visitEnd();
}
mv.visitCode();
method.instructions().accept(mv);
mv.visitMaxs(0, 0); //we dont need to do shit lol
mv.visitEnd();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import net.minecraft.world.effect.MobEffectInstance;

public class LivingEvents {
public static Event<LivingHurt> LIVING_HURT = EventFactoryUtil.createEvent(new LivingEntityEvent<TripleValue<Boolean, DamageSource, Float>>(false, new TripleValue<>(true, null, -1.0f), null));
public static Event<LivingHurt> LIVING_HURT = EventFactoryUtil.createEvent(new LivingEntityEvent<>(false, new TripleValue<>(true, null, -1.0f), null));
public static Event<LivingDamaged> LIVING_DAMAGED = EventFactoryUtil.createEvent(new LivingEntityEvent<>(false, new DoubleValue<>(true, -1.0f), null));
public static Event<LivingEarlyTick> LIVING_EARLY_TICK = EventFactoryUtil.createVoidEvent(new LivingEntityEvent<Void>(true, null, null));
public static Event<LivingLateTick> LIVING_LATE_TICK = EventFactoryUtil.createVoidEvent(new LivingEntityEvent<Void>(true, null, null));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private void livingHurt(final DamageSource damageSource, final float f, Callback
if (tripleValue == null) return;
hmmm$tempDamageSource = sanitize(tripleValue.getA(), damageSource);
hmmm$hurtTempDamage = sanitizeFloat(tripleValue.getB());
if (!sanitize(event.isCancelled().value(), true)) {
if (event.isCancelled()) {
cir.setReturnValue(false);
}
if (hmmm$hurtTempDamage <= 0.0f && hmmm$hurtTempDamage != -1.0f) {
Expand Down Expand Up @@ -134,7 +134,7 @@ private void livingDamaged(final DamageSource damageSource, final float f, Callb
var fl = event.getValue();
//a damage source cannot be altered that late
hmmm$damagedTempDamage = sanitizeFloat(fl);
if (!sanitize(event.isCancelled().value(), true)) {
if (event.isCancelled()) {
ci.cancel();
}
if (hmmm$damagedTempDamage <= 0.0f && hmmm$damagedTempDamage != -1.0f) {
Expand Down Expand Up @@ -195,8 +195,8 @@ private void beforeEffectApplied(final MobEffectInstance mobEffectInstance, Call
var the = event.getValue();
if (the == null) return;
hmmm$effectAppliedTempEffect = sanitize(the, mobEffectInstance);
if (event.isCancelled().value() != null) {
cir.setReturnValue(event.isCancelled().value());
if (event.getEventResult().cancelsEvents()) {
cir.setReturnValue(event.getEventResult().representation());
}
}

Expand Down Expand Up @@ -224,7 +224,7 @@ private void beforeEffectAdded(MobEffectInstance mobEffectInstance, @Nullable En
var event = EventUtils.createVoidLivingEntityEvent(mobEffectInstance, hmmm$instance);
LivingEvents.LIVING_BEFORE_EFFECT_ADDED.invoker().livingBeforeEffectAdded(event);
MobEffectInstance the = event.getValue();
Boolean a = event.isCancelled().value();
Boolean a = event.getEventResult().representation();
if (the == null) return;
hmmm$effectAddedTempEffect = sanitize(the, mobEffectInstance);
if (a != null) {
Expand Down Expand Up @@ -273,7 +273,7 @@ private void beforeEffectRemoved(MobEffect mobEffect, CallbackInfoReturnable<Boo
LivingEvents.LIVING_BEFORE_EFFECT_REMOVED.invoker().livingBeforeEffectRemoved(event);
var the = event.getValue();
if (the == null) return;
if (event.isCancelled().value()) {
if (event.getEventResult().representation()) {
MobEffectInstance mobeffectinstance = this.removeEffectNoUpdate(mobEffect);
if (mobeffectinstance != null) {
this.onEffectRemoved(mobeffectinstance);
Expand Down

0 comments on commit 97155bf

Please sign in to comment.