24w09a - Transfer API ()

* First pass on transfer API

* More fixes

* Another fix

* Small fixes

* Move transfer API tests to junit

* Fix client run

* Small fixes

* Copy stack when component changes

* Small improvement

* More tests and docs fixes

* Mutate existing stack
This commit is contained in:
modmuss 2024-03-01 17:46:49 +00:00 committed by GitHub
parent 2f977a43d9
commit 9d6d003f62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 586 additions and 385 deletions

View file

@ -18,13 +18,14 @@ package net.fabricmc.fabric.api.transfer.v1.fluid;
import org.jetbrains.annotations.Nullable;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BucketItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.PotionUtil;
import net.minecraft.potion.Potions;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
@ -161,8 +162,8 @@ public final class FluidStorage {
combinedItemApiProvider(Items.GLASS_BOTTLE).register(context -> {
return new EmptyItemFluidStorage(context, emptyBottle -> {
ItemStack newStack = emptyBottle.toStack();
PotionUtil.setPotion(newStack, Potions.WATER);
return ItemVariant.of(Items.POTION, newStack.getNbt());
newStack.set(DataComponentTypes.POTION_CONTENTS, new PotionContentsComponent(Potions.WATER));
return ItemVariant.of(Items.POTION, newStack.getComponentChanges());
}, Fluids.WATER, FluidConstants.BOTTLE);
});
// Register water potion storage

View file

@ -16,29 +16,35 @@
package net.fabricmc.fabric.api.transfer.v1.fluid;
import com.mojang.serialization.Codec;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.component.ComponentChanges;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.registry.entry.RegistryEntry;
import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
import net.fabricmc.fabric.impl.transfer.VariantCodecs;
import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantImpl;
/**
* An immutable association of a still fluid and an optional NBT tag.
* An immutable association of a still fluid and data components.
*
* <p>Do not extend this class. Use {@link #of(Fluid)} and {@link #of(Fluid, NbtCompound)} to create instances.
* <p>Do not extend this class. Use {@link #of(Fluid)} and {@link #of(Fluid, ComponentChanges)} to create instances.
*
* <p>{@link net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering} can be used for client-side rendering of fluid variants.
*
* <p><b>Fluid variants must always be compared with {@code equals}, never by reference!</b>
* {@code hashCode} is guaranteed to be correct and constant time independently of the size of the NBT.
* {@code hashCode} is guaranteed to be correct and constant time independently of the size of the components.
*/
@ApiStatus.NonExtendable
public interface FluidVariant extends TransferVariant<Fluid> {
Codec<FluidVariant> CODEC = VariantCodecs.FLUID_CODEC;
PacketCodec<RegistryByteBuf, FluidVariant> PACKET_CODEC = VariantCodecs.FLUID_PACKET_CODEC;
/**
* Retrieve a blank FluidVariant.
*/
@ -54,7 +60,7 @@ public interface FluidVariant extends TransferVariant<Fluid> {
* {@code FluidVariant.of(Fluids.FLOWING_WATER).getFluid() == Fluids.WATER}.
*/
static FluidVariant of(Fluid fluid) {
return of(fluid, null);
return of(fluid, ComponentChanges.EMPTY);
}
/**
@ -62,10 +68,10 @@ public interface FluidVariant extends TransferVariant<Fluid> {
*
* <p>The flowing and still variations of {@linkplain net.minecraft.fluid.FlowableFluid flowable fluids}
* are normalized to always refer to the still fluid. For example,
* {@code FluidVariant.of(Fluids.FLOWING_WATER, nbt).getFluid() == Fluids.WATER}.
* {@code FluidVariant.of(Fluids.FLOWING_WATER, ComponentChanges.EMPTY).getFluid() == Fluids.WATER}.
*/
static FluidVariant of(Fluid fluid, @Nullable NbtCompound nbt) {
return FluidVariantImpl.of(fluid, nbt);
static FluidVariant of(Fluid fluid, ComponentChanges components) {
return FluidVariantImpl.of(fluid, components);
}
/**
@ -75,19 +81,7 @@ public interface FluidVariant extends TransferVariant<Fluid> {
return getObject();
}
/**
* Deserialize a variant from an NBT compound tag, assuming it was serialized using {@link #toNbt}.
*
* <p>If an error occurs during deserialization, it will be logged with the DEBUG level, and a blank variant will be returned.
*/
static FluidVariant fromNbt(NbtCompound nbt) {
return FluidVariantImpl.fromNbt(nbt);
}
/**
* Read a variant from a packet byte buffer, assuming it was serialized using {@link #toPacket}.
*/
static FluidVariant fromPacket(PacketByteBuf buf) {
return FluidVariantImpl.fromPacket(buf);
default RegistryEntry<Fluid> getRegistryEntry() {
return getFluid().getRegistryEntry();
}
}

View file

@ -71,7 +71,7 @@ public final class EmptyItemFluidStorage implements InsertionOnlyStorage<FluidVa
* @param insertableAmount The amount of fluid that can be inserted.
*/
public EmptyItemFluidStorage(ContainerItemContext context, Item fullItem, Fluid insertableFluid, long insertableAmount) {
this(context, emptyVariant -> ItemVariant.of(fullItem, emptyVariant.getNbt()), insertableFluid, insertableAmount);
this(context, emptyVariant -> ItemVariant.of(fullItem, emptyVariant.getComponents()), insertableFluid, insertableAmount);
}
/**

View file

@ -52,7 +52,7 @@ public final class FullItemFluidStorage implements ExtractionOnlyStorage<FluidVa
* @param containedAmount How much of {@code containedFluid} is contained.
*/
public FullItemFluidStorage(ContainerItemContext context, Item emptyItem, FluidVariant containedFluid, long containedAmount) {
this(context, fullVariant -> ItemVariant.of(emptyItem, fullVariant.getNbt()), containedFluid, containedAmount);
this(context, fullVariant -> ItemVariant.of(emptyItem, fullVariant.getComponents()), containedFluid, containedAmount);
}
/**

View file

@ -19,6 +19,7 @@ package net.fabricmc.fabric.api.transfer.v1.fluid.base;
import java.util.Objects;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.RegistryWrapper;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
@ -66,8 +67,14 @@ public abstract class SingleFluidStorage extends SingleVariantStorage<FluidVaria
* Simple implementation of reading from NBT, to match what is written by {@link #writeNbt}.
* Other formats are allowed, this is just a suggestion.
*/
public void readNbt(NbtCompound nbt) {
variant = FluidVariant.fromNbt(nbt.getCompound("variant"));
amount = nbt.getLong("amount");
public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.readNbt(this, FluidVariant.CODEC, FluidVariant::blank, nbt, wrapperLookup);
}
/**
* Simple implementation of writing to NBT. Other formats are allowed, this is just a convenient suggestion.
*/
public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.writeNbt(this, FluidVariant.CODEC, nbt, wrapperLookup);
}
}

View file

@ -16,26 +16,34 @@
package net.fabricmc.fabric.api.transfer.v1.item;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import com.mojang.serialization.Codec;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.component.ComponentChanges;
import net.minecraft.item.Item;
import net.minecraft.item.ItemConvertible;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.registry.entry.RegistryEntry;
import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
import net.fabricmc.fabric.impl.transfer.VariantCodecs;
import net.fabricmc.fabric.impl.transfer.item.ItemVariantImpl;
/**
* An immutable count-less ItemStack, i.e. an immutable association of an item and an optional NBT compound tag.
* An immutable count-less ItemStack, i.e. an immutable association of an item and its data components.
*
* <p>Do not implement, use the static {@code of(...)} functions instead.
*/
@ApiStatus.NonExtendable
public interface ItemVariant extends TransferVariant<Item> {
Codec<ItemVariant> CODEC = VariantCodecs.ITEM_CODEC;
PacketCodec<RegistryByteBuf, ItemVariant> PACKET_CODEC = VariantCodecs.ITEM_PACKET_CODEC;
/**
* Retrieve a blank ItemVariant.
*/
@ -47,28 +55,28 @@ public interface ItemVariant extends TransferVariant<Item> {
* Retrieve an ItemVariant with the item and tag of a stack.
*/
static ItemVariant of(ItemStack stack) {
return of(stack.getItem(), stack.getNbt());
return of(stack.getItem(), stack.getComponentChanges());
}
/**
* Retrieve an ItemVariant with an item and without a tag.
*/
static ItemVariant of(ItemConvertible item) {
return of(item, null);
return of(item, ComponentChanges.EMPTY);
}
/**
* Retrieve an ItemVariant with an item and an optional tag.
*/
static ItemVariant of(ItemConvertible item, @Nullable NbtCompound tag) {
return ItemVariantImpl.of(item.asItem(), tag);
static ItemVariant of(ItemConvertible item, ComponentChanges components) {
return ItemVariantImpl.of(item.asItem(), components);
}
/**
* Return true if the item and tag of this variant match those of the passed stack, and false otherwise.
*/
default boolean matches(ItemStack stack) {
return isOf(stack.getItem()) && nbtMatches(stack.getNbt());
return isOf(stack.getItem()) && Objects.equals(stack.getComponentChanges(), getComponents());
}
/**
@ -78,6 +86,10 @@ public interface ItemVariant extends TransferVariant<Item> {
return getObject();
}
default RegistryEntry<Item> getRegistryEntry() {
return getItem().getRegistryEntry();
}
/**
* Create a new item stack with count 1 from this variant.
*/
@ -92,25 +104,6 @@ public interface ItemVariant extends TransferVariant<Item> {
*/
default ItemStack toStack(int count) {
if (isBlank()) return ItemStack.EMPTY;
ItemStack stack = new ItemStack(getItem(), count);
stack.setNbt(copyNbt());
return stack;
}
/**
* Deserialize a variant from an NBT compound tag, assuming it was serialized using
* {@link #toNbt}. If an error occurs during deserialization, it will be logged
* with the DEBUG level, and a blank variant will be returned.
*/
static ItemVariant fromNbt(NbtCompound nbt) {
return ItemVariantImpl.fromNbt(nbt);
}
/**
* Write a variant from a packet byte buffer, assuming it was serialized using
* {@link #toPacket}.
*/
static ItemVariant fromPacket(PacketByteBuf buf) {
return ItemVariantImpl.fromPacket(buf);
return new ItemStack(getRegistryEntry(), count, getComponents());
}
}

View file

@ -17,6 +17,7 @@
package net.fabricmc.fabric.api.transfer.v1.item.base;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.RegistryWrapper;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
@ -40,8 +41,14 @@ public abstract class SingleItemStorage extends SingleVariantStorage<ItemVariant
* Simple implementation of reading from NBT, to match what is written by {@link #writeNbt}.
* Other formats are allowed, this is just a suggestion.
*/
public void readNbt(NbtCompound nbt) {
variant = ItemVariant.fromNbt(nbt.getCompound("variant"));
amount = nbt.getLong("amount");
public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.readNbt(this, ItemVariant.CODEC, ItemVariant::blank, nbt, wrapperLookup);
}
/**
* Simple implementation of writing to NBT. Other formats are allowed, this is just a convenient suggestion.
*/
public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.writeNbt(this, ItemVariant.CODEC, nbt, wrapperLookup);
}
}

View file

@ -18,13 +18,10 @@ package net.fabricmc.fabric.api.transfer.v1.storage;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.component.ComponentChanges;
/**
* An immutable association of an immutable object instance (for example {@code Item} or {@code Fluid}) and an optional NBT tag.
* An immutable association of an immutable object instance (for example {@code Item} or {@code Fluid}) and data components.
*
* <p>This is exposed for convenience for code that needs to be generic across multiple transfer variants,
* but note that a {@link Storage} is not necessarily bound to {@code TransferVariant}. Its generic parameter can be any immutable object.
@ -46,18 +43,15 @@ public interface TransferVariant<O> {
O getObject();
/**
* Return the underlying tag.
*
* <p><b>NEVER MUTATE THIS NBT TAG</b>, if you need to mutate it you can use {@link #copyNbt()} to retrieve a copy instead.
* @return The {@link ComponentChanges} of this variant.
*/
@Nullable
NbtCompound getNbt();
ComponentChanges getComponents();
/**
* Return true if this variant has a tag, false otherwise.
* Return true if this variant has a component changes.
*/
default boolean hasNbt() {
return getNbt() != null;
default boolean hasComponents() {
return !getComponents().isEmpty();
}
/**
@ -65,8 +59,8 @@ public interface TransferVariant<O> {
*
* <p>Note: True is returned if both tags are {@code null}.
*/
default boolean nbtMatches(@Nullable NbtCompound other) {
return Objects.equals(getNbt(), other);
default boolean componentsMatches(ComponentChanges other) {
return Objects.equals(getComponents(), other);
}
/**
@ -75,37 +69,4 @@ public interface TransferVariant<O> {
default boolean isOf(O object) {
return getObject() == object;
}
/**
* Return a copy of the tag of this variant, or {@code null} if this variant doesn't have a tag.
*
* <p>Note: Use {@link #nbtMatches} if you only need to check for custom tag equality, or {@link #getNbt()} if you don't need to mutate the tag.
*/
@Nullable
default NbtCompound copyNbt() {
NbtCompound nbt = getNbt();
return nbt == null ? null : nbt.copy();
}
/**
* Return a copy of the tag of this variant, or a new compound if this variant doesn't have a tag.
*/
default NbtCompound copyOrCreateNbt() {
NbtCompound nbt = getNbt();
return nbt == null ? new NbtCompound() : nbt.copy();
}
/**
* Save this variant into an NBT compound tag. Subinterfaces should have a matching static {@code fromNbt}.
*
* <p>Note: This is safe to use for persisting data as objects are saved using their full Identifier.
*/
NbtCompound toNbt();
/**
* Write this variant into a packet byte buffer. Subinterfaces should have a matching static {@code fromPacket}.
*
* <p>Implementation note: Objects are saved using their raw registry integer id.
*/
void toPacket(PacketByteBuf buf);
}

View file

@ -16,7 +16,19 @@
package net.fabricmc.fabric.api.transfer.v1.storage.base;
import java.util.function.Supplier;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtOps;
import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.util.Util;
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
@ -36,6 +48,8 @@ import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant;
* @see net.fabricmc.fabric.api.transfer.v1.item.base.SingleItemStorage SingleItemStorage for item variants.
*/
public abstract class SingleVariantStorage<T extends TransferVariant<?>> extends SnapshotParticipant<ResourceAmount<T>> implements SingleSlotStorage<T> {
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-transfer-api-v1/variant-storage");
public T variant = getBlankVariant();
public long amount = 0;
@ -67,15 +81,6 @@ public abstract class SingleVariantStorage<T extends TransferVariant<?>> extends
return true;
}
/**
* Simple implementation of writing to NBT. Other formats are allowed, this is just a convenient suggestion.
*/
// Reading from NBT is not provided because it would need to call the static FluidVariant/ItemVariant.fromNbt
public void writeNbt(NbtCompound nbt) {
nbt.put("variant", variant.toNbt());
nbt.putLong("amount", amount);
}
@Override
public long insert(T insertedVariant, long maxAmount, TransactionContext transaction) {
StoragePreconditions.notBlankNotNegative(insertedVariant, maxAmount);
@ -157,4 +162,43 @@ public abstract class SingleVariantStorage<T extends TransferVariant<?>> extends
public String toString() {
return "SingleVariantStorage[%d %s]".formatted(amount, variant);
}
/**
* Read a {@link SingleVariantStorage} from NBT.
*
* @param storage the {@link SingleVariantStorage} to read into
* @param codec the item variant codec
* @param fallback the fallback item variant, used when the NBT is invalid
* @param nbt the NBT to read from
* @param wrapperLookup the {@link RegistryWrapper.WrapperLookup} instance
* @param <T> the type of the item variant
*/
public static <T extends TransferVariant<?>> void readNbt(SingleVariantStorage<T> storage, Codec<T> codec, Supplier<T> fallback, NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
final RegistryOps<NbtElement> ops = wrapperLookup.getOps(NbtOps.INSTANCE);
final DataResult<T> result = codec.parse(ops, nbt.getCompound("variant"));
if (result.error().isPresent()) {
LOGGER.debug("Failed to load an ItemVariant from NBT: {}", result.error().get());
storage.variant = fallback.get();
} else {
storage.variant = result.result().get();
}
storage.amount = nbt.getLong("amount");
}
/**
* Write a {@link SingleVariantStorage} to NBT.
*
* @param storage the {@link SingleVariantStorage} to write from
* @param codec the item variant codec
* @param nbt the NBT to write to
* @param wrapperLookup the {@link RegistryWrapper.WrapperLookup} instance
* @param <T> the type of the item variant
*/
public static <T extends TransferVariant<?>> void writeNbt(SingleVariantStorage<T> storage, Codec<T> codec, NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
final RegistryOps<NbtElement> ops = wrapperLookup.getOps(NbtOps.INSTANCE);
nbt.put("variant", Util.getResult(codec.encodeStart(ops, storage.variant), RuntimeException::new));
nbt.putLong("amount", storage.amount);
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.transfer;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.component.ComponentChanges;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKeys;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantImpl;
import net.fabricmc.fabric.impl.transfer.item.ItemVariantImpl;
public class VariantCodecs {
public static final Codec<ItemVariant> ITEM_CODEC = RecordCodecBuilder.create(instance -> instance.group(
Registries.ITEM.getEntryCodec().fieldOf("item").forGetter(ItemVariant::getRegistryEntry),
ComponentChanges.CODEC.fieldOf("components").forGetter(ItemVariant::getComponents)
).apply(instance, ItemVariantImpl::of)
);
public static final PacketCodec<RegistryByteBuf, ItemVariant> ITEM_PACKET_CODEC = PacketCodec.tuple(
PacketCodecs.registryEntry(RegistryKeys.ITEM), ItemVariant::getRegistryEntry,
ComponentChanges.PACKET_CODEC, ItemVariant::getComponents,
ItemVariantImpl::of
);
public static final Codec<FluidVariant> FLUID_CODEC = RecordCodecBuilder.create(instance -> instance.group(
Registries.FLUID.getEntryCodec().fieldOf("fluid").forGetter(FluidVariant::getRegistryEntry),
ComponentChanges.CODEC.fieldOf("components").forGetter(FluidVariant::getComponents)
).apply(instance, FluidVariantImpl::of)
);
public static final PacketCodec<RegistryByteBuf, FluidVariant> FLUID_PACKET_CODEC = PacketCodec.tuple(
PacketCodecs.registryEntry(RegistryKeys.FLUID), FluidVariant::getRegistryEntry,
ComponentChanges.PACKET_CODEC, FluidVariant::getComponents,
FluidVariantImpl::of
);
}

View file

@ -55,7 +55,7 @@ public class EmptyBucketStorage implements InsertionOnlyStorage<FluidVariant> {
// Make sure the resource is a correct fluid mapping: the fluid <-> bucket mapping must be bidirectional.
if (fullBucket instanceof BucketItemAccessor accessor && resource.isOf(accessor.fabric_getFluid())) {
if (maxAmount >= FluidConstants.BUCKET) {
ItemVariant newVariant = ItemVariant.of(fullBucket, context.getItemVariant().getNbt());
ItemVariant newVariant = ItemVariant.of(fullBucket, context.getItemVariant().getComponents());
if (context.exchange(newVariant, 1, transaction) == 1) {
return FluidConstants.BUCKET;

View file

@ -19,22 +19,21 @@ package net.fabricmc.fabric.impl.transfer.fluid;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.component.ComponentChanges;
import net.minecraft.fluid.FlowableFluid;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
public class FluidVariantImpl implements FluidVariant {
public static FluidVariant of(Fluid fluid, @Nullable NbtCompound nbt) {
public static FluidVariant of(Fluid fluid, ComponentChanges components) {
Objects.requireNonNull(fluid, "Fluid may not be null.");
Objects.requireNonNull(components, "Components may not be null.");
if (!fluid.isStill(fluid.getDefaultState()) && fluid != Fluids.EMPTY) {
// Note: the empty fluid is not still, that's why we check for it specifically.
@ -49,25 +48,27 @@ public class FluidVariantImpl implements FluidVariant {
}
}
if (nbt == null || fluid == Fluids.EMPTY) {
if (components.isEmpty() || fluid == Fluids.EMPTY) {
// Use the cached variant inside the fluid
return ((FluidVariantCache) fluid).fabric_getCachedFluidVariant();
} else {
// TODO explore caching fluid variants for non null tags.
return new FluidVariantImpl(fluid, nbt);
return new FluidVariantImpl(fluid, components);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-transfer-api-v1/fluid");
public static FluidVariant of(RegistryEntry<Fluid> fluid, ComponentChanges components) {
return of(fluid.value(), components);
}
private final Fluid fluid;
private final @Nullable NbtCompound nbt;
private final ComponentChanges components;
private final int hashCode;
public FluidVariantImpl(Fluid fluid, NbtCompound nbt) {
public FluidVariantImpl(Fluid fluid, ComponentChanges components) {
this.fluid = fluid;
this.nbt = nbt == null ? null : nbt.copy(); // defensive copy
this.hashCode = Objects.hash(fluid, nbt);
this.components = components;
this.hashCode = Objects.hash(fluid, components);
}
@Override
@ -81,57 +82,13 @@ public class FluidVariantImpl implements FluidVariant {
}
@Override
public @Nullable NbtCompound getNbt() {
return nbt;
}
@Override
public NbtCompound toNbt() {
NbtCompound result = new NbtCompound();
result.putString("fluid", Registries.FLUID.getId(fluid).toString());
if (nbt != null) {
result.put("tag", nbt.copy());
}
return result;
}
public static FluidVariant fromNbt(NbtCompound compound) {
try {
Fluid fluid = Registries.FLUID.get(new Identifier(compound.getString("fluid")));
NbtCompound nbt = compound.contains("tag") ? compound.getCompound("tag") : null;
return of(fluid, nbt);
} catch (RuntimeException runtimeException) {
LOGGER.debug("Tried to load an invalid FluidVariant from NBT: {}", compound, runtimeException);
return FluidVariant.blank();
}
}
@Override
public void toPacket(PacketByteBuf buf) {
if (isBlank()) {
buf.writeBoolean(false);
} else {
buf.writeBoolean(true);
buf.writeVarInt(Registries.FLUID.getRawId(fluid));
buf.writeNbt(nbt);
}
}
public static FluidVariant fromPacket(PacketByteBuf buf) {
if (!buf.readBoolean()) {
return FluidVariant.blank();
} else {
Fluid fluid = Registries.FLUID.get(buf.readVarInt());
NbtCompound nbt = buf.readNbt();
return of(fluid, nbt);
}
public @Nullable ComponentChanges getComponents() {
return components;
}
@Override
public String toString() {
return "FluidVariant{fluid=" + fluid + ", tag=" + nbt + '}';
return "FluidVariant{fluid=" + fluid + ", components=" + components + '}';
}
@Override
@ -142,7 +99,7 @@ public class FluidVariantImpl implements FluidVariant {
FluidVariantImpl fluidVariant = (FluidVariantImpl) o;
// fail fast with hash code
return hashCode == fluidVariant.hashCode && fluid == fluidVariant.fluid && nbtMatches(fluidVariant.nbt);
return hashCode == fluidVariant.hashCode && fluid == fluidVariant.fluid && componentsMatches(fluidVariant.components);
}
@Override

View file

@ -16,13 +16,18 @@
package net.fabricmc.fabric.impl.transfer.fluid;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.PotionUtil;
import net.minecraft.potion.Potion;
import net.minecraft.potion.Potions;
import net.minecraft.registry.entry.RegistryEntry;
import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants;
@ -47,8 +52,9 @@ public class WaterPotionStorage implements ExtractionOnlyStorage<FluidVariant>,
private static boolean isWaterPotion(ContainerItemContext context) {
ItemVariant variant = context.getItemVariant();
return variant.isOf(Items.POTION) && PotionUtil.getPotion(variant.getNbt()) == Potions.WATER;
Optional<? extends PotionContentsComponent> potionContents = variant.getComponents().get(DataComponentTypes.POTION_CONTENTS);
RegistryEntry<Potion> potion = potionContents.map(PotionContentsComponent::potion).orElse(null).orElse(null);
return variant.isOf(Items.POTION) && potion == Potions.WATER;
}
private final ContainerItemContext context;
@ -63,8 +69,8 @@ public class WaterPotionStorage implements ExtractionOnlyStorage<FluidVariant>,
private ItemVariant mapToGlassBottle() {
ItemStack newStack = context.getItemVariant().toStack();
PotionUtil.setPotion(newStack, Potions.EMPTY);
return ItemVariant.of(Items.GLASS_BOTTLE, newStack.getNbt());
newStack.set(DataComponentTypes.POTION_CONTENTS, PotionContentsComponent.DEFAULT);
return ItemVariant.of(Items.GLASS_BOTTLE, newStack.getComponentChanges());
}
@Override

View file

@ -16,12 +16,15 @@
package net.fabricmc.fabric.impl.transfer.item;
import java.util.Objects;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.block.entity.BrewingStandBlockEntity;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.block.enums.ChestType;
import net.minecraft.component.DataComponentType;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.math.BlockPos;
@ -153,9 +156,18 @@ class InventorySlotWrapper extends SingleStackStorage {
}
if (!original.isEmpty() && original.getItem() == currentStack.getItem()) {
// None is empty and the items match: just update the amount and NBT, and reuse the original stack.
// Components have changed, we need to copy the stack.
if (!Objects.equals(original.getComponentChanges(), currentStack.getComponentChanges())) {
// Remove all the existing components and copy the new ones on top.
for (DataComponentType<?> type : original.getComponents().getTypes()) {
original.set(type, null);
}
original.copyComponentsFrom(currentStack.getComponents());
}
// None is empty and the items and components match: just update the amount, and reuse the original stack.
original.setCount(currentStack.getCount());
original.setNbt(currentStack.hasNbt() ? currentStack.getNbt().copy() : null);
setStack(original);
} else {
// Otherwise assume everything was taken from original so empty it.

View file

@ -19,45 +19,44 @@ package net.fabricmc.fabric.impl.transfer.item;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.component.ComponentChanges;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;
import net.minecraft.registry.entry.RegistryEntry;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
public class ItemVariantImpl implements ItemVariant {
public static ItemVariant of(Item item, @Nullable NbtCompound tag) {
public static ItemVariant of(Item item, ComponentChanges components) {
Objects.requireNonNull(item, "Item may not be null.");
Objects.requireNonNull(components, "Components may not be null.");
// Only tag-less or empty item variants are cached for now.
if (tag == null || item == Items.AIR) {
if (components.isEmpty() || item == Items.AIR) {
return ((ItemVariantCache) item).fabric_getCachedItemVariant();
} else {
return new ItemVariantImpl(item, tag);
return new ItemVariantImpl(item, components);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-transfer-api-v1/item");
public static ItemVariant of(RegistryEntry<Item> item, ComponentChanges components) {
return of(item.value(), components);
}
private final Item item;
private final @Nullable NbtCompound nbt;
private final ComponentChanges components;
private final int hashCode;
/**
* Lazily computed, equivalent to calling toStack(1). <b>MAKE SURE IT IS NEVER MODIFIED!</b>
*/
private volatile @Nullable ItemStack cachedStack = null;
public ItemVariantImpl(Item item, NbtCompound nbt) {
public ItemVariantImpl(Item item, ComponentChanges components) {
this.item = item;
this.nbt = nbt == null ? null : nbt.copy(); // defensive copy
hashCode = Objects.hash(item, nbt);
this.components = components;
hashCode = Objects.hash(item, components);
}
@Override
@ -67,8 +66,8 @@ public class ItemVariantImpl implements ItemVariant {
@Nullable
@Override
public NbtCompound getNbt() {
return nbt;
public ComponentChanges getComponents() {
return components;
}
@Override
@ -76,53 +75,9 @@ public class ItemVariantImpl implements ItemVariant {
return item == Items.AIR;
}
@Override
public NbtCompound toNbt() {
NbtCompound result = new NbtCompound();
result.putString("item", Registries.ITEM.getId(item).toString());
if (nbt != null) {
result.put("tag", nbt.copy());
}
return result;
}
public static ItemVariant fromNbt(NbtCompound tag) {
try {
Item item = Registries.ITEM.get(new Identifier(tag.getString("item")));
NbtCompound aTag = tag.contains("tag") ? tag.getCompound("tag") : null;
return of(item, aTag);
} catch (RuntimeException runtimeException) {
LOGGER.debug("Tried to load an invalid ItemVariant from NBT: {}", tag, runtimeException);
return ItemVariant.blank();
}
}
@Override
public void toPacket(PacketByteBuf buf) {
if (isBlank()) {
buf.writeBoolean(false);
} else {
buf.writeBoolean(true);
buf.writeVarInt(Item.getRawId(item));
buf.writeNbt(nbt);
}
}
public static ItemVariant fromPacket(PacketByteBuf buf) {
if (!buf.readBoolean()) {
return ItemVariant.blank();
} else {
Item item = Item.byRawId(buf.readVarInt());
NbtCompound nbt = buf.readNbt();
return of(item, nbt);
}
}
@Override
public String toString() {
return "ItemVariant{item=" + item + ", tag=" + nbt + '}';
return "ItemVariant{item=" + item + ", components=" + components + '}';
}
@Override
@ -133,7 +88,7 @@ public class ItemVariantImpl implements ItemVariant {
ItemVariantImpl ItemVariant = (ItemVariantImpl) o;
// fail fast with hash code
return hashCode == ItemVariant.hashCode && item == ItemVariant.item && nbtMatches(ItemVariant.nbt);
return hashCode == ItemVariant.hashCode && item == ItemVariant.item && componentsMatches(ItemVariant.components);
}
@Override

View file

@ -23,6 +23,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.component.ComponentChanges;
import net.minecraft.fluid.Fluid;
import net.minecraft.sound.SoundEvent;
@ -43,7 +44,7 @@ import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantImpl;
@SuppressWarnings("unused")
public class FluidMixin implements FluidVariantCache {
@SuppressWarnings("ConstantConditions")
private final FluidVariant fabric_cachedFluidVariant = new FluidVariantImpl((Fluid) (Object) this, null);
private final FluidVariant fabric_cachedFluidVariant = new FluidVariantImpl((Fluid) (Object) this, ComponentChanges.EMPTY);
@Override
public FluidVariant fabric_getCachedFluidVariant() {

View file

@ -18,6 +18,7 @@ package net.fabricmc.fabric.mixin.transfer;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.component.ComponentChanges;
import net.minecraft.item.Item;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
@ -30,7 +31,7 @@ import net.fabricmc.fabric.impl.transfer.item.ItemVariantImpl;
@Mixin(Item.class)
public class ItemMixin implements ItemVariantCache {
@SuppressWarnings("ConstantConditions")
private final ItemVariant fabric_cachedItemVariant = new ItemVariantImpl((Item) (Object) this, null);
private final ItemVariant fabric_cachedItemVariant = new ItemVariantImpl((Item) (Object) this, ComponentChanges.EMPTY);
@Override
public ItemVariant fabric_getCachedItemVariant() {

View file

@ -16,24 +16,18 @@
package net.fabricmc.fabric.test.transfer.unittests;
import org.slf4j.LoggerFactory;
import net.minecraft.Bootstrap;
import net.minecraft.SharedConstants;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registries;
import net.fabricmc.api.ModInitializer;
public abstract class AbstractTransferApiTest {
protected static void bootstrap() {
SharedConstants.createGameVersion();
Bootstrap.initialize();
}
public class UnitTestsInitializer implements ModInitializer {
@Override
public void onInitialize() {
AttributeTests.run();
BaseStorageTests.run();
FluidItemTests.run();
FluidTests.run();
FluidVariantTests.run();
ItemTests.run();
PlayerInventoryStorageTests.run();
SingleVariantItemStorageTests.run();
TransactionStateTests.run();
UnderlyingViewTests.run();
LoggerFactory.getLogger("fabric-transfer-api-v1 testmod").info("Transfer API unit tests successful.");
protected static DynamicRegistryManager staticDrm() {
return DynamicRegistryManager.of(Registries.REGISTRIES);
}
}

View file

@ -16,7 +16,10 @@
package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.fluid.Fluids;
import net.minecraft.sound.SoundEvents;
@ -28,13 +31,14 @@ import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributes;
/**
* Test that fluid attributes for vanilla fluids have the correct values.
*/
public class AttributeTests {
public static void run() {
testWater();
testLava();
public class AttributeTests extends AbstractTransferApiTest {
@BeforeAll
static void beforeAll() {
bootstrap();
}
private static void testWater() {
@Test
public void testWater() {
FluidVariant water = FluidVariant.of(Fluids.WATER);
assertEquals(SoundEvents.ITEM_BUCKET_FILL, FluidVariantAttributes.getFillSound(water));
@ -45,7 +49,8 @@ public class AttributeTests {
assertEquals(false, FluidVariantAttributes.isLighterThanAir(water));
}
private static void testLava() {
@Test
public void testLava() {
FluidVariant lava = FluidVariant.of(Fluids.LAVA);
assertEquals(SoundEvents.ITEM_BUCKET_FILL_LAVA, FluidVariantAttributes.getFillSound(lava));

View file

@ -17,10 +17,13 @@
package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants.BUCKET;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import java.util.Iterator;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.fluid.Fluids;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
@ -32,13 +35,14 @@ import net.fabricmc.fabric.api.transfer.v1.storage.base.FilteringStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
public class BaseStorageTests {
public static void run() {
testFilteringStorage();
testNonEmptyIteratorWithModifiedView();
public class BaseStorageTests extends AbstractTransferApiTest {
@BeforeAll
static void beforeAll() {
bootstrap();
}
private static void testFilteringStorage() {
@Test
public void testFilteringStorage() {
SingleVariantStorage<FluidVariant> storage = new SingleVariantStorage<>() {
@Override
protected FluidVariant getBlankVariant() {
@ -102,7 +106,8 @@ public class BaseStorageTests {
* Regression test for <a href="https://github.com/FabricMC/fabric/issues/3414">
* {@code nonEmptyIterator} not handling views that become empty during iteration correctly</a>.
*/
private static void testNonEmptyIteratorWithModifiedView() {
@Test
public void testNonEmptyIteratorWithModifiedView() {
SingleVariantStorage<FluidVariant> storage = SingleFluidStorage.withFixedCapacity(BUCKET, () -> { });
storage.variant = FluidVariant.of(Fluids.WATER);

View file

@ -18,18 +18,25 @@ package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants.BOTTLE;
import static net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants.BUCKET;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.fluid.Fluids;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.PotionUtil;
import net.minecraft.potion.Potion;
import net.minecraft.potion.Potions;
import net.minecraft.registry.entry.RegistryEntry;
import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage;
@ -43,17 +50,14 @@ import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
class FluidItemTests {
public static void run() {
testFluidItemApi();
testWaterPotion();
testSimpleContentsQuery();
// Ensure this doesn't throw an error due to the empty stack.
assertEquals(null, ContainerItemContext.withConstant(ItemStack.EMPTY).find(FluidStorage.ITEM));
class FluidItemTests extends AbstractTransferApiTest {
@BeforeAll
static void beforeAll() {
bootstrap();
}
private static void testFluidItemApi() {
@Test
public void testFluidItemApi() {
FluidVariant water = FluidVariant.of(Fluids.WATER);
ItemVariant waterBucket = ItemVariant.of(Items.WATER_BUCKET);
Inventory testInventory = new FluidItemTestInventory(ItemStack.EMPTY, new ItemStack(Items.BUCKET), new ItemStack(Items.WATER_BUCKET));
@ -139,7 +143,8 @@ class FluidItemTests {
}
}
private static void testWaterPotion() {
@Test
public void testWaterPotion() {
FluidVariant water = FluidVariant.of(Fluids.WATER);
Inventory testInventory = new SimpleInventory(new ItemStack(Items.GLASS_BOTTLE));
@ -151,7 +156,7 @@ class FluidItemTests {
transaction.commit();
}
if (PotionUtil.getPotion(testInventory.getStack(0)) != Potions.WATER) throw new AssertionError("Expected water potion.");
if (getPotion(testInventory.getStack(0)) != Potions.WATER) throw new AssertionError("Expected water potion.");
// Try to empty from water potion
Storage<FluidVariant> waterBottleStorage = new InventoryContainerItem(testInventory, 0).find(FluidStorage.ITEM);
@ -162,7 +167,7 @@ class FluidItemTests {
}
// Make sure extraction nothing is returned for other potions
PotionUtil.setPotion(testInventory.getStack(0), Potions.LUCK);
setPotion(testInventory.getStack(0), Potions.LUCK);
Storage<FluidVariant> luckyStorage = new InventoryContainerItem(testInventory, 0).find(FluidStorage.ITEM);
if (StorageUtil.findStoredResource(luckyStorage) != null) {
@ -170,7 +175,8 @@ class FluidItemTests {
}
}
private static void testSimpleContentsQuery() {
@Test
public void testSimpleContentsQuery() {
assertEquals(
new ResourceAmount<>(FluidVariant.of(Fluids.WATER), BUCKET),
StorageUtil.findExtractableContent(
@ -183,9 +189,24 @@ class FluidItemTests {
null,
StorageUtil.findExtractableContent(
ContainerItemContext.withConstant(new ItemStack(Items.WATER_BUCKET)).find(FluidStorage.ITEM),
FluidVariant::hasNbt, // Only allow NBT -> won't match anything.
FluidVariant::hasComponents, // Only allow NBT -> won't match anything.
null
)
);
}
@Test
public void testDoesNotThrow() {
// Ensure this doesn't throw an error due to the empty stack.
assertEquals(null, ContainerItemContext.withConstant(ItemStack.EMPTY).find(FluidStorage.ITEM));
}
@Nullable
public static RegistryEntry<Potion> getPotion(ItemStack stack) {
return stack.getOrDefault(DataComponentTypes.POTION_CONTENTS, PotionContentsComponent.DEFAULT).potion().orElse(null);
}
public static void setPotion(ItemStack itemStack, RegistryEntry<Potion> potion) {
itemStack.set(DataComponentTypes.POTION_CONTENTS, new PotionContentsComponent(potion));
}
}

View file

@ -18,23 +18,33 @@ package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants.BUCKET;
import io.netty.buffer.Unpooled;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.component.ComponentChanges;
import net.minecraft.component.DataComponentType;
import net.minecraft.fluid.Fluids;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;
import net.minecraft.util.dynamic.Codecs;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
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.Transaction;
import net.fabricmc.fabric.test.transfer.ingame.TransferTestInitializer;
class FluidTests {
public static void run() {
testFluidStorage();
}
private static final FluidVariant TAGGED_WATER, TAGGED_WATER_2, WATER, LAVA;
class FluidTests extends AbstractTransferApiTest {
private static FluidVariant TAGGED_WATER, TAGGED_WATER_2, WATER, LAVA;
private static int finalCommitCount = 0;
public static DataComponentType<Integer> TEST;
private static SingleSlotStorage<FluidVariant> createWaterStorage() {
return new SingleVariantStorage<>() {
@Override
@ -59,16 +69,23 @@ class FluidTests {
};
}
static {
NbtCompound tag = new NbtCompound();
tag.putInt("test", 1);
TAGGED_WATER = FluidVariant.of(Fluids.WATER, tag);
TAGGED_WATER_2 = FluidVariant.of(Fluids.WATER, tag);
@BeforeAll
static void beforeAll() {
bootstrap();
ComponentChanges components = ComponentChanges.builder()
.add(TEST, 1)
.build();
TAGGED_WATER = FluidVariant.of(Fluids.WATER, components);
TAGGED_WATER_2 = FluidVariant.of(Fluids.WATER, components);
WATER = FluidVariant.of(Fluids.WATER);
LAVA = FluidVariant.of(Fluids.LAVA);
TEST = Registry.register(Registries.DATA_COMPONENT_TYPE, new Identifier(TransferTestInitializer.MOD_ID, "test"),
DataComponentType.<Integer>builder().codec(Codecs.NONNEGATIVE_INT).packetCodec(PacketCodecs.VAR_INT).build());
}
private static void testFluidStorage() {
@Test
public void testFluidStorage() {
SingleSlotStorage<FluidVariant> waterStorage = createWaterStorage();
// Test content
@ -136,6 +153,17 @@ class FluidTests {
if (finalCommitCount != 1) throw new AssertionError("onFinalCommit() should have been called exactly once.");
}
@Test
void testPacketCodec() {
FluidVariant variant = FluidVariant.of(Fluids.WATER, ComponentChanges.builder().add(TEST, 1).build());
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
RegistryByteBuf rbuf = new RegistryByteBuf(buf, staticDrm());
FluidVariant.PACKET_CODEC.encode(rbuf, variant);
FluidVariant decoded = FluidVariant.PACKET_CODEC.decode(rbuf);
Assertions.assertTrue(variant.equals(decoded));
}
private static void insertWaterWithNesting(SingleSlotStorage<FluidVariant> waterStorage, boolean doOuterCommit) {
try (Transaction tx = Transaction.openOuter()) {
if (waterStorage.getAmount() != 0) throw new AssertionError("Initial amount is wrong");

View file

@ -16,19 +16,24 @@
package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
class FluidVariantTests {
public static void run() {
testFlowing();
class FluidVariantTests extends AbstractTransferApiTest {
@BeforeAll
static void beforeAll() {
bootstrap();
}
private static void testFlowing() {
@Test
public void testFlowing() {
assertFluidEquals(Fluids.WATER, FluidVariant.of(Fluids.WATER), FluidVariant.of(Fluids.FLOWING_WATER));
assertFluidEquals(Fluids.LAVA, FluidVariant.of(Fluids.LAVA), FluidVariant.of(Fluids.FLOWING_LAVA));
assertEquals(FluidVariant.of(Fluids.WATER), FluidVariant.of(Fluids.FLOWING_WATER));

View file

@ -16,20 +16,34 @@
package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import java.util.stream.IntStream;
import io.netty.buffer.Unpooled;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.component.ComponentChanges;
import net.minecraft.component.DataComponentType;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.SidedInventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.dynamic.Codecs;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
@ -37,20 +51,23 @@ import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.test.transfer.ingame.TransferTestInitializer;
/**
* Tests for the item transfer APIs.
*/
class ItemTests {
public static void run() {
testStackReference();
testInventoryWrappers();
testLimitedStackCountInventory();
testLimitedStackCountItem();
testSimpleInventoryUpdates();
class ItemTests extends AbstractTransferApiTest {
public static DataComponentType<Integer> ENERGY;
@BeforeAll
static void beforeAll() {
bootstrap();
ENERGY = Registry.register(Registries.DATA_COMPONENT_TYPE, new Identifier(TransferTestInitializer.MOD_ID, "energy"),
DataComponentType.<Integer>builder().codec(Codecs.NONNEGATIVE_INT).packetCodec(PacketCodecs.VAR_INT).build());
}
private static void testStackReference() {
@Test
public void testStackReference() {
// Ensure that Inventory wrappers will try to mutate the backing stack as much as possible.
// In many cases, MC code captures a reference to the ItemStack so we want to edit that stack directly
// and not a copy whenever we can. Obviously this can't be perfect, but we try to cover as many cases as possible.
@ -75,9 +92,8 @@ class ItemTests {
// Also edit the stack when the item matches, even when the NBT and the count change.
ItemVariant oldVariant = ItemVariant.of(Items.DIAMOND);
NbtCompound testTag = new NbtCompound();
testTag.putInt("energy", 42);
ItemVariant newVariant = ItemVariant.of(Items.DIAMOND, testTag);
ComponentChanges components = ComponentChanges.builder().add(ENERGY, 42).build();
ItemVariant newVariant = ItemVariant.of(Items.DIAMOND, components);
try (Transaction tx = Transaction.openOuter()) {
invWrapper.extract(oldVariant, 2, tx);
@ -89,7 +105,8 @@ class ItemTests {
if (!stackEquals(stack, newVariant, 5)) throw new AssertionError("Failed to update stack NBT or count.");
}
private static void testInventoryWrappers() {
@Test
public void testInventoryWrappers() {
ItemVariant emptyBucket = ItemVariant.of(Items.BUCKET);
TestSidedInventory testInventory = new TestSidedInventory();
checkComparatorOutput(testInventory);
@ -141,6 +158,19 @@ class ItemTests {
}
}
@Test
void testPacketCodec() {
ItemStack stack = new ItemStack(Items.DIAMOND_PICKAXE);
stack.set(DataComponentTypes.CUSTOM_NAME, Text.literal("Custom name"));
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
RegistryByteBuf rbuf = new RegistryByteBuf(buf, staticDrm());
ItemVariant.PACKET_CODEC.encode(rbuf, ItemVariant.of(stack));
ItemVariant decoded = ItemVariant.PACKET_CODEC.decode(rbuf);
Assertions.assertTrue(ItemStack.areEqual(stack, decoded.toStack()));
}
private static boolean stackEquals(ItemStack stack, Item item, int count) {
return stackEquals(stack, ItemVariant.of(item), count);
}
@ -180,7 +210,8 @@ class ItemTests {
/**
* Test insertion when {@link Inventory#getMaxCountPerStack()} is the bottleneck.
*/
private static void testLimitedStackCountInventory() {
@Test
public void testLimitedStackCountInventory() {
ItemVariant diamond = ItemVariant.of(Items.DIAMOND);
LimitedStackCountInventory inventory = new LimitedStackCountInventory(diamond.toStack(), diamond.toStack(), diamond.toStack());
InventoryStorage wrapper = InventoryStorage.of(inventory, null);
@ -198,7 +229,8 @@ class ItemTests {
/**
* Test insertion when {@link Item#getMaxCount()} is the bottleneck.
*/
private static void testLimitedStackCountItem() {
@Test
public void testLimitedStackCountItem() {
ItemVariant diamondPickaxe = ItemVariant.of(Items.DIAMOND_PICKAXE);
LimitedStackCountInventory inventory = new LimitedStackCountInventory(5);
InventoryStorage wrapper = InventoryStorage.of(inventory, null);
@ -247,7 +279,8 @@ class ItemTests {
/**
* Ensure that SimpleInventory only calls markDirty at the end of a successful transaction.
*/
private static void testSimpleInventoryUpdates() {
@Test
public void testSimpleInventoryUpdates() {
var simpleInventory = new SimpleInventory(2) {
boolean throwOnMarkDirty = true;
boolean markDirtyCalled = false;

View file

@ -16,10 +16,13 @@
package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import java.util.function.Function;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
@ -29,15 +32,25 @@ import net.fabricmc.fabric.api.transfer.v1.item.PlayerInventoryStorage;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
public class PlayerInventoryStorageTests {
public static void run() {
public class PlayerInventoryStorageTests extends AbstractTransferApiTest {
@BeforeAll
static void beforeAll() {
bootstrap();
}
@Test
public void testStackingOffer() {
// Ensure that offer stacks as expected.
testStacking(playerInv -> playerInv::offer);
}
@Test
public void testStackingInser() {
// Also test that the behavior of insert matches that of offer.
testStacking(playerInv -> playerInv::insert);
}
private static void testStacking(Function<PlayerInventoryStorage, InsertionFunction> inserterBuilder) {
private void testStacking(Function<PlayerInventoryStorage, InsertionFunction> inserterBuilder) {
// A bit hacky... but nothing should try using the null player entity as long as we don't call drop.
PlayerInventory inv = new PlayerInventory(null);
InsertionFunction inserter = inserterBuilder.apply(PlayerInventoryStorage.of(inv));

View file

@ -17,36 +17,60 @@
package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants.BUCKET;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import java.util.List;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.component.ComponentChanges;
import net.minecraft.component.DataComponentType;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.fluid.Fluids;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.item.base.SingleItemStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantItemStorage;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
import net.fabricmc.fabric.test.transfer.ingame.TransferTestInitializer;
public class SingleVariantItemStorageTests {
private static final FluidVariant LAVA = FluidVariant.of(Fluids.LAVA);
public class SingleVariantItemStorageTests extends AbstractTransferApiTest {
private static FluidVariant LAVA;
public static DataComponentType<FluidData> FLUID;
public static void run() {
testWaterTank();
@BeforeAll
static void beforeAll() {
bootstrap();
LAVA = FluidVariant.of(Fluids.LAVA);
FLUID = Registry.register(
Registries.DATA_COMPONENT_TYPE, new Identifier(TransferTestInitializer.MOD_ID, "fluid"),
DataComponentType.<FluidData>builder().codec(FluidData.CODEC).packetCodec(FluidData.PACKET_CODEC).build());
}
private static void testWaterTank() {
@Test
public void testWaterTank() {
SimpleInventory inv = new SimpleInventory(new ItemStack(Items.DIAMOND, 2), ItemStack.EMPTY);
ContainerItemContext ctx = new InventoryContainerItemContext(inv);
@ -57,7 +81,7 @@ public class SingleVariantItemStorageTests {
assertEquals(BUCKET, storage.insert(LAVA, BUCKET, tx));
// Insertion should create a new stack.
assertEquals(1, inv.getStack(0).getCount());
assertEquals(null, inv.getStack(0).getNbt());
assertEquals(ComponentChanges.EMPTY, inv.getStack(0).getComponentChanges());
assertEquals(1, inv.getStack(1).getCount());
assertEquals(LAVA, getFluid(inv.getStack(1)));
assertEquals(BUCKET, getAmount(inv.getStack(1)));
@ -75,7 +99,7 @@ public class SingleVariantItemStorageTests {
// Make sure custom NBT is kept.
Text customName = Text.literal("Lava-containing diamond!");
inv.getStack(0).setCustomName(customName);
inv.getStack(0).set(DataComponentTypes.CUSTOM_NAME, customName);
try (Transaction tx = Transaction.openOuter()) {
// Test extract along the way.
@ -90,37 +114,105 @@ public class SingleVariantItemStorageTests {
assertEquals(0L, getAmount(inv.getStack(0)));
}
private static FluidVariant getFluid(ItemStack stack) {
NbtCompound nbt = stack.getNbt();
@Test
public void writeNbtTest() {
SingleItemStorage storage = new SingleItemStorage() {
@Override
protected long getCapacity(ItemVariant variant) {
return 10;
}
};
if (nbt != null && nbt.contains("fluid")) {
return FluidVariant.fromNbt(nbt.getCompound("fluid"));
} else {
return FluidVariant.blank();
try (Transaction tx = Transaction.openOuter()) {
storage.insert(ItemVariant.of(Items.DIAMOND), 1, tx);
tx.commit();
}
NbtCompound nbt = new NbtCompound();
storage.writeNbt(nbt, staticDrm());
assertEquals("{amount:1L,variant:{components:{},item:\"minecraft:diamond\"}}", nbt.toString());
}
@Test
public void readNbtTest() {
SingleItemStorage storage = new SingleItemStorage() {
@Override
protected long getCapacity(ItemVariant variant) {
return 10;
}
};
NbtCompound variantNbt = new NbtCompound();
variantNbt.putString("item", "minecraft:diamond");
variantNbt.put("components", new NbtCompound());
NbtCompound nbt = new NbtCompound();
nbt.putLong("amount", 1);
nbt.put("variant", variantNbt);
storage.readNbt(nbt, staticDrm());
try (Transaction tx = Transaction.openOuter()) {
assertEquals(1L, storage.extract(ItemVariant.of(Items.DIAMOND), 1, tx));
tx.commit();
}
}
private static long getAmount(ItemStack stack) {
NbtCompound nbt = stack.getNbt();
@Test
public void readInvalidNbtTest() {
SingleItemStorage storage = new SingleItemStorage() {
@Override
protected long getCapacity(ItemVariant variant) {
return 10;
}
};
if (nbt != null) {
return nbt.getLong("amount");
} else {
return 0;
// Test that invalid NBT defaults to empty.
NbtCompound variantNbt = new NbtCompound();
variantNbt.putString("id", "minecraft:diamond");
NbtCompound nbt = new NbtCompound();
nbt.putLong("amount", 1);
nbt.put("variant", variantNbt);
storage.readNbt(nbt, staticDrm());
try (Transaction tx = Transaction.openOuter()) {
assertEquals(0L, storage.extract(ItemVariant.of(Items.DIAMOND), 1, tx));
tx.commit();
}
}
private static FluidVariant getFluid(ItemStack stack) {
return stack.getOrDefault(FLUID, FluidData.DEFAULT).variant();
}
private static long getAmount(ItemStack stack) {
return stack.getOrDefault(FLUID, FluidData.DEFAULT).amount();
}
private static void setContents(ItemStack stack, FluidVariant newResource, long newAmount) {
if (newAmount > 0) {
stack.getOrCreateNbt().put("fluid", newResource.toNbt());
stack.getOrCreateNbt().putLong("amount", newAmount);
FluidData fluidData = new FluidData(newResource, newAmount);
stack.set(FLUID, fluidData);
} else {
// Make sure emptied tanks can stack with tanks without NBT.
stack.removeSubNbt("fluid");
stack.removeSubNbt("amount");
stack.remove(FLUID);
}
}
public record FluidData(FluidVariant variant, long amount) {
public static Codec<FluidData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
FluidVariant.CODEC.fieldOf("variant").forGetter(FluidData::variant),
Codec.LONG.fieldOf("amount").forGetter(FluidData::amount)
).apply(instance, FluidData::new));
public static PacketCodec<RegistryByteBuf, FluidData> PACKET_CODEC = PacketCodec.tuple(
FluidVariant.PACKET_CODEC, FluidData::variant,
PacketCodecs.VAR_LONG, FluidData::amount,
FluidData::new
);
public static FluidData DEFAULT = new FluidData(FluidVariant.blank(), 0);
}
private static Storage<FluidVariant> createTankStorage(ContainerItemContext ctx) {
return new SingleVariantItemStorage<>(ctx) {
@Override

View file

@ -16,20 +16,25 @@
package net.fabricmc.fabric.test.transfer.unittests;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.test.transfer.TestUtil;
class TransactionStateTests {
public static void run() {
testTransactionExceptions();
testTransactionLifecycle();
class TransactionStateTests extends AbstractTransferApiTest {
private int callbacksInvoked = 0;
@BeforeAll
static void beforeAll() {
bootstrap();
}
private static int callbacksInvoked = 0;
/**
* Make sure that transaction global state stays valid in case of exceptions.
*/
private static void testTransactionExceptions() {
@Test
public void testTransactionExceptions() {
// Test exception inside the try.
ensureException(() -> {
try (Transaction tx = Transaction.openOuter()) {
@ -96,7 +101,8 @@ class TransactionStateTests {
}
}
private static void testTransactionLifecycle() {
@Test
public void testTransactionLifecycle() {
TestUtil.assertEquals(Transaction.Lifecycle.NONE, Transaction.getLifecycle());
try (Transaction transaction = Transaction.openOuter()) {

View file

@ -21,6 +21,8 @@ import java.util.Set;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenCustomHashMap;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.FurnaceBlockEntity;
@ -30,16 +32,19 @@ import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
import net.fabricmc.fabric.test.transfer.TestUtil;
public class UnderlyingViewTests {
public static void run() {
testFurnaceSides();
public class UnderlyingViewTests extends AbstractTransferApiTest {
@BeforeAll
static void beforeAll() {
bootstrap();
}
/**
* Ensure that only 3 slots with different underlying view exist on all sides of a furnace combined.
*/
private static void testFurnaceSides() {
@Test
public void testFurnaceSides() {
FurnaceBlockEntity furnace = new FurnaceBlockEntity(BlockPos.ORIGIN, Blocks.FURNACE.getDefaultState());
Set<StorageView<ItemVariant>> viewSet = Collections.newSetFromMap(new Reference2ReferenceOpenCustomHashMap<>(new Hash.Strategy<>() {

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.unittests;
package net.fabricmc.fabric.test.transfer;
import java.util.Objects;

View file

@ -282,7 +282,7 @@ public class VanillaStorageTests {
/**
* Regression test for <a href="https://github.com/FabricMC/fabric/issues/2810">double chest wrapper only updating modified halves</a>.
*/
@GameTest(templateName = "fabric-transfer-api-v1-testmod:double_chest_comparators", method_57098 = true)
@GameTest(templateName = "fabric-transfer-api-v1-testmod:double_chest_comparators", skyAccess = true)
public void testDoubleChestComparator(TestContext context) {
BlockPos chestPos = new BlockPos(2, 2, 2);
Storage<ItemVariant> storage = ItemStorage.SIDED.find(context.getWorld(), context.getAbsolutePos(chestPos), Direction.UP);

View file

@ -16,7 +16,7 @@
package net.fabricmc.fabric.test.transfer.gametests;
import static net.fabricmc.fabric.test.transfer.unittests.TestUtil.assertEquals;
import static net.fabricmc.fabric.test.transfer.TestUtil.assertEquals;
import net.minecraft.fluid.Fluids;
import net.minecraft.server.world.ServerWorld;

View file

@ -60,12 +60,12 @@ public class FluidChuteBlockEntity extends BlockEntity {
@Override
protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
super.writeNbt(nbt, wrapperLookup);
storage.writeNbt(nbt);
storage.writeNbt(nbt, wrapperLookup);
}
@Override
public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
super.readNbt(nbt, wrapperLookup);
storage.readNbt(nbt);
storage.readNbt(nbt, wrapperLookup);
}
}

View file

@ -10,8 +10,7 @@
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.transfer.ingame.TransferTestInitializer",
"net.fabricmc.fabric.test.transfer.unittests.UnitTestsInitializer"
"net.fabricmc.fabric.test.transfer.ingame.TransferTestInitializer"
],
"client": [
"net.fabricmc.fabric.test.transfer.ingame.client.FluidVariantRenderTest"

View file

@ -55,7 +55,7 @@ include 'fabric-resource-loader-v0'
include 'fabric-screen-api-v1'
include 'fabric-screen-handler-api-v1'
include 'fabric-sound-api-v1'
//include 'fabric-transfer-api-v1'
include 'fabric-transfer-api-v1'
include 'fabric-transitive-access-wideners-v1'
include 'deprecated'