Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Capability system #112

Merged
merged 6 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/capability/Capabilities.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.gtnewhorizon.gtnhlib.capability;

import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.ForgeDirection;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

// spotless:off
/**
* Utility class for retrieving capabilities from various Minecraft objects. This class provides convenience methods
* that handle both capability-based and direct interface implementations, ensuring backward compatibility with
* traditional interface implementations.
* <p>
* Example usage:
* <pre>{@code
* // Retrieve capability from a TileEntity
* MyInterface impl = Capabilities.getCapability(tileEntity, MyCapability.class, ForgeDirection.NORTH);
*
* // Retrieve capability from an ItemStack
* MyInterface impl = Capabilities.getCapability(itemStack, MyCapability.class);
*
* // Retrieve capability from an Entity
* MyInterface impl = Capabilities.getCapability(entity, MyCapability.class);
* }</pre>
*
* @see CapabilityProvider
*/
// spotless:on
@SuppressWarnings("unused")
public final class Capabilities {

/**
* Retrieves a capability from the given TileEntity.
*
* @param tileEntity The TileEntity to query.
* @param capability The capability being requested.
* @param side The side of the TileEntity being queried.
* @param <T> The type of the capability interface.
* @return The capability implementation, or null if not available.
*/
public static <T> T getCapability(@Nullable TileEntity tileEntity, @NotNull Class<T> capability,
@NotNull ForgeDirection side) {
return getCapability((Object) tileEntity, capability, side);
}

/**
* Retrieves a capability from the given TileEntity without specifying a side.
*
* @param tileEntity The TileEntity to query.
* @param capability The capability being requested.
* @param <T> The type of the capability interface.
* @return The capability implementation, or null if not available.
*/
public static <T> T getCapability(@Nullable TileEntity tileEntity, @NotNull Class<T> capability) {
return getCapability((Object) tileEntity, capability, ForgeDirection.UNKNOWN);
}

/**
* Retrieves a capability from the given ItemStack's Item.
*
* @param itemStack The ItemStack to query.
* @param capability The capability being requested.
* @param <T> The type of the capability interface.
* @return The capability implementation, or null if not available.
*/
public static <T> T getCapability(@Nullable ItemStack itemStack, @NotNull Class<T> capability) {
if (itemStack == null) {
return null;
}
return getCapability(itemStack.getItem(), capability, ForgeDirection.UNKNOWN);
}

/**
* Retrieves a capability from the given Entity.
*
* @param entity The Entity to query.
* @param capability The capability being requested.
* @param <T> The type of the capability interface.
* @return The capability implementation, or null if not available.
*/
public static <T> T getCapability(@Nullable Entity entity, @NotNull Class<T> capability) {
return getCapability(entity, capability, ForgeDirection.UNKNOWN);
}

/**
* Internal utility method that tries a direct cast if the object matches the capability's interface, then falls
* back to querying {@link CapabilityProvider} if available.
*
* @param object The object to query.
* @param capability The capability being requested.
* @param side The side from which the capability is being requested.
* @param <T> The type of the capability interface.
* @return The capability implementation, or null if not available.
*/
private static <T> T getCapability(@Nullable Object object, @NotNull Class<T> capability,
@NotNull ForgeDirection side) {
if (object == null) {
return null;
}
if (capability.isAssignableFrom(object.getClass())) {
return capability.cast(object);
}
if (object instanceof CapabilityProvider provider) {
return provider.getCapability(capability, side);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.gtnewhorizon.gtnhlib.capability;

import net.minecraftforge.common.util.ForgeDirection;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

// spotless:off
/**
* Capability represents the ability of an object to expose a specific interface without directly implementing it,
* allowing for a more flexible and decoupled approach compared to the traditional approach.
* It is particularly useful when dealing with complex systems where objects may need to dynamically provide
* different functionalities or when working with complex patterns, like delegating responsibilities to another class.
* <p>
* By implementing this interface, other systems are able to query and retrieve capabilities
* without relying on direct interface implementations.
* <p>
* Note that it is required to replace all the {@code instanceof} checks with capability checks in order to
* ensure compatibility when migrating from direct interface implementation to capability.
* Avoid using capabilities for widely-used interfaces like {@code IFluidHandler} to prevent compatibility issues
* with other mods.
* <p>
* Example usage:
* <pre>{@code
* public class MyTileEntity implements CapabilityProvider {
* private MyCapability myCapability = new MyCapabilityImplementation();
*
* @Override
* public <T> T getCapability(@NotNull Class<T> capability, @NotNull ForgeDirection side) {
* if (capability == MyCapability.class) {
* return capability.cast(myCapability);
* }
* return null;
* }
* }
* }</pre>
*
* @see Capabilities
*/
// spotless:on
public interface CapabilityProvider {

/**
* Queries this provider for a capability implementation.
* <p>
* This method should:
* <ul>
* <li>Compare the requested capability with known capabilities using {@code ==} or do HashMap dispatch for a larger
* quantity of supported capability types.</li>
* <li>If matched, use {@link Class#cast} to return the implementation.</li>
* <li>Return null if the capability is not supported.</li>
* </ul>
*
* @param capability The capability being requested.
* @param side The {@link ForgeDirection} from which the capability is requested. Can be
* {@link ForgeDirection#UNKNOWN UNKNOWN} if the direction is not relevant.
* @param <T> The type of the capability interface.
* @return The capability implementation, or null if not available.
*/
@Nullable
<T> T getCapability(@NotNull Class<T> capability, @NotNull ForgeDirection side);
}
Loading