前言
Forge
定义物品、方块等元数据后,需要主动进行大量的注册操作:
- 物品:注册物品、注册模型
- 方块:注册方块、注册实体、注册物品方块、注册物品方块模型
为了简化操作并节省工作量,决定编写一套机制实现物品、方块、附魔等元数据的自动注册。
设计
自动注册机制的实现参考了Spring Boot
的Bean
加载,在自动注册实现类中堆外暴露一个静态run
方法作为入口:
java
/**
* 自动注册入口
*/
public static void run(@Nonnull Class<?> primarySource) {
// ......
}
/**
* 自动注册入口
*/
public static void run(@Nonnull Class<?> primarySource) {
// ......
}
该方法在FMLPreInitializationEvent
事件发布时执行:
java
@Mod.EventHandler
public void onFMLPreInitialization(FMLPreInitializationEvent event) {
// ......
AutoRegistry.run(LegendOfTheCyberHeroes.class);
}
@Mod.EventHandler
public void onFMLPreInitialization(FMLPreInitializationEvent event) {
// ......
AutoRegistry.run(LegendOfTheCyberHeroes.class);
}
之所以在这个时机执行扫描,是因为物品、方块和模型等元数据注册事件在该事件之后执行。该方法会从入参类所在package
开始扫描,将所有标注有@RegistryMethod
注解的方法缓存起来,注册方法对应的类也会被实例化并缓存起来。@RegistryMethod
注解定义如下:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RegistryMethod {
RegistryType value();
enum RegistryType {
ITEM,
ITEM_BLOCK,
BLOCK,
BLOCK_ENTITY,
MODEL,
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RegistryMethod {
RegistryType value();
enum RegistryType {
ITEM,
ITEM_BLOCK,
BLOCK,
BLOCK_ENTITY,
MODEL,
}
}
注解目标方法要求没有入参,可以是静态或者实例方法,访问级别需为public
。自动注册本身并不向注册表写入任何数据,写入操作均由@RegistryMethod
标注的方法实现。
实现
第一步,使用Guava
的ClassPath
扫描入口类所在包下的所有类,并保存到ThreadLocal
中:
java
/**
* 自动注册入口
*/
public static void run(@Nonnull Class<?> primarySource) {
try {
// step1 扫描类
String basePackage = primarySource.getPackage().getName();
ImmutableSet<ClassPath.ClassInfo> modClasses = ClassPath.from(primarySource.getClassLoader()).getTopLevelClassesRecursive(basePackage);
modClassesLocal.set(modClasses);
// step2 扫描注册方法
scanRegistryMethod();
} catch (IOException e) {
throw new RuntimeException("自动注册异常", e);
}
}
/**
* 自动注册入口
*/
public static void run(@Nonnull Class<?> primarySource) {
try {
// step1 扫描类
String basePackage = primarySource.getPackage().getName();
ImmutableSet<ClassPath.ClassInfo> modClasses = ClassPath.from(primarySource.getClassLoader()).getTopLevelClassesRecursive(basePackage);
modClassesLocal.set(modClasses);
// step2 扫描注册方法
scanRegistryMethod();
} catch (IOException e) {
throw new RuntimeException("自动注册异常", e);
}
}
第二步,遍历上一步扫描出的类,获取该类有@RegistryMethod
注解的方法:
java
private static void scanRegistryMethod() {
for (ClassPath.ClassInfo classInfo : modClassesLocal.get()) {
Class<?> clazz = classInfo.load();
Method[] registryMethods = MethodUtils.getMethodsWithAnnotation(clazz, RegistryMethod.class);
// ......
}
}
private static void scanRegistryMethod() {
for (ClassPath.ClassInfo classInfo : modClassesLocal.get()) {
Class<?> clazz = classInfo.load();
Method[] registryMethods = MethodUtils.getMethodsWithAnnotation(clazz, RegistryMethod.class);
// ......
}
}
第三步,将扫描出的方法及其类实例缓存起来:
java
for (Method registryMethod : registryMethods) {
RegistryMethod annotation = registryMethod.getAnnotation(RegistryMethod.class);
switch (annotation.value()) {
case ITEM:
itemRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case BLOCK:
blockRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case MODEL:
modelRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case ITEM_BLOCK:
itemBlockRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case BLOCK_ENTITY:
blockEntityRegistryMethodMap.put(registryMethod, registryDefinition);
break;
default:
throw new RuntimeException("未知的注册对象");
}
}
for (Method registryMethod : registryMethods) {
RegistryMethod annotation = registryMethod.getAnnotation(RegistryMethod.class);
switch (annotation.value()) {
case ITEM:
itemRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case BLOCK:
blockRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case MODEL:
modelRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case ITEM_BLOCK:
itemBlockRegistryMethodMap.put(registryMethod, registryDefinition);
break;
case BLOCK_ENTITY:
blockEntityRegistryMethodMap.put(registryMethod, registryDefinition);
break;
default:
throw new RuntimeException("未知的注册对象");
}
}
registryDefinition
保存了类对象、类实例和该类中所有注册方法。
第四步,在相应注册事件发布时调用扫描到的注册方法:
java
/**
* 注册事件监听器
*/
@Mod.EventBusSubscriber(modid = MOD_ID)
public class RegistryEventListener {
/**
* 物品注册事件消费
*/
@SubscribeEvent
public static void onItemRegistry(RegistryEvent.Register<Item> event) {
AutoRegistry.setItemRegistry(event.getRegistry());
AutoRegistry.registryItem();
}
/**
* 方块注册事件消费
*/
@SubscribeEvent
public static void onBlockRegistry(RegistryEvent.Register<Block> event) {
AutoRegistry.setBlockRegistry(event.getRegistry());
AutoRegistry.registryBlock();
}
/**
* 模型注册事件
*/
@SideOnly(Side.CLIENT)
@SubscribeEvent
public static void onModelRegistry(ModelRegistryEvent event) {
AutoRegistry.registryModel();
}
}
/**
* 注册事件监听器
*/
@Mod.EventBusSubscriber(modid = MOD_ID)
public class RegistryEventListener {
/**
* 物品注册事件消费
*/
@SubscribeEvent
public static void onItemRegistry(RegistryEvent.Register<Item> event) {
AutoRegistry.setItemRegistry(event.getRegistry());
AutoRegistry.registryItem();
}
/**
* 方块注册事件消费
*/
@SubscribeEvent
public static void onBlockRegistry(RegistryEvent.Register<Block> event) {
AutoRegistry.setBlockRegistry(event.getRegistry());
AutoRegistry.registryBlock();
}
/**
* 模型注册事件
*/
@SideOnly(Side.CLIENT)
@SubscribeEvent
public static void onModelRegistry(ModelRegistryEvent event) {
AutoRegistry.registryModel();
}
}