Transfer API improvements and breaking changes for 1.19 (#2139)

* Remove deprecated APIs

* Remove the transaction requirement for storage iteration

* Remove useless TransactionContext parameters in StorageUtil

* Improve SnapshotParticipant javadoc

* Fix tests

* Allow client-side SIDED item/fluid storage queries with a few caveats

* Add SidedStorageBlockEntity for easier block entity hierarchy registration
This commit is contained in:
Technici4n 2022-04-27 19:38:29 +02:00 committed by GitHub
parent ec94c6f636
commit 50e8465e2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 181 additions and 404 deletions

View file

@ -32,8 +32,6 @@ import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributes;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributeHandler;
/**
* Defines how {@linkplain FluidVariant fluid variants} of a given Fluid should be displayed to clients.
@ -45,15 +43,6 @@ import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributeHandler;
@ApiStatus.Experimental
@Environment(EnvType.CLIENT)
public interface FluidVariantRenderHandler {
/**
* @deprecated Implement {@link FluidVariantAttributeHandler#getName} instead.
* This function will be removed in a future iteration of the API.
*/
@Deprecated(forRemoval = true)
default Text getName(FluidVariant fluidVariant) {
return FluidVariantAttributes.getName(fluidVariant);
}
/**
* Append additional tooltips to the passed list if additional information is contained in the fluid variant.
*
@ -83,26 +72,6 @@ public interface FluidVariantRenderHandler {
}
}
/**
* @deprecated Use and implement {@linkplain #getSprites(FluidVariant) the other more general overload}.
* This one will be removed in a future iteration of the API.
*/
@Deprecated(forRemoval = true)
@Nullable
default Sprite getSprite(FluidVariant fluidVariant) {
Sprite[] sprites = getSprites(fluidVariant);
return sprites != null ? sprites[0] : null;
}
/**
* @deprecated Use and implement {@linkplain #getColor(FluidVariant, BlockRenderView, BlockPos) the other more general overload}.
* This one will be removed in a future iteration of the API.
*/
@Deprecated(forRemoval = true)
default int getColor(FluidVariant fluidVariant) {
return getColor(fluidVariant, null, null);
}
/**
* Return the color to use when rendering {@linkplain #getSprite the sprite} of this fluid variant.
* Transparency (alpha) will generally be taken into account and should be specified as well.
@ -122,14 +91,4 @@ public interface FluidVariantRenderHandler {
return -1;
}
}
/**
* @deprecated Implement {@link FluidVariantAttributeHandler#isLighterThanAir(FluidVariant)} instead.
* This function will be removed in a future iteration of the API.
*/
@Deprecated(forRemoval = true)
default boolean fillsFromTop(FluidVariant fluidVariant) {
// By default, only fluids lighter than air should be filled from top.
return FluidVariantAttributes.isLighterThanAir(fluidVariant);
}
}

View file

@ -76,15 +76,6 @@ public class FluidVariantRendering {
return handler == null ? DEFAULT_HANDLER : handler;
}
/**
* Return the name of the passed fluid variant.
* @deprecated use {@link FluidVariantAttributes#getName} instead.
*/
@Deprecated(forRemoval = true)
public static Text getName(FluidVariant fluidVariant) {
return getHandlerOrDefault(fluidVariant.getFluid()).getName(fluidVariant);
}
/**
* Return a mutable list: the tooltip for the passed fluid variant, including the name and additional lines if available
* and the id of the fluid if advanced tooltips are enabled.
@ -103,7 +94,7 @@ public class FluidVariantRendering {
List<Text> tooltip = new ArrayList<>();
// Name first
tooltip.add(getName(fluidVariant));
tooltip.add(FluidVariantAttributes.getName(fluidVariant));
// Additional tooltip information
getHandlerOrDefault(fluidVariant.getFluid()).appendTooltip(fluidVariant, tooltip, context);
@ -156,13 +147,4 @@ public class FluidVariantRendering {
public static int getColor(FluidVariant fluidVariant, @Nullable BlockRenderView view, @Nullable BlockPos pos) {
return getHandlerOrDefault(fluidVariant.getFluid()).getColor(fluidVariant, view, pos);
}
/**
* Return {@code true} if this fluid variant should be rendered as filling tanks from the top.
* @deprecated use {@link FluidVariantAttributes#isLighterThanAir} instead.
*/
@Deprecated(forRemoval = true)
public static boolean fillsFromTop(FluidVariant fluidVariant) {
return getHandlerOrDefault(fluidVariant.getFluid()).fillsFromTop(fluidVariant);
}
}

