Sunday, 24 November 2013

How Forge starts up your code

One of the things I found very confusing when first starting modding was the way that Forge starts up your code - @Mod, @SidedProxy, @EventHandler and the rest.  By following Havvy's tutorial I got it to work, but I didn't really understand what I was doing.  After working with Forge for a while I've finally figured it out....

Base Mod Class


When you start Minecraft, the Forge code prepares a couple of things and then starts searching for mods.  It looks through the java code in the mods folder until it finds a class which is preceded by the @Mod annotation.

If you have designed your mod properly, this will be your "Base Mod" class.

For example- your mod contains a file called TestFrameworkMod.java:
@Mod(modid="testframeworkmod", name="Forge Test Framework Mod", version="0.0.1")
public class TestFrameworkMod {

 //.. etc ...
}


Forge finds the @Mod, reads a couple of pieces of information about the mod (a unique "ID" name, the human-readable name, and the version), and now knows that the base class for your mod is TestFrameworkMod.

(NB all the code in this tutorial can be found here).

The general name for this technique is Reflection.
Reflection: the ability of a computer program to examine and modify the structure and behavior of an object at runtime.
Reflection allows inspection of classes, interfaces, fields and methods at runtime without knowing the names of the interfaces, fields, methods at compile time.
Forge uses this quite a bit to integrate with your mod code without knowing in advance what you've called your classes and how they're structured.

Now that Forge has found your Base Mod Class definition, it also needs to know where the instance of your class is.  To do this, it searches a bit further until it finds @Mod.Instance.

// The instance of your mod that Forge uses.
@Mod.Instance("testframeworkmod")
public static TestFrameworkMod instance;


It now knows that instance is the instance of your Base Mod Class, which it uses later on to communicate with your class.

For more technical details, see @Mod in the Forge code-
net/minecraftforge/fml/common/@Mod.

Proxies


In order to understand the next part, I first need to explain a couple of concepts about Server and Client.
Minecraft is designed from the ground up to be a multiplayer game.  For this reason, it is split into two parts - "server" and "client".
  • the "client" interacts with each user - reading their keyboard and mouse, and drawing things on your computer screen.  Every player will have their own "client".
  • the "server"  maintains the world and everything in it.  It talks to all of the clients to find out what each user is doing, updates the world accordingly, then sends the updated information back to all the clients.  There is only one server.   
Historically, Minecraft actually shipped with two completely separate programs -
  • Dedicated Client - contained all the "client-side" code
  • Dedicated Server - contained all the "server-side" code
But then they figured out that it was pretty user-unfriendly for you to have to run the DedicatedServer if you wanted to play LAN multiplayer with your buddies, so they added an "Integrated Server" into the Client (the "open to LAN" feature) and made what I like to call the "CombinedClient".  (For some reason everyone else just calls it the "Client", which is a bit confusing I think).

So the two different Minecraft programs are now:
  • CombinedClient (which has both client-side code and server-side code in it)
  • Dedicated Server (which has only server-side code).


