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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 )
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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( ".." ) )
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user