Sunday, 18 January 2015

The Client<-->Server division [1.14.4+]

The Minecraft code is divided into two “sides” – Client and Server.
  • The Server side is responsible for maintaining the master copy of the world - updating the blocks and entities based on packets received from the client, and sending updated information to all the clients.
  • The Client side is primarily responsible for reading input from the player and for rendering the screen.
There is one server, and a number of clients which connect to it. Even in single player mode, the server and client code are running simultaneously (in separate threads).


Some parts of the code are used by both Client and Server code, for example Block methods which describe the behaviour of Blocks and which are needed on both sides.
Vanilla groups these three types of classes into "client", "server", and everything else ("common"). 


If you are writing code which might be called by either Client or Server, how can you tell which side has called the code?
Your method will nearly always be provided with a World object, or an object containing a World field (typically called worldIn).  If so:
  • if World.isRemote is true, this is Client side
  • if World.isRemote is false, this is Server side.
As a last resort:
  • EffectiveSide.get() returns Side.SERVER or Side.CLIENT.  However it is not robust in all situations so you should be wary.
When the client and the server need to synchronise with each other, they exchange information over the network.    Even when a single player game is running, the client and server are still completely separate and do not access each other's objects.  If your code running on (say) the server side accesses object instances belonging to the client side, it will lead to random crashes and strange behaviour.  So don't do that!

I personally find it very helpful to split my mod into three top-level packages

  • client
  • common
  • server

This helps me clearly remember which classes are used on which side, and to make sure that I don't mix them up.


3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Thank you for your blog, it's very useful for developers. What you wrote about FMLcommonHandler.instance().getEffectiveSide() is "not robust in all situations" has helped me solve some obscure issues with our mod.
    See https://github.com/micdoodle8/Galacticraft/issues/1419. I guess I didn't read the comments on getEffectiveSide() before... seems that it will return the wrong result on servers if there's any multithreaded code, because only the main Server thread will give the correct result. Also in practice a server which is saving and shutting down seems to return the wrong result sometimes.

    As a coder, the main situation I have problems with is the validate() code for tile entities. Since Minecraft version 1.6.4 (I think), the vanilla Chunk code which places tile entities in the world and validates them does not set the TileEntity.worldObj until after the TileEntity has been validated. It means that a tile's validate() code cannot know which side it is on without calling FMLCommonHandler.getEffectiveSide() or some other trickery.

    ReplyDelete
  3. Keen thanks for that, glad you find it useful :)
    The multithreading is going to cause plenty of headaches I think - network code and chunk rendering in particular. I'm tiptoeing through the minefield still myself.

    I had a quick look and I think in 1.8 the world is now set before validate?

    public void addTileEntity(BlockPos pos, TileEntity tileEntityIn)
    {
    tileEntityIn.setWorldObj(this.worldObj);
    tileEntityIn.setPos(pos);

    if (this.getBlock(pos).hasTileEntity(getBlock(pos).getStateFromMeta(this.getBlockMetadata(pos))))
    {
    if (this.chunkTileEntityMap.containsKey(pos))
    {
    ((TileEntity)this.chunkTileEntityMap.get(pos)).invalidate();
    }

    tileEntityIn.validate();
    this.chunkTileEntityMap.put(pos, tileEntityIn);
    }
    }

    From what I've found so far, vanilla uses something a bit different to tell which thread it is on - see the (confusingly named) MinecraftServer.isCallingFromMinecraftThread()
    public boolean isCallingFromMinecraftThread()
    {
    return Thread.currentThread() == this.serverThread;
    }
    Something like that might be applicable if you put it in your proxy and check both Minecraft and MinecraftServer as appropriate.

    Cheers
    TGG

    PS Galacticraft is rather awesome I have to say, I've had a lot of fun with it!

    ReplyDelete