The Forge authors wanted to make it easy to use the same Mod code in both the "Combined" Client and the Dedicated Server.  (This is not trivial because the Dedicated Server is missing the client-side code, and if your Mod attempts to call any of the client-side code when it's installed in the DedicatedServer, it will crash.)

They use the "SidedProxy" in order to do this. Forge hunts through your Base Mod until it finds @SidedProxy.  It then chooses the appropriate Class from the options you have provided, creates an instance of it, and puts it into the field immediately after the @SidedProxy annotation.
So in the example below:
  • If the mod is installed in a normal Minecraft client, the proxy variable will be set to your class CombinedClientProxy (which inherits from CommonProxy).
  • If the mod is installed in a dedicated server, the proxy variable will be set to your class DedicatedServerProxy (which also inherits from CommonProxy).
// the proxy reference will be set to either CombinedClientProxy or
//   DedicatedServerProxy depending on whether this mod is running in a
//   normal Minecraft client or a dedicated server.
@SidedProxy(clientSide="testframework.clientonly.CombinedClientProxy",
                       serverSide="testframework.serveronly.DedicatedServerProxy")
public static CommonProxy proxy;


Why is this useful?  Because you can now easily split your initialisation code into three parts:
  1. Code in the CombinedClientProxy, which only gets called on the normal Minecraft client.
  2. Code in the DedicatedServerProxy, which only gets called on a dedicated server.
  3. Code in the CommonProxy base class, which is called regardless of where the mod is installed.
Please note - this has NOTHING TO DO with the difference between client-side  and server-side.  That is something totally different.

An example of what the Proxy code might look like:

public class CommonProxy {
  public void doSomething() {
    // do something on both 
  } 
}

public class CombinedClientProxy extends CommonProxy {
  @Override
  public void doSomething() {
     super.doSomething();   // do something on both
     // do something on normal Minecraft only
  } 
}

public class DedicatedServerProxy extends CommonProxy {
  @Override
  public void doSomething() {
     super.doSomething();   // do something on both
     // do something on dedicated server only
  } 
}

A comment about Server vs Client objects - because of the way that Java loads classes, your server-side classes should not have any mention of Client-side-only vanilla objects whatsoever.  It doesn't matter whether the code is ever executed- simply having a Client-side-only definition somewhere in your class is often enough to cause a crash.  Use a dedicated client-side-only class instead, or alternatively a method in your proxy classes.

EventHandlers

During startup, Forge will call your Mod several times to let you add new blocks, items, read configuration files, and otherwise integrate itself into the game by registering your Classes in the appropriate spots.

Before Forge can call your Mod, it needs to know which methods to use.  This is where @EventHandler comes in.  For example - during startup Forge will go through a number of phases for all of the mods which are loaded:
  • PreInitialization - "Run before anything else. Read your config, create blocks, items, etc, and register them with the GameRegistry."
  • Initialization - "Do your mod setup. Build whatever data structures you care about. Register recipes."
  • PostInitialization -  "Handle interaction with other mods, complete your setup based on this. 
PreInitialization is peformed for all the mods, followed by Initialization for all mods, followed by PostInitialization for all mods.  Initialising the mods in phases is particularly useful when there might be interactions between multiple mods - for example if one mod adds an extra type of wood (during PreInit), and your mod adds a recipe which uses that wood (during Init).

When Forge wants to tell your mod that it's time to run your PreInitialization code, it reads through your mod's code until it finds @EventHandler in front of a method, then checks the parameter definition to see if it matches the FMLPreInitializationEvent Class.  If so, it calls the method.

The PreInitialization, Initialization, and PostInitialization events will often need to do different things depending on whether your mod is in a CombinedClient or a DedicatedServer.  For this reason I suggest that your event handlers should just immediately call a method in the CommonProxy, see below.

@EventHandler
public void preInit(FMLPreInitializationEvent event) {
    proxy.preInit();
}

@EventHandler
public void load(FMLInitializationEvent event) {
    proxy.load();
}

@EventHandler
public void postInit(FMLPostInitializationEvent event) {
    proxy.postInit();
}


There are many other events in addition to these three (not that you're likely to need them unless you're doing some hardcore trickery).  See
cpw/mods/fml/common/event



~Overview of Forge (and what it can do for you)
    Forge concepts
           Some general notes
           How Forge starts up your code (@Mod, @SidedProxy, etc)  
           Registering your classes
           Extra methods for vanilla base classes
           Events
     Forge summary - grouped by task
           Blocks
           Items
           TileEntities
           Entities
           World generation, loading, saving
           Miscellaneous - player input, rendering, GUI, chat, sounds


4 comments:

  1. Thank you for doing all of this!

    ReplyDelete
    Replies
    1. You're welcome :-) Actually it's good fun for me to figure out how it all works, and I find that trying to explain it helps me understand it better myself too.

      Delete
  2. It's great to see some informative Forge documentation! Cleared a lot up for me.

    ReplyDelete
  3. This is great information about server and client. Well explained about dedicated proxies and all. I am using Microleves Dedicated Proxies. It works fine for me and will understand what you want to explain in this post.

    ReplyDelete