Registry
这一页是签名与行为参考。注册表的通用读写语义,冻结规则见 onPreInit。直接做一遍:教程 · 第一个模组。
冻结式注册表:只读视图 + 启动期一次性写入。模组在注册窗口(onPreInit)内通过 ctx.registries() 拿到各领域注册表的实例并写入自有内容;宿主在全部模组的 onPreInit 结束后立即冻结所有注册表,运行期只做只读查询。同一个接口服务全部内容类型(音符类型、游戏模式、渲染滤镜、收集品……),所以注册写法永远只有一种:register(key, value)。
适用场景:注册自有内容(onPreInit 内);在运行期按键/按资源标识查询已注册内容(get / contains / keys)。
方法速览
| 方法 | 用途 |
|---|---|
register(key, value) | 注册一项内容,返回传入的键 |
get(key) | 按注册键查询(带类型收敛) |
get(location) | 按资源标识查询 |
contains(location) | 该资源标识是否已注册 |
keys() | 全部已注册标识(不可变视图,注册顺序) |
keysByNamespace(namespace) | 按来源命名空间过滤的只读查询 |
isFrozen() | 注册表是否已冻结 |
完整签名
public interface Registry<T> {
<V extends T> RegistryKey<V> register(RegistryKey<V> key, V value);
<V extends T> Optional<V> get(RegistryKey<V> key);
Optional<T> get(ResourceLocation location);
boolean contains(ResourceLocation location);
Set<ResourceLocation> keys();
default Set<ResourceLocation> keysByNamespace(String namespace);
boolean isFrozen();
}register(key, value)
签名:
<V extends T> RegistryKey<V> register(RegistryKey<V> key, V value);行为:写入一项内容并返回传入的键(便于链式书写)。校验顺序以宿主实现为准:
key为null→IllegalArgumentException;value为null→IllegalArgumentException;key.type()必须是本注册表元素类型T的子类型,否则IllegalArgumentException(编译期泛型拦不住时由运行期兜底);value必须是key.type()的实例,否则IllegalArgumentException(防止「键声明NoteTypeDefinition、值却是别的类型」);- 注册表已冻结 →
RegistryFrozenException; - 同一
ResourceLocation已注册 →IllegalArgumentException(明确失败,不静默忽略)。
注意判重口径是资源标识而不是 RegistryKey.equals:同一 location 用不同 type 再注册一次同样算重复。
经 ModContext.registries() 取得的注册表是 per-mod 视图,另有前置校验:注册键的命名空间必须等于本模组 id,否则 IllegalArgumentException(归属错配会让宿主把内容记到别的模组名下、本模组收不到回调,因此注册期就拒绝)。该归属校验发生在冻结检查之前。
特殊注册表的额外约束:noteTypes() 还要求 NoteTypeDefinition.serializationKey() 非空白且全局唯一,冲突会抛 IllegalArgumentException(谱面 JSON 的 "type" 用它选类型,两者共用同一 key 将无法区分)。
示例:
@Override
public void onPreInit(ModContext ctx) {
RegistryKey<RenderFilter> key =
RegistryKey.of("mymod", "bloom", RenderFilter.class);
ctx.registries().renderFilters().register(key, new BloomFilter());
}get(key)
签名:
<V extends T> Optional<V> get(RegistryKey<V> key);行为:按 key.location() 查表,再用 key.type() 做一次类型收敛:未注册、key 为 null、或已注册值不是 key.type() 的实例时返回空 Optional(不抛异常)。key.type() 比实际注册类型更具体时会得到空结果,而不是 ClassCastException。
示例:
Optional<NoteTypeDefinition> normal = ctx.registries().noteTypes()
.get(RegistryKey.of("seecmd", "normal", NoteTypeDefinition.class));
if (normal.isPresent()) {
String ser = normal.get().serializationKey();
}get(location)
签名:
Optional<T> get(ResourceLocation location);行为:按资源标识查询,不关心值类型;未注册或 location 为 null 时返回空 Optional。适合「已知 id,想拿到内容」的运行期查询。
示例:
Optional<RenderFilter> filter =
ctx.registries().renderFilters().get(ResourceLocation.parse("mymod:bloom"));contains(location)
签名:
boolean contains(ResourceLocation location);行为:等价于 get(location).isPresent();location 为 null 时返回 false。注册前可用于冲突自查(但真正防重复靠 register 抛 IllegalArgumentException,不要只靠 contains 做「先查再写」)。
示例:
if (!ctx.registries().skins().contains(ResourceLocation.of("mymod", "neon"))) {
// 不冲突,可以注册
}keys()
签名:
Set<ResourceLocation> keys();行为:返回全部已注册的资源标识,保持注册顺序(宿主内置内容先于模组内容,同批次按注册先后)。返回不可变视图:冻结后是冻结快照的键集,冻结前是当前内容的副本;两种情况下调用方都不能修改,也不能指望它反映之后的写入。
示例:
for (ResourceLocation id : ctx.registries().gameModes().keys()) {
ctx.getLogger().info("game mode: " + id);
}keysByNamespace(namespace)
签名:
default Set<ResourceLocation> keysByNamespace(String namespace);行为:返回该命名空间下全部已注册标识,保持注册顺序;namespace 为 null 或没有匹配时返回空集合。自 起 per-mod 视图强制「注册键命名空间 = 本模组 id」,因此这个查询就是「本模组(或指定来源)注册了哪些」的精确答案,不必依赖「命名空间 = 模组 id」的猜测。
实现细节(以宿主为准):默认实现遍历 keys() 逐个过滤,返回的是新建的可变 LinkedHashSet——接口 javadoc 称「不可变视图」,但实际可以修改且不会影响注册表;它是调用时刻的快照,不随后续注册更新。宿主实现未覆写该方法。
示例:
for (ResourceLocation id : ctx.registries().renderFilters().keysByNamespace("mymod")) {
ctx.getLogger().info("my filter: " + id);
}isFrozen()
签名:
boolean isFrozen();行为:返回注册表是否已冻结。宿主在全部模组的 onPreInit 结束后调用一次冻结(幂等);此后写入抛 RegistryFrozenException,读取路径切到无锁快照。不能靠它判断「现在是否在注册窗口」来做注册:请把注册写在 onPreInit 内。
示例:
boolean canRegister = !ctx.registries().collectibles().isFrozen();线程:读操作(get / contains / keys / keysByNamespace / isFrozen)任意线程安全;写操作(register)仅允许在注册窗口内的宿主线程。
生命周期阶段:写入仅限注册窗口(onPreInit);运行期可读。注册表在进程生命周期内只冻结一次,因此模组停用/启用需要重启才生效;接口不暴露删除,已注册内容不会消失。
稳定性:2.0 起冻结:2.x 内只增不破(禁止删签名 / 改签名 / 改语义)。