Skip to content

前言

Forge定义物品、方块等元数据后,需要主动进行大量的注册操作:

  1. 物品:注册物品、注册模型
  2. 方块:注册方块、注册实体、注册物品方块、注册物品方块模型

为了简化操作并节省工作量,决定编写一套机制实现物品、方块、附魔等元数据的自动注册。

设计

自动注册机制的实现参考了Spring BootBean加载,在自动注册实现类中堆外暴露一个静态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标注的方法实现。

实现

第一步,使用GuavaClassPath扫描入口类所在包下的所有类,并保存到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();
    }
}