Interface BlockApiLookup<A,C>

Type Parameters:
A - The type of the API.
C - The type of the additional context object.

@NonExtendable public interface BlockApiLookup<A,C>
An object that allows retrieving APIs from blocks in a world. Instances of this interface can be obtained through get(net.minecraft.util.Identifier, java.lang.Class<A>, java.lang.Class<C>).

When trying to find(net.minecraft.world.World, net.minecraft.util.math.BlockPos, C) an API, the block or block entity at that position will be queried if it exists. If it doesn't exist, or if it returns null, the fallback providers will be queried in order.

Note: If you are going to query APIs a lot, consider using BlockApiCache, it may drastically improve performance.

Usage Example

Let us pretend we have the following interface that we would like to attach to some blocks depending on the direction.

 public interface FluidContainer {
     boolean containsFluids(); // return true if not empty
 }
Let us first create a static BlockApiLookup instance that will manage the registration and the query.

 public final class MyApi {
     public static final BlockApiLookup<FluidContainer, Direction> FLUID_CONTAINER = BlockApiLookup.get(new Identifier("mymod:fluid_container"), FluidContainer.class, Direction.class);
 }
Using that, we can query instances of FluidContainer:

 FluidContainer container = MyApi.FLUID_CONTAINER.find(world, pos, direction);
 if (container != null) {
     // Do something with the container
     if (container.containsFluids()) {
         System.out.println("It contains fluids!");
     }
 }
For the query to return a useful result, functions that provide an API for a block or a block entity must be registered.

 // If the block entity directly implements the interface, registerSelf can be used.
 public class ContainerBlockEntity implements FluidContainer {
     // ...
 }
 BlockEntityType<ContainerBlockEntity> CONTAINER_BLOCK_ENTITY_TYPE;
 MyApi.FLUID_CONTAINER.registerSelf(CONTAINER_BLOCK_ENTITY_TYPE);

 // For more complicated block entity logic, registerForBlockEntities can be used.
 // For example, let's provide a stored field, and only when the direction is UP:
 public class MyBlockEntity {
     public final FluidContainer upContainer;
     // ...
 }
 MyApi.FLUID_CONTAINER.registerForBlockEntities((blockEntity, direction) -> {
     if (direction == Direction.UP) { // only expose from the top
         // return a field
         return ((MyBlockEntity) blockEntity).upContainer;
     } else {
         return null;
     }
 }, BLOCK_ENTITY_TYPE_1, BLOCK_ENTITY_TYPE_2);

 // Without a block entity, registerForBlocks can be used.
 MyApi.FLUID_CONTAINER.registerForBlocks((world, pos, state, blockEntity, direction) -> {
     // return a FluidContainer for your block, or null if there is none
 }, BLOCK_INSTANCE, ANOTHER_BLOCK_INSTANCE); // register as many blocks as you want

 // Block entity fallback, for example to interface with another mod's FluidInventory.
 MyApi.FLUID_CONTAINER.registerFallback((world, pos, state, blockEntity, direction) -> {
     if (blockEntity instanceof FluidInventory) {
         // return wrapper
     }
     return null;
 });

 // General fallback, to interface with anything, for example another BlockApiLookup.
 MyApi.FLUID_CONTAINER.registerFallback((world, pos, state, blockEntity, direction) -> {
     // return something if available, or null
 });

Improving performance

When performing queries every tick, it is recommended to use BlockApiCache<A, C> instead of directly querying the BlockApiLookup.

 // 1) create and store an instance
 BlockApiCache<FluidContainer, Direction> cache = BlockApiCache.create(MyApi.FLUID_CONTAINER, serverWorld, pos);

 // 2) use it later, the block entity instance will be cached among other things
 FluidContainer container = cache.find(direction);
 if (container != null) {
     // ...
 }

 // 2bis) if the caller is able to cache the block state as well, for example by listening to neighbor updates,
 //       that will further improve performance.
 FluidContainer container = cache.find(direction, cachedBlockState);
 if (container != null) {
     // ...
 }

 // no need to destroy the cache, the garbage collector will take care of it

Generic context types

