Skip to content
Advertisement

Offload all “ImmutableMap/List” build work to compile-time?

NOTE: This isn’t specific to Minecraft Fabric. I’m just new to rigid pre-runtime optimization.

I’m writing an API hook for Minecraft mods that allows the mapping of various tasks to a Villager’s “profession” attribute, allowing other mods to add custom tasks for custom professions. I have all of the backend code done, so now I’m worried about optimization.

I have an ImmutableMap.Builder<VillagerProfession, VillagerTask> that I’m using to store the other mods’ added tasks. Problem is, while I know that the “put” method will never be called at runtime, I don’t know if the compiler does. Obviously, since this is a game and startup times in modpacks are already long, I’d like to optimize this as much as possible, since it will be used by every mod that wishes to add a new villager task.

Here’s my current source code for the “task registry”:

private static final ImmutableMap.Builder<VillagerProfession, ImmutableList<Pair<Task<? super VillagerEntity>, Integer>>> professionToVillagerTaskBuilder = ImmutableMap.builder();
    
    private static final ImmutableMap<VillagerProfession, ImmutableList<Pair<Task<? super VillagerEntity>, Integer>>> professionToVillagerTaskMap;
    
    // The hook that any mods will use in their source code
    public static void addVillagerTasks(VillagerProfession executingProfession, ImmutableList<Pair<Task<? super VillagerEntity>, Integer>> task)
    {
        professionToVillagerTaskBuilder.put(executingProfession, task);
    }
    
    //The tasklist retrieval method used at runtime
    static ImmutableList<Pair<Task<? super VillagerEntity>, Integer>> getVillagerRandomTasks(VillagerProfession profession)
    {
        return professionToVillagerTaskMap.get(profession);
    }
    
    static { // probably not the correct way to do this, but it lets me mark the map as final
        professionToVillagerTaskMap = professionToVillagerTaskBuilder.build();
    }

Thanks!

Advertisement

Answer

The brief answer is: you can’t do what you want to do.

Problem is, while I know that the “put” method will never be called at runtime, I don’t know if the compiler does.

The put method has to be called at runtime for your mod to be useful. By the time your code is being loaded in a form that it can be executed — that’s runtime. It may be the setup phase for your mod, but it’s running in a JVM.

If the source code doesn’t contain the registry itself, then the compiler can’t translate it to executable code; it can’t optimize something it doesn’t know exists. You (the developer) can’t know what mods will be loading, hence the compiler can’t know, hence it can’t optimize or pre-calculate it. That’s the price you pay for dynamic loading of code.


As for the code you put up: it won’t work.

The static block is executed when the class is loaded. Think of it as a constructor for your class instead of the objects. By the time a mod can call any of its methods, the class has to be loaded, and its static blocks will already have been executed. Your map will be set and empty before any method is called from the outside. All tasks added will forever linger in the builder, unused, unseen, unloved.

Keep the builder. Let mods add their entries to it. Then, when all mod-loading is done and the game starts, call build() and use the result as a registry. (Use whichever ‘game is starting’ hook your modding framework provides.)

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement