|
| 1 | +/* |
| 2 | + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package net.fabricmc.fabric.api.lookup.v1.block; |
| 18 | + |
| 19 | +import org.jetbrains.annotations.ApiStatus; |
| 20 | +import org.jetbrains.annotations.Nullable; |
| 21 | + |
| 22 | +import net.minecraft.block.Block; |
| 23 | +import net.minecraft.block.BlockState; |
| 24 | +import net.minecraft.block.entity.BlockEntity; |
| 25 | +import net.minecraft.block.entity.BlockEntityType; |
| 26 | +import net.minecraft.util.Identifier; |
| 27 | +import net.minecraft.util.math.BlockPos; |
| 28 | +import net.minecraft.world.World; |
| 29 | + |
| 30 | +import net.fabricmc.fabric.impl.lookup.block.BlockApiLookupImpl; |
| 31 | + |
| 32 | +/** |
| 33 | + * An object that allows retrieving APIs from blocks in a world. |
| 34 | + * Instances of this interface can be obtained through {@link #get}. |
| 35 | + * |
| 36 | + * <p>When trying to {@link BlockApiLookup#find} an API, the block or block entity at that position will be queried if it exists. |
| 37 | + * If it doesn't exist, or if it returns {@code null}, the fallback providers will be queried in order. |
| 38 | + * |
| 39 | + * <p>Note: If you are going to query APIs a lot, consider using {@link BlockApiCache}, it may drastically improve performance. |
| 40 | + * |
| 41 | + * <p><h3>Usage Example</h3> |
| 42 | + * Let us pretend we have the following interface that we would like to attach to some blocks depending on the direction. |
| 43 | + * |
| 44 | + * <pre>{@code |
| 45 | + * public interface FluidContainer { |
| 46 | + * boolean containsFluids(); // return true if not empty |
| 47 | + * }}</pre> |
| 48 | + * Let us first create a static {@code BlockApiLookup} instance that will manage the registration and the query. |
| 49 | + * |
| 50 | + * <pre>{@code |
| 51 | + * public final class MyApi { |
| 52 | + * public static final BlockApiLookup<FluidContainer, Direction> FLUID_CONTAINER = BlockApiLookup.get(new Identifier("mymod:fluid_container"), FluidContainer.class, Direction.class); |
| 53 | + * }}</pre> |
| 54 | + * Using that, we can query instances of {@code FluidContainer}: |
| 55 | + * |
| 56 | + * <pre>{@code |
| 57 | + * FluidContainer container = MyApi.FLUID_CONTAINER.find(world, pos, direction); |
| 58 | + * if (container != null) { |
| 59 | + * // Do something with the container |
| 60 | + * if (container.containsFluids()) { |
| 61 | + * System.out.println("It contains fluids!"); |
| 62 | + * } |
| 63 | + * }}</pre> |
| 64 | + * For the query to return a useful result, functions that provide an API for a block or a block entity must be registered. |
| 65 | + * |
| 66 | + * <pre>{@code |
| 67 | + * // If the block entity directly implements the interface, registerSelf can be used. |
| 68 | + * public class ContainerBlockEntity implements FluidContainer { |
| 69 | + * // ... |
| 70 | + * } |
| 71 | + * BlockEntityType<ContainerBlockEntity> CONTAINER_BLOCK_ENTITY_TYPE; |
| 72 | + * MyApi.FLUID_CONTAINER.registerSelf(CONTAINER_BLOCK_ENTITY_TYPE); |
| 73 | + * |
| 74 | + * // For more complicated block entity logic, registerForBlockEntities can be used. |
| 75 | + * // For example, let's provide a stored field, and only when the direction is UP: |
| 76 | + * public class MyBlockEntity { |
| 77 | + * public final FluidContainer upContainer; |
| 78 | + * // ... |
| 79 | + * } |
| 80 | + * MyApi.FLUID_CONTAINER.registerForBlockEntities((blockEntity, direction) -> { |
| 81 | + * if (direction == Direction.UP) { // only expose from the top |
| 82 | + * // return a field |
| 83 | + * return ((MyBlockEntity) blockEntity).upContainer; |
| 84 | + * } else { |
| 85 | + * return null; |
| 86 | + * } |
| 87 | + * }, BLOCK_ENTITY_TYPE_1, BLOCK_ENTITY_TYPE_2); |
| 88 | + * |
| 89 | + * // Without a block entity, registerForBlocks can be used. |
| 90 | + * MyApi.FLUID_CONTAINER.registerForBlocks((world, pos, state, blockEntity, direction) -> { |
| 91 | + * // return a FluidContainer for your block, or null if there is none |
| 92 | + * }, BLOCK_INSTANCE, ANOTHER_BLOCK_INSTANCE); // register as many blocks as you want |
| 93 | + * |
| 94 | + * // Block entity fallback, for example to interface with another mod's FluidInventory. |
| 95 | + * MyApi.FLUID_CONTAINER.registerFallback((world, pos, state, blockEntity, direction) -> { |
| 96 | + * if (blockEntity instanceof FluidInventory) { |
| 97 | + * // return wrapper |
| 98 | + * } |
| 99 | + * return null; |
| 100 | + * }); |
| 101 | + * |
| 102 | + * // General fallback, to interface with anything, for example another BlockApiLookup. |
| 103 | + * MyApi.FLUID_CONTAINER.registerFallback((world, pos, state, blockEntity, direction) -> { |
| 104 | + * // return something if available, or null |
| 105 | + * });}</pre> |
| 106 | + * |
| 107 | + * <p><h3>Improving performance</h3> |
| 108 | + * When performing queries every tick, it is recommended to use {@link BlockApiCache BlockApiCache<A, C>} |
| 109 | + * instead of directly querying the {@code BlockApiLookup}. |
| 110 | + * |
| 111 | + * <pre>{@code |
| 112 | + * // 1) create and store an instance |
| 113 | + * BlockApiCache<FluidContainer, Direction> cache = BlockApiCache.create(MyApi.FLUID_CONTAINER, serverWorld, pos); |
| 114 | + * |
| 115 | + * // 2) use it later, the block entity instance will be cached among other things |
| 116 | + * FluidContainer container = cache.find(direction); |
| 117 | + * if (container != null) { |
| 118 | + * // ... |
| 119 | + * } |
| 120 | + * |
| 121 | + * // 2bis) if the caller is able to cache the block state as well, for example by listening to neighbor updates, |
| 122 | + * // that will further improve performance. |
| 123 | + * FluidContainer container = cache.find(direction, cachedBlockState); |
| 124 | + * if (container != null) { |
| 125 | + * // ... |
| 126 | + * } |
| 127 | + * |
| 128 | + * // no need to destroy the cache, the garbage collector will take care of it}</pre> |
| 129 | + * |
| 130 | + * <p><h3>Generic context types</h3> |
| 131 | + * Note that {@code FluidContainer} and {@code Direction} were completely arbitrary in this example. |
| 132 | + * We can define any {@code BlockApiLookup<A, C>}, where {@code A} is the type of the queried API, and {@code C} is the type of the additional context |
| 133 | + * (the direction parameter in the previous example). |
| 134 | + * If no context is necessary, {@code Void} should be used, and {@code null} instances should be passed. |
| 135 | + * |
| 136 | + * @param <A> The type of the API. |
| 137 | + * @param <C> The type of the additional context object. |
| 138 | + */ |
| 139 | +@ApiStatus.NonExtendable |
| 140 | +public interface BlockApiLookup<A, C> { |
| 141 | + /** |
| 142 | + * Retrieve the {@link BlockApiLookup} associated with an identifier, or create it if it didn't exist yet. |
| 143 | + * |
| 144 | + * @param lookupId The unique identifier of the lookup. |
| 145 | + * @param apiClass The class of the API. |
| 146 | + * @param contextClass The class of the additional context. |
| 147 | + * @return The unique lookup with the passed lookupId. |
| 148 | + * @throws IllegalArgumentException If another {@code apiClass} or another {@code contextClass} was already registered with the same identifier. |
| 149 | + */ |
| 150 | + static <A, C> BlockApiLookup<A, C> get(Identifier lookupId, Class<A> apiClass, Class<C> contextClass) { |
| 151 | + return BlockApiLookupImpl.get(lookupId, apiClass, contextClass); |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Attempt to retrieve an API from a block in the world. |
| 156 | + * Consider using {@link BlockApiCache} if you are doing frequent queries at the same position. |
| 157 | + * |
| 158 | + * <p>Note: If the block state or the block entity is known, it is more efficient to use {@link BlockApiLookup#find(World, BlockPos, BlockState, BlockEntity, Object)}. |
| 159 | + * |
| 160 | + * @param world The world. |
| 161 | + * @param pos The position of the block. |
| 162 | + * @param context Additional context for the query, defined by type parameter C. |
| 163 | + * @return The retrieved API, or {@code null} if no API was found. |
| 164 | + */ |
| 165 | + @Nullable |
| 166 | + default A find(World world, BlockPos pos, C context) { |
| 167 | + return find(world, pos, null, null, context); |
| 168 | + } |
| 169 | + |
| 170 | + /** |
| 171 | + * Attempt to retrieve an API from a block in the world. |
| 172 | + * Consider using {@link BlockApiCache} if you are doing frequent queries at the same position. |
| 173 | + * |
| 174 | + * @param world The world. |
| 175 | + * @param pos The position of the block. |
| 176 | + * @param context Additional context for the query, defined by type parameter C. |
| 177 | + * @param state The block state at the target position, or null if unknown. |
| 178 | + * @param blockEntity The block entity at the target position if it is known, or null if it is unknown or does not exist. |
| 179 | + * @return The retrieved API, or {@code null} if no API was found. |
| 180 | + */ |
| 181 | + @Nullable |
| 182 | + A find(World world, BlockPos pos, @Nullable BlockState state, @Nullable BlockEntity blockEntity, C context); |
| 183 | + |
| 184 | + /** |
| 185 | + * Expose the API for the passed block entities directly implementing it. |
| 186 | + * |
| 187 | + * <p>Implementation note: this is checked at registration time by creating block entity instances using the passed types. |
| 188 | + * |
| 189 | + * @param blockEntityTypes Block entity types for which to expose the API. |
| 190 | + * @throws IllegalArgumentException If the API class is not assignable from instances of the passed block entity types. |
| 191 | + */ |
| 192 | + void registerSelf(BlockEntityType<?>... blockEntityTypes); |
| 193 | + |
| 194 | + /** |
| 195 | + * Expose the API for the passed blocks. |
| 196 | + * The mapping from the parameters of the query to the API is handled by the passed {@link BlockApiProvider}. |
| 197 | + * |
| 198 | + * @param provider The provider. |
| 199 | + * @param blocks The blocks. |
| 200 | + */ |
| 201 | + void registerForBlocks(BlockApiProvider<A, C> provider, Block... blocks); |
| 202 | + |
| 203 | + /** |
| 204 | + * Expose the API for instances of the passed block entity types. |
| 205 | + * The mapping from the parameters of the query to the API is handled by the passed {@link BlockEntityApiProvider}. |
| 206 | + * |
| 207 | + * @param provider The provider. |
| 208 | + * @param blockEntityTypes The block entity types. |
| 209 | + */ |
| 210 | + void registerForBlockEntities(BlockEntityApiProvider<A, C> provider, BlockEntityType<?>... blockEntityTypes); |
| 211 | + |
| 212 | + /** |
| 213 | + * Expose the API for all queries: the provider will be invoked if no object was found using the block or block entity providers. |
| 214 | + * This may have a big performance impact on all queries, use cautiously. |
| 215 | + * |
| 216 | + * @param fallbackProvider The fallback provider. |
| 217 | + */ |
| 218 | + void registerFallback(BlockApiProvider<A, C> fallbackProvider); |
| 219 | + |
| 220 | + @FunctionalInterface |
| 221 | + interface BlockApiProvider<A, C> { |
| 222 | + /** |
| 223 | + * Return an API of type {@code A} if available in the world at the given pos with the given context, or {@code null} otherwise. |
| 224 | + * |
| 225 | + * @param world The world. |
| 226 | + * @param pos The position in the world. |
| 227 | + * @param state The block state. |
| 228 | + * @param blockEntity The block entity, if it exists in the world. |
| 229 | + * @param context Additional context passed to the query. |
| 230 | + * @return An API of type {@code A}, or {@code null} if no API is available. |
| 231 | + */ |
| 232 | + @Nullable |
| 233 | + A find(World world, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, C context); |
| 234 | + } |
| 235 | + |
| 236 | + @FunctionalInterface |
| 237 | + interface BlockEntityApiProvider<A, C> { |
| 238 | + /** |
| 239 | + * Return an API of type {@code A} if available in the given block entity with the given context, or {@code null} otherwise. |
| 240 | + * |
| 241 | + * @param blockEntity The block entity. It is guaranteed that it is never null. |
| 242 | + * @param context Additional context passed to the query. |
| 243 | + * @return An API of type {@code A}, or {@code null} if no API is available. |
| 244 | + */ |
| 245 | + @Nullable |
| 246 | + A find(BlockEntity blockEntity, C context); |
| 247 | + } |
| 248 | +} |
0 commit comments