Jump to content

Recommended Posts

Posted (edited)

Hello!

I was wondering, if I call a method in a server-side method where only the server thread runs, will the contents in the method be run by the server and not the client?

For example:

public void onCommand(EntityPlayer playerIn)
{
  World worldIn = playerIn.worldObj;
  worldIn.playSound(null, playerIn.getPosition(), SoundEvents.BLOCK_CLOTH_BREAK, SoundCategory.PLAYERS, 1.0F, 1.0F);
  Random rand = worldIn.rand;
  worldIn.spawnParticle(EnumParticleTypes.CLOUD, 
       playerIn.posX + (double)(rand.nextFloat() * playerIn.width * 2.5F) - (double)playerIn.width, 
       playerIn.posY + 0.5D + (double)(rand.nextFloat() * playerIn.height) - 1.25D, 
       playerIn.posZ + (double)(rand.nextFloat() * playerIn.width * 2.5F) - (double)playerIn.width, 
       rand.nextGaussian() * 0.025D, rand.nextGaussian() * 0.025D, rand.nextGaussian() * 0.025D, 1
    );
  
  DinocraftPlayer player = DinocraftPlayer.getEntityPlayer(playerIn);
            	
  if (player != null)
  {
    player.setMaxHealth(playerIn.getMaxHealth() + 1.0F);
  }
}

@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException 
{
  EntityPlayer playerIn = getCommandSenderAsPlayer(sender);
  this.onCommand(playerIn); // when I call this method, will the contents inside the method run on the server-thread, client-thread, or common (client and server-side)?
}

This is just a quick example.

 

Any help will be welcome.

Thanks! :)

Edited by Differentiation
Posted

Okay, let me give you a high-level explanation of this whole "sides" stuff.

 

Whenever you make a client and server game you don't want people to be able to cheat. Therefore you want all game logic to happen on the server, otherwise people would create clients that would do things like allow them to teleport or do extra damage and such. So ideally every multi-player game would have all logic on the server and the client would only do user interface (graphics, sounds and input from keyboard and mouse).

 

However, if you try to make a game where the client only does user interface you'll quickly find that the movement is not smooth. This is because the graphics updates faster than the network can send the information from the server. So every real-time game has to figure out a way where the client does some of the processing in between updates from the server.

 

Unfortunately, because the game is complicated for the client to smooth things out graphically it needs to actually process some of the logic while waiting for updates.

 

That is the general idea. But then in Minecraft there are a couple specific additional things that cause confusion.

 

In order to optimize things like memory use, they decided it would be good to not load things that are not needed. So they used the "sided" annotation. When a class or field or method is annotated as Side.CLIENT then the server doesn't have to load it and saves memory. 

 

That would be easy to understand except there is one other thing. Normally your client and server will run in separate Java machine instances. However, that is a bit wasteful when playing in single player. So instead they decided to combine them into a single Java instance (integrated server).

 

The problem with the integrated server is that because it is a single Java machine running both sides then the classes for both sides are loaded -- basically the sided annotation is pretty much ignored. Yes there are two threads, but they are in one JVM. This causes issues for novice modders because if they write code that "reaches across the sides" it will still appear to function without error. A lot of modders will test in integrated server mode and so won't realize the problem and then get yelled at when they post their code on the forum.

 

So a very important tip for modding is always test your mod by running with a dedicated server. You can do this easily in Eclipse by first running the Server run configuration and then while that is running run the Client run configuration. Then in your client game you choose connect to server and put in the "loopback" address 127:0:0:1.  If you test like this, you'll very quickly learn when you are making mistakes with sided functionality.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted
11 minutes ago, jabelar said:

In order to optimize things like memory use, they decided it would be good to not load things that are not needed. So they used the "sided" annotation. When a class or field or method is annotated as Side.CLIENT then the server doesn't have to load it and saves memory.

