Reorganize transfer API testmod and rework item api injections (#1652)

* Reorganize the transer API testmod

* Rework mixins. Closes #1649.
This commit is contained in:
Technici4n 2021-08-21 19:33:23 +02:00 committed by GitHub
parent d19fec74a7
commit a9bcdbef6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 257 additions and 195 deletions

View file

@ -20,11 +20,15 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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;
public class TransferApiImpl {
public static final Logger LOGGER = LogManager.getLogger("fabric-transfer-api-v1");
public static final AtomicLong version = new AtomicLong();
@SuppressWarnings("rawtypes")
public static final Storage EMPTY_STORAGE = new Storage() {

View file

@ -20,13 +20,11 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.DispenserBlock;
import net.minecraft.block.DropperBlock;
import net.minecraft.block.entity.DispenserBlockEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPointerImpl;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
@ -35,37 +33,47 @@ 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;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
import net.fabricmc.fabric.impl.transfer.TransferApiImpl;
/**
* Allows droppers to insert into ItemVariant storages.
*
* <p>Maintainer note: it's important that we inject BEFORE the getStack() call,
* as the returned stack can be mutated by the StorageUtil.move() call in the injected callback.
*/
@Mixin(DropperBlock.class)
public class DropperBlockMixin {
@Inject(
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/block/entity/DispenserBlockEntity;getStack(I)Lnet/minecraft/item/ItemStack;"
target = "Lnet/minecraft/block/dispenser/DispenserBehavior;dispense(Lnet/minecraft/util/math/BlockPointer;Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;"
),
method = "dispense",
locals = LocalCapture.CAPTURE_FAILHARD,
cancellable = true,
allow = 1
)
public void hookDispense(ServerWorld world, BlockPos pos, CallbackInfo ci, BlockPointerImpl blockPointerImpl, DispenserBlockEntity dispenser, int slot) {
if (dispenser.getStack(slot).isEmpty()) return;
public void hookDispense(ServerWorld world, BlockPos pos, CallbackInfo ci) {
DispenserBlockEntity dispenser = (DispenserBlockEntity) world.getBlockEntity(pos);
Direction direction = dispenser.getCachedState().get(DispenserBlock.FACING);
Direction direction = world.getBlockState(pos).get(DispenserBlock.FACING);
Storage<ItemVariant> target = ItemStorage.SIDED.find(world, pos.offset(direction), direction.getOpposite());
if (target != null) {
Storage<ItemVariant> source = InventoryStorage.of(dispenser, null).getSlots().get(slot);
// Always cancel if a storage is available.
ci.cancel();
if (StorageUtil.move(source, target, k -> true, 1, null) == 1) {
ci.cancel();
// We pick a non empty slot. It's not necessarily the same as the one vanilla picked, but that doesn't matter.
int slot = dispenser.chooseNonEmptySlot();
if (slot == -1) {
TransferApiImpl.LOGGER.warn("Skipping dropper transfer because the empty slot is unexpectedly -1.");
return;
}
StorageUtil.move(
InventoryStorage.of(dispenser, null).getSlots().get(slot),
target,
k -> true,
1,
null
);
}
}
}

View file

@ -16,15 +16,14 @@
package net.fabricmc.fabric.mixin.transfer;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.BlockState;
import net.minecraft.block.HopperBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.Hopper;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.inventory.Inventory;
@ -44,49 +43,61 @@ import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
@Mixin(HopperBlockEntity.class)
public class HopperBlockEntityMixin {
@Inject(
at = @At("HEAD"),
at = @At(
value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/block/entity/HopperBlockEntity;getOutputInventory(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)Lnet/minecraft/inventory/Inventory;"
),
method = "insert(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/inventory/Inventory;)Z",
locals = LocalCapture.CAPTURE_FAILHARD,
cancellable = true
)
private static void hookInsert(World world, BlockPos pos, BlockState state, Inventory inventory, CallbackInfoReturnable<Boolean> cir) {
private static void hookInsert(World world, BlockPos pos, BlockState state, Inventory inventory, CallbackInfoReturnable<Boolean> cir, Inventory targetInventory) {
// Let vanilla handle the transfer if it found an inventory.
if (targetInventory != null) return;
// Otherwise inject our transfer logic.
Direction direction = state.get(HopperBlock.FACING);
BlockPos targetPos = pos.offset(direction);
BlockEntity targetBe = world.getBlockEntity(targetPos);
Storage<ItemVariant> target = ItemStorage.SIDED.find(world, targetPos, null, targetBe, direction.getOpposite());
Storage<ItemVariant> target = ItemStorage.SIDED.find(world, targetPos, direction.getOpposite());
if (target != null) {
cir.setReturnValue(doTransfer(InventoryStorage.of(inventory, direction), target, inventory, targetBe));
long moved = StorageUtil.move(
InventoryStorage.of(inventory, direction),
target,
iv -> true,
1,
null
);
cir.setReturnValue(moved == 1);
}
}
@Inject(
at = @At("HEAD"),
at = @At(
value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/block/entity/HopperBlockEntity;getInputInventory(Lnet/minecraft/world/World;Lnet/minecraft/block/entity/Hopper;)Lnet/minecraft/inventory/Inventory;"
),
method = "extract(Lnet/minecraft/world/World;Lnet/minecraft/block/entity/Hopper;)Z",
locals = LocalCapture.CAPTURE_FAILHARD,
cancellable = true
)
private static void hookExtract(World world, Hopper hopper, CallbackInfoReturnable<Boolean> cir) {
private static void hookExtract(World world, Hopper hopper, CallbackInfoReturnable<Boolean> cir, Inventory inputInventory) {
// Let vanilla handle the transfer if it found an inventory.
if (inputInventory != null) return;
// Otherwise inject our transfer logic.
BlockPos sourcePos = new BlockPos(hopper.getHopperX(), hopper.getHopperY() + 1.0D, hopper.getHopperZ());
BlockEntity sourceBe = world.getBlockEntity(sourcePos);
Storage<ItemVariant> source = ItemStorage.SIDED.find(world, sourcePos, null, sourceBe, Direction.DOWN);
Storage<ItemVariant> source = ItemStorage.SIDED.find(world, sourcePos, Direction.DOWN);
if (source != null) {
cir.setReturnValue(doTransfer(source, InventoryStorage.of(hopper, Direction.UP), sourceBe, hopper));
}
}
private static boolean doTransfer(Storage<ItemVariant> from, Storage<ItemVariant> to, @Nullable Object invFrom, @Nullable Object invTo) {
if (invFrom instanceof HopperBlockEntityAccessor hopperFrom && invTo instanceof HopperBlockEntityAccessor hopperTo) {
// Hoppers have some special interactions (see HopperBlockEntity#transfer)
boolean wasEmpty = hopperTo.isEmpty();
boolean moved = StorageUtil.move(from, to, k -> true, 1, null) == 1;
if (moved && wasEmpty && hopperTo.fabric_getLastTickTime() >= hopperFrom.fabric_getLastTickTime()) {
hopperTo.fabric_callSetCooldown(7);
}
return moved;
} else {
return StorageUtil.move(from, to, k -> true, 1, null) == 1;
long moved = StorageUtil.move(
source,
InventoryStorage.of(hopper, Direction.UP),
iv -> true,
1,
null
);
cir.setReturnValue(moved == 1);
}
}
}

View file

@ -7,7 +7,6 @@
"DoubleInventoryAccessor",
"DropperBlockMixin",
"FluidMixin",
"HopperBlockEntityAccessor",
"HopperBlockEntityMixin",
"ItemMixin"
]

View file

@ -14,37 +14,38 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
import java.util.Iterator;
package net.fabricmc.fabric.test.transfer.ingame;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.Items;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
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.ExtractionOnlyStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleViewIterator;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
public class CreativeFluidStorage implements ExtractionOnlyStorage<FluidVariant>, StorageView<FluidVariant> {
public static final CreativeFluidStorage WATER = new CreativeFluidStorage(FluidVariant.of(Fluids.WATER));
public static final CreativeFluidStorage LAVA = new CreativeFluidStorage(FluidVariant.of(Fluids.LAVA));
public class CreativeStorage<T extends TransferVariant<?>> implements ExtractionOnlyStorage<T>, SingleSlotStorage<T> {
public static final CreativeStorage<FluidVariant> WATER = new CreativeStorage<>(FluidVariant.of(Fluids.WATER));
public static final CreativeStorage<FluidVariant> LAVA = new CreativeStorage<>(FluidVariant.of(Fluids.LAVA));
public static final CreativeStorage<ItemVariant> DIAMONDS = new CreativeStorage<>(ItemVariant.of(Items.DIAMOND));
private final FluidVariant infiniteFluid;
private final T infiniteResource;
private CreativeFluidStorage(FluidVariant infiniteFluid) {
this.infiniteFluid = infiniteFluid;
private CreativeStorage(T infiniteResource) {
this.infiniteResource = infiniteResource;
}
@Override
public boolean isResourceBlank() {
return infiniteFluid.isBlank();
return infiniteResource.isBlank();
}
@Override
public FluidVariant getResource() {
return infiniteFluid;
public T getResource() {
return infiniteResource;
}
@Override
@ -58,21 +59,16 @@ public class CreativeFluidStorage implements ExtractionOnlyStorage<FluidVariant>
}
@Override
public long extract(FluidVariant resource, long maxAmount, TransactionContext transaction) {
public long extract(T resource, long maxAmount, TransactionContext transaction) {
StoragePreconditions.notBlankNotNegative(resource, maxAmount);
if (resource.equals(infiniteFluid)) {
if (resource.equals(infiniteResource)) {
return maxAmount;
} else {
return 0;
}
}
@Override
public Iterator<StorageView<FluidVariant>> iterator(TransactionContext transaction) {
return SingleViewIterator.create(this, transaction);
}
@Override
public long getVersion() {
return 0;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
package net.fabricmc.fabric.test.transfer.ingame;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
package net.fabricmc.fabric.test.transfer.ingame;
import org.jetbrains.annotations.Nullable;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
package net.fabricmc.fabric.test.transfer.ingame;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
@ -31,7 +31,7 @@ public class FluidChuteBlockEntity extends BlockEntity {
private int tickCounter = 0;
public FluidChuteBlockEntity(BlockPos pos, BlockState state) {
super(FluidTransferTest.FLUID_CHUTE_TYPE, pos, state);
super(TransferTestInitializer.FLUID_CHUTE_TYPE, pos, state);
}
@SuppressWarnings("ConstantConditions")

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.ingame;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.Material;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
public class TransferTestInitializer implements ModInitializer {
public static final String MOD_ID = "fabric-transfer-api-v1-testmod";
private static final Block INFINITE_WATER_SOURCE = new Block(AbstractBlock.Settings.of(Material.METAL));
private static final Block INFINITE_LAVA_SOURCE = new Block(AbstractBlock.Settings.of(Material.METAL));
private static final Block FLUID_CHUTE = new FluidChuteBlock();
private static final Item EXTRACT_STICK = new ExtractStickItem();
public static BlockEntityType<FluidChuteBlockEntity> FLUID_CHUTE_TYPE;
@Override
public void onInitialize() {
registerBlock(INFINITE_WATER_SOURCE, "infinite_water_source");
registerBlock(INFINITE_LAVA_SOURCE, "infinite_lava_source");
registerBlock(FLUID_CHUTE, "fluid_chute");
Registry.register(Registry.ITEM, new Identifier(MOD_ID, "extract_stick"), EXTRACT_STICK);
FLUID_CHUTE_TYPE = FabricBlockEntityTypeBuilder.create(FluidChuteBlockEntity::new, FLUID_CHUTE).build();
Registry.register(Registry.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "fluid_chute"), FLUID_CHUTE_TYPE);
FluidStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> CreativeStorage.WATER, INFINITE_WATER_SOURCE);
FluidStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> CreativeStorage.LAVA, INFINITE_LAVA_SOURCE);
// Obsidian is now a trash can :-P
ItemStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> TrashingStorage.ITEM, Blocks.OBSIDIAN);
// And diamond ore blocks are an infinite source of diamonds! Yay!
ItemStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> CreativeStorage.DIAMONDS, Blocks.DIAMOND_ORE);
}
private static void registerBlock(Block block, String name) {
Identifier id = new Identifier(MOD_ID, name);
Registry.register(Registry.BLOCK, id, block);
Registry.register(Registry.ITEM, id, new BlockItem(block, new Item.Settings().group(ItemGroup.MISC)));
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
package net.fabricmc.fabric.test.transfer.ingame;
import java.util.Collections;
import java.util.Iterator;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
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;
@ -43,7 +43,7 @@ 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;
public class FluidItemTests {
class FluidItemTests {
public static void run() {
testFluidItemApi();
testWaterPotion();

View file

@ -14,67 +14,21 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
package net.fabricmc.fabric.test.transfer.unittests;
import static net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants.BUCKET;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.Material;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
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;
public class FluidTransferTest implements ModInitializer {
public static final String MOD_ID = "fabric-transfer-api-v1-testmod";
private static final Block INFINITE_WATER_SOURCE = new Block(AbstractBlock.Settings.of(Material.METAL));
private static final Block INFINITE_LAVA_SOURCE = new Block(AbstractBlock.Settings.of(Material.METAL));
private static final Block FLUID_CHUTE = new FluidChuteBlock();
private static final Item EXTRACT_STICK = new ExtractStickItem();
public static BlockEntityType<FluidChuteBlockEntity> FLUID_CHUTE_TYPE;
@Override
public void onInitialize() {
registerBlock(INFINITE_WATER_SOURCE, "infinite_water_source");
registerBlock(INFINITE_LAVA_SOURCE, "infinite_lava_source");
registerBlock(FLUID_CHUTE, "fluid_chute");
Registry.register(Registry.ITEM, new Identifier(MOD_ID, "extract_stick"), EXTRACT_STICK);
FLUID_CHUTE_TYPE = FabricBlockEntityTypeBuilder.create(FluidChuteBlockEntity::new, FLUID_CHUTE).build();
Registry.register(Registry.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "fluid_chute"), FLUID_CHUTE_TYPE);
FluidStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> CreativeFluidStorage.WATER, INFINITE_WATER_SOURCE);
FluidStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> CreativeFluidStorage.LAVA, INFINITE_LAVA_SOURCE);
// Obsidian is now a trash can :-P
ItemStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> TrashingStorage.ITEM, Blocks.OBSIDIAN);
class FluidTests {
public static void run() {
testFluidStorage();
testTransactionExceptions();
ItemTests.run();
FluidItemTests.run();
}
private static void registerBlock(Block block, String name) {
Identifier id = new Identifier(MOD_ID, name);
Registry.register(Registry.BLOCK, id, block);
Registry.register(Registry.ITEM, id, new BlockItem(block, new Item.Settings().group(ItemGroup.MISC)));
}
private static final FluidVariant TAGGED_WATER, TAGGED_WATER_2, WATER, LAVA;
@ -195,61 +149,4 @@ public class FluidTransferTest implements ModInitializer {
}
}
}
private static int callbacksInvoked = 0;
/**
* Make sure that transaction global state stays valid in case of exceptions.
*/
private static void testTransactionExceptions() {
// Test exception inside the try.
ensureException(() -> {
try (Transaction tx = Transaction.openOuter()) {
tx.addCloseCallback((t, result) -> {
callbacksInvoked++; throw new RuntimeException("Close.");
});
throw new RuntimeException("Inside try.");
}
}, "Exception should have propagated through the transaction.");
if (callbacksInvoked != 1) throw new AssertionError("Callback should have been invoked.");
// Test exception inside the close.
callbacksInvoked = 0;
ensureException(() -> {
try (Transaction tx = Transaction.openOuter()) {
tx.addCloseCallback((t, result) -> {
callbacksInvoked++; throw new RuntimeException("Close 1.");
});
tx.addCloseCallback((t, result) -> {
callbacksInvoked++; throw new RuntimeException("Close 2.");
});
tx.addOuterCloseCallback(result -> {
callbacksInvoked++; throw new RuntimeException("Outer close 1.");
});
tx.addOuterCloseCallback(result -> {
callbacksInvoked++; throw new RuntimeException("Outer close 2.");
});
}
}, "Exceptions in close callbacks should be propagated through the transaction.");
if (callbacksInvoked != 4) throw new AssertionError("All 4 callbacks should have been invoked, only so many were: " + callbacksInvoked);
// Test that transaction state is still OK after these exceptions.
try (Transaction tx = Transaction.openOuter()) {
tx.commit();
}
}
private static void ensureException(Runnable runnable, String message) {
boolean failed = false;
try {
runnable.run();
} catch (Throwable t) {
failed = true;
}
if (!failed) {
throw new AssertionError(message);
}
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.fluid;
package net.fabricmc.fabric.test.transfer.unittests;
import java.util.stream.IntStream;
@ -38,7 +38,7 @@ import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
/**
* Tests for the item transfer APIs.
*/
public class ItemTests {
class ItemTests {
public static void run() {
testInventoryWrappers();
testLimitedStackCountInventory();

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.transfer.unittests;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
class TransactionExceptionsTests {
public static void run() {
testTransactionExceptions();
}
private static int callbacksInvoked = 0;
/**
* Make sure that transaction global state stays valid in case of exceptions.
*/
private static void testTransactionExceptions() {
// Test exception inside the try.
ensureException(() -> {
try (Transaction tx = Transaction.openOuter()) {
tx.addCloseCallback((t, result) -> {
callbacksInvoked++; throw new RuntimeException("Close.");
});
throw new RuntimeException("Inside try.");
}
}, "Exception should have propagated through the transaction.");
if (callbacksInvoked != 1) throw new AssertionError("Callback should have been invoked.");
// Test exception inside the close.
callbacksInvoked = 0;
ensureException(() -> {
try (Transaction tx = Transaction.openOuter()) {
tx.addCloseCallback((t, result) -> {
callbacksInvoked++; throw new RuntimeException("Close 1.");
});
tx.addCloseCallback((t, result) -> {
callbacksInvoked++; throw new RuntimeException("Close 2.");
});
tx.addOuterCloseCallback(result -> {
callbacksInvoked++; throw new RuntimeException("Outer close 1.");
});
tx.addOuterCloseCallback(result -> {
callbacksInvoked++; throw new RuntimeException("Outer close 2.");
});
}
}, "Exceptions in close callbacks should be propagated through the transaction.");
if (callbacksInvoked != 4) throw new AssertionError("All 4 callbacks should have been invoked, only so many were: " + callbacksInvoked);
// Test that transaction state is still OK after these exceptions.
try (Transaction tx = Transaction.openOuter()) {
tx.commit();
}
}
private static void ensureException(Runnable runnable, String message) {
boolean failed = false;
try {
runnable.run();
} catch (Throwable t) {
failed = true;
}
if (!failed) {
throw new AssertionError(message);
}
}
}

View file

@ -14,23 +14,19 @@
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.transfer;
package net.fabricmc.fabric.test.transfer.unittests;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.apache.logging.log4j.LogManager;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.inventory.Inventory;
import net.fabricmc.api.ModInitializer;
/**
* Hopper accessors, for use in {@link HopperBlockEntityMixin}.
*/
@Mixin(HopperBlockEntity.class)
public interface HopperBlockEntityAccessor extends Inventory {
@Invoker("setCooldown")
void fabric_callSetCooldown(int cooldown);
@Accessor("lastTickTime")
long fabric_getLastTickTime();
public class UnitTestsInitializer implements ModInitializer {
@Override
public void onInitialize() {
TransactionExceptionsTests.run();
FluidTests.run();
ItemTests.run();
FluidItemTests.run();
LogManager.getLogger("fabric-transfer-api-v1 testmod").info("Transfer API unit tests successful.");
}
}

View file

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