1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-07-04 11:02:54 +00:00

Invalidate peripherals during the computer's tick instead

- Capability invalidation and tile/block entity changes set a dirty bit
   instead of refetching the peripheral immediately.
 - Then on the block's tick we recompute the peripheral if the dirty bit
   is set.

Fixes #696 and probably fixes #882. Some way towards #893, but not
everything yet.

This is probably going to break things horribly. Let's find out!
This commit is contained in:
Jonathan Coates 2021-11-28 20:03:27 +00:00
parent 9d44f1ca66
commit 298f339376
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
7 changed files with 162 additions and 104 deletions

View File

@ -54,7 +54,7 @@ public abstract class BlockComputerBase<T extends TileComputerBase> extends Bloc
super.onPlace( state, world, pos, oldState, isMoving ); super.onPlace( state, world, pos, oldState, isMoving );
TileEntity tile = world.getBlockEntity( pos ); TileEntity tile = world.getBlockEntity( pos );
if( tile instanceof TileComputerBase ) ((TileComputerBase) tile).updateInput(); if( tile instanceof TileComputerBase ) ((TileComputerBase) tile).updateInputsImmediately( );
} }
@Override @Override

View File

@ -19,9 +19,6 @@ import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.RedstoneUtil; import dan200.computercraft.shared.util.RedstoneUtil;
import joptsimple.internal.Strings; import joptsimple.internal.Strings;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.RedstoneDiodeBlock;
import net.minecraft.block.RedstoneWireBlock;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.container.INamedContainerProvider; import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@ -38,7 +35,6 @@ import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TranslationTextComponent; import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import net.minecraftforge.common.util.NonNullConsumer; import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -57,6 +53,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
private boolean on = false; private boolean on = false;
boolean startOn = false; boolean startOn = false;
private boolean fresh = false; private boolean fresh = false;
private int invalidSides = 0;
private final NonNullConsumer<Object>[] invalidate; private final NonNullConsumer<Object>[] invalidate;
private final ComputerFamily family; private final ComputerFamily family;
@ -71,7 +69,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
NonNullConsumer<Object>[] invalidate = this.invalidate = new NonNullConsumer[6]; NonNullConsumer<Object>[] invalidate = this.invalidate = new NonNullConsumer[6];
for( Direction direction : Direction.values() ) for( Direction direction : Direction.values() )
{ {
invalidate[direction.ordinal()] = o -> updateInput( direction ); int mask = 1 << direction.ordinal();
invalidate[direction.ordinal()] = o -> invalidSides |= mask;
} }
} }
@ -143,45 +142,51 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
@Override @Override
public void onNeighbourChange( @Nonnull BlockPos neighbour ) public void onNeighbourChange( @Nonnull BlockPos neighbour )
{ {
updateInput( neighbour ); updateInputAt( neighbour );
} }
@Override @Override
public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour ) public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour )
{ {
updateInput( neighbour ); updateInputAt( neighbour );
} }
@Override @Override
public void tick() public void tick()
{ {
if( !getLevel().isClientSide ) if( getLevel().isClientSide ) return;
ServerComputer computer = createServerComputer();
if( invalidSides != 0 )
{ {
ServerComputer computer = createServerComputer(); for( Direction direction : DirectionUtil.FACINGS )
if( computer == null ) return;
// If the computer isn't on and should be, then turn it on
if( startOn || (fresh && on) )
{ {
computer.turnOn(); if( (invalidSides & (1 << direction.ordinal())) != 0 ) refreshPeripheral( computer, direction );
startOn = false;
} }
computer.keepAlive();
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.
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();
} }
// If the computer isn't on and should be, then turn it on
if( startOn || (fresh && on) )
{
computer.turnOn();
startOn = false;
}
computer.keepAlive();
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.
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();
} }
protected abstract void updateBlockState( ComputerState newState ); protected abstract void updateBlockState( ComputerState newState );
@ -226,89 +231,80 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
return localSide; return localSide;
} }
private void updateSideInput( ServerComputer computer, Direction dir, BlockPos offset ) private void updateRedstoneInput( @Nonnull ServerComputer computer, Direction dir, BlockPos targetPos )
{ {
Direction offsetSide = dir.getOpposite(); Direction offsetSide = dir.getOpposite();
ComputerSide localDir = remapToLocalSide( dir ); ComputerSide localDir = remapToLocalSide( dir );
computer.setRedstoneInput( localDir, getRedstoneInput( level, offset, dir ) ); computer.setRedstoneInput( localDir, RedstoneUtil.getRedstoneInput( level, targetPos, dir ) );
computer.setBundledRedstoneInput( localDir, BundledRedstone.getOutput( getLevel(), offset, offsetSide ) ); computer.setBundledRedstoneInput( localDir, BundledRedstone.getOutput( getLevel(), targetPos, offsetSide ) );
if( !isPeripheralBlockedOnSide( localDir ) ) }
{
IPeripheral peripheral = Peripherals.getPeripheral( getLevel(), offset, offsetSide, invalidate[dir.ordinal()] ); private void refreshPeripheral( @Nonnull ServerComputer computer, Direction dir )
computer.setPeripheral( localDir, peripheral ); {
} invalidSides &= ~(1 << dir.ordinal());
ComputerSide localDir = remapToLocalSide( dir );
if( isPeripheralBlockedOnSide( localDir ) ) return;
Direction offsetSide = dir.getOpposite();
IPeripheral peripheral = Peripherals.getPeripheral( getLevel(), getBlockPos().relative( dir ), offsetSide, invalidate[dir.ordinal()] );
computer.setPeripheral( localDir, peripheral );
}
public void updateInputsImmediately()
{
ServerComputer computer = getServerComputer();
if( computer != null ) updateInputsImmediately( computer );
} }
/** /**
* Gets the redstone input for an adjacent block. * Update all redstone and peripherals.
* *
* @param world The world we exist in * This should only be really be called when the computer is being ticked (though there are some cases where it
* @param pos The position of the neighbour * won't be), as peripheral scanning requires adjacent tiles to be in a "correct" state - which may not be the case
* @param side The side we are reading from * if they're still updating!
* @return The effective redstone power *
* @see RedstoneDiodeBlock#calculateInputStrength(World, BlockPos, BlockState) * @param computer The current computer instance.
*/ */
protected static int getRedstoneInput( World world, BlockPos pos, Direction side ) private void updateInputsImmediately( @Nonnull ServerComputer computer )
{ {
int power = world.getSignal( pos, side ); BlockPos pos = getBlockPos();
if( power >= 15 ) return power;
BlockState neighbour = world.getBlockState( pos );
return neighbour.getBlock() == Blocks.REDSTONE_WIRE
? Math.max( power, neighbour.getValue( RedstoneWireBlock.POWER ) )
: power;
}
public void updateInput()
{
if( getLevel() == null || getLevel().isClientSide ) return;
// Update all sides
ServerComputer computer = getServerComputer();
if( computer == null ) return;
BlockPos pos = computer.getPosition();
for( Direction dir : DirectionUtil.FACINGS ) for( Direction dir : DirectionUtil.FACINGS )
{ {
updateSideInput( computer, dir, pos.relative( dir ) ); updateRedstoneInput( computer, dir, pos.relative( dir ) );
refreshPeripheral( computer, dir );
} }
} }
private void updateInput( BlockPos neighbour ) private void updateInputAt( @Nonnull BlockPos neighbour )
{ {
if( getLevel() == null || getLevel().isClientSide ) return;
ServerComputer computer = getServerComputer(); ServerComputer computer = getServerComputer();
if( computer == null ) return; if( computer == null ) return;
for( Direction dir : DirectionUtil.FACINGS ) for( Direction dir : DirectionUtil.FACINGS )
{ {
BlockPos offset = worldPosition.relative( dir ); BlockPos offset = getBlockPos().relative( dir );
if( offset.equals( neighbour ) ) if( offset.equals( neighbour ) )
{ {
updateSideInput( computer, dir, offset ); updateRedstoneInput( computer, dir, offset );
invalidSides |= 1 << dir.ordinal();
return; return;
} }
} }
// If the position is not any adjacent one, update all inputs. // If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
updateInput(); // handle this incorrectly.
} BlockPos pos = getBlockPos();
for( Direction dir : DirectionUtil.FACINGS ) updateRedstoneInput( computer, dir, pos.relative( dir ) );
private void updateInput( Direction dir ) invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
{
if( getLevel() == null || getLevel().isClientSide ) return;
ServerComputer computer = getServerComputer();
if( computer == null ) return;
updateSideInput( computer, dir, worldPosition.relative( dir ) );
} }
/**
* Update the block's state and propagate redstone output.
*/
public void updateOutput() public void updateOutput()
{ {
// Update redstone
updateBlock(); updateBlock();
for( Direction dir : DirectionUtil.FACINGS ) for( Direction dir : DirectionUtil.FACINGS )
{ {
@ -358,9 +354,10 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
return family; return family;
} }
@Nonnull
public ServerComputer createServerComputer() public ServerComputer createServerComputer()
{ {
if( getLevel().isClientSide ) return null; if( getLevel().isClientSide ) throw new IllegalStateException( "Cannot access server computer on the client." );
boolean changed = false; boolean changed = false;
if( instanceID < 0 ) if( instanceID < 0 )
@ -368,18 +365,21 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
instanceID = ComputerCraft.serverComputerRegistry.getUnusedInstanceID(); instanceID = ComputerCraft.serverComputerRegistry.getUnusedInstanceID();
changed = true; changed = true;
} }
if( !ComputerCraft.serverComputerRegistry.contains( instanceID ) )
ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instanceID );
if( computer == null )
{ {
ServerComputer computer = createComputer( instanceID, computerID ); computer = createComputer( instanceID, computerID );
ComputerCraft.serverComputerRegistry.add( instanceID, computer ); ComputerCraft.serverComputerRegistry.add( instanceID, computer );
fresh = true; fresh = true;
changed = true; changed = true;
} }
if( changed ) updateInput(); if( changed ) updateInputsImmediately( computer );
return ComputerCraft.serverComputerRegistry.get( instanceID ); return computer;
} }
@Nullable
public ServerComputer getServerComputer() public ServerComputer getServerComputer()
{ {
return getLevel().isClientSide ? null : ComputerCraft.serverComputerRegistry.get( instanceID ); return getLevel().isClientSide ? null : ComputerCraft.serverComputerRegistry.get( instanceID );

View File

@ -77,8 +77,9 @@ public class TileCable extends TileGeneric
} }
} }
private boolean invalidPeripheral;
private boolean peripheralAccessAllowed; private boolean peripheralAccessAllowed;
private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral( this::refreshPeripheral ); private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral( this::queueRefreshPeripheral );
private boolean destroyed = false; private boolean destroyed = false;
@ -239,12 +240,20 @@ public class TileCable extends TileGeneric
if( !level.isClientSide && peripheralAccessAllowed ) if( !level.isClientSide && peripheralAccessAllowed )
{ {
Direction facing = getDirection(); Direction facing = getDirection();
if( getBlockPos().relative( facing ).equals( neighbour ) ) refreshPeripheral(); if( getBlockPos().relative( facing ).equals( neighbour ) ) queueRefreshPeripheral();
} }
} }
private void queueRefreshPeripheral()
{
if( invalidPeripheral ) return;
invalidPeripheral = true;
TickScheduler.schedule( this );
}
private void refreshPeripheral() private void refreshPeripheral()
{ {
invalidPeripheral = false;
if( level != null && !isRemoved() && peripheral.attach( level, getBlockPos(), getDirection() ) ) if( level != null && !isRemoved() && peripheral.attach( level, getBlockPos(), getDirection() ) )
{ {
updateConnectedPeripherals(); updateConnectedPeripherals();
@ -324,6 +333,8 @@ public class TileCable extends TileGeneric
elementCap = CapabilityUtil.invalidate( elementCap ); elementCap = CapabilityUtil.invalidate( elementCap );
} }
if( invalidPeripheral ) refreshPeripheral();
if( modem.getModemState().pollChanged() ) updateBlockState(); if( modem.getModemState().pollChanged() ) updateBlockState();
if( !connectionsFormed ) if( !connectionsFormed )

View File

@ -108,13 +108,15 @@ public class TileWiredModemFull extends TileGeneric
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged(); private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged();
private int invalidSides = 0;
public TileWiredModemFull( TileEntityType<TileWiredModemFull> type ) public TileWiredModemFull( TileEntityType<TileWiredModemFull> type )
{ {
super( type ); super( type );
for( int i = 0; i < peripherals.length; i++ ) for( int i = 0; i < peripherals.length; i++ )
{ {
Direction facing = Direction.from3DDataValue( i ); Direction facing = Direction.from3DDataValue( i );
peripherals[i] = new WiredModemLocalPeripheral( () -> refreshPeripheral( facing ) ); peripherals[i] = new WiredModemLocalPeripheral( () -> queueRefreshPeripheral( facing ) );
} }
} }
@ -173,13 +175,20 @@ public class TileWiredModemFull extends TileGeneric
{ {
for( Direction facing : DirectionUtil.FACINGS ) for( Direction facing : DirectionUtil.FACINGS )
{ {
if( getBlockPos().relative( facing ).equals( neighbour ) ) refreshPeripheral( facing ); if( getBlockPos().relative( facing ).equals( neighbour ) ) queueRefreshPeripheral( facing );
} }
} }
} }
private void queueRefreshPeripheral( @Nonnull Direction facing )
{
if( invalidSides == 0 ) TickScheduler.schedule( this );
invalidSides |= 1 << facing.ordinal();
}
private void refreshPeripheral( @Nonnull Direction facing ) private void refreshPeripheral( @Nonnull Direction facing )
{ {
invalidSides &= ~(1 << facing.ordinal());
WiredModemLocalPeripheral peripheral = peripherals[facing.ordinal()]; WiredModemLocalPeripheral peripheral = peripherals[facing.ordinal()];
if( level != null && !isRemoved() && peripheral.attach( level, getBlockPos(), facing ) ) if( level != null && !isRemoved() && peripheral.attach( level, getBlockPos(), facing ) )
{ {
@ -262,6 +271,14 @@ public class TileWiredModemFull extends TileGeneric
{ {
if( getLevel().isClientSide ) return; if( getLevel().isClientSide ) return;
if( invalidSides != 0 )
{
for( Direction direction : DirectionUtil.FACINGS )
{
if( (invalidSides & (1 << direction.ordinal())) != 0 ) refreshPeripheral( direction );
}
}
if( modemState.pollChanged() ) updateBlockState(); if( modemState.pollChanged() ) updateBlockState();
if( !connectionsFormed ) if( !connectionsFormed )

View File

@ -322,8 +322,10 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
{ {
if( dir.getAxis() == Direction.Axis.Y ) dir = Direction.NORTH; if( dir.getAxis() == Direction.Axis.Y ) dir = Direction.NORTH;
level.setBlockAndUpdate( worldPosition, getBlockState().setValue( BlockTurtle.FACING, dir ) ); level.setBlockAndUpdate( worldPosition, getBlockState().setValue( BlockTurtle.FACING, dir ) );
updateOutput(); updateOutput();
updateInput(); updateInputsImmediately();
onTileEntityChange(); onTileEntityChange();
} }

View File

@ -333,16 +333,17 @@ public class TurtleBrain implements ITurtleAccess
TileTurtle newTurtle = (TileTurtle) newTile; TileTurtle newTurtle = (TileTurtle) newTile;
newTurtle.setLevelAndPosition( world, pos ); newTurtle.setLevelAndPosition( world, pos );
newTurtle.transferStateFrom( oldOwner ); newTurtle.transferStateFrom( oldOwner );
newTurtle.createServerComputer().setWorld( world );
newTurtle.createServerComputer().setPosition( pos ); ServerComputer computer = newTurtle.createServerComputer();
computer.setWorld( world );
computer.setPosition( pos );
// Remove the old turtle // Remove the old turtle
oldWorld.removeBlock( oldPos, false ); oldWorld.removeBlock( oldPos, false );
// Make sure everybody knows about it // Make sure everybody knows about it
newTurtle.updateBlock();
newTurtle.updateInput();
newTurtle.updateOutput(); newTurtle.updateOutput();
newTurtle.updateInputsImmediately();
return true; return true;
} }
} }
@ -614,16 +615,16 @@ public class TurtleBrain implements ITurtleAccess
@Override @Override
public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade ) public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
{ {
if( !setUpgradeDirect( side, upgrade ) ) return; if( !setUpgradeDirect( side, upgrade ) || owner.getLevel() == null ) return;
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as // This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
// either the block is newly placed (and so won't have changed) or is being updated with /data, which calls // either the block is newly placed (and so won't have changed) or is being updated with /data, which calls
// updateBlock for us. // updateBlock for us.
if( owner.getLevel() != null ) owner.updateBlock();
{
owner.updateBlock(); // Recompute peripherals in case an upgrade being removed has exposed a new peripheral.
owner.updateInput(); // TODO: Only update peripherals, or even only two sides?
} owner.updateInputsImmediately();
} }
private boolean setUpgradeDirect( @Nonnull TurtleSide side, ITurtleUpgrade upgrade ) private boolean setUpgradeDirect( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
@ -645,7 +646,7 @@ public class TurtleBrain implements ITurtleAccess
if( upgrade != null ) upgrades.put( side, upgrade ); if( upgrade != null ) upgrades.put( side, upgrade );
// Notify clients and create peripherals // Notify clients and create peripherals
if( owner.getLevel() != null ) if( owner.getLevel() != null && !owner.getLevel().isClientSide )
{ {
updatePeripherals( owner.createServerComputer() ); updatePeripherals( owner.createServerComputer() );
} }

View File

@ -6,6 +6,9 @@
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.RedstoneDiodeBlock;
import net.minecraft.block.RedstoneWireBlock;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
@ -15,6 +18,30 @@ import java.util.EnumSet;
public final class RedstoneUtil public final class RedstoneUtil
{ {
private RedstoneUtil()
{
}
/**
* Gets the redstone input for an adjacent block.
*
* @param world The world we exist in
* @param pos The position of the neighbour
* @param side The side we are reading from
* @return The effective redstone power
* @see RedstoneDiodeBlock#getInputSignal(World, BlockPos, BlockState)
*/
public static int getRedstoneInput( World world, BlockPos pos, Direction side )
{
int power = world.getSignal( pos, side );
if( power >= 15 ) return power;
BlockState neighbour = world.getBlockState( pos );
return neighbour.getBlock() == Blocks.REDSTONE_WIRE
? Math.max( power, neighbour.getValue( RedstoneWireBlock.POWER ) )
: power;
}
public static void propagateRedstoneOutput( World world, BlockPos pos, Direction side ) public static void propagateRedstoneOutput( World world, BlockPos pos, Direction side )
{ {
// Propagate ordinary output. See BlockRedstoneDiode.notifyNeighbors // Propagate ordinary output. See BlockRedstoneDiode.notifyNeighbors