More specifically, the class isn't even available. The Minecraft dedicated server jar does not include those classes (or fields/methods). They've literally been stripped. Even if the JVM wants them, they aren't there to go get. That's why the server will crash with a "Class Not Found" exception. While the reason for the removal is to make the jar more lightweight, it's more complicated than simply "not loading" things.

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 (edited)
3 hours ago, Differentiation said:

I was wondering, if I call a method in a server-side method where only the server thread runs, will the contents in the method be run by the server and not the client?

Okay, back to your original question. So both the server and client are totally separate threads. The only issue with mixing up sides is if you try to access classes that aren't supposed to be available (but might be if you're using integrated server) due to being on the other "side".

 

So methods are only called in the client thread from the client thread. And methods are only called in the server thread from the server thread. However, there are some methods which are part of the logic on both sides and will both get called (separately but in parallel). This is for the reason I explained above -- some logic does need to execute on the client in order to smooth out the action between server updates.

 

However, the client calls it itself. The client is just trying to copy what the server will do.

 

It is actually kinda hard by just looking at the code to know where the code will run. There is very likely code that is not marked as SideOnly that still only runs on one side. And if the code has any checks for world#isRemote then it will execute differently depending on where it will run. Remember, the Side.CLIENT stuff is stripped out to optimize the server JAR size but there is no requirement to strip out everything.

 

In many ways you just need to get a feel for which things are working in each place. One good way to get a feel for that is to make some test classes (block, entity, etc.) that have console statements in the methods of interest and watch what get's printed to the console. You'll see some that happen in one thread or the other, and some that happen in both. 

 

Sometimes things are obvious, like a renderer only being on client side. However, sometimes it gets confusing because the server implementation will use a same or similar method name to notify clients (rather than do the client thing) -- a good example is various sound playing methods where on the client they will directly play the sound, but on server they will send notifications to the clients (to tell them to play the sound).

 

So don't feel too bad if you still get confused. But do try to understand the main concepts:

1) The dedicated server JAR does not have access to anything marked Side.CLIENT, although testing with integrated server will fool you (because it does have access).

2) Methods that are not marked as sided may run on one side or both, and if they run on both may use world#isRemote to have different behavior.

 

P.S. Modders mostly run into problems with Side.CLIENT., not Side.SERVER There are also cases of Side.SERVER but those are mostly limited to networking, user authentication, and a few other MinecraftServer fields, so modders don't seem to run into problems with that much.

 

Edited by jabelar
  • Like 1

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted
6 hours ago, jabelar said:

Okay, back to your original question. So both the server and client are totally separate threads. The only issue with mixing up sides is if you try to access classes that aren't supposed to be available (but might be if you're using integrated server) due to being on the other "side".

 

So methods are only called in the client thread from the client thread. And methods are only called in the server thread from the server thread. However, there are some methods which are part of the logic on both sides and will both get called (separately but in parallel). This is for the reason I explained above -- some logic does need to execute on the client in order to smooth out the action between server updates.

 

However, the client calls it itself. The client is just trying to copy what the server will do.

 

It is actually kinda hard by just looking at the code to know where the code will run. There is very likely code that is not marked as SideOnly that still only runs on one side. And if the code has any checks for world#isRemote then it will execute differently depending on where it will run. Remember, the Side.CLIENT stuff is stripped out to optimize the server JAR size but there is no requirement to strip out everything.

 

In many ways you just need to get a feel for which things are working in each place. One good way to get a feel for that is to make some test classes (block, entity, etc.) that have console statements in the methods of interest and watch what get's printed to the console. You'll see some that happen in one thread or the other, and some that happen in both. 

 

Sometimes things are obvious, like a renderer only being on client side. However, sometimes it gets confusing because the server implementation will use a same or similar method name to notify clients (rather than do the client thing) -- a good example is various sound playing methods where on the client they will directly play the sound, but on server they will send notifications to the clients (to tell them to play the sound).

 

So don't feel too bad if you still get confused. But do try to understand the main concepts:

1) The dedicated server JAR does not have access to anything marked Side.CLIENT, although testing with integrated server will fool you (because it does have access).

2) Methods that are not marked as sided may run on one side or both, and if they run on both may use world#isRemote to have different behavior.

 

P.S. Modders mostly run into problems with Side.CLIENT., not Side.SERVER There are also cases of Side.SERVER but those are mostly limited to networking, user authentication, and a few other MinecraftServer fields, so modders don't seem to run into problems with that much.

 

1 hour ago, diesieben07 said:

I think the confusion mainly comes from the fact, that there are two types of sides, "logical" and "physical". Most of the time you want to think about "logical" side.

By "physical" side I mean "the client launched from the launcher" vs. "the server launched via minecraft_server.jar".

By "logical" side I mean "the thread running the client logic" vs.  "the thread running the server logic".

 

Now, when you start a single player game (in the physical client), the physical client starts two threads, one running a logical client and one running a logical server. This logical server thread is called the integrated server, because it is integrated into the physical client. Since it is running inside the same JVM process, it has access to the logical client thread and all it's resources. Doing so, however, is known as "reaching across logical sides" and is a very bad idea.

 

When you connect to a server, your physical client only spawns one thread, running the logical client, since the logical server runs remotely.

 

The physical server only ever spawns one thread, running the logical server.

 

I hope this clears things up a bit.

Thank you!

You cleared up many things for me. I finally fully understand why Minecraft.getMinecraft(), for example, doesn't work when on a server, and things like that. :)

 

Thanks again!

Happy modding!

Posted (edited)

But then if I run a method in a method that runs on the logical server such as execute(...), will the method that I run inside run on the logical server, the logical client or both, in that case?

Ex.

public void onCommand(EntityPlayer playerIn)
{
  World worldIn = playerIn.worldObj;
  worldIn.playSound(null, playerIn.getPosition(), SoundEvents.BLOCK_CLOTH_BREAK, SoundCategory.PLAYERS, 1.0F, 1.0F);
  
  DinocraftPlayer player = DinocraftPlayer.getEntityPlayer(playerIn);
  player.setMaxHealth(playerIn.getMaxHealth() + 1.0F);
}

@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException 
{
  EntityPlayer playerIn = getCommandSenderAsPlayer(sender);
  this.onCommand(playerIn); // when I call this method, will the contents inside the method run on the server-thread, client-thread, or common (client and server-side)?
}

Thanks!

Edited by Differentiation
Posted
6 minutes ago, diesieben07 said:

I think you are lacking a basic understanding of how threads work and you should look into that. A thread is a chain of instructions. If you call a method, that method runs on the same thread.

I mean, yeah, I was expecting it to run on the same thread.

Thanks!

Posted

(Note that there are clean ways of reaching across threads in multithreaded applications, but none of them apply to Minecraft because of the physical sided nature of things: you want it to work on a dedicated server as well, then you can't reach across, even cleanly. The only way to do so is with packets, and if you look at the packet documentaiton, you'll see that one of those clean methods of reaching across is utilized, because surprise! There's another thread running: the netcode thread).

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.

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

    • So me and a couple of friends are playing with a shitpost mod pack and one of the mods in the pack is corail tombstone and for some reason there is a problem with it, where on death to fire the player will get kicked out of the server and the tombstone will not spawn basically deleting an entire inventory, it doesn't matter what type of fire it is, whether it's from vanilla fire/lava, or from modded fire like ice&fire/lycanites and it's common enough to where everyone on the server has experienced at least once or twice and it doesn't give any crash log. a solution to this would be much appreciated thank you!
    • It is 1.12.2 - I have no idea if there is a 1.12 pack
    • Okay, but does the modpack works with 1.12 or just with 1.12.2, because I need the Forge client specifically for Minecraft 1.12, not 1.12.2
    • 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() ); } }  
  • Topics

×
×
  • Create New...

Important Information

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