Note that FluidContainer and Direction were completely arbitrary in this example. We can define any BlockApiLookup&lt;A, C&gt;, where A is the type of the queried API, and C is the type of the additional context (the direction parameter in the previous example). If no context is necessary, Void should be used, and null instances should be passed.
  • Method Details

    • get

      static <A, C> BlockApiLookup<A,C> get(Identifier lookupId, Class<A> apiClass, Class<C> contextClass)
      Retrieve the BlockApiLookup associated with an identifier, or create it if it didn't exist yet.
      Parameters:
      lookupId - The unique identifier of the lookup.
      apiClass - The class of the API.
      contextClass - The class of the additional context.
      Returns:
      The unique lookup with the passed lookupId.
      Throws:
      IllegalArgumentException - If another apiClass or another contextClass was already registered with the same identifier.
    • find

      @Nullable default A find(World world, BlockPos pos, C context)
      Attempt to retrieve an API from a block in the world. Consider using BlockApiCache if you are doing frequent queries at the same position.

      Note: If the block state or the block entity is known, it is more efficient to use find(World, BlockPos, BlockState, BlockEntity, Object).

      Parameters:
      world - The world.
      pos - The position of the block.
      context - Additional context for the query, defined by type parameter C.
      Returns:
      The retrieved API, or null if no API was found.
    • find

      @Nullable A find(World world, BlockPos pos, @Nullable @Nullable BlockState state, @Nullable @Nullable BlockEntity blockEntity, C context)
      Attempt to retrieve an API from a block in the world. Consider using BlockApiCache if you are doing frequent queries at the same position.
      Parameters:
      world - The world.
      pos - The position of the block.
      context - Additional context for the query, defined by type parameter C.
      state - The block state at the target position, or null if unknown.
      blockEntity - The block entity at the target position if it is known, or null if it is unknown or does not exist.
      Returns:
      The retrieved API, or null if no API was found.
    • registerSelf

      void registerSelf(BlockEntityType<?>... blockEntityTypes)
      Expose the API for the passed block entities directly implementing it.

      Implementation note: this is checked at registration time by creating block entity instances using the passed types.

      Parameters:
      blockEntityTypes - Block entity types for which to expose the API.
      Throws:
      IllegalArgumentException - If the API class is not assignable from instances of the passed block entity types.
    • registerForBlocks

      void registerForBlocks(BlockApiLookup.BlockApiProvider<A,C> provider, Block... blocks)
      Expose the API for the passed blocks. The mapping from the parameters of the query to the API is handled by the passed BlockApiLookup.BlockApiProvider.
      Parameters:
      provider - The provider.
      blocks - The blocks.
    • registerForBlockEntity

      default <T extends BlockEntity> void registerForBlockEntity(BiFunction<? super T,C,@Nullable A> provider, BlockEntityType<T> blockEntityType)
      Expose the API for instances of the passed block entity type. The mapping from the parameters of the query to the API is handled by the passed provider. This overload allows using the correct block entity class directly.

      Note: The type is not used directly for detecting the supported blocks and block entities in the world, but it is converted to its BlockEntityType.blocks when this method is called. If the blocks field is empty, IllegalArgumentException is thrown.

      Type Parameters:
      T - The block entity class for which an API is exposed.
      Parameters:
      provider - The provider: returns an API if available in the passed block entity with the passed context, or null if no API is available.
      blockEntityType - The block entity type.
    • registerForBlockEntities

      void registerForBlockEntities(BlockApiLookup.BlockEntityApiProvider<A,C> provider, BlockEntityType<?>... blockEntityTypes)
      Expose the API for instances of the passed block entity types. The mapping from the parameters of the query to the API is handled by the passed BlockApiLookup.BlockEntityApiProvider. This overload allows registering multiple block entity types at once, but due to how generics work in java, the provider has to cast to the correct block entity class if necessary.

      Note: The type is not used directly for detecting the supported blocks and block entities in the world, but it is converted to its BlockEntityType.blocks when this method is called. If the blocks field is empty, IllegalArgumentException is thrown.

      Parameters:
      provider - The provider.
      blockEntityTypes - The block entity types.
    • registerFallback

      void registerFallback(BlockApiLookup.BlockApiProvider<A,C> fallbackProvider)
      Expose the API for all queries: the provider will be invoked if no object was found using the block or block entity providers. This may have a big performance impact on all queries, use cautiously.
      Parameters:
      fallbackProvider - The fallback provider.
    • getId

      Identifier getId()
      Return the identifier of this lookup.
    • apiClass

      Class<A> apiClass()
      Return the API class of this lookup.
    • contextClass

      Class<C> contextClass()
      Return the context class of this lookup.
    • getProvider

      Return the provider for the passed block (registered with one of the register functions), or null if none was registered (yet). Queries should go through find(net.minecraft.world.World, net.minecraft.util.math.BlockPos, C), only use this to inspect registered providers!