diff --git a/patches/minecraft/net/minecraft/item/ItemStack.java.patch b/patches/minecraft/net/minecraft/item/ItemStack.java.patch index f71324d267..9e633a34b4 100644 --- a/patches/minecraft/net/minecraft/item/ItemStack.java.patch +++ b/patches/minecraft/net/minecraft/item/ItemStack.java.patch @@ -19,33 +19,29 @@ private static final Logger field_199558_c = LogManager.getLogger(); public static final ItemStack field_190927_a = new ItemStack((Item)null); public static final DecimalFormat field_111284_a = Util.func_200696_a(new DecimalFormat("#.##"), (p_234699_0_) -> { -@@ -109,14 +_,19 @@ +@@ -109,10 +_,15 @@ p_i231596_3_.ifPresent(this::func_77982_d); } - public ItemStack(IItemProvider p_i48204_1_, int p_i48204_2_) { + public ItemStack(IItemProvider p_i48204_1_, int p_i48204_2_) { this(p_i48204_1_, p_i48204_2_, (CompoundNBT) null); } + public ItemStack(IItemProvider p_i48204_1_, int p_i48204_2_, @Nullable CompoundNBT capNBT) { -+ super(ItemStack.class); ++ super(ItemStack.class, true); + this.capNBT = capNBT; this.field_151002_e = p_i48204_1_ == null ? null : p_i48204_1_.func_199767_j(); + this.delegate = p_i48204_1_ == null ? null : p_i48204_1_.func_199767_j().delegate; this.field_77994_a = p_i48204_2_; - if (this.field_151002_e != null && this.field_151002_e.func_77645_m()) { ++ this.forgeInit(); + if (this.field_151002_e != null && this.field_151002_e.isDamageable(this)) { this.func_196085_b(this.func_77952_i()); } - this.func_190923_F(); -+ this.forgeInit(); - } - - private void func_190923_F() { -@@ -125,18 +_,23 @@ +@@ -125,14 +_,18 @@ } private ItemStack(CompoundNBT p_i47263_1_) { -+ super(ItemStack.class); ++ super(ItemStack.class, true); + this.capNBT = p_i47263_1_.func_74764_b("ForgeCaps") ? p_i47263_1_.func_74775_l("ForgeCaps") : null; + Item rawItem = this.field_151002_e = Registry.field_212630_s.func_82594_a(new ResourceLocation(p_i47263_1_.func_74779_i("id"))); @@ -55,17 +51,13 @@ this.field_77990_d = p_i47263_1_.func_74775_l("tag"); this.func_77973_b().func_179215_a(p_i47263_1_); } - +- - if (this.func_77973_b().func_77645_m()) { ++ this.forgeInit(); + if (this.func_77973_b().isDamageable(this)) { this.func_196085_b(this.func_77952_i()); } - this.func_190923_F(); -+ this.forgeInit(); - } - - public static ItemStack func_199557_a(CompoundNBT p_199557_0_) { @@ -167,10 +_,19 @@ } @@ -220,7 +212,7 @@ return multimap; } -@@ -952,6 +_,24 @@ +@@ -952,6 +_,23 @@ public boolean func_222117_E() { return this.func_77973_b().func_219971_r(); @@ -238,8 +230,7 @@ + */ + private void forgeInit() { + if (this.delegate != null) { -+ net.minecraftforge.common.capabilities.ICapabilityProvider provider = field_151002_e.initCapabilities(this, this.capNBT); -+ this.gatherCapabilities(provider); ++ this.gatherCapabilities(() -> field_151002_e.initCapabilities(this, this.capNBT)); + if (this.capNBT != null) deserializeCaps(this.capNBT); + } } diff --git a/src/main/java/net/minecraftforge/common/capabilities/CapabilityProvider.java b/src/main/java/net/minecraftforge/common/capabilities/CapabilityProvider.java index 4d68e3a782..16ebf453c6 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/CapabilityProvider.java +++ b/src/main/java/net/minecraftforge/common/capabilities/CapabilityProvider.java @@ -22,35 +22,80 @@ package net.minecraftforge.common.capabilities; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; +import com.google.common.annotations.VisibleForTesting; import mcp.MethodsReturnNonnullByDefault; import net.minecraft.nbt.CompoundNBT; import net.minecraft.util.Direction; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.event.ForgeEventFactory; +import java.util.function.Supplier; @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault public abstract class CapabilityProvider> implements ICapabilityProvider { + @VisibleForTesting + static boolean SUPPORTS_LAZY_CAPABILITIES = true; + private final @Nonnull Class baseClass; private @Nullable CapabilityDispatcher capabilities; private boolean valid = true; + private boolean isLazy = false; + private Supplier lazyParentSupplier = null; + private CompoundNBT lazyData = null; + private boolean initialized = false; + protected CapabilityProvider(Class baseClass) { - this.baseClass = baseClass; + this(baseClass, false); } - protected final void gatherCapabilities() { gatherCapabilities(null); } + protected CapabilityProvider(final Class baseClass, final boolean isLazy) + { + this.baseClass = baseClass; + this.isLazy = SUPPORTS_LAZY_CAPABILITIES && isLazy; + } + + protected final void gatherCapabilities() + { + gatherCapabilities(() -> null); + } protected final void gatherCapabilities(@Nullable ICapabilityProvider parent) + { + gatherCapabilities(() -> parent); + } + + protected final void gatherCapabilities(@Nullable Supplier parent) + { + if (isLazy && !initialized) + { + lazyParentSupplier = parent == null ? () -> null : parent; + return; + } + + doGatherCapabilities(parent == null ? null : parent.get()); + } + + private void doGatherCapabilities(@Nullable ICapabilityProvider parent) { this.capabilities = ForgeEventFactory.gatherCapabilities(baseClass, this, parent); + this.initialized = true; } protected final @Nullable CapabilityDispatcher getCapabilities() { - return this.capabilities; + if(isLazy && !initialized) + { + doGatherCapabilities(lazyParentSupplier == null ? null : lazyParentSupplier.get()); + if (lazyData != null) + { + deserializeCaps(lazyData); + } + } + + return capabilities; } public final boolean areCapsCompatible(CapabilityProvider other) @@ -80,6 +125,11 @@ public abstract class CapabilityProvider> implem protected final @Nullable CompoundNBT serializeCaps() { + if(isLazy && !initialized) + { + return lazyData; + } + final CapabilityDispatcher disp = getCapabilities(); if (disp != null) { @@ -90,6 +140,12 @@ public abstract class CapabilityProvider> implem protected final void deserializeCaps(CompoundNBT tag) { + if (isLazy && !initialized) + { + lazyData = tag; + return; + } + final CapabilityDispatcher disp = getCapabilities(); if (disp != null) { diff --git a/src/test/java/net/minecraftforge/common/capabilities/LazyCapabilitiesOnItemsTest.java b/src/test/java/net/minecraftforge/common/capabilities/LazyCapabilitiesOnItemsTest.java new file mode 100644 index 0000000000..627f60d24f --- /dev/null +++ b/src/test/java/net/minecraftforge/common/capabilities/LazyCapabilitiesOnItemsTest.java @@ -0,0 +1,190 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.capabilities; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.Lists; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.Direction; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.common.util.NonNullLazy; +import net.minecraftforge.common.util.TablePrinter; +import net.minecraftforge.common.util.TextTable; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; +import net.minecraftforge.fluids.capability.templates.FluidHandlerItemStackSimple; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static net.minecraftforge.common.util.TextTable.column; + +@Mod(LazyCapabilitiesOnItemsTest.MOD_ID) +@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, modid = LazyCapabilitiesOnItemsTest.MOD_ID) +public class LazyCapabilitiesOnItemsTest +{ + public static final String MOD_ID = "lazy_capabilities_on_items"; + + private static final Logger LOGGER = LogManager.getLogger(); + private static final boolean ENABLED = true; + private static final int SAMPLE_SIZE = 100000; + + private static final List WARMUP_RESULTS = new ArrayList<>(SAMPLE_SIZE * 4); + private static final List NO_CAPS_DISABLED_RESULTS = new ArrayList<>(SAMPLE_SIZE); + private static final List WITH_CAPS_DISABLED_RESULTS = new ArrayList<>(SAMPLE_SIZE); + private static final List NO_CAPS_ENABLED_RESULTS = new ArrayList<>(SAMPLE_SIZE); + private static final List WITH_CAPS_ENABLED_RESULTS = new ArrayList<>(SAMPLE_SIZE); + + public LazyCapabilitiesOnItemsTest() + { + if (!ENABLED) + return; + + final IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); + modBus.addListener(this::onCommonSetup); + } + + private void onCommonSetup(FMLCommonSetupEvent event) + { + try + { + final Field supportsFlagField = CapabilityProvider.class.getDeclaredField("SUPPORTS_LAZY_CAPABILITIES"); + supportsFlagField.setAccessible(true); + supportsFlagField.set(null, false); + + final Stopwatch timer = Stopwatch.createUnstarted(); + final IEventBus bus = MinecraftForge.EVENT_BUS; + + final ResourceLocation testCapId = new ResourceLocation(MOD_ID, "test"); + final Consumer> capAttachmentHandler = e -> { + //Example capability we make everything a bucket :D + e.addCapability(testCapId, new FluidHandlerItemStackSimple(e.getObject(), SAMPLE_SIZE)); + }; + + //Warmup: + for (int i = 0; i < (SAMPLE_SIZE); i++) + { + WARMUP_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + + ///First test: SAMPLE_SIZE itemstacks which do not have a capability attached. + timer.start(); + for (int i = 0; i < SAMPLE_SIZE; i++) + { + NO_CAPS_DISABLED_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + timer.stop(); + + final long simpleNoCapsLazyDisabledElapsed = timer.elapsed(TimeUnit.MICROSECONDS); + timer.reset(); + + ///Second test: SAMPLE_SIZE itemstacks with a capability attached. + bus.addGenericListener(ItemStack.class, capAttachmentHandler); + //Warmup: + for (int i = 0; i < (SAMPLE_SIZE); i++) + { + WARMUP_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + + timer.start(); + for (int i = 0; i < SAMPLE_SIZE; i++) + { + WITH_CAPS_DISABLED_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + timer.stop(); + + final long withCapsLazyDisabledElapsed = timer.elapsed(TimeUnit.MICROSECONDS); + timer.reset(); + bus.unregister(capAttachmentHandler); + + ///Third test: SAMPLE_SIZE itemstacks which do not have a capability attached. + supportsFlagField.set(null, true); + //Warmup: + for (int i = 0; i < (SAMPLE_SIZE); i++) + { + WARMUP_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + + timer.start(); + for (int i = 0; i < SAMPLE_SIZE; i++) + { + NO_CAPS_ENABLED_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + timer.stop(); + + final long simpleNoCapsLazyEnabledElapsed = timer.elapsed(TimeUnit.MICROSECONDS); + timer.reset(); + + ///Fourth test: SAMPLE_SIZE itemstacks with a capability attached. + bus.addGenericListener(ItemStack.class, capAttachmentHandler); + //Warmup: + for (int i = 0; i < (SAMPLE_SIZE); i++) + { + WARMUP_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + + timer.start(); + for (int i = 0; i < SAMPLE_SIZE; i++) + { + WITH_CAPS_ENABLED_RESULTS.add(new ItemStack(Items.WATER_BUCKET)); + } + timer.stop(); + + final long withCapsLazyEnabledElapsed = timer.elapsed(TimeUnit.MICROSECONDS); + timer.reset(); + bus.unregister(capAttachmentHandler); + + TextTable table = new TextTable(Lists.newArrayList( + column("Test type", TextTable.Alignment.LEFT), + column("Total time", TextTable.Alignment.CENTER) + )); + + table.add("Lazy: Disabled / Caps: None", simpleNoCapsLazyDisabledElapsed + " ms."); + table.add("Lazy: Disabled / Caps: One", withCapsLazyDisabledElapsed + " ms."); + table.add("Lazy: Enabled / Caps: None", simpleNoCapsLazyEnabledElapsed + " ms."); + table.add("Lazy: Enabled / Caps: One", withCapsLazyEnabledElapsed + " ms."); + + final String[] resultData = table.build("\n").split("\n"); + for (final String line : resultData) + { + LOGGER.warn(line); + } + } + catch (NoSuchFieldException | IllegalAccessException e) + { + LOGGER.error("Failed to run capabilities on items test!"); + throw new IllegalStateException(e); + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 5624bd166f..79ce324427 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -138,6 +138,8 @@ license="LGPL v2.1" modId="dimension_settings_test" [[mods]] modId="player_attack_knockback_test" +[[mods]] + modId="lazy_capabilities_on_items" [[mods]] modId="send_datapacks_to_client" [[mods]]