Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
230 lines
12 KiB
Java
230 lines
12 KiB
Java
package net.minecraft.world.level.redstone;
|
|
|
|
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
|
import java.util.ArrayDeque;
|
|
import java.util.Deque;
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.debug.DebugSubscriptions;
|
|
import net.minecraft.world.level.BlockGetter;
|
|
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.EnumProperty;
|
|
import net.minecraft.world.level.block.state.properties.Property;
|
|
import net.minecraft.world.level.block.state.properties.RedstoneSide;
|
|
import net.minecraft.world.level.redstone.Orientation;
|
|
import net.minecraft.world.level.redstone.RedstoneWireEvaluator;
|
|
import org.jspecify.annotations.Nullable;
|
|
|
|
// FIX minecraft-0003: companion HashSets for O(1) membership in wiresToTurnOn/Off queues
|
|
// Previously: Deque.contains() O(N) → O(N²) BFS for N-wire networks
|
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
public class ExperimentalRedstoneWireEvaluator
|
|
extends RedstoneWireEvaluator {
|
|
private final Deque<BlockPos> wiresToTurnOff = new ArrayDeque<BlockPos>();
|
|
private final Set<BlockPos> wiresToTurnOffSet = new HashSet<>(); // FIX: O(1) contains
|
|
private final Deque<BlockPos> wiresToTurnOn = new ArrayDeque<BlockPos>();
|
|
private final Set<BlockPos> wiresToTurnOnSet = new HashSet<>(); // FIX: O(1) contains
|
|
private final Object2IntMap<BlockPos> updatedWires = new Object2IntLinkedOpenHashMap();
|
|
|
|
public ExperimentalRedstoneWireEvaluator(RedStoneWireBlock wireBlock) {
|
|
super(wireBlock);
|
|
}
|
|
|
|
public void updatePowerStrength(Level level, BlockPos initialPos, BlockState ignored, @Nullable Orientation orientation, boolean shapeUpdateWiresAroundInitialPosition) {
|
|
Orientation initialOrientation = ExperimentalRedstoneWireEvaluator.getInitialOrientation(level, orientation);
|
|
this.calculateCurrentChanges(level, initialPos, initialOrientation);
|
|
ObjectIterator iterator = this.updatedWires.object2IntEntrySet().iterator();
|
|
boolean initialWire = true;
|
|
while (iterator.hasNext()) {
|
|
Object2IntMap.Entry next = (Object2IntMap.Entry)iterator.next();
|
|
BlockPos pos = (BlockPos)next.getKey();
|
|
int packed = next.getIntValue();
|
|
int newLevel = ExperimentalRedstoneWireEvaluator.unpackPower(packed);
|
|
BlockState state = level.getBlockState(pos);
|
|
if (state.is(this.wireBlock) && !((Integer)state.getValue((Property)RedStoneWireBlock.POWER)).equals(newLevel)) {
|
|
int updateFlags = 2;
|
|
if (!shapeUpdateWiresAroundInitialPosition || !initialWire) {
|
|
updateFlags |= 0x80;
|
|
}
|
|
level.setBlock(pos, (BlockState)state.setValue((Property)RedStoneWireBlock.POWER, (Comparable)Integer.valueOf(newLevel)), updateFlags);
|
|
} else {
|
|
iterator.remove();
|
|
}
|
|
initialWire = false;
|
|
}
|
|
this.causeNeighborUpdates(level);
|
|
}
|
|
|
|
private void causeNeighborUpdates(Level level) {
|
|
this.updatedWires.forEach((wirePos, packed) -> {
|
|
Orientation orientation = ExperimentalRedstoneWireEvaluator.unpackOrientation(packed);
|
|
BlockState state = level.getBlockState(wirePos);
|
|
for (Direction neighborDirection : orientation.getDirections()) {
|
|
if (!ExperimentalRedstoneWireEvaluator.isConnected(state, neighborDirection)) continue;
|
|
BlockPos neighborPos = wirePos.relative(neighborDirection);
|
|
BlockState neighborState = level.getBlockState(neighborPos);
|
|
Orientation neighborOrientation = orientation.withFrontPreserveUp(neighborDirection);
|
|
level.neighborChanged(neighborState, neighborPos, (Block)this.wireBlock, neighborOrientation, false);
|
|
if (!neighborState.isRedstoneConductor((BlockGetter)level, neighborPos)) continue;
|
|
for (Direction direction : neighborOrientation.getDirections()) {
|
|
if (direction == neighborDirection.getOpposite()) continue;
|
|
level.neighborChanged(neighborPos.relative(direction), (Block)this.wireBlock, neighborOrientation.withFrontPreserveUp(direction));
|
|
}
|
|
}
|
|
});
|
|
if (level instanceof ServerLevel serverLevel) {
|
|
if (serverLevel.debugSynchronizers().hasAnySubscriberFor(DebugSubscriptions.REDSTONE_WIRE_ORIENTATIONS)) {
|
|
this.updatedWires.forEach((wirePos, packed) ->
|
|
serverLevel.debugSynchronizers().sendBlockValue(wirePos,
|
|
DebugSubscriptions.REDSTONE_WIRE_ORIENTATIONS,
|
|
ExperimentalRedstoneWireEvaluator.unpackOrientation(packed)));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean isConnected(BlockState state, Direction direction) {
|
|
EnumProperty property = (EnumProperty)RedStoneWireBlock.PROPERTY_BY_DIRECTION.get(direction);
|
|
if (property == null) {
|
|
return direction == Direction.DOWN;
|
|
}
|
|
return ((RedstoneSide)state.getValue((Property)property)).isConnected();
|
|
}
|
|
|
|
private static Orientation getInitialOrientation(Level level, @Nullable Orientation incomingOrigination) {
|
|
Orientation orientation = incomingOrigination != null ? incomingOrigination : Orientation.random((RandomSource)level.getRandom());
|
|
return orientation.withUp(Direction.UP).withSideBias(Orientation.SideBias.LEFT);
|
|
}
|
|
|
|
private void calculateCurrentChanges(Level level, BlockPos initialPosition, Orientation initialOrientation) {
|
|
BlockPos pos;
|
|
BlockState initialState = level.getBlockState(initialPosition);
|
|
if (initialState.is(this.wireBlock)) {
|
|
this.setPower(initialPosition, (Integer)initialState.getValue((Property)RedStoneWireBlock.POWER), initialOrientation);
|
|
this.wiresToTurnOff.add(initialPosition);
|
|
this.wiresToTurnOffSet.add(initialPosition); // FIX
|
|
} else {
|
|
this.propagateChangeToNeighbors(level, initialPosition, 0, initialOrientation, true);
|
|
}
|
|
while (!this.wiresToTurnOff.isEmpty()) {
|
|
int powerToSet;
|
|
int wirePower;
|
|
pos = this.wiresToTurnOff.removeFirst();
|
|
this.wiresToTurnOffSet.remove(pos); // FIX: keep set in sync
|
|
int packed = this.updatedWires.getInt(pos);
|
|
Orientation orientation = ExperimentalRedstoneWireEvaluator.unpackOrientation(packed);
|
|
int oldPower = ExperimentalRedstoneWireEvaluator.unpackPower(packed);
|
|
int blockPower = this.getBlockSignal(level, pos);
|
|
int newPower = Math.max(blockPower, wirePower = this.getIncomingWireSignal(level, pos));
|
|
if (newPower < oldPower) {
|
|
if (blockPower > 0 && !this.wiresToTurnOnSet.contains(pos)) { // FIX: O(1)
|
|
this.wiresToTurnOn.add(pos);
|
|
this.wiresToTurnOnSet.add(pos); // FIX
|
|
}
|
|
powerToSet = 0;
|
|
} else {
|
|
powerToSet = newPower;
|
|
}
|
|
if (powerToSet != oldPower) {
|
|
this.setPower(pos, powerToSet, orientation);
|
|
}
|
|
this.propagateChangeToNeighbors(level, pos, powerToSet, orientation, oldPower > newPower);
|
|
}
|
|
while (!this.wiresToTurnOn.isEmpty()) {
|
|
pos = this.wiresToTurnOn.removeFirst();
|
|
this.wiresToTurnOnSet.remove(pos); // FIX: keep set in sync
|
|
int packed = this.updatedWires.getInt(pos);
|
|
int oldPower = ExperimentalRedstoneWireEvaluator.unpackPower(packed);
|
|
int blockPower = this.getBlockSignal(level, pos);
|
|
int wirePower = this.getIncomingWireSignal(level, pos);
|
|
int newPower = Math.max(blockPower, wirePower);
|
|
Orientation orientation = ExperimentalRedstoneWireEvaluator.unpackOrientation(packed);
|
|
if (newPower > oldPower) {
|
|
this.setPower(pos, newPower, orientation);
|
|
} else if (newPower < oldPower) {
|
|
throw new IllegalStateException("Turning off wire while trying to turn it on. Should not happen.");
|
|
}
|
|
this.propagateChangeToNeighbors(level, pos, newPower, orientation, false);
|
|
}
|
|
}
|
|
|
|
private static int packOrientationAndPower(Orientation orientation, int power) {
|
|
return orientation.getIndex() << 4 | power;
|
|
}
|
|
|
|
private static Orientation unpackOrientation(int packed) {
|
|
return Orientation.fromIndex((int)(packed >> 4));
|
|
}
|
|
|
|
private static int unpackPower(int packed) {
|
|
return packed & 0xF;
|
|
}
|
|
|
|
// FIX: replaced compute() lambda (raw-type incompatible) with explicit get+put
|
|
private void setPower(BlockPos pos, int newPower, Orientation orientation) {
|
|
int existing = this.updatedWires.getOrDefault(pos, -1);
|
|
if (existing == -1) {
|
|
this.updatedWires.put(pos, ExperimentalRedstoneWireEvaluator.packOrientationAndPower(orientation, newPower));
|
|
} else {
|
|
this.updatedWires.put(pos, ExperimentalRedstoneWireEvaluator.packOrientationAndPower(
|
|
ExperimentalRedstoneWireEvaluator.unpackOrientation(existing), newPower));
|
|
}
|
|
}
|
|
|
|
private void propagateChangeToNeighbors(Level level, BlockPos pos, int newPower, Orientation orientation, boolean allowTurningOff) {
|
|
BlockPos offsetPos;
|
|
for (Direction directionHorizontal : orientation.getHorizontalDirections()) {
|
|
offsetPos = pos.relative(directionHorizontal);
|
|
this.enqueueNeighborWire(level, offsetPos, newPower, orientation.withFront(directionHorizontal), allowTurningOff);
|
|
}
|
|
for (Direction directionVertical : orientation.getVerticalDirections()) {
|
|
offsetPos = pos.relative(directionVertical);
|
|
boolean solidBlock = level.getBlockState(offsetPos).isRedstoneConductor((BlockGetter)level, offsetPos);
|
|
for (Direction directionHorizontal : orientation.getHorizontalDirections()) {
|
|
BlockPos neighborWire;
|
|
BlockPos neighbor = pos.relative(directionHorizontal);
|
|
if (directionVertical == Direction.UP && !solidBlock) {
|
|
neighborWire = offsetPos.relative(directionHorizontal);
|
|
this.enqueueNeighborWire(level, neighborWire, newPower, orientation.withFront(directionHorizontal), allowTurningOff);
|
|
continue;
|
|
}
|
|
if (directionVertical != Direction.DOWN || level.getBlockState(neighbor).isRedstoneConductor((BlockGetter)level, neighbor)) continue;
|
|
neighborWire = offsetPos.relative(directionHorizontal);
|
|
this.enqueueNeighborWire(level, neighborWire, newPower, orientation.withFront(directionHorizontal), allowTurningOff);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void enqueueNeighborWire(Level level, BlockPos pos, int newFromPower, Orientation orientation, boolean allowTurningOff) {
|
|
BlockState state = level.getBlockState(pos);
|
|
if (state.is(this.wireBlock)) {
|
|
int toPower = this.getWireSignal(pos, state);
|
|
if (toPower < newFromPower - 1 && !this.wiresToTurnOnSet.contains(pos)) { // FIX: O(1)
|
|
this.wiresToTurnOn.add(pos);
|
|
this.wiresToTurnOnSet.add(pos); // FIX
|
|
this.setPower(pos, toPower, orientation);
|
|
}
|
|
if (allowTurningOff && toPower > newFromPower && !this.wiresToTurnOffSet.contains(pos)) { // FIX: O(1)
|
|
this.wiresToTurnOff.add(pos);
|
|
this.wiresToTurnOffSet.add(pos); // FIX
|
|
this.setPower(pos, toPower, orientation);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected int getWireSignal(BlockPos pos, BlockState state) {
|
|
int packed = this.updatedWires.getOrDefault(pos, -1);
|
|
if (packed != -1) {
|
|
return ExperimentalRedstoneWireEvaluator.unpackPower(packed);
|
|
}
|
|
return super.getWireSignal(pos, state);
|
|
}
|
|
}
|