Jump to content

Recommended Posts

Posted

I know this is not a direct Minecraft modding question, but it is related to Forge.  I was wondering how the reflection, the examination of classes at runtime, works with Forge?  I read up on how to do reflection from the Oracle website, but it didn't mention much about how to examine unknown classes, like forge does.  You never can know, unless at runtime, how many mods, or which ones, you will have in your Minecraft instance.  I was wanting to learn a bit more about how it all happened.  Can anyone shed some light on the matter?

Posted

You need to look at ClassLoaders.

 

ClassLoaders are objects that load other classes when needed.  This means that you can write a custom class loader that fetches a class, examines it, makes changes (if it wants), and then passes it off to the JVM.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

Set a breakpoint in a constructor of your main mod class. Run in the debugger and then see the call stack that got you there. Set more break points in some of those loaders. See how they work. If/when you write an event handler, do likewise. Then go back and see what the mod and event handler annotations do. It'll make your head spin.

 

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

You need to look at ClassLoaders.

 

ClassLoaders are objects that load other classes when needed.  This means that you can write a custom class loader that fetches a class, examines it, makes changes (if it wants), and then passes it off to the JVM.

Set a breakpoint in a constructor of your main mod class. Run in the debugger and then see the call stack that got you there. Set more break points in some of those loaders. See how they work. If/when you write an event handler, do likewise. Then go back and see what the mod and event handler annotations do. It'll make your head spin.

Thank you both for your replies.  I'm wanting to get much deeper into Java, and programming, than one can learn in casual research, so the feedback is much appreciated.

Posted

Set a breakpoint in a constructor of your main mod class. Run in the debugger and then see the call stack that got you there. Set more break points in some of those loaders. See how they work. If/when you write an event handler, do likewise. Then go back and see what the mod and event handler annotations do. It'll make your head spin.

 

Question about this, I did as you suggested, and after looking at the stack trace at the time my mod's main constructor was called, I get a method return from Class.class in the "forName" method.  However, I can't see where that method gets it's class name from.  I see what it's doing to it, and, examining it briefly, it makes sense, but I don't understand HOW it retrieves the needed class name. I even added a breakpoint on the "forName" method header, but I couldn't get it to tell me where it was being called from.  It's passed some information about the class it's loading, but that information is retrieved what I wanted to know.  I got a little closer to my answer with the "loadClass" method inside ClassLoader.class, but it's passed a binary name for a class to find.  Which method passes it that I still don't know, even after adding another breakpoint to that method.  Maybe I'm debugging wrong?  Idk.

 

 

Posted

I think what you are asking is this: How does FML actually know what mods to load, right?

Check out the ModDiscoverer class.

 

Yep, that totally helped.  Thanks!  And now my head hurts...  That is a complicated call hierarchy, and some impressive code.  The ModDiscover Class links, indirectly, to like 6 other classes, which all indirectly and directly link to each other.  I barely understand all this, but I'm quite impressed.  CPW, sir, you have done well.  For anyone interested, I found it in the referenced forge libraries in "net.minecraftforge.fml.common.discovery".

Posted

Isn't the JVM amazing?

 

