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 extends B> blockEntityType, @NotNull BiFunction super B, ? super C, ? extends @Nullable A> 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 extends K, ? extends V> 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;
}
}