-
Notifications
You must be signed in to change notification settings - Fork 41
Registering and using a component
Once you are done writing your own implementation of the Component
interface, you still need to register it for it to function. This is done in two steps : the registration of the component itself, and the registration of one or more factories to attach it to the desired objects.
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 ComponentKey
s.
Since version 2.4 of Cardinal Components API, components are declared and attached statically. Component containers will hold those components in runtime-generated dedicated fields, guaranteeing a ComponentKey#get
performance comparable to direct field access (that's fast). This however means that all component types must be known before the first ComponentKey
is created.
To register a component, you must first declare your component's identifier in your mod's metadata.
Note: It is safe to declare a component type that belongs to another mod. It is also safe to declare an id that may not be registered at runtime.
Declare your component's identifier in your quilt.mod.json
at the top level. This is done by adding the identifier to an array of strings with the key "cardinal-components"
. For more information on the structure of the quilt.mod.json
file, refer to the Quilt RFC.
For example, if your mod uses a component with the id "mymod:magik"
, your quilt.mod.json
should contain the following custom field (in addition to everything else that is already there) :
{
"schema_version": 1,
"quilt_loader": {
"id": "mymod"
},
"cardinal-components": [
"mymod:magik"
]
}
Declare your component's identifier in your fabric.mod.json
's custom
properties. This is done by adding the identifier to an array of strings with the key "cardinal-components"
. For more information on the structure of the fabric.mod.json
file, refer to the fabric wiki.
For example, if your mod uses a component with the id "mymod:magik"
, your fabric.mod.json
should contain the following custom field (in addition to everything else that is already there) :
{
"schemaVersion": 1,
"id": "mymod",
"custom": {
"cardinal-components": [
"mymod:magik"
]
}
}
Then, to retrieve the ComponentKey
, you need to call ComponentRegistry.getOrCreate(Identifier, Class)
. The first argument is the identifier you put in your fabric.mod.json
. The second argument should be the component class you wish to use. This class should be the superclass or superinterface of all implementations you may use with the resulting key (eg. MyComponent
, not MyComponentImpl
).
For example, if you created an IntComponent
class like in the Implementing the Component interface page, you would retrieve the corresponding key as such :
// 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);
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). If you retrieve a component through this method, you do not have to declare it in your fabric.mod.json
metadata.
The following example demonstrates retrieving a component that another mod registers with the identifier theirmod:blue
:
// retrieving a component type registered by an optional dependency
public static final Lazy<@Nullable ComponentKey<?>> BLUE =
new Lazy<>(() -> ComponentRegistry.get(new Identifier("theirmod:blue")));
Component containers use component factories to initialize their generated component 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
).
Note:XIntComponent::new
is a constructor reference. You can also write it -> new XIntComponent()
if your constructor does not take parameters.
The component registrar should then be added as an entrypoint to your mod's metadata.
Entrypoint documentation: https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md#the-entrypoints-field
{
"schema_version": 1,
"quilt_loader": {
"id": "mymod",
"entrypoints": {
"cardinal-components": "a.b.c.MyComponents"
},
},
"custom": {
"cardinal-components": [
"mymod:magik"
]
}
}
Entrypoint documentation: https://fabricmc.net/wiki/documentation:entrypoint
{
"schemaVersion": 1,
"id": "mymod",
"entrypoints": {
"cardinal-components": [
"a.b.c.MyComponents"
]
},
"custom": {
"cardinal-components": [
"mymod:magik"
]
}
}
Instead of the universal entrypoint, you can also use the component module's name as a key, for example:
"entrypoints": {
"cardinal-components-entity": [
"a.b.c.MyComponents"
],
"cardinal-components-world": [
"a.b.c.MyComponents::registerWorldComponents"
]
},
Note that this is the only way to use method references as registration entrypoints.
Components are added to a provider in the order they were registered. Some modules (currently cardinal-components-entity
and cardinal-components-block
) also support explicit ordering through Registration#after(ComponentKey)
. 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).
Once the component is getting attached to at least one provider, you can access it with the ComponentKey
you obtained earlier. To do this, simply pass an object of the right type to ComponentKey#get
or one of its variants (ComponentKey#maybeGet
and ComponentKey#getNullable
).
The following example demonstrates getting an IntComponent
- as written in the Implementing the Component interface page - that was attached to instances of the Entity
class using the cardinal-components-entity
module.
public static final ComponentKey<IntComponent> MAGIK = ...; // See the "Registering your component" section
public static void useMagik(Entity provider) { // anything will work, as long as a module allows it!
// Retrieve a provided component
int magik = MAGIK.get(provider).getValue();
// Or, if the object is not guaranteed to provide that component:
int magik = MAGIK.maybeGet(provider).map(IntComponent::getValue).orElse(0);
// ...
}
Note: if you are using Loom 0.11+ and CCA 4.1+, you can see which objects implement ComponentProvider
. Otherwise, you can assume objects implement it behind the scenes as long as you have a corresponding module (eg. if you have the cardinal-components-world
module, any World
object can be passed to ComponentKey#get
)