mirror of
https://github.com/FabricMC/fabric.git
synced 2024-11-14 19:25:23 -05:00
Fabric Transfer API: "fluid only" edition (#1356)
* Original fluid API design * Rework the transaction system * First javadoc pass * Add a testmod, a base implementation for fluid storages and fix some little bugs * Fix checkstyle * Make Movement#move extract from the view and not the whole Storage * Document and update FluidPreconditions * Use for-each in CombinedStorage and document a little * Remove useless overrides in Insertion/ExtractionOnlyStorage * Move SnapshotParticipant#snapshots to the top of the class, and make updateSnapshots public * Fix garbage collection of unused CauldronWrappers * Use ArrayList directly * Remove locking, reorganize transaction implementation, and add outer close callback * Add more javadoc * Rework Storage#forEach into Storage#iterator * Add a few missing `transaction.addCloseCallback(iterator)` * Add anyView(), exactView(), capacity() and isEmpty() * Add Storage#iterable to make iteration friendlier to for loops * Storages may now have multiple open iterators Co-authored-by: Devan-Kerman <dev.sel20@gmail.com> * Make CombinedStorage#supportsInsertion/Extraction iterate through the parts * Block updates should be used when the supportsInsertion/Extraction status changes * Fluid -> FluidKey * Remove all references to ItemKey inside FluidKey, and other minor tweaks * Cache FluidKeys with a null tag inside Fluid directly * Fluid unit convention * Add FluidKeyRendering and RenderHandler * Bump version for more testing (also published to my maven) * Add SingleViewIterator, massively reduce code duplication! * Make API experimental, and add README * Bump version * Apparently Fluids.EMPTY is flowing * Add package info * Minor adjustements * 1.17 port, cauldron support, add ResourceKey * Checkstyle, gas rendering, use record for ResourceAmount * Add a few helpers, rename some stuff * Remove anyView, allow nullable in StorageUtil#find*, fix missing try block * Slight findStoredResource cleanup * Slightly improve implementation * Bump version * Fix wrong transaction * I wrote in a comment that this could happen... * Fix SingleFluidStorage bugs, add tests in the testmod, add testmod assets * Add extract stick * Rename a few things * `ResourceKey<T>` -> `TransferKey<O>` * `ResourceKey#getResource()` -> `TransferKey#getObject()` as resource is already widely used through the API for the keys themselves. * `tag` -> `nbt` * Add `get` prefixes to `StorageView` functions * Bump version * FluidKey -> FluidVariant * Bump version * Expand getVersion() documentation, make it thread-safe and use long. Co-authored-by: Player <player@player.to> * empty resource -> blank resource, and update SingleFluidStorage Co-authored-by: Player <player@player.to> * Make CauldronFluidContent a final class instead of a record. Co-authored-by: Player <player@player.to> * Get rid of CauldronFluidContent#minLevel (was always 1) * Fix nested commits. (Thanks @warjort!) * Separate Transaction and TransactionContext Co-authored-by: Devan-Kerman <dev.sel20@gmail.com> Co-authored-by: Player <player@player.to> * Change WorldLocation into a private record * Bump version * Guard against exceptions thrown in close callbacks * Make sure blank fluid variants don't have a tag * Add documentation, make CauldronStorage clearer Co-authored-by: frqnny <45723631+frqnny@users.noreply.github.com> * Allow null storages in StorageUtil#move, and clarify sidedness of FluidStorage * Add explicit hashCode and equals for transfer variants * Remove ugly equals and hashCode overrides, and add constant time hashcode spec Co-authored-by: Devan-Kerman <dev.sel20@gmail.com> Co-authored-by: liach <liach@users.noreply.github.com> Co-authored-by: Player <player@player.to> Co-authored-by: frqnny <45723631+frqnny@users.noreply.github.com>
This commit is contained in:
parent
5f02c96920
commit
c09be4c48a
55 changed files with 3602 additions and 1 deletions
|
@ -175,7 +175,7 @@ allprojects {
|
|||
|
||||
checkstyle {
|
||||
configFile = rootProject.file("checkstyle.xml")
|
||||
toolVersion = "8.31"
|
||||
toolVersion = "8.43"
|
||||
}
|
||||
|
||||
tasks.withType(AbstractArchiveTask) {
|
||||
|
|
36
fabric-transfer-api-v1/README.md
Normal file
36
fabric-transfer-api-v1/README.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Fabric Transfer API (v1)
|
||||
This module provides common facilities for the transfer of fluids and other game resources.
|
||||
|
||||
## Transactions
|
||||
The [`Transaction`](src/main/java/net/fabricmc/fabric/api/transfer/v1/transaction/Transaction.java) system provides a
|
||||
scope that can be used to simulate any number of transfer operations, and then cancel or validate all of them at once.
|
||||
One can think of transactions as video game checkpoints. A more detailed explanation can be found in the class javadoc of `Transaction`.
|
||||
Every transfer operation requires a `Transaction` parameter.
|
||||
[`SnapshotParticipant`](src/main/java/net/fabricmc/fabric/api/transfer/v1/transaction/base/SnapshotParticipant.java)
|
||||
is the reference implementation of a "participant", that is an object participating in a transaction.
|
||||
|
||||
## Storages
|
||||
A [`Storage<T>`](src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/Storage.java) is any object that can store resources of type `T`.
|
||||
Its contents can be read, and resources can be inserted into it or extracted from it.
|
||||
[`StorageUtil`](src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageUtil.java) provides a few helpful functions to work with `Storage`s,
|
||||
for example to move resources between two `Storage`s.
|
||||
The [`storage/base`](src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/base) package provides a few helpers to accelerate
|
||||
implementation of `Storage<T>`.
|
||||
|
||||
## Fluid transfer
|
||||
A `Storage<FluidVariant>` is any object that can store fluids. It is just a `Storage<T>`, where `T` is
|
||||
[`FluidVariant`](src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/FluidVariant.java), the immutable combination of a `Fluid` and additional NBT data.
|
||||
Instances can be accessed through the API lookups defined in [`FluidStorage`](src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/FluidStorage.java).
|
||||
|
||||
Implementors of fluid inventories with a fixed number of "slots" or "tanks" can use
|
||||
[`SingleFluidStorage`](src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/base/SingleFluidStorage.java),
|
||||
and combine them with `CombinedStorage`.
|
||||
|
||||
The unit for fluid transfer is 1/81000ths of a bucket, also known as _droplets_.
|
||||
[`FluidConstants`](src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/FluidConstants.java) contains a few helpful constants
|
||||
to work with droplets.
|
||||
|
||||
Client-side [Fluid variant rendering](src/main/java/net/fabricmc/fabric/api/transfer/v1/client/fluid/FluidVariantRendering.java) will use regular fluid rendering by default,
|
||||
ignoring the additional NBT data.
|
||||
`Fluid`s that wish to render differently depending on the stored NBT data can register a
|
||||
[`FluidVariantRenderHandler`](src/main/java/net/fabricmc/fabric/api/transfer/v1/client/fluid/FluidVariantRenderHandler.java).
|
17
fabric-transfer-api-v1/build.gradle
Normal file
17
fabric-transfer-api-v1/build.gradle
Normal file
|
@ -0,0 +1,17 @@
|
|||
archivesBaseName = "fabric-transfer-api-v1"
|
||||
version = getSubprojectVersion(project, "0.3.4")
|
||||
|
||||
moduleDependencies(project, [
|
||||
'fabric-api-base',
|
||||
'fabric-api-lookup-api-v1',
|
||||
'fabric-lifecycle-events-v1', // transitive dependency of API Lookup
|
||||
'fabric-rendering-fluids-v1',
|
||||
'fabric-textures-v0' // transitive dependency of Rendering Fluids
|
||||
])
|
||||
|
||||
dependencies {
|
||||
testmodImplementation project(path: ':fabric-object-builder-api-v1', configuration: 'dev')
|
||||
testmodImplementation project(path: ':fabric-resource-loader-v0', configuration: 'dev')
|
||||
testmodImplementation project(path: ':fabric-tag-extensions-v0', configuration: 'dev')
|
||||
testmodImplementation project(path: ':fabric-tool-attribute-api-v1', configuration: 'dev')
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.client.fluid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.item.TooltipContext;
|
||||
import net.minecraft.client.texture.Sprite;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
|
||||
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
|
||||
/**
|
||||
* Defines how {@linkplain FluidVariant fluid variants} of a given Fluid should be displayed to clients.
|
||||
* Register with {@link FluidVariantRendering#register}.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
@Environment(EnvType.CLIENT)
|
||||
public interface FluidVariantRenderHandler {
|
||||
/**
|
||||
* Return the name that should be used for the passed fluid variant.
|
||||
*/
|
||||
default Text getName(FluidVariant fluidVariant) {
|
||||
return fluidVariant.getFluid().getDefaultState().getBlockState().getBlock().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append additional tooltips to the passed list if additional information is contained in the fluid variant.
|
||||
*
|
||||
* <p>The name of the fluid, and its identifier if the tooltip context is advanced, should not be appended.
|
||||
* They are already added by {@link FluidVariantRendering#getTooltip}.
|
||||
*/
|
||||
default void appendTooltip(FluidVariant fluidVariant, List<Text> tooltip, TooltipContext tooltipContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sprite that should be used to render the passed fluid variant, for use in baked models, (block) entity renderers, or user interfaces.
|
||||
*
|
||||
* <p>Null may be returned if the fluid variant should not be rendered.
|
||||
*/
|
||||
@Nullable
|
||||
default Sprite getSprite(FluidVariant fluidVariant) {
|
||||
// Use the fluid render handler by default.
|
||||
FluidRenderHandler fluidRenderHandler = FluidRenderHandlerRegistry.INSTANCE.get(fluidVariant.getFluid());
|
||||
|
||||
if (fluidRenderHandler != null) {
|
||||
return fluidRenderHandler.getFluidSprites(null, null, fluidVariant.getFluid().getDefaultState())[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the color to use when rendering {@linkplain #getSprite the sprite} of this fluid variant.
|
||||
*/
|
||||
default int getColor(FluidVariant fluidVariant) {
|
||||
// Use the fluid render handler by default.
|
||||
FluidRenderHandler fluidRenderHandler = FluidRenderHandlerRegistry.INSTANCE.get(fluidVariant.getFluid());
|
||||
|
||||
if (fluidRenderHandler != null) {
|
||||
return fluidRenderHandler.getFluidColor(null, null, fluidVariant.getFluid().getDefaultState());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if this fluid should fill tanks from top.
|
||||
*/
|
||||
default boolean fillsFromTop(FluidVariant fluidVariant) {
|
||||
// By default, fluids should be filled from the bottom.
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.client.fluid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.item.TooltipContext;
|
||||
import net.minecraft.client.texture.Sprite;
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap;
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
|
||||
/**
|
||||
* Client-side display of fluid variants.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class FluidVariantRendering {
|
||||
private static final ApiProviderMap<Fluid, FluidVariantRenderHandler> HANDLERS = ApiProviderMap.create();
|
||||
private static final FluidVariantRenderHandler DEFAULT_HANDLER = new FluidVariantRenderHandler() { };
|
||||
|
||||
/**
|
||||
* Register a render handler for the passed fluid.
|
||||
*/
|
||||
public static void register(Fluid fluid, FluidVariantRenderHandler handler) {
|
||||
if (HANDLERS.putIfAbsent(fluid, handler) != null) {
|
||||
throw new IllegalArgumentException("Duplicate handler registration for fluid " + fluid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the render handler for the passed fluid, if available, and {@code null} otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public static FluidVariantRenderHandler getHandler(Fluid fluid) {
|
||||
return HANDLERS.get(fluid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the render handler for the passed fluid, if available, or the default instance otherwise.
|
||||
*/
|
||||
public static FluidVariantRenderHandler getHandlerOrDefault(Fluid fluid) {
|
||||
FluidVariantRenderHandler handler = HANDLERS.get(fluid);
|
||||
return handler == null ? DEFAULT_HANDLER : handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the passed fluid variant.
|
||||
*/
|
||||
public static Text getName(FluidVariant fluidVariant) {
|
||||
return getHandlerOrDefault(fluidVariant.getFluid()).getName(fluidVariant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tooltip for the passed fluid variant, including the name and additional lines if available
|
||||
* and the id of the fluid if advanced tooltips are enabled.
|
||||
*/
|
||||
public static List<Text> getTooltip(FluidVariant fluidVariant, TooltipContext context) {
|
||||
List<Text> tooltip = new ArrayList<>();
|
||||
|
||||
// Name first
|
||||
tooltip.add(getName(fluidVariant));
|
||||
|
||||
// Additional tooltip information
|
||||
getHandlerOrDefault(fluidVariant.getFluid()).appendTooltip(fluidVariant, tooltip, context);
|
||||
|
||||
// If advanced tooltips are enabled, render the fluid id
|
||||
if (context.isAdvanced()) {
|
||||
tooltip.add(new LiteralText(Registry.FLUID.getId(fluidVariant.getFluid()).toString()).formatted(Formatting.DARK_GRAY));
|
||||
}
|
||||
|
||||
// TODO: consider adding an event to append to tooltips?
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sprite that should be used to render the passed fluid variant, or null if it's not available.
|
||||
* The sprite should be rendered using the color returned by {@link #getColor}.
|
||||
*/
|
||||
@Nullable
|
||||
public static Sprite getSprite(FluidVariant fluidVariant) {
|
||||
return getHandlerOrDefault(fluidVariant.getFluid()).getSprite(fluidVariant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the color that should be used to render {@linkplain #getSprite the sprite} of the passed fluid variant.
|
||||
*/
|
||||
public static int getColor(FluidVariant fluidVariant) {
|
||||
return getHandlerOrDefault(fluidVariant.getFluid()).getColor(fluidVariant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if this fluid variant should be rendered as filling tanks from the top.
|
||||
*/
|
||||
public static boolean fillsFromTop(FluidVariant fluidVariant) {
|
||||
return getHandlerOrDefault(fluidVariant.getFluid()).fillsFromTop(fluidVariant);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.fluid;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.LeveledCauldronBlock;
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.fluid.Fluids;
|
||||
import net.minecraft.state.property.IntProperty;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap;
|
||||
import net.fabricmc.fabric.impl.transfer.fluid.CauldronStorage;
|
||||
|
||||
/**
|
||||
* Entrypoint to expose cauldrons to the Fluid Transfer API.
|
||||
* Empty, water and lava cauldrons are registered by default, and additional cauldrons must be registered with {@link #registerCauldron}.
|
||||
* Contents can be queried with {@link #getForBlock} and {@link #getForFluid}.
|
||||
*
|
||||
* <p>The {@code CauldronFluidContent} itself defines:
|
||||
* <ul>
|
||||
* <li>The block of the cauldron.</li>
|
||||
* <li>The fluid that can be accepted by the cauldron. NBT is discarded when entering the cauldron.</li>
|
||||
* <li>Which fluid amounts can be stored in the cauldron, and how they map to the level property of the cauldron.
|
||||
* If {@code levelProperty} is {@code null}, then {@code maxLevel = 1}, and there is only one level.
|
||||
* Otherwise, the levels are all the integer values between {@code 1} and {@code maxLevel} (included).
|
||||
* </li>
|
||||
* <li>{@code amountPerLevel} defines how much fluid (in droplets) there is in one level of the cauldron.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public final class CauldronFluidContent {
|
||||
/**
|
||||
* Block of the cauldron.
|
||||
*/
|
||||
public final Block block;
|
||||
/**
|
||||
* Fluid stored inside the cauldron.
|
||||
*/
|
||||
public final Fluid fluid;
|
||||
/**
|
||||
* Amount in droplets for each level of {@link #levelProperty}.
|
||||
*/
|
||||
public final long amountPerLevel;
|
||||
/**
|
||||
* Maximum level for {@link #levelProperty}. {@code 1} if {@code levelProperty} is null, otherwise a number {@code >= 1}.
|
||||
* The minimum level is always 1.
|
||||
*/
|
||||
public final int maxLevel;
|
||||
/**
|
||||
* Property storing the level of the cauldron. If it's null, only one level is possible.
|
||||
*/
|
||||
@Nullable
|
||||
public final IntProperty levelProperty;
|
||||
|
||||
private CauldronFluidContent(Block block, Fluid fluid, long amountPerLevel, int maxLevel, @Nullable IntProperty levelProperty) {
|
||||
this.block = block;
|
||||
this.fluid = fluid;
|
||||
this.amountPerLevel = amountPerLevel;
|
||||
this.maxLevel = maxLevel;
|
||||
this.levelProperty = levelProperty;
|
||||
}
|
||||
|
||||
// Copy-on-write, identity semantics, null-checked.
|
||||
private static final ApiProviderMap<Block, CauldronFluidContent> BLOCK_TO_CAULDRON = ApiProviderMap.create();
|
||||
private static final ApiProviderMap<Fluid, CauldronFluidContent> FLUID_TO_CAULDRON = ApiProviderMap.create();
|
||||
|
||||
/**
|
||||
* Get the cauldron fluid content for a cauldron block, or {@code null} if none was registered (yet).
|
||||
*/
|
||||
@Nullable
|
||||
public static CauldronFluidContent getForBlock(Block block) {
|
||||
return BLOCK_TO_CAULDRON.get(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cauldron fluid content for a fluid, or {@code null} if no cauldron was registered for that fluid (yet).
|
||||
*/
|
||||
@Nullable
|
||||
public static CauldronFluidContent getForFluid(Fluid fluid) {
|
||||
return FLUID_TO_CAULDRON.get(fluid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to register a new cauldron if not already registered, allowing it to be filled and emptied through the Fluid Transfer API.
|
||||
* In both cases, return the content of the cauldron, either the existing one, or the newly registered one.
|
||||
*
|
||||
* @param block The block of the cauldron.
|
||||
* @param fluid The fluid stored in this cauldron.
|
||||
* @param amountPerLevel How much fluid is contained in one level of the cauldron, in {@linkplain FluidConstants droplets}.
|
||||
* @param levelProperty The property used by the cauldron to store its levels. {@code null} if the cauldron only has one level.
|
||||
*/
|
||||
public static synchronized CauldronFluidContent registerCauldron(Block block, Fluid fluid, long amountPerLevel, @Nullable IntProperty levelProperty) {
|
||||
CauldronFluidContent existingBlockData = BLOCK_TO_CAULDRON.get(block);
|
||||
|
||||
if (existingBlockData != null) {
|
||||
return existingBlockData;
|
||||
}
|
||||
|
||||
if (FLUID_TO_CAULDRON.get(fluid) != null) {
|
||||
throw new IllegalArgumentException("Fluid already has a mapping for a different block."); // TODO better message
|
||||
}
|
||||
|
||||
CauldronFluidContent data;
|
||||
|
||||
if (levelProperty == null) {
|
||||
data = new CauldronFluidContent(block, fluid, amountPerLevel, 1, null);
|
||||
} else {
|
||||
Collection<Integer> levels = levelProperty.getValues();
|
||||
|
||||
if (levels.size() == 0) {
|
||||
throw new RuntimeException("Cauldron should have at least one possible level.");
|
||||
}
|
||||
|
||||
int minLevel = Integer.MAX_VALUE;
|
||||
int maxLevel = 0;
|
||||
|
||||
for (int level : levels) {
|
||||
minLevel = Math.min(minLevel, level);
|
||||
maxLevel = Math.max(maxLevel, level);
|
||||
}
|
||||
|
||||
if (minLevel != 1 || maxLevel < 1) {
|
||||
throw new IllegalStateException("Minimum level should be 1, and maximum level should be >= 1.");
|
||||
}
|
||||
|
||||
data = new CauldronFluidContent(block, fluid, amountPerLevel, maxLevel, levelProperty);
|
||||
}
|
||||
|
||||
BLOCK_TO_CAULDRON.putIfAbsent(block, data);
|
||||
FLUID_TO_CAULDRON.putIfAbsent(fluid, data);
|
||||
|
||||
FluidStorage.SIDED.registerForBlocks((world, pos, state, be, context) -> CauldronStorage.get(world, pos), block);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current level of the cauldron given its block state, or 0 if it's an empty cauldron.
|
||||
*/
|
||||
public int currentLevel(BlockState state) {
|
||||
if (fluid == Fluids.EMPTY) {
|
||||
return 0;
|
||||
} else if (levelProperty == null) {
|
||||
return 1;
|
||||
} else {
|
||||
return state.get(levelProperty);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
// Vanilla registrations
|
||||
CauldronFluidContent.registerCauldron(Blocks.CAULDRON, Fluids.EMPTY, FluidConstants.BUCKET, null);
|
||||
CauldronFluidContent.registerCauldron(Blocks.WATER_CAULDRON, Fluids.WATER, FluidConstants.BOTTLE, LeveledCauldronBlock.LEVEL);
|
||||
CauldronFluidContent.registerCauldron(Blocks.LAVA_CAULDRON, Fluids.LAVA, FluidConstants.BUCKET, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.fluid;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Constants for fluid transfer. In general, 1 bucket = 81000 droplets = 1 block.
|
||||
*
|
||||
* <p>If you don't know how much droplets you should pick for a specific resource that has a block form,
|
||||
* the convention is to use 81000 droplets for what is worth one block of that resource.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public final class FluidConstants {
|
||||
public static final long BUCKET = 81000;
|
||||
public static final long BOTTLE = 27000;
|
||||
public static final long BLOCK = 81000;
|
||||
public static final long INGOT = 9000;
|
||||
public static final long NUGGET = 1000;
|
||||
public static final long DROPLET = 1;
|
||||
|
||||
/**
|
||||
* Convert a fraction of buckets into droplets.
|
||||
*
|
||||
* <p>For example, passing {@code (1, 3)} will return the 1/3 of a bucket as droplets, so 27000.
|
||||
*
|
||||
* @return The amount of droplets that the passed fraction is equivalent to.
|
||||
* @throws IllegalArgumentException If the fraction can't be converted to droplets exactly.
|
||||
*/
|
||||
public static long fromBucketFraction(long numerator, long denominator) {
|
||||
long total = numerator * BUCKET;
|
||||
|
||||
if (total % denominator != 0) {
|
||||
throw new IllegalArgumentException("Not a valid number of droplets!");
|
||||
} else {
|
||||
return total / denominator;
|
||||
}
|
||||
}
|
||||
|
||||
private FluidConstants() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.fluid;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraft.fluid.Fluids;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
|
||||
/**
|
||||
* Access to {@link Storage Storage<FluidVariant>} instances.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public final class FluidStorage {
|
||||
/**
|
||||
* Sided block access to fluid variant storages.
|
||||
* Fluid amounts are always expressed in {@linkplain FluidConstants droplets}.
|
||||
* The {@code Direction} parameter may never be null.
|
||||
* Refer to {@link BlockApiLookup} for documentation on how to use this field.
|
||||
*
|
||||
* <p>When the operations supported by a storage change,
|
||||
* that is if the return value of {@link Storage#supportsInsertion} or {@link Storage#supportsExtraction} changes,
|
||||
* the storage should notify its neighbors with a block update so that they can refresh their connections if necessary.
|
||||
*
|
||||
* <p>May only be queried on the logical server thread, never client-side or from another thread!
|
||||
*/
|
||||
public static final BlockApiLookup<Storage<FluidVariant>, Direction> SIDED =
|
||||
BlockApiLookup.get(new Identifier("fabric:sided_fluid_storage"), Storage.asClass(), Direction.class);
|
||||
|
||||
private FluidStorage() {
|
||||
}
|
||||
|
||||
static {
|
||||
// Initialize vanilla cauldron wrappers
|
||||
CauldronFluidContent.getForFluid(Fluids.WATER);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.fluid;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.fluid.Fluids;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
|
||||
import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantImpl;
|
||||
|
||||
/**
|
||||
* An immutable association of a still fluid and an optional NBT tag.
|
||||
*
|
||||
* <p>Do not extend this class. Use {@link #of(Fluid)} and {@link #of(Fluid, NbtCompound)} to create instances.
|
||||
*
|
||||
* <p>{@link FluidVariantRendering} can be used for client-side rendering of fluid variants.
|
||||
*
|
||||
* <p><b>Fluid variants must always be compared with {@link #equals}, never by reference!</b>
|
||||
* {@link #hashCode} is guaranteed to be correct and constant time independently of the size of the NBT.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
@ApiStatus.NonExtendable
|
||||
public interface FluidVariant extends TransferVariant<Fluid> {
|
||||
/**
|
||||
* Retrieve a blank FluidVariant.
|
||||
*/
|
||||
static FluidVariant blank() {
|
||||
return of(Fluids.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a FluidVariant with a fluid, and a {@code null} tag.
|
||||
*/
|
||||
static FluidVariant of(Fluid fluid) {
|
||||
return of(fluid, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a FluidVariant with a fluid, and an optional tag.
|
||||
*/
|
||||
static FluidVariant of(Fluid fluid, @Nullable NbtCompound nbt) {
|
||||
return FluidVariantImpl.of(fluid, nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fluid of this variant.
|
||||
*/
|
||||
default Fluid getFluid() {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.fluid.base;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.ResourceAmount;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant;
|
||||
|
||||
/**
|
||||
* A storage that can store a single fluid variant at any given time.
|
||||
* Implementors should at least override {@link #getCapacity(FluidVariant)}, and probably {@link #markDirty} as well.
|
||||
*
|
||||
* <p>{@link #canInsert} and {@link #canExtract} can be used for more precise control over which fluids may be inserted or extracted.
|
||||
* If one of these two functions is overridden to always return false, implementors may also wish to override
|
||||
* {@link #supportsInsertion} and/or {@link #supportsExtraction}.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public abstract class SingleFluidStorage extends SnapshotParticipant<ResourceAmount<FluidVariant>> implements SingleSlotStorage<FluidVariant> {
|
||||
public FluidVariant fluidVariant = FluidVariant.blank();
|
||||
public long amount;
|
||||
|
||||
/**
|
||||
* Implement if you want.
|
||||
*/
|
||||
protected void markDirty() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the passed non-blank fluid variant can be inserted, {@code false} otherwise.
|
||||
*/
|
||||
protected boolean canInsert(FluidVariant fluidVariant) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the passed non-blank fluid variant can be extracted, {@code false} otherwise.
|
||||
*/
|
||||
protected boolean canExtract(FluidVariant fluidVariant) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The maximum capacity of this storage for the passed fluid variant.
|
||||
* If the passed fluid variant is blank, an estimate should be returned.
|
||||
*/
|
||||
protected abstract long getCapacity(FluidVariant fluidVariant);
|
||||
|
||||
@Override
|
||||
public final boolean isResourceBlank() {
|
||||
return fluidVariant.isBlank();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final FluidVariant getResource() {
|
||||
return fluidVariant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long getAmount() {
|
||||
return fluidVariant.isBlank() ? 0 : amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long getCapacity() {
|
||||
return getCapacity(fluidVariant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long insert(FluidVariant insertedFluid, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notBlankNotNegative(insertedFluid, maxAmount);
|
||||
|
||||
if ((insertedFluid.equals(fluidVariant) || fluidVariant.isBlank()) && canInsert(insertedFluid)) {
|
||||
long insertedAmount = Math.min(maxAmount, getCapacity(insertedFluid) - amount);
|
||||
|
||||
if (insertedAmount > 0) {
|
||||
updateSnapshots(transaction);
|
||||
|
||||
// Just in case.
|
||||
if (fluidVariant.isBlank()) {
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
amount += insertedAmount;
|
||||
fluidVariant = insertedFluid;
|
||||
}
|
||||
|
||||
return insertedAmount;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long extract(FluidVariant extractedFluid, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notBlankNotNegative(extractedFluid, maxAmount);
|
||||
|
||||
if (extractedFluid.equals(fluidVariant) && canExtract(extractedFluid)) {
|
||||
long extractedAmount = Math.min(maxAmount, amount);
|
||||
|
||||
if (extractedAmount > 0) {
|
||||
updateSnapshots(transaction);
|
||||
amount -= extractedAmount;
|
||||
|
||||
if (amount == 0) {
|
||||
fluidVariant = FluidVariant.blank();
|
||||
}
|
||||
}
|
||||
|
||||
return extractedAmount;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ResourceAmount<FluidVariant> createSnapshot() {
|
||||
return new ResourceAmount<>(fluidVariant, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void readSnapshot(ResourceAmount<FluidVariant> snapshot) {
|
||||
this.fluidVariant = snapshot.resource();
|
||||
this.amount = snapshot.amount();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onFinalCommit() {
|
||||
markDirty();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <h1>The Transfer API, version 1.</h1>
|
||||
*
|
||||
* <p>This module provides common facilities for the transfer of fluids and other game resources.
|
||||
*
|
||||
* <p><h2>Transactions</h2>
|
||||
* The {@link net.fabricmc.fabric.api.transfer.v1.transaction.Transaction Transaction} system provides a
|
||||
* scope that can be used to simulate any number of transfer operations, and then cancel or validate all of them at once.
|
||||
* One can think of transactions as video game checkpoints. A more detailed explanation can be found in the class javadoc of {@code Transaction}.
|
||||
* Every transfer operation requires a {@code Transaction} parameter.
|
||||
* {@link net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant SnapshotParticipant}
|
||||
* is the reference implementation of a "participant", that is an object participating in a transaction.
|
||||
* </p>
|
||||
*
|
||||
* <p><h2>Storages</h2>
|
||||
* A {@link net.fabricmc.fabric.api.transfer.v1.storage.Storage Storage<T>} is any object that can store resources of type {@code T}.
|
||||
* Its contents can be read, and resources can be inserted into it or extracted from it.
|
||||
* {@link net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil StorageUtil} provides a few helpful function to work with {@code Storage}s,
|
||||
* for example to move resources between two {@code Storage}s.
|
||||
* The {@link net.fabricmc.fabric.api.transfer.v1.storage.base storage/base package} provides a few helpers to accelerate
|
||||
* implementation of {@code Storage<T>}.
|
||||
* Usage of {@link net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions StoragePreconditions} is recommended to detect
|
||||
* wrong usage of {@code Storage} and {@code StorageView} methods.
|
||||
* </p>
|
||||
*
|
||||
* <p><h2>Fluid transfer</h2>
|
||||
* A {@code Storage<FluidVariant>} is any object that can store fluids. It is just a {@code Storage<T>}, where {@code T} is
|
||||
* {@link net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant FluidVariant}, the immutable combination of a {@code Fluid} and additional NBT data.
|
||||
* Instances can be accessed through the API lookup defined in {@link net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage FluidStorage}.
|
||||
* </p>
|
||||
*
|
||||
* <p>Implementors of fluid inventories with a fixed number of "slots" or "tanks" can use
|
||||
* {@link net.fabricmc.fabric.api.transfer.v1.fluid.base.SingleFluidStorage SingleFluidStorage},
|
||||
* and combine them with {@link net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage CombinedStorage}.
|
||||
*
|
||||
* <p>The amount for fluid transfer is droplets, that is 1/81000ths of a bucket.
|
||||
* {@link net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants FluidConstants} contains a few helpful constants to work with droplets.
|
||||
*
|
||||
* <p>Client-side {@linkplain net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering fluid variant rendering} will use regular fluid rendering by default,
|
||||
* ignoring the additional NBT data.
|
||||
* {@code Fluid}s that wish to render differently depending on the stored NBT data can register a
|
||||
* {@link net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRenderHandler FluidVariantRenderHandler}.
|
||||
*/
|
||||
package net.fabricmc.fabric.api.transfer.v1;
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.base.SingleFluidStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.ExtractionOnlyStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.InsertionOnlyStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleViewIterator;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
import net.fabricmc.fabric.impl.transfer.TransferApiImpl;
|
||||
|
||||
/**
|
||||
* An object that can store resources.
|
||||
*
|
||||
* <p><ul>
|
||||
* <li>{@link #supportsInsertion} and {@link #supportsExtraction} can be used to tell if insertion and extraction
|
||||
* functionality are possibly supported by this storage.</li>
|
||||
* <li>{@link #insert} and {@link #extract} can be used to insert or extract resources from this storage.</li>
|
||||
* <li>{@link #iterator} and {@link #exactView} can be used to inspect the contents of this storage.</li>
|
||||
* <li>{@link #getVersion()} can be used to quickly check if a storage has changed, without having to rescan its contents.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Users that wish to implement this interface can use the helpers in the {@code base} package:
|
||||
* <ul>
|
||||
* <li>{@link CombinedStorage} can be used to combine multiple instances, for example to combine multiple "slots" in one big storage.</li>
|
||||
* <li>{@link ExtractionOnlyStorage} and {@link InsertionOnlyStorage} can be used when only extraction or insertion is needed.</li>
|
||||
* <li>{@link SingleViewIterator} can be used to wrap a single view for use with {@link #iterator}.</li>
|
||||
* <li>Resource-specific base implementations may also be available.
|
||||
* For example, Fabric API provides {@link SingleFluidStorage} to accelerate implementations of {@code Storage<FluidVariant>}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Important note:</b> Unless otherwise specified, all transfer functions take a non-blank resource
|
||||
* and a non-negative maximum amount as parameters.
|
||||
* Implementations are encouraged to throw an exception if these preconditions are violated.
|
||||
* {@link StoragePreconditions} can be used for these checks.
|
||||
*
|
||||
* <p>For transfer functions, the returned amount must be non-negative, and smaller than the passed maximum amount.
|
||||
* Consumers of these functions are encourage to throw an exception if these postconditions are violated.
|
||||
*
|
||||
* @param <T> The type of the stored resources.
|
||||
* @see Transaction
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public interface Storage<T> {
|
||||
/**
|
||||
* Return an empty storage.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Storage<T> empty() {
|
||||
return (Storage<T>) TransferApiImpl.EMPTY_STORAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false if calling {@link #insert} will absolutely always return 0, or true otherwise or in doubt.
|
||||
*
|
||||
* <p>Note: This function is meant to be used by pipes or other devices that can transfer resources to know if
|
||||
* they should interact with this storage at all.
|
||||
*/
|
||||
default boolean supportsInsertion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to insert up to some amount of a resource into this storage.
|
||||
*
|
||||
* @param resource The resource to insert. May not be blank.
|
||||
* @param maxAmount The maximum amount of resource to insert. May not be negative.
|
||||
* @param transaction The transaction this operation is part of.
|
||||
* @return A nonnegative integer not greater than maxAmount: the amount that was inserted.
|
||||
*/
|
||||
long insert(T resource, long maxAmount, TransactionContext transaction);
|
||||
|
||||
/**
|
||||
* Return false if calling {@link #extract} will absolutely always return 0, or true otherwise or in doubt.
|
||||
*
|
||||
* <p>Note: This function is meant to be used by pipes or other devices that can transfer resources to know if
|
||||
* they should interact with this storage at all.
|
||||
*/
|
||||
default boolean supportsExtraction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to extract up to some amount of a resource from this storage.
|
||||
*
|
||||
* @param resource The resource to extract. May not be blank.
|
||||
* @param maxAmount The maximum amount of resource to extract. May not be negative.
|
||||
* @param transaction The transaction this operation is part of.
|
||||
* @return A nonnegative integer not greater than maxAmount: the amount that was extracted.
|
||||
*/
|
||||
long extract(T resource, long maxAmount, TransactionContext transaction);
|
||||
|
||||
/**
|
||||
* Iterate through the contents of this storage, for the scope of the passed transaction.
|
||||
* Every visited {@link StorageView} represents a stored resource and an amount.
|
||||
*
|
||||
* <p>The returned iterator and any view it returns are only valid for the scope of to the passed transaction.
|
||||
* They should not be used once that transaction is closed.
|
||||
*
|
||||
* <p>More precisely, as soon as the transaction is closed,
|
||||
* {@link Iterator#hasNext hasNext()} must return {@code false},
|
||||
* and any call to {@link Iterator#next next()} must throw a {@link NoSuchElementException}.
|
||||
*
|
||||
* <p>{@link #insert} and {@link #extract} may be called safely during iteration.
|
||||
* Extractions should be visible to an open iterator, but insertions are not required to.
|
||||
* In particular, inventories with a fixed amount of slots may wish to make insertions visible to iterators,
|
||||
* but inventories with a dynamic or very large amount of slots should not do that to ensure timely termination of
|
||||
* the iteration.
|
||||
*
|
||||
* @param transaction The transaction to which the scope of the returned iterator is tied.
|
||||
* @return An iterator over the contents of this storage.
|
||||
*/
|
||||
Iterator<StorageView<T>> iterator(TransactionContext transaction);
|
||||
|
||||
/**
|
||||
* Iterate through the contents of this storage, for the scope of the passed transaction.
|
||||
* This function follows the semantics of {@link #iterator}, but returns an {@code Iterable} for use in {@code for} loops.
|
||||
*
|
||||
* @param transaction The transaction to which the scope of the returned iterator is tied.
|
||||
* @return An iterable over the contents of this storage.
|
||||
* @see #iterator
|
||||
*/
|
||||
default Iterable<StorageView<T>> iterable(TransactionContext transaction) {
|
||||
return () -> iterator(transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a view over this storage, for a specific resource, or {@code null} if none is quickly available.
|
||||
*
|
||||
* <p>This function should only return a non-null view if this storage can provide it quickly,
|
||||
* for example with a hashmap lookup.
|
||||
* If returning the requested view would require iteration through a potentially large number of views,
|
||||
* {@code null} should be returned instead.
|
||||
*
|
||||
* <p>The returned view is tied to the passed transaction,
|
||||
* and may never be used once the passed transaction has been closed.
|
||||
*
|
||||
* @param transaction The transaction to which the scope of the returned storage view is tied.
|
||||
* @param resource The resource for which a storage view is requested. May be blank, for example to estimate capacity.
|
||||
* @return A view over this storage for the passed resource, or {@code null} if none is quickly available.
|
||||
*/
|
||||
@Nullable
|
||||
default StorageView<T> exactView(TransactionContext transaction, T resource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an integer representing the current version of this storage instance to allow for fast change detection:
|
||||
* if the version hasn't changed since the last time, <b>and the storage instance is the same</b>, the storage has the same contents.
|
||||
* This can be used to avoid re-scanning the contents of the storage, which could be an expensive operation.
|
||||
* It may be used like that:
|
||||
* <pre>{@code
|
||||
* // Store storage and version:
|
||||
* Storage<?> firstStorage = // ...
|
||||
* long firstVersion = firstStorage.getVersion();
|
||||
*
|
||||
* // Later, check if the secondStorage is the unchanged firstStorage:
|
||||
* Storage<?> secondStorage = // ...
|
||||
* long secondVersion = secondStorage.getVersion();
|
||||
* // We must check firstStorage == secondStorage first, otherwise versions may not be compared.
|
||||
* if (firstStorage == secondStorage && firstVersion == secondVersion) {
|
||||
* // storage contents are the same.
|
||||
* } else {
|
||||
* // storage contents might have changed.
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>The version <b>must</b> change if the state of the storage has changed,
|
||||
* generally after a direct modification, or at the end of a modifying transaction.
|
||||
* The version may also change even if the state of the storage hasn't changed.
|
||||
*
|
||||
* <p>It is not valid to call this during a transaction,
|
||||
* and implementations are encouraged to throw an exception if that happens.
|
||||
*/
|
||||
default long getVersion() {
|
||||
if (Transaction.isOpen()) {
|
||||
throw new IllegalStateException("getVersion() may not be called during a transaction.");
|
||||
}
|
||||
|
||||
return TransferApiImpl.version.getAndIncrement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a class instance of this interface with the desired generic type,
|
||||
* to be used for easier registration with API lookups.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Class<Storage<T>> asClass() {
|
||||
return (Class<Storage<T>>) (Object) Storage.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Preconditions that can be used when working with storages.
|
||||
*
|
||||
* <p>In particular, {@link #notNegative} or {@link #notBlankNotNegative} can be used by implementations of
|
||||
* {@link Storage#insert} and {@link Storage#extract} to fail-fast if the arguments are invalid.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public class StoragePreconditions {
|
||||
/**
|
||||
* Ensure that the passed transfer variant is not blank.
|
||||
*
|
||||
* @throws IllegalArgumentException If the variant is blank.
|
||||
*/
|
||||
public static void notBlank(TransferVariant<?> variant) {
|
||||
if (variant.isBlank()) {
|
||||
throw new IllegalArgumentException("Transfer variant may not be blank.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the passed amount is not negative. That is, it must be {@code >= 0}.
|
||||
*
|
||||
* @throws IllegalArgumentException If the amount is negative.
|
||||
*/
|
||||
public static void notNegative(long amount) {
|
||||
if (amount < 0) {
|
||||
throw new IllegalArgumentException("Amount may not be negative, but it is: " + amount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check both for a not blank transfer variant and a not negative amount.
|
||||
*/
|
||||
public static void notBlankNotNegative(TransferVariant<?> variant, long amount) {
|
||||
notBlank(variant);
|
||||
notNegative(amount);
|
||||
}
|
||||
|
||||
private StoragePreconditions() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
/**
|
||||
* Helper functions to work with {@link Storage}s.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public final class StorageUtil {
|
||||
/**
|
||||
* Move resources between two storages, matching the passed filter, and return the amount that was successfully transferred.
|
||||
*
|
||||
* <p>Here is a usage example with fluid variant storages:
|
||||
* <pre>{@code
|
||||
* // Source
|
||||
* Storage<FluidVariant> source;
|
||||
* // Target
|
||||
* Storage<FluidVariant> target;
|
||||
*
|
||||
* // Move up to one bucket in total from source to target, outside of a transaction:
|
||||
* long amountMoved = StorageUtil.move(source, target, variant -> true, FluidConstants.BUCKET, null);
|
||||
* // Move exactly one bucket in total, only of water:
|
||||
* try (Transaction transaction = Transaction.openOuter()) {
|
||||
* Predicate<FluidVariant> filter = variant -> variant.isOf(Fluids.WATER);
|
||||
* long waterMoved = StorageUtil.move(source, target, filter, FluidConstants.BUCKET, transaction);
|
||||
* if (waterMoved == FluidConstants.BUCKET) {
|
||||
* // Only commit if exactly one bucket was moved (no less!).
|
||||
* transaction.commit();
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param from The source storage. May be null.
|
||||
* @param to The target storage. May be null.
|
||||
* @param filter The filter for transferred resources.
|
||||
* Only resources for which this filter returns {@code true} will be transferred.
|
||||
* This filter will never be tested with a blank resource, and filters are encouraged to throw an
|
||||
* exception if this guarantee is violated.
|
||||
* @param maxAmount The maximum amount that will be transferred.
|
||||
* @param transaction The transaction this transfer is part of, or {@code null} if a transaction should be opened just for this transfer.
|
||||
* @param <T> The type of resources to move.
|
||||
* @return The total amount of resources that was successfully transferred.
|
||||
* @throws IllegalStateException If no transaction is passed and a transaction is already active on the current thread.
|
||||
*/
|
||||
public static <T> long move(@Nullable Storage<T> from, @Nullable Storage<T> to, Predicate<T> filter, long maxAmount, @Nullable TransactionContext transaction) {
|
||||
if (from == null || to == null) return 0;
|
||||
|
||||
long totalMoved = 0;
|
||||
|
||||
try (Transaction iterationTransaction = (transaction == null ? Transaction.openOuter() : transaction.openNested())) {
|
||||
for (StorageView<T> view : from.iterable(iterationTransaction)) {
|
||||
if (view.isResourceBlank()) continue;
|
||||
T resource = view.getResource();
|
||||
if (!filter.test(resource)) continue;
|
||||
long maxExtracted;
|
||||
|
||||
// check how much can be extracted
|
||||
try (Transaction extractionTestTransaction = iterationTransaction.openNested()) {
|
||||
maxExtracted = view.extract(resource, maxAmount - totalMoved, extractionTestTransaction);
|
||||
extractionTestTransaction.abort();
|
||||
}
|
||||
|
||||
try (Transaction transferTransaction = iterationTransaction.openNested()) {
|
||||
// check how much can be inserted
|
||||
long accepted = to.insert(resource, maxExtracted, transferTransaction);
|
||||
|
||||
// extract it, or rollback if the amounts don't match
|
||||
if (view.extract(resource, accepted, transferTransaction) == accepted) {
|
||||
totalMoved += accepted;
|
||||
transferTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
if (maxAmount == totalMoved) {
|
||||
// early return if nothing can be moved anymore
|
||||
iterationTransaction.commit();
|
||||
return totalMoved;
|
||||
}
|
||||
}
|
||||
|
||||
iterationTransaction.commit();
|
||||
}
|
||||
|
||||
return totalMoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find a resource stored in the passed storage.
|
||||
*
|
||||
* @param storage The storage to inspect, may be null.
|
||||
* @param transaction The current transaction, or {@code null} if a transaction should be opened for this query.
|
||||
* @param <T> The type of the stored resources.
|
||||
* @return A non-blank resource stored in the storage, or {@code null} if none could be found.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T findStoredResource(@Nullable Storage<T> storage, @Nullable TransactionContext transaction) {
|
||||
if (storage == null) return null;
|
||||
|
||||
if (transaction == null) {
|
||||
try (Transaction outer = Transaction.openOuter()) {
|
||||
return findStoredResourceInner(storage, outer);
|
||||
}
|
||||
} else {
|
||||
return findStoredResourceInner(storage, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T> T findStoredResourceInner(Storage<T> storage, TransactionContext transaction) {
|
||||
for (StorageView<T> view : storage.iterable(transaction)) {
|
||||
if (!view.isResourceBlank()) {
|
||||
return view.getResource();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find a resource stored in the passed storage that can be extracted.
|
||||
*
|
||||
* @param storage The storage to inspect, may be null.
|
||||
* @param transaction The current transaction, or {@code null} if a transaction should be opened for this query.
|
||||
* @param <T> The type of the stored resources.
|
||||
* @return A non-blank resource stored in the storage that can be extracted, or {@code null} if none could be found.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T findExtractableResource(@Nullable Storage<T> storage, @Nullable TransactionContext transaction) {
|
||||
if (storage == null) return null;
|
||||
|
||||
try (Transaction nested = transaction == null ? Transaction.openOuter() : transaction.openNested()) {
|
||||
for (StorageView<T> view : storage.iterable(nested)) {
|
||||
// Extract below could change the resource, so we have to query it before extracting.
|
||||
T resource = view.getResource();
|
||||
|
||||
if (!view.isResourceBlank() && view.extract(resource, Long.MAX_VALUE, nested) > 0) {
|
||||
// Will abort the extraction.
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
/**
|
||||
* A view of a single stored resource in a {@link Storage}, for use with {@link Storage#iterator} or {@link Storage#exactView}.
|
||||
*
|
||||
* <p>A view is always tied to a specific transaction, and should not be accessed outside of it.
|
||||
*
|
||||
* @param <T> The type of the stored resource.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public interface StorageView<T> {
|
||||
/**
|
||||
* Try to extract a resource from this view.
|
||||
*
|
||||
* @return The amount that was extracted.
|
||||
*/
|
||||
long extract(T resource, long maxAmount, TransactionContext transaction);
|
||||
|
||||
/**
|
||||
* @return {@code true} if the {@link #getResource} contained in this storage view is blank, or {@code false} otherwise.
|
||||
*/
|
||||
boolean isResourceBlank();
|
||||
|
||||
/**
|
||||
* @return The resource stored in this view. May not be blank if {@link #isResourceBlank} is {@code false}.
|
||||
*/
|
||||
T getResource();
|
||||
|
||||
/**
|
||||
* @return The amount of {@link #getResource} stored in this view.
|
||||
*/
|
||||
long getAmount();
|
||||
|
||||
/**
|
||||
* @return The total amount of {@link #getResource} that could be stored in this view,
|
||||
* or an estimate of the number of resources that could be stored if this view has a blank resource.
|
||||
*/
|
||||
long getCapacity();
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
/**
|
||||
* An immutable association of an immutable object instance (for example {@code Item} or {@code Fluid}) and an optional NBT tag.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* <p><b>Transfer variants must always be compared with {@link #equals}, never by reference!</b>
|
||||
* {@link #hashCode} is guaranteed to be correct and constant time independently of the size of the NBT.
|
||||
*
|
||||
* @param <O> The type of the immutable object instance, for example {@code Item} or {@code Fluid}.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public interface TransferVariant<O> {
|
||||
/**
|
||||
* Return true if this variant is blank, and false otherwise.
|
||||
*/
|
||||
boolean isBlank();
|
||||
|
||||
/**
|
||||
* Return the immutable object instance of this variant.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@Nullable
|
||||
NbtCompound getNbt();
|
||||
|
||||
/**
|
||||
* Return true if this variant has a tag, false otherwise.
|
||||
*/
|
||||
default boolean hasNbt() {
|
||||
return getNbt() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the tag of this variant matches the passed tag, and false otherwise.
|
||||
*
|
||||
* <p>Note: True is returned if both tags are {@code null}.
|
||||
*/
|
||||
default boolean nbtMatches(@Nullable NbtCompound other) {
|
||||
return Objects.equals(getNbt(), other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the object of this variant matches the passed fluid.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage.base;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
|
||||
/**
|
||||
* A {@link Storage} wrapping multiple storages.
|
||||
*
|
||||
* <p>The storages passed to {@linkplain CombinedStorage#CombinedStorage the constructor} will be iterated in order.
|
||||
*
|
||||
* @param <T> The type of the stored resources.
|
||||
* @param <S> The class of every part. {@code ? extends Storage<T>} can be used if the parts are of different types.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public class CombinedStorage<T, S extends Storage<T>> implements Storage<T> {
|
||||
public final List<S> parts;
|
||||
|
||||
public CombinedStorage(List<S> parts) {
|
||||
this.parts = parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInsertion() {
|
||||
for (S part : parts) {
|
||||
if (part.supportsInsertion()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insert(T resource, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notNegative(maxAmount);
|
||||
long amount = 0;
|
||||
|
||||
for (S part : parts) {
|
||||
amount += part.insert(resource, maxAmount - amount, transaction);
|
||||
if (amount == maxAmount) break;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsExtraction() {
|
||||
for (S part : parts) {
|
||||
if (part.supportsExtraction()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extract(T resource, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notNegative(maxAmount);
|
||||
long amount = 0;
|
||||
|
||||
for (S part : parts) {
|
||||
amount += part.extract(resource, maxAmount - amount, transaction);
|
||||
if (amount == maxAmount) break;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<StorageView<T>> iterator(TransactionContext transaction) {
|
||||
return new CombinedIterator(transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* The combined iterator for multiple storages.
|
||||
*/
|
||||
private class CombinedIterator implements Iterator<StorageView<T>>, Transaction.CloseCallback {
|
||||
boolean open = true;
|
||||
final TransactionContext transaction;
|
||||
final Iterator<S> partIterator = parts.iterator();
|
||||
// Always holds the next StorageView<T>, except during next() while the iterator is being advanced.
|
||||
Iterator<StorageView<T>> currentPartIterator = null;
|
||||
|
||||
CombinedIterator(TransactionContext transaction) {
|
||||
this.transaction = transaction;
|
||||
advanceCurrentPartIterator();
|
||||
transaction.addCloseCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return open && currentPartIterator != null && currentPartIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageView<T> next() {
|
||||
if (!open) {
|
||||
throw new NoSuchElementException("The transaction for this iterator was closed.");
|
||||
}
|
||||
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
StorageView<T> returned = currentPartIterator.next();
|
||||
|
||||
// Advance the current part iterator
|
||||
if (!currentPartIterator.hasNext()) {
|
||||
advanceCurrentPartIterator();
|
||||
}
|
||||
|
||||
return returned;
|
||||
}
|
||||
|
||||
private void advanceCurrentPartIterator() {
|
||||
while (partIterator.hasNext()) {
|
||||
this.currentPartIterator = partIterator.next().iterator(transaction);
|
||||
|
||||
if (this.currentPartIterator.hasNext()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(TransactionContext transaction, Transaction.Result result) {
|
||||
// As soon as the transaction is closed, this iterator is not valid anymore.
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage.base;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
/**
|
||||
* A {@link Storage} that supports extraction, and not insertion.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public interface ExtractionOnlyStorage<T> extends Storage<T> {
|
||||
@Override
|
||||
default boolean supportsInsertion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default long insert(T resource, long maxAmount, TransactionContext transaction) {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage.base;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
/**
|
||||
* A {@link Storage} that supports insertion, and not extraction.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public interface InsertionOnlyStorage<T> extends Storage<T> {
|
||||
@Override
|
||||
default boolean supportsExtraction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default long extract(T resource, long maxAmount, TransactionContext transaction) {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage.base;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* An immutable object storing both a resource and an amount, provided for convenience.
|
||||
* @param <T> The type of the stored resource.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public record ResourceAmount<T> (T resource, long amount) {
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage.base;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
/**
|
||||
* A storage that is also its only storage view.
|
||||
* It can be used in APIs for storages that are wrappers around a single "slot", or for slightly more convenient implementation.
|
||||
*
|
||||
* @param <T> The type of the stored resource.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public interface SingleSlotStorage<T> extends Storage<T>, StorageView<T> {
|
||||
@Override
|
||||
default Iterator<StorageView<T>> iterator(TransactionContext transaction) {
|
||||
return SingleViewIterator.create(this, transaction);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.storage.base;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
/**
|
||||
* An iterator for a single {@link StorageView}, tied to a transaction. Instances can be created with {@link #create}.
|
||||
*
|
||||
* <p>This class should only be used by implementors of {@link Storage#iterator}, that wish to expose a single storage view.
|
||||
* In that case, usage of this class is recommended, as it will ensure that the storage view can't be accessed after the transaction is closed.
|
||||
*
|
||||
* @param <T> The type of the stored resource.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public final class SingleViewIterator<T> implements Iterator<StorageView<T>>, Transaction.CloseCallback {
|
||||
/**
|
||||
* Create a new iterator for the passed storage view, tied to the passed transaction.
|
||||
*
|
||||
* <p>The iterator will ensure that it can only be used as long as the transaction is open.
|
||||
*/
|
||||
public static <T> Iterator<StorageView<T>> create(StorageView<T> view, TransactionContext transaction) {
|
||||
SingleViewIterator<T> it = new SingleViewIterator<>(view);
|
||||
transaction.addCloseCallback(it);
|
||||
return it;
|
||||
}
|
||||
|
||||
private boolean open = true;
|
||||
private boolean hasNext = true;
|
||||
private final StorageView<T> view;
|
||||
|
||||
private SingleViewIterator(StorageView<T> view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return open && hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageView<T> next() {
|
||||
if (!open) {
|
||||
throw new NoSuchElementException("The transaction for this iterator was closed.");
|
||||
}
|
||||
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
hasNext = false;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(TransactionContext transaction, Transaction.Result result) {
|
||||
open = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.transaction;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant;
|
||||
import net.fabricmc.fabric.impl.transfer.transaction.TransactionManagerImpl;
|
||||
|
||||
/**
|
||||
* A global operation where participants guarantee atomicity: either the whole operation succeeds,
|
||||
* or it is completely aborted and rolled back.
|
||||
*
|
||||
* <p>One can imagine that transactions are like video game checkpoints.
|
||||
* <ul>
|
||||
* <li>{@linkplain #openOuter Opening a transaction} with a try-with-resources block creates a checkpoint.</li>
|
||||
* <li>Modifications to game state can then happen.</li>
|
||||
* <li>Calling {@link #commit} validates the modifications that happened during the transaction,
|
||||
* essentially discarding the checkpoint.</li>
|
||||
* <li>Calling {@link #abort} or doing nothing and letting the transaction be {@linkplain #close closed} at the end
|
||||
* of the try-with-resources block cancels any modification that happened during the transaction,
|
||||
* reverting to the checkpoint.</li>
|
||||
* <li>Calling {@link #openNested} on a transaction creates a new nested transaction, i.e. a new checkpoint with the current state.
|
||||
* Committing a nested transaction will validate the changes that happened, but they may
|
||||
* still be cancelled later if a parent transaction is cancelled.
|
||||
* Aborting a nested transaction immediately reverts the changes - cancelling any modification made after the call
|
||||
* to {@link #openNested}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>This is illustrated in the following example.
|
||||
* <pre>{@code
|
||||
* try (Transaction outerTransaction = Transaction.openOuter()) {
|
||||
* // (A) some transaction operations
|
||||
* try (Transaction nestedTransaction = outerTransaction.openNested()) {
|
||||
* // (B) more operations
|
||||
* nestedTransaction.commit(); // Validate the changes that happened in this transaction.
|
||||
* // This is a nested transaction, so changes will only be applied if the outer
|
||||
* // transaction is committed too.
|
||||
* }
|
||||
* // (C) even more operations
|
||||
* outerTransaction.commit(); // This is an outer transaction: changes (A), (B) and (C) are applied.
|
||||
* }
|
||||
* // If we hadn't committed the outerTransaction, all changes (A), (B) and (C) would have been reverted.
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Participants are responsible for upholding this contract themselves, by using {@link #addCloseCallback}
|
||||
* to react to transaction close events and properly validate or revert changes.
|
||||
* Any action that modifies state outside of the transaction, such as calls to {@code markDirty()} or neighbor updates,
|
||||
* should be deferred until {@linkplain #addOuterCloseCallback after the outer transaction is closed}
|
||||
* to give every participant a chance to react to transaction close events.
|
||||
*
|
||||
* <p>This is very low-level for most applications, and most participants should subclass {@link SnapshotParticipant}
|
||||
* that will take care of properly maintaining their state.
|
||||
*
|
||||
* <p>Participants should generally be passed a {@link TransactionContext} parameter instead of the full {@code Transaction},
|
||||
* to make sure they don't call {@link #abort}, {@link #commit} or {@link #close} mistakenly.
|
||||
*
|
||||
* <p>Every transaction is only valid on the thread it was opened on,
|
||||
* and attempts to use it on another thread will throw an exception.
|
||||
* Consequently, transactions can be concurrent across multiple threads, as long as they don't share any state.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
@ApiStatus.NonExtendable
|
||||
public interface Transaction extends AutoCloseable, TransactionContext {
|
||||
/**
|
||||
* Open a new outer transaction.
|
||||
*
|
||||
* @throws IllegalStateException If a transaction is already active on the current thread.
|
||||
*/
|
||||
static Transaction openOuter() {
|
||||
return TransactionManagerImpl.MANAGERS.get().openOuter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if a transaction is open on the current thread, and false otherwise.
|
||||
*/
|
||||
static boolean isOpen() {
|
||||
return TransactionManagerImpl.MANAGERS.get().isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the current transaction, rolling back all the changes that happened during this transaction and
|
||||
* the transactions opened with {@link #openNested} from this transaction.
|
||||
*
|
||||
* @throws IllegalStateException If this function is not called on the thread this transaction was opened in.
|
||||
* @throws IllegalStateException If this transaction is not the current transaction.
|
||||
* @throws IllegalStateException If this transaction was closed.
|
||||
*/
|
||||
void abort();
|
||||
|
||||
/**
|
||||
* Close the current transaction, committing all the changes that happened during this transaction and
|
||||
* the <b>committed</b> transactions opened with {@link #openNested} from this transaction.
|
||||
* If this transaction was opened with {@link #openOuter}, all changes are applied.
|
||||
* If this transaction was opened with {@link #openNested}, all changes will be applied when and if the changes of
|
||||
* the parent transactions are applied.
|
||||
*
|
||||
* @throws IllegalStateException If this function is not called on the thread this transaction was opened in.
|
||||
* @throws IllegalStateException If this transaction is not the current transaction.
|
||||
* @throws IllegalStateException If this transaction was closed.
|
||||
*/
|
||||
void commit();
|
||||
|
||||
/**
|
||||
* Abort the current transaction if it was not closed already.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.transaction;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A subset of a {@link Transaction} that lets participants properly take part in transactions, manage their state,
|
||||
* or open nested transactions, but does not allow them to close the transaction they are passed.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TransactionContext {
|
||||
/**
|
||||
* Open a new nested transaction.
|
||||
*
|
||||
* @throws IllegalStateException If this function is not called on the thread this transaction was opened in.
|
||||
* @throws IllegalStateException If this transaction is not the current transaction.
|
||||
* @throws IllegalStateException If this transaction was closed.
|
||||
*/
|
||||
Transaction openNested();
|
||||
|
||||
/**
|
||||
* @return The nesting depth of this transaction: 0 if it was opened with {@link Transaction#openOuter},
|
||||
* 1 if its parent was opened with {@link Transaction#openOuter}, and so on...
|
||||
* @throws IllegalStateException If this function is not called on the thread this transaction was opened in.
|
||||
*/
|
||||
int nestingDepth();
|
||||
|
||||
/**
|
||||
* Return the transaction with the specific nesting depth.
|
||||
*
|
||||
* @param nestingDepth Queried nesting depth.
|
||||
* @throws IndexOutOfBoundsException If there is no open transaction with the request nesting depth.
|
||||
* @throws IllegalStateException If this function is not called on the thread this transaction was opened in.
|
||||
*/
|
||||
Transaction getOpenTransaction(int nestingDepth);
|
||||
|
||||
/**
|
||||
* Register a callback that will be invoked when this transaction is closed.
|
||||
* Registered callbacks are invoked last-to-first: the last callback to be registered will be the first to be invoked, and so on...
|
||||
*
|
||||
* <p>Updates that may change the state of other participants should be deferred until after the outermost transaction is closed
|
||||
* using {@link #addOuterCloseCallback}.
|
||||
*
|
||||
* @throws IllegalStateException If this function is not called on the thread this transaction was opened in.
|
||||
*/
|
||||
void addCloseCallback(CloseCallback closeCallback);
|
||||
|
||||
/**
|
||||
* A callback that is invoked when a transaction is closed.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface CloseCallback {
|
||||
/**
|
||||
* Perform an action when a transaction is closed.
|
||||
*
|
||||
* @param transaction The closed transaction. Only {@link #nestingDepth}, {@link #getOpenTransaction} and {@link #addOuterCloseCallback}
|
||||
* may be called on that transaction.
|
||||
* {@link #addCloseCallback} may additionally be called on parent transactions
|
||||
* (accessed through {@link #getOpenTransaction} for lower nesting depths).
|
||||
* @param result The result of this transaction: whether it was committed or aborted.
|
||||
*/
|
||||
void onClose(TransactionContext transaction, Result result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback that will be invoked after the outermost transaction is closed,
|
||||
* and after callbacks registered with {@link #addCloseCallback} are ran.
|
||||
* Registered callbacks are invoked last-to-first.
|
||||
*
|
||||
* @throws IllegalStateException If this function is not called on the thread this transaction was opened in.
|
||||
*/
|
||||
void addOuterCloseCallback(OuterCloseCallback outerCloseCallback);
|
||||
|
||||
/**
|
||||
* A callback that is invoked after the outer transaction is closed.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface OuterCloseCallback {
|
||||
/**
|
||||
* Perform an action after the top-level transaction is closed.
|
||||
*
|
||||
* @param result The result of the top-level transaction.
|
||||
*/
|
||||
void afterOuterClose(Result result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a transaction operation.
|
||||
*/
|
||||
enum Result {
|
||||
ABORTED,
|
||||
COMMITTED;
|
||||
|
||||
/**
|
||||
* @return true if the transaction was aborted, false if it was committed.
|
||||
*/
|
||||
public boolean wasAborted() {
|
||||
return this == ABORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the transaction was committed, false if it was aborted.
|
||||
*/
|
||||
public boolean wasCommitted() {
|
||||
return this == COMMITTED;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.transfer.v1.transaction.base;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
/**
|
||||
* A base participant implementation that modifies itself during transactions,
|
||||
* saving snapshots of its state in objects of type {@code T} in case it needs to revert to a previous state.
|
||||
*
|
||||
* <p>{@link #updateSnapshots} should be called before any modification.
|
||||
* This will save the state of this participant using {@link #createSnapshot} if no state was already saved for that transaction.
|
||||
* When the transaction is aborted and changes need to be rolled back, {@link #readSnapshot} will be called
|
||||
* to signal that the current state should revert to that of the snapshot.
|
||||
* The snapshot object is then {@linkplain #releaseSnapshot released}, and can be cached for subsequent use, or discarded.
|
||||
*
|
||||
* <p>When an outer transaction is committed, {@link #readSnapshot} will not be called so that the current state of this participant
|
||||
* is retained. {@link #releaseSnapshot} will be called because the snapshot is not necessary anymore,
|
||||
* and {@link #onFinalCommit} will be called after the transaction is closed.
|
||||
*
|
||||
* @param <T> The objects that this participant uses to save its state snapshots.
|
||||
*
|
||||
* @deprecated Experimental feature, we reserve the right to remove or change it without further notice.
|
||||
* The transfer API is a complex addition, and we want to be able to correct possible design mistakes.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Deprecated
|
||||
public abstract class SnapshotParticipant<T> implements Transaction.CloseCallback, Transaction.OuterCloseCallback {
|
||||
private final List<T> snapshots = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Return a new <b>nonnull</b> object containing the current state of this participant.
|
||||
* <b>{@code null} may not be returned, or an exception will be thrown!</b>
|
||||
*/
|
||||
protected abstract T createSnapshot();
|
||||
|
||||
/**
|
||||
* Roll back to a state previously created by {@link #createSnapshot}.
|
||||
*/
|
||||
protected abstract void readSnapshot(T snapshot);
|
||||
|
||||
/**
|
||||
* Signals that the snapshot will not be used anymore, and is safe to cache for next calls to {@link #createSnapshot},
|
||||
* or discard entirely.
|
||||
*/
|
||||
protected void releaseSnapshot(T snapshot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after an outer transaction succeeded,
|
||||
* to perform irreversible actions such as {@code markDirty()} or neighbor updates.
|
||||
*/
|
||||
protected void onFinalCommit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the stored snapshots so that the changes happening as part of the passed transaction can be correctly
|
||||
* committed or rolled back.
|
||||
* This function should be called every time the participant is about to change its internal state as part of a transaction.
|
||||
*/
|
||||
public final void updateSnapshots(TransactionContext transaction) {
|
||||
// Make sure we have enough storage for snapshots
|
||||
while (snapshots.size() <= transaction.nestingDepth()) {
|
||||
snapshots.add(null);
|
||||
}
|
||||
|
||||
// If the snapshot is null, we need to create it, and we need to register a callback.
|
||||
if (snapshots.get(transaction.nestingDepth()) == null) {
|
||||
T snapshot = createSnapshot();
|
||||
Objects.requireNonNull(snapshot, "Snapshot may not be null!");
|
||||
|
||||
snapshots.set(transaction.nestingDepth(), snapshot);
|
||||
transaction.addCloseCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onClose(TransactionContext transaction, Transaction.Result result) {
|
||||
// Get and remove the relevant snapshot.
|
||||
T snapshot = snapshots.set(transaction.nestingDepth(), null);
|
||||
|
||||
if (result.wasAborted()) {
|
||||
// If the transaction was aborted, we just revert to the state of the snapshot.
|
||||
readSnapshot(snapshot);
|
||||
releaseSnapshot(snapshot);
|
||||
} else if (transaction.nestingDepth() > 0) {
|
||||
if (snapshots.get(transaction.nestingDepth() - 1) == null) {
|
||||
// No snapshot yet, so move the snapshot one nesting level up.
|
||||
snapshots.set(transaction.nestingDepth() - 1, snapshot);
|
||||
// This is the first snapshot at this level: we need to call addCloseCallback.
|
||||
transaction.getOpenTransaction(transaction.nestingDepth() - 1).addCloseCallback(this);
|
||||
} else {
|
||||
// There is already an older snapshot at the nesting level above, just release the newer one.
|
||||
releaseSnapshot(snapshot);
|
||||
}
|
||||
} else {
|
||||
releaseSnapshot(snapshot);
|
||||
transaction.addOuterCloseCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void afterOuterClose(Transaction.Result result) {
|
||||
// The result is guaranteed to be COMMITTED,
|
||||
// as this is only scheduled during onClose() when the outer transaction is successful.
|
||||
onFinalCommit();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
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 AtomicLong version = new AtomicLong();
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static final Storage EMPTY_STORAGE = new Storage() {
|
||||
@Override
|
||||
public boolean supportsInsertion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insert(Object resource, long maxAmount, TransactionContext transaction) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsExtraction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extract(Object resource, long maxAmount, TransactionContext transaction) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<StorageView> iterator(TransactionContext transaction) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.MapMaker;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.CauldronFluidContent;
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant;
|
||||
|
||||
/**
|
||||
* Standard implementation of {@code Storage<FluidVariant>}, using cauldron/fluid mappings registered in {@link CauldronFluidContent}.
|
||||
*
|
||||
* <p>Implementation notes:
|
||||
* <ul>
|
||||
* <li>To make sure multiple access to the same cauldron return the same wrapper, we maintain a {@code (World, BlockPos) -> Wrapper} cache.</li>
|
||||
* <li>The wrapper mutates the world directly with setBlockState, but updates are suppressed.
|
||||
* On final commit, a block update is sent by reverting to {@linkplain #lastReleasedSnapshot the initial block state} with updates suppressed,
|
||||
* then setting the final block state again, without suppressing updates.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CauldronStorage extends SnapshotParticipant<BlockState> implements SingleSlotStorage<FluidVariant> {
|
||||
// Record is used for convenient constructor, hashcode and equals implementations.
|
||||
private record WorldLocation(World world, BlockPos pos) {
|
||||
}
|
||||
|
||||
// Weak values to make sure wrappers are cleaned up after use, thread-safe.
|
||||
private static final Map<WorldLocation, CauldronStorage> CAULDRONS = new MapMaker().concurrencyLevel(1).weakValues().makeMap();
|
||||
|
||||
public static CauldronStorage get(World world, BlockPos pos) {
|
||||
WorldLocation location = new WorldLocation(world, pos.toImmutable());
|
||||
CAULDRONS.computeIfAbsent(location, CauldronStorage::new);
|
||||
return CAULDRONS.get(location);
|
||||
}
|
||||
|
||||
private final WorldLocation location;
|
||||
// this is the last released snapshot, which means it's the first snapshot ever saved when onFinalCommit() is called.
|
||||
private BlockState lastReleasedSnapshot;
|
||||
|
||||
CauldronStorage(WorldLocation location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseSnapshot(BlockState snapshot) {
|
||||
lastReleasedSnapshot = snapshot;
|
||||
}
|
||||
|
||||
// Retrieve the current CauldronFluidContent.
|
||||
private CauldronFluidContent getCurrentContent() {
|
||||
CauldronFluidContent content = CauldronFluidContent.getForBlock(createSnapshot().getBlock());
|
||||
|
||||
if (content == null) {
|
||||
throw new IllegalStateException("Unexpected error: no cauldron at location " + location);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// Called by insert and extract to update the block state.
|
||||
private void updateLevel(CauldronFluidContent newContent, int level, TransactionContext transaction) {
|
||||
updateSnapshots(transaction);
|
||||
BlockState newState = newContent.block.getDefaultState();
|
||||
|
||||
if (newContent.levelProperty != null) {
|
||||
newState = newState.with(newContent.levelProperty, level);
|
||||
}
|
||||
|
||||
// Set block state without updates.
|
||||
location.world.setBlockState(location.pos, newState, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insert(FluidVariant fluidVariant, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notBlankNotNegative(fluidVariant, maxAmount);
|
||||
|
||||
CauldronFluidContent insertContent = CauldronFluidContent.getForFluid(fluidVariant.getFluid());
|
||||
|
||||
if (insertContent != null) {
|
||||
int maxLevelsInserted = Ints.saturatedCast(maxAmount / insertContent.amountPerLevel);
|
||||
|
||||
if (getAmount() == 0) {
|
||||
// Currently empty, so we can accept any fluid.
|
||||
int levelsInserted = Math.min(maxLevelsInserted, insertContent.maxLevel);
|
||||
|
||||
if (levelsInserted > 0) {
|
||||
updateLevel(insertContent, levelsInserted, transaction);
|
||||
}
|
||||
|
||||
return levelsInserted * insertContent.amountPerLevel;
|
||||
}
|
||||
|
||||
CauldronFluidContent currentContent = getCurrentContent();
|
||||
|
||||
if (fluidVariant.isOf(currentContent.fluid)) {
|
||||
// Otherwise we can only accept the same fluid as the current one.
|
||||
int currentLevel = currentContent.currentLevel(createSnapshot());
|
||||
int levelsInserted = Math.min(maxLevelsInserted, currentContent.maxLevel - currentLevel);
|
||||
|
||||
if (levelsInserted > 0) {
|
||||
updateLevel(currentContent, currentLevel + levelsInserted, transaction);
|
||||
}
|
||||
|
||||
return levelsInserted * currentContent.amountPerLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extract(FluidVariant fluidVariant, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notBlankNotNegative(fluidVariant, maxAmount);
|
||||
|
||||
CauldronFluidContent currentContent = getCurrentContent();
|
||||
|
||||
if (fluidVariant.isOf(currentContent.fluid)) {
|
||||
int maxLevelsExtracted = Ints.saturatedCast(maxAmount / currentContent.amountPerLevel);
|
||||
int currentLevel = currentContent.currentLevel(createSnapshot());
|
||||
int levelsExtracted = Math.min(maxLevelsExtracted, currentLevel);
|
||||
|
||||
if (levelsExtracted > 0) {
|
||||
if (levelsExtracted == currentLevel) {
|
||||
// Fully extract -> back to empty cauldron
|
||||
updateSnapshots(transaction);
|
||||
location.world.setBlockState(location.pos, Blocks.CAULDRON.getDefaultState(), 0);
|
||||
} else {
|
||||
// Otherwise just decrease levels
|
||||
updateLevel(currentContent, currentLevel - levelsExtracted, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
return levelsExtracted * currentContent.amountPerLevel;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceBlank() {
|
||||
return getResource().isBlank();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidVariant getResource() {
|
||||
return FluidVariant.of(getCurrentContent().fluid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAmount() {
|
||||
CauldronFluidContent currentContent = getCurrentContent();
|
||||
return currentContent.currentLevel(createSnapshot()) * currentContent.amountPerLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCapacity() {
|
||||
CauldronFluidContent currentContent = getCurrentContent();
|
||||
return currentContent.maxLevel * currentContent.amountPerLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState createSnapshot() {
|
||||
return location.world.getBlockState(location.pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSnapshot(BlockState savedState) {
|
||||
location.world.setBlockState(location.pos, savedState, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalCommit() {
|
||||
BlockState state = createSnapshot();
|
||||
BlockState originalState = lastReleasedSnapshot;
|
||||
|
||||
if (originalState != state) {
|
||||
// Revert change
|
||||
location.world.setBlockState(location.pos, originalState, 0);
|
||||
// Then do the actual change with normal block updates
|
||||
location.world.setBlockState(location.pos, state);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
|
||||
/**
|
||||
* Implemented by fluids to cache the FluidVariant with a null tag inside the Fluid object directly.
|
||||
*/
|
||||
public interface FluidVariantCache {
|
||||
FluidVariant fabric_getCachedFluidVariant();
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.fluid.Fluids;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
|
||||
public class FluidVariantImpl implements FluidVariant {
|
||||
public static FluidVariant of(Fluid fluid, @Nullable NbtCompound nbt) {
|
||||
Objects.requireNonNull(fluid, "Fluid 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.
|
||||
throw new IllegalArgumentException("Fluid may not be flowing.");
|
||||
}
|
||||
|
||||
if (nbt == null || 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);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("fabric-transfer-api-v1/fluid");
|
||||
|
||||
private final Fluid fluid;
|
||||
private final @Nullable NbtCompound nbt;
|
||||
private final int hashCode;
|
||||
|
||||
public FluidVariantImpl(Fluid fluid, NbtCompound nbt) {
|
||||
this.fluid = fluid;
|
||||
this.nbt = nbt == null ? null : nbt.copy(); // defensive copy
|
||||
this.hashCode = Objects.hash(fluid, nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlank() {
|
||||
return fluid == Fluids.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fluid getObject() {
|
||||
return fluid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable NbtCompound getNbt() {
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtCompound toNbt() {
|
||||
NbtCompound result = new NbtCompound();
|
||||
result.putString("fluid", Registry.FLUID.getId(fluid).toString());
|
||||
|
||||
if (nbt != null) {
|
||||
result.put("tag", nbt.copy());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static FluidVariant fromNbt(NbtCompound compound) {
|
||||
try {
|
||||
Fluid fluid = Registry.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(Registry.FLUID.getRawId(fluid));
|
||||
buf.writeNbt(nbt);
|
||||
}
|
||||
}
|
||||
|
||||
public static FluidVariant fromPacket(PacketByteBuf buf) {
|
||||
if (!buf.readBoolean()) {
|
||||
return FluidVariant.blank();
|
||||
} else {
|
||||
Fluid fluid = Registry.FLUID.get(buf.readVarInt());
|
||||
NbtCompound nbt = buf.readNbt();
|
||||
return of(fluid, nbt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FluidVariantImpl{fluid=" + fluid + ", tag=" + nbt + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
// succeed fast with == check
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
FluidVariantImpl fluidVariant = (FluidVariantImpl) o;
|
||||
// fail fast with hash code
|
||||
return hashCode == fluidVariant.hashCode && fluid == fluidVariant.fluid && nbtMatches(fluidVariant.nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* 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.transaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
|
||||
public class TransactionManagerImpl {
|
||||
public static final ThreadLocal<TransactionManagerImpl> MANAGERS = ThreadLocal.withInitial(TransactionManagerImpl::new);
|
||||
|
||||
private final Thread thread = Thread.currentThread();
|
||||
private final ArrayList<TransactionImpl> stack = new ArrayList<>();
|
||||
private final ArrayList<Transaction.OuterCloseCallback> outerCloseCallbacks = new ArrayList<>();
|
||||
private int currentDepth = -1;
|
||||
|
||||
public boolean isOpen() {
|
||||
return currentDepth > -1;
|
||||
}
|
||||
|
||||
public Transaction openOuter() {
|
||||
if (isOpen()) {
|
||||
throw new IllegalStateException("An outer transaction is already active on this thread.");
|
||||
}
|
||||
|
||||
return open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new transaction, outer or nested, without performing any state check.
|
||||
*/
|
||||
Transaction open() {
|
||||
currentDepth++;
|
||||
|
||||
if (stack.size() == currentDepth) {
|
||||
stack.add(new TransactionImpl(currentDepth));
|
||||
}
|
||||
|
||||
TransactionImpl current = stack.get(currentDepth);
|
||||
current.isOpen = true;
|
||||
return current;
|
||||
}
|
||||
|
||||
void validateCurrentThread() {
|
||||
if (Thread.currentThread() != thread) {
|
||||
String errorMessage = String.format(
|
||||
"Attempted to access transaction state from thread %s, but this transaction is only valid on thread %s.",
|
||||
Thread.currentThread().getName(),
|
||||
thread.getName());
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private class TransactionImpl implements Transaction {
|
||||
final int nestingDepth;
|
||||
final ArrayList<CloseCallback> closeCallbacks = new ArrayList<>();
|
||||
// This may be false even when the transaction is not fully closed, to prevent callbacks calling other functions in an invalid state.
|
||||
// It is reset to true in TransactionManagerImpl#open.
|
||||
boolean isOpen = false;
|
||||
|
||||
TransactionImpl(int nestingDepth) {
|
||||
this.nestingDepth = nestingDepth;
|
||||
}
|
||||
|
||||
void validateCurrentTransaction() {
|
||||
validateCurrentThread();
|
||||
|
||||
if (currentDepth == -1 || stack.get(currentDepth) != this) {
|
||||
String errorMessage = String.format(
|
||||
"Transaction function was called on a transaction with depth %d, but the current transaction has depth %d.",
|
||||
nestingDepth,
|
||||
currentDepth);
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that this transaction is open.
|
||||
private void validateOpen() {
|
||||
if (!isOpen) {
|
||||
throw new IllegalStateException("Transaction operation cannot be applied to a closed transaction.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction openNested() {
|
||||
validateCurrentTransaction();
|
||||
validateOpen();
|
||||
return open();
|
||||
}
|
||||
|
||||
private void close(Result result) {
|
||||
validateCurrentTransaction();
|
||||
validateOpen();
|
||||
// Block transaction operations
|
||||
isOpen = false;
|
||||
|
||||
// Note: it is important that we don't let exceptions corrupt the global state of the transaction manager.
|
||||
// That is why any callback has to run inside a try block.
|
||||
RuntimeException closeException = null;
|
||||
|
||||
// Invoke callbacks in reverse order
|
||||
for (int i = closeCallbacks.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
closeCallbacks.get(i).onClose(this, result);
|
||||
} catch (Exception exception) {
|
||||
if (closeException == null) {
|
||||
closeException = new RuntimeException("Encountered an exception while invoking a transaction close callback.", exception);
|
||||
} else {
|
||||
closeException.addSuppressed(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeCallbacks.clear();
|
||||
|
||||
if (currentDepth == 0) {
|
||||
// Invoke outer close callbacks in reverse order
|
||||
for (int i = outerCloseCallbacks.size() - 1; i >= 0; i--) {
|
||||
try {
|
||||
outerCloseCallbacks.get(i).afterOuterClose(result);
|
||||
} catch (Exception exception) {
|
||||
if (closeException == null) {
|
||||
closeException = new RuntimeException("Encountered an exception while invoking a transaction outer close callback.", exception);
|
||||
} else {
|
||||
closeException.addSuppressed(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outerCloseCallbacks.clear();
|
||||
}
|
||||
|
||||
// Only this check will allow openOuter operations.
|
||||
currentDepth--;
|
||||
|
||||
// Throw exception if necessary
|
||||
if (closeException != null) {
|
||||
throw closeException;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
close(Result.ABORTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
close(Result.COMMITTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (isOpen() && isOpen) { // check that a transaction is open on this thread and that this transaction is open.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nestingDepth() {
|
||||
validateCurrentThread();
|
||||
return nestingDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction getOpenTransaction(int nestingDepth) {
|
||||
validateCurrentThread();
|
||||
|
||||
if (nestingDepth < 0) {
|
||||
throw new IndexOutOfBoundsException("Nesting depth may not be negative.");
|
||||
}
|
||||
|
||||
if (nestingDepth > currentDepth) {
|
||||
throw new IndexOutOfBoundsException("There is no open transaction for nesting depth " + nestingDepth);
|
||||
}
|
||||
|
||||
TransactionImpl transaction = stack.get(nestingDepth);
|
||||
transaction.validateOpen();
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCloseCallback(CloseCallback closeCallback) {
|
||||
validateCurrentThread();
|
||||
validateOpen();
|
||||
closeCallbacks.add(closeCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOuterCloseCallback(OuterCloseCallback outerCloseCallback) {
|
||||
validateCurrentThread();
|
||||
// Note: we don't call validateOpen() because this transaction may not be open if this is called during a CloseCallback.
|
||||
// We rely on a currentDepth check instead, as the depth is only set to -1 at the very end of close(Result).
|
||||
|
||||
if (currentDepth == -1) {
|
||||
throw new IllegalStateException("There is no open transaction on this thread.");
|
||||
}
|
||||
|
||||
outerCloseCallbacks.add(outerCloseCallback);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.mixin.transfer;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
import net.minecraft.fluid.Fluid;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantImpl;
|
||||
import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantCache;
|
||||
|
||||
/**
|
||||
* Cache the FluidVariant with a null tag inside each Fluid directly.
|
||||
*/
|
||||
@Mixin(Fluid.class)
|
||||
@SuppressWarnings("unused")
|
||||
public class FluidMixin implements FluidVariantCache {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private final FluidVariant cachedFluidVariant = new FluidVariantImpl((Fluid) (Object) this, null);
|
||||
|
||||
@Override
|
||||
public FluidVariant fabric_getCachedFluidVariant() {
|
||||
return cachedFluidVariant;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin.transfer",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"FluidMixin"
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
30
fabric-transfer-api-v1/src/main/resources/fabric.mod.json
Normal file
30
fabric-transfer-api-v1/src/main/resources/fabric.mod.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-transfer-api-v1",
|
||||
"name": "Fabric Transfer API (v1)",
|
||||
"version": "${version}",
|
||||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"icon": "assets/fabric-api-lookup-api-v1/icon.png",
|
||||
"contact": {
|
||||
"homepage": "https://fabricmc.net",
|
||||
"irc": "irc://irc.esper.net:6667/fabric",
|
||||
"issues": "https://github.com/FabricMC/fabric/issues",
|
||||
"sources": "https://github.com/FabricMC/fabric"
|
||||
},
|
||||
"authors": [
|
||||
"FabricMC"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.9.2",
|
||||
"fabric-api-lookup-api-v1": "*",
|
||||
"fabric-rendering-fluids-v1": "*"
|
||||
},
|
||||
"description": "A common API for the transfer of fluids and other game resources.",
|
||||
"mixins": [
|
||||
"fabric-transfer-api-v1.mixins.json"
|
||||
],
|
||||
"custom": {
|
||||
"fabric-api:module-lifecycle": "experimental"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.minecraft.fluid.Fluids;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.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.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));
|
||||
|
||||
private final FluidVariant infiniteFluid;
|
||||
|
||||
private CreativeFluidStorage(FluidVariant infiniteFluid) {
|
||||
this.infiniteFluid = infiniteFluid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceBlank() {
|
||||
return infiniteFluid.isBlank();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidVariant getResource() {
|
||||
return infiniteFluid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAmount() {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCapacity() {
|
||||
return getAmount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extract(FluidVariant resource, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notBlankNotNegative(resource, maxAmount);
|
||||
|
||||
if (resource.equals(infiniteFluid)) {
|
||||
return maxAmount;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<StorageView<FluidVariant>> iterator(TransactionContext transaction) {
|
||||
return SingleViewIterator.create(this, transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.item.ItemUsageContext;
|
||||
import net.minecraft.util.ActionResult;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants;
|
||||
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.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
|
||||
public class ExtractStickItem extends Item {
|
||||
public ExtractStickItem() {
|
||||
super(new Settings().group(ItemGroup.MISC));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionResult useOnBlock(ItemUsageContext context) {
|
||||
Storage<FluidVariant> storage = FluidStorage.SIDED.find(context.getWorld(), context.getBlockPos(), context.getSide());
|
||||
|
||||
try (Transaction transaction = Transaction.openOuter()) {
|
||||
// Find something to extract
|
||||
FluidVariant stored = StorageUtil.findExtractableResource(storage, transaction);
|
||||
if (stored == null) return ActionResult.PASS;
|
||||
|
||||
// By now, storage can't be null :P
|
||||
long extracted = storage.extract(stored, FluidConstants.BUCKET, transaction);
|
||||
// If sneaking, we require exact extraction (can be tested on cauldrons)
|
||||
boolean requireExact = context.getPlayer() != null && context.getPlayer().isSneaking();
|
||||
|
||||
if (!requireExact || extracted == FluidConstants.BUCKET) {
|
||||
transaction.commit();
|
||||
return ActionResult.success(context.getWorld().isClient());
|
||||
}
|
||||
}
|
||||
|
||||
return ActionResult.FAIL;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockEntityProvider;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Material;
|
||||
import net.minecraft.block.ShapeContext;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.block.entity.BlockEntityType;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.shape.VoxelShape;
|
||||
import net.minecraft.util.shape.VoxelShapes;
|
||||
import net.minecraft.world.BlockView;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class FluidChuteBlock extends Block implements BlockEntityProvider {
|
||||
public FluidChuteBlock() {
|
||||
super(Settings.of(Material.METAL));
|
||||
}
|
||||
|
||||
private static final VoxelShape SHAPE = VoxelShapes.cuboid(
|
||||
3 / 16f, 0, 3 / 16f, 13 / 16f, 1, 13 / 16f
|
||||
);
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new FluidChuteBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
|
||||
return (w, p, s, be) -> ((FluidChuteBlockEntity) be).tick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
|
||||
return SHAPE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants;
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
|
||||
public class FluidChuteBlockEntity extends BlockEntity {
|
||||
private int tickCounter = 0;
|
||||
|
||||
public FluidChuteBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(FluidTransferTest.FLUID_CHUTE_TYPE, pos, state);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public void tick() {
|
||||
if (!world.isClient() && tickCounter++ % 20 == 0) {
|
||||
Storage<FluidVariant> top = FluidStorage.SIDED.find(world, pos.offset(Direction.UP), Direction.DOWN);
|
||||
Storage<FluidVariant> bottom = FluidStorage.SIDED.find(world, pos.offset(Direction.DOWN), Direction.UP);
|
||||
|
||||
if (top != null && bottom != null) {
|
||||
StorageUtil.move(top, bottom, fluid -> true, FluidConstants.BUCKET, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* 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.fluid;
|
||||
|
||||
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.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.fluid.base.SingleFluidStorage;
|
||||
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);
|
||||
|
||||
testFluidStorage();
|
||||
testTransactionExceptions();
|
||||
}
|
||||
|
||||
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;
|
||||
private static int markDirtyCount = 0;
|
||||
|
||||
private static SingleFluidStorage createWaterStorage() {
|
||||
return new SingleFluidStorage() {
|
||||
@Override
|
||||
protected long getCapacity(FluidVariant fluidVariant) {
|
||||
return BUCKET * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canInsert(FluidVariant fluidVariant) {
|
||||
return fluidVariant.isOf(Fluids.WATER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void markDirty() {
|
||||
markDirtyCount++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static {
|
||||
NbtCompound tag = new NbtCompound();
|
||||
tag.putInt("test", 1);
|
||||
TAGGED_WATER = FluidVariant.of(Fluids.WATER, tag);
|
||||
TAGGED_WATER_2 = FluidVariant.of(Fluids.WATER, tag);
|
||||
WATER = FluidVariant.of(Fluids.WATER);
|
||||
LAVA = FluidVariant.of(Fluids.LAVA);
|
||||
}
|
||||
|
||||
private static void testFluidStorage() {
|
||||
SingleFluidStorage waterStorage = createWaterStorage();
|
||||
|
||||
// Test content
|
||||
if (!waterStorage.isResourceBlank()) throw new AssertionError("Should have been blank");
|
||||
|
||||
// Test some insertions
|
||||
try (Transaction tx = Transaction.openOuter()) {
|
||||
// Should not allow lava (canInsert returns false)
|
||||
if (waterStorage.insert(LAVA, BUCKET, tx) != 0) throw new AssertionError("Lava inserted");
|
||||
// Should allow insert
|
||||
if (waterStorage.insert(TAGGED_WATER, BUCKET, tx) != BUCKET) throw new AssertionError("Tagged water insert 1 failed");
|
||||
// Variants are different, should not allow insert
|
||||
if (waterStorage.insert(WATER, BUCKET, tx) != 0) throw new AssertionError("Water inserted");
|
||||
// Should allow insert again even if the variant is different cause they are equal
|
||||
if (waterStorage.insert(TAGGED_WATER_2, BUCKET, tx) != BUCKET) throw new AssertionError("Tagged water insert 2 failed");
|
||||
// Should not allow further insertion because the storage is full
|
||||
if (waterStorage.insert(TAGGED_WATER, BUCKET, tx) != 0) throw new AssertionError("Storage full, yet something was inserted");
|
||||
// Should allow extraction
|
||||
if (waterStorage.extract(TAGGED_WATER_2, BUCKET, tx) != BUCKET) throw new AssertionError("Extraction failed");
|
||||
// Re-insert
|
||||
if (waterStorage.insert(TAGGED_WATER_2, BUCKET, tx) != BUCKET) throw new AssertionError("Tagged water insert 3 failed");
|
||||
// Test contents
|
||||
if (waterStorage.getAmount() != BUCKET * 2 || !waterStorage.getResource().equals(TAGGED_WATER_2)) throw new AssertionError("Contents are wrong");
|
||||
// No commit -> will abort
|
||||
}
|
||||
|
||||
// Test content again to make sure the rollback worked as expected
|
||||
if (!waterStorage.isResourceBlank()) throw new AssertionError("Should have been blank");
|
||||
|
||||
// Test highly nested commit
|
||||
try (Transaction tx = Transaction.openOuter()) {
|
||||
if (waterStorage.getAmount() != 0) throw new AssertionError("Initial amount is wrong");
|
||||
if (waterStorage.insert(WATER, BUCKET, tx) != BUCKET) throw new AssertionError("Water insertion failed");
|
||||
|
||||
try (Transaction nested1 = tx.openNested()) {
|
||||
try (Transaction nested2 = nested1.openNested()) {
|
||||
if (waterStorage.insert(WATER, BUCKET, nested2) != BUCKET) throw new AssertionError("Nested insertion failed");
|
||||
if (waterStorage.getAmount() != 2 * BUCKET) throw new AssertionError("Two buckets have been inserted");
|
||||
nested2.commit();
|
||||
}
|
||||
|
||||
if (waterStorage.getAmount() != 2 * BUCKET) throw new AssertionError("Nested no 1 was committed, so we should still have two buckets");
|
||||
nested1.commit();
|
||||
}
|
||||
|
||||
if (waterStorage.getAmount() != 2 * BUCKET) throw new AssertionError("Nested no 1 was committed, so we should still have two buckets");
|
||||
}
|
||||
|
||||
if (waterStorage.getAmount() != 0) throw new AssertionError("Amount should have been reverted to zero");
|
||||
|
||||
// Test nested commit to make sure it behaves as expected
|
||||
|
||||
// Without outer commit
|
||||
insertWaterWithNesting(waterStorage, false);
|
||||
if (waterStorage.getAmount() != 0) throw new AssertionError("Amount should have been reverted to zero");
|
||||
if (markDirtyCount != 0) throw new AssertionError("Nothing should have called markDirty() yet (no outer commit)");
|
||||
|
||||
// With outer commit
|
||||
insertWaterWithNesting(waterStorage, true);
|
||||
if (waterStorage.getAmount() != 2 * BUCKET) throw new AssertionError("Outer was committed, so we should still have two buckets");
|
||||
if (markDirtyCount != 1) throw new AssertionError("markDirty() should have been called exactyl once.");
|
||||
}
|
||||
|
||||
private static void insertWaterWithNesting(SingleFluidStorage waterStorage, boolean doOuterCommit) {
|
||||
try (Transaction tx = Transaction.openOuter()) {
|
||||
if (waterStorage.getAmount() != 0) throw new AssertionError("Initial amount is wrong");
|
||||
if (waterStorage.insert(WATER, BUCKET, tx) != BUCKET) throw new AssertionError("Water insertion failed");
|
||||
|
||||
try (Transaction nested = tx.openNested()) {
|
||||
if (waterStorage.insert(WATER, BUCKET, nested) != BUCKET) throw new AssertionError("Nested insertion failed");
|
||||
if (waterStorage.getAmount() != 2 * BUCKET) throw new AssertionError("Two buckets have been inserted");
|
||||
nested.commit();
|
||||
}
|
||||
|
||||
if (waterStorage.getAmount() != 2 * BUCKET) throw new AssertionError("Nested was committed, so we should still have two buckets");
|
||||
|
||||
if (doOuterCommit) {
|
||||
tx.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "fabric-transfer-api-v1-testmod:block/fluid_chute"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "fabric-transfer-api-v1-testmod:block/infinite_lava_source"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "fabric-transfer-api-v1-testmod:block/infinite_water_source"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"credit": "Made with Blockbench",
|
||||
"textures": {
|
||||
"0": "fabric-transfer-api-v1-testmod:block/fluid_chute",
|
||||
"particle": "fabric-transfer-api-v1-testmod:block/fluid_chute"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [3, 0, 3],
|
||||
"to": [13, 16, 13],
|
||||
"faces": {
|
||||
"north": {"uv": [0, 0, 10, 16], "texture": "#0"},
|
||||
"east": {"uv": [0, 0, 10, 16], "texture": "#0"},
|
||||
"south": {"uv": [0, 0, 10, 16], "texture": "#0"},
|
||||
"west": {"uv": [0, 0, 10, 16], "texture": "#0"},
|
||||
"up": {"uv": [6, 0, 16, 10], "texture": "#0"},
|
||||
"down": {"uv": [6, 0, 16, 10], "texture": "#0"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "fabric-transfer-api-v1-testmod:block/infinite_lava_source"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "fabric-transfer-api-v1-testmod:block/infinite_water_source"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "minecraft:item/handheld",
|
||||
"textures": {
|
||||
"layer0": "fabric-transfer-api-v1-testmod:item/extract_stick"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "fabric-transfer-api-v1-testmod:block/fluid_chute"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "fabric-transfer-api-v1-testmod:block/infinite_lava_source"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "fabric-transfer-api-v1-testmod:block/infinite_water_source"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 158 B |
Binary file not shown.
After Width: | Height: | Size: 747 B |
Binary file not shown.
After Width: | Height: | Size: 732 B |
Binary file not shown.
After Width: | Height: | Size: 783 B |
16
fabric-transfer-api-v1/src/testmod/resources/fabric.mod.json
Normal file
16
fabric-transfer-api-v1/src/testmod/resources/fabric.mod.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-transfer-api-v1-testmod",
|
||||
"name": "Fabric Transfer API (v1) Test Mod",
|
||||
"version": "1.0.0",
|
||||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabric-transfer-api-v1": "*"
|
||||
},
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"net.fabricmc.fabric.test.transfer.fluid.FluidTransferTest"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -55,3 +55,4 @@ include 'fabric-structure-api-v1'
|
|||
include 'fabric-tag-extensions-v0'
|
||||
include 'fabric-textures-v0'
|
||||
include 'fabric-tool-attribute-api-v1'
|
||||
include 'fabric-transfer-api-v1'
|
||||
|
|
Loading…
Reference in a new issue