-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update redstone wire optimizations and fix #581 #583
Closed
SpaceWalkerRS
wants to merge
1
commit into
CaffeineMC:develop
from
SpaceWalkerRS:update-redstone-wire-optimizations
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
222 changes: 222 additions & 0 deletions
222
...java/net/caffeinemc/mods/lithium/common/block/redstone/RedstoneWirePowerCalculations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: | ||
* <br> | ||
* - 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. | ||
* <br> | ||
* - 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. | ||
* | ||
* <p> | ||
* 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. | ||
* | ||
* <p> | ||
* What we can optimize, however, are the power calculations. In vanilla, these are split | ||
* into two parts: | ||
* <br> | ||
* - Power from non-wire components. | ||
* <br> | ||
* - Power from other redstone wires. | ||
* | ||
* <p> | ||
* 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. | ||
* <br> | ||
* These changes can lead to a mspt reduction of up to 20% on top of Lithium's other | ||
* performance improvements. | ||
* | ||
* <p> | ||
* 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; | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
.../caffeinemc/mods/lithium/mixin/block/redstone_wire/DefaultRedstoneWireEvaluatorMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Integer> cir) { | ||
cir.setReturnValue(RedstoneWirePowerCalculations.getNeighborSignal(this.wireBlock, this, level, pos)); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am concerned about these static fields, mod compatibility with threading mods will definitely suffer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think converting them to a local variable should be possible if I am not misunderstanding the code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that'd work