User:TheSilkMiner/Capabilities
Capabilities are a Forge system that allows cross-mod interactions by allowing capability providers to dynamically respect contracts and provide specialized behavior without requiring the implementation of many interfaces or hard dependencies on mods.
History
In an ideal world, all that would be needed for a mod to provide the equivalent of a capability would be implementing an interface. This is in fact how cross-mod interaction used to work prior to the introduction of capabilities.
The real world, though, is often much more complicated: users wanted to be free to combine mods the way they wanted and saw fit, and developers wanted to be able to declare soft dependencies on other mods, thus reducing the need of having a huge mod pack just for testing.
The first approach used by Forge was conditional stripping of interfaces and methods, but this proved to be problematic. While the idea works well in theory, in practice the ASM editing of classes relied on complex mechanics and could lead to hard to spot bugs.
For this reason, the entire system was redesigned and the concept of capabilities was born.
The Concept
A capability allows any capability provider to conditionally expose a certain ability to do something, e.g. accepting power or handling items. A capability provider, moreover, can decide to expose a capability only on certain sides, allowing for easy interactions with hoppers, cables, etc.
Capabilities may also be added and removed dynamically both from the "owner" of the capability provider and other mods, allowing even easier cross-mod interaction. For example, a mod that isn't compatible with Forge Energy could be converted into one by dynamically attaching the Forge Energy capability and handling the conversion to a third-party energy system without having to alter the original mod.
Terminology
The high flexibility of the system comes with a cost, though, which is terminology. The following section wants to be a dictionary of sorts, defining all the terms that you may come across when dealing with capabilities.
In the rest of this article, we will refer to these terms frequently, so make sure you are familiar with them.
- Capability: the ability to perform something. In-code this is represented by the
Capability
class. - Capability Provider: something that is able to support capabilities and provides a mean of accessing them. In-code they are represented by implementations of
ICapabilityProvider
. There are multiple kinds of capability providers:- Volatile Provider: a provider that doesn't persist data to disk; once the provider ceases to exist for any number of reasons, all capability data gets deleted.
- Persistent Provider: a provider that requires all capabilities to serialize data to disk, in order to persist data even across game restarts. They implement the
INBTSerializable
interface. - Agnostic Provider: a provider that isn't neither volatile nor persistent, rather delegates the decision either to the capability directly or to sub-implementations. They also implement the
INBTSerializable
interface.
- Capability Interface: the interface that defines the contract of the capability, so what operations the capability exposes.
- Capability Implementation: one of the possibly many implementations of the capability interface, that actually carries out the work; one of the various implementations may also be considered the default capability implementation.
- Capability Storage: the manager that handles loading and storing persistent capabilities data from and to disk, guaranteeing preservation of information; in-code this is represented by an implementation of the
Capability.IStorage
interface.
The wary reader may note that both persistent and agnostic providers are represented the same way in code. In fact, the only difference between them comes from pure semantics in how their serialization methods are designed. This will be further discussed in their respective sections.
Moreover, it is also common to refer to the capability interface as simply the capability. While not strictly correct, due to common usage we will also use this convention. So, to refer to the capability interface MyCapability
, we will usually talk about the "MyCapability
capability".
Forge-provided Capabilities and Providers
In order to ensure mods can work together seamlessly, Forge provides a set of default capabilities and capability providers.
The default capability providers in a Forge environment are: TileEntity
, Entity
, ItemStack
, World
, and Chunk
. These are all agnostic providers, since they don't mandate any sort of capability persistency requirements. Rather, it is the job of whoever subclasses these providers to deal with either volatile or non-volatile capabilities.
The default capabilities that forge provides are represented by the interfaces IItemHandler
, IFluidHandler
, IFluidHandlerItem
, IEnergyStorage
, and IAnimationStateMachine
. Each one of these capabilities will be discussed in the corresponding section.
IItemHandler
The IItemHandler
capability refers to the ability for any capability provider to have some sort of internal inventory with a certain number of slots, from which items can be inserted and extracted. It is also possible, though, to expose this capability even if no such inventory is present as long as the capability provider can emulate its presence (e.g. tools that allow accessing remote inventories).
This effectively replaces the vanilla interfaces IInventory
and ISidedInventory
. These interfaces are in fact retained only to allow vanilla code to compile and should not be used in mod code. This extends to anything that implements those vanilla interfaces, such as LockableLootTileEntity
.
A default reference implementation for this capability interface is provided in ItemStackHandler
.
IFluidHandler
The IFluidHandler
capability refers to the ability for any capability provider to handle and store fluids in one or multiple fluid tanks. It is effectively the equivalent in terms of fluids of the IItemHandler
capability.
A default reference implementation for this capability interface is provided in TileFluidHandler
.
IFluidHandlerItem
The IFluidHandlerItem
capability referes to the ability for an ItemStack
capability provider to handle and store fluids in one or multiple fluid tanks. It is basically a specialized version of the IFluidHandler
capability that allows ItemStack
s to define a custom container.
IEnergyStorage
The IEnergyStorage
capability refers to the ability for any capability provider to store, consume, and produce energy. This capability is the base capability for what's commonly known in the modded world as Forge Energy (or FE), i.e. the energy system most mods use. Its internal design is heavily based on the (now defunct) Redstone Flux Energy API, supporting both a push and pull system.
A default reference implementation for this capability interface is provided in EnergyStorage
.
IAnimationStateMachine
The IAnimationStateMachine
capability refers to the ability for any capability provider to leverage the Forge Animation State Machine API for animations.
Working with Capabilities
Both capability providers and users need to be able to provide and access capabilities through a common framework, otherwise the ideal of dynamic and mod-agnostic would not really exist. For this reason, both capability providers and capability accessors (which we define as everything that wants to access a capability), also known as clients, need to work together and with Forge to ensure that the common interface is used sensibly and correctly by all parties.
Obtaining a Capability
Before being able to work with a capability, it is necessary to obtain an instance of the Capability
object itself. Since these objects are created by Forge and there is only one unique instance for each capability that may exist, this instance cannot be obtained by "common" means. Forge provides two different methods of obtaining such instances: injecting into a field, or a callback method.
Injecting into a Field
Capability
can be injected automatically into a field as soon as they get created by Forge, following the principle commonly known as dependency injection. This provides less flexibility, since it doesn't notify the user that the capability has been injected nor runs arbitrary code. Nevertheless, it is suggested to use this method instead of the callback approach.
To inject the Capability
into a field, all that's needed is to declare a static
field of type Capability<T>
, where T
represents the capability interface, and annotate it with @CapabilityInject(T.class)
.
For a more practical example, consider the following snippet:
@CapabilityInject(IItemHandler.class) public static Capability<IItemHandler> ITEM_HANDLER_CAPABILITY = null;
The above code will let Forge know that the field ITEM_HANDLER_CAPABILITY
should be injected with the unique instance of the IItemHandler
capability. Assigning the field to null
allows us to provide a reasonable fallback in case the capability we want hasn't been registered yet.
This injection is, for obvious reasons, redundant, since that capability is also available through CapabilityItemHandler
.
Declaring a Callback
Another option is to declare a callback method, meaning a method that will be called with the value of the
desired Capability
once the instance is available. This gives more flexibility since the method may perform a number of arbitrary actions with the received instance prior to storing it in a field, or may even discard the capability entirely if wanted. Nevertheless, the usage of a field instead of a method is encouraged as a matter of style.
To use a method as a callback, the method must be declared as static
and accepting a single parameter of type Capability<T>
, where T
represents the capability interface. The method should also be annotated with @CapabilityInject(T.class)
.
For a more practical example, consider the following snippet:
public static Capability<IEnergyStorage> ENERGY = null; @CapabilityInject(IEnergyStorage.class) private static void onEnergyStorageInit(Capability<IEnergyStorage> capability) { LOGGER.info("Received IEnergyStorage capability '{}': enabling Forge Energy support", capability); ENERGY = capability; }
The above code declares a callback method that will be invoked when a Capability
instance for IEnergyStorage
is available. The callback then prints a log message and stores the capability into a public
field for accessibility. The field is initialized to null
to provide a reasonable fallback in case the capability does not exist.
This callback is, for obvious reasons, redundant, since that capability is also available through CapabilityEnergy
.
Exposing a Capability
Exposing a capability is a voluntary act by a capability provider that allows the capability to be discovered and accessed by clients.
To do so, a capability provider needs to juggle a couple more moving pieces to ensure that the capability state remains consistent and that the lookup remains fast. It is in fact possible for a capability provider to be asked to provide many capabilities many times in the same tick. For this reason, a provider is asked to do the following:
- the
LazyOptional
s that get returned must be cached; - if a capability changes exposure state (more on this later), all listeners must be notified;
- if a capability gets invalidated (more on this later), all listeners must be notified
- the lookup inside
getCapability
must be performed with anif
-else
chain; - all unexposed but still present capabilities should be available if the provider is queried with a
null
direction (see Accessing a Capability for more information); - if no capability of a given type is available or accessible, the provider must call
super
as long as it is possible to do so.
Capability providers must also reflect changes in the exposure state of a capability, meaning that if the accessibility of a capability from a certain Direction
changes (refer to Accessing a Capability for more information), it is the provider's responsibility to trigger a state response by invalidating the returned LazyOptional
and caching a new one. This should also be performed when a capability gets invalidated, such as when a capability provider gets removed.
With all of the above in mind, part of a capability provider implementation may be similar to the following snippet:
// suppose the presence of a field 'inventory' of type 'IItemHandler' private static final LazyOptional<IItemhandler> INVENTORY_OPTIONAL = LazyOptional.of(() -> inventory); @Override public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction direction) { if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY && (direction == null || direction == Direction.UP || direction == Direction.DOWN)) { return INVENTORY_OPTIONAL.cast(); } return super.getCapability(capability, direction); // See note after snippet } @Override protected void invalidateCaps() { super.invalidateCaps(); INVENTORY_OPTIONAL.invalidate(); }
This possible implementation of a capability provider exposes an IItemHandler
capability and restricts access only to the UP
and DOWN
directions. If we assume this capability provider is a TileEntity
, then we may also say that the inventory is only accessible from the top and the bottom of the block.
Moreover, the capability gets automatically invalidated when the provider gets invalidated. Assuming this is a TileEntity
, this usually happens when the block gets removed from the world, either because it's too far away or it simply got broken.
The super
call at the end of the getCapability
method is extremely important, since it's what allows Attaching external Capabilities to capability providers. Nevertheless, it is not always possible to invoke super
: in those cases, an empty LazyOptional
should be returned. For more information on when and why this is needed refer to The Capability Provider section.
Attaching a Capability
Accessing a Capability
Creating Custom Capabilities
The Capability Interface and the Capability Implementation
The Capability Storage
The Capability Provider
Tying it All Together
Code Examples
OLD SHIT FOLLOWS
Capabilities allow exposing features in a dynamic and flexible way, without having to resort to directly implementing many interfaces.
In general terms, each capability provides a feature in the form of an interface, alongside with a default implementation which can be requested, and a storage handler for at least this default implementation. The storage handler can support other implementations, but this is up to the capability implementor, so look it up in their documentation before trying to use the default storage with non-default implementations.
Forge adds capability support to TileEntities
, Entities
, ItemStack
s, World
s and Chunk
s, which can be exposed either by attaching them through an event or by overriding the capability methods in your own implementations of the objects. This will be explained in more detail in the following sections.
Forge-provided Capabilities
Forge provides three capabilities: IItemHandler
, IFluidHandler
and IEnergyStorage
.
IItemHandler
exposes an interface for handling inventory slots. It can be applied to TileEntities
(chests, machines, etc.), Entities
(extra player slots, mob/creature inventories/bags), or ItemStacks
(portable backpacks and such). It replaces the old IInventory
and ISidedInventory
with an automation-friendly system.
IFluidHandler
exposes an interface for handling fluid inventories. It can also be applied to TileEntities
Entities
, or ItemStacks
. It replaces the old IFluidHandler
with a more consistent and automation-friendly system.
IEnergyStorage
exposes an interface for handling energy containers. It can be applied to TileEntities
, Entities
or ItemStacks
. It is based on the RedstoneFlux API by TeamCoFH.
Using an Existing Capability
As mentioned earlier, TileEntities
, Entities
, and ItemStacks
implement the capability provider feature, through the ICapabilityProvider
interface. This interface adds the method getCapability
, which can be used to query the capabilities present in the objects.
In order to obtain a capability, you will need to refer it by its unique instance. In the case of the IItemHandler
, this capability is primarily stored in CapabilityItemHandler#ITEM_HANDLER_CAPABILITY
, but it is possible to get other instance references by using the @CapabilityInject
annotation.
@CapabilityInject(IItemHandler.class) static Capability<IItemHandler> ITEM_HANDLER_CAPABILITY = null;
This annotation can be applied to fields and methods. When applied to a field, it will assign the instance of the capability (the same one gets assigned to all fields) upon registration of the capability, and left to the existing value (null
), if the capability was never registered. Because local static field accesses are fast, it is a good idea to keep your own local copy of the reference for objects that work with capabilities. This annotation can also be used on a method, in order to get notified when a capability is registered, so that certain features can be enabled conditionally.
Both the getCapability
methods have a second parameter, of type Direction
, which can be used in the to request the specific instance for that one face. If passed null
, it can be assumed that the request comes either from within the block, or from some place where the side has no meaning, such as a different dimension. In this case a general capability instance that does not care about sides will be requested instead. The return type of getCapability
will correspond to the type declared in the capability passed to the method. For the item handler capability, this is indeed IItemHandler
.
Exposing a Capability
In order to expose a capability, you will first need an instance of the underlying capability type. Note that you should assign a separate instance to each object that keeps the capability, since the capability will most probably be tied to the containing object.
There’s two ways to obtain such an instance, through the Capability
itself, or by explicitly instantiating an implementation of it. The first method is designed to use a default implementation via Capability#getDefaultInstance
, if those default values are useful for you. In the case of the item handler capability, the default implementation will expose a single slot inventory, which is most probably not what you want.
The second method can be used to provide custom implementations. In the case of IItemHandler
, the default implementation uses the ItemStackHandler
class, which has an optional argument in the constructor, to specify a number of slots. However, relying on the existence of these default implementations should be avoided, as the purpose of the capability system is to prevent loading errors in contexts where the capability is not present, so instantiation should be protected behind a check testing if the capability has been registered (see the remarks about @CapabilityInject
in the previous section).
Once you have your own instance of the capability interface, you will want to notify users of the capability system that you expose this capability and provide a holder of the instance. This is done by overriding the getCapability
method, and comparing the instance with the capability you are exposing. If your machine has different slots based on which side is being queried, you can test this with the side
parameter. For Entities
and ItemStack
s, this parameter can be ignored, but it is still possible to have side as a context, such as different armor slots on a player (top side => head slot?), or about the surrounding blocks in the inventory (west => slot on the left?). Don’t forget to fall back to super
, otherwise the attached capabilities will stop working. Make sure to invalidate the holder of the instance at the end of the provider's lifecycle.
// Somewhere in your TileEntity subclass LazyOptional<IItemHandler> inventoryHandlerLazyOptional; // After initializing inventoryHandler inventoryHandlerLazyOptional = LazyOptional.of(() -> inventoryHandler); public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) { if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { return inventoryHandlerLazyOptional.cast(); } return super.getCapability(cap, side); } @Override protected void invalidateCaps() { super.invalidateCaps(); inventoryHandlerLazyOptional.invalidate(); }
Item
s are a special case since their capability providers are stored on an ItemStack
. Instead, a provider should be attached through Item#initCapabilities
when applicable. This should hold your capabilities for the lifecycle of the stack.
It is strongly suggested that direct checks in code are used to test for capabilities instead of attempting to rely on maps or other data structures, since capability tests can be done by many objects every tick, and they need to be as fast as possible in order to avoid slowing down the game.