-
Notifications
You must be signed in to change notification settings - Fork 428
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Fabric API Lookup API v1 ## Introduction This module allows Api instances to be associated with game objects without specifying how the association is implemented. This is useful when the same Api could be implemented more than once or implemented in different ways. Many thanks to @grondag for providing the original concept (#1072). Thanks also go to @i509VCB, @Pyrofab, @sfPlayer1 and the others who were involved with the design of this module. This is the foundation upon which can be built for example a fluid transfer api (#1166). Closes #1199. ## Flexible Api Querying ## Block Api Usage example ## Building blocks This PR was changed a lot, please have a look at the README, the package info, and the javadoc for `BlockApiLookup` and `ApiLookupMap` for up-to-date documentation. ## More usage examples FastTransferLib (https://github.com/Technici4n/FastTransferLib) is an experiment to build an item, fluid and energy transfer api on top of this module. (Which was until recently called `fabric-provider-api-v1`.) ## Missing things? ~~I could add an overload of `BlockApiLookup#find` with nullable `BlockState` and `BlockEntity` parameters, so that the caller can directly provide them if they are available for some reason.~~ Added in later commits. There is no module to retrieve apis from items or entities yet because there were unsolved issues with those. The community can use the provided building blocks to experiment with their own implementations of `ItemStackApiLookup` and `EntityApiLookup` until the way forward becomes clear, but let's please not delay the `BlockApiLookup` because of that. Co-authored-by: i509VCB <git@i509.me> Co-authored-by: PepperBell <44146161+PepperCode1@users.noreply.github.com>
- Loading branch information
1 parent
f9b3753
commit dc716ea
Showing
33 changed files
with
1,869 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Fabric API Lookup API (v1) | ||
This module allows API instances to be associated with game objects without specifying how the association is implemented. | ||
This is useful when the same API could be implemented more than once or implemented in different ways. | ||
See also the [package-info.java file](src/main/java/net/fabricmc/fabric/api/lookup/v1/package-info.java). | ||
|
||
* What we call an API is any object that can be offered or queried, possibly by different mods, to be used in an agreed-upon manner. | ||
* This module allows flexible retrieving of such APIs from blocks in the world, represented by the generic type `A`. | ||
* It also provides building blocks for defining custom ways of retrieving APIs from other game objects. | ||
|
||
# Retrieving APIs from blocks | ||
See the javadoc of `BlockApiLookup` for a full usage example. | ||
|
||
## [`BlockApiLookup`](src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiLookup.java) | ||
The primary way of querying API instances for blocks in the world. | ||
It exposes a `find` function to retrieve an API instance, and multiple `register*` functions to register Apis for blocks and block entities. | ||
|
||
Instances can be obtained using the `get` function. | ||
|
||
## [`BlockApiCache`](src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiCache.java) | ||
A `BlockApiLookup` bound to a position and a server world, allowing much faster repeated API queries. | ||
|
||
# Retrieving APIs from custom objects | ||
The subpackage `custom` provides helper classes to accelerate implementations of `ApiLookup`s for custom objects, | ||
similar to the existing `BlockApiLookup`, but with different query parameters. | ||
|
||
## [`ApiLookupMap`](src/main/java/net/fabricmc/fabric/api/lookup/v1/custom/ApiLookupMap.java) | ||
A map meant to be used as the backing storage for custom `ApiLookup` instances, to implement a custom equivalent of `BlockApiLookup#get`. | ||
|
||
## [`ApiProviderMap`](src/main/java/net/fabricmc/fabric/api/lookup/v1/custom/ApiProviderMap.java) | ||
A fast thread-safe copy-on-write map meant to be used as the backing storage for registered providers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
archivesBaseName = "fabric-api-lookup-api-v1" | ||
version = getSubprojectVersion(project, "1.0.0") | ||
|
||
moduleDependencies(project, [ | ||
'fabric-api-base', | ||
'fabric-lifecycle-events-v1' | ||
]) | ||
|
||
dependencies { | ||
testmodCompile project(path: ':fabric-object-builder-api-v1', configuration: 'dev') | ||
} |
80 changes: 80 additions & 0 deletions
80
...pi-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.api.lookup.v1.block; | ||
|
||
import java.util.Objects; | ||
|
||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import net.minecraft.block.BlockState; | ||
import net.minecraft.server.world.ServerWorld; | ||
import net.minecraft.util.math.BlockPos; | ||
|
||
import net.fabricmc.fabric.impl.lookup.block.BlockApiCacheImpl; | ||
import net.fabricmc.fabric.impl.lookup.block.BlockApiLookupImpl; | ||
|
||
/** | ||
* A {@link BlockApiLookup} bound to a {@link ServerWorld} and a position, providing much faster API access. | ||
* Refer to {@link BlockApiLookup} for example code. | ||
* | ||
* <p>This object caches the block entity at the target position, and the last used API provider, removing those queries. | ||
* If a block entity is available or if the block state is passed as a parameter, the block state doesn't have to be looked up either. | ||
* | ||
* @param <A> The type of the API. | ||
* @param <C> The type of the additional context object. | ||
* @see BlockApiLookup | ||
*/ | ||
@ApiStatus.NonExtendable | ||
public interface BlockApiCache<A, C> { | ||
/** | ||
* Attempt to retrieve an API from a block in the world, using the world and the position passed at creation time. | ||
* | ||
* <p>Note: If the block state is known, it is more efficient to use {@link BlockApiCache#find(BlockState, Object)}. | ||
* | ||
* @param context Additional context for the query, defined by type parameter C. | ||
* @return The retrieved API, or {@code null} if no API was found. | ||
*/ | ||
@Nullable | ||
default A find(C context) { | ||
return find(null, context); | ||
} | ||
|
||
/** | ||
* Attempt to retrieve an API from a block in the world, using the world and the position passed at creation time. | ||
* | ||
* @param state The block state at the target position, or null if unknown. | ||
* @param context Additional context for the query, defined by type parameter C. | ||
* @return The retrieved API, or {@code null} if no API was found. | ||
*/ | ||
@Nullable | ||
A find(@Nullable BlockState state, C context); | ||
|
||
/** | ||
* Create a new instance bound to the passed {@link ServerWorld} and position, and querying the same API as the passed lookup. | ||
*/ | ||
static <A, C> BlockApiCache<A, C> create(BlockApiLookup<A, C> lookup, ServerWorld world, BlockPos pos) { | ||
Objects.requireNonNull(pos, "BlockPos may not be null."); | ||
Objects.requireNonNull(world, "ServerWorld may not be null."); | ||
|
||
if (!(lookup instanceof BlockApiLookupImpl)) { | ||
throw new IllegalArgumentException("Cannot cache foreign implementation of BlockApiLookup. Use `BlockApiLookup#get(Identifier, Class<A>, Class<C>);` to get instances."); | ||
} | ||
|
||
return new BlockApiCacheImpl<>((BlockApiLookupImpl<A, C>) lookup, world, pos); | ||
} | ||
} |
248 changes: 248 additions & 0 deletions
248
...i-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiLookup.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.api.lookup.v1.block; | ||
|
||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import net.minecraft.block.Block; | ||
import net.minecraft.block.BlockState; | ||
import net.minecraft.block.entity.BlockEntity; | ||
import net.minecraft.block.entity.BlockEntityType; | ||
import net.minecraft.util.Identifier; | ||
import net.minecraft.util.math.BlockPos; | ||
import net.minecraft.world.World; | ||
|
||
import net.fabricmc.fabric.impl.lookup.block.BlockApiLookupImpl; | ||
|
||
/** | ||
* An object that allows retrieving APIs from blocks in a world. | ||
* Instances of this interface can be obtained through {@link #get}. | ||
* | ||
* <p>When trying to {@link BlockApiLookup#find} 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 {@code null}, the fallback providers will be queried in order. | ||
* | ||
* <p>Note: If you are going to query APIs a lot, consider using {@link BlockApiCache}, it may drastically improve performance. | ||
* | ||
* <p><h3>Usage Example</h3> | ||
* Let us pretend we have the following interface that we would like to attach to some blocks depending on the direction. | ||
* | ||
* <pre>{@code | ||
* public interface FluidContainer { | ||
* boolean containsFluids(); // return true if not empty | ||
* }}</pre> | ||
* Let us first create a static {@code BlockApiLookup} instance that will manage the registration and the query. | ||
* | ||
* <pre>{@code | ||
* public final class MyApi { | ||
* public static final BlockApiLookup<FluidContainer, Direction> FLUID_CONTAINER = BlockApiLookup.get(new Identifier("mymod:fluid_container"), FluidContainer.class, Direction.class); | ||
* }}</pre> | ||
* Using that, we can query instances of {@code FluidContainer}: | ||
* | ||
* <pre>{@code | ||
* 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!"); | ||
* } | ||
* }}</pre> | ||
* For the query to return a useful result, functions that provide an API for a block or a block entity must be registered. | ||
* | ||
* <pre>{@code | ||
* // 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 | ||
* });}</pre> | ||
* | ||
* <p><h3>Improving performance</h3> | ||
* When performing queries every tick, it is recommended to use {@link BlockApiCache BlockApiCache<A, C>} | ||
* instead of directly querying the {@code BlockApiLookup}. | ||
* | ||
* <pre>{@code | ||
* // 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}</pre> | ||
* | ||
* <p><h3>Generic context types</h3> | ||
* Note that {@code FluidContainer} and {@code Direction} were completely arbitrary in this example. | ||
* 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 | ||
* (the direction parameter in the previous example). | ||
* If no context is necessary, {@code Void} should be used, and {@code null} instances should be passed. | ||
* | ||
* @param <A> The type of the API. | ||
* @param <C> The type of the additional context object. | ||
*/ | ||
@ApiStatus.NonExtendable | ||
public interface BlockApiLookup<A, C> { | ||
/** | ||
* Retrieve the {@link BlockApiLookup} associated with an identifier, or create it if it didn't exist yet. | ||
* | ||
* @param lookupId The unique identifier of the lookup. | ||
* @param apiClass The class of the API. | ||
* @param contextClass The class of the additional context. | ||
* @return The unique lookup with the passed lookupId. | ||
* @throws IllegalArgumentException If another {@code apiClass} or another {@code contextClass} was already registered with the same identifier. | ||
*/ | ||
static <A, C> BlockApiLookup<A, C> get(Identifier lookupId, Class<A> apiClass, Class<C> contextClass) { | ||
return BlockApiLookupImpl.get(lookupId, apiClass, contextClass); | ||
} | ||
|
||
/** | ||
* Attempt to retrieve an API from a block in the world. | ||
* Consider using {@link BlockApiCache} if you are doing frequent queries at the same position. | ||
* | ||
* <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)}. | ||
* | ||
* @param world The world. | ||
* @param pos The position of the block. | ||
* @param context Additional context for the query, defined by type parameter C. | ||
* @return The retrieved API, or {@code null} if no API was found. | ||
*/ | ||
@Nullable | ||
default A find(World world, BlockPos pos, C context) { | ||
return find(world, pos, null, null, context); | ||
} | ||
|
||
/** | ||
* Attempt to retrieve an API from a block in the world. | ||
* Consider using {@link BlockApiCache} if you are doing frequent queries at the same position. | ||
* | ||
* @param world The world. | ||
* @param pos The position of the block. | ||
* @param context Additional context for the query, defined by type parameter C. | ||
* @param state The block state at the target position, or null if unknown. | ||
* @param blockEntity The block entity at the target position if it is known, or null if it is unknown or does not exist. | ||
* @return The retrieved API, or {@code null} if no API was found. | ||
*/ | ||
@Nullable | ||
A find(World world, BlockPos pos, @Nullable BlockState state, @Nullable BlockEntity blockEntity, C context); | ||
|
||
/** | ||
* Expose the API for the passed block entities directly implementing it. | ||
* | ||
* <p>Implementation note: this is checked at registration time by creating block entity instances using the passed types. | ||
* | ||
* @param 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. | ||
*/ | ||
void registerSelf(BlockEntityType<?>... blockEntityTypes); | ||
|
||
/** | ||
* Expose the API for the passed blocks. | ||
* The mapping from the parameters of the query to the API is handled by the passed {@link BlockApiProvider}. | ||
* | ||
* @param provider The provider. | ||
* @param blocks The blocks. | ||
*/ | ||
void registerForBlocks(BlockApiProvider<A, C> provider, Block... blocks); | ||
|
||
/** | ||
* 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 {@link BlockEntityApiProvider}. | ||
* | ||
* @param provider The provider. | ||
* @param blockEntityTypes The block entity types. | ||
*/ | ||
void registerForBlockEntities(BlockEntityApiProvider<A, C> provider, BlockEntityType<?>... blockEntityTypes); | ||
|
||
/** | ||
* 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. | ||
* | ||
* @param fallbackProvider The fallback provider. | ||
*/ | ||
void registerFallback(BlockApiProvider<A, C> fallbackProvider); | ||
|
||
@FunctionalInterface | ||
interface BlockApiProvider<A, C> { | ||
/** | ||
* Return an API of type {@code A} if available in the world at the given pos with the given context, or {@code null} otherwise. | ||
* | ||
* @param world The world. | ||
* @param pos The position in the world. | ||
* @param state The block state. | ||
* @param blockEntity The block entity, if it exists in the world. | ||
* @param context Additional context passed to the query. | ||
* @return An API of type {@code A}, or {@code null} if no API is available. | ||
*/ | ||
@Nullable | ||
A find(World world, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, C context); | ||
} | ||
|
||
@FunctionalInterface | ||
interface BlockEntityApiProvider<A, C> { | ||
/** | ||
* Return an API of type {@code A} if available in the given block entity with the given context, or {@code null} otherwise. | ||
* | ||
* @param blockEntity The block entity. It is guaranteed that it is never null. | ||
* @param context Additional context passed to the query. | ||
* @return An API of type {@code A}, or {@code null} if no API is available. | ||
*/ | ||
@Nullable | ||
A find(BlockEntity blockEntity, C context); | ||
} | ||
} |
Oops, something went wrong.