mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 03:10:54 -04:00
Fabric API Lookup (#1234)
# 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>
This commit is contained in:
parent
f9b3753bc0
commit
dc716ea167
33 changed files with 1869 additions and 0 deletions
checkstyle.xml
fabric-api-lookup-api-v1
README.mdbuild.gradle
settings.gradlesrc
main
java/net/fabricmc/fabric
resources
testmod
java/net/fabricmc/fabric/test/lookup
ChuteBlock.javaChuteBlockEntity.javaCobbleGenBlock.javaCobbleGenBlockEntity.javaFabricApiLookupTest.java
api
compat
resources
|
@ -62,6 +62,13 @@
|
|||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<!-- Allow "//CHECKSTYLE.OFF: <InspectionName>" and "//CHECKSTYLE.ON: <InspectionName>" pairs to toggle some inspections -->
|
||||
<module name="SuppressionCommentFilter">
|
||||
<property name="offCommentFormat" value="CHECKSTYLE.OFF\: ([\w\|]+)"/>
|
||||
<property name="onCommentFormat" value="CHECKSTYLE.ON\: ([\w\|]+)"/>
|
||||
<property name="checkFormat" value="$1"/>
|
||||
</module>
|
||||
|
||||
<!-- Ensure all imports are ship shape -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"/>
|
||||
|
|
30
fabric-api-lookup-api-v1/README.md
Normal file
30
fabric-api-lookup-api-v1/README.md
Normal file
|
@ -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.
|
11
fabric-api-lookup-api-v1/build.gradle
Normal file
11
fabric-api-lookup-api-v1/build.gradle
Normal file
|
@ -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')
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.custom;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.lookup.custom.ApiLookupMapImpl;
|
||||
|
||||
//CHECKSTYLE.OFF: JavadocStyle - Checkstyle didn't like <A, C>, even though {@code ... } already escapes it.
|
||||
/**
|
||||
* A a map meant to be used as the backing storage for custom {@code ApiLookup} instances,
|
||||
* to implement a custom equivalent of {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#get BlockApiLookup#get}.
|
||||
*
|
||||
* <p><h3>Usage Example</h3>
|
||||
* We will be implementing the following simplified version of an API lookup interface for item stacks
|
||||
* to illustrate how to use {@link ApiLookupMap} and {@link ApiProviderMap}.
|
||||
* <pre>{@code
|
||||
* public interface ItemStackApiLookup<A, C> {
|
||||
* static <A, C> ItemStackApiLookup<A, C> get(Identifier lookupId, Class<A> apiClass, Class<C> contextClass) {
|
||||
* return ItemStackApiLookupImpl.get(lookupId, apiClass, contextClass);
|
||||
* }
|
||||
* // Find an API instance.
|
||||
* @Nullable
|
||||
* A find(ItemStack stack, C context);
|
||||
* // Expose the API for some item.
|
||||
* void register(ItemStackApiProvider<A, C> provider, Item item);
|
||||
*
|
||||
* interface ItemStackApiProvider<A, C> {
|
||||
* // Return an API instance if available, or null otherwise.
|
||||
* @Nullable
|
||||
* A find(ItemStack stack, C context);
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* All the implementation can fit in a single class:
|
||||
* <pre>{@code
|
||||
* public class ItemStackApiLookupImpl<A, C> implements ItemStackApiLookup<A, C> {
|
||||
* // Management of lookup instances is handled by ApiLookupMap.
|
||||
* private static final ApiLookupMap<ItemStackApiLookup<?, ?>> LOOKUPS = ApiLookupMap.create(ItemStackApiLookupImpl::new);
|
||||
* // We have to perform an unchecked cast to convert <?, ?> back to <A, C>.
|
||||
* @SuppressWarnings("unchecked")
|
||||
* public static <A, C> ItemStackApiLookup<A, C> get(Identifier lookupId, Class<A> apiClass, Class<C> contextClass) {
|
||||
* // Null checks are already handled by ApiLookupMap#get.
|
||||
* return (ItemStackApiLookup<A, C>) LOOKUPS.getLookup(lookupId, apiClass, contextClass);
|
||||
* }
|
||||
*
|
||||
* private ItemStackApiLookupImpl(Class<?> apiClass, Class<?> contextClass) {
|
||||
* // We don't use these classes, so nothing to do here.
|
||||
* }
|
||||
* // We will use an ApiProviderMap to store the providers.
|
||||
* private final ApiProviderMap<Item, ItemStackApiProvider<A, C>> providerMap = ApiProviderMap.create();
|
||||
* @Nullable
|
||||
* public A find(ItemStack stack, C context) {
|
||||
* ItemStackApiProvider<A, C> provider = providerMap.get(stack.getItem());
|
||||
* if (provider == null) {
|
||||
* return null;
|
||||
* } else {
|
||||
* return provider.find(stack, context);
|
||||
* }
|
||||
* }
|
||||
* public void register(ItemStackApiProvider provider, Item item) {
|
||||
* // Let's add a few null checks just in case.
|
||||
* Objects.requireNonNull(provider, "ItemStackApiProvider may not be null.");
|
||||
* Objects.requireNonNull(item, "Item may not be null.");
|
||||
* // Register the provider, or warn if it is already registered
|
||||
* if (providerMap.putIfAbsent(item, provider) != null) {
|
||||
* // Emit a warning printing the item ID to help users debug more easily.
|
||||
* LogManager.getLogger("The name of your mod").warn("Encountered duplicate API provider registration for item " + Registry.ITEM.getId(item) + ".");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param <L> The type of the lookup implementation, similar to the existing {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup BlockApiLookup}.
|
||||
*/
|
||||
//CHECKSTYLE.ON: JavadocStyle
|
||||
@ApiStatus.NonExtendable
|
||||
public interface ApiLookupMap<L> extends Iterable<L> {
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param lookupFactory The factory that is used to create API lookup instances.
|
||||
*/
|
||||
static <L> ApiLookupMap<L> create(LookupFactory<L> lookupFactory) {
|
||||
Objects.requireNonNull(lookupFactory, "Lookup factory may not be null.");
|
||||
|
||||
return new ApiLookupMapImpl<>(lookupFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the API lookup associated with an identifier.
|
||||
*
|
||||
* @param lookupId The unique identifier of the lookup.
|
||||
* @param apiClass The class of the queried API.
|
||||
* @param contextClass The class of the queried 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.
|
||||
* @throws NullPointerException If one of the arguments is null.
|
||||
*/
|
||||
L getLookup(Identifier lookupId, Class<?> apiClass, Class<?> contextClass);
|
||||
|
||||
interface LookupFactory<L> {
|
||||
/**
|
||||
* Create a new API lookup implementation.
|
||||
*
|
||||
* @param apiClass The API class passed to {@link #getLookup}.
|
||||
* @param contextClass The context class passed to {@link #getLookup}.
|
||||
*/
|
||||
L get(Class<?> apiClass, Class<?> contextClass);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.custom;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.fabric.impl.lookup.custom.ApiProviderHashMap;
|
||||
|
||||
/**
|
||||
* A fast thread-safe copy-on-write map meant to be used as the backing storage for registered providers.
|
||||
* See {@link ApiLookupMap} for a usage example.
|
||||
*
|
||||
* <p>Note: This map allows very fast lock-free concurrent reads, but in exchange writes are very expensive and should not be too frequent.
|
||||
* Also, keys are compared by reference ({@code ==}) and not using {@link Object#equals}.
|
||||
*
|
||||
* @param <K> The key type of the map, compared by reference ({@code ==}).
|
||||
* @param <V> The value type of the map.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface ApiProviderMap<K, V> {
|
||||
/**
|
||||
* Create a new instance.
|
||||
*/
|
||||
static <K, V> ApiProviderMap<K, V> create() {
|
||||
return new ApiProviderHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the provider to which the specified key is mapped,
|
||||
* or {@code null} if this map contains no mapping for the key.
|
||||
*
|
||||
* @throws NullPointerException If the key is null.
|
||||
*/
|
||||
@Nullable
|
||||
V get(K key);
|
||||
|
||||
/**
|
||||
* If the specified key is not already associated with a provider,
|
||||
* associate it with the given value and return {@code null}, else return the current value.
|
||||
*
|
||||
* @throws NullPointerException If the key or the provider is null.
|
||||
*/
|
||||
V putIfAbsent(K key, V provider);
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <h1>The API Lookup, version 1.</h1>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p><h2>Definitions and purpose</h2>
|
||||
* <ul>
|
||||
* <li>What we call an <i>API</i> is any object that can be offered or queried, possibly by different mods, to be used in an agreed-upon manner.</li>
|
||||
* <li>This module allows flexible retrieving of such APIs from blocks in the world, represented by the generic type {@code A}.</li>
|
||||
* <li>It also provides building blocks for defining custom ways of retrieving APIs from other game objects.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p><h2>Retrieving APIs from blocks in the world</h2>
|
||||
* <ul>
|
||||
* <li>A block query for an API is an operation that takes a world, a block position, and additional context of type {@code C}, and uses that
|
||||
* to find an object of type {@code A}, or {@code null} if there was no such object.</li>
|
||||
* <li>An instance of {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup BlockApiLookup<A, C>}
|
||||
* provides a {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#find find()} function that does exactly that.</li>
|
||||
* <li>It also allows registering APIs for blocks, because for the query to work the API must be registered first.
|
||||
* Registration primarily happens through {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#registerSelf registerSelf()},
|
||||
* {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#registerForBlocks registerForBlocks()}
|
||||
* and {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#registerForBlockEntities registerForBlockEntities()}.</li>
|
||||
* <li>{@code BlockApiLookup} instances can be accessed through {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#get BlockApiLookup#get()}.
|
||||
* For optimal performance, it is better to store them in a {@code public static final} field instead of querying them multiple times.</li>
|
||||
* <li>See {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup BlockApiLookup} for example code.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p><h2>Retrieving APIs from custom game objects</h2>
|
||||
* <ul>
|
||||
* <li>The subpackage {@code custom} provides helper classes to accelerate implementations of {@code ApiLookup}s for custom objects,
|
||||
* similar to the existing {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup BlockApiLookup}, but with different query parameters.</li>
|
||||
* <li>{@link net.fabricmc.fabric.api.lookup.v1.custom.ApiLookupMap ApiLookupMap} is a map meant to be used as the backing storage for custom {@code ApiLookup} instances,
|
||||
* to implement a custom equivalent of {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#get BlockApiLookup#get}.</li>
|
||||
* <li>{@link net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap ApiProviderMap} is a fast thread-safe copy-on-write map meant to be used as the backing storage for registered providers.</li>
|
||||
* <li>See {@link net.fabricmc.fabric.api.lookup.v1.custom.ApiLookupMap ApiLookupMap} for example code.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
package net.fabricmc.fabric.api.lookup.v1;
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.impl.lookup.block;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents;
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache;
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
|
||||
|
||||
public final class BlockApiCacheImpl<A, C> implements BlockApiCache<A, C> {
|
||||
private final BlockApiLookupImpl<A, C> lookup;
|
||||
private final ServerWorld world;
|
||||
private final BlockPos pos;
|
||||
/**
|
||||
* We always cache the block entity, even if it's null. We rely on BE load and unload events to invalidate the cache when necessary.
|
||||
* blockEntityCacheValid maintains whether the cache is valid or not.
|
||||
*/
|
||||
private boolean blockEntityCacheValid = false;
|
||||
private BlockEntity cachedBlockEntity = null;
|
||||
/**
|
||||
* We also cache the BlockApiProvider at the target position. We check if the block state has changed to invalidate the cache.
|
||||
* lastState maintains for which block state the cachedProvider is valid.
|
||||
*/
|
||||
private BlockState lastState = null;
|
||||
private BlockApiLookup.BlockApiProvider<A, C> cachedProvider = null;
|
||||
|
||||
public BlockApiCacheImpl(BlockApiLookupImpl<A, C> lookup, ServerWorld world, BlockPos pos) {
|
||||
((ServerWorldCache) world).fabric_registerCache(pos, this);
|
||||
this.lookup = lookup;
|
||||
this.world = world;
|
||||
this.pos = pos.toImmutable();
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
blockEntityCacheValid = false;
|
||||
cachedBlockEntity = null;
|
||||
lastState = null;
|
||||
cachedProvider = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public A find(@Nullable BlockState state, C context) {
|
||||
// Get block entity
|
||||
if (!blockEntityCacheValid) {
|
||||
cachedBlockEntity = world.getBlockEntity(pos);
|
||||
blockEntityCacheValid = true;
|
||||
}
|
||||
|
||||
// Get block state
|
||||
if (state == null) {
|
||||
if (cachedBlockEntity != null) {
|
||||
state = cachedBlockEntity.getCachedState();
|
||||
} else {
|
||||
state = world.getBlockState(pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Get provider
|
||||
if (lastState != state) {
|
||||
cachedProvider = lookup.getProvider(state.getBlock());
|
||||
lastState = state;
|
||||
}
|
||||
|
||||
// Query the provider
|
||||
A instance = null;
|
||||
|
||||
if (cachedProvider != null) {
|
||||
instance = cachedProvider.find(world, pos, state, cachedBlockEntity, context);
|
||||
}
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Query the fallback providers
|
||||
for (BlockApiLookup.BlockApiProvider<A, C> fallbackProvider : lookup.getFallbackProviders()) {
|
||||
instance = fallbackProvider.find(world, pos, state, cachedBlockEntity, context);
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static {
|
||||
ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> {
|
||||
((ServerWorldCache) world).fabric_invalidateCache(blockEntity.getPos());
|
||||
});
|
||||
|
||||
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> {
|
||||
((ServerWorldCache) world).fabric_invalidateCache(blockEntity.getPos());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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.impl.lookup.block;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
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.util.registry.Registry;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.custom.ApiLookupMap;
|
||||
import net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap;
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
|
||||
import net.fabricmc.fabric.mixin.lookup.BlockEntityTypeAccessor;
|
||||
|
||||
public final class BlockApiLookupImpl<A, C> implements BlockApiLookup<A, C> {
|
||||
private static final Logger LOGGER = LogManager.getLogger("fabric-api-lookup-api-v1/block");
|
||||
private static final ApiLookupMap<BlockApiLookup<?, ?>> LOOKUPS = ApiLookupMap.create(BlockApiLookupImpl::new);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A, C> BlockApiLookup<A, C> get(Identifier lookupId, Class<A> apiClass, Class<C> contextClass) {
|
||||
return (BlockApiLookup<A, C>) LOOKUPS.getLookup(lookupId, apiClass, contextClass);
|
||||
}
|
||||
|
||||
private final Class<A> apiClass;
|
||||
private final ApiProviderMap<Block, BlockApiProvider<A, C>> providerMap = ApiProviderMap.create();
|
||||
private final List<BlockApiProvider<A, C>> fallbackProviders = new CopyOnWriteArrayList<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private BlockApiLookupImpl(Class<?> apiClass, Class<?> contextClass) {
|
||||
this.apiClass = (Class<A>) apiClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public A find(World world, BlockPos pos, @Nullable BlockState state, @Nullable BlockEntity blockEntity, C context) {
|
||||
Objects.requireNonNull(world, "World may not be null.");
|
||||
Objects.requireNonNull(pos, "BlockPos may not be null.");
|
||||
// Providers have the final say whether a null context is allowed.
|
||||
|
||||
// Get the block state and the block entity
|
||||
if (blockEntity == null) {
|
||||
if (state == null) {
|
||||
state = world.getBlockState(pos);
|
||||
}
|
||||
|
||||
if (state.getBlock().hasBlockEntity()) {
|
||||
blockEntity = world.getBlockEntity(pos);
|
||||
}
|
||||
} else {
|
||||
if (state == null) {
|
||||
state = blockEntity.getCachedState();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
BlockApiProvider<A, C> provider = getProvider(state.getBlock());
|
||||
A instance = null;
|
||||
|
||||
if (provider != null) {
|
||||
instance = provider.find(world, pos, state, blockEntity, context);
|
||||
}
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Query the fallback providers
|
||||
for (BlockApiProvider<A, C> fallbackProvider : fallbackProviders) {
|
||||
instance = fallbackProvider.find(world, pos, state, blockEntity, context);
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void registerSelf(BlockEntityType<?>... blockEntityTypes) {
|
||||
for (BlockEntityType<?> blockEntityType : blockEntityTypes) {
|
||||
BlockEntity blockEntity = blockEntityType.instantiate();
|
||||
Objects.requireNonNull(blockEntity, "Instantiated block entity may not be null.");
|
||||
|
||||
if (!apiClass.isAssignableFrom(blockEntity.getClass())) {
|
||||
String errorMessage = String.format(
|
||||
"Failed to register self-implementing block entities. API class %s is not assignable from block entity class %s.",
|
||||
apiClass.getCanonicalName(),
|
||||
blockEntity.getClass().getCanonicalName()
|
||||
);
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
registerForBlockEntities((blockEntity, context) -> (A) blockEntity, blockEntityTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerForBlocks(BlockApiProvider<A, C> provider, Block... blocks) {
|
||||
Objects.requireNonNull(provider, "BlockApiProvider may not be null.");
|
||||
|
||||
if (blocks.length == 0) {
|
||||
throw new IllegalArgumentException("Must register at least one Block instance with a BlockApiProvider.");
|
||||
}
|
||||
|
||||
for (Block block : blocks) {
|
||||
Objects.requireNonNull(block, "Encountered null block while registering a block API provider mapping.");
|
||||
|
||||
if (providerMap.putIfAbsent(block, provider) != null) {
|
||||
LOGGER.warn("Encountered duplicate API provider registration for block: " + Registry.BLOCK.getId(block));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerForBlockEntities(BlockEntityApiProvider<A, C> provider, BlockEntityType<?>... blockEntityTypes) {
|
||||
Objects.requireNonNull(provider, "BlockEntityApiProvider may not be null.");
|
||||
|
||||
if (blockEntityTypes.length == 0) {
|
||||
throw new IllegalArgumentException("Must register at least one BlockEntityType instance with a BlockEntityApiProvider.");
|
||||
}
|
||||
|
||||
BlockApiProvider<A, C> nullCheckedProvider = (world, pos, state, blockEntity, context) -> {
|
||||
if (blockEntity == null) {
|
||||
return null;
|
||||
} else {
|
||||
return provider.find(blockEntity, context);
|
||||
}
|
||||
};
|
||||
|
||||
for (BlockEntityType<?> blockEntityType : blockEntityTypes) {
|
||||
Objects.requireNonNull(blockEntityType, "Encountered null block entity type while registering a block entity API provider mapping.");
|
||||
|
||||
Block[] blocks = ((BlockEntityTypeAccessor) blockEntityType).getBlocks().toArray(new Block[0]);
|
||||
registerForBlocks(nullCheckedProvider, blocks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerFallback(BlockApiProvider<A, C> fallbackProvider) {
|
||||
Objects.requireNonNull(fallbackProvider, "BlockApiProvider may not be null.");
|
||||
|
||||
fallbackProviders.add(fallbackProvider);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockApiProvider<A, C> getProvider(Block block) {
|
||||
return providerMap.get(block);
|
||||
}
|
||||
|
||||
public List<BlockApiProvider<A, C>> getFallbackProviders() {
|
||||
return fallbackProviders;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.impl.lookup.block;
|
||||
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
/**
|
||||
* Allows attachment of a BlockApiCache to a {@link net.minecraft.server.world.ServerWorld}.
|
||||
*/
|
||||
public interface ServerWorldCache {
|
||||
void fabric_registerCache(BlockPos pos, BlockApiCacheImpl<?, ?> cache);
|
||||
|
||||
void fabric_invalidateCache(BlockPos pos);
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.impl.lookup.custom;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.custom.ApiLookupMap;
|
||||
|
||||
public final class ApiLookupMapImpl<L> implements ApiLookupMap<L> {
|
||||
private final Map<Identifier, StoredLookup<L>> lookups = new HashMap<>();
|
||||
private final LookupFactory<L> lookupFactory;
|
||||
|
||||
public ApiLookupMapImpl(LookupFactory<L> lookupFactory) {
|
||||
this.lookupFactory = lookupFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized L getLookup(Identifier lookupId, Class<?> apiClass, Class<?> contextClass) {
|
||||
Objects.requireNonNull(lookupId, "Lookup Identifier may not be null.");
|
||||
Objects.requireNonNull(apiClass, "API class may not be null.");
|
||||
Objects.requireNonNull(contextClass, "Context class may not be null.");
|
||||
|
||||
StoredLookup<L> storedLookup = lookups.computeIfAbsent(lookupId, id -> new StoredLookup<>(lookupFactory.get(apiClass, contextClass), apiClass, contextClass));
|
||||
|
||||
if (storedLookup.apiClass == apiClass && storedLookup.contextClass == contextClass) {
|
||||
return storedLookup.lookup;
|
||||
}
|
||||
|
||||
String errorMessage = String.format(
|
||||
"Lookup with id %s is already registered with api class %s and context class %s. It can't be registered with api class %s and context class %s.",
|
||||
lookupId,
|
||||
storedLookup.apiClass.getCanonicalName(),
|
||||
storedLookup.contextClass.getCanonicalName(),
|
||||
apiClass.getCanonicalName(),
|
||||
contextClass.getCanonicalName()
|
||||
);
|
||||
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Iterator<L> iterator() {
|
||||
return lookups.values().stream().map(storedLookup -> storedLookup.lookup).collect(Collectors.toList()).iterator();
|
||||
}
|
||||
|
||||
private static final class StoredLookup<L> {
|
||||
final L lookup;
|
||||
final Class<?> apiClass;
|
||||
final Class<?> contextClass;
|
||||
|
||||
StoredLookup(L lookup, Class<?> apiClass, Class<?> contextClass) {
|
||||
this.lookup = lookup;
|
||||
this.apiClass = apiClass;
|
||||
this.contextClass = contextClass;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.impl.lookup.custom;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap;
|
||||
|
||||
public final class ApiProviderHashMap<K, V> implements ApiProviderMap<K, V> {
|
||||
private volatile Map<K, V> lookups = new Reference2ReferenceOpenHashMap<>();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public V get(K key) {
|
||||
Objects.requireNonNull(key, "Key may not be null.");
|
||||
|
||||
return lookups.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized V putIfAbsent(K key, V provider) {
|
||||
Objects.requireNonNull(key, "Key may not be null.");
|
||||
Objects.requireNonNull(provider, "Provider may not be null.");
|
||||
|
||||
// We use a copy-on-write strategy to allow any number of reads to concur with a write
|
||||
Map<K, V> lookupsCopy = new Reference2ReferenceOpenHashMap<>(lookups);
|
||||
V result = lookupsCopy.putIfAbsent(key, provider);
|
||||
lookups = lookupsCopy;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.mixin.lookup;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.entity.BlockEntityType;
|
||||
|
||||
@Mixin(BlockEntityType.class)
|
||||
public interface BlockEntityTypeAccessor {
|
||||
@Accessor("blocks")
|
||||
Set<Block> getBlocks();
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.mixin.lookup;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
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.ServerWorldCache;
|
||||
|
||||
@Mixin(ServerWorld.class)
|
||||
abstract class ServerWorldMixin implements ServerWorldCache {
|
||||
@Unique
|
||||
private final Map<BlockPos, List<WeakReference<BlockApiCacheImpl<?, ?>>>> apiLookupCaches = new Object2ReferenceOpenHashMap<>();
|
||||
/**
|
||||
* Ensures that the apiLookupCaches map is iterated over every once in a while to clean up caches.
|
||||
*/
|
||||
@Unique
|
||||
private int apiLookupAccessesWithoutCleanup = 0;
|
||||
|
||||
@Override
|
||||
public void fabric_registerCache(BlockPos pos, BlockApiCacheImpl<?, ?> cache) {
|
||||
List<WeakReference<BlockApiCacheImpl<?, ?>>> caches = apiLookupCaches.computeIfAbsent(pos.toImmutable(), ignored -> new ArrayList<>());
|
||||
caches.removeIf(weakReference -> weakReference.get() == null);
|
||||
caches.add(new WeakReference<>(cache));
|
||||
apiLookupAccessesWithoutCleanup++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_invalidateCache(BlockPos pos) {
|
||||
List<WeakReference<BlockApiCacheImpl<?, ?>>> caches = apiLookupCaches.get(pos);
|
||||
|
||||
if (caches != null) {
|
||||
caches.removeIf(weakReference -> weakReference.get() == null);
|
||||
|
||||
if (caches.size() == 0) {
|
||||
apiLookupCaches.remove(pos);
|
||||
} else {
|
||||
caches.forEach(weakReference -> {
|
||||
BlockApiCacheImpl<?, ?> cache = weakReference.get();
|
||||
|
||||
if (cache != null) {
|
||||
cache.invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
apiLookupAccessesWithoutCleanup++;
|
||||
|
||||
// Try to invalidate GC'd lookups from the cache after 2 * the number of cached lookups
|
||||
if (apiLookupAccessesWithoutCleanup > 2 * apiLookupCaches.size()) {
|
||||
apiLookupCaches.entrySet().removeIf(entry -> {
|
||||
entry.getValue().removeIf(weakReference -> weakReference.get() == null);
|
||||
return entry.getValue().isEmpty();
|
||||
});
|
||||
|
||||
apiLookupAccessesWithoutCleanup = 0;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After ![]() (image error) Size: 1.5 KiB |
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin.lookup",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"BlockEntityTypeAccessor",
|
||||
"ServerWorldMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
30
fabric-api-lookup-api-v1/src/main/resources/fabric.mod.json
Normal file
30
fabric-api-lookup-api-v1/src/main/resources/fabric.mod.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-api-lookup-api-v1",
|
||||
"name": "Fabric API Lookup API (v1)",
|
||||
"version": "${version}",
|
||||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"icon": "assets/fabric-api-lookup-api-v1/icon.png",
|
||||
"contact": {
|
||||
"homepage": "https://fabricmc.net",
|
||||
"irc": "irc://irc.esper.net:6667/fabric",
|
||||
"issues": "https://github.com/FabricMC/fabric/issues",
|
||||
"sources": "https://github.com/FabricMC/fabric"
|
||||
},
|
||||
"authors": [
|
||||
"FabricMC"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.9.2",
|
||||
"fabric-api-base": "*",
|
||||
"fabric-lifecycle-events-v1": "*"
|
||||
},
|
||||
"description": "A universal way to expose and query APIs",
|
||||
"mixins": [
|
||||
"fabric-api-lookup-api-v1.mixins.json"
|
||||
],
|
||||
"custom": {
|
||||
"fabric-api:module-lifecycle": "stable"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.test.lookup;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockEntityProvider;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.world.BlockView;
|
||||
|
||||
public class ChuteBlock extends Block implements BlockEntityProvider {
|
||||
public ChuteBlock(Settings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity createBlockEntity(BlockView world) {
|
||||
return new ChuteBlockEntity();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.test.lookup;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.Tickable;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemApis;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemExtractable;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemInsertable;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemUtils;
|
||||
|
||||
public class ChuteBlockEntity extends BlockEntity implements Tickable {
|
||||
private int moveDelay = 0;
|
||||
private BlockApiCache<ItemInsertable, @NotNull Direction> cachedInsertable = null;
|
||||
private BlockApiCache<ItemExtractable, @NotNull Direction> cachedExtractable = null;
|
||||
|
||||
public ChuteBlockEntity() {
|
||||
super(FabricApiLookupTest.CHUTE_BLOCK_ENTITY_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
//noinspection ConstantConditions - Intellij intrinsics don't know that hasWorld makes getWorld evaluate to non-null
|
||||
if (!this.hasWorld() || this.getWorld().isClient()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedInsertable == null) {
|
||||
cachedInsertable = BlockApiCache.create(ItemApis.INSERTABLE, (ServerWorld) world, pos.offset(Direction.DOWN));
|
||||
}
|
||||
|
||||
if (cachedExtractable == null) {
|
||||
cachedExtractable = BlockApiCache.create(ItemApis.EXTRACTABLE, (ServerWorld) world, pos.offset(Direction.UP));
|
||||
}
|
||||
|
||||
if (moveDelay == 0) {
|
||||
ItemExtractable from = cachedExtractable.find(Direction.DOWN);
|
||||
ItemInsertable to = cachedInsertable.find(Direction.UP);
|
||||
|
||||
if (from != null && to != null) {
|
||||
ItemUtils.move(from, to, 1);
|
||||
}
|
||||
|
||||
moveDelay = 20;
|
||||
}
|
||||
|
||||
--moveDelay;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.test.lookup;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockEntityProvider;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.world.BlockView;
|
||||
|
||||
public class CobbleGenBlock extends Block implements BlockEntityProvider {
|
||||
public CobbleGenBlock(Settings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity createBlockEntity(BlockView world) {
|
||||
return new CobbleGenBlockEntity();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.test.lookup;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.Items;
|
||||
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemExtractable;
|
||||
|
||||
public class CobbleGenBlockEntity extends BlockEntity implements ItemExtractable {
|
||||
public CobbleGenBlockEntity() {
|
||||
super(FabricApiLookupTest.COBBLE_GEN_BLOCK_ENTITY_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack tryExtract(int maxCount, Predicate<ItemStack> filter, boolean simulate) {
|
||||
ItemStack cobble = new ItemStack(Items.COBBLESTONE);
|
||||
|
||||
if (filter.test(cobble)) {
|
||||
return cobble;
|
||||
} else {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.test.lookup;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import net.minecraft.block.Material;
|
||||
import net.minecraft.block.entity.BlockEntityType;
|
||||
import net.minecraft.item.BlockItem;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemApis;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemInsertable;
|
||||
import net.fabricmc.fabric.test.lookup.compat.InventoryExtractableProvider;
|
||||
import net.fabricmc.fabric.test.lookup.compat.InventoryInsertableProvider;
|
||||
|
||||
public class FabricApiLookupTest implements ModInitializer {
|
||||
public static final String MOD_ID = "fabric-lookup-api-v1-testmod";
|
||||
// Chute - Block without model that transfers item from the container above to the container below.
|
||||
// It's meant to work with unsided containers: chests, dispensers, droppers and hoppers.
|
||||
public static final ChuteBlock CHUTE_BLOCK = new ChuteBlock(FabricBlockSettings.of(Material.METAL));
|
||||
public static final BlockItem CHUTE_ITEM = new BlockItem(CHUTE_BLOCK, new Item.Settings().group(ItemGroup.MISC));
|
||||
public static BlockEntityType<ChuteBlockEntity> CHUTE_BLOCK_ENTITY_TYPE;
|
||||
// Cobble gen - Block without model that can generate infinite cobblestone when placed above a chute.
|
||||
// It's meant to test BlockApiLookup#registerSelf.
|
||||
public static final CobbleGenBlock COBBLE_GEN_BLOCK = new CobbleGenBlock(FabricBlockSettings.of(Material.METAL));
|
||||
public static final BlockItem COBBLE_GEN_ITEM = new BlockItem(COBBLE_GEN_BLOCK, new Item.Settings().group(ItemGroup.MISC));
|
||||
public static BlockEntityType<CobbleGenBlockEntity> COBBLE_GEN_BLOCK_ENTITY_TYPE;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
Identifier chute = new Identifier(MOD_ID, "chute");
|
||||
Registry.register(Registry.BLOCK, chute, CHUTE_BLOCK);
|
||||
Registry.register(Registry.ITEM, chute, CHUTE_ITEM);
|
||||
CHUTE_BLOCK_ENTITY_TYPE = Registry.register(Registry.BLOCK_ENTITY_TYPE, chute, BlockEntityType.Builder.create(ChuteBlockEntity::new, CHUTE_BLOCK).build(null));
|
||||
|
||||
Identifier cobbleGen = new Identifier(MOD_ID, "cobble_gen");
|
||||
Registry.register(Registry.BLOCK, cobbleGen, COBBLE_GEN_BLOCK);
|
||||
Registry.register(Registry.ITEM, cobbleGen, COBBLE_GEN_ITEM);
|
||||
COBBLE_GEN_BLOCK_ENTITY_TYPE = Registry.register(Registry.BLOCK_ENTITY_TYPE, cobbleGen, BlockEntityType.Builder.create(CobbleGenBlockEntity::new, COBBLE_GEN_BLOCK).build(null));
|
||||
|
||||
InventoryExtractableProvider extractableProvider = new InventoryExtractableProvider();
|
||||
InventoryInsertableProvider insertableProvider = new InventoryInsertableProvider();
|
||||
|
||||
ItemApis.INSERTABLE.registerForBlockEntities(insertableProvider, BlockEntityType.CHEST, BlockEntityType.DISPENSER, BlockEntityType.DROPPER, BlockEntityType.HOPPER);
|
||||
ItemApis.EXTRACTABLE.registerForBlockEntities(extractableProvider, BlockEntityType.CHEST, BlockEntityType.DISPENSER, BlockEntityType.DROPPER, BlockEntityType.HOPPER);
|
||||
ItemApis.EXTRACTABLE.registerSelf(COBBLE_GEN_BLOCK_ENTITY_TYPE);
|
||||
|
||||
testLookupRegistry();
|
||||
testSelfRegistration();
|
||||
}
|
||||
|
||||
private static void testLookupRegistry() {
|
||||
BlockApiLookup<ItemInsertable, @NotNull Direction> insertable2 = BlockApiLookup.get(new Identifier("testmod:item_insertable"), ItemInsertable.class, Direction.class);
|
||||
|
||||
if (insertable2 != ItemApis.INSERTABLE) {
|
||||
throw new AssertionError("The registry should have returned the same instance.");
|
||||
}
|
||||
|
||||
ensureException(() -> {
|
||||
BlockApiLookup<Void, Void> wrongInsertable = BlockApiLookup.get(new Identifier("testmod:item_insertable"), Void.class, Void.class);
|
||||
wrongInsertable.registerFallback((world, pos, state, be, nocontext) -> null);
|
||||
}, "The registry should have prevented creation of another instance with different classes, but same id.");
|
||||
}
|
||||
|
||||
private static void testSelfRegistration() {
|
||||
ensureException(() -> {
|
||||
ItemApis.INSERTABLE.registerSelf(COBBLE_GEN_BLOCK_ENTITY_TYPE);
|
||||
}, "The BlockApiLookup should have prevented self-registration of incompatible block entity types.");
|
||||
}
|
||||
|
||||
private static void ensureException(Runnable runnable, String message) {
|
||||
boolean failed = false;
|
||||
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable t) {
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
throw new AssertionError(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.test.lookup.api;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
|
||||
|
||||
public final class ItemApis {
|
||||
public static final BlockApiLookup<ItemInsertable, @NotNull Direction> INSERTABLE =
|
||||
BlockApiLookup.get(new Identifier("testmod:item_insertable"), ItemInsertable.class, Direction.class);
|
||||
public static final BlockApiLookup<ItemExtractable, @NotNull Direction> EXTRACTABLE =
|
||||
BlockApiLookup.get(new Identifier("testmod:item_extractable"), ItemExtractable.class, Direction.class);
|
||||
|
||||
private ItemApis() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.test.lookup.api;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Something that can provide items.
|
||||
*/
|
||||
public interface ItemExtractable {
|
||||
/**
|
||||
* Try to extract a single stack.
|
||||
* @param maxCount The maximum number of items to extract
|
||||
* @param filter What items to extract. Please note that the predicate should be independent of the count of the stack!
|
||||
* @param simulate If true, don't modify any state
|
||||
* @return The extracted stack
|
||||
*/
|
||||
ItemStack tryExtract(int maxCount, Predicate<ItemStack> filter, boolean simulate);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.test.lookup.api;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Something that can accept items.
|
||||
*/
|
||||
public interface ItemInsertable {
|
||||
/**
|
||||
* Try to insert some items. If this object can accept a stack of n items, it should also accept the same stack with
|
||||
* a smaller count!
|
||||
* @param input The input items. Must not be changed by this function!
|
||||
* @param simulate If true, don't modify any state
|
||||
* @return The leftover items; it should never be the received input stack!
|
||||
*/
|
||||
ItemStack tryInsert(ItemStack input, boolean simulate);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.test.lookup.api;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
public final class ItemUtils {
|
||||
/**
|
||||
* Move at most maxCount items, and return the number of items moved.
|
||||
*/
|
||||
public static int move(ItemExtractable from, ItemInsertable to, int maxCount) {
|
||||
Predicate<ItemStack> insertionFilter = stack -> {
|
||||
if (stack.isEmpty()) return false;
|
||||
|
||||
ItemStack insertedStack = to.tryInsert(stack, true);
|
||||
return insertedStack.isEmpty() || insertedStack.getCount() < stack.getCount();
|
||||
};
|
||||
|
||||
ItemStack extracted = from.tryExtract(maxCount, insertionFilter, true);
|
||||
ItemStack leftover = to.tryInsert(extracted, false);
|
||||
int moved = extracted.getCount() - leftover.getCount();
|
||||
from.tryExtract(moved, insertionFilter, false);
|
||||
return moved;
|
||||
}
|
||||
|
||||
private ItemUtils() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.test.lookup.compat;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemExtractable;
|
||||
|
||||
public class InventoryExtractableProvider implements BlockApiLookup.BlockEntityApiProvider<ItemExtractable, @NotNull Direction> {
|
||||
@Override
|
||||
public @Nullable ItemExtractable find(BlockEntity blockEntity, @NotNull Direction context) {
|
||||
if (blockEntity instanceof Inventory) {
|
||||
return new WrappedInventory((Inventory) blockEntity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.test.lookup.compat;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemInsertable;
|
||||
|
||||
public class InventoryInsertableProvider implements BlockApiLookup.BlockEntityApiProvider<ItemInsertable, @NotNull Direction> {
|
||||
@Override
|
||||
public @Nullable ItemInsertable find(BlockEntity blockEntity, @NotNull Direction context) {
|
||||
if (blockEntity instanceof Inventory) {
|
||||
return new WrappedInventory((Inventory) blockEntity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.test.lookup.compat;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
final class ItemStackUtil {
|
||||
public static boolean areEqualIgnoreCount(ItemStack s1, ItemStack s2) {
|
||||
return s1.getItem() == s2.getItem() && ItemStack.areTagsEqual(s1, s2);
|
||||
}
|
||||
|
||||
private ItemStackUtil() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.test.lookup.compat;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemExtractable;
|
||||
import net.fabricmc.fabric.test.lookup.api.ItemInsertable;
|
||||
|
||||
final class WrappedInventory implements ItemInsertable, ItemExtractable {
|
||||
private final Inventory inv;
|
||||
|
||||
WrappedInventory(Inventory inv) {
|
||||
this.inv = inv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack tryExtract(int maxCount, Predicate<ItemStack> filter, boolean simulate) {
|
||||
for (int i = 0; i < inv.size(); ++i) {
|
||||
ItemStack stack = inv.getStack(i);
|
||||
|
||||
if (!stack.isEmpty() && filter.test(stack)) {
|
||||
ItemStack returned;
|
||||
|
||||
if (simulate) {
|
||||
returned = stack.copy().split(maxCount);
|
||||
} else {
|
||||
returned = stack.split(maxCount);
|
||||
}
|
||||
|
||||
return returned;
|
||||
}
|
||||
}
|
||||
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack tryInsert(ItemStack input, boolean simulate) {
|
||||
input = input.copy();
|
||||
|
||||
for (int i = 0; i < inv.size(); ++i) {
|
||||
if (inv.isValid(i, input)) {
|
||||
ItemStack stack = inv.getStack(i);
|
||||
|
||||
if (stack.isEmpty() || ItemStackUtil.areEqualIgnoreCount(stack, input)) {
|
||||
int remainingSpace = Math.min(inv.getMaxCountPerStack(), stack.getItem().getMaxCount()) - stack.getCount();
|
||||
int inserted = Math.min(remainingSpace, input.getCount());
|
||||
|
||||
if (!simulate) {
|
||||
if (stack.isEmpty()) {
|
||||
inv.setStack(i, input.copy());
|
||||
inv.getStack(i).setCooldown(inserted);
|
||||
} else {
|
||||
stack.increment(inserted);
|
||||
}
|
||||
}
|
||||
|
||||
input.decrement(inserted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-api-lookup-api-v1-testmod",
|
||||
"name": "Fabric API Lookup API (v1) Test Mod",
|
||||
"version": "1.0.0",
|
||||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabric-api-lookup-api-v1": "*"
|
||||
},
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"net.fabricmc.fabric.test.lookup.FabricApiLookupTest"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ rootProject.name = "fabric-api"
|
|||
|
||||
include 'fabric-api-base'
|
||||
|
||||
include 'fabric-api-lookup-api-v1'
|
||||
include 'fabric-biome-api-v1'
|
||||
include 'fabric-blockrenderlayer-v1'
|
||||
include 'fabric-commands-v0'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue