1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-26 19:37:39 +00:00

Merge branch 'master' into mc-1.14.x

This also deletes display list support - MC 1.14 now requires VBOs to be
supported in some capacity.
This commit is contained in:
SquidDev
2020-04-22 09:45:23 +01:00
537 changed files with 8869 additions and 4413 deletions

View File

@@ -24,6 +24,7 @@ import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem;
import dan200.computercraft.shared.peripheral.monitor.BlockMonitor;
import dan200.computercraft.shared.peripheral.printer.BlockPrinter;
import dan200.computercraft.shared.peripheral.speaker.BlockSpeaker;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
@@ -81,7 +82,7 @@ public final class ComputerCraft
public static long httpMaxDownload = 16 * 1024 * 1024;
public static long httpMaxUpload = 4 * 1024 * 1024;
public static int httpMaxWebsockets = 4;
public static int httpMaxWebsocketMessage = Websocket.MAX_MESSAGE_SIZE;
public static int httpMaxWebsocketMessage = 128 * 1024;
public static boolean enableCommandBlock = false;
public static int modem_range = 64;
@@ -89,6 +90,7 @@ public final class ComputerCraft
public static int modem_rangeDuringStorm = 64;
public static int modem_highAltitudeRangeDuringStorm = 384;
public static int maxNotesPerTick = 8;
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
public static boolean turtlesNeedFuel = true;
public static int turtleFuelLimit = 20000;

View File

@@ -0,0 +1,81 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.filesystem;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
/**
* A simple version of {@link BasicFileAttributes}, which provides what information a {@link IMount} already exposes.
*/
final class FileAttributes implements BasicFileAttributes
{
private static final FileTime EPOCH = FileTime.from( Instant.EPOCH );
private final boolean isDirectory;
private final long size;
FileAttributes( boolean isDirectory, long size )
{
this.isDirectory = isDirectory;
this.size = size;
}
@Override
public FileTime lastModifiedTime()
{
return EPOCH;
}
@Override
public FileTime lastAccessTime()
{
return EPOCH;
}
@Override
public FileTime creationTime()
{
return EPOCH;
}
@Override
public boolean isRegularFile()
{
return !isDirectory;
}
@Override
public boolean isDirectory()
{
return isDirectory;
}
@Override
public boolean isSymbolicLink()
{
return false;
}
@Override
public boolean isOther()
{
return false;
}
@Override
public long size()
{
return size;
}
@Override
public Object fileKey()
{
return null;
}
}

View File

@@ -27,7 +27,7 @@ public class FileOperationException extends IOException
this.filename = filename;
}
public FileOperationException( String message )
public FileOperationException( @Nonnull String message )
{
super( Objects.requireNonNull( message, "message cannot be null" ) );
this.filename = null;

View File

@@ -14,6 +14,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
/**
@@ -93,4 +94,18 @@ public interface IMount
{
return Channels.newChannel( openForRead( path ) );
}
/**
* Get attributes about the given file.
*
* @param path The path to query.
* @return File attributes for the given file.
* @throws IOException If the file does not exist, or attributes could not be fetched.
*/
@Nonnull
default BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
if( !exists( path ) ) throw new FileOperationException( path, "No such file" );
return new FileAttributes( isDirectory( path ), getSize( path ) );
}
}

View File

@@ -14,6 +14,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.OptionalLong;
/**
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
@@ -105,4 +106,16 @@ public interface IWritableMount extends IMount
* @throws IOException If the remaining space could not be computed.
*/
long getRemainingSpace() throws IOException;
/**
* Get the capacity of this mount. This should be equal to the size of all files/directories on this mount, minus
* the {@link #getRemainingSpace()}.
*
* @return The capacity of this mount, in bytes.
*/
@Nonnull
default OptionalLong getCapacity()
{
return OptionalLong.empty();
}
}

View File

@@ -6,194 +6,323 @@
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public final class FixedWidthFontRenderer
{
private static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" );
public static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/term_background.png" );
/**
* Like {@link DefaultVertexFormats#POSITION_TEX_COLOR}, but flipped. This is backported from 1.15, hence the
* custom format.
*/
public static final VertexFormat POSITION_COLOR_TEX = new VertexFormat();
static
{
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.POSITION_3F );
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.COLOR_4UB );
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.TEX_2F );
}
public static final int FONT_HEIGHT = 9;
public static final int FONT_WIDTH = 6;
public static final float WIDTH = 256.0f;
private static FixedWidthFontRenderer instance;
public static FixedWidthFontRenderer instance()
{
if( instance != null ) return instance;
return instance = new FixedWidthFontRenderer();
}
private final TextureManager m_textureManager;
public static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
public static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
private FixedWidthFontRenderer()
{
m_textureManager = Minecraft.getInstance().getTextureManager();
}
private static void greyscaleify( double[] rgb )
private static float toGreyscale( double[] rgb )
{
Arrays.fill( rgb, (rgb[0] + rgb[1] + rgb[2]) / 3.0f );
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
}
private void drawChar( BufferBuilder renderer, double x, double y, int index, int color, Palette p, boolean greyscale )
private static int getColour( char c )
{
int i = "0123456789abcdef".indexOf( c );
return i < 0 ? 0 : 15 - i;
}
private static void drawChar( BufferBuilder buffer, float x, float y, int index, float r, float g, float b )
{
// Short circuit to avoid the common case - the texture should be blank here after all.
if( index == '\0' || index == ' ' ) return;
int column = index % 16;
int row = index / 16;
double[] colour = p.getColour( 15 - color );
if( greyscale )
{
greyscaleify( colour );
}
float r = (float) colour[0];
float g = (float) colour[1];
float b = (float) colour[2];
int xStart = 1 + column * (FONT_WIDTH + 2);
int yStart = 1 + row * (FONT_HEIGHT + 2);
renderer.pos( x, y, 0.0 ).tex( xStart / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x + FONT_WIDTH, y + FONT_HEIGHT, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( x, y, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
buffer.pos( x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
buffer.pos( x + FONT_WIDTH, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
}
private void drawQuad( BufferBuilder renderer, double x, double y, int color, double width, Palette p, boolean greyscale )
private static void drawQuad( BufferBuilder buffer, float x, float y, float width, float height, float r, float g, float b )
{
double[] colour = p.getColour( 15 - color );
buffer.pos( x, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_START ).endVertex();
buffer.pos( x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
buffer.pos( x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
buffer.pos( x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
buffer.pos( x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
buffer.pos( x + width, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_END ).endVertex();
}
private static void drawQuad( BufferBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
{
double[] colour = palette.getColour( getColour( colourIndex ) );
float r, g, b;
if( greyscale )
{
greyscaleify( colour );
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
float r = (float) colour[0];
float g = (float) colour[1];
float b = (float) colour[2];
renderer.pos( x, y, 0.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x + width, y, 0.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x + width, y, 0.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( x + width, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).endVertex();
drawQuad( buffer, x, y, width, height, r, g, b );
}
private boolean isGreyScale( int colour )
private static void drawBackground(
@Nonnull BufferBuilder renderer, float x, float y,
@Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
float leftMarginSize, float rightMarginSize, float height
)
{
return colour == 0 || colour == 15 || colour == 7 || colour == 8;
}
if( leftMarginSize > 0 )
{
drawQuad( renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) );
}
public void drawStringBackgroundPart( int x, int y, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )
{
// Draw the quads
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
renderer.begin( GL11.GL_TRIANGLES, DefaultVertexFormats.POSITION_COLOR );
if( leftMarginSize > 0.0 )
if( rightMarginSize > 0 )
{
int colour1 = "0123456789abcdef".indexOf( backgroundColour.charAt( 0 ) );
if( colour1 < 0 || (greyScale && !isGreyScale( colour1 )) )
{
colour1 = 15;
}
drawQuad( renderer, x - leftMarginSize, y, colour1, leftMarginSize, p, greyScale );
}
if( rightMarginSize > 0.0 )
{
int colour2 = "0123456789abcdef".indexOf( backgroundColour.charAt( backgroundColour.length() - 1 ) );
if( colour2 < 0 || (greyScale && !isGreyScale( colour2 )) )
{
colour2 = 15;
}
drawQuad( renderer, x + backgroundColour.length() * FONT_WIDTH, y, colour2, rightMarginSize, p, greyScale );
drawQuad( renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) );
}
// Batch together runs of identical background cells.
int blockStart = 0;
char blockColour = '\0';
for( int i = 0; i < backgroundColour.length(); i++ )
{
int colour = "0123456789abcdef".indexOf( backgroundColour.charAt( i ) );
if( colour < 0 || (greyScale && !isGreyScale( colour )) )
char colourIndex = backgroundColour.charAt( i );
if( colourIndex == blockColour ) continue;
if( blockColour != '\0' )
{
colour = 15;
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour );
}
drawQuad( renderer, x + i * FONT_WIDTH, y, colour, FONT_WIDTH, p, greyScale );
blockColour = colourIndex;
blockStart = i;
}
if( blockColour != '\0' )
{
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour );
}
GlStateManager.disableTexture();
tessellator.draw();
GlStateManager.enableTexture();
}
public void drawStringTextPart( int x, int y, TextBuffer s, TextBuffer textColour, boolean greyScale, Palette p )
public static void drawString(
@Nonnull BufferBuilder renderer, float x, float y,
@Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour,
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize
)
{
// Draw the quads
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
renderer.begin( GL11.GL_TRIANGLES, DefaultVertexFormats.POSITION_TEX_COLOR );
for( int i = 0; i < s.length(); i++ )
if( backgroundColour != null )
{
// Switch colour
int colour = "0123456789abcdef".indexOf( textColour.charAt( i ) );
if( colour < 0 || (greyScale && !isGreyScale( colour )) )
drawBackground( renderer, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT );
}
for( int i = 0; i < text.length(); i++ )
{
double[] colour = palette.getColour( getColour( textColour.charAt( i ) ) );
float r, g, b;
if( greyscale )
{
colour = 0;
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
// Draw char
int index = s.charAt( i );
if( index < 0 || index > 255 )
{
index = '?';
}
drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale );
int index = text.charAt( i );
if( index > 255 ) index = '?';
drawChar( renderer, x + i * FONT_WIDTH, y, index, r, g, b );
}
}
public static void drawString(
float x, float y, @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour,
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize
)
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
drawString( buffer, x, y, text, textColour, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize );
tessellator.draw();
}
public void drawString( TextBuffer s, int x, int y, TextBuffer textColour, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )
public static void drawTerminalWithoutCursor(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
// Draw background
if( backgroundColour != null )
Palette palette = terminal.getPalette();
int height = terminal.getHeight();
// Top and bottom margins
drawBackground(
buffer, x, y - topMarginSize,
terminal.getBackgroundColourLine( 0 ), palette, greyscale,
leftMarginSize, rightMarginSize, topMarginSize
);
drawBackground(
buffer, x, y + height * FONT_HEIGHT,
terminal.getBackgroundColourLine( height - 1 ), palette, greyscale,
leftMarginSize, rightMarginSize, bottomMarginSize
);
// The main text
for( int i = 0; i < height; i++ )
{
// Bind the background texture
m_textureManager.bindTexture( BACKGROUND );
// Draw the quads
drawStringBackgroundPart( x, y, backgroundColour, leftMarginSize, rightMarginSize, greyScale, p );
}
// Draw text
if( s != null && textColour != null )
{
// Bind the font texture
bindFont();
// Draw the quads
drawStringTextPart( x, y, s, textColour, greyScale, p );
drawString(
buffer, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i,
terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ),
palette, greyscale, leftMarginSize, rightMarginSize
);
}
}
public int getStringWidth( String s )
public static void drawCursor(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale
)
{
if( s == null )
Palette palette = terminal.getPalette();
int width = terminal.getWidth();
int height = terminal.getHeight();
int cursorX = terminal.getCursorX();
int cursorY = terminal.getCursorY();
if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height && FrameInfo.getGlobalCursorBlink() )
{
return 0;
double[] colour = palette.getColour( 15 - terminal.getTextColour() );
float r, g, b;
if( greyscale )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
drawChar( buffer, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', r, g, b );
}
return s.length() * FONT_WIDTH;
}
public void bindFont()
public static void drawTerminal(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
m_textureManager.bindTexture( FONT );
drawTerminalWithoutCursor( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
drawCursor( buffer, x, y, terminal, greyscale );
}
public static void drawTerminal(
float x, float y, @Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
drawTerminal( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
tessellator.draw();
}
public static void drawEmptyTerminal( float x, float y, float width, float height )
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
Colour colour = Colour.Black;
drawQuad( buffer, x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
tessellator.draw();
}
public static void drawBlocker( @Nonnull BufferBuilder buffer, float x, float y, float width, float height )
{
Colour colour = Colour.Black;
drawQuad( buffer, x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
}
public static void drawBlocker( float x, float y, float width, float height )
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
drawBlocker( buffer, x, y, width, height );
tessellator.draw();
}
public static void bindFont()
{
Minecraft.getInstance().getTextureManager().bindTexture( FONT );
GlStateManager.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
GlStateManager.enableTexture();
}
public static void begin( BufferBuilder buffer )
{
buffer.begin( GL11.GL_TRIANGLES, POSITION_COLOR_TEX );
}
}

View File

@@ -5,29 +5,18 @@
*/
package dan200.computercraft.client.gui.widgets;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.IGuiEventListener;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.SharedConstants;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
import java.util.BitSet;
import java.util.function.Supplier;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.BACKGROUND;
public class WidgetTerminal implements IGuiEventListener
{
private static final float TERMINATE_TIME = 0.5f;
@@ -329,89 +318,19 @@ public class WidgetTerminal implements IGuiEventListener
Terminal terminal = computer != null ? computer.getTerminal() : null;
if( terminal != null )
{
// Draw the terminal
boolean greyscale = !computer.isColour();
Palette palette = terminal.getPalette();
// Get the data from the terminal first
// Unfortunately we have to keep the lock for the whole of drawing, so the text doesn't change under us.
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
boolean tblink = terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink();
int tw = terminal.getWidth();
int th = terminal.getHeight();
int tx = terminal.getCursorX();
int ty = terminal.getCursorY();
// Draw margins
TextBuffer emptyLine = new TextBuffer( ' ', tw );
if( topMargin > 0 )
{
fontRenderer.drawString( emptyLine, originX, originY - topMargin,
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ),
leftMargin, rightMargin, greyscale, palette );
}
if( bottomMargin > 0 )
{
fontRenderer.drawString( emptyLine, originX, originY + bottomMargin + (th - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
terminal.getTextColourLine( th - 1 ), terminal.getBackgroundColourLine( th - 1 ),
leftMargin, rightMargin, greyscale, palette );
}
// Draw lines
int y = originY;
for( int line = 0; line < th; line++ )
{
TextBuffer text = terminal.getLine( line );
TextBuffer colour = terminal.getTextColourLine( line );
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
fontRenderer.drawString( text, originX, y, colour, backgroundColour, leftMargin, rightMargin, greyscale, palette );
y += FixedWidthFontRenderer.FONT_HEIGHT;
}
if( tblink && tx >= 0 && ty >= 0 && tx < tw && ty < th )
{
TextBuffer cursor = new TextBuffer( '_', 1 );
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
cursor,
originX + FixedWidthFontRenderer.FONT_WIDTH * tx,
originY + FixedWidthFontRenderer.FONT_HEIGHT * ty,
cursorColour, null,
0, 0,
greyscale,
palette
);
}
FixedWidthFontRenderer.drawTerminal(
originX, originY,
terminal, !computer.isColour(), topMargin, bottomMargin, leftMargin, rightMargin
);
}
else
{
// Draw a black background
Colour black = Colour.Black;
GlStateManager.color4f( black.getR(), black.getG(), black.getB(), 1.0f );
try
{
int x = originX - leftMargin;
int y = originY - rightMargin;
int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin;
int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin;
int x = originX - leftMargin;
int y = originY - rightMargin;
int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin;
int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin;
client.getTextureManager().bindTexture( BACKGROUND );
Tessellator tesslector = Tessellator.getInstance();
BufferBuilder buffer = tesslector.getBuffer();
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX );
buffer.pos( x, y + height, 0 ).tex( 0 / 256.0, height / 256.0 ).endVertex();
buffer.pos( x + width, y + height, 0 ).tex( width / 256.0, height / 256.0 ).endVertex();
buffer.pos( x + width, y, 0 ).tex( width / 256.0, 0 / 256.0 ).endVertex();
buffer.pos( x, y, 0 ).tex( 0 / 256.0, 0 / 256.0 ).endVertex();
tesslector.draw();
}
finally
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
}
FixedWidthFontRenderer.drawEmptyTerminal( x, y, width, height );
}
}
}

View File

@@ -7,15 +7,12 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
@@ -27,7 +24,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.lwjgl.opengl.GL11;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.gui.GuiComputer.*;
/**
@@ -105,21 +103,11 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
if( computer != null && terminal != null )
{
// If we've a computer and terminal then attempt to render it.
renderTerminal( terminal, !computer.isColour(), width, height );
FixedWidthFontRenderer.drawTerminal( MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN );
}
else
{
// Otherwise render a plain background
Minecraft.getInstance().getTextureManager().bindTexture( BACKGROUND );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Colour black = Colour.Black;
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION );
renderTexture( buffer, 0, 0, 0, 0, width, height, black.getR(), black.getG(), black.getB() );
tessellator.draw();
FixedWidthFontRenderer.drawEmptyTerminal( 0, 0, width, height );
}
GlStateManager.enableDepthTest();
@@ -190,53 +178,6 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
GlStateManager.enableTexture();
}
private static void renderTerminal( Terminal terminal, boolean greyscale, int width, int height )
{
synchronized( terminal )
{
int termWidth = terminal.getWidth();
int termHeight = terminal.getHeight();
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
Palette palette = terminal.getPalette();
// Render top/bottom borders
TextBuffer emptyLine = new TextBuffer( ' ', termWidth );
fontRenderer.drawString(
emptyLine, MARGIN, 0,
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ), MARGIN, MARGIN, greyscale, palette
);
fontRenderer.drawString(
emptyLine, MARGIN, 2 * MARGIN + (termHeight - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
terminal.getTextColourLine( termHeight - 1 ), terminal.getBackgroundColourLine( termHeight - 1 ), MARGIN, MARGIN, greyscale, palette
);
// Render the actual text
for( int line = 0; line < termWidth; line++ )
{
TextBuffer text = terminal.getLine( line );
TextBuffer colour = terminal.getTextColourLine( line );
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
fontRenderer.drawString(
text, MARGIN, MARGIN + line * FONT_HEIGHT,
colour, backgroundColour, MARGIN, MARGIN, greyscale, palette
);
}
// And render the cursor;
int tx = terminal.getCursorX(), ty = terminal.getCursorY();
if( terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink() &&
tx >= 0 && ty >= 0 && tx < termWidth && ty < termHeight )
{
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
new TextBuffer( '_', 1 ), MARGIN + FONT_WIDTH * tx, MARGIN + FONT_HEIGHT * ty,
cursorColour, null, 0, 0, greyscale, palette
);
}
}
}
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, float r, float g, float b )
{
renderTexture( builder, x, y, textureX, textureY, width, height, width, height, r, g, b );

View File

@@ -63,11 +63,12 @@ public final class PrintoutRenderer
public static void drawText( int x, int y, int start, TextBuffer[] text, TextBuffer[] colours )
{
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{
fontRenderer.drawString( text[start + line], x, y + line * FONT_HEIGHT, colours[start + line], null, 0, 0, false, Palette.DEFAULT );
FixedWidthFontRenderer.drawString(
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], null, Palette.DEFAULT,
false, 0, 0
);
}
}
@@ -78,11 +79,13 @@ public final class PrintoutRenderer
GlStateManager.enableTexture();
GlStateManager.blendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{
fontRenderer.drawString( new TextBuffer( text[start + line] ), x, y + line * FONT_HEIGHT, new TextBuffer( colours[start + line] ), null, 0, 0, false, Palette.DEFAULT );
FixedWidthFontRenderer.drawString(
x, y + line * FONT_HEIGHT,
new TextBuffer( text[start + line] ), new TextBuffer( colours[start + line] ),
null, Palette.DEFAULT, false, 0, 0
);
}
}

View File

@@ -10,33 +10,29 @@ import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nonnull;
import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN;
public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
{
@Override
public void render( TileMonitor tileEntity, double posX, double posY, double posZ, float f, int i )
{
if( tileEntity != null )
{
renderMonitorAt( tileEntity, posX, posY, posZ, f, i );
}
}
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
private static void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
@Override
public void render( @Nonnull TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
{
// Render from the origin monitor
ClientMonitor originTerminal = monitor.getClientMonitor();
@@ -69,223 +65,129 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
float pitch = DirectionUtil.toPitchAngle( front );
GlStateManager.pushMatrix();
try
// Setup initial transform
GlStateManager.translated( posX + 0.5, posY + 0.5, posZ + 0.5 );
GlStateManager.rotatef( -yaw, 0.0f, 1.0f, 0.0f );
GlStateManager.rotatef( pitch, 1.0f, 0.0f, 0.0f );
GlStateManager.translated(
-0.5 + TileMonitor.RENDER_BORDER + RENDER_MARGIN,
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + RENDER_MARGIN) + 0,
0.5
);
double xSize = origin.getWidth() - 2.0 * (RENDER_MARGIN + TileMonitor.RENDER_BORDER);
double ySize = origin.getHeight() - 2.0 * (RENDER_MARGIN + TileMonitor.RENDER_BORDER);
// Get renderers
Minecraft mc = Minecraft.getInstance();
// Set up render state for monitors. We disable writing to the depth buffer (we draw a "blocker" later),
// and setup lighting so that we render with a glow.
GlStateManager.depthMask( false );
GLX.glMultiTexCoord2f( GLX.GL_TEXTURE1, 0xFFFF, 0xFFFF );
GlStateManager.disableLighting();
mc.gameRenderer.disableLightmap();
Terminal terminal = originTerminal.getTerminal();
if( terminal != null )
{
// Setup initial transform
GlStateManager.translated( posX + 0.5, posY + 0.5, posZ + 0.5 );
GlStateManager.rotatef( -yaw, 0.0f, 1.0f, 0.0f );
GlStateManager.rotatef( pitch, 1.0f, 0.0f, 0.0f );
GlStateManager.translated(
-0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN,
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN) + 0,
0.5
);
double xSize = origin.getWidth() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
double ySize = origin.getHeight() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
// Draw a terminal
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
// Get renderers
Minecraft mc = Minecraft.getInstance();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
GlStateManager.pushMatrix();
GlStateManager.scaled( (float) xScale, (float) -yScale, 1.0f );
// Get terminal
boolean redraw = originTerminal.pollTerminalChanged();
renderTerminal( originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
// Draw the contents
GlStateManager.depthMask( false );
GLX.glMultiTexCoord2f( GLX.GL_TEXTURE1, 0xFFFF, 0xFFFF );
GlStateManager.disableLighting();
mc.gameRenderer.disableLightmap();
try
{
Terminal terminal = originTerminal.getTerminal();
if( terminal != null )
{
Palette palette = terminal.getPalette();
// Allocate display lists
if( originTerminal.renderDisplayLists == null )
{
originTerminal.createLists();
redraw = true;
}
// Draw a terminal
boolean greyscale = !originTerminal.isColour();
int width = terminal.getWidth();
int height = terminal.getHeight();
int cursorX = terminal.getCursorX();
int cursorY = terminal.getCursorY();
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
GlStateManager.pushMatrix();
try
{
double xScale = xSize / (width * FixedWidthFontRenderer.FONT_WIDTH);
double yScale = ySize / (height * FixedWidthFontRenderer.FONT_HEIGHT);
GlStateManager.scaled( xScale, -yScale, 1.0 );
// Draw background
mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND );
if( redraw )
{
// Build background display list
GlStateManager.newList( originTerminal.renderDisplayLists[0], GL11.GL_COMPILE );
try
{
double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
double marginYSize = TileMonitor.RENDER_MARGIN / yScale;
double marginSquash = marginYSize / FixedWidthFontRenderer.FONT_HEIGHT;
// Top and bottom margins
GlStateManager.pushMatrix();
try
{
GlStateManager.scaled( 1.0, marginSquash, 1.0 );
GlStateManager.translated( 0.0, -marginYSize / marginSquash, 0.0 );
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( 0 ), marginXSize, marginXSize, greyscale, palette );
GlStateManager.translated( 0.0, (marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT) / marginSquash, 0.0 );
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( height - 1 ), marginXSize, marginXSize, greyscale, palette );
}
finally
{
GlStateManager.popMatrix();
}
// Backgrounds
for( int y = 0; y < height; y++ )
{
fontRenderer.drawStringBackgroundPart(
0, FixedWidthFontRenderer.FONT_HEIGHT * y,
terminal.getBackgroundColourLine( y ),
marginXSize, marginXSize,
greyscale,
palette
);
}
}
finally
{
GlStateManager.endList();
}
}
GlStateManager.callList( originTerminal.renderDisplayLists[0] );
GlStateManager.clearCurrentColor();
// Draw text
fontRenderer.bindFont();
if( redraw )
{
// Build text display list
GlStateManager.newList( originTerminal.renderDisplayLists[1], GL11.GL_COMPILE );
try
{
// Lines
for( int y = 0; y < height; y++ )
{
fontRenderer.drawStringTextPart(
0, FixedWidthFontRenderer.FONT_HEIGHT * y,
terminal.getLine( y ),
terminal.getTextColourLine( y ),
greyscale,
palette
);
}
}
finally
{
GlStateManager.endList();
}
}
GlStateManager.callList( originTerminal.renderDisplayLists[1] );
GlStateManager.clearCurrentColor();
// Draw cursor
fontRenderer.bindFont();
if( redraw )
{
// Build cursor display list
GlStateManager.newList( originTerminal.renderDisplayLists[2], GL11.GL_COMPILE );
try
{
// Cursor
if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height )
{
TextBuffer cursor = new TextBuffer( "_" );
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
cursor,
FixedWidthFontRenderer.FONT_WIDTH * cursorX,
FixedWidthFontRenderer.FONT_HEIGHT * cursorY,
cursorColour, null,
0, 0,
greyscale,
palette
);
}
}
finally
{
GlStateManager.endList();
}
}
if( FrameInfo.getGlobalCursorBlink() )
{
GlStateManager.callList( originTerminal.renderDisplayLists[2] );
GlStateManager.clearCurrentColor();
}
}
finally
{
GlStateManager.popMatrix();
}
}
else
{
// Draw a big black quad
mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND );
final Colour colour = Colour.Black;
final float r = colour.getR();
final float g = colour.getG();
final float b = colour.getB();
renderer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR );
renderer.pos( -TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0D ).tex( 0.0, 0.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( -TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).tex( 0.0, 1.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0D ).tex( 1.0, 0.0 ).color( r, g, b, 1.0f ).endVertex();
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).tex( 1.0, 1.0 ).color( r, g, b, 1.0f ).endVertex();
tessellator.draw();
}
}
finally
{
GlStateManager.depthMask( true );
mc.gameRenderer.enableLightmap();
GlStateManager.enableLighting();
}
// Draw the depth blocker
GlStateManager.colorMask( false, false, false, false );
try
{
mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND );
renderer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
renderer.pos( -TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
renderer.pos( -TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
tessellator.draw();
}
finally
{
GlStateManager.colorMask( true, true, true, true );
}
}
finally
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.popMatrix();
}
else
{
FixedWidthFontRenderer.drawEmptyTerminal(
-MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
);
}
// Tear down render state for monitors.
GlStateManager.depthMask( true );
mc.gameRenderer.enableLightmap();
GlStateManager.enableLighting();
// Draw the depth blocker
GlStateManager.colorMask( false, false, false, false );
FixedWidthFontRenderer.drawBlocker(
(float) -TileMonitor.RENDER_MARGIN, (float) TileMonitor.RENDER_MARGIN,
(float) (xSize + 2 * TileMonitor.RENDER_MARGIN), (float) -(ySize + TileMonitor.RENDER_MARGIN * 2)
);
GlStateManager.colorMask( true, true, true, true );
GlStateManager.popMatrix();
}
private static void renderTerminal( ClientMonitor monitor, float xMargin, float yMargin )
{
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
boolean redraw = monitor.pollTerminalChanged();
// Setup the buffers if needed. We get the renderer here, to avoid the (unlikely) race condition between
// creating the buffers and rendering.
MonitorRenderer renderer = MonitorRenderer.current();
if( monitor.createBuffer( renderer ) ) redraw = true;
FixedWidthFontRenderer.bindFont();
switch( renderer )
{
case VBO:
{
VertexBuffer vbo = monitor.buffer;
if( redraw )
{
renderTerminalTo( monitor, buffer, xMargin, yMargin );
buffer.finishDrawing();
buffer.reset();
vbo.bufferData( buffer.getByteBuffer() );
}
vbo.bindBuffer();
setupBufferFormat();
vbo.drawArrays( GL11.GL_TRIANGLES );
VertexBuffer.unbindBuffer();
break;
}
}
// We don't draw the cursor with a buffer, as it's dynamic and so we'll end up refreshing far more than is
// reasonable.
FixedWidthFontRenderer.begin( buffer );
FixedWidthFontRenderer.drawCursor( buffer, 0, 0, monitor.getTerminal(), !monitor.isColour() );
tessellator.draw();
}
private static void renderTerminalTo( ClientMonitor monitor, BufferBuilder buffer, float xMargin, float yMargin )
{
FixedWidthFontRenderer.begin( buffer );
FixedWidthFontRenderer.drawTerminalWithoutCursor(
buffer, 0, 0,
monitor.getTerminal(), !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
);
}
public static void setupBufferFormat()
{
int stride = FixedWidthFontRenderer.POSITION_COLOR_TEX.getSize();
GlStateManager.vertexPointer( 3, GL11.GL_FLOAT, stride, 0 );
GlStateManager.enableClientState( GL11.GL_VERTEX_ARRAY );
GlStateManager.colorPointer( 4, GL11.GL_UNSIGNED_BYTE, stride, 12 );
GlStateManager.enableClientState( GL11.GL_COLOR_ARRAY );
GlStateManager.texCoordPointer( 2, GL11.GL_FLOAT, stride, 16 );
GlStateManager.enableClientState( GL11.GL_TEXTURE_COORD_ARRAY );
}
}

View File

@@ -22,6 +22,11 @@ import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.HashMap;
import java.util.Map;
import java.util.OptionalLong;
import java.util.function.Function;
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
@@ -76,6 +81,8 @@ public class FSAPI implements ILuaAPI
"getFreeSpace",
"find",
"getDir",
"getCapacity",
"attributes",
};
}
@@ -315,9 +322,8 @@ public class FSAPI implements ILuaAPI
throw new LuaException( e.getMessage() );
}
}
case 14:
case 14: // find
{
// find
String path = getString( args, 0 );
try
{
@@ -329,15 +335,50 @@ public class FSAPI implements ILuaAPI
throw new LuaException( e.getMessage() );
}
}
case 15:
case 15: // getDir
{
// getDir
String path = getString( args, 0 );
return new Object[] { FileSystem.getDirectory( path ) };
}
case 16: // getCapacity
{
String path = getString( args, 0 );
try
{
OptionalLong capacity = m_fileSystem.getCapacity( path );
return new Object[] { capacity.isPresent() ? capacity.getAsLong() : null };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 17: // attributes
{
String path = getString( args, 0 );
try
{
BasicFileAttributes attributes = m_fileSystem.getAttributes( path );
Map<String, Object> result = new HashMap<>();
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
result.put( "created", getFileTime( attributes.creationTime() ) );
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
result.put( "isDir", attributes.isDirectory() );
return new Object[] { result };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
default:
assert false;
return null;
}
}
private static long getFileTime( FileTime time )
{
return time == null ? 0 : time.toMillis();
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nullable;
/**
* A Lua exception which does not contain its stack trace.
*/
public class FastLuaException extends LuaException
{
private static final long serialVersionUID = 5957864899303561143L;
public FastLuaException( @Nullable String message )
{
super( message );
}
public FastLuaException( @Nullable String message, int level )
{
super( message, level );
}
@Override
public synchronized Throwable fillInStackTrace()
{
return this;
}
}

View File

@@ -18,6 +18,8 @@ import javax.annotation.Nullable;
public interface IAPIEnvironment
{
String TIMER_EVENT = "timer";
@FunctionalInterface
interface IPeripheralChangeListener
{
@@ -64,6 +66,10 @@ public interface IAPIEnvironment
void setLabel( @Nullable String label );
int startTimer( long ticks );
void cancelTimer( int id );
void addTrackingChange( @Nonnull TrackingField field, long change );
default void addTrackingChange( @Nonnull TrackingField field )

View File

@@ -9,6 +9,8 @@ import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nonnull;
import java.time.Instant;
@@ -24,24 +26,12 @@ public class OSAPI implements ILuaAPI
{
private IAPIEnvironment m_apiEnvironment;
private final Map<Integer, Timer> m_timers;
private final Map<Integer, Alarm> m_alarms;
private final Int2ObjectMap<Alarm> m_alarms = new Int2ObjectOpenHashMap<>();
private int m_clock;
private double m_time;
private int m_day;
private int m_nextTimerToken;
private int m_nextAlarmToken;
private static class Timer
{
int m_ticksLeft;
Timer( int ticksLeft )
{
m_ticksLeft = ticksLeft;
}
}
private int m_nextAlarmToken = 0;
private static class Alarm implements Comparable<Alarm>
{
@@ -66,10 +56,6 @@ public class OSAPI implements ILuaAPI
public OSAPI( IAPIEnvironment environment )
{
m_apiEnvironment = environment;
m_nextTimerToken = 0;
m_nextAlarmToken = 0;
m_timers = new HashMap<>();
m_alarms = new HashMap<>();
}
// ILuaAPI implementation
@@ -87,11 +73,6 @@ public class OSAPI implements ILuaAPI
m_day = m_apiEnvironment.getComputerEnvironment().getDay();
m_clock = 0;
synchronized( m_timers )
{
m_timers.clear();
}
synchronized( m_alarms )
{
m_alarms.clear();
@@ -101,26 +82,7 @@ public class OSAPI implements ILuaAPI
@Override
public void update()
{
synchronized( m_timers )
{
// Update the clock
m_clock++;
// Countdown all of our active timers
Iterator<Map.Entry<Integer, Timer>> it = m_timers.entrySet().iterator();
while( it.hasNext() )
{
Map.Entry<Integer, Timer> entry = it.next();
Timer timer = entry.getValue();
timer.m_ticksLeft--;
if( timer.m_ticksLeft <= 0 )
{
// Queue the "timer" event
queueLuaEvent( "timer", new Object[] { entry.getKey() } );
it.remove();
}
}
}
m_clock++;
// Wait for all of our alarms
synchronized( m_alarms )
@@ -155,11 +117,6 @@ public class OSAPI implements ILuaAPI
@Override
public void shutdown()
{
synchronized( m_timers )
{
m_timers.clear();
}
synchronized( m_alarms )
{
m_alarms.clear();
@@ -229,11 +186,8 @@ public class OSAPI implements ILuaAPI
{
// startTimer
double timer = getFiniteDouble( args, 0 );
synchronized( m_timers )
{
m_timers.put( m_nextTimerToken, new Timer( (int) Math.round( timer / 0.05 ) ) );
return new Object[] { m_nextTimerToken++ };
}
int id = m_apiEnvironment.startTimer( Math.round( timer / 0.05 ) );
return new Object[] { id };
}
case 2:
{
@@ -278,10 +232,7 @@ public class OSAPI implements ILuaAPI
return null;
}
case 10: // clock
synchronized( m_timers )
{
return new Object[] { m_clock * 0.05 };
}
return new Object[] { m_clock * 0.05 };
case 11:
{
// time
@@ -345,10 +296,7 @@ public class OSAPI implements ILuaAPI
{
// cancelTimer
int token = getInt( args, 0 );
synchronized( m_timers )
{
m_timers.remove( token );
}
m_apiEnvironment.cancelTimer( token );
return null;
}
case 14:

View File

@@ -383,22 +383,30 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
String methodName = getString( args, 1 );
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
if( side != null )
if( side == null ) throw new LuaException( "No peripheral attached" );
PeripheralWrapper p;
synchronized( m_peripherals )
{
PeripheralWrapper p;
synchronized( m_peripherals )
{
p = m_peripherals[side.ordinal()];
}
if( p != null )
{
return p.call( context, methodName, methodArgs );
}
p = m_peripherals[side.ordinal()];
}
if( p == null ) throw new LuaException( "No peripheral attached" );
try
{
return p.call( context, methodName, methodArgs );
}
catch( LuaException e )
{
// We increase the error level by one in order to shift the error level to where peripheral.call was
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
throw e;
}
throw new LuaException( "No peripheral attached" );
}
default:
return null;
}
}
}

View File

@@ -40,7 +40,11 @@ import java.util.concurrent.Future;
*/
public class Websocket extends Resource<Websocket>
{
public static final int MAX_MESSAGE_SIZE = 64 * 1024;
/**
* We declare the maximum size to be 2^30 bytes. While messages can be much longer, we set an arbitrary limit as
* working with larger messages (especially within a Lua VM) is absurd.
*/
public static final int MAX_MESSAGE_SIZE = 1 << 30;
static final String SUCCESS_EVENT = "websocket_success";
static final String FAILURE_EVENT = "websocket_failure";

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http.websocket;
import com.google.common.base.Objects;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ArgumentHelper;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
@@ -23,6 +24,7 @@ import java.io.Closeable;
import java.util.Arrays;
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
import static dan200.computercraft.core.apis.IAPIEnvironment.TIMER_EVENT;
import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT;
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
@@ -53,7 +55,21 @@ public class WebsocketHandle implements ILuaObject, Closeable
switch( method )
{
case 0: // receive
{
checkOpen();
int timeoutId;
if( arguments.length <= 0 || arguments[0] == null )
{
// We do this rather odd argument validation to ensure we can tell the difference between a
// negative timeout and an absent one.
timeoutId = -1;
}
else
{
double timeout = ArgumentHelper.getFiniteDouble( arguments, 0 );
timeoutId = websocket.environment().startTimer( Math.round( timeout / 0.05 ) );
}
while( true )
{
Object[] event = context.pullEvent( null );
@@ -63,9 +79,17 @@ public class WebsocketHandle implements ILuaObject, Closeable
}
else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
{
// If the socket is closed abort.
return null;
}
else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT )
&& event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId )
{
// If we received a matching timer event then abort.
return null;
}
}
}
case 1: // send
{

View File

@@ -181,7 +181,7 @@ public class Computer
executor.tick();
// Update the environment's internal state.
internalEnvironment.update();
internalEnvironment.tick();
// Propagate the environment's output to the world.
if( internalEnvironment.updateOutput() ) externalOutputChanged.set( true );

View File

@@ -426,6 +426,7 @@ final class ComputerExecutor
}
// Init APIs
computer.getEnvironment().reset();
for( ILuaAPI api : apis ) api.startup();
// Init lua
@@ -469,6 +470,7 @@ final class ComputerExecutor
// Shutdown our APIs
for( ILuaAPI api : apis ) api.shutdown();
computer.getEnvironment().reset();
// Unload filesystem
if( fileSystem != null )

View File

@@ -5,6 +5,7 @@
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.apis.IAPIEnvironment;
@@ -12,9 +13,12 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Iterator;
/**
* Represents the "environment" that a {@link Computer} exists in.
@@ -53,6 +57,9 @@ public final class Environment implements IAPIEnvironment
private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
private IPeripheralChangeListener peripheralListener = null;
private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>();
private int nextTimerToken = 0;
Environment( Computer computer )
{
this.computer = computer;
@@ -198,17 +205,47 @@ public final class Environment implements IAPIEnvironment
}
/**
* Called on the main thread to update the internal state of the computer.
* Called when the computer starts up or shuts down, to reset any internal state.
*
* This just queues a {@code redstone} event if the input has changed.
* @see ILuaAPI#startup()
* @see ILuaAPI#shutdown()
*/
void update()
void reset()
{
synchronized( timers )
{
timers.clear();
}
}
/**
* Called on the main thread to update the internal state of the computer.
*/
void tick()
{
if( inputChanged )
{
inputChanged = false;
queueEvent( "redstone", null );
}
synchronized( timers )
{
// Countdown all of our active timers
Iterator<Int2ObjectMap.Entry<Timer>> it = timers.int2ObjectEntrySet().iterator();
while( it.hasNext() )
{
Int2ObjectMap.Entry<Timer> entry = it.next();
Timer timer = entry.getValue();
timer.ticksLeft--;
if( timer.ticksLeft <= 0 )
{
// Queue the "timer" event
queueEvent( TIMER_EVENT, new Object[] { entry.getIntKey() } );
it.remove();
}
}
}
}
/**
@@ -303,9 +340,38 @@ public final class Environment implements IAPIEnvironment
computer.setLabel( label );
}
@Override
public int startTimer( long ticks )
{
synchronized( timers )
{
timers.put( nextTimerToken, new Timer( ticks ) );
return nextTimerToken++;
}
}
@Override
public void cancelTimer( int id )
{
synchronized( timers )
{
timers.remove( id );
}
}
@Override
public void addTrackingChange( @Nonnull TrackingField field, long change )
{
Tracking.addValue( computer, field, change );
}
private static class Timer
{
long ticksLeft;
Timer( long ticksLeft )
{
this.ticksLeft = ticksLeft;
}
}
}

View File

@@ -12,6 +12,7 @@ import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -143,4 +144,19 @@ public class ComboMount implements IMount
}
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
for( int i = m_parts.length - 1; i >= 0; --i )
{
IMount part = m_parts[i];
if( part.exists( path ) && !part.isDirectory( path ) )
{
return part.getAttributes( path );
}
}
throw new FileOperationException( path, "No such file" );
}
}

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.filesystem;
import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IWritableMount;
@@ -13,11 +14,11 @@ import javax.annotation.Nonnull;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.List;
import java.util.OptionalLong;
import java.util.Set;
public class FileMount implements IWritableMount
@@ -224,6 +225,19 @@ public class FileMount implements IWritableMount
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
if( created() )
{
File file = getRealPath( path );
if( file.exists() ) return Files.readAttributes( file.toPath(), BasicFileAttributes.class );
}
throw new FileOperationException( path, "No such file" );
}
// IWritableMount implementation
@Override
@@ -360,6 +374,13 @@ public class FileMount implements IWritableMount
return Math.max( m_capacity - m_usedSpace, 0 );
}
@Nonnull
@Override
public OptionalLong getCapacity()
{
return OptionalLong.of( m_capacity - MINIMUM_FILE_SIZE );
}
private File getRealPath( String path )
{
return new File( m_rootPath, path );
@@ -382,23 +403,46 @@ public class FileMount implements IWritableMount
}
}
private static class Visitor extends SimpleFileVisitor<Path>
{
long size;
@Override
public FileVisitResult preVisitDirectory( Path dir, BasicFileAttributes attrs )
{
size += MINIMUM_FILE_SIZE;
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
{
size += Math.max( attrs.size(), MINIMUM_FILE_SIZE );
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed( Path file, IOException exc )
{
ComputerCraft.log.error( "Error computing file size for {}", file, exc );
return FileVisitResult.CONTINUE;
}
}
private static long measureUsedSpace( File file )
{
if( !file.exists() ) return 0;
if( file.isDirectory() )
try
{
long size = MINIMUM_FILE_SIZE;
String[] contents = file.list();
for( String content : contents )
{
size += measureUsedSpace( new File( file, content ) );
}
return size;
Visitor visitor = new Visitor();
Files.walkFileTree( file.toPath(), visitor );
return visitor.size;
}
else
catch( IOException e )
{
return Math.max( file.length(), MINIMUM_FILE_SIZE );
ComputerCraft.log.error( "Error computing file size for {}", file, e );
return 0;
}
}
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.core.filesystem;
import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
@@ -23,309 +22,23 @@ import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern;
public class FileSystem
{
private static class MountWrapper
{
private String m_label;
private String m_location;
private IMount m_mount;
private IWritableMount m_writableMount;
MountWrapper( String label, String location, IMount mount )
{
m_label = label;
m_location = location;
m_mount = mount;
m_writableMount = null;
}
MountWrapper( String label, String location, IWritableMount mount )
{
this( label, location, (IMount) mount );
m_writableMount = mount;
}
public String getLabel()
{
return m_label;
}
public String getLocation()
{
return m_location;
}
public long getFreeSpace()
{
if( m_writableMount == null )
{
return 0;
}
try
{
return m_writableMount.getRemainingSpace();
}
catch( IOException e )
{
return 0;
}
}
public boolean isReadOnly( String path )
{
return m_writableMount == null;
}
// IMount forwarders:
public boolean exists( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return m_mount.exists( path );
}
catch( IOException e )
{
throw new FileSystemException( e.getMessage() );
}
}
public boolean isDirectory( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return m_mount.exists( path ) && m_mount.isDirectory( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void list( String path, List<String> contents ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
{
m_mount.list( path, contents );
}
else
{
throw localExceptionOf( path, "Not a directory" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public long getSize( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) )
{
if( m_mount.isDirectory( path ) )
{
return 0;
}
else
{
return m_mount.getSize( path );
}
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public ReadableByteChannel openForRead( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) && !m_mount.isDirectory( path ) )
{
return m_mount.openChannelForRead( path );
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
// IWritableMount forwarders:
public void makeDirectory( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( m_mount.exists( path ) )
{
if( !m_mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
}
else
{
m_writableMount.makeDirectory( path );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void delete( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
try
{
path = toLocal( path );
if( m_mount.exists( path ) )
{
m_writableMount.delete( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForWrite( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
if( !path.isEmpty() )
{
String dir = getDirectory( path );
if( !dir.isEmpty() && !m_mount.exists( path ) )
{
m_writableMount.makeDirectory( dir );
}
}
return m_writableMount.openChannelForWrite( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForAppend( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( !m_mount.exists( path ) )
{
if( !path.isEmpty() )
{
String dir = getDirectory( path );
if( !dir.isEmpty() && !m_mount.exists( path ) )
{
m_writableMount.makeDirectory( dir );
}
}
return m_writableMount.openChannelForWrite( path );
}
else if( m_mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
return m_writableMount.openChannelForAppend( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
private String toLocal( String path )
{
return FileSystem.toLocal( path, m_location );
}
private FileSystemException localExceptionOf( IOException e )
{
if( !m_location.isEmpty() && e instanceof FileOperationException )
{
FileOperationException ex = (FileOperationException) e;
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
}
return new FileSystemException( e.getMessage() );
}
private FileSystemException localExceptionOf( String path, String message )
{
if( !m_location.isEmpty() ) path = path.isEmpty() ? m_location : m_location + "/" + path;
return exceptionOf( path, message );
}
private static FileSystemException exceptionOf( String path, String message )
{
return new FileSystemException( "/" + path + ": " + message );
}
}
/**
* Maximum depth that {@link #copyRecursive(String, MountWrapper, String, MountWrapper, int)} will descend into.
*
* This is a pretty arbitrary value, though hopefully it is large enough that it'll never be normally hit. This
* exists to prevent it overflowing if it ever gets into an infinite loop.
*/
private static final int MAX_COPY_DEPTH = 128;
private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this );
private final Map<String, MountWrapper> m_mounts = new HashMap<>();
private final Map<String, MountWrapper> mounts = new HashMap<>();
private final HashMap<WeakReference<FileSystemWrapper<?>>, ChannelWrapper<?>> m_openFiles = new HashMap<>();
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
@@ -355,10 +68,7 @@ public class FileSystem
{
if( mount == null ) throw new NullPointerException();
location = sanitizePath( location );
if( location.contains( ".." ) )
{
throw new FileSystemException( "Cannot mount below the root" );
}
if( location.contains( ".." ) ) throw new FileSystemException( "Cannot mount below the root" );
mount( new MountWrapper( label, location, mount ) );
}
@@ -379,14 +89,13 @@ public class FileSystem
private synchronized void mount( MountWrapper wrapper )
{
String location = wrapper.getLocation();
m_mounts.remove( location );
m_mounts.put( location, wrapper );
mounts.remove( location );
mounts.put( location, wrapper );
}
public synchronized void unmount( String path )
{
path = sanitizePath( path );
m_mounts.remove( path );
mounts.remove( sanitizePath( path ) );
}
public synchronized String combine( String path, String childPath )
@@ -430,27 +139,20 @@ public class FileSystem
public static String getName( String path )
{
path = sanitizePath( path, true );
if( path.isEmpty() )
{
return "root";
}
if( path.isEmpty() ) return "root";
int lastSlash = path.lastIndexOf( '/' );
if( lastSlash >= 0 )
{
return path.substring( lastSlash + 1 );
}
else
{
return path;
}
return lastSlash >= 0 ? path.substring( lastSlash + 1 ) : path;
}
public synchronized long getSize( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getSize( path );
return getMount( sanitizePath( path ) ).getSize( sanitizePath( path ) );
}
public synchronized BasicFileAttributes getAttributes( String path ) throws FileSystemException
{
return getMount( sanitizePath( path ) ).getAttributes( sanitizePath( path ) );
}
public synchronized String[] list( String path ) throws FileSystemException
@@ -463,7 +165,7 @@ public class FileSystem
mount.list( path, list );
// Add any mounts that are mounted at this location
for( MountWrapper otherMount : m_mounts.values() )
for( MountWrapper otherMount : mounts.values() )
{
if( getDirectory( otherMount.getLocation() ).equals( path ) )
{
@@ -611,15 +313,13 @@ public class FileSystem
{
throw new FileSystemException( "/" + sourcePath + ": Can't copy a directory inside itself" );
}
copyRecursive( sourcePath, getMount( sourcePath ), destPath, getMount( destPath ) );
copyRecursive( sourcePath, getMount( sourcePath ), destPath, getMount( destPath ), 0 );
}
private synchronized void copyRecursive( String sourcePath, MountWrapper sourceMount, String destinationPath, MountWrapper destinationMount ) throws FileSystemException
private synchronized void copyRecursive( String sourcePath, MountWrapper sourceMount, String destinationPath, MountWrapper destinationMount, int depth ) throws FileSystemException
{
if( !sourceMount.exists( sourcePath ) )
{
return;
}
if( !sourceMount.exists( sourcePath ) ) return;
if( depth >= MAX_COPY_DEPTH ) throw new FileSystemException( "Too many directories to copy" );
if( sourceMount.isDirectory( sourcePath ) )
{
@@ -634,7 +334,8 @@ public class FileSystem
{
copyRecursive(
combine( sourcePath, child ), sourceMount,
combine( destinationPath, child ), destinationMount
combine( destinationPath, child ), destinationMount,
depth + 1
);
}
}
@@ -726,17 +427,25 @@ public class FileSystem
return null;
}
public long getFreeSpace( String path ) throws FileSystemException
public synchronized long getFreeSpace( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getFreeSpace();
}
private MountWrapper getMount( String path ) throws FileSystemException
@Nonnull
public synchronized OptionalLong getCapacity( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getCapacity();
}
private synchronized MountWrapper getMount( String path ) throws FileSystemException
{
// Return the deepest mount that contains a given path
Iterator<MountWrapper> it = m_mounts.values().iterator();
Iterator<MountWrapper> it = mounts.values().iterator();
MountWrapper match = null;
int matchLength = 999;
while( it.hasNext() )
@@ -854,8 +563,8 @@ public class FileSystem
public static boolean contains( String pathA, String pathB )
{
pathA = sanitizePath( pathA );
pathB = sanitizePath( pathB );
pathA = sanitizePath( pathA ).toLowerCase( Locale.ROOT );
pathB = sanitizePath( pathB ).toLowerCase( Locale.ROOT );
if( pathB.equals( ".." ) )
{

View File

@@ -23,6 +23,9 @@ import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@@ -221,6 +224,20 @@ public class JarMount implements IMount
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
FileEntry file = get( path );
if( file != null )
{
ZipEntry entry = zip.getEntry( file.path );
if( entry != null ) return new ZipEntryAttributes( entry );
}
throw new FileOperationException( path, "No such file" );
}
private static class FileEntry
{
String path;
@@ -261,4 +278,76 @@ public class JarMount implements IMount
Reference<? extends JarMount> next;
while( (next = MOUNT_QUEUE.poll()) != null ) IoUtil.closeQuietly( ((MountReference) next).file );
}
private static class ZipEntryAttributes implements BasicFileAttributes
{
private final ZipEntry entry;
ZipEntryAttributes( ZipEntry entry )
{
this.entry = entry;
}
@Override
public FileTime lastModifiedTime()
{
return orEpoch( entry.getLastModifiedTime() );
}
@Override
public FileTime lastAccessTime()
{
return orEpoch( entry.getLastAccessTime() );
}
@Override
public FileTime creationTime()
{
FileTime time = entry.getCreationTime();
return time == null ? lastModifiedTime() : time;
}
@Override
public boolean isRegularFile()
{
return !entry.isDirectory();
}
@Override
public boolean isDirectory()
{
return entry.isDirectory();
}
@Override
public boolean isSymbolicLink()
{
return false;
}
@Override
public boolean isOther()
{
return false;
}
@Override
public long size()
{
return entry.getSize();
}
@Override
public Object fileKey()
{
return null;
}
private static final FileTime EPOCH = FileTime.from( Instant.EPOCH );
private static FileTime orEpoch( FileTime time )
{
return time == null ? EPOCH : time;
}
}
}

View File

@@ -0,0 +1,312 @@
/*
* 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.core.filesystem;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.OptionalLong;
class MountWrapper
{
private String label;
private String location;
private IMount mount;
private IWritableMount writableMount;
MountWrapper( String label, String location, IMount mount )
{
this.label = label;
this.location = location;
this.mount = mount;
writableMount = null;
}
MountWrapper( String label, String location, IWritableMount mount )
{
this( label, location, (IMount) mount );
writableMount = mount;
}
public String getLabel()
{
return label;
}
public String getLocation()
{
return location;
}
public long getFreeSpace()
{
if( writableMount == null ) return 0;
try
{
return writableMount.getRemainingSpace();
}
catch( IOException e )
{
return 0;
}
}
public OptionalLong getCapacity()
{
return writableMount == null ? OptionalLong.empty() : writableMount.getCapacity();
}
public boolean isReadOnly( String path )
{
return writableMount == null;
}
public boolean exists( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return mount.exists( path );
}
catch( IOException e )
{
throw new FileSystemException( e.getMessage() );
}
}
public boolean isDirectory( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return mount.exists( path ) && mount.isDirectory( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void list( String path, List<String> contents ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) || !mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Not a directory" );
}
mount.list( path, contents );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public long getSize( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
return mount.isDirectory( path ) ? 0 : mount.getSize( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
@Nonnull
public BasicFileAttributes getAttributes( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
return mount.getAttributes( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public ReadableByteChannel openForRead( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( mount.exists( path ) && !mount.isDirectory( path ) )
{
return mount.openChannelForRead( path );
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void makeDirectory( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( mount.exists( path ) )
{
if( !mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
}
else
{
writableMount.makeDirectory( path );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void delete( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
try
{
path = toLocal( path );
if( mount.exists( path ) )
{
writableMount.delete( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForWrite( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( mount.exists( path ) && mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
if( !path.isEmpty() )
{
String dir = FileSystem.getDirectory( path );
if( !dir.isEmpty() && !mount.exists( path ) )
{
writableMount.makeDirectory( dir );
}
}
return writableMount.openChannelForWrite( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForAppend( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( !mount.exists( path ) )
{
if( !path.isEmpty() )
{
String dir = FileSystem.getDirectory( path );
if( !dir.isEmpty() && !mount.exists( path ) )
{
writableMount.makeDirectory( dir );
}
}
return writableMount.openChannelForWrite( path );
}
else if( mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
return writableMount.openChannelForAppend( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
private String toLocal( String path )
{
return FileSystem.toLocal( path, location );
}
private FileSystemException localExceptionOf( IOException e )
{
if( !location.isEmpty() && e instanceof FileOperationException )
{
FileOperationException ex = (FileOperationException) e;
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
}
return new FileSystemException( e.getMessage() );
}
private FileSystemException localExceptionOf( String path, String message )
{
if( !location.isEmpty() ) path = path.isEmpty() ? location : location + "/" + path;
return exceptionOf( path, message );
}
private static FileSystemException exceptionOf( String path, String message )
{
return new FileSystemException( "/" + path + ": " + message );
}
}

View File

@@ -11,43 +11,42 @@ import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
public class SubMount implements IMount
{
private IMount m_parent;
private String m_subPath;
private IMount parent;
private String subPath;
public SubMount( IMount parent, String subPath )
{
m_parent = parent;
m_subPath = subPath;
this.parent = parent;
this.subPath = subPath;
}
// IMount implementation
@Override
public boolean exists( @Nonnull String path ) throws IOException
{
return m_parent.exists( getFullPath( path ) );
return parent.exists( getFullPath( path ) );
}
@Override
public boolean isDirectory( @Nonnull String path ) throws IOException
{
return m_parent.isDirectory( getFullPath( path ) );
return parent.isDirectory( getFullPath( path ) );
}
@Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{
m_parent.list( getFullPath( path ), contents );
parent.list( getFullPath( path ), contents );
}
@Override
public long getSize( @Nonnull String path ) throws IOException
{
return m_parent.getSize( getFullPath( path ) );
return parent.getSize( getFullPath( path ) );
}
@Nonnull
@@ -55,25 +54,25 @@ public class SubMount implements IMount
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException
{
return m_parent.openForRead( getFullPath( path ) );
return parent.openForRead( getFullPath( path ) );
}
@Nonnull
@Override
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
return m_parent.openChannelForRead( getFullPath( path ) );
return parent.openChannelForRead( getFullPath( path ) );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
return parent.getAttributes( getFullPath( path ) );
}
private String getFullPath( String path )
{
if( path.isEmpty() )
{
return m_subPath;
}
else
{
return m_subPath + "/" + path;
}
return path.isEmpty() ? subPath : subPath + "/" + path;
}
}

View File

@@ -92,6 +92,7 @@ public class CobaltLuaMachine implements ILuaMachine
m_globals.load( state, new MathLib() );
m_globals.load( state, new CoroutineLib() );
m_globals.load( state, new Bit32Lib() );
m_globals.load( state, new Utf8Lib() );
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
// Remove globals we don't want to expose

View File

@@ -5,8 +5,9 @@
*/
package dan200.computercraft.shared.peripheral.monitor;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.shared.common.ClientTerminal;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@@ -15,7 +16,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ClientMonitor extends ClientTerminal
public final class ClientMonitor extends ClientTerminal
{
private static final Set<ClientMonitor> allMonitors = new HashSet<>();
@@ -23,7 +24,8 @@ public class ClientMonitor extends ClientTerminal
public long lastRenderFrame = -1;
public BlockPos lastRenderPos = null;
public int[] renderDisplayLists = null;
public VertexBuffer buffer;
public ClientMonitor( boolean colour, TileMonitor origin )
{
@@ -36,41 +38,59 @@ public class ClientMonitor extends ClientTerminal
return origin;
}
/**
* Create the appropriate buffer if needed.
*
* @param renderer The renderer to use. This can be fetched from {@link MonitorRenderer#current()}.
* @return If a buffer was created. This will return {@code false} if we already have an appropriate buffer,
* or this mode does not require one.
*/
@OnlyIn( Dist.CLIENT )
public void createLists()
public boolean createBuffer( MonitorRenderer renderer )
{
if( renderDisplayLists == null )
switch( renderer )
{
renderDisplayLists = new int[3];
case VBO:
if( buffer != null ) return false;
for( int i = 0; i < renderDisplayLists.length; i++ )
{
renderDisplayLists[i] = GlStateManager.genLists( 1 );
}
deleteBuffers();
buffer = new VertexBuffer( FixedWidthFontRenderer.POSITION_COLOR_TEX );
addMonitor();
return true;
synchronized( allMonitors )
{
allMonitors.add( this );
}
default:
return false;
}
}
private void addMonitor()
{
synchronized( allMonitors )
{
allMonitors.add( this );
}
}
private void deleteBuffers()
{
if( buffer != null )
{
buffer.deleteGlBuffers();
buffer = null;
}
}
@OnlyIn( Dist.CLIENT )
public void destroy()
{
if( renderDisplayLists != null )
if( buffer != null )
{
synchronized( allMonitors )
{
allMonitors.remove( this );
}
for( int list : renderDisplayLists )
{
GlStateManager.deleteLists( list, 1 );
}
renderDisplayLists = null;
deleteBuffers();
}
}
@@ -82,14 +102,7 @@ public class ClientMonitor extends ClientTerminal
for( Iterator<ClientMonitor> iterator = allMonitors.iterator(); iterator.hasNext(); )
{
ClientMonitor monitor = iterator.next();
if( monitor.renderDisplayLists != null )
{
for( int list : monitor.renderDisplayLists )
{
GlStateManager.deleteLists( list, 1 );
}
monitor.renderDisplayLists = null;
}
monitor.deleteBuffers();
iterator.remove();
}

View File

@@ -0,0 +1,98 @@
/*
* 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 com.mojang.blaze3d.platform.GLX;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import javax.annotation.Nonnull;
import java.util.Locale;
/**
* The render type to use for monitors.
*
* @see TileEntityMonitorRenderer
* @see ClientMonitor
*/
public enum MonitorRenderer
{
/**
* Determine the best monitor backend.
*/
BEST,
/**
* Render using VBOs.
*
* @see net.minecraft.client.renderer.vertex.VertexBuffer
*/
VBO;
private static final MonitorRenderer[] VALUES = values();
public static final String[] NAMES;
private final String displayName = "gui.computercraft:config.peripheral.monitor_renderer." + name().toLowerCase( Locale.ROOT );
static
{
NAMES = new String[VALUES.length];
for( int i = 0; i < VALUES.length; i++ ) NAMES[i] = VALUES[i].displayName();
}
public String displayName()
{
return displayName;
}
@Nonnull
public static MonitorRenderer ofString( String name )
{
for( MonitorRenderer backend : VALUES )
{
if( backend.displayName.equalsIgnoreCase( name ) || backend.name().equalsIgnoreCase( name ) )
{
return backend;
}
}
ComputerCraft.log.warn( "Unknown monitor renderer {}. Falling back to default.", name );
return BEST;
}
/**
* Get the current renderer to use.
*
* @return The current renderer. Will not return {@link MonitorRenderer#BEST}.
*/
@Nonnull
public static MonitorRenderer current()
{
MonitorRenderer current = ComputerCraft.monitorRenderer;
switch( current )
{
case BEST:
return best();
case VBO:
if( !GLX.useVbo() )
{
ComputerCraft.log.warn( "VBOs are not supported on your graphics card. Falling back to default." );
ComputerCraft.monitorRenderer = BEST;
return best();
}
return VBO;
default:
return current;
}
}
private static MonitorRenderer best()
{
return VBO;
}
}

View File

@@ -48,13 +48,13 @@ public class Palette
{
if( i >= 0 && i < colours.length )
{
setColour( i, Colour.values()[i] );
setColour( i, Colour.VALUES[i] );
}
}
public void resetColours()
{
for( int i = 0; i < Colour.values().length; i++ )
for( int i = 0; i < Colour.VALUES.length; i++ )
{
resetColour( i );
}