===== Fabric Transfer API: Understanding Storage ===== //This article is part of a series on the Fabric Transfer API. [[tutorial:transfer-api|Link to the home page of the series]].// **''Storage'' is a thing that contains fluids, it is what tanks, buckets, etc... implement and it is what pipe mods and others use to move fluids between containers.** ==== You already used Storage ==== [[tutorial:transfer-api/simpletank|Now that you implemented a simple tank]], let's have a look at what exactly this ''SingleVariantStorage'' is. public abstract class SingleVariantStorage> extends ... implements SingleSlotStorage { ... } public interface SingleSlotStorage extends Storage, ... { ... } The takeaway here is that ''SingleVariantStorage'' is a ''Storage'', which is what we registered to ''FluidStorage.SIDED''! ==== Retrieving a Storage from the world ==== Let's see how we can retrieve one from the world: World world = ...; // Important: must be a server world!!! You must never query this on the client. BlockPos pos = ...; // The postion in the world Direction direction = ...; // The side for which we want a storage. @Nullable Storage storage = FluidStorage.SIDED.find(world, pos, direction); if (storage != null) { // Use the storage } ==== A look at Storage ==== Let's have a look at ''Storage.java'': public interface Storage { // Try to insert a resource in the storage, return how much was inserted. long insert(T resource, long maxAmount, TransactionContext transaction); // Try to extract a resource from the storage, return how much was extracted. long extract(T resource, long maxAmount, TransactionContext transaction); // Iterate over the contents of this storage. default Iterable> iterable(TransactionContext transaction) { ... } ... } This interface allows us to insert into a storage, extract from it, and read its contents. ==== First example: how to insert exactly one bucket of water into a storage ==== Storage storage = ...; FluidVariant water = FluidVariant.of(Fluids.WATER); // Open a transaction: this allows cancelling the operation if it doesn't go as expected. try (Transaction transaction = Transaction.openOuter()) { // Try to insert, will return how much was actually inserted. long amountInserted = storage.insert(water, FluidConstants.BUCKET, transaction); if (amountInserted == FluidConstants.BUCKET) { // "Commit" the transaction: this validates all the operations that were part of this transaction. // You should call this if you are satisfied with the result of the operation, and want to keep it. transaction.commit(); } else { // Doing nothing "aborts" the transaction: this cancels the insertion. // You should call this if you are not satisfied with the result of the operation, and want to abort it. } } ==== Second example: move exactly one bucket of lava from a storage to another storage ==== import static net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants.BUCKET; // a bit shorter to type :) Storage source, destination; FluidVariant lava = FluidVariant.of(Fluids.LAVA); try (Transaction transaction = Transaction.openOuter()) { if (source.extract(lava, BUCKET, transaction) == BUCKET && destination.insert(lava, BUCKET, transaction) == BUCKET) { transaction.commit(); } } Hopefully you understand why this works, and why this will never duplicate or void fluid. ==== A more complicated example: Extracting the contents of a storage ==== In the previous example, we knew that we wanted to move water or lava. But what if we don't know what to extract? The answer is: iterate over the contents! This example also introduces the concept of nested transactions. Storage storage; Predicate filter = fv -> fv.isOf(Fluids.WATER); // What we want to extract, in this example let's say we want to extract any water, regardless of its NBT tag. long totalExtracted = 0; // Open a transaction, as before. try (Transaction transaction = Transaction.openOuter()) { // Loop over the contents! Each StorageView can contain at most one resource. You can think of a StorageView as an inventory slot. for (StorageView view : storage.iterable(transaction)) { if (view.isResourceBlank()) continue; // This means that the view contains no resource, represented by FluidVariant.blank(). FluidVariant storedResource = view.getResource(); // Current resource if (!filter.test(storedResource)) continue; // The filter rejected this resource, skip it. // If you want to extract any amount <= view.getAmount(), do this. totalExtracted += view.extract(storedResource, view.getAmount(), transaction); // If you want to extract either the exact amount or nothing, you can use a nested transaction! try (Transaction nestedTransaction = transaction.openNested()) { long amount = view.getAmount(); // The amount will change after the call to extract, so make sure to save it first. long extracted = view.extract(storedResource, amount, nestedTransaction); if (extracted == amount) { totalExtracted += amount; nestedTransaction.commit(); // Validate the nestedTransaction: the outer one will have to committed as well to validate this change. } else { // If we do nothing, the extraction is cancelled immediately when nestedTransaction is closed at the end of the try { ... } block. } } } transaction.commit(); // Don't forget to commit, or all of the extraction will be cancelled! } ==== Conclusion ==== You should now be able to use ''Storage''s and ''StorageView''s. They can be a bit challenging in the beginning, but once you're used to them they are very easy to work with. You should also have a look at ''StorageUtil'': it already contains functions that perform common transfer operations. In particular, [[https://maven.fabricmc.net/docs/fabric-api-0.41.0+1.17/net/fabricmc/fabric/api/transfer/v1/storage/StorageUtil.html#move(net.fabricmc.fabric.api.transfer.v1.storage.Storage,net.fabricmc.fabric.api.transfer.v1.storage.Storage,java.util.function.Predicate,long,net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext)|move]] is very useful if you just want to move resources between two storages.