AppleSkin

AppleSkin

236M Downloads

[forge-mc1.18-2.3.0] Infinite loop still happening when selecting rotten flesh in the hotbar

TigerWalts opened this issue ยท 6 comments

commented

Previous issue - #153

Confirmed in Visual VM that it's still getting stuck in the getEstimatedHealthIncrement(int, float, float) method.
I also decompiled the class file to make sure it had the previous fix applied.

Triggered by a stack of 2 minecraft:rotten_flesh just as before.

Values in the Player Data file:

  • foodExhaustionLevel 1.401881
  • foodLevel 17
  • foodSaturationLevel 0.2000003
  • foodTickTimer 0
  • Health 17
Modlist

absentbydesign-1.18.1-1.6.1.jar
angelring-1.18-1.5.0.jar
animal_feeding_trough-1.0.3+1.18.1-forge.jar
appleskin-forge-mc1.18-2.3.0.jar
architectury-3.7.31.jar
artifacts-1.18.1-4.0.3.jar
balm-2.4.3+0.jar
BetterDungeons-Forge-1.18.1-1.0.1.jar
bonsaitrees3-3.0.4.jar
caelus-forge-1.18.1-3.0.0.2.jar
Clumps-forge-1.18.1-8.0.0+5.jar
collective-1.18.1-4.7.jar
ColossalChests-1.18.1-1.8.2.jar
configured-1.5.3-1.18.1.jar
connectedglass-1.1.1-mc1.18.jar
Controlling-forge-1.18.1-9.0+15.jar
create-mc1.18.1_v0.4d.jar
curioofundying-forge-1.18-5.3.0.0.jar
curios-forge-1.18.1-5.0.6.2.jar
curiouselytra-forge-1.18.1-5.0.1.0.jar
CyclopsCore-1.18.1-1.13.0.jar
DoggyTalents-1.18.1-2.4.2.jar
doubledoors_1.18.1-3.2.jar
DrawersTooltip-1.18.1-forge-4.1.0.jar
EasierSleeping-1.18.1-2.1.0.jar
elevatorid-1.18-1.8.3.jar
EnchantingInfuser-v3.1.1-1.18.1-Forge.jar
EnchantmentDescriptions-Forge-1.18.1-9.0.11.jar
expandability-5.0.0-forge.jar
ExplorersCompass-1.18.1-1.1.2-forge.jar
fixedanvilrepaircost_1.18.1-1.7.jar
flatbedrock-1.4.1-build.8+mc1.18.1.jar
flywheel-forge-1.18-0.6.0.jar
forgivingvoid-forge-1.18.1-6.0.1.jar
forgottenrecipes-forge-1.18.1-1.0.0.jar
ftb-chunks-forge-1801.3.4-build.127.jar
ftb-library-forge-1801.3.5-build.109.jar
ftb-teams-forge-1801.2.5-build.48.jar
ftb-ultimine-forge-1801.3.2-build.52.jar
furniture-7.0.0-pre28-1.18.1.jar
gravestone-1.18.1-1.0.2.jar
harvest-1.18.1-1.1.jar
inventorysorter-1.18-19.0.0.jar
itemcollectors-1.1.4-mc1.18.jar
jei-1.18.1-9.2.1.69.jar
justmobheads_1.18.1-5.3.jar
libnonymous-2.0.5.jar
lootr-1.18-0.1.15.50.jar
mcw-paintings-1.0.2-mc1.18.1.jar
minicoal-1.18.1-1.0.0.jar
MouseTweaks-forge-mc1.18-2.21.jar
OreTree-1.18.1-0.2.5.jar
pitg-1.18.1-2.0.2.jar
PuzzlesLib-v3.2.1-1.18.1-Forge.jar
RapidLeafDecay-1.18.1-2.0.0.jar
ShulkerTooltip-1.10.jar
shutupexperimentalsettings-1.0.4-1.18+.jar
SimpleStorageNetwork-1.18.1-1.5.4.jar
simplylight-1.18.1-1.4.0-build.26.jar
Snad-1.18.1-1.21.12.11a.jar
StorageDrawers-1.18.1-10.1.1.jar
supermartijn642configlib-1.0.9-mc1.18.jar
supermartijn642corelib-1.0.16b-mc1.18.jar
theoneprobe-1.18-5.0.4.jar
time-in-a-bottle-2.1.0-mc1.18.1.jar
torchmaster-18.0.3-beta.jar
upgradedcore-1.18.1-3.1.0.0-release.jar
upgradednetherite-1.18.1-4.1.0.0-release.jar
vanillaplustools-1.18-1.0.jar
VisualWorkbench-v3.1.0-1.18.1-Forge.jar
voicechat-forge-1.18.1-2.2.26.jar
waystones-forge-1.18.1-9.0.4.jar
weirdinggadget-1.18.1-2.2.11.jar
YungsApi-1.18.1-Forge-24.jar

commented

Thanks for all the info, really need to get this one figured out.

EDIT: More info:

2x rotten_flesh stack has the following FoodValues:

hunger = 4
saturationModifier = 0.1
getSaturationIncrement = 0.8

Which means with the player data in the OP, I believe getEstimatedHealthIncrement(int, float, float) is being called with:

foodLevel = Math.min(17 + 4, 20) = 20
saturationLevel = Math.min(0.2000003 + 0.8, 20) = 1.0000003
exhaustionLevel = 1.401881
commented

Okay, I think I've figured out the problem:

Inside this conditional, we increment exhaustionLevel by limitedSaturationLevel:

if (foodLevel >= 20 && saturationLevel > 0)
{
// fast regen health
float limitedSaturationLevel = Math.min(saturationLevel, exhaustionForRegen);
health += limitedSaturationLevel / exhaustionForRegen;
exhaustionLevel += limitedSaturationLevel;
}

It's possible, though, for limitedSaturationLevel to be extremely small, i.e. 1.4E-45, and, due to how floating point works, that can be small enough that exhaustionLevel doesn't actually change representation from the increment (exhaustionLevel + limitedSaturationLevel == exhaustionLevel). Therefore, we enter an infinite loop since exhaustionLevel is no longer changing at all.

Vanilla Minecraft doesn't run into this problem because it just does 1 'iteration' of our getEstimatedHealthIncrement in its FoodStats.update function, so if exhaustion isn't incremented in a given update it's not a problem (future player actions will change saturation/exhaustion which changes how update works).

I believe we can fix this by treating close-to-zero exhaustion/saturation levels as zero, or otherwise ensuring that we handle close-to-zero values in a safer way.

commented

I think limitedSaturationLevel because it's too small that the number of loops has become very bigger(pseudo-infinite loops)

commented

I think limitedSaturationLevel because it's too small that the number of loops has become very bigger(pseudo-infinite loops)

It's a real infinite loop. Here's a test case that reproduces it:

    @Test
    public void testEstimatedHealth() {
        int foodLevel = 20;
        float saturationLevel = Float.intBitsToFloat(0x0000001);
        float exhaustionLevel = Float.intBitsToFloat(0x1000000);
        float healthIncrement = FoodHelper.getEstimatedHealthIncrement(foodLevel, saturationLevel, exhaustionLevel);
    }
commented

If saturationLevel is 1000 times smaller than exhaustionForRegen, there will be no obvious variant of recovered health. We can ignore it

commented

Will be fixed by c44304e (Fabric) and b563b91 (Forge)

Testing this also made me realize that there's the potential for a massive improvement in the worst-case performance of this function. The worst case number of iterations of the main loop of the function has gone from millions down to around 18.

Thanks again for the detailed report of this bug @TigerWalts!