1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-30 05:12:58 +00:00

Don't propagate redstone when blink/label changes

Historically, computers tracked whether any world-visible state
(on/off/blinking, label and redstone outputs) had changed with a single
"has changed" flag. While this is simple to use, this has the curious
side effect of that term.setCursorBlink() or os.setComputerLabel() would
cause a block update!

This isn't really a problem in practice - it just means slightly more
block updates. However, the redstone propagation sometimes causes the
computer to invalidate/recheck peripherals, which masks several other
(yet unfixed) bugs.
This commit is contained in:
Jonathan Coates
2024-03-06 18:56:40 +00:00
parent 6e374579a4
commit d38b1da974
12 changed files with 115 additions and 77 deletions

View File

@@ -113,16 +113,13 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
return InteractionResult.PASS;
}
public void neighborChanged(BlockPos neighbour) {
updateInputAt(neighbour);
}
protected void serverTick() {
if (getLevel().isClientSide) return;
if (computerID < 0 && !startOn) return; // Don't tick if we don't need a computer!
var computer = createServerComputer();
// Update any peripherals that have changed.
if (invalidSides != 0) {
for (var direction : DirectionUtil.FACINGS) {
if ((invalidSides & (1 << direction.ordinal())) != 0) refreshPeripheral(computer, direction);
@@ -139,16 +136,30 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
fresh = false;
computerID = computer.getID();
label = computer.getLabel();
on = computer.isOn();
// Update the block state if needed. We don't fire a block update intentionally,
// as this only really is needed on the client side.
// If the on state has changed, mark as as dirty.
var newOn = computer.isOn();
if (on != newOn) {
on = newOn;
setChanged();
}
// If the label has changed, mark as dirty and sync to client.
var newLabel = computer.getLabel();
if (!Objects.equals(label, newLabel)) {
label = newLabel;
BlockEntityHelpers.updateBlock(this);
}
// Update the block state if needed.
updateBlockState(computer.getState());
// TODO: This should ideally be split up into label/id/on (which should save NBT and sync to client) and
// redstone (which should update outputs)
if (computer.hasOutputChanged()) updateOutput();
var changes = computer.pollAndResetChanges();
if (changes != 0) {
for (var direction : DirectionUtil.FACINGS) {
if ((changes & (1 << remapToLocalSide(direction).ordinal())) != 0) updateRedstoneTo(direction);
}
}
}
protected abstract void updateBlockState(ComputerState newState);
@@ -198,11 +209,15 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
return localSide;
}
private void updateRedstoneInputs(ServerComputer computer) {
var pos = getBlockPos();
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, pos.relative(dir));
}
/**
* Update the redstone input on a particular side.
* <p>
* This is called <em>immediately</em> when a neighbouring block changes (see {@link #neighborChanged(BlockPos)}).
*
* @param computer The current server computer.
* @param dir The direction to update in.
* @param targetPos The position of the adjacent block, equal to {@code getBlockPos().offset(dir)}.
*/
private void updateRedstoneInput(ServerComputer computer, Direction dir, BlockPos targetPos) {
var offsetSide = dir.getOpposite();
var localDir = remapToLocalSide(dir);
@@ -211,6 +226,15 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
computer.setBundledRedstoneInput(localDir, BundledRedstone.getOutput(getLevel(), targetPos, offsetSide));
}
/**
* Update the peripheral on a particular side.
* <p>
* This is called from {@link #serverTick()}, after a peripheral has been marked as invalid (such as in
* {@link #neighborChanged(BlockPos)})
*
* @param computer The current server computer.
* @param dir The direction to update in.
*/
private void refreshPeripheral(ServerComputer computer, Direction dir) {
invalidSides &= ~(1 << dir.ordinal());
@@ -243,7 +267,18 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
}
}
private void updateInputAt(BlockPos neighbour) {
/**
* Called when a neighbour block changes.
* <p>
* This finds the side the neighbour block is on, and updates the inputs accordingly.
* <p>
* We do <strong>NOT</strong> update the peripheral immediately. Blocks and block entities are sometimes
* inconsistent at the point where an update is received, and so we instead just mark that side as dirty (see
* {@link #invalidSides}) and refresh it {@linkplain #serverTick() next tick}.
*
* @param neighbour The position of the neighbour block.
*/
public void neighborChanged(BlockPos neighbour) {
var computer = getServerComputer();
if (computer == null) return;
@@ -258,22 +293,28 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
// handle this incorrectly.
updateRedstoneInputs(computer);
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, getBlockPos().relative(dir));
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
}
/**
* Update the block's state and propagate redstone output.
* Update outputs in a specific direction.
*
* @param direction The direction to propagate outputs in.
*/
public void updateOutput() {
BlockEntityHelpers.updateBlock(this);
for (var dir : DirectionUtil.FACINGS) RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
protected void updateRedstoneTo(Direction direction) {
RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), direction);
var computer = getServerComputer();
if (computer != null) updateRedstoneInputs(computer);
if (computer != null) updateRedstoneInput(computer, direction, getBlockPos().relative(direction));
}
protected abstract ServerComputer createComputer(int id);
/**
* Update all redstone outputs.
*/
public void updateRedstone() {
for (var dir : DirectionUtil.FACINGS) updateRedstoneTo(dir);
}
@Override
public final int getComputerID() {
@@ -331,6 +372,8 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
return computer;
}
protected abstract ServerComputer createComputer(int id);
@Nullable
public ServerComputer getServerComputer() {
return getLevel().isClientSide || getLevel().getServer() == null ? null : ServerContext.get(getLevel().getServer()).registry().get(instanceID);

View File

@@ -51,7 +51,7 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
protected void updateBlockState(ComputerState newState) {
var existing = getBlockState();
if (existing.getValue(ComputerBlock.STATE) != newState) {
getLevel().setBlock(getBlockPos(), existing.setValue(ComputerBlock.STATE, newState), 3);
getLevel().setBlock(getBlockPos(), existing.setValue(ComputerBlock.STATE, newState), ComputerBlock.UPDATE_CLIENTS);
}
}

View File

@@ -42,7 +42,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
private final NetworkedTerminal terminal;
private final AtomicBoolean terminalChanged = new AtomicBoolean(false);
private boolean changedLastFrame;
private int ticksSincePing;
public ServerComputer(
@@ -96,10 +95,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
public void tickServer() {
ticksSincePing++;
computer.tick();
changedLastFrame = computer.pollAndResetChanged();
if (terminalChanged.getAndSet(false)) onTerminalChanged();
}
@@ -119,8 +115,8 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
return ticksSincePing > 100;
}
public boolean hasOutputChanged() {
return changedLastFrame;
public int pollAndResetChanges() {
return computer.pollAndResetChanges();
}
public int register() {
@@ -167,7 +163,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
}
public ComputerState getState() {
if (!isOn()) return ComputerState.OFF;
if (!computer.isOn()) return ComputerState.OFF;
return computer.isBlinking() ? ComputerState.BLINKING : ComputerState.ON;
}

View File

@@ -127,7 +127,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
item = new ItemStack(ModRegistry.Items.CABLE.get());
}
world.setBlock(pos, correctConnections(world, pos, newState), 3);
world.setBlockAndUpdate(pos, correctConnections(world, pos, newState));
cable.modemChanged();
cable.connectionsChanged();

View File

@@ -30,7 +30,7 @@ public abstract class CableBlockItem extends BlockItem {
// TODO: Check entity collision.
if (!state.canSurvive(world, pos)) return false;
world.setBlock(pos, state, 3);
world.setBlockAndUpdate(pos, state);
var soundType = state.getBlock().getSoundType(state);
world.playSound(null, pos, soundType.getPlaceSound(), SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F);

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
@@ -37,7 +38,10 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
private ItemStack stack = ItemStack.EMPTY;
private int lightColour = -1;
private boolean lightChanged = false;
// The state the previous tick, used to determine if the state needs to be sent to the client.
private int oldLightColour = -1;
private @Nullable ComputerState oldComputerState;
private final Set<ServerPlayer> tracking = new HashSet<>();
@@ -82,10 +86,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
@Override
public void setLight(int colour) {
if (colour < 0 || colour > 0xFFFFFF) colour = -1;
if (lightColour == colour) return;
lightColour = colour;
lightChanged = true;
}
@Override
@@ -156,9 +157,11 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
tracking.removeIf(player -> !player.isAlive() || player.level() != getLevel());
// And now find any new players, add them to the tracking list, and broadcast state where appropriate.
var sendState = hasOutputChanged() || lightChanged;
lightChanged = false;
if (sendState) {
var state = getState();
if (oldLightColour != lightColour || oldComputerState != state) {
oldComputerState = state;
oldLightColour = lightColour;
// Broadcast the state to all players
tracking.addAll(getLevel().players());
ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), tracking);

View File

@@ -186,7 +186,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
if (dir.getAxis() == Direction.Axis.Y) dir = Direction.NORTH;
level.setBlockAndUpdate(worldPosition, getBlockState().setValue(TurtleBlock.FACING, dir));
updateOutput();
updateRedstone();
updateInputsImmediately();
onTileEntityChange();

View File

@@ -296,7 +296,7 @@ public class TurtleBrain implements TurtleAccessInternal {
oldWorld.removeBlock(oldPos, false);
// Make sure everybody knows about it
newTurtle.updateOutput();
newTurtle.updateRedstone();
newTurtle.updateInputsImmediately();
return true;
}