View file

@ -16,7 +16,6 @@
package net.fabricmc.fabric.api.transfer.v1.fluid;
import com.google.common.base.Preconditions;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@ -34,6 +33,7 @@ import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
import net.fabricmc.fabric.api.lookup.v1.item.ItemApiLookup;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SidedStorageBlockEntity;
import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext;
import net.fabricmc.fabric.api.transfer.v1.fluid.base.EmptyItemFluidStorage;
import net.fabricmc.fabric.api.transfer.v1.fluid.base.FullItemFluidStorage;
@ -58,11 +58,15 @@ public final class FluidStorage {
* The {@code Direction} parameter may never be null.
* Refer to {@link BlockApiLookup} for documentation on how to use this field.
*
* <p>A simple way to expose fluid variant storages for a block entity hierarchy is to extend {@link SidedStorageBlockEntity}.
*
* <p>When the operations supported by a storage change,
* that is if the return value of {@link Storage#supportsInsertion} or {@link Storage#supportsExtraction} changes,
* the storage should notify its neighbors with a block update so that they can refresh their connections if necessary.
*
* <p>May only be queried on the logical server thread, never client-side or from another thread!
* <p>This may be queried safely both on the logical server and on the logical client threads.
* On the server thread (i.e. with a server world), all transfer functionality is always supported.
* On the client thread (i.e. with a client world), contents of queried Storages are unreliable and should not be modified.
*/
public static final BlockApiLookup<Storage<FluidVariant>, Direction> SIDED =
BlockApiLookup.get(new Identifier("fabric:sided_fluid_storage"), Storage.asClass(), Direction.class);
@ -129,15 +133,18 @@ public final class FluidStorage {
}
static {
// Ensure that the lookup is only queried on the server side.
FluidStorage.SIDED.registerFallback((world, pos, state, blockEntity, context) -> {
Preconditions.checkArgument(!world.isClient(), "Sided fluid storage may only be queried for a server world.");
return null;
});
// Initialize vanilla cauldron wrappers
CauldronFluidContent.getForFluid(Fluids.WATER);
// Support for SidedStorageBlockEntity.
FluidStorage.SIDED.registerFallback((world, pos, state, blockEntity, direction) -> {
if (blockEntity instanceof SidedStorageBlockEntity sidedStorageBlockEntity) {
return sidedStorageBlockEntity.getFluidStorage(direction);
}
return null;
});
// Register combined fallback
FluidStorage.ITEM.registerFallback((stack, context) -> GENERAL_COMBINED_PROVIDER.invoker().find(context));
// Register empty bucket storage

View file

@ -17,6 +17,7 @@
package net.fabricmc.fabric.api.transfer.v1.fluid.base;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
@ -31,7 +32,6 @@ import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
import net.fabricmc.fabric.api.transfer.v1.storage.base.BlankVariantView;
import net.fabricmc.fabric.api.transfer.v1.storage.base.InsertionOnlyStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleViewIterator;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
/**
@ -66,6 +66,7 @@ public final class EmptyItemFluidStorage implements InsertionOnlyStorage<FluidVa
private final Function<ItemVariant, ItemVariant> emptyToFullMapping;
private final Fluid insertableFluid;
private final long insertableAmount;
private final List<StorageView<FluidVariant>> blankView;
/**
* Create a new instance.
@ -98,6 +99,7 @@ public final class EmptyItemFluidStorage implements InsertionOnlyStorage<FluidVa
this.emptyToFullMapping = emptyToFullMapping;
this.insertableFluid = insertableFluid;
this.insertableAmount = insertableAmount;
this.blankView = List.of(new BlankVariantView<>(FluidVariant.blank(), insertableAmount));
}
@Override
@ -122,7 +124,7 @@ public final class EmptyItemFluidStorage implements InsertionOnlyStorage<FluidVa
}
@Override
public Iterator<StorageView<FluidVariant>> iterator(TransactionContext transaction) {
return SingleViewIterator.create(new BlankVariantView<>(FluidVariant.blank(), insertableAmount), transaction);
public Iterator<StorageView<FluidVariant>> iterator() {
return blankView.iterator();
}
}

View file

@ -1,146 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.transfer.v1.fluid.base;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
import net.fabricmc.fabric.api.transfer.v1.storage.base.ResourceAmount;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant;
/**
* @deprecated Superseded by {@link SingleVariantStorage}. Will be removed in a future iteration of the API.
*/
@ApiStatus.Experimental
@Deprecated(forRemoval = true)
public abstract class SingleFluidStorage extends SnapshotParticipant<ResourceAmount<FluidVariant>> implements SingleSlotStorage<FluidVariant> {
public FluidVariant fluidVariant = FluidVariant.blank();
public long amount;
/**
* Implement if you want.
*/
protected void markDirty() {
}
/**
* @return {@code true} if the passed non-blank fluid variant can be inserted, {@code false} otherwise.
*/
protected boolean canInsert(FluidVariant fluidVariant) {
return true;
}
/**
* @return {@code true} if the passed non-blank fluid variant can be extracted, {@code false} otherwise.
*/
protected boolean canExtract(FluidVariant fluidVariant) {
return true;
}
/**
* @return The maximum capacity of this storage for the passed fluid variant.
* If the passed fluid variant is blank, an estimate should be returned.
*/
protected abstract long getCapacity(FluidVariant fluidVariant);
@Override
public final boolean isResourceBlank() {
return fluidVariant.isBlank();
}
@Override
public final FluidVariant getResource() {
return fluidVariant;
}
@Override
public final long getAmount() {
return fluidVariant.isBlank() ? 0 : amount;
}
@Override
public final long getCapacity() {
return getCapacity(fluidVariant);
}
@Override
public final long insert(FluidVariant insertedFluid, long maxAmount, TransactionContext transaction) {
StoragePreconditions.notBlankNotNegative(insertedFluid, maxAmount);
if ((insertedFluid.equals(fluidVariant) || fluidVariant.isBlank()) && canInsert(insertedFluid)) {
long insertedAmount = Math.min(maxAmount, getCapacity(insertedFluid) - amount);
if (insertedAmount > 0) {
updateSnapshots(transaction);
// Just in case.
if (fluidVariant.isBlank()) {
amount = 0;
}
amount += insertedAmount;
fluidVariant = insertedFluid;
}
return insertedAmount;
}
return 0;
}
@Override
public final long extract(FluidVariant extractedFluid, long maxAmount, TransactionContext transaction) {
StoragePreconditions.notBlankNotNegative(extractedFluid, maxAmount);
if (extractedFluid.equals(fluidVariant) && canExtract(extractedFluid)) {
long extractedAmount = Math.min(maxAmount, amount);
if (extractedAmount > 0) {
updateSnapshots(transaction);
amount -= extractedAmount;
if (amount == 0) {
fluidVariant = FluidVariant.blank();
}
}
return extractedAmount;
}
return 0;
}
@Override
protected final ResourceAmount<FluidVariant> createSnapshot() {
return new ResourceAmount<>(fluidVariant, amount);
}
@Override
protected final void readSnapshot(ResourceAmount<FluidVariant> snapshot) {
this.fluidVariant = snapshot.resource();
this.amount = snapshot.amount();
}
@Override
protected final void onFinalCommit() {
markDirty();
}
}

View file

@ -18,7 +18,6 @@ package net.fabricmc.fabric.api.transfer.v1.item;
import java.util.List;
import com.google.common.base.Preconditions;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.block.Blocks;
@ -32,6 +31,7 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SidedStorageBlockEntity;
import net.fabricmc.fabric.api.transfer.v1.item.base.SingleStackStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage;
@ -72,6 +72,12 @@ public final class ItemStorage {
* <li>{@link SingleStackStorage} can also be used for more flexibility. Multiple of them can be combined with {@link CombinedStorage}.</li>
* <li>Directly providing a custom implementation of {@code Storage<ItemVariant>} is also possible.</li>
* </ul>
*
* <p>A simple way to expose item variant storages for a block entity hierarchy is to extend {@link SidedStorageBlockEntity}.
*
* <p>This may be queried safely both on the logical server and on the logical client threads.
* On the server thread (i.e. with a server world), all transfer functionality is always supported.
* On the client thread (i.e. with a client world), contents of queried Storages are unreliable and should not be modified.
*/
public static final BlockApiLookup<Storage<ItemVariant>, Direction> SIDED =
BlockApiLookup.get(new Identifier("fabric:sided_item_storage"), Storage.asClass(), Direction.class);
@ -80,15 +86,18 @@ public final class ItemStorage {
}
static {
// Ensure that the lookup is only queried on the server side.
ItemStorage.SIDED.registerFallback((world, pos, state, blockEntity, context) -> {
Preconditions.checkArgument(!world.isClient(), "Sided item storage may only be queried for a server world.");
return null;
});
// Composter support.
ItemStorage.SIDED.registerForBlocks((world, pos, state, blockEntity, direction) -> ComposterWrapper.get(world, pos, direction), Blocks.COMPOSTER);
// Support for SidedStorageBlockEntity.
ItemStorage.SIDED.registerFallback((world, pos, state, blockEntity, direction) -> {
if (blockEntity instanceof SidedStorageBlockEntity sidedStorageBlockEntity) {
return sidedStorageBlockEntity.getItemStorage(direction);
}
return null;
});
// Register Inventory fallback.
ItemStorage.SIDED.registerFallback((world, pos, state, blockEntity, direction) -> {
Inventory inventoryToWrap = null;

View file

@ -25,7 +25,6 @@ import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.ExtractionOnlyStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.InsertionOnlyStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleViewIterator;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
import net.fabricmc.fabric.impl.transfer.TransferApiImpl;
@ -45,7 +44,6 @@ import net.fabricmc.fabric.impl.transfer.TransferApiImpl;
* <ul>
* <li>{@link CombinedStorage} can be used to combine multiple instances, for example to combine multiple "slots" in one big storage.</li>
* <li>{@link ExtractionOnlyStorage} and {@link InsertionOnlyStorage} can be used when only extraction or insertion is needed.</li>
* <li>{@link SingleViewIterator} can be used to wrap a single view for use with {@link #iterator}.</li>
* <li>Resource-specific base implementations may also be available.
* For example, Fabric API provides {@link SingleVariantStorage} to accelerate implementations of transfer variant storages.</li>
* </ul>
@ -65,7 +63,7 @@ import net.fabricmc.fabric.impl.transfer.TransferApiImpl;
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
*/
@ApiStatus.Experimental
public interface Storage<T> {
public interface Storage<T> extends Iterable<StorageView<T>> {
/**
* Return an empty storage.
*/
@ -137,38 +135,21 @@ public interface Storage<T> {
}
/**
* Iterate through the contents of this storage, for the scope of the passed transaction.
* Iterate through the contents of this storage.
* Every visited {@link StorageView} represents a stored resource and an amount.
* The iterator doesn't guarantee that a single resource only occurs once during an iteration.
* Calling {@linkplain Iterator#remove remove} on the iterator is not allowed.
*
* <p>The returned iterator and any view it returns are only valid for the scope of to the passed transaction.
* They should not be used once that transaction is closed.
* Using the iterator or any view once the transaction is closed is undefined behavior.
*
* <p>{@link #insert} and {@link #extract} may be called safely during iteration.
* Extractions should be visible to an open iterator, but insertions are not required to.
* In particular, inventories with a fixed amount of slots may wish to make insertions visible to iterators,
* but inventories with a dynamic or very large amount of slots should not do that to ensure timely termination of
* the iteration.
*
* @param transaction The transaction to which the scope of the returned iterator is tied.
* @return An iterator over the contents of this storage. Calling remove on the iterator is not allowed.
*/
Iterator<? extends StorageView<T>> iterator(TransactionContext transaction);
/**
* Iterate through the contents of this storage, for the scope of the passed transaction.
* This function follows the semantics of {@link #iterator}, but returns an {@code Iterable} for use in {@code for} loops.
*
* @param transaction The transaction to which the scope of the returned iterator is tied.
* @return An iterable over the contents of this storage.
* @see #iterator
*/
@SuppressWarnings({"rawtypes", "unchecked"})
default Iterable<? extends StorageView<T>> iterable(TransactionContext transaction) {
return () -> (Iterator) iterator(transaction);
}
@Override
Iterator<StorageView<T>> iterator();
/**
* Return a view over this storage, for a specific resource, or {@code null} if none is quickly available.

View file

@ -17,6 +17,7 @@
package net.fabricmc.fabric.api.transfer.v1.storage;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import org.jetbrains.annotations.ApiStatus;
@ -78,12 +79,13 @@ public final class StorageUtil {
* @throws IllegalStateException If no transaction is passed and a transaction is already active on the current thread.
*/
public static <T> long move(@Nullable Storage<T> from, @Nullable Storage<T> to, Predicate<T> filter, long maxAmount, @Nullable TransactionContext transaction) {
Objects.requireNonNull(filter, "Filter may not be null");
if (from == null || to == null) return 0;
long totalMoved = 0;
try (Transaction iterationTransaction = Transaction.openNested(transaction)) {
for (StorageView<T> view : from.iterable(iterationTransaction)) {
for (StorageView<T> view : from) {
if (view.isResourceBlank()) continue;
T resource = view.getResource();
if (!filter.test(resource)) continue;
@ -148,12 +150,12 @@ public final class StorageUtil {
/**
* Attempt to find a resource stored in the passed storage.
*
* @see #findStoredResource(Storage, Predicate, TransactionContext)
* @see #findStoredResource(Storage, Predicate)
* @return A non-blank resource stored in the storage, or {@code null} if none could be found.
*/
@Nullable
public static <T> T findStoredResource(@Nullable Storage<T> storage, @Nullable TransactionContext transaction) {
return findStoredResource(storage, r -> true, transaction);
public static <T> T findStoredResource(@Nullable Storage<T> storage) {
return findStoredResource(storage, r -> true);
}
/**
@ -161,26 +163,15 @@ public final class StorageUtil {
*
* @param storage The storage to inspect, may be null.
* @param filter The filter. Only a resource for which this filter returns {@code true} will be returned.
* @param transaction The current transaction, or {@code null} if a transaction should be opened for this query.
* @param <T> The type of the stored resources.
* @return A non-blank resource stored in the storage that matches the filter, or {@code null} if none could be found.
*/
@Nullable
public static <T> T findStoredResource(@Nullable Storage<T> storage, Predicate<T> filter, @Nullable TransactionContext transaction) {
public static <T> T findStoredResource(@Nullable Storage<T> storage, Predicate<T> filter) {
Objects.requireNonNull(filter, "Filter may not be null");
if (storage == null) return null;
if (transaction == null) {
try (Transaction outer = Transaction.openOuter()) {
return findStoredResourceInner(storage, filter, outer);
}
} else {
return findStoredResourceInner(storage, filter, transaction);
}
}
@Nullable
private static <T> T findStoredResourceInner(Storage<T> storage, Predicate<T> filter, TransactionContext transaction) {
for (StorageView<T> view : storage.iterable(transaction)) {
for (StorageView<T> view : storage) {
if (!view.isResourceBlank() && filter.test(view.getResource())) {
return view.getResource();
}
@ -211,10 +202,11 @@ public final class StorageUtil {
*/
@Nullable
public static <T> T findExtractableResource(@Nullable Storage<T> storage, Predicate<T> filter, @Nullable TransactionContext transaction) {
Objects.requireNonNull(filter, "Filter may not be null");
if (storage == null) return null;
try (Transaction nested = Transaction.openNested(transaction)) {
for (StorageView<T> view : storage.iterable(nested)) {
for (StorageView<T> view : storage) {
// Extract below could change the resource, so we have to query it before extracting.
T resource = view.getResource();
@ -269,28 +261,17 @@ public final class StorageUtil {
* Compute the comparator output for a storage, similar to {@link ScreenHandler#calculateComparatorOutput(Inventory)}.
*
* @param storage The storage for which the comparator level should be computed.
* @param transaction The current transaction, or {@code null} if a transaction should be opened for this computation.
* @param <T> The type of the stored resources.
* @return An integer between 0 and 15 (inclusive): the comparator output for the passed storage.
*/
public static <T> int calculateComparatorOutput(@Nullable Storage<T> storage, @Nullable TransactionContext transaction) {
public static <T> int calculateComparatorOutput(@Nullable Storage<T> storage) {
if (storage == null) return 0;
if (transaction == null) {
try (Transaction outer = Transaction.openOuter()) {
return calculateComparatorOutputInner(storage, outer);
}
} else {
return calculateComparatorOutputInner(storage, transaction);
}
}
private static <T> int calculateComparatorOutputInner(Storage<T> storage, TransactionContext transaction) {
double fillPercentage = 0;
int viewCount = 0;
boolean hasNonEmptyView = false;
for (StorageView<T> view : storage.iterable(transaction)) {
for (StorageView<T> view : storage) {
viewCount++;
if (view.getAmount() > 0) {

View file

@ -26,7 +26,6 @@ import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
/**
* A {@link Storage} wrapping multiple storages.
@ -96,37 +95,29 @@ public class CombinedStorage<T, S extends Storage<T>> implements Storage<T> {
}
@Override
public Iterator<StorageView<T>> iterator(TransactionContext transaction) {
return new CombinedIterator(transaction);
public Iterator<StorageView<T>> iterator() {
return new CombinedIterator();
}
/**
* The combined iterator for multiple storages.
*/
private class CombinedIterator implements Iterator<StorageView<T>>, Transaction.CloseCallback {
boolean open = true;
final TransactionContext transaction;
private class CombinedIterator implements Iterator<StorageView<T>> {
final Iterator<S> partIterator = parts.iterator();
// Always holds the next StorageView<T>, except during next() while the iterator is being advanced.
Iterator<? extends StorageView<T>> currentPartIterator = null;
CombinedIterator(TransactionContext transaction) {
this.transaction = transaction;
CombinedIterator() {
advanceCurrentPartIterator();
transaction.addCloseCallback(this);
}
@Override
public boolean hasNext() {
return open && currentPartIterator != null && currentPartIterator.hasNext();
return currentPartIterator != null && currentPartIterator.hasNext();
}
@Override
public StorageView<T> next() {
if (!open) {
throw new NoSuchElementException("The transaction for this iterator was closed.");
}
if (!hasNext()) {
throw new NoSuchElementException();
}
@ -143,18 +134,12 @@ public class CombinedStorage<T, S extends Storage<T>> implements Storage<T> {
private void advanceCurrentPartIterator() {
while (partIterator.hasNext()) {
this.currentPartIterator = partIterator.next().iterator(transaction);
this.currentPartIterator = partIterator.next().iterator();
if (this.currentPartIterator.hasNext()) {
break;
}
}
}
@Override
public void onClose(TransactionContext transaction, Transaction.Result result) {
// As soon as the transaction is closed, this iterator is not valid anymore.
open = false;
}
}
}

View file

@ -161,8 +161,8 @@ public abstract class FilteringStorage<T> implements Storage<T> {
}
@Override
public Iterator<StorageView<T>> iterator(TransactionContext transaction) {
return Iterators.transform(backingStorage.get().iterator(transaction), FilteringStorageView::new);
public Iterator<StorageView<T>> iterator() {
return Iterators.transform(backingStorage.get().iterator(), FilteringStorageView::new);
}
@Override

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.transfer.v1.storage.base;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
/**
* <b>Optional</b> helper class that can be implemented on block entities that wish to provide a {@linkplain FluidStorage#SIDED sided fluid storage}
* and/or a {@linkplain ItemStorage#SIDED sided item storage} without having to register a provider for each block entity type.
*
* <p>How it works is that fabric registers fallback providers for instances of this interface.
* This can be used for convenient Storage registration, but please always use the SIDED lookups for queries:
* <pre>{@code
* Storage<FluidStorage> maybeFluidStorage = FluidStorage.SIDED.find(world, pos, direction);
* if (maybeFluidStorage != null) {
* // use it
* }
* Storage<ItemStorage> maybeItemStorage = ItemStorage.SIDED.find(world, pos, direction);
* if (maybeItemStorage != null) {
* // use it
* }
* }</pre>
*/
public interface SidedStorageBlockEntity {
/**
* Return a fluid storage if available on the queried side, or null otherwise.
*/
@ApiStatus.OverrideOnly
@Nullable
default Storage<FluidVariant> getFluidStorage(Direction side) {
return null;
}
/**
* Return an item storage if available on the queried side, or null otherwise.
*/
@ApiStatus.OverrideOnly
@Nullable
default Storage<ItemVariant> getItemStorage(Direction side) {
return null;
}
}

View file

@ -22,7 +22,7 @@ import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
import net.fabricmc.fabric.impl.transfer.TransferApiImpl;
/**
* A storage that is also its only storage view.
@ -36,7 +36,7 @@ import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
@ApiStatus.Experimental
public interface SingleSlotStorage<T> extends Storage<T>, StorageView<T> {
@Override
default Iterator<StorageView<T>> iterator(TransactionContext transaction) {
return SingleViewIterator.create(this, transaction);
default Iterator<StorageView<T>> iterator() {
return TransferApiImpl.singletonIterator(this);
}
}

View file

@ -1,84 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.transfer.v1.storage.base;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
/**
* An iterator for a single {@link StorageView}, tied to a transaction. Instances can be created with {@link #create}.
*
* <p>This class should only be used by implementors of {@link Storage#iterator}, that wish to expose a single storage view.
* In that case, usage of this class is recommended, as it will ensure that the storage view can't be accessed after the transaction is closed.
*
* @param <T> The type of the stored resource.
*
* <b>Experimental feature</b>, we reserve the right to remove or change it without further notice.
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
*/
@ApiStatus.Experimental
public final class SingleViewIterator<T> implements Iterator<StorageView<T>>, Transaction.CloseCallback {
/**
* Create a new iterator for the passed storage view, tied to the passed transaction.
*
* <p>The iterator will ensure that it can only be used as long as the transaction is open.
*/
public static <T> Iterator<StorageView<T>> create(StorageView<T> view, TransactionContext transaction) {
SingleViewIterator<T> it = new SingleViewIterator<>(view);
transaction.addCloseCallback(it);
return it;
}
private boolean open = true;
private boolean hasNext = true;
private final StorageView<T> view;
private SingleViewIterator(StorageView<T> view) {
this.view = view;
}
@Override
public boolean hasNext() {
return open && hasNext;
}
@Override
public StorageView<T> next() {
if (!open) {
throw new NoSuchElementException("The transaction for this iterator was closed.");
}
if (!hasNext()) {
throw new NoSuchElementException();
}
hasNext = false;
return view;
}
@Override
public void onClose(TransactionContext transaction, Transaction.Result result) {
open = false;
}
}

View file

@ -29,6 +29,19 @@ import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
* A base participant implementation that modifies itself during transactions,
* saving snapshots of its state in objects of type {@code T} in case it needs to revert to a previous state.
*
* <h3>How to use from subclasses</h3>
* <ul>
* <li>Call {@link #updateSnapshots} right before the state of your subclass is modified in a transaction.</li>
* <li>Override {@link #createSnapshot}: it is called when necessary to create an object representing the state of your subclass.</li>
* <li>Override {@link #readSnapshot}: it is called when necessary to revert to a previous state of your subclass.</li>
* <li>You may optionally override {@link #onFinalCommit}: it is called at the of a transaction that modified the state.
* For example, it could contain a call to {@code markDirty()}.</li>
* <li>(Advanced!) You may optionally override {@link #releaseSnapshot}: it is called once a snapshot object will not be used,
* for example you may wish to pool expensive state objects.</li>
* </ul>
*
* <h3>More technical explanation</h3>
*
* <p>{@link #updateSnapshots} should be called before any modification.
* This will save the state of this participant using {@link #createSnapshot} if no state was already saved for that transaction.
* When the transaction is aborted and changes need to be rolled back, {@link #readSnapshot} will be called

View file

@ -18,6 +18,7 @@ package net.fabricmc.fabric.impl.transfer;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.LoggerFactory;
@ -53,7 +54,7 @@ public class TransferApiImpl {
}
@Override
public Iterator<StorageView> iterator(TransactionContext transaction) {
public Iterator<StorageView> iterator() {
return Collections.emptyIterator();
}
@ -67,4 +68,25 @@ public class TransferApiImpl {
* Not null when writing to an inventory in a transaction, null otherwise.
*/
public static final ThreadLocal<Object> SUPPRESS_SPECIAL_LOGIC = new ThreadLocal<>();
public static <T> Iterator<T> singletonIterator(T it) {
return new Iterator<T>() {
boolean hasNext = true;
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public T next() {
if (!hasNext) {
throw new NoSuchElementException();
}
hasNext = false;
return it;
}
};
}
}

View file

@ -17,6 +17,7 @@
package net.fabricmc.fabric.impl.transfer.fluid;
import java.util.Iterator;
import java.util.List;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
@ -29,7 +30,6 @@ import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
import net.fabricmc.fabric.api.transfer.v1.storage.base.BlankVariantView;
import net.fabricmc.fabric.api.transfer.v1.storage.base.InsertionOnlyStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleViewIterator;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
import net.fabricmc.fabric.mixin.transfer.BucketItemAccessor;
@ -38,6 +38,7 @@ import net.fabricmc.fabric.mixin.transfer.BucketItemAccessor;
*/
public class EmptyBucketStorage implements InsertionOnlyStorage<FluidVariant> {
private final ContainerItemContext context;
private final List<StorageView<FluidVariant>> blankView = List.of(new BlankVariantView<>(FluidVariant.blank(), FluidConstants.BUCKET));
public EmptyBucketStorage(ContainerItemContext context) {
this.context = context;
@ -66,7 +67,7 @@ public class EmptyBucketStorage implements InsertionOnlyStorage<FluidVariant> {
}
@Override
public Iterator<StorageView<FluidVariant>> iterator(TransactionContext transaction) {
return SingleViewIterator.create(new BlankVariantView<>(FluidVariant.blank(), FluidConstants.BUCKET), transaction);
public Iterator<StorageView<FluidVariant>> iterator() {
return blankView.iterator();
}
}

View file

@ -154,7 +154,7 @@ public class ComposterWrapper extends SnapshotParticipant<Float> {
}
@Override
public Iterator<StorageView<ItemVariant>> iterator(TransactionContext transaction) {
public Iterator<StorageView<ItemVariant>> iterator() {
return Collections.emptyIterator();
}
}

View file

@ -38,7 +38,7 @@ public class TrashingStorage<T extends TransferVariant<?>> implements InsertionO
}
@Override
public Iterator<StorageView<T>> iterator(TransactionContext transaction) {
public Iterator<StorageView<T>> iterator() {
return Collections.emptyIterator();
}
}

View file

@ -70,9 +70,9 @@ public class BaseStorageTests {
// Extraction should also fail.
assertEquals(0L, noWater.simulateExtract(water, BUCKET, null));
// The fluid should be visible.
assertEquals(water, StorageUtil.findStoredResource(noWater, null));
assertEquals(water, StorageUtil.findStoredResource(noWater));
// Test the filter.
assertEquals(null, StorageUtil.findStoredResource(noWater, fv -> fv.isOf(Fluids.LAVA), null));
assertEquals(null, StorageUtil.findStoredResource(noWater, fv -> fv.isOf(Fluids.LAVA)));
// But it can't be extracted, even through a storage view.
assertEquals(null, StorageUtil.findExtractableResource(noWater, null));
assertEquals(null, StorageUtil.findExtractableContent(noWater, null));

View file

@ -165,7 +165,7 @@ class FluidItemTests {
PotionUtil.setPotion(testInventory.getStack(0), Potions.LUCK);
Storage<FluidVariant> luckyStorage = new InventoryContainerItem(testInventory, 0).find(FluidStorage.ITEM);
if (StorageUtil.findStoredResource(luckyStorage, null) != null) {
if (StorageUtil.findStoredResource(luckyStorage) != null) {
throw new AssertionError("Found a resource in an unhandled potion.");
}
}

View file

@ -90,7 +90,7 @@ class ItemTests {
private static void testInventoryWrappers() {
ItemVariant emptyBucket = ItemVariant.of(Items.BUCKET);
TestSidedInventory testInventory = new TestSidedInventory();
checkComparatorOutput(testInventory, null);
checkComparatorOutput(testInventory);
// Create a few wrappers.
InventoryStorage unsidedWrapper = InventoryStorage.of(testInventory, null);
@ -126,7 +126,7 @@ class ItemTests {
if (!testInventory.getStack(0).isEmpty()) throw new AssertionError("Slot 0 should have been empty.");
if (!testInventory.getStack(1).isOf(Items.BUCKET) || testInventory.getStack(1).getCount() != 1) throw new AssertionError("Slot 1 should have been a bucket.");
checkComparatorOutput(testInventory, null);
checkComparatorOutput(testInventory);
}
private static boolean stackEquals(ItemStack stack, Item item, int count) {
@ -179,7 +179,7 @@ class ItemTests {
throw new AssertionError("Only 6 diamonds should have been inserted.");
}
checkComparatorOutput(inventory, transaction);
checkComparatorOutput(inventory);
}
}
@ -197,7 +197,7 @@ class ItemTests {
throw new AssertionError("Only 5 pickaxes should have been inserted.");
}
checkComparatorOutput(inventory, transaction);
checkComparatorOutput(inventory);
}
}
@ -216,11 +216,11 @@ class ItemTests {
}
}
private static void checkComparatorOutput(Inventory inventory, @Nullable Transaction transaction) {
private static void checkComparatorOutput(Inventory inventory) {
Storage<ItemVariant> storage = InventoryStorage.of(inventory, null);
int vanillaOutput = ScreenHandler.calculateComparatorOutput(inventory);
int transferApiOutput = StorageUtil.calculateComparatorOutput(storage, transaction);
int transferApiOutput = StorageUtil.calculateComparatorOutput(storage);
if (vanillaOutput != transferApiOutput) {
String error = String.format(