Ванильная листва

Версия Minecraft
1.12.2
API
Forge
434
41
110
Доброго времени суток, копался я в ванильном коде 1.12.2 и нашел вот такое фигню, как бы это по-мягче сказать ...

Java:
public abstract class BlockLeaves extends Block implements net.minecraftforge.common.IShearable {
    ... 2-3 строки
    int[] surroundings;
    ... 20-30 строк

    public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand) {
        ... 5-7 строк
       
        if (this.surroundings == null) {
            this.surroundings = new int[32768];
        }
        ... 60-70 строк
    }

    ... 100-120 строк
}
Это что получается .. 128 мб уходит на расчет, какие блоки листвы должны опадать, а какие нет? До кучи поле не статик, и у класса не 1 наследник ...
Если кто знает патчи/ядра/моды/что-то еще, где это пофикшено - дайте ссылку на исходники ресурса (может я плохо гуглил и не нашел)

Очень не хочется самому исправлять это, математика с индексами выглядит сложной и страшной, искренне надеюсь, что за 10 лет кто-то да исправил это

BlockLeaves:
public abstract class BlockLeaves extends Block implements net.minecraftforge.common.IShearable {
    public static final PropertyBool DECAYABLE = PropertyBool.create("decayable");
    public static final PropertyBool CHECK_DECAY = PropertyBool.create("check_decay");
    protected boolean leavesFancy;
    int[] surroundings;

    public BlockLeaves() {
        super(Material.LEAVES);
        this.setTickRandomly(true);
        this.setCreativeTab(CreativeTabs.DECORATIONS);
        this.setHardness(0.2F);
        this.setLightOpacity(1);
        this.setSoundType(SoundType.PLANT);
    }

   
    @Override
    public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
        int k = pos.getX();
        int l = pos.getY();
        int i1 = pos.getZ();

        if (worldIn.isAreaLoaded(new BlockPos(k - 2, l - 2, i1 - 2), new BlockPos(k + 2, l + 2, i1 + 2))) {
            for (int j1 = -1; j1 <= 1; ++j1) {
                for (int k1 = -1; k1 <= 1; ++k1) {
                    for (int l1 = -1; l1 <= 1; ++l1) {
                        BlockPos blockpos = pos.add(j1, k1, l1);
                        IBlockState iblockstate = worldIn.getBlockState(blockpos);

                        if (iblockstate.getBlock().isLeaves(iblockstate, worldIn, blockpos)) {
                            iblockstate.getBlock().beginLeavesDecay(iblockstate, worldIn, blockpos);
                        }
                    }
                }
            }
        }
    }

    public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand) {
        if (!worldIn.isRemote) {
            if (state.getValue(CHECK_DECAY) && state.getValue(DECAYABLE)) {

                int k = pos.getX();
                int l = pos.getY();
                int i1 = pos.getZ();

                if (this.surroundings == null) {
                    this.surroundings = new int[32768];
                }

                // Forge: prevent decaying leaves from updating neighbors and loading unloaded chunks
                if (!worldIn.isAreaLoaded(pos, 1)) return;
                // Forge: extend range from 5 to 6 to account for neighbor checks in world.markAndNotifyBlock -> world.updateObservingBlocksAt
                if (worldIn.isAreaLoaded(pos, 6)) {
                    BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

                    for (int i2 = -4; i2 <= 4; ++i2) {
                        for (int j2 = -4; j2 <= 4; ++j2) {
                            for (int k2 = -4; k2 <= 4; ++k2) {
                                IBlockState iblockstate = worldIn.getBlockState(blockpos$mutableblockpos.setPos(k + i2, l + j2, i1 + k2));
                                Block block = iblockstate.getBlock();

                                if (!block.canSustainLeaves(iblockstate, worldIn, blockpos$mutableblockpos.setPos(k + i2, l + j2, i1 + k2))) {
                                    if (block.isLeaves(iblockstate, worldIn, blockpos$mutableblockpos.setPos(k + i2, l + j2, i1 + k2))) {
                                        this.surroundings[(i2 + 16) * 1024 + (j2 + 16) * 32 + k2 + 16] = -2;
                                    } else {
                                        this.surroundings[(i2 + 16) * 1024 + (j2 + 16) * 32 + k2 + 16] = -1;
                                    }
                                } else {
                                    this.surroundings[(i2 + 16) * 1024 + (j2 + 16) * 32 + k2 + 16] = 0;
                                }
                            }
                        }
                    }

                    for (int i3 = 1; i3 <= 4; ++i3) {
                        for (int j3 = -4; j3 <= 4; ++j3) {
                            for (int k3 = -4; k3 <= 4; ++k3) {
                                for (int l3 = -4; l3 <= 4; ++l3) {
                                    if (this.surroundings[(j3 + 16) * 1024 + (k3 + 16) * 32 + l3 + 16] == i3 - 1) {
                                        if (this.surroundings[(j3 + 16 - 1) * 1024 + (k3 + 16) * 32 + l3 + 16] == -2) {
                                            this.surroundings[(j3 + 16 - 1) * 1024 + (k3 + 16) * 32 + l3 + 16] = i3;
                                        }

                                        if (this.surroundings[(j3 + 16 + 1) * 1024 + (k3 + 16) * 32 + l3 + 16] == -2) {
                                            this.surroundings[(j3 + 16 + 1) * 1024 + (k3 + 16) * 32 + l3 + 16] = i3;
                                        }

                                        if (this.surroundings[(j3 + 16) * 1024 + (k3 + 16 - 1) * 32 + l3 + 16] == -2) {
                                            this.surroundings[(j3 + 16) * 1024 + (k3 + 16 - 1) * 32 + l3 + 16] = i3;
                                        }

                                        if (this.surroundings[(j3 + 16) * 1024 + (k3 + 16 + 1) * 32 + l3 + 16] == -2) {
                                            this.surroundings[(j3 + 16) * 1024 + (k3 + 16 + 1) * 32 + l3 + 16] = i3;
                                        }

                                        if (this.surroundings[(j3 + 16) * 1024 + (k3 + 16) * 32 + (l3 + 16 - 1)] == -2) {
                                            this.surroundings[(j3 + 16) * 1024 + (k3 + 16) * 32 + (l3 + 16 - 1)] = i3;
                                        }

                                        if (this.surroundings[(j3 + 16) * 1024 + (k3 + 16) * 32 + l3 + 16 + 1] == -2) {
                                            this.surroundings[(j3 + 16) * 1024 + (k3 + 16) * 32 + l3 + 16 + 1] = i3;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                int l2 = this.surroundings[16912];

                if (l2 >= 0) {
                    worldIn.setBlockState(pos, state.withProperty(CHECK_DECAY, Boolean.valueOf(false)), 4);
                } else {
                    this.destroy(worldIn, pos);
                }
            }
        }
    }

   
    private void destroy(World worldIn, BlockPos pos) {
        this.dropBlockAsItem(worldIn, pos, worldIn.getBlockState(pos), 0);
        worldIn.setBlockToAir(pos);
    }
   

    @SideOnly(Side.CLIENT)
    public void randomDisplayTick(IBlockState stateIn, World worldIn, BlockPos pos, Random rand) {
        if (worldIn.isRainingAt(pos.up()) && !worldIn.getBlockState(pos.down()).isTopSolid() && rand.nextInt(15) == 1) {
            double d0 = (double)((float)pos.getX() + rand.nextFloat());
            double d1 = (double)pos.getY() - 0.05D;
            double d2 = (double)((float)pos.getZ() + rand.nextFloat());
            worldIn.spawnParticle(EnumParticleTypes.DRIP_WATER, d0, d1, d2, 0.0D, 0.0D, 0.0D);
        }
    }

   
    public int quantityDropped(Random random) {
        return random.nextInt(20) == 0 ? 1 : 0;
    }

   
    public Item getItemDropped(IBlockState state, Random rand, int fortune) {
        return Item.getItemFromBlock(Blocks.SAPLING);
    }

   
    public void dropBlockAsItemWithChance(World worldIn, BlockPos pos, IBlockState state, float chance, int fortune) {
        super.dropBlockAsItemWithChance(worldIn, pos, state, chance, fortune);
    }

    protected void dropApple(World worldIn, BlockPos pos, IBlockState state, int chance) {}

   
    protected int getSaplingDropChance(IBlockState state) {
        return 20;
    }

   
    public boolean isOpaqueCube(IBlockState state) {
        return !this.leavesFancy;
    }

   
    @SideOnly(Side.CLIENT)
    public void setGraphicsLevel(boolean fancy) {
        this.leavesFancy = fancy;
    }

   
    @SideOnly(Side.CLIENT)
    public BlockRenderLayer getRenderLayer() {
        return this.leavesFancy ? BlockRenderLayer.CUTOUT_MIPPED : BlockRenderLayer.SOLID;
    }

   
    public boolean causesSuffocation(IBlockState state)
    {
        return false;
    }

   
    public abstract BlockPlanks.EnumType getWoodType(int meta);

   
    @Override
    public boolean isShearable(ItemStack item, IBlockAccess world, BlockPos pos){ return true; }
   
   
    @Override
    public boolean isLeaves(IBlockState state, IBlockAccess world, BlockPos pos){ return true; }

   
    @Override
    public void beginLeavesDecay(IBlockState state, World world, BlockPos pos) {
        if (!(Boolean)state.getValue(CHECK_DECAY)) {
            world.setBlockState(pos, state.withProperty(CHECK_DECAY, true), 4);
        }
    }

   
    @Override
    public void getDrops(net.minecraft.util.NonNullList<ItemStack> drops, IBlockAccess world, BlockPos pos, IBlockState state, int fortune) {
        Random rand = world instanceof World ? ((World)world).rand : new Random();
        int chance = this.getSaplingDropChance(state);

        if (fortune > 0) {
            chance -= 2 << fortune;
            if (chance < 10) chance = 10;
        }

        if (rand.nextInt(chance) == 0) {
            ItemStack drop = new ItemStack(getItemDropped(state, rand, fortune), 1, damageDropped(state));
            if (!drop.isEmpty())
                drops.add(drop);
        }

        chance = 200;
        if (fortune > 0) {
            chance -= 10 << fortune;
            if (chance < 40) chance = 40;
        }

        this.captureDrops(true);
        if (world instanceof World)
            this.dropApple((World)world, pos, state, chance); // Dammet mojang
        drops.addAll(this.captureDrops(false));
    }


    @SideOnly(Side.CLIENT)
    public boolean shouldSideBeRendered(IBlockState blockState, IBlockAccess blockAccess, BlockPos pos, EnumFacing side) {
        return !this.leavesFancy && blockAccess.getBlockState(pos.offset(side)).getBlock() == this ? false : super.shouldSideBeRendered(blockState, blockAccess, pos, side);
    }
}
 
Последнее редактирование:
Решение
Это что получается .. 128 мб уходит на расчет
128 КБ, а не МБ.
32 768 * 32 (в битах) = 1 048 576
/ 8 (в байтах) = 131 072
/ 1024 (в килобайтах) = 128

И, насколько я понимаю, там происходит расчёт четырёхмерной матрицы (позиция блока и его decay) в радиусе 8 блоков для каждого блока листвы. И то далеко не факт, что правильно понял.
Но выглядит паршиво, да.
На версиях повыше сделано гораздо опрятнее, и твои замечания учтены ;D

Тоже ванилльный класс LeavesBlock (mojmaps):
package net.minecraft.world.level.block;

import java.util.OptionalInt;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import...
1,371
112
241
Это что получается .. 128 мб уходит на расчет
128 КБ, а не МБ.
32 768 * 32 (в битах) = 1 048 576
/ 8 (в байтах) = 131 072
/ 1024 (в килобайтах) = 128

И, насколько я понимаю, там происходит расчёт четырёхмерной матрицы (позиция блока и его decay) в радиусе 8 блоков для каждого блока листвы. И то далеко не факт, что правильно понял.
Но выглядит паршиво, да.
На версиях повыше сделано гораздо опрятнее, и твои замечания учтены ;D

Тоже ванилльный класс LeavesBlock (mojmaps):
package net.minecraft.world.level.block;

import java.util.OptionalInt;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.ParticleUtils;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;

public class LeavesBlock extends Block implements SimpleWaterloggedBlock, net.minecraftforge.common.IForgeShearable {
   public static final int DECAY_DISTANCE = 7;
   public static final IntegerProperty DISTANCE = BlockStateProperties.DISTANCE;
   public static final BooleanProperty PERSISTENT = BlockStateProperties.PERSISTENT;
   public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
   private static final int TICK_DELAY = 1;

   public LeavesBlock(BlockBehaviour.Properties p_54422_) {
      super(p_54422_);
      this.registerDefaultState(this.stateDefinition.any().setValue(DISTANCE, Integer.valueOf(7)).setValue(PERSISTENT, Boolean.valueOf(false)).setValue(WATERLOGGED, Boolean.valueOf(false)));
   }

   public VoxelShape getBlockSupportShape(BlockState p_54456_, BlockGetter p_54457_, BlockPos p_54458_) {
      return Shapes.empty();
   }

   public boolean isRandomlyTicking(BlockState p_54449_) {
      return p_54449_.getValue(DISTANCE) == 7 && !p_54449_.getValue(PERSISTENT);
   }

   public void randomTick(BlockState p_221379_, ServerLevel p_221380_, BlockPos p_221381_, RandomSource p_221382_) {
      if (this.decaying(p_221379_)) {
         dropResources(p_221379_, p_221380_, p_221381_);
         p_221380_.removeBlock(p_221381_, false);
      }

   }

   protected boolean decaying(BlockState p_221386_) {
      return !p_221386_.getValue(PERSISTENT) && p_221386_.getValue(DISTANCE) == 7;
   }

   public void tick(BlockState p_221369_, ServerLevel p_221370_, BlockPos p_221371_, RandomSource p_221372_) {
      p_221370_.setBlock(p_221371_, updateDistance(p_221369_, p_221370_, p_221371_), 3);
   }

   public int getLightBlock(BlockState p_54460_, BlockGetter p_54461_, BlockPos p_54462_) {
      return 1;
   }

   public BlockState updateShape(BlockState p_54440_, Direction p_54441_, BlockState p_54442_, LevelAccessor p_54443_, BlockPos p_54444_, BlockPos p_54445_) {
      if (p_54440_.getValue(WATERLOGGED)) {
         p_54443_.scheduleTick(p_54444_, Fluids.WATER, Fluids.WATER.getTickDelay(p_54443_));
      }

      int i = getDistanceAt(p_54442_) + 1;
      if (i != 1 || p_54440_.getValue(DISTANCE) != i) {
         p_54443_.scheduleTick(p_54444_, this, 1);
      }

      return p_54440_;
   }

   private static BlockState updateDistance(BlockState p_54436_, LevelAccessor p_54437_, BlockPos p_54438_) {
      int i = 7;
      BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

      for(Direction direction : Direction.values()) {
         blockpos$mutableblockpos.setWithOffset(p_54438_, direction);
         i = Math.min(i, getDistanceAt(p_54437_.getBlockState(blockpos$mutableblockpos)) + 1);
         if (i == 1) {
            break;
         }
      }

      return p_54436_.setValue(DISTANCE, Integer.valueOf(i));
   }

   private static int getDistanceAt(BlockState p_54464_) {
      return getOptionalDistanceAt(p_54464_).orElse(7);
   }

   public static OptionalInt getOptionalDistanceAt(BlockState p_277868_) {
      if (p_277868_.is(BlockTags.LOGS)) {
         return OptionalInt.of(0);
      } else {
         return p_277868_.hasProperty(DISTANCE) ? OptionalInt.of(p_277868_.getValue(DISTANCE)) : OptionalInt.empty();
      }
   }

   public FluidState getFluidState(BlockState p_221384_) {
      return p_221384_.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(p_221384_);
   }

   public void animateTick(BlockState p_221374_, Level p_221375_, BlockPos p_221376_, RandomSource p_221377_) {
      if (p_221375_.isRainingAt(p_221376_.above())) {
         if (p_221377_.nextInt(15) == 1) {
            BlockPos blockpos = p_221376_.below();
            BlockState blockstate = p_221375_.getBlockState(blockpos);
            if (!blockstate.canOcclude() || !blockstate.isFaceSturdy(p_221375_, blockpos, Direction.UP)) {
               ParticleUtils.spawnParticleBelow(p_221375_, p_221376_, p_221377_, ParticleTypes.DRIPPING_WATER);
            }
         }
      }
   }

   protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> p_54447_) {
      p_54447_.add(DISTANCE, PERSISTENT, WATERLOGGED);
   }

   public BlockState getStateForPlacement(BlockPlaceContext p_54424_) {
      FluidState fluidstate = p_54424_.getLevel().getFluidState(p_54424_.getClickedPos());
      BlockState blockstate = this.defaultBlockState().setValue(PERSISTENT, Boolean.valueOf(true)).setValue(WATERLOGGED, Boolean.valueOf(fluidstate.getType() == Fluids.WATER));
      return updateDistance(blockstate, p_54424_.getLevel(), p_54424_.getClickedPos());
   }
}
 
434
41
110
Я бы скорее ржал, если бы реально каждый блок листвы жрал 128 МБ. Этож в лес было бы нельзя зайти без 256 ГБ оперативы и 0,5 ГБ L3 кэша xDD
Так блок а не блокстат - 10 разных блоков листвы - было бы 1гб оперативки константно
 
434
41
110
Решил не менять принципиально алгоритм, только поправить размеры кеша, уместился в 1093 байта, оставлю для потомков, может кому пригодиться

Java:
public static class LeavesCache{
    private final byte[] updates = new byte[1093];

    public int get(int dx, int dy, int dz){
        int cursor = ((dx + 4) << 7) | ((dy + 4) << 3) | ((dz + 4) >> 1);
        int result = updates[cursor];
        if((dz & 1) == 1)return (result >> 4) & 0xF;
        else return (result & 0xF);
    }


    public void set(int dx, int dy, int dz, int value){
        int cursor = ((dx + 4) << 7) | ((dy + 4) << 3) | ((dz + 4) >> 1),
            mask, pre;

        if((dz & 1) == 1){
            mask = 0x0F;
            pre = value << 4;
        } else {
            mask = 0xF0;
            pre = value;
        }
        updates[cursor] = (byte) ((updates[cursor] & mask) | pre);
    }
}
 
Последнее редактирование:
Сверху