(There's also a reason that I call coremodding / ASM an abyss that looks back.  Javacode can modify javacode at runtime.  Hell, you can create new class types at runtime, entirely through the bytecode manipulation features if you really want to.)

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted
Hell, you can create new class types at runtime

hehehehe

 

But yes, this has nothign to do with reflection its bytecode inspection. It's simple if you know what you're doing. But stupid complex if you're the average modder.

Its WAY more complex then a normal system needs to be.

I do Forge for free, however the servers to run it arn't free, so anything is appreciated.
Consider supporting the team on Patreon

Posted

Hell, you can create new class types at runtime

hehehehe

 

Figures Forge would do that. :P

What I don't know is why runtime class creation is preferable over a standard compiled class.  That is: I can't think of a use-case where it would be necessary.  Then again, the only example I saw was just that: an example to show that it could be done.  And no, I have no idea what that Forge one does.

 

It's simple if you know what you're doing. But stupid complex if you're the average modder.

 

It's still stupid complex even if you know what you're doing, because of how many things you have to keep in your head at one time in order to write a single line of code.  You have to know what's on the stack, what operation you need to do next, how it will affect the stack, and so on.  So I wouldn't say it's easy, more like doable.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

Ah! So that's how the event handlers work.  I always wondered about that.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted
If forge were to invoke all
@SubscribeEvent

methods using reflection the game would just die (there are a lot of events fired on a per-frame basis alone). Instead a wrapper class is emitted at runtime that directly invokes the target method and also implements an interface so the (pre-compiled) event handler code can call the methods in that interface directly.

And here I was, naively believing that reflection came without a "price".

 

PS: The class rewriting looks like a way to divorce horse armor from entity horse someday. Then we modders can finally create mods with custom horse armor (I can create the item in inventory, but multiple threads over the last few years have all failed to produce a solution short of a core-mod that can be worn by a horse and protect it).

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

And here I was, naively believing that reflection came without a "price".

 

Ohgodno.  Reflection is awful.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

And here I was, naively believing that reflection came without a "price".

 

Ohgodno.  Reflection is awful.

60 to 80 tiles slower then normal invocation. Thats why our event bus is a BEAST. Its specifically designed to be the least overhead as possible.

I do Forge for free, however the servers to run it arn't free, so anything is appreciated.
Consider supporting the team on Patreon

Posted

Thats why our event bus is a BEAST. Its specifically designed to be the least overhead as possible.

 

Props on making it, too.

Modding Minecraft has seriously been one of the most enjoyable programming experiences I've ever had.  Everything is laid out so well with incredibly intuitive and easy to use systems that let me hook into nearly everything.

 

I've had to coremod a few things, but they were things that have either been turned down (an event for when the player feeds animals...I still don't understand that one getting declined*) or don't have a general-purpose use (modifying the overworld moon phase calculation, an event for crop growth ticks, tweaking silverfish to enter custom blocks (actually that one might be worth trying to patch into Forge), couple other things).

 

*I was told that the method that sets animals into love mode (func_146082_f) never got an event because there were other ways to get at what it does.  And while true, my usecase needed to specifically detect the player setting the animal into love mode, rather than it coming from another source.  I wanted to make it take more food to feed an animal before it would breed, but also make animals go into love mode on their own.  If I couldn't detect that it was a player setting love mode, vs. it being from an AI task, then I coudn't achieve the effect I wanted.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

tweaking silverfish to enter custom blocks (actually that one might be worth trying to patch into Forge)

It was already suggested by PR (by diesieben) more than half a year ago. Still open. Don't know why forge never merged it.

https://github.com/MinecraftForge/MinecraftForge/pull/2012

Posted

Well then.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

And here I was, naively believing that reflection came without a "price".

 

Ohgodno.  Reflection is awful.

60 to 80 tiles slower then normal invocation.

I just reviewed at one of my reflections. In a class constructor, I acquire the field object of a private (and static) vanilla class data field and tell it to be accessible. That same field object is later reused during the game to fetch the field's value. If I understand correctly, then reflection's high price is paid in the setup, so the runtime value assignment is reasonably fast. Is that right, or are data gets slow too?

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

I just reviewed at one of my reflections. In a class constructor, I acquire the field object of a private (and static) vanilla class data field and tell it to be accessible. That same field object is later reused during the game to fetch the field's value. If I understand correctly, then reflection's high price is paid in the setup, so the runtime value assignment is reasonably fast. Is that right, or are data gets slow too?

 

IIRC, the lookup ("find this field/method") is the most expensive portion, but the rest of it is more costly than direct access.  But a single per-tick field.setValue() or method.invoke() isn't going to be noticeable.

 

The reason it matters for Forge events is because there are potentially thousands of them running per-tick.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

The lookup is the most expensive part... sortof.

The invocation is still ~60x slower then normal invocation.

Constant usage of reflection is noticeable.

People don't think it is because SOME JVMs SOMETIMES optimize the reflection classes that are used often by compiling them into native classes.

 

The problem through extensive research that I've done i've found that this JVM optimization is not reliable, only about 15% of end users JVMs do this at all. And of those 15% only about 1/2 do it aggressively enough to be useful in MC world.

 

And even when they do JIT it the resulting class still goes though a bulk of reflection redirection which make it at most 1/12th the speed of native code in my tests.

I do Forge for free, however the servers to run it arn't free, so anything is appreciated.
Consider supporting the team on Patreon

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
    • Remove rubidium - you are already using embeddium, which is a fork of rubidium
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.