Table of Contents

入口点

入口点是在模组的 fabric.mod.json 中声明的,以将部分代码用于 Fabric 加载器或者其他模组加载。入口点最初用于使部分代码在游戏初始化过程中运行以初始化模组,虽然入口点系统也有其他用途。入口点由语言接收器(language adapters)加载,这些语言接收器会使用代码对象的名称产生一个指定类型的 Java 对象。

An entrypoint is exposed under some name, refers to some code object and must be based on a familiar entrypoint prototype. An entrypoint prototype defines the name (such as “main” or “client”) and the expected type of object that the entrypoint should refer to (such as the ModInitializer interface). An entrypoint prototype provider declares an entrypoint prototype and is responsible for accessing entrypoints, and can state how the they will be used. Fabric Loader provides some built-in entrypoint prototypes, while mods can also provide their own.

可以认为,入口点是 Java Service Provider Interfaces 的更强大的实现。

基本用途

模组可以在其 fabric.mod.json 中声明不同名称的多个入口点。“main”用于初始化模组对 Minecraft 客户端和专用服务器都需要使用的部分,例如注册表项。“client”用于初始化模组只给客户端保留的部分,例如与渲染有关的对象的注册。

{
  [...]
  "entrypoints": {
    "main": [
      "net.fabricmc.ExampleMod"
    ],
    "client": [
      "net.fabricmc.ExampleClientMod"
    ]
  }
  [...]
}

注意:建议main、client 和 server 入口点的类分开以避免类加载问题。考虑部分类同时用于 main 和 client 入口点的情况。在专用服务器启动时,即使 client 入口点从未加载,但包含 client 初始化逻辑的类仍然会。即使客户端逻辑不会执行,仅加载代码的行为也有可能触发类加载问题。

内置入口点协议类型

Fabric 加载器提供四种内置的入口点协议以用于模组初始化,其中一些用于处理与物理端有关的初始化(见 side)。main、client和server入口点在游戏初始化早期就会被加载并调用,此时大多数但并非全部的游戏系统都已准备好被修改。这些入口点用于通过注册注册表对象、事件监听器和其他用于后面的事情的回调来启动。

所有的 main 入口点都是在 client/server 入口点之前调用的。调用这些入口点的准确时间是未指定的,可能因游戏版本而异。

代码引用类型

An entrypoint's code reference is turned into an instance of the entrypoint prototype's type. The most common way to make an entrypoint is to refer to a class which implements the expected type, but these code references can be made in multiple ways. Internally, a language adapter is responsible for interpreting the references and turning them into instances. The default language adapter is designed for Java code, and thus supports the following types of references:

References to class members must be unambiguous, meaning the class must contain one and only one field or method with the targeted name. The language adapter cannot resolve methods overloads. In case of ambiguity, the entrypoint will fail to resolve.

Language adapters for other languages can be implemented by mods. fabric-language-kotlin provides a language adapter for Kotlin.

其他入口点应用

Mods can call each others' entrypoints for integration purposes. An entrypoint is loaded lazily when entrypoints for a specific entrypoint prototype are requested, which makes an entrypoint an excellent tool for optional mod integrations. A mod may become an entrypoint prototype provider by declaring that other mods should provide entrypoints based on an entrypoint prototype, often using a class or interface that the mod provides in its API. Mods can safely use this class or interface even if the provider is not installed (rendering the class or interface inaccessible) because entrypoints are loaded only on request. When the provider is not present, the entrypoint will simply be ignored.

Entrypoint instances can be accessed by calling FabricLoader#getEntrypointContainers(name, type). This returns a list of entrypoint containers. These containers contain the entrypoint instance and the mod container of the mod which provided the instance. This can be used by a mod to determine which mods have registered an entrypoint.

Entrypoint instances are memoized by their name and also their type. Using the same code reference for multiple entrypoints will result in multiple instances. Though highly absurd in practice, if getEntrypoints is called multiple times with the same name but different types, instances are constructed and memoized per type.

关于加载器顺序和时期的注意事项

Fabric Loader does not have a concept of a load order or loading phases. Initializer entrypoints are the mechanism with which most mod loading is usually done, but whether or not an initializer has been called does not determine whether or not a mod can be considered to be “loaded”. Thus, it is unreasonable to expect that a mod has completed its modifications to the game after its initializers have been called. Additionally, the order in which entrypoints are called is mostly undefined and cannot be altered. The only guarantee is that a list of initializers in a fabric.mod.json file are called in the order in which they are declared. Fabric Loader does not provide multiple phases of initializers to work around the lack of order, either.

A common example is the expectation that mod A should be able to load after mod B because mod A will replace an object registered by mod B. Alternatively, mod C wants to be loaded before mod D because mod D will do something in response to a registration performed by mod C. This is cannot be done for two reasons:

  1. Mod initializers are not required to represent a transition in a “mod loading lifecycle” so that after the initializer is called, all its registry objects are registered.
  2. The order in which mod initializers are called is undefined, and cannot be influenced so that mod A's initializers are called after mod B's initializers, or so that mod C's initializers are called before mod D's initializers.

Leaving aside the missing guarantee of registration of all objects in initializers, one might argue that there should therefore be other entrypoints to perform “pre-initialization” and “post-initialization” so that there is a sense of order. This creates a multi-phase loading scheme, which in practice creates issues with the establishment of conventions for which operations are to be performed in which phase, uncertainty and lack of adherence to these conventions and outliers which do not fit.