Skip to content

Custom Builders

tterrag edited this page Feb 19, 2020 · 6 revisions

Creating Custom Builders

The problem: Registrate doesn't yet support the registry type I want to build, or I want to build mod-specific objects that I have a custom registry for.

Good news! Registrate is 100% open to extension, you just need to implement the builder class for your registry objects. The builders shipped with Registrate are not special in any way, other than that they get convenience methods in Registrate. You can extend the Registrate class to add your own helper methods.

Creating the Builder

To create custom builders, it's recommended to extend AbstractBuilder, as it provides a lot of convenience methods and basic functionality. But it is also possible to implement Builder directly, if needed.

A simple builder implementation looks like this:

public class CustomObjectBuilder<T extends CustomObject, P> extends AbstractBuilder<CustomObject, T, P, CustomObjectBuilder<T, P>> {
    
    private final Supplier<? extends T> factory;

    public CustomObjectBuilder(Registrate owner, P parent, String name, BuilderCallback callback, Supplier<? extends T> factory) {
        super(owner, parent, name, callback, CustomObject.class);
        this.factory = factory;
    }

    @Override
    protected T createEntry() {
        return factory.get();
    }
}

You may notice that Builder (and AbstractBuilder) has a lot of generic type variables. It may seem like a lot at first glance, but there's nothing very complicated here. Let's go over each type.

  • R, the first type, is used for the "registry type" of the builder. This is the concrete base class that all registry objects must extend, and the type used for the forge registry itself. It's most likely your class that extends ForgeRegistryEntry. In this case, it's CustomObject.
  • T is the "actual type". This is redefined by the current class so that it can be whatever the type of the object currently being built is.
  • P is the type of the builder's parent. This is simply the type of object that will be returned back from build(). Most of the time it will be Registrate, but can be different when builders create other builders.
  • S, finally, is simply a self type. It recursively references the current type, so that the right builder type can be returned from methods defined in superclasses, such as addData.

Using the Builder

Now that you have a custom builder, it can be plugged in to Registrate using the entry methods.

public static final RegistryObject<CustomObject> CUSTOM_OBJECT = REGISTRATE.object("custom_object")
        .entry((name, callback) -> new CustomObjectBuilder<>(REGISTRATE, REGISTRATE, name, callback, CustomObject::new))
        .register();

Of course, this is a lot of boilerplate. You can make things easier by adding utility methods in your own custom Registrate subclass, such as:

public <T extends CustomObject> CustomObjectBuilder<T, Registrate> customObject(Supplier<? extends T> factory) {
    return customObject(this, factory);
}

public <T extends CustomObject, P> CustomObjectBuilder<T, P> customObject(P parent, Supplier<? extends T> factory) {
    return customObject(parent, currentName(), factory);
}

public <T extends CustomObject, P> CustomObjectBuilder<T, P> customObject(P parent, String name, Supplier<? extends T> factory) {
    return entry(name, callback -> new CustomObjectBuilder<>(this, parent, name, callback, factory));
}

Or whatever is most convenient. Now this can be used just like any other builder.

public static final RegistryObject<CustomObject> CUSTOM_OBJECT = CUSTOM_REGISTRATE.object("custom_object")
        .customObject(CustomObject::new)
        .register();
Clone this wiki locally