1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-10-31 23:26:19 +00:00

Merge branch 'master' into mc-1.14.x

This commit is contained in:
SquidDev 2020-06-15 22:05:02 +01:00
commit 9134f243c1
26 changed files with 563 additions and 118 deletions

View File

@ -1,5 +1,5 @@
# Mod properties # Mod properties
mod_version=1.88.0 mod_version=1.89.0
# Minecraft properties (update mods.toml when changing) # Minecraft properties (update mods.toml when changing)
mc_version=1.14.4 mc_version=1.14.4

View File

@ -91,6 +91,7 @@ public final class ComputerCraft
public static int modem_highAltitudeRangeDuringStorm = 384; public static int modem_highAltitudeRangeDuringStorm = 384;
public static int maxNotesPerTick = 8; public static int maxNotesPerTick = 8;
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST; public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
public static long monitorBandwidth = 1_000_000;
public static boolean turtlesNeedFuel = true; public static boolean turtlesNeedFuel = true;
public static int turtleFuelLimit = 20000; public static int turtleFuelLimit = 20000;

View File

@ -13,7 +13,6 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer; import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.shared.util.Palette; import dan200.computercraft.shared.util.Palette;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
@ -25,12 +24,8 @@ class MonitorTextureBufferShader
{ {
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3; static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 );
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 ); private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
private static int uniformMv;
private static int uniformP;
private static int uniformFont; private static int uniformFont;
private static int uniformWidth; private static int uniformWidth;
private static int uniformHeight; private static int uniformHeight;
@ -43,16 +38,6 @@ class MonitorTextureBufferShader
static void setupUniform( int width, int height, Palette palette, boolean greyscale ) static void setupUniform( int width, int height, Palette palette, boolean greyscale )
{ {
MATRIX_BUFFER.rewind();
GL11.glGetFloatv( GL11.GL_MODELVIEW_MATRIX, MATRIX_BUFFER );
MATRIX_BUFFER.rewind();
GLX.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER );
MATRIX_BUFFER.rewind();
GL11.glGetFloatv( GL11.GL_PROJECTION_MATRIX, MATRIX_BUFFER );
MATRIX_BUFFER.rewind();
GLX.glUniformMatrix4( uniformP, false, MATRIX_BUFFER );
GLX.glUniform1i( uniformWidth, width ); GLX.glUniform1i( uniformWidth, width );
GLX.glUniform1i( uniformHeight, height ); GLX.glUniform1i( uniformHeight, height );
@ -121,9 +106,6 @@ class MonitorTextureBufferShader
if( !ok ) return false; if( !ok ) return false;
uniformMv = getUniformLocation( program, "u_mv" );
uniformP = getUniformLocation( program, "u_p" );
uniformFont = getUniformLocation( program, "u_font" ); uniformFont = getUniformLocation( program, "u_font" );
uniformWidth = getUniformLocation( program, "u_width" ); uniformWidth = getUniformLocation( program, "u_width" );
uniformHeight = getUniformLocation( program, "u_height" ); uniformHeight = getUniformLocation( program, "u_height" );

View File

@ -39,6 +39,7 @@ import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_
public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor> public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
{ {
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1); private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
private static ByteBuffer tboContents;
@Override @Override
public void render( @Nonnull TileMonitor monitor, double posX, double posY, double posZ, float f, int i ) public void render( @Nonnull TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
@ -161,7 +162,14 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
if( redraw ) if( redraw )
{ {
ByteBuffer monitorBuffer = GLAllocation.createDirectByteBuffer( width * height * 3 ); int size = width * height * 3;
if( tboContents == null || tboContents.capacity() < size )
{
tboContents = GLAllocation.createDirectByteBuffer( size );
}
ByteBuffer monitorBuffer = tboContents;
monitorBuffer.position( 0 );
for( int y = 0; y < height; y++ ) for( int y = 0; y < height; y++ )
{ {
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y ); TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );

View File

@ -37,12 +37,8 @@ public abstract class HandleGeneric implements ILuaObject
{ {
m_open = false; m_open = false;
Closeable closeable = m_closable; IoUtil.closeQuietly( m_closable );
if( closeable != null ) m_closable = null;
{
IoUtil.closeQuietly( closeable );
m_closable = null;
}
} }
/** /**

View File

@ -106,7 +106,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
protected static <T extends Closeable> T closeCloseable( T closeable ) protected static <T extends Closeable> T closeCloseable( T closeable )
{ {
if( closeable != null ) IoUtil.closeQuietly( closeable ); IoUtil.closeQuietly( closeable );
return null; return null;
} }

View File

@ -221,7 +221,7 @@ public class Websocket extends Resource<Websocket>
WeakReference<WebsocketHandle> websocketHandleRef = websocketHandle; WeakReference<WebsocketHandle> websocketHandleRef = websocketHandle;
WebsocketHandle websocketHandle = websocketHandleRef == null ? null : websocketHandleRef.get(); WebsocketHandle websocketHandle = websocketHandleRef == null ? null : websocketHandleRef.get();
if( websocketHandle != null ) IoUtil.closeQuietly( websocketHandle ); IoUtil.closeQuietly( websocketHandle );
this.websocketHandle = null; this.websocketHandle = null;
} }

View File

@ -366,8 +366,7 @@ public class FileSystem
Reference<?> ref; Reference<?> ref;
while( (ref = m_openFileQueue.poll()) != null ) while( (ref = m_openFileQueue.poll()) != null )
{ {
Closeable file = m_openFiles.remove( ref ); IoUtil.closeQuietly( m_openFiles.remove( ref ) );
if( file != null ) IoUtil.closeQuietly( file );
} }
} }
} }

View File

@ -67,6 +67,7 @@ public final class Config
private static final ConfigValue<Integer> modemRangeDuringStorm; private static final ConfigValue<Integer> modemRangeDuringStorm;
private static final ConfigValue<Integer> modemHighAltitudeRangeDuringStorm; private static final ConfigValue<Integer> modemHighAltitudeRangeDuringStorm;
private static final ConfigValue<Integer> maxNotesPerTick; private static final ConfigValue<Integer> maxNotesPerTick;
private static final ConfigValue<Integer> monitorBandwidth;
private static final ConfigValue<Boolean> turtlesNeedFuel; private static final ConfigValue<Boolean> turtlesNeedFuel;
private static final ConfigValue<Integer> turtleFuelLimit; private static final ConfigValue<Integer> turtleFuelLimit;
@ -230,6 +231,16 @@ public final class Config
.comment( "Maximum amount of notes a speaker can play at once" ) .comment( "Maximum amount of notes a speaker can play at once" )
.defineInRange( "max_notes_per_tick", ComputerCraft.maxNotesPerTick, 1, Integer.MAX_VALUE ); .defineInRange( "max_notes_per_tick", ComputerCraft.maxNotesPerTick, 1, Integer.MAX_VALUE );
monitorBandwidth = builder
.comment( "The limit to how much monitor data can be sent *per tick*. Note:\n" +
" - Bandwidth is measured before compression, so the data sent to the client is smaller.\n" +
" - This ignores the number of players a packet is sent to. Updating a monitor for one player consumes " +
"the same bandwidth limit as sending to 20.\n" +
" - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40 monitors to be updated " +
"in a single tick. \n" +
"Set to 0 to disable." )
.defineInRange( "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth, 0, Integer.MAX_VALUE );
builder.pop(); builder.pop();
} }
@ -317,6 +328,7 @@ public final class Config
ComputerCraft.modem_highAltitudeRange = modemHighAltitudeRange.get(); ComputerCraft.modem_highAltitudeRange = modemHighAltitudeRange.get();
ComputerCraft.modem_rangeDuringStorm = modemRangeDuringStorm.get(); ComputerCraft.modem_rangeDuringStorm = modemRangeDuringStorm.get();
ComputerCraft.modem_highAltitudeRangeDuringStorm = modemHighAltitudeRangeDuringStorm.get(); ComputerCraft.modem_highAltitudeRangeDuringStorm = modemHighAltitudeRangeDuringStorm.get();
ComputerCraft.monitorBandwidth = monitorBandwidth.get();
// Turtles // Turtles
ComputerCraft.turtlesNeedFuel = turtlesNeedFuel.get(); ComputerCraft.turtlesNeedFuel = turtlesNeedFuel.get();

View File

@ -6,9 +6,7 @@
package dan200.computercraft.shared.common; package dan200.computercraft.shared.common;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import io.netty.buffer.Unpooled; import dan200.computercraft.shared.network.client.TerminalState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.PacketBuffer;
public class ClientTerminal implements ITerminal public class ClientTerminal implements ITerminal
{ {
@ -48,14 +46,13 @@ public class ClientTerminal implements ITerminal
return m_colour; return m_colour;
} }
public void readDescription( CompoundNBT nbt ) public void read( TerminalState state )
{ {
m_colour = nbt.getBoolean( "colour" ); m_colour = state.colour;
if( nbt.contains( "terminal" ) ) if( state.hasTerminal() )
{ {
CompoundNBT terminal = nbt.getCompound( "terminal" ); resizeTerminal( state.width, state.height );
resizeTerminal( terminal.getInt( "term_width" ), terminal.getInt( "term_height" ) ); state.apply( m_terminal );
m_terminal.read( new PacketBuffer( Unpooled.wrappedBuffer( terminal.getByteArray( "term_contents" ) ) ) );
} }
else else
{ {

View File

@ -5,12 +5,8 @@
*/ */
package dan200.computercraft.shared.common; package dan200.computercraft.shared.common;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import io.netty.buffer.ByteBuf; import dan200.computercraft.shared.network.client.TerminalState;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.PacketBuffer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -73,8 +69,6 @@ public class ServerTerminal implements ITerminal
return m_terminalChangedLastFrame; return m_terminalChangedLastFrame;
} }
// ITerminal implementation
@Override @Override
public Terminal getTerminal() public Terminal getTerminal()
{ {
@ -87,29 +81,8 @@ public class ServerTerminal implements ITerminal
return m_colour; return m_colour;
} }
public void writeDescription( CompoundNBT nbt ) public TerminalState write()
{ {
nbt.putBoolean( "colour", m_colour ); return new TerminalState( m_colour, m_terminal );
if( m_terminal != null )
{
// We have a 10 byte header (2 integer positions, then blinking and current colours), followed by the
// contents and palette.
// Yes, this serialisation code is terrible, but we need to serialise to NBT in order to work with monitors
// (or rather tile entity serialisation).
final int length = 10 + (2 * m_terminal.getWidth() * m_terminal.getHeight()) + (16 * 3);
ByteBuf buffer = Unpooled.buffer( length );
m_terminal.write( new PacketBuffer( buffer ) );
if( buffer.writableBytes() != 0 )
{
ComputerCraft.log.warn( "Should have written {} bytes, but have {} ({} remaining).", length, buffer.writerIndex(), buffer.writableBytes() );
}
CompoundNBT terminal = new CompoundNBT();
terminal.putInt( "term_width", m_terminal.getWidth() );
terminal.putInt( "term_height", m_terminal.getHeight() );
terminal.putByteArray( "term_contents", buffer.array() );
nbt.put( "terminal", terminal );
}
} }
} }

View File

@ -154,9 +154,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
protected NetworkMessage createTerminalPacket() protected NetworkMessage createTerminalPacket()
{ {
CompoundNBT tagCompound = new CompoundNBT(); return new ComputerTerminalClientMessage( getInstanceID(), write() );
writeDescription( tagCompound );
return new ComputerTerminalClientMessage( getInstanceID(), tagCompound );
} }
public void broadcastState( boolean force ) public void broadcastState( boolean force )

View File

@ -14,9 +14,11 @@ import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.fml.network.NetworkDirection; import net.minecraftforge.fml.network.NetworkDirection;
import net.minecraftforge.fml.network.NetworkEvent; import net.minecraftforge.fml.network.NetworkEvent;
import net.minecraftforge.fml.network.NetworkRegistry; import net.minecraftforge.fml.network.NetworkRegistry;
import net.minecraftforge.fml.network.PacketDistributor;
import net.minecraftforge.fml.network.simple.SimpleChannel; import net.minecraftforge.fml.network.simple.SimpleChannel;
import net.minecraftforge.fml.server.ServerLifecycleHooks; import net.minecraftforge.fml.server.ServerLifecycleHooks;
@ -52,6 +54,7 @@ public final class NetworkHandler
registerMainThread( 12, ComputerDeletedClientMessage::new ); registerMainThread( 12, ComputerDeletedClientMessage::new );
registerMainThread( 13, ComputerTerminalClientMessage::new ); registerMainThread( 13, ComputerTerminalClientMessage::new );
registerMainThread( 14, PlayRecordClientMessage.class, PlayRecordClientMessage::new ); registerMainThread( 14, PlayRecordClientMessage.class, PlayRecordClientMessage::new );
registerMainThread( 15, MonitorClientMessage.class, MonitorClientMessage::new );
} }
public static void sendToPlayer( PlayerEntity player, NetworkMessage packet ) public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
@ -74,15 +77,13 @@ public final class NetworkHandler
public static void sendToAllAround( NetworkMessage packet, World world, Vec3d pos, double range ) public static void sendToAllAround( NetworkMessage packet, World world, Vec3d pos, double range )
{ {
for( ServerPlayerEntity player : ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayers() ) PacketDistributor.TargetPoint target = new PacketDistributor.TargetPoint( pos.x, pos.y, pos.z, range, world.getDimension().getType() );
{ network.send( PacketDistributor.NEAR.with( () -> target ), packet );
if( player.getEntityWorld() != world ) continue; }
double x = pos.x - player.posX; public static void sendToAllTracking( NetworkMessage packet, Chunk chunk )
double y = pos.y - player.posY; {
double z = pos.z - player.posZ; network.send( PacketDistributor.TRACKING_CHUNK.with( () -> chunk ), packet );
if( x * x + y * y + z * z < range * range ) sendToPlayer( player, packet );
}
} }
/** /**

View File

@ -5,7 +5,6 @@
*/ */
package dan200.computercraft.shared.network.client; package dan200.computercraft.shared.network.client;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.PacketBuffer; import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkEvent; import net.minecraftforge.fml.network.NetworkEvent;
@ -13,12 +12,12 @@ import javax.annotation.Nonnull;
public class ComputerTerminalClientMessage extends ComputerClientMessage public class ComputerTerminalClientMessage extends ComputerClientMessage
{ {
private CompoundNBT tag; private TerminalState state;
public ComputerTerminalClientMessage( int instanceId, CompoundNBT tag ) public ComputerTerminalClientMessage( int instanceId, TerminalState state )
{ {
super( instanceId ); super( instanceId );
this.tag = tag; this.state = state;
} }
public ComputerTerminalClientMessage() public ComputerTerminalClientMessage()
@ -29,19 +28,19 @@ public class ComputerTerminalClientMessage extends ComputerClientMessage
public void toBytes( @Nonnull PacketBuffer buf ) public void toBytes( @Nonnull PacketBuffer buf )
{ {
super.toBytes( buf ); super.toBytes( buf );
buf.writeCompoundTag( tag ); // TODO: Do we need to compress this? state.write( buf );
} }
@Override @Override
public void fromBytes( @Nonnull PacketBuffer buf ) public void fromBytes( @Nonnull PacketBuffer buf )
{ {
super.fromBytes( buf ); super.fromBytes( buf );
tag = buf.readCompoundTag(); state = new TerminalState( buf );
} }
@Override @Override
public void handle( NetworkEvent.Context context ) public void handle( NetworkEvent.Context context )
{ {
getComputer().readDescription( tag ); getComputer().read( state );
} }
} }

View File

@ -0,0 +1,55 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.player.ClientPlayerEntity;
import net.minecraft.network.PacketBuffer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.fml.network.NetworkEvent;
import javax.annotation.Nonnull;
public class MonitorClientMessage implements NetworkMessage
{
private final BlockPos pos;
private final TerminalState state;
public MonitorClientMessage( BlockPos pos, TerminalState state )
{
this.pos = pos;
this.state = state;
}
public MonitorClientMessage( @Nonnull PacketBuffer buf )
{
pos = buf.readBlockPos();
state = new TerminalState( buf );
}
@Override
public void toBytes( @Nonnull PacketBuffer buf )
{
buf.writeBlockPos( pos );
state.write( buf );
}
@Override
public void handle( NetworkEvent.Context context )
{
ClientPlayerEntity player = Minecraft.getInstance().player;
if( player == null || player.world == null ) return;
TileEntity te = player.world.getTileEntity( pos );
if( !(te instanceof TileMonitor) ) return;
((TileMonitor) te).read( state );
}
}

View File

@ -0,0 +1,183 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.IoUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import net.minecraft.network.PacketBuffer;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* A snapshot of a terminal's state.
*
* This is somewhat memory inefficient (we build a buffer, only to write it elsewhere), however it means we get a
* complete and accurate description of a terminal, which avoids a lot of complexities with resizing terminals, dirty
* states, etc...
*/
public class TerminalState
{
public final boolean colour;
public final int width;
public final int height;
private final boolean compress;
@Nullable
private final ByteBuf buffer;
private ByteBuf compressed;
public TerminalState( boolean colour, @Nullable Terminal terminal )
{
this( colour, terminal, true );
}
public TerminalState( boolean colour, @Nullable Terminal terminal, boolean compress )
{
this.colour = colour;
this.compress = compress;
if( terminal == null )
{
this.width = this.height = 0;
this.buffer = null;
}
else
{
this.width = terminal.getWidth();
this.height = terminal.getHeight();
ByteBuf buf = this.buffer = Unpooled.buffer();
terminal.write( new PacketBuffer( buf ) );
}
}
public TerminalState( PacketBuffer buf )
{
this.colour = buf.readBoolean();
this.compress = buf.readBoolean();
if( buf.readBoolean() )
{
this.width = buf.readVarInt();
this.height = buf.readVarInt();
int length = buf.readVarInt();
this.buffer = readCompressed( buf, length, compress );
}
else
{
this.width = this.height = 0;
this.buffer = null;
}
}
public void write( PacketBuffer buf )
{
buf.writeBoolean( colour );
buf.writeBoolean( compress );
buf.writeBoolean( buffer != null );
if( buffer != null )
{
buf.writeVarInt( width );
buf.writeVarInt( height );
ByteBuf sendBuffer = getCompressed();
buf.writeVarInt( sendBuffer.readableBytes() );
buf.writeBytes( sendBuffer, sendBuffer.readerIndex(), sendBuffer.readableBytes() );
}
}
public boolean hasTerminal()
{
return buffer != null;
}
public int size()
{
return buffer == null ? 0 : buffer.readableBytes();
}
public void apply( Terminal terminal )
{
if( buffer == null ) throw new NullPointerException( "buffer" );
terminal.read( new PacketBuffer( buffer ) );
}
private ByteBuf getCompressed()
{
if( buffer == null ) throw new NullPointerException( "buffer" );
if( !compress ) return buffer;
if( compressed != null ) return compressed;
ByteBuf compressed = Unpooled.directBuffer();
OutputStream stream = null;
try
{
stream = new GZIPOutputStream( new ByteBufOutputStream( compressed ) );
stream.write( buffer.array(), buffer.arrayOffset(), buffer.readableBytes() );
}
catch( IOException e )
{
throw new UncheckedIOException( e );
}
finally
{
IoUtil.closeQuietly( stream );
}
return this.compressed = compressed;
}
private static ByteBuf readCompressed( ByteBuf buf, int length, boolean compress )
{
if( compress )
{
ByteBuf buffer = Unpooled.buffer();
InputStream stream = null;
try
{
stream = new GZIPInputStream( new ByteBufInputStream( buf, length ) );
byte[] swap = new byte[8192];
while( true )
{
int bytes = stream.read( swap );
if( bytes == -1 ) break;
buffer.writeBytes( swap, 0, bytes );
}
}
catch( IOException e )
{
throw new UncheckedIOException( e );
}
finally
{
IoUtil.closeQuietly( stream );
}
return buffer;
}
else
{
ByteBuf buffer = Unpooled.buffer( length );
buf.readBytes( buffer, length );
return buffer;
}
}
}

View File

@ -0,0 +1,104 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.MonitorClientMessage;
import dan200.computercraft.shared.network.client.TerminalState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.ChunkWatchEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.ArrayDeque;
import java.util.Queue;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
public final class MonitorWatcher
{
private static final Queue<TileMonitor> watching = new ArrayDeque<>();
private MonitorWatcher()
{
}
static void enqueue( TileMonitor monitor )
{
if( monitor.enqueued ) return;
monitor.enqueued = true;
monitor.cached = null;
watching.add( monitor );
}
@SubscribeEvent
public static void onWatch( ChunkWatchEvent.Watch event )
{
ChunkPos chunkPos = event.getPos();
Chunk chunk = event.getWorld().getChunk( chunkPos.x, chunkPos.z );
if( chunk == null ) return;
for( TileEntity te : chunk.getTileEntityMap().values() )
{
// Find all origin monitors who are not already on the queue.
if( !(te instanceof TileMonitor) ) continue;
TileMonitor monitor = (TileMonitor) te;
ServerMonitor serverMonitor = getMonitor( monitor );
if( serverMonitor == null || monitor.enqueued ) continue;
// We use the cached terminal state if available - this is guaranteed to
TerminalState state = monitor.cached;
if( state == null ) state = monitor.cached = serverMonitor.write();
NetworkHandler.sendToPlayer( event.getPlayer(), new MonitorClientMessage( monitor.getPos(), state ) );
}
}
@SubscribeEvent
public static void onTick( TickEvent.ServerTickEvent event )
{
if( event.phase != TickEvent.Phase.END ) return;
long limit = ComputerCraft.monitorBandwidth;
boolean obeyLimit = limit > 0;
TileMonitor tile;
while( (!obeyLimit || limit > 0) && (tile = watching.poll()) != null )
{
tile.enqueued = false;
ServerMonitor monitor = getMonitor( tile );
if( monitor == null ) continue;
BlockPos pos = tile.getPos();
World world = tile.getWorld();
if( !(world instanceof ServerWorld) ) continue;
Chunk chunk = world.getChunkAt( pos );
if( !((ServerWorld) world).getChunkProvider().chunkManager.getTrackingPlayers( chunk.getPos(), false ).findAny().isPresent() )
{
continue;
}
TerminalState state = tile.cached = monitor.write();
NetworkHandler.sendToAllTracking( new MonitorClientMessage( pos, state ), chunk );
limit -= state.size();
}
}
private static ServerMonitor getMonitor( TileMonitor monitor )
{
return !monitor.isRemoved() && monitor.getXIndex() == 0 && monitor.getYIndex() == 0 ? monitor.getCachedServerMonitor() : null;
}
}

View File

@ -12,6 +12,7 @@ import dan200.computercraft.api.peripheral.IPeripheralTile;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.common.ServerTerminal; import dan200.computercraft.shared.common.ServerTerminal;
import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.network.client.TerminalState;
import dan200.computercraft.shared.util.NamedTileEntityType; import dan200.computercraft.shared.util.NamedTileEntityType;
import dan200.computercraft.shared.util.TickScheduler; import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
@ -63,6 +64,10 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
private boolean m_destroyed = false; private boolean m_destroyed = false;
private boolean visiting = false; private boolean visiting = false;
// MonitorWatcher state.
boolean enqueued;
TerminalState cached;
private int m_width = 1; private int m_width = 1;
private int m_height = 1; private int m_height = 1;
private int m_xIndex = 0; private int m_xIndex = 0;
@ -170,7 +175,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
} }
} }
if( m_serverMonitor.pollTerminalChanged() ) updateBlock(); if( m_serverMonitor.pollTerminalChanged() ) MonitorWatcher.enqueue( this );
} }
// IPeripheralTile implementation // IPeripheralTile implementation
@ -249,16 +254,10 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
protected void writeDescription( @Nonnull CompoundNBT nbt ) protected void writeDescription( @Nonnull CompoundNBT nbt )
{ {
super.writeDescription( nbt ); super.writeDescription( nbt );
nbt.putInt( NBT_X, m_xIndex ); nbt.putInt( NBT_X, m_xIndex );
nbt.putInt( NBT_Y, m_yIndex ); nbt.putInt( NBT_Y, m_yIndex );
nbt.putInt( NBT_WIDTH, m_width ); nbt.putInt( NBT_WIDTH, m_width );
nbt.putInt( NBT_HEIGHT, m_height ); nbt.putInt( NBT_HEIGHT, m_height );
if( m_xIndex == 0 && m_yIndex == 0 && m_serverMonitor != null )
{
m_serverMonitor.writeDescription( nbt );
}
} }
@Override @Override
@ -286,9 +285,8 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
if( m_xIndex == 0 && m_yIndex == 0 ) if( m_xIndex == 0 && m_yIndex == 0 )
{ {
// If we're the origin terminal then read the description // If we're the origin terminal then create it.
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( advanced, this ); if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( advanced, this );
m_clientMonitor.readDescription( nbt );
} }
if( oldXIndex != m_xIndex || oldYIndex != m_yIndex || if( oldXIndex != m_xIndex || oldYIndex != m_yIndex ||
@ -299,6 +297,20 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
} }
} }
public final void read( TerminalState state )
{
if( m_xIndex != 0 || m_yIndex != 0 )
{
ComputerCraft.log.warn( "Receiving monitor state for non-origin terminal at {}", getPos() );
return;
}
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( advanced, this );
m_clientMonitor.read( state );
}
// Sizing and placement stuff
private void updateBlockState() private void updateBlockState()
{ {
getWorld().setBlockState( getPos(), getBlockState() getWorld().setBlockState( getPos(), getBlockState()

View File

@ -5,6 +5,7 @@
*/ */
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import javax.annotation.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@ -12,11 +13,11 @@ public final class IoUtil
{ {
private IoUtil() {} private IoUtil() {}
public static void closeQuietly( Closeable closeable ) public static void closeQuietly( @Nullable Closeable closeable )
{ {
try try
{ {
closeable.close(); if( closeable != null ) closeable.close();
} }
catch( IOException ignored ) catch( IOException ignored )
{ {

View File

@ -35,6 +35,6 @@ void main() {
int bg = int(texelFetch(u_tbo, index + 2).r * 255.0); int bg = int(texelFetch(u_tbo, index + 2).r * 255.0);
vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT); vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT);
vec4 img = texture2D(u_font, (texture_corner(character) + pos) / 256.0); vec4 img = texture(u_font, (texture_corner(character) + pos) / 256.0);
colour = vec4(mix(u_palette[bg], img.rgb * u_palette[fg], img.a * mult), 1.0); colour = vec4(mix(u_palette[bg], img.rgb * u_palette[fg], img.a * mult), 1.0);
} }

View File

@ -1,13 +1,10 @@
#version 140 #version 130
uniform mat4 u_mv;
uniform mat4 u_p;
in vec3 v_pos; in vec3 v_pos;
out vec2 f_pos; out vec2 f_pos;
void main() { void main() {
gl_Position = u_p * u_mv * vec4(v_pos.x, v_pos.y, 0, 1); gl_Position = gl_ModelViewProjectionMatrix * vec4(v_pos.x, v_pos.y, 0, 1);
f_pos = v_pos.xy; f_pos = v_pos.xy;
} }

View File

@ -205,7 +205,7 @@ function load(sPath)
end end
for k, v in pairs(tFile) do for k, v in pairs(tFile) do
local ty_v = type(k) local ty_v = type(v)
if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
local opt = details[k] local opt = details[k]
if not opt or not opt.type or ty_v == opt.type then if not opt or not opt.type or ty_v == opt.type then

View File

@ -1,3 +1,13 @@
# New features in CC: Tweaked 1.89.0
* Compress monitor data, reducing network traffic by a significant amount.
* Allow limiting the bandwidth monitor updates use.
* Several optimisations to monitor rendering (@Lignum).
* Expose block and item tags to turtle.inspect and turtle.getItemDetail.
And several bug fixes:
* Fix settings.load failing on defined settings.
# New features in CC: Tweaked 1.88.0 # New features in CC: Tweaked 1.88.0
* Computers and turtles now preserve their ID when broken. * Computers and turtles now preserve their ID when broken.

View File

@ -1,18 +1,11 @@
New features in CC: Tweaked 1.88.0 New features in CC: Tweaked 1.89.0
* Computers and turtles now preserve their ID when broken. * Compress monitor data, reducing network traffic by a significant amount.
* Add `peripheral.getName` - returns the name of a wrapped peripheral. * Allow limiting the bandwidth monitor updates use.
* Reduce network overhead of monitors and terminals. * Several optimisations to monitor rendering (@Lignum).
* Add a TBO backend for monitors, with a significant performance boost. * Expose block and item tags to turtle.inspect and turtle.getItemDetail.
* The Lua REPL warns when declaring locals (lupus590, exerro)
* Add config to allow using command computers in survival.
* Add fs.isDriveRoot - checks if a path is the root of a drive.
* `cc.pretty` can now display a function's arguments and where it was defined. The Lua REPL will show arguments by default.
* Move the shell's `require`/`package` implementation to a separate `cc.require` module.
And several bug fixes: And several bug fixes:
* Fix io.lines not accepting arguments. * Fix settings.load failing on defined settings.
* Fix settings.load using an unknown global (MCJack123).
* Prevent computers scanning peripherals twice.
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

@ -0,0 +1,85 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import io.netty.buffer.Unpooled;
import net.minecraft.network.PacketBuffer;
import org.junit.jupiter.api.RepeatedTest;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests {@link TerminalState} round tripping works as expected.
*/
public class TerminalStateTest
{
@RepeatedTest( 5 )
public void testCompressed()
{
Terminal terminal = randomTerminal();
PacketBuffer buffer = new PacketBuffer( Unpooled.directBuffer() );
new TerminalState( true, terminal, true ).write( buffer );
checkEqual( terminal, read( buffer ) );
assertEquals( 0, buffer.readableBytes() );
}
@RepeatedTest( 5 )
public void testUncompressed()
{
Terminal terminal = randomTerminal();
PacketBuffer buffer = new PacketBuffer( Unpooled.directBuffer() );
new TerminalState( true, terminal, false ).write( buffer );
checkEqual( terminal, read( buffer ) );
assertEquals( 0, buffer.readableBytes() );
}
private static Terminal randomTerminal()
{
Random random = new Random();
Terminal terminal = new Terminal( 10, 5 );
for( int y = 0; y < terminal.getHeight(); y++ )
{
TextBuffer buffer = terminal.getLine( y );
for( int x = 0; x < buffer.length(); x++ ) buffer.setChar( x, (char) (random.nextInt( 26 ) + 65) );
}
return terminal;
}
private static void checkEqual( Terminal expected, Terminal actual )
{
assertNotNull( expected, "Expected cannot be null" );
assertNotNull( actual, "Actual cannot be null" );
assertEquals( expected.getHeight(), actual.getHeight(), "Heights must match" );
assertEquals( expected.getWidth(), actual.getWidth(), "Widths must match" );
for( int y = 0; y < expected.getHeight(); y++ )
{
assertEquals( expected.getLine( y ).toString(), actual.getLine( y ).toString() );
}
}
private static Terminal read( PacketBuffer buffer )
{
TerminalState state = new TerminalState( buffer );
assertTrue( state.colour );
if( !state.hasTerminal() ) return null;
Terminal other = new Terminal( state.width, state.height );
state.apply( other );
return other;
}
}

View File

@ -153,11 +153,50 @@ describe("The settings library", function()
expect.error(settings.load, 1):eq("bad argument #1 (expected string, got number)") expect.error(settings.load, 1):eq("bad argument #1 (expected string, got number)")
end) end)
local function setup_with(contents)
settings.clear()
local h = fs.open("/test-files/.settings", "w")
h.write(contents)
h.close()
return settings.load("/test-files/.settings")
end
local function setup(contents)
return setup_with(textutils.serialize(contents))
end
it("defaults to .settings", function() it("defaults to .settings", function()
local s = stub(fs, "open") local s = stub(fs, "open")
settings.load() settings.load()
expect(s):called_with(".settings", "r") expect(s):called_with(".settings", "r")
end) end)
it("loads undefined settings", function()
expect(setup { ["test"] = 1 }):eq(true)
expect(settings.get("test")):eq(1)
end)
it("loads defined settings", function()
settings.define("test.defined", { type = "number" })
expect(setup { ["test.defined"] = 1 }):eq(true)
expect(settings.get("test.defined")):eq(1)
end)
it("skips defined settings with incorrect types", function()
settings.define("test.defined", { type = "number" })
expect(setup { ["test.defined"] = "abc" }):eq(true)
expect(settings.get("test.defined")):eq(nil)
end)
it("skips unserializable values", function()
expect(setup_with "{ test = function() end }"):eq(true)
expect(settings.get("test")):eq(nil)
end)
it("skips non-table files", function()
expect(setup "not a table"):eq(false)
end)
end) end)
describe("settings.save", function() describe("settings.save", function()