diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/common/block/redstone/RedstoneWirePowerCalculations.java b/common/src/main/java/net/caffeinemc/mods/lithium/common/block/redstone/RedstoneWirePowerCalculations.java new file mode 100644 index 000000000..baca252e7 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/common/block/redstone/RedstoneWirePowerCalculations.java @@ -0,0 +1,222 @@ +package net.caffeinemc.mods.lithium.common.block.redstone; + +import net.caffeinemc.mods.lithium.common.util.DirectionConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.redstone.RedstoneWireEvaluator; + +/** + * Optimizing redstone dust is tricky, but even more so if you wish to preserve behavior + * perfectly. There are two big reasons redstone wire is laggy: + *
+ * - It updates recursively. Each wire updates its power level in isolation, rather than + * in the context of the network it is a part of. This means each wire in a network + * could check and update its power level over half a dozen times. This also leads to + * way more shape and block updates than necessary. + *
+ * - It emits copious amounts of duplicate and redundant shape and block updates. While + * the recursive updates are largely to blame, even a single state change leads to 18 + * redundant block updates and up to 16 redundant shape updates. + * + *

+ * Unfortunately fixing either of these aspects can be detected in-game, even if it is + * through obscure mechanics. Removing redundant block updates can be detected with + * something as simple as a redstone wire on a trapdoor, while removing the recursive + * updates can be detected with locational setups that rely on a specific block update + * order. + * + *

+ * What we can optimize, however, are the power calculations. In vanilla, these are split + * into two parts: + *
+ * - Power from non-wire components. + *
+ * - Power from other redstone wires. + * + *

+ * For the default evaluator, we can combine the two to reduce calls to Level.getBlockState + * and BlockState.isRedstoneConductor as well as calls to BlockState.getSignal and + * BlockState.getDirectSignal. We can avoid calling those last two methods on redstone wires + * altogether, since we know they should return 0. + *
+ * These changes can lead to a mspt reduction of up to 20% on top of Lithium's other + * performance improvements. + * + *

+ * The experimental evaluator actually uses the wire-power and non-wire-power separately, + * so we can't combine them. We can, however, optimize each individually, with the same + * principles. + * + * @author Space Walker + */ +public class RedstoneWirePowerCalculations { + + private static final int MIN = 0; // smallest possible power value + private static final int MAX = 15; // largest possible power value + private static final int MAX_WIRE = MAX - 1; // largest possible power a wire can receive from another wire + + private static Block wireBlock; + private static RedstoneWireEvaluator evaluator; + private static boolean ignoreWires = false; + private static boolean ignoreNonWires = false; + + public static int getNeighborBlockSignal(Block wireBlock, RedstoneWireEvaluator evaluator, Level level, BlockPos pos) { + ignoreWires = true; + int signal = getNeighborSignal(wireBlock, evaluator, level, pos); + ignoreWires = false; + + return signal; + } + + public static int getNeighborWireSignal(Block wireBlock, RedstoneWireEvaluator evaluator, Level level, BlockPos pos) { + ignoreNonWires = true; + int signal = getNeighborSignal(wireBlock, evaluator, level, pos); + ignoreNonWires = false; + + return signal; + } + + /** + * Calculate the redstone power a wire at the given location receives from the + * blocks around it. + */ + public static int getNeighborSignal(Block wireBlock, RedstoneWireEvaluator evaluator, Level level, BlockPos pos) { + RedstoneWirePowerCalculations.wireBlock = wireBlock; + RedstoneWirePowerCalculations.evaluator = evaluator; + + int signal = MIN; + LevelChunk chunk = level.getChunkAt(pos); + + if (!ignoreNonWires) { + for (Direction dir : DirectionConstants.VERTICAL) { + BlockPos side = pos.relative(dir); + BlockState neighbor = chunk.getBlockState(side); + + // Wires do not accept power from other wires directly above or below them, + // so those can be ignored. Similarly, if there is air directly above or + // below a wire, it does not receive any power from that direction. + if (!neighbor.isAir() && !neighbor.is(wireBlock)) { + signal = Math.max(signal, getSignalFromVertical(level, side, neighbor, dir)); + + if (signal >= MAX) { + return MAX; + } + } + } + } + + boolean checkWiresAbove = false; + + if (!ignoreWires) { + // In vanilla this check is done up to 4 times. + BlockPos above = pos.above(); + checkWiresAbove = !chunk.getBlockState(above).isRedstoneConductor(level, above); + } + + for (Direction dir : DirectionConstants.HORIZONTAL) { + signal = Math.max(signal, getSignalFromSide(level, pos.relative(dir), dir, checkWiresAbove)); + + if (signal >= MAX) { + return MAX; + } + } + + return signal; + } + + /** + * Calculate the redstone power a wire receives from a block above or below it. + * We do these positions separately because there are no wire connections + * vertically. This simplifies the calculations a little. + */ + private static int getSignalFromVertical(Level level, BlockPos pos, BlockState state, Direction toDir) { + int signal = state.getSignal(level, pos, toDir); + + if (signal >= MAX) { + return MAX; + } + + if (state.isRedstoneConductor(level, pos)) { + return Math.max(signal, getDirectSignalTo(level, pos, toDir.getOpposite())); + } + + return signal; + } + + /** + * Calculate the redstone power a wire receives from blocks next to it. + */ + private static int getSignalFromSide(Level level, BlockPos pos, Direction toDir, boolean checkWiresAbove) { + LevelChunk chunk = level.getChunkAt(pos); + BlockState state = chunk.getBlockState(pos); + + if (state.is(wireBlock)) { + return ignoreWires ? MIN : evaluator.getWireSignal(pos, state) - 1; + } + + int signal = MIN; + + if (!ignoreNonWires) { + signal = state.getSignal(level, pos, toDir); + + if (signal >= MAX) { + return MAX; + } + } + + if (state.isRedstoneConductor(level, pos)) { + if (!ignoreNonWires) { + signal = Math.max(signal, getDirectSignalTo(level, pos, toDir.getOpposite())); + + if (signal >= MAX) { + return MAX; + } + } + if (!ignoreWires && checkWiresAbove && signal < MAX_WIRE) { + BlockPos above = pos.above(); + BlockState aboveState = chunk.getBlockState(above); + + if (aboveState.is(wireBlock)) { + signal = Math.max(signal, evaluator.getWireSignal(above, aboveState) - 1); + } + } + } else if (!ignoreWires && signal < MAX_WIRE) { + BlockPos below = pos.below(); + BlockState belowState = chunk.getBlockState(below); + + if (belowState.is(wireBlock)) { + signal = Math.max(signal, evaluator.getWireSignal(below, belowState) - 1); + } + } + + return signal; + } + + /** + * Calculate the strong power a block receives from the blocks around it. + */ + private static int getDirectSignalTo(Level level, BlockPos pos, Direction ignore) { + int signal = MIN; + + for (Direction dir : DirectionConstants.ALL) { + if (dir != ignore) { + BlockPos side = pos.relative(dir); + BlockState neighbor = level.getBlockState(side); + + if (!neighbor.isAir() && !neighbor.is(wireBlock)) { + signal = Math.max(signal, neighbor.getDirectSignal(level, side, dir)); + + if (signal >= MAX) { + return MAX; + } + } + } + } + + return signal; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/DefaultRedstoneWireEvaluatorMixin.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/DefaultRedstoneWireEvaluatorMixin.java new file mode 100644 index 000000000..a11464755 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/DefaultRedstoneWireEvaluatorMixin.java @@ -0,0 +1,31 @@ +package net.caffeinemc.mods.lithium.mixin.block.redstone_wire; + +import net.caffeinemc.mods.lithium.common.block.redstone.RedstoneWirePowerCalculations; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.RedStoneWireBlock; +import net.minecraft.world.level.redstone.DefaultRedstoneWireEvaluator; +import net.minecraft.world.level.redstone.RedstoneWireEvaluator; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(DefaultRedstoneWireEvaluator.class) +public abstract class DefaultRedstoneWireEvaluatorMixin extends RedstoneWireEvaluator { + + private DefaultRedstoneWireEvaluatorMixin(RedStoneWireBlock wireBlock) { + super(wireBlock); + } + + @Inject( + method = "calculateTargetStrength(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)I", + cancellable = true, + at = @At( + value = "HEAD" + ) + ) + private void calculateTargetStrengthFaster(Level level, BlockPos pos, CallbackInfoReturnable cir) { + cir.setReturnValue(RedstoneWirePowerCalculations.getNeighborSignal(this.wireBlock, this, level, pos)); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/RedStoneWireBlockMixin.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/RedStoneWireBlockMixin.java index 2c4434c2f..b5ff375b1 100644 --- a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/RedStoneWireBlockMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/RedStoneWireBlockMixin.java @@ -1,63 +1,24 @@ package net.caffeinemc.mods.lithium.mixin.block.redstone_wire; -import net.caffeinemc.mods.lithium.common.util.DirectionConstants; +import net.caffeinemc.mods.lithium.common.block.redstone.RedstoneWirePowerCalculations; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.RedStoneWireBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.redstone.RedstoneWireEvaluator; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -/** - * Optimizing redstone dust is tricky, but even more so if you wish to preserve behavior - * perfectly. There are two big reasons redstone wire is laggy: - *
- * - It updates recursively. Each wire updates its power level in isolation, rather than - * in the context of the network it is a part of. This means each wire in a network - * could check and update its power level over half a dozen times. This also leads to - * way more shape and block updates than necessary. - *
- * - It emits copious amounts of duplicate and redundant shape and block updates. While - * the recursive updates are largely to blame, even a single state change leads to 18 - * redundant block updates and up to 16 redundant shape updates. - * - *

- * Unfortunately fixing either of these aspects can be detected in-game, even if it is - * through obscure mechanics. Removing redundant block updates can be detected with - * something as simple as a redstone wire on a trapdoor, while removing the recursive - * updates can be detected with locational setups that rely on a specific block update - * order. - * - *

- * What we can optimize, however, are the power calculations. In vanilla, these are split - * into two parts: - *
- * - Power from non-wire components. - *
- * - Power from other redstone wires. - *
- * We can combine the two to reduce calls to World.getBlockState and BlockState.isSolidBlock - * as well as calls to BlockState.getWeakRedstonePower and BlockState.getStrongRedstonePower. - * We can avoid calling those last two methods on redstone wires altogether, since we know - * they should return 0. - *
- * These changes can lead to a mspt reduction of up to 30% on top of Lithium's other - * performance improvements. - * - * @author Space Walker - */ @Mixin(RedStoneWireBlock.class) public class RedStoneWireBlockMixin extends Block { - private static final int MIN = 0; // smallest possible power value - private static final int MAX = 15; // largest possible power value - private static final int MAX_WIRE = MAX - 1; // largest possible power a wire can receive from another wire + @Shadow + @Final + private RedstoneWireEvaluator evaluator; public RedStoneWireBlockMixin(Properties settings) { super(settings); @@ -70,133 +31,7 @@ public RedStoneWireBlockMixin(Properties settings) { value = "HEAD" ) ) - private void getReceivedPowerFaster(Level world, BlockPos pos, CallbackInfoReturnable cir) { - cir.setReturnValue(this.getReceivedPower(world, pos)); - } - - /** - * Calculate the redstone power a wire at the given location receives from the - * blocks around it. - */ - private int getReceivedPower(Level world, BlockPos pos) { - LevelChunk chunk = world.getChunkAt(pos); - int power = MIN; - - for (Direction dir : DirectionConstants.VERTICAL) { - BlockPos side = pos.relative(dir); - BlockState neighbor = chunk.getBlockState(side); - - // Wires do not accept power from other wires directly above or below them, - // so those can be ignored. Similarly, if there is air directly above or - // below a wire, it does not receive any power from that direction. - if (!neighbor.isAir() && !neighbor.is(this)) { - power = Math.max(power, this.getPowerFromVertical(world, side, neighbor, dir)); - - if (power >= MAX) { - return MAX; - } - } - } - - // In vanilla this check is done up to 4 times. - BlockPos up = pos.above(); - boolean checkWiresAbove = !chunk.getBlockState(up).isRedstoneConductor(world, up); - - for (Direction dir : DirectionConstants.HORIZONTAL) { - power = Math.max(power, this.getPowerFromSide(world, pos.relative(dir), dir, checkWiresAbove)); - - if (power >= MAX) { - return MAX; - } - } - - return power; - } - - /** - * Calculate the redstone power a wire receives from a block above or below it. - * We do these positions separately because there are no wire connections - * vertically. This simplifies the calculations a little. - */ - private int getPowerFromVertical(Level world, BlockPos pos, BlockState state, Direction toDir) { - int power = state.getSignal(world, pos, toDir); - - if (power >= MAX) { - return MAX; - } - - if (state.isRedstoneConductor(world, pos)) { - return Math.max(power, this.getStrongPowerTo(world, pos, toDir.getOpposite())); - } - - return power; - } - - /** - * Calculate the redstone power a wire receives from blocks next to it. - */ - private int getPowerFromSide(Level world, BlockPos pos, Direction toDir, boolean checkWiresAbove) { - LevelChunk chunk = world.getChunkAt(pos); - BlockState state = chunk.getBlockState(pos); - - if (state.is(this)) { - return state.getValue(BlockStateProperties.POWER) - 1; - } - - int power = state.getSignal(world, pos, toDir); - - if (power >= MAX) { - return MAX; - } - - if (state.isRedstoneConductor(world, pos)) { - power = Math.max(power, this.getStrongPowerTo(world, pos, toDir.getOpposite())); - - if (power >= MAX) { - return MAX; - } - - if (checkWiresAbove && power < MAX_WIRE) { - BlockPos up = pos.above(); - BlockState aboveState = chunk.getBlockState(up); - - if (aboveState.is(this)) { - power = Math.max(power, aboveState.getValue(BlockStateProperties.POWER) - 1); - } - } - } else if (power < MAX_WIRE) { - BlockPos down = pos.below(); - BlockState belowState = chunk.getBlockState(down); - - if (belowState.is(this)) { - power = Math.max(power, belowState.getValue(BlockStateProperties.POWER) - 1); - } - } - - return power; - } - - /** - * Calculate the strong power a block receives from the blocks around it. - */ - private int getStrongPowerTo(Level world, BlockPos pos, Direction ignore) { - int power = MIN; - - for (Direction dir : DirectionConstants.ALL) { - if (dir != ignore) { - BlockPos side = pos.relative(dir); - BlockState neighbor = world.getBlockState(side); - - if (!neighbor.isAir() && !neighbor.is(this)) { - power = Math.max(power, neighbor.getDirectSignal(world, side, dir)); - - if (power >= MAX) { - return MAX; - } - } - } - } - - return power; + private void getBlockSignalFaster(Level world, BlockPos pos, CallbackInfoReturnable cir) { + cir.setReturnValue(RedstoneWirePowerCalculations.getNeighborBlockSignal((Block) (Object) this, this.evaluator, world, pos)); } } diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/RedstoneWireEvaluatorMixin.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/RedstoneWireEvaluatorMixin.java new file mode 100644 index 000000000..fdcdc8d3f --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block/redstone_wire/RedstoneWireEvaluatorMixin.java @@ -0,0 +1,32 @@ +package net.caffeinemc.mods.lithium.mixin.block.redstone_wire; + +import net.caffeinemc.mods.lithium.common.block.redstone.RedstoneWirePowerCalculations; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.RedStoneWireBlock; +import net.minecraft.world.level.redstone.RedstoneWireEvaluator; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(RedstoneWireEvaluator.class) +public class RedstoneWireEvaluatorMixin { + + @Shadow + @Final + private RedStoneWireBlock wireBlock; + + @Inject( + method = "getIncomingWireSignal(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)I", + cancellable = true, + at = @At( + value = "HEAD" + ) + ) + private void getIncomingWireSignalFaster(Level level, BlockPos pos, CallbackInfoReturnable cir) { + cir.setReturnValue(RedstoneWirePowerCalculations.getNeighborWireSignal(this.wireBlock, (RedstoneWireEvaluator) (Object) this, level, pos)); + } +} diff --git a/common/src/main/resources/lithium.accesswidener b/common/src/main/resources/lithium.accesswidener index 140fd9fae..6b010881d 100644 --- a/common/src/main/resources/lithium.accesswidener +++ b/common/src/main/resources/lithium.accesswidener @@ -20,5 +20,6 @@ accessible method net/minecraft/world/level/chunk/PalettedContainer$Strategy