Skip to content

Registering and using a component

Pyrofab edited this page Oct 21, 2020 · 15 revisions

1) Implementing your component

To get started, you only need a class implementing the Component interface. It is good practice to make an interface for your component separate from the implementation, so that internals get properly encapsulated and so that the component itself can be used as an API by other mods.

Example:

public interface IntComponent extends Component {
    int getValue();
}

class RandomIntComponent implements IntComponent {
    private int value = (int) (Math.random() * 20);
    @Override public int getValue() { return this.value; }
    @Override public void readFromNbt(CompoundTag tag) { this.value = tag.getInt("value"); }
    @Override public void writeToNbt(CompoundTag tag) { tag.putInt("value", this.value); }
}

A Component implementation can also implement some extended interfaces to help with tasks such as server-client synchronization or data transfers. More information on those interfaces is available in the javadoc and in the documentation for each module.

2) Registering your component

Components are provided by various objects through the ComponentProvider interface. To interact with those, you need to obtain a ComponentKey instance - a unique key made up of an identifier and of the component's type information. Such an instance can be retrieved in a few ways, all using the ComponentRegistry. Note that the same Component implementation can be reused between several ComponentKeys.

Since 2.4, components are declared and attached statically. These components will have runtime-generated dedicated fields in the relevant component containers, guaranteeing a ComponentKey#get performance comparable to direct field access.

To register a component, you must first declare your component type id in your fabric.mod.json's custom properties:

{
    "schemaVersion": 1,
    "id": "mymod",

    "custom": {
        "cardinal-components": [
            "mymod:magik"
        ]
    }
}

It is safe to declare a component type that belongs to another mod in this array. It is also safe to declare an id that may not be registered at runtime.

Then, to retrieve the ComponentKey :

// retrieving a type for my component or for a required dependency's
public static final ComponentKey<IntComponent> MAGIK = 
        ComponentRegistry.getOrCreate(new Identifier("mymod", "magik"), IntComponent.class);

Retrieval of an already registered type

If your mod uses another mod's component, you may not want to (or may not be able to) register it yourself - in which case you can use the get method (note that it returns null if the component was not registered).

// retrieving a component type registered by an optional dependency
public static final Lazy<@Nullable ComponentKey<?>> BLUE = 
        new Lazy<>(() -> ComponentRegistry.get(new Identifier("theirmod:blue")));

3) Attaching your component

Components use component factories that will be used to initialize generated ComponentContainer fields.

Those factories are registered through dedicated entrypoints, which are typically implemented as follows:

public final class MyComponents implements XComponentInitializer[, YComponentInitializer...] {
    public static final ComponentKey<IntComponent> MAGIK = ...;

    @Override
    public void registerXComponentFactories(XComponentFactoryRegistry registry) {
        registry.register(MAGIK, XIntComponent::new);
    }
}

Where X is one of the possible component providers (eg. EntityComponentInitializer, ItemComponentInitializer).

The component registrar should then be added as an entrypoint to the fabric.mod.json file, with the component module's name as a key. For example:

{
    "schemaVersion": 1,
    "id": "mymod",

    "entrypoints": {
        "cardinal-components-entity": [
            "a.b.c.MyComponents"
        ]
    },

    "custom": {
        "cardinal-components": [
            "mymod:magik"
        ]
    }
}

Component ordering

Components are added to a provider in the order they were registered. This ordering is reflected in (de)serialization, synchronization, and ticking. Currently, components are initialized all at once, which means a component cannot reference another component in its constructor (unless the latter is attached to another provider).