1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-04-10 12:46:41 +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 );
TileEntity tile = world.getBlockEntity( pos );
if( tile instanceof TileComputerBase ) ((TileComputerBase) tile).updateInput();
if( tile instanceof TileComputerBase ) ((TileComputerBase) tile).updateInputsImmediately( );
}
@Override

View File

@ -19,9 +19,6 @@ import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.RedstoneUtil;
import joptsimple.internal.Strings;
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.inventory.container.INamedContainerProvider;
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.StringTextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull;
@ -57,6 +53,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
private boolean on = false;
boolean startOn = false;
private boolean fresh = false;
private int invalidSides = 0;
private final NonNullConsumer<Object>[] invalidate;
private final ComputerFamily family;
@ -71,7 +69,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
NonNullConsumer<Object>[] invalidate = this.invalidate = new NonNullConsumer[6];
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
public void onNeighbourChange( @Nonnull BlockPos neighbour )
{
updateInput( neighbour );
updateInputAt( neighbour );
}
@Override
public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour )
{
updateInput( neighbour );
updateInputAt( neighbour );
}
@Override
public void tick()
{
if( !getLevel().isClientSide )
if( getLevel().isClientSide ) return;
ServerComputer computer = createServerComputer();
if( invalidSides != 0 )
{
ServerComputer computer = createServerComputer();
if( computer == null ) return;
// If the computer isn't on and should be, then turn it on
if( startOn || (fresh && on) )
for( Direction direction : DirectionUtil.FACINGS )
{
computer.turnOn();
startOn = false;
if( (invalidSides & (1 << direction.ordinal())) != 0 ) refreshPeripheral( computer, direction );
}
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 );
@ -226,89 +231,80 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
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();
ComputerSide localDir = remapToLocalSide( dir );
computer.setRedstoneInput( localDir, getRedstoneInput( level, offset, dir ) );
computer.setBundledRedstoneInput( localDir, BundledRedstone.getOutput( getLevel(), offset, offsetSide ) );
if( !isPeripheralBlockedOnSide( localDir ) )
{
IPeripheral peripheral = Peripherals.getPeripheral( getLevel(), offset, offsetSide, invalidate[dir.ordinal()] );
computer.setPeripheral( localDir, peripheral );
}
computer.setRedstoneInput( localDir, RedstoneUtil.getRedstoneInput( level, targetPos, dir ) );
computer.setBundledRedstoneInput( localDir, BundledRedstone.getOutput( getLevel(), targetPos, offsetSide ) );
}
private void refreshPeripheral( @Nonnull ServerComputer computer, Direction dir )
{
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
* @param pos The position of the neighbour
* @param side The side we are reading from
* @return The effective redstone power
* @see RedstoneDiodeBlock#calculateInputStrength(World, BlockPos, BlockState)
* This should only be really be called when the computer is being ticked (though there are some cases where it
* won't be), as peripheral scanning requires adjacent tiles to be in a "correct" state - which may not be the case
* if they're still updating!
*
* @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 );
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();
BlockPos pos = getBlockPos();
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();
if( computer == null ) return;
for( Direction dir : DirectionUtil.FACINGS )
{
BlockPos offset = worldPosition.relative( dir );
BlockPos offset = getBlockPos().relative( dir );
if( offset.equals( neighbour ) )
{
updateSideInput( computer, dir, offset );
updateRedstoneInput( computer, dir, offset );
invalidSides |= 1 << dir.ordinal();
return;
}
}
// If the position is not any adjacent one, update all inputs.
updateInput();
}
private void updateInput( Direction dir )
{
if( getLevel() == null || getLevel().isClientSide ) return;
ServerComputer computer = getServerComputer();
if( computer == null ) return;
updateSideInput( computer, dir, worldPosition.relative( dir ) );
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
// handle this incorrectly.
BlockPos pos = getBlockPos();
for( Direction dir : DirectionUtil.FACINGS ) updateRedstoneInput( computer, dir, pos.relative( dir ) );
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
}
/**
* Update the block's state and propagate redstone output.
*/
public void updateOutput()
{
// Update redstone
updateBlock();
for( Direction dir : DirectionUtil.FACINGS )
{
@ -358,9 +354,10 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
return family;
}
@Nonnull
public ServerComputer createServerComputer()
{
if( getLevel().isClientSide ) return null;
if( getLevel().isClientSide ) throw new IllegalStateException( "Cannot access server computer on the client." );
boolean changed = false;
if( instanceID < 0 )
@ -368,18 +365,21 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
instanceID = ComputerCraft.serverComputerRegistry.getUnusedInstanceID();
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 );
fresh = true;
changed = true;
}
if( changed ) updateInput();
return ComputerCraft.serverComputerRegistry.get( instanceID );
if( changed ) updateInputsImmediately( computer );
return computer;
}
@Nullable
public ServerComputer getServerComputer()
{
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 final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral( this::refreshPeripheral );
private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral( this::queueRefreshPeripheral );
private boolean destroyed = false;
@ -239,12 +240,20 @@ public class TileCable extends TileGeneric
if( !level.isClientSide && peripheralAccessAllowed )
{
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()
{
invalidPeripheral = false;
if( level != null && !isRemoved() && peripheral.attach( level, getBlockPos(), getDirection() ) )
{
updateConnectedPeripherals();
@ -324,6 +333,8 @@ public class TileCable extends TileGeneric
elementCap = CapabilityUtil.invalidate( elementCap );
}
if( invalidPeripheral ) refreshPeripheral();
if( modem.getModemState().pollChanged() ) updateBlockState();
if( !connectionsFormed )

View File

@ -108,13 +108,15 @@ public class TileWiredModemFull extends TileGeneric
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged();
private int invalidSides = 0;
public TileWiredModemFull( TileEntityType<TileWiredModemFull> type )
{
super( type );
for( int i = 0; i < peripherals.length; 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 )
{
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 )
{
invalidSides &= ~(1 << facing.ordinal());
WiredModemLocalPeripheral peripheral = peripherals[facing.ordinal()];
if( level != null && !isRemoved() && peripheral.attach( level, getBlockPos(), facing ) )
{
@ -262,6 +271,14 @@ public class TileWiredModemFull extends TileGeneric
{
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( !connectionsFormed )

View File

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

View File

@ -333,16 +333,17 @@ public class TurtleBrain implements ITurtleAccess
TileTurtle newTurtle = (TileTurtle) newTile;
newTurtle.setLevelAndPosition( world, pos );
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
oldWorld.removeBlock( oldPos, false );
// Make sure everybody knows about it
newTurtle.updateBlock();
newTurtle.updateInput();
newTurtle.updateOutput();
newTurtle.updateInputsImmediately();
return true;
}
}
@ -614,16 +615,16 @@ public class TurtleBrain implements ITurtleAccess
@Override
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
// either the block is newly placed (and so won't have changed) or is being updated with /data, which calls
// updateBlock for us.
if( owner.getLevel() != null )
{
owner.updateBlock();
owner.updateInput();
}
owner.updateBlock();
// Recompute peripherals in case an upgrade being removed has exposed a new peripheral.
// TODO: Only update peripherals, or even only two sides?
owner.updateInputsImmediately();
}
private boolean setUpgradeDirect( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
@ -645,7 +646,7 @@ public class TurtleBrain implements ITurtleAccess
if( upgrade != null ) upgrades.put( side, upgrade );
// Notify clients and create peripherals
if( owner.getLevel() != null )
if( owner.getLevel() != null && !owner.getLevel().isClientSide )
{
updatePeripherals( owner.createServerComputer() );
}

View File

@ -6,6 +6,9 @@
package dan200.computercraft.shared.util;
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.math.BlockPos;
import net.minecraft.world.World;
@ -15,6 +18,30 @@ import java.util.EnumSet;
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 )
{
// Propagate ordinary output. See BlockRedstoneDiode.notifyNeighbors