diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiLookup.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiLookup.java index 2408f0ad6e..9aa2d7f15a 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiLookup.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/block/BlockApiLookup.java @@ -16,10 +16,13 @@ package net.fabricmc.fabric.api.lookup.v1.block; +import java.util.Map; import java.util.function.BiFunction; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import net.minecraft.block.Block; import net.minecraft.block.BlockState; @@ -29,6 +32,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.impl.lookup.block.BlockApiLookupImpl; /** @@ -143,8 +147,8 @@ public interface BlockApiLookup { /** * 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 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. @@ -153,19 +157,31 @@ static BlockApiLookup get(Identifier lookupId, Class apiClass, C return BlockApiLookupImpl.get(lookupId, apiClass, contextClass); } + @SuppressWarnings("unchecked") + static BlockApiLookup getUnchecked(Identifier lookupId, Class apiClass, Class contextClass) { + return get(lookupId, (Class) apiClass, (Class) contextClass); + } + + @SafeVarargs + static void registerForBlockEntities(BlockApiLookup lookup, BiFunction provider, BlockEntityType... blockEntityTypes) { + for (BlockEntityType blockEntityType : blockEntityTypes) { + lookup.registerForBlockEntity(blockEntityType, provider); + } + } + /** * 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. * *

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 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) { + default A find(@NotNull World world, @NotNull BlockPos pos, C context) { return find(world, pos, null, null, context); } @@ -173,15 +189,39 @@ default A find(World world, BlockPos pos, C 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 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); + default A find(@NotNull World world, @NotNull BlockPos pos, @Nullable BlockState state, @Nullable BlockEntity blockEntity, C context) { + if (blockEntity == null) { + if (state == null) { + state = world.getBlockState(pos); + } + + if (state.hasBlockEntity()) { + blockEntity = world.getBlockEntity(pos); + } + } else { + if (state == null) { + state = blockEntity.getCachedState(); + } + } + + A api = preliminary().invoker().find(world, pos, state, blockEntity, context); + if (api != null) return api; + + if (blockSpecific().containsKey(state.getBlock())) { + api = getSpecificFor(state.getBlock()).invoker().find(world, pos, state, blockEntity, context); + if (api != null) return api; + } + + return fallback().invoker().find(world, pos, state, blockEntity, context); + } /** * Expose the API for the passed block entities directly implementing it. @@ -198,9 +238,13 @@ default A find(World world, BlockPos pos, C context) { * 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. + * @param blocks The blocks. */ - void registerForBlocks(BlockApiProvider provider, Block... blocks); + default void registerForBlocks(BlockApiProvider provider, Block... blocks) { + for (Block block : blocks) { + getSpecificFor(block).register(provider); + } + } /** * Expose the API for instances of the passed block entity type. @@ -211,16 +255,19 @@ default A find(World world, BlockPos pos, C context) { * its {@linkplain BlockEntityType#blocks} when this method is called. * If the {@code blocks} field is empty, {@link IllegalArgumentException} is thrown. * - * @param The block entity class for which an API is exposed. - * @param provider The provider: returns an API if available in the passed block entity with the passed context, - * or {@code null} if no API is available. + * @param The block entity class for which an API is exposed. + * @param provider The provider: returns an API if available in the passed block entity with the passed context, + * or {@code null} if no API is available. * @param blockEntityType The block entity type. + * @deprecated see {@link #registerForBlockEntity(BlockEntityType, BiFunction)} */ - @SuppressWarnings("unchecked") + @Deprecated default void registerForBlockEntity(BiFunction provider, BlockEntityType blockEntityType) { - registerForBlockEntities((blockEntity, context) -> provider.apply((T) blockEntity, context), blockEntityType); + registerForBlockEntity(blockEntityType, provider); } + void registerForBlockEntity(@NotNull BlockEntityType blockEntityType, @NotNull BiFunction provider); + /** * 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}. @@ -231,18 +278,25 @@ default void registerForBlockEntity(BiFunction provider, BlockEntityType... blockEntityTypes); + @Deprecated + default void registerForBlockEntities(BlockEntityApiProvider provider, BlockEntityType... blockEntityTypes) { + registerForBlockEntities(this, provider::find, 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. + * @see #fallback() */ - void registerFallback(BlockApiProvider fallbackProvider); + default void registerFallback(@NotNull BlockApiProvider fallbackProvider) { + fallback().register(fallbackProvider); + } /** * Return the identifier of this lookup. @@ -262,33 +316,65 @@ default void registerForBlockEntity(BiFunction getProvider(Block block); + @Deprecated(forRemoval = true) + + @Nullable BlockApiProvider getProvider(Block block); + + /** + * It is queried before {@link #blockSpecific()} and {@link #fallback()}. + */ + Event> preliminary(); + + /** + *

It's queried after {@link #preliminary()} while before {@link #fallback()}.

+ *

This is for query existing providers. To register new providers, see {@link #getSpecificFor}.

+ * + * @return The map that stores providers for different blocks. If a block doesn't have any specific provider, there is no entry about it in the map ({@code blockSpecific().get(block) == null}). + */ + @UnmodifiableView Map<@NotNull Block, @NotNull Event>> blockSpecific(); + + /** + * This is for registering new providers. To query existing providers, see {@link #blockSpecific()}. + * + * @return The event for registering providers for the block. If there has not been any provider for it yet, a new event will be created and put into {@link #blockSpecific()}. + */ + @NotNull Event> getSpecificFor(@NotNull Block block); + + /** + * It's queried after {@link #preliminary()} and {@link #blockSpecific()}. + */ + Event> fallback(); @FunctionalInterface interface BlockApiProvider { /** * 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 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. + * @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); + + @Nullable A find(@NotNull World world, @NotNull BlockPos pos, @NotNull BlockState state, @Nullable BlockEntity blockEntity, C context); } + /** + * @deprecated use {@link BiFunction}{@code } instead + */ + @Deprecated @FunctionalInterface interface BlockEntityApiProvider { /** * 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. + * @param context Additional context passed to the query. * @return An API of type {@code A}, or {@code null} if no API is available. */ @Nullable diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/custom/ApiProviderMap.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/custom/ApiProviderMap.java index e31a9083e8..3ead728957 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/custom/ApiProviderMap.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/custom/ApiProviderMap.java @@ -16,8 +16,11 @@ package net.fabricmc.fabric.api.lookup.v1.custom; +import java.util.Map; + import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import net.fabricmc.fabric.impl.lookup.custom.ApiProviderHashMap; @@ -46,8 +49,7 @@ static ApiProviderMap create() { * * @throws NullPointerException If the key is null. */ - @Nullable - V get(K key); + @Nullable V get(K key); /** * If the specified key is not already associated with a provider, @@ -55,5 +57,10 @@ static ApiProviderMap create() { * * @throws NullPointerException If the key or the provider is null. */ - V putIfAbsent(K key, V provider); + @Nullable V putIfAbsent(K key, V provider); + + /** + * @return A read-only {@link Map} view of this instance. All modification operations will throw {@link UnsupportedOperationException}, except {@link Map#putIfAbsent(Object, Object)}. + */ + @UnmodifiableView Map asMap(); } diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/entity/EntityApiLookup.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/entity/EntityApiLookup.java index cd1609ce0c..1da514b808 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/entity/EntityApiLookup.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/entity/EntityApiLookup.java @@ -16,15 +16,19 @@ package net.fabricmc.fabric.api.lookup.v1.entity; +import java.util.Map; import java.util.function.BiFunction; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.impl.lookup.entity.EntityApiLookupImpl; /** @@ -97,6 +101,19 @@ static EntityApiLookup get(Identifier lookupId, Class
apiClass, return EntityApiLookupImpl.get(lookupId, apiClass, contextClass); } + @SuppressWarnings("unchecked") + static EntityApiLookup getUnchecked(Identifier lookupId, Class apiClass, Class contextClass) { + return get(lookupId, (Class) apiClass, (Class) contextClass); + } + + @SuppressWarnings("unchecked") + @SafeVarargs + static void registerForEntityTypes(EntityApiLookup lookup, BiFunction provider, EntityType... entityTypes) { + for (EntityType entityType : entityTypes) { + lookup.getSpecificFor(entityType).register((entity, context) -> provider.apply((E) entity, context)); + } + } + /** * Attempt to retrieve an API from an entity. * @@ -104,8 +121,17 @@ static EntityApiLookup get(Identifier lookupId, Class apiClass, * @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(Entity entity, C context); + default @Nullable A find(@NotNull Entity entity, C context) { + A api = preliminary().invoker().find(entity, context); + if (api != null) return api; + + if (typeSpecific().containsKey(entity.getType())) { + api = getSpecificFor(entity.getType()).invoker().find(entity, context); + if (api != null) return api; + } + + return fallback().invoker().find(entity, context); + } /** * Expose the API for the passed entities that directly implements it. @@ -115,7 +141,10 @@ static EntityApiLookup get(Identifier lookupId, Class apiClass, * @param entityTypes the entity types for which the API are exposed to. * @throws IllegalArgumentException if the entity is not an instance of the API class. */ - void registerSelf(EntityType... entityTypes); + @SuppressWarnings("unchecked") + default void registerSelf(EntityType... entityTypes) { + registerForTypes((entity, context) -> (A) entity, entityTypes); + } /** * Expose the API for instances of the entity type. @@ -130,21 +159,19 @@ default void registerForType(BiFunction pr registerForTypes((entity, context) -> provider.apply((T) entity, context), entityType); } - /** - * Expose the API for instances of the entity types. - * This overload allows for registering multiple entity types at once, - * but due to how generics work in java, the provider has to cast to the correct type if necessary. - * - * @param provider the provider. - * @param entityTypes the entity types for which the API are exposed to. - */ - void registerForTypes(EntityApiProvider provider, EntityType... entityTypes); + default void registerForTypes(@NotNull EntityApiProvider provider, EntityType... entityTypes) { + if (entityTypes.length == 0) { + throw new IllegalArgumentException("Must register at least one EntityType instance with an EntityApiProvider."); + } - /** - * Expose the API for all queries: the provider will be invoked if no object was found using the entity providers. - * May have big performance impact on all queries, use cautiously. - */ - void registerFallback(EntityApiProvider fallbackProvider); + for (EntityType entityType : entityTypes) { + getSpecificFor(entityType).register(provider); + } + } + + default void registerFallback(@NotNull EntityApiProvider fallbackProvider) { + fallback().register(fallbackProvider); + } /** * Return the identifier of this lookup. @@ -165,8 +192,33 @@ default void registerForType(BiFunction pr * Returns the provider for the passed entity type (registered with one of the {@code register} functions), or null if none was registered (yet). * Queries should go through {@link #find}, only use this to inspect registered providers! */ - @Nullable - EntityApiProvider getProvider(EntityType entityType); + @Deprecated(forRemoval = true) + @Nullable EntityApiProvider getProvider(@NotNull EntityType entityType); + + /** + * It is queried before {@link #typeSpecific()} and {@link #fallback()}. + */ + Event> preliminary(); + + /** + *

It's queried after {@link #preliminary()} while before {@link #fallback()}.

+ *

This is for query existing providers. To register new providers, see {@link #getSpecificFor}.

+ * + * @return The map that stores providers for different entity types. If an entity type doesn't have any specific provider, there is no entry about it in the map ({@code blockSpecific().get(entityType) == null}). + */ + @UnmodifiableView Map<@NotNull EntityType, @NotNull Event>> typeSpecific(); + + /** + * This is for registering new providers. To query existing providers, see {@link #typeSpecific()}. + * + * @return The event for registering providers for the entity type. If there has not been any provider for it yet, a new event will be created and put into {@link #typeSpecific()}. + */ + @NotNull Event> getSpecificFor(EntityType type); + + /** + * It's queried after {@link #preliminary()} and {@link #typeSpecific()}. + */ + Event> fallback(); interface EntityApiProvider { /** @@ -175,7 +227,6 @@ interface EntityApiProvider { * @param entity the entity. * @param context additional context for the query. */ - @Nullable - A find(Entity entity, C context); + @Nullable A find(@NotNull Entity entity, C context); } } diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/item/ItemApiLookup.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/item/ItemApiLookup.java index b2ad7a10cd..94a821f428 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/item/ItemApiLookup.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/item/ItemApiLookup.java @@ -16,7 +16,11 @@ package net.fabricmc.fabric.api.lookup.v1.item; +import java.util.Map; +import java.util.Objects; + import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import net.minecraft.item.Item; @@ -24,6 +28,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.fabricmc.fabric.impl.lookup.item.ItemApiLookupImpl; @@ -96,8 +101,8 @@ public interface ItemApiLookup { /** * Retrieve the {@link ItemApiLookup} 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 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. @@ -106,6 +111,11 @@ static ItemApiLookup get(Identifier lookupId, Class
apiClass, Cl return ItemApiLookupImpl.get(lookupId, apiClass, contextClass); } + @SuppressWarnings("unchecked") + static ItemApiLookup getUnchecked(Identifier lookupId, Class apiClass, Class contextClass) { + return get(lookupId, (Class) apiClass, (Class) contextClass); + } + /** * Attempt to retrieve an API from an item stack. * @@ -115,35 +125,62 @@ static ItemApiLookup get(Identifier lookupId, Class apiClass, Cl *
While providers may capture a reference to the stack, it is expected that they do not modify it directly. * * @param itemStack The item stack. - * @param context Additional context for the query, defined by type parameter C. + * @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(ItemStack itemStack, C context); + default A find(@NotNull ItemStack itemStack, C context) { + A api = preliminary().invoker().find(itemStack, context); + if (api != null) return api; - /** - * Expose the API for the passed items directly implementing it. - * - * @param items Items for which to expose the API. - * @throws IllegalArgumentException If the API class is not assignable from a class of one of the items. - */ - void registerSelf(ItemConvertible... items); + if (itemSpecific().containsKey(itemStack.getItem())) { + api = getSpecificFor(itemStack.getItem()).invoker().find(itemStack, context); + if (api != null) return api; + } - /** - * Expose the API for the passed items. - * The mapping from the parameters of the query to the API is handled by the passed {@link ItemApiProvider}. - * - * @param provider The provider. - * @param items The items. - */ - void registerForItems(ItemApiProvider provider, ItemConvertible... items); + return fallback().invoker().find(itemStack, context); + } + + @SuppressWarnings("unchecked") + default void registerSelf(ItemConvertible... items) { + for (ItemConvertible itemConvertible : items) { + Item item = itemConvertible.asItem(); + + if (!apiClass().isAssignableFrom(item.getClass())) { + String errorMessage = String.format( + "Failed to register self-implementing items. API class %s is not assignable from item class %s.", + apiClass().getCanonicalName(), + item.getClass().getCanonicalName() + ); + throw new IllegalArgumentException(errorMessage); + } + } + + registerForItems((itemStack, context) -> (A) itemStack.getItem(), items); + } + + default void registerForItems(@NotNull ItemApiProvider provider, ItemConvertible... items) { + Objects.requireNonNull(provider, "ItemApiProvider may not be null."); + + if (items.length == 0) { + throw new IllegalArgumentException("Must register at least one ItemConvertible instance with an ItemApiProvider."); + } + + for (ItemConvertible itemConvertible : items) { + Item item = itemConvertible.asItem(); + Objects.requireNonNull(item, "Item convertible in item form may not be null."); + getSpecificFor(item).register(provider); + } + } /** * Expose the API for all queries: the fallbacks providers will be invoked if no object was found using the regular providers. * * @param fallbackProvider The fallback provider. */ - void registerFallback(ItemApiProvider fallbackProvider); + default void registerFallback(@NotNull ItemApiProvider fallbackProvider) { + fallback().register(fallbackProvider); + } /** * Return the identifier of this lookup. @@ -163,9 +200,36 @@ static ItemApiLookup get(Identifier lookupId, Class
apiClass, Cl /** * Return the provider for the passed item (registered with one of the {@code register} functions), or null if none was registered (yet). * Queries should go through {@link #find}, only use this to inspect registered providers! + * + * @deprecated see {@link #getSpecificFor(Item)} */ - @Nullable - ItemApiProvider getProvider(Item item); + @Deprecated(forRemoval = true) + @Nullable ItemApiProvider getProvider(@NotNull Item item); + + /** + * It is queried before {@link #itemSpecific()} and {@link #fallback()}. + */ + Event> preliminary(); + + /** + *

It's queried after {@link #preliminary()} while before {@link #fallback()}.

+ *

This is for query existing providers. To register new providers, see {@link #getSpecificFor}.

+ * + * @return The map that stores providers for different items. If an item doesn't have any specific provider, there is no entry about it in the map ({@code blockSpecific().get(item) == null}). + */ + Map<@NotNull Item, @NotNull Event>> itemSpecific(); + + /** + * This is for registering new providers. To query existing providers, see {@link #itemSpecific()}. + * + * @return The event for registering providers for the item. If there has not been any provider for it yet, a new event will be created and put into {@link #itemSpecific()}. + */ + @NotNull Event> getSpecificFor(@NotNull Item item); + + /** + * It's queried after {@link #preliminary()} and {@link #itemSpecific()}. + */ + Event> fallback(); @FunctionalInterface interface ItemApiProvider { @@ -178,10 +242,10 @@ interface ItemApiProvider { *
While providers may capture a reference to the stack, it is expected that they do not modify it directly. * * @param itemStack The item stack. - * @param context Additional context passed to the query. + * @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(ItemStack itemStack, C context); + + @Nullable A find(@NotNull ItemStack itemStack, C context); } } diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/package-info.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/package-info.java index ff89757a52..1ec010953e 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/package-info.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/api/lookup/v1/package-info.java @@ -37,7 +37,7 @@ *
  • 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()}.
  • + * and {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup#registerForBlockEntities(net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup, java.util.function.BiFunction, net.minecraft.block.entity.BlockEntityType[]) registerForBlockEntities}. *
  • {@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.
  • *
  • See {@link net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup BlockApiLookup} for example code.
  • diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiCacheImpl.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiCacheImpl.java index cc597d1d05..7af7750251 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiCacheImpl.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiCacheImpl.java @@ -23,6 +23,7 @@ import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; +import net.fabricmc.fabric.api.event.Event; 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; @@ -36,13 +37,13 @@ public final class BlockApiCacheImpl implements BlockApiCache { * blockEntityCacheValid maintains whether the cache is valid or not. */ private boolean blockEntityCacheValid = false; - private BlockEntity cachedBlockEntity = null; + private @Nullable 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 cachedProvider = null; + private @Nullable BlockState lastState = null; + private @Nullable Event> cachedProviders = null; public BlockApiCacheImpl(BlockApiLookupImpl lookup, ServerWorld world, BlockPos pos) { ((ServerWorldCache) world).fabric_registerCache(pos, this); @@ -55,7 +56,7 @@ public void invalidate() { blockEntityCacheValid = false; cachedBlockEntity = null; lastState = null; - cachedProvider = null; + cachedProviders = null; } @Nullable @@ -75,31 +76,25 @@ public A find(@Nullable BlockState state, C context) { // Get provider if (lastState != state) { - cachedProvider = lookup.getProvider(state.getBlock()); + cachedProviders = lookup.getSpecificFor(state.getBlock()); lastState = state; } - // Query the provider - A instance = null; + // Query the preliminary provider + A instance = lookup.preliminary().invoker().find(world, pos, state, cachedBlockEntity, context); + if (instance != null) return instance; - if (cachedProvider != null) { - instance = cachedProvider.find(world, pos, state, cachedBlockEntity, context); - } - - if (instance != null) { - return instance; - } - - // Query the fallback providers - for (BlockApiLookup.BlockApiProvider fallbackProvider : lookup.getFallbackProviders()) { - instance = fallbackProvider.find(world, pos, state, cachedBlockEntity, context); + // Query the providers + if (cachedProviders != null) { + instance = cachedProviders.invoker().find(world, pos, state, cachedBlockEntity, context); if (instance != null) { return instance; } } - return null; + // Query the fallback providers + return lookup.fallback().invoker().find(world, pos, state, cachedBlockEntity, context); } @Override diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiLookupImpl.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiLookupImpl.java index 367fa16962..6bea434d1f 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiLookupImpl.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/block/BlockApiLookupImpl.java @@ -17,22 +17,29 @@ package net.fabricmc.fabric.impl.lookup.block; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiFunction; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.registry.Registries; -import net.minecraft.world.World; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.fabricmc.fabric.api.lookup.v1.custom.ApiLookupMap; import net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap; @@ -47,10 +54,34 @@ public static BlockApiLookup get(Identifier lookupId, Class
    apiC return (BlockApiLookup) LOOKUPS.getLookup(lookupId, apiClass, contextClass); } + public static Event> newEvent() { + return EventFactory.createArrayBacked(BlockApiProvider.class, providers -> (world, pos, state, blockEntity, context) -> { + for (BlockApiProvider provider : providers) { + A api = provider.find(world, pos, state, blockEntity, context); + if (api != null) return api; + } + + return null; + }); + } + private final Identifier identifier; private final Class apiClass; private final Class contextClass; - private final ApiProviderMap> providerMap = ApiProviderMap.create(); + private final Event> preliminary = newEvent(); + private final ApiProviderMap>> blockSpecific = ApiProviderMap.create(); + /** + * It can't reflect phase order.
    + * It's just for {@link #getProvider}. It should be removed in the future. + */ + @ApiStatus.Experimental + private final Multimap> blockSpecificProviders = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + private final Event> fallback = newEvent(); + /** + * It can't reflect phase order.
    + * It's just for {@link #getFallbackProviders()}. It should be removed in the future. + */ + @ApiStatus.Experimental private final List> fallbackProviders = new CopyOnWriteArrayList<>(); @SuppressWarnings("unchecked") @@ -60,52 +91,6 @@ private BlockApiLookupImpl(Identifier identifier, Class apiClass, Class co this.contextClass = (Class) contextClass; } - @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.hasBlockEntity()) { - blockEntity = world.getBlockEntity(pos); - } - } else { - if (state == null) { - state = blockEntity.getCachedState(); - } - } - - @Nullable - BlockApiProvider 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 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) { @@ -125,79 +110,78 @@ public void registerSelf(BlockEntityType... blockEntityTypes) { } } - registerForBlockEntities((blockEntity, context) -> (A) blockEntity, blockEntityTypes); + BlockApiLookup.registerForBlockEntities(this, (blockEntity, context) -> (A) blockEntity, blockEntityTypes); } @Override - public void registerForBlocks(BlockApiProvider 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."); - } + public void registerFallback(@NotNull BlockApiProvider fallbackProvider) { + BlockApiLookup.super.registerFallback(fallbackProvider); + fallbackProviders.add(fallbackProvider); + } - for (Block block : blocks) { - Objects.requireNonNull(block, "Encountered null block while registering a block API provider mapping."); + @Override + public Identifier getId() { + return identifier; + } - if (providerMap.putIfAbsent(block, provider) != null) { - LOGGER.warn("Encountered duplicate API provider registration for block: " + Registries.BLOCK.getId(block)); - } - } + @Override + public Class
    apiClass() { + return apiClass; } @Override - public void registerForBlockEntities(BlockEntityApiProvider provider, BlockEntityType... blockEntityTypes) { - Objects.requireNonNull(provider, "BlockEntityApiProvider may not be null."); + public Class contextClass() { + return contextClass; + } - if (blockEntityTypes.length == 0) { - throw new IllegalArgumentException("Must register at least one BlockEntityType instance with a BlockEntityApiProvider."); + @SuppressWarnings("removal")//though just override while no invoke, idea still warns + @Override + @Deprecated(forRemoval = true) + public @Nullable BlockApiProvider getProvider(Block block) { + for (BlockApiProvider provider : blockSpecificProviders.get(block)) { + return provider; } - BlockApiProvider 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); - } + return null; } - @Override - public void registerFallback(BlockApiProvider fallbackProvider) { - Objects.requireNonNull(fallbackProvider, "BlockApiProvider may not be null."); - - fallbackProviders.add(fallbackProvider); + @Deprecated(forRemoval = true) + public @UnmodifiableView List> getFallbackProviders() { + return fallbackProviders; } @Override - public Identifier getId() { - return identifier; + public Event> preliminary() { + return preliminary; } @Override - public Class apiClass() { - return apiClass; + public @UnmodifiableView Map<@NotNull Block, @NotNull Event>> blockSpecific() { + return blockSpecific.asMap(); } @Override - public Class contextClass() { - return contextClass; + public @NotNull Event> getSpecificFor(@NotNull Block block) { + Event> event = blockSpecific.get(block); + + if (event == null) { + blockSpecific.putIfAbsent(block, newEvent()); + event = Objects.requireNonNull(blockSpecific.get(block)); + } + + return event; } @Override - @Nullable - public BlockApiProvider getProvider(Block block) { - return providerMap.get(block); + public Event> fallback() { + return fallback; } - public List> getFallbackProviders() { - return fallbackProviders; + @SuppressWarnings("unchecked") + @Override + public void registerForBlockEntity(@NotNull BlockEntityType blockEntityType, @NotNull BiFunction provider) { + for (Block block : ((BlockEntityTypeAccessor) blockEntityType).getBlocks().toArray(new Block[0])) { + getSpecificFor(block).register((world, pos, state, blockEntity, context) -> provider.apply((B) blockEntity, context)); + } } } diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/custom/ApiProviderHashMap.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/custom/ApiProviderHashMap.java index 1aa1be2c14..d75329105c 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/custom/ApiProviderHashMap.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/custom/ApiProviderHashMap.java @@ -16,27 +16,87 @@ package net.fabricmc.fabric.impl.lookup.custom; +import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Objects; +import java.util.Set; import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap; -public final class ApiProviderHashMap implements ApiProviderMap { +public final class ApiProviderHashMap implements ApiProviderMap, @UnmodifiableView Map { private volatile Map lookups = new Reference2ReferenceOpenHashMap<>(); + @Override + public int size() { + return lookups.size(); + } + + @Override + public boolean isEmpty() { + return lookups.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return lookups.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return lookups.containsValue(value); + } + @Nullable @Override - public V get(K key) { + public V get(Object key) { Objects.requireNonNull(key, "Key may not be null."); return lookups.get(key); } @Override - public synchronized V putIfAbsent(K key, V provider) { + public @Nullable V put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(@NotNull Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Set keySet() { + return Collections.unmodifiableSet(lookups.keySet()); + } + + @Override + public @NotNull Collection values() { + return Collections.unmodifiableCollection(lookups.values()); + } + + @Override + public @NotNull Set> entrySet() { + return Collections.unmodifiableSet(lookups.entrySet()); + } + + @Override + public synchronized @Nullable V putIfAbsent(K key, V provider) { Objects.requireNonNull(key, "Key may not be null."); Objects.requireNonNull(provider, "Provider may not be null."); @@ -47,4 +107,9 @@ public synchronized V putIfAbsent(K key, V provider) { return result; } + + @Override + public @UnmodifiableView Map asMap() { + return this; + } } diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/entity/EntityApiLookupImpl.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/entity/EntityApiLookupImpl.java index 9a8dc948f6..8b6e4f73a2 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/entity/EntityApiLookupImpl.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/entity/EntityApiLookupImpl.java @@ -17,40 +17,63 @@ package net.fabricmc.fabric.impl.lookup.entity; import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; -import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.registry.Registries; import net.minecraft.server.MinecraftServer; import net.minecraft.util.Identifier; -import net.minecraft.registry.Registries; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; 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.entity.EntityApiLookup; public class EntityApiLookupImpl implements EntityApiLookup { private static final Logger LOGGER = LoggerFactory.getLogger("fabric-api-lookup-api-v1/entity"); - private static final ApiLookupMap> LOOKUPS = ApiLookupMap.>create(EntityApiLookupImpl::new); - private static final Map, Set>> REGISTERED_SELVES = new HashMap<>(); + private static final ApiLookupMap> LOOKUPS = ApiLookupMap.create(EntityApiLookupImpl::new); + private static final SetMultimap, EntityType> REGISTERED_SELVES = MultimapBuilder.hashKeys().linkedHashSetValues().build(); private static boolean checkEntityLookup = true; + public static Event> newEvent() { + return EventFactory.createArrayBacked(EntityApiProvider.class, providers -> (entity, context) -> { + for (EntityApiProvider provider : providers) { + A api = provider.find(entity, context); + if (api != null) return api; + } + + return null; + }); + } + private final Identifier identifier; private final Class apiClass; private final Class contextClass; - private final ApiProviderMap, EntityApiProvider> providerMap = ApiProviderMap.create(); - private final List> fallbackProviders = new CopyOnWriteArrayList<>(); + private final Event> preliminary = newEvent(); + private final ApiProviderMap, Event>> typeSpecific = ApiProviderMap.create(); + /** + * It can't reflect phase order.
    + * It's just for {@link #getProvider}. It should be removed in the future. + */ + @ApiStatus.Experimental + private final Multimap, EntityApiProvider> typeSpecificProviders = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + private final Event> fallback = newEvent(); private EntityApiLookupImpl(Identifier identifier, Class
    apiClass, Class contextClass) { this.identifier = identifier; @@ -68,8 +91,10 @@ public static void checkSelfImplementingTypes(MinecraftServer server) { checkEntityLookup = false; synchronized (REGISTERED_SELVES) { - REGISTERED_SELVES.forEach((apiClass, entityTypes) -> { - for (EntityType entityType : entityTypes) { + for (Map.Entry, Collection>> entry : REGISTERED_SELVES.asMap().entrySet()) { + Class apiClass = entry.getKey(); + + for (EntityType entityType : entry.getValue()) { Entity entity = entityType.create(server.getOverworld()); if (entity == null) { @@ -90,89 +115,70 @@ public static void checkSelfImplementingTypes(MinecraftServer server) { throw new IllegalArgumentException(errorMessage); } } - }); - } - } - } - - @Override - @Nullable - public A find(Entity entity, C context) { - Objects.requireNonNull(entity, "Entity may not be null."); - - if (EntityPredicates.VALID_ENTITY.test(entity)) { - EntityApiProvider provider = providerMap.get(entity.getType()); - - if (provider != null) { - A instance = provider.find(entity, context); - - if (instance != null) { - return instance; - } - } - - for (EntityApiProvider fallback : fallbackProviders) { - A instance = fallback.find(entity, context); - - if (instance != null) { - return instance; } } } - - return null; } - @SuppressWarnings("unchecked") @Override public void registerSelf(EntityType... entityTypes) { synchronized (REGISTERED_SELVES) { - REGISTERED_SELVES.computeIfAbsent(apiClass, c -> new LinkedHashSet<>()).addAll(Arrays.asList(entityTypes)); + REGISTERED_SELVES.putAll(apiClass(), Arrays.asList(entityTypes)); } - registerForTypes((entity, context) -> (A) entity, entityTypes); + EntityApiLookup.super.registerSelf(entityTypes); } @Override - public void registerForTypes(EntityApiProvider provider, EntityType... entityTypes) { - Objects.requireNonNull(provider, "EntityApiProvider may not be null."); + public Identifier getId() { + return identifier; + } - if (entityTypes.length == 0) { - throw new IllegalArgumentException("Must register at least one EntityType instance with an EntityApiProvider."); - } + @Override + public Class apiClass() { + return apiClass; + } - for (EntityType entityType : entityTypes) { - if (providerMap.putIfAbsent(entityType, provider) != null) { - LOGGER.warn("Encountered duplicate API provider registration for entity type: " + Registries.ENTITY_TYPE.getId(entityType)); - } - } + @Override + public Class contextClass() { + return contextClass; } + @SuppressWarnings("removal") @Override - public void registerFallback(EntityApiProvider fallbackProvider) { - Objects.requireNonNull(fallbackProvider, "EntityApiProvider may not be null."); + @Deprecated(forRemoval = true) + public @Nullable EntityApiProvider getProvider(@NotNull EntityType entityType) { + for (EntityApiProvider provider : typeSpecificProviders.get(entityType)) { + return provider; + } - fallbackProviders.add(fallbackProvider); + return null; } @Override - public Identifier getId() { - return identifier; + public Event> preliminary() { + return preliminary; } @Override - public Class apiClass() { - return apiClass; + public @UnmodifiableView Map<@NotNull EntityType, @NotNull Event>> typeSpecific() { + return typeSpecific.asMap(); } @Override - public Class contextClass() { - return contextClass; + public @NotNull Event> getSpecificFor(EntityType type) { + Event> event = typeSpecific.get(type); + + if (event == null) { + typeSpecific.putIfAbsent(type, newEvent()); + event = Objects.requireNonNull(typeSpecific.get(type)); + } + + return event; } @Override - @Nullable - public EntityApiProvider getProvider(EntityType entityType) { - return providerMap.get(entityType); + public Event> fallback() { + return fallback; } } diff --git a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/item/ItemApiLookupImpl.java b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/item/ItemApiLookupImpl.java index d03b5f7371..a060453583 100644 --- a/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/item/ItemApiLookupImpl.java +++ b/fabric-api-lookup-api-v1/src/main/java/net/fabricmc/fabric/impl/lookup/item/ItemApiLookupImpl.java @@ -16,20 +16,23 @@ package net.fabricmc.fabric.impl.lookup.item; -import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.concurrent.CopyOnWriteArrayList; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.minecraft.item.Item; -import net.minecraft.item.ItemConvertible; -import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; -import net.minecraft.registry.Registries; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; 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.item.ItemApiLookup; @@ -43,11 +46,29 @@ public static ItemApiLookup get(Identifier lookupId, Class apiCl return (ItemApiLookup) LOOKUPS.getLookup(lookupId, apiClass, contextClass); } + public static Event> newEvent() { + return EventFactory.createArrayBacked(ItemApiProvider.class, providers -> (itemStack, context) -> { + for (ItemApiProvider provider : providers) { + A api = provider.find(itemStack, context); + if (api != null) return api; + } + + return null; + }); + } + private final Identifier identifier; private final Class apiClass; private final Class contextClass; - private final ApiProviderMap> providerMap = ApiProviderMap.create(); - private final List> fallbackProviders = new CopyOnWriteArrayList<>(); + private final Event> preliminary = newEvent(); + private final ApiProviderMap>> itemSpecific = ApiProviderMap.create(); + /** + * It can't reflect phase order.
    + * It's just for {@link #getProvider}. It should be removed in the future. + */ + @ApiStatus.Experimental + private final Multimap> itemSpecificProviders = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + private final Event> fallback = newEvent(); @SuppressWarnings("unchecked") private ItemApiLookupImpl(Identifier identifier, Class apiClass, Class contextClass) { @@ -57,93 +78,55 @@ private ItemApiLookupImpl(Identifier identifier, Class apiClass, Class con } @Override - public @Nullable A find(ItemStack itemStack, C context) { - Objects.requireNonNull(itemStack, "ItemStack may not be null."); - - @Nullable - ItemApiProvider provider = providerMap.get(itemStack.getItem()); - - if (provider != null) { - A instance = provider.find(itemStack, context); - - if (instance != null) { - return instance; - } - } - - for (ItemApiProvider fallbackProvider : fallbackProviders) { - A instance = fallbackProvider.find(itemStack, context); - - if (instance != null) { - return instance; - } - } - - return null; + public Identifier getId() { + return identifier; } - @SuppressWarnings("unchecked") @Override - public void registerSelf(ItemConvertible... items) { - for (ItemConvertible itemConvertible : items) { - Item item = itemConvertible.asItem(); - - if (!apiClass.isAssignableFrom(item.getClass())) { - String errorMessage = String.format( - "Failed to register self-implementing items. API class %s is not assignable from item class %s.", - apiClass.getCanonicalName(), - item.getClass().getCanonicalName() - ); - throw new IllegalArgumentException(errorMessage); - } - } - - registerForItems((itemStack, context) -> (A) itemStack.getItem(), items); + public Class
    apiClass() { + return apiClass; } @Override - public void registerForItems(ItemApiProvider provider, ItemConvertible... items) { - Objects.requireNonNull(provider, "ItemApiProvider may not be null."); - - if (items.length == 0) { - throw new IllegalArgumentException("Must register at least one ItemConvertible instance with an ItemApiProvider."); - } - - for (ItemConvertible itemConvertible : items) { - Item item = itemConvertible.asItem(); - Objects.requireNonNull(item, "Item convertible in item form may not be null."); - - if (providerMap.putIfAbsent(item, provider) != null) { - LOGGER.warn("Encountered duplicate API provider registration for item: " + Registries.ITEM.getId(item)); - } - } + public Class contextClass() { + return contextClass; } + @SuppressWarnings("removal") // IDE always warns @Override - public void registerFallback(ItemApiProvider fallbackProvider) { - Objects.requireNonNull(fallbackProvider, "ItemApiProvider may not be null."); + @Deprecated(forRemoval = true) + public @Nullable ItemApiProvider getProvider(@NotNull Item item) { + for (ItemApiProvider provider : itemSpecificProviders.get(item)) { + return provider; + } - fallbackProviders.add(fallbackProvider); + return null; } @Override - public Identifier getId() { - return identifier; + public Event> preliminary() { + return preliminary; } @Override - public Class apiClass() { - return apiClass; + public Map<@NotNull Item, @NotNull Event>> itemSpecific() { + return itemSpecific.asMap(); } @Override - public Class contextClass() { - return contextClass; + public @NotNull Event> getSpecificFor(@NotNull Item item) { + Event> event = itemSpecific.get(item); + + if (event == null) { + itemSpecific.putIfAbsent(item, newEvent()); + event = Objects.requireNonNull(itemSpecific.get(item)); + } + + return event; } @Override - @Nullable - public ItemApiProvider getProvider(Item item) { - return providerMap.get(item); + public Event> fallback() { + return fallback; } } diff --git a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/FabricApiLookupTest.java b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/FabricApiLookupTest.java index 116e5a5e3a..2e27349ada 100644 --- a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/FabricApiLookupTest.java +++ b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/FabricApiLookupTest.java @@ -69,8 +69,8 @@ public void onInitialize() { 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); + BlockApiLookup.registerForBlockEntities(ItemApis.INSERTABLE, insertableProvider, BlockEntityType.CHEST, BlockEntityType.DISPENSER, BlockEntityType.DROPPER, BlockEntityType.HOPPER); + BlockApiLookup.registerForBlockEntities(ItemApis.EXTRACTABLE, extractableProvider, BlockEntityType.CHEST, BlockEntityType.DISPENSER, BlockEntityType.DROPPER, BlockEntityType.HOPPER); ItemApis.EXTRACTABLE.registerSelf(COBBLE_GEN_BLOCK_ENTITY_TYPE); testLookupRegistry(); diff --git a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryExtractableProvider.java b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryExtractableProvider.java index 365532f57f..e43a371e2d 100644 --- a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryExtractableProvider.java +++ b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryExtractableProvider.java @@ -16,6 +16,8 @@ package net.fabricmc.fabric.test.lookup.compat; +import java.util.function.BiFunction; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,12 +25,11 @@ 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 { +public class InventoryExtractableProvider implements BiFunction { @Override - public @Nullable ItemExtractable find(BlockEntity blockEntity, @NotNull Direction context) { + public @Nullable ItemExtractable apply(BlockEntity blockEntity, @NotNull Direction context) { if (blockEntity instanceof Inventory) { return new WrappedInventory((Inventory) blockEntity); } diff --git a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryInsertableProvider.java b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryInsertableProvider.java index b60ce8e06e..7d45a01729 100644 --- a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryInsertableProvider.java +++ b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/compat/InventoryInsertableProvider.java @@ -16,6 +16,8 @@ package net.fabricmc.fabric.test.lookup.compat; +import java.util.function.BiFunction; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,12 +25,11 @@ 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 { +public class InventoryInsertableProvider implements BiFunction { @Override - public @Nullable ItemInsertable find(BlockEntity blockEntity, @NotNull Direction context) { + public @Nullable ItemInsertable apply(BlockEntity blockEntity, @NotNull Direction context) { if (blockEntity instanceof Inventory) { return new WrappedInventory((Inventory) blockEntity); } diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/fluid/CombinedProvidersImpl.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/fluid/CombinedProvidersImpl.java index 0231d2007a..5f82e93b0d 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/fluid/CombinedProvidersImpl.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/fluid/CombinedProvidersImpl.java @@ -17,8 +17,11 @@ package net.fabricmc.fabric.impl.transfer.fluid; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import net.minecraft.item.Item; @@ -34,6 +37,8 @@ import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage; public class CombinedProvidersImpl { + private static final Map ITEM_SPECIFIC = new HashMap<>(); + public static Event createEvent(boolean invokeFallback) { return EventFactory.createArrayBacked(FluidStorage.CombinedItemApiProvider.class, listeners -> context -> { List> storages = new ArrayList<>(); @@ -67,7 +72,7 @@ private static class Provider implements ItemApiLookup.ItemApiProvider find(ItemStack itemStack, ContainerItemContext context) { + public Storage find(@NotNull ItemStack itemStack, ContainerItemContext context) { if (!context.getItemVariant().matches(itemStack)) { String errorMessage = String.format( "Query stack %s and ContainerItemContext variant %s don't match.", @@ -82,23 +87,20 @@ public Storage find(ItemStack itemStack, ContainerItemContext cont } public static Event getOrCreateItemEvent(Item item) { - ItemApiLookup.ItemApiProvider, ContainerItemContext> existingProvider = FluidStorage.ITEM.getProvider(item); + Provider provider = ITEM_SPECIFIC.get(item); - if (existingProvider == null) { - FluidStorage.ITEM.registerForItems(new Provider(), item); - // The provider might not be new Provider() if a concurrent registration happened, re-query. - existingProvider = FluidStorage.ITEM.getProvider(item); - } + if (provider == null) { + synchronized (ITEM_SPECIFIC) { + provider = ITEM_SPECIFIC.get(item); - if (existingProvider instanceof Provider registeredProvider) { - return registeredProvider.event; - } else { - String errorMessage = String.format( - "An incompatible provider was already registered for item %s. Provider: %s.", - item, - existingProvider - ); - throw new IllegalStateException(errorMessage); + if (provider == null) { + provider = new Provider(); + ITEM_SPECIFIC.putIfAbsent(item, provider); + FluidStorage.ITEM.getSpecificFor(item).register(provider); + } + } } + + return provider.event; } }