Merge branch 'master' into mc-1.14.x
This commit is contained in:
commit
4be0b15afa
|
@ -32,6 +32,9 @@ jobs:
|
||||||
name: CC-Tweaked
|
name: CC-Tweaked
|
||||||
path: build/libs
|
path: build/libs
|
||||||
|
|
||||||
|
- name: Upload Coverage
|
||||||
|
run: bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
||||||
lint-lua:
|
lint-lua:
|
||||||
name: Lint Lua
|
name: Lint Lua
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
10
build.gradle
10
build.gradle
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "checkstyle"
|
id "checkstyle"
|
||||||
|
id "jacoco"
|
||||||
id "com.github.hierynomus.license" version "0.15.0"
|
id "com.github.hierynomus.license" version "0.15.0"
|
||||||
id "com.matthewprenger.cursegradle" version "1.3.0"
|
id "com.matthewprenger.cursegradle" version "1.3.0"
|
||||||
id "com.github.breadmoirai.github-release" version "2.2.4"
|
id "com.github.breadmoirai.github-release" version "2.2.4"
|
||||||
|
@ -288,6 +289,15 @@ task compressJson(dependsOn: jar) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
reports {
|
||||||
|
xml.enabled true
|
||||||
|
html.enabled true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check.dependsOn jacocoTestReport
|
||||||
|
|
||||||
license {
|
license {
|
||||||
mapping("java", "SLASHSTAR_STYLE")
|
mapping("java", "SLASHSTAR_STYLE")
|
||||||
strictCheck true
|
strictCheck true
|
||||||
|
|
|
@ -1,6 +1,77 @@
|
||||||
|
--- Execute a specific command.
|
||||||
|
--
|
||||||
|
-- @tparam string command The command to execute.
|
||||||
|
-- @treturn boolean Whether the command executed successfully.
|
||||||
|
-- @treturn { string... } The output of this command, as a list of lines.
|
||||||
|
-- @treturn number|nil The number of "affected" objects, or `nil` if the command
|
||||||
|
-- failed. The definition of this varies from command to command.
|
||||||
|
-- @usage Set the block above the command computer to stone.
|
||||||
|
--
|
||||||
|
-- commands.exec("setblock ~ ~1 ~ minecraft:stone")
|
||||||
function exec(command) end
|
function exec(command) end
|
||||||
|
|
||||||
|
--- Asynchronously execute a command.
|
||||||
|
--
|
||||||
|
-- Unlike @{exec}, this will immediately return, instead of waiting for the
|
||||||
|
-- command to execute. This allows you to run multiple commands at the same
|
||||||
|
-- time.
|
||||||
|
--
|
||||||
|
-- When this command has finished executing, it will queue a `task_complete`
|
||||||
|
-- event containing the result of executing this command (what @{exec} would
|
||||||
|
-- return).
|
||||||
|
--
|
||||||
|
-- @tparam string command The command to execute.
|
||||||
|
-- @treturn number The "task id". When this command has been executed, it will
|
||||||
|
-- queue a `task_complete` event with a matching id.
|
||||||
|
-- @usage Asynchronously sets the block above the computer to stone.
|
||||||
|
--
|
||||||
|
-- commands.execAsync("~ ~1 ~ minecraft:stone")
|
||||||
|
-- @see parallel One may also use the parallel API to run multiple commands at
|
||||||
|
-- once.
|
||||||
function execAsync(commad) end
|
function execAsync(commad) end
|
||||||
|
|
||||||
|
--- List all available commands which the computer has permission to execute.
|
||||||
|
--
|
||||||
|
-- @treturn { string... } A list of all available commands
|
||||||
function list() end
|
function list() end
|
||||||
|
|
||||||
|
--- Get the position of the current command computer.
|
||||||
|
--
|
||||||
|
-- @treturn number This computer's x position.
|
||||||
|
-- @treturn number This computer's y position.
|
||||||
|
-- @treturn number This computer's z position.
|
||||||
|
-- @see gps.locate To get the position of a non-command computer.
|
||||||
function getBlockPosition() end
|
function getBlockPosition() end
|
||||||
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
|
|
||||||
|
--- Get some basic information about a block.
|
||||||
|
--
|
||||||
|
-- The returned table contains the current name, metadata and block state (as
|
||||||
|
-- with @{turtle.inspect}). If there is a tile entity for that block, its NBT
|
||||||
|
-- will also be returned.
|
||||||
|
--
|
||||||
|
-- @tparam number x The x position of the block to query.
|
||||||
|
-- @tparam number y The y position of the block to query.
|
||||||
|
-- @tparam number z The z position of the block to query.
|
||||||
|
-- @treturn table The given block's information.
|
||||||
|
-- @throws If the coordinates are not within the world, or are not currently
|
||||||
|
-- loaded.
|
||||||
function getBlockInfo(x, y, z) end
|
function getBlockInfo(x, y, z) end
|
||||||
|
|
||||||
|
--- Get information about a range of blocks.
|
||||||
|
--
|
||||||
|
-- This returns the same information as @{getBlockInfo}, just for multiple
|
||||||
|
-- blocks at once.
|
||||||
|
--
|
||||||
|
-- Blocks are traversed by ascending y level, followed by z and x - the returned
|
||||||
|
-- table may be indexed using `x + z*width + y*depth*depth`.
|
||||||
|
--
|
||||||
|
-- @tparam number min_x The start x coordinate of the range to query.
|
||||||
|
-- @tparam number min_y The start y coordinate of the range to query.
|
||||||
|
-- @tparam number min_z The start z coordinate of the range to query.
|
||||||
|
-- @tparam number max_x The end x coordinate of the range to query.
|
||||||
|
-- @tparam number max_y The end y coordinate of the range to query.
|
||||||
|
-- @tparam number max_z The end z coordinate of the range to query.
|
||||||
|
-- @treturn { table... } A list of information about each block.
|
||||||
|
-- @throws If the coordinates are not within the world.
|
||||||
|
-- @throws If trying to get information about more than 4096 blocks.
|
||||||
|
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
|
||||||
|
|
|
@ -19,6 +19,18 @@ function getFreeSpace(path) end
|
||||||
function find(pattern) end
|
function find(pattern) end
|
||||||
function getDir(path) end
|
function getDir(path) end
|
||||||
|
|
||||||
|
--- Returns true if a path is mounted to the parent filesystem.
|
||||||
|
--
|
||||||
|
-- The root filesystem "/" is considered a mount, along with disk folders and
|
||||||
|
-- the rom folder. Other programs (such as network shares) can exstend this to
|
||||||
|
-- make other mount types by correctly assigning their return value for getDrive.
|
||||||
|
--
|
||||||
|
-- @tparam string path The path to check.
|
||||||
|
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
|
||||||
|
-- @throws If the path does not exist.
|
||||||
|
-- @see getDrive
|
||||||
|
function isDriveRoot(path) end
|
||||||
|
|
||||||
--- Get the capacity of the drive at the given path.
|
--- Get the capacity of the drive at the given path.
|
||||||
--
|
--
|
||||||
-- This may be used in conjunction with @{getFreeSpace} to determine what
|
-- This may be used in conjunction with @{getFreeSpace} to determine what
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Mod properties
|
# Mod properties
|
||||||
mod_version=1.87.1
|
mod_version=1.88.0
|
||||||
|
|
||||||
# Minecraft properties (update mods.toml when changing)
|
# Minecraft properties (update mods.toml when changing)
|
||||||
mc_version=1.14.4
|
mc_version=1.14.4
|
||||||
|
|
|
@ -70,14 +70,12 @@
|
||||||
|
|
||||||
;; Suppress warnings for currently undocumented modules.
|
;; Suppress warnings for currently undocumented modules.
|
||||||
(at
|
(at
|
||||||
(/doc/stub/commands.lua
|
(/doc/stub/fs.lua
|
||||||
/doc/stub/fs.lua
|
|
||||||
/doc/stub/http.lua
|
/doc/stub/http.lua
|
||||||
/doc/stub/os.lua
|
/doc/stub/os.lua
|
||||||
/doc/stub/redstone.lua
|
/doc/stub/redstone.lua
|
||||||
/doc/stub/term.lua
|
/doc/stub/term.lua
|
||||||
/doc/stub/turtle.lua
|
/doc/stub/turtle.lua
|
||||||
/src/main/resources/*/computercraft/lua/rom/apis/command/commands.lua
|
|
||||||
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
|
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
|
||||||
/src/main/resources/*/computercraft/lua/rom/apis/window.lua
|
/src/main/resources/*/computercraft/lua/rom/apis/window.lua
|
||||||
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua)
|
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua)
|
||||||
|
|
|
@ -65,7 +65,8 @@ public final class ComputerCraft
|
||||||
public static boolean disable_lua51_features = false;
|
public static boolean disable_lua51_features = false;
|
||||||
public static String default_computer_settings = "";
|
public static String default_computer_settings = "";
|
||||||
public static boolean debug_enable = true;
|
public static boolean debug_enable = true;
|
||||||
public static boolean logPeripheralErrors = false;
|
public static boolean logPeripheralErrors = true;
|
||||||
|
public static boolean commandRequireCreative = true;
|
||||||
|
|
||||||
public static int computer_threads = 1;
|
public static int computer_threads = 1;
|
||||||
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
|
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
|
||||||
|
|
|
@ -50,12 +50,12 @@ private FixedWidthFontRenderer()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float toGreyscale( double[] rgb )
|
public static float toGreyscale( double[] rgb )
|
||||||
{
|
{
|
||||||
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
|
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getColour( char c, Colour def )
|
public static int getColour( char c, Colour def )
|
||||||
{
|
{
|
||||||
return 15 - Terminal.getColour( c, def );
|
return 15 - Terminal.getColour( c, def );
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
* 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.client.render;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.mojang.blaze3d.platform.GLX;
|
||||||
|
import com.mojang.blaze3d.platform.TextureUtil;
|
||||||
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||||
|
import dan200.computercraft.shared.util.Palette;
|
||||||
|
import org.lwjgl.BufferUtils;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.lwjgl.opengl.GL13;
|
||||||
|
import org.lwjgl.opengl.GL20;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
|
class MonitorTextureBufferShader
|
||||||
|
{
|
||||||
|
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
|
||||||
|
|
||||||
|
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 );
|
||||||
|
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
|
||||||
|
|
||||||
|
private static int uniformMv;
|
||||||
|
private static int uniformP;
|
||||||
|
|
||||||
|
private static int uniformFont;
|
||||||
|
private static int uniformWidth;
|
||||||
|
private static int uniformHeight;
|
||||||
|
private static int uniformTbo;
|
||||||
|
private static int uniformPalette;
|
||||||
|
|
||||||
|
private static boolean initialised;
|
||||||
|
private static boolean ok;
|
||||||
|
private static int program;
|
||||||
|
|
||||||
|
static void setupUniform( int width, int height, Palette palette, boolean greyscale )
|
||||||
|
{
|
||||||
|
MATRIX_BUFFER.rewind();
|
||||||
|
GL11.glGetFloatv( GL11.GL_MODELVIEW_MATRIX, MATRIX_BUFFER );
|
||||||
|
MATRIX_BUFFER.rewind();
|
||||||
|
GLX.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER );
|
||||||
|
|
||||||
|
MATRIX_BUFFER.rewind();
|
||||||
|
GL11.glGetFloatv( GL11.GL_PROJECTION_MATRIX, MATRIX_BUFFER );
|
||||||
|
MATRIX_BUFFER.rewind();
|
||||||
|
GLX.glUniformMatrix4( uniformP, false, MATRIX_BUFFER );
|
||||||
|
|
||||||
|
GLX.glUniform1i( uniformWidth, width );
|
||||||
|
GLX.glUniform1i( uniformHeight, height );
|
||||||
|
|
||||||
|
PALETTE_BUFFER.rewind();
|
||||||
|
for( int i = 0; i < 16; i++ )
|
||||||
|
{
|
||||||
|
double[] colour = palette.getColour( i );
|
||||||
|
if( greyscale )
|
||||||
|
{
|
||||||
|
float f = FixedWidthFontRenderer.toGreyscale( colour );
|
||||||
|
PALETTE_BUFFER.put( f ).put( f ).put( f );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PALETTE_BUFFER.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PALETTE_BUFFER.flip();
|
||||||
|
GLX.glUniform3( uniformPalette, PALETTE_BUFFER );
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean use()
|
||||||
|
{
|
||||||
|
if( initialised )
|
||||||
|
{
|
||||||
|
if( ok ) GLX.glUseProgram( program );
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ok = load() )
|
||||||
|
{
|
||||||
|
GL20.glUseProgram( program );
|
||||||
|
GLX.glUniform1i( uniformFont, 0 );
|
||||||
|
GLX.glUniform1i( uniformTbo, TEXTURE_INDEX - GL13.GL_TEXTURE0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean load()
|
||||||
|
{
|
||||||
|
initialised = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int vertexShader = loadShader( GL20.GL_VERTEX_SHADER, "assets/computercraft/shaders/monitor.vert" );
|
||||||
|
int fragmentShader = loadShader( GL20.GL_FRAGMENT_SHADER, "assets/computercraft/shaders/monitor.frag" );
|
||||||
|
|
||||||
|
program = GLX.glCreateProgram();
|
||||||
|
GLX.glAttachShader( program, vertexShader );
|
||||||
|
GLX.glAttachShader( program, fragmentShader );
|
||||||
|
GL20.glBindAttribLocation( program, 0, "v_pos" );
|
||||||
|
|
||||||
|
GLX.glLinkProgram( program );
|
||||||
|
boolean ok = GLX.glGetProgrami( program, GL20.GL_LINK_STATUS ) != 0;
|
||||||
|
String log = GLX.glGetProgramInfoLog( program, Short.MAX_VALUE ).trim();
|
||||||
|
if( !Strings.isNullOrEmpty( log ) )
|
||||||
|
{
|
||||||
|
ComputerCraft.log.warn( "Problems when linking monitor shader: {}", log );
|
||||||
|
}
|
||||||
|
|
||||||
|
GL20.glDetachShader( program, vertexShader );
|
||||||
|
GL20.glDetachShader( program, fragmentShader );
|
||||||
|
GLX.glDeleteShader( vertexShader );
|
||||||
|
GLX.glDeleteShader( fragmentShader );
|
||||||
|
|
||||||
|
if( !ok ) return false;
|
||||||
|
|
||||||
|
uniformMv = getUniformLocation( program, "u_mv" );
|
||||||
|
uniformP = getUniformLocation( program, "u_p" );
|
||||||
|
|
||||||
|
uniformFont = getUniformLocation( program, "u_font" );
|
||||||
|
uniformWidth = getUniformLocation( program, "u_width" );
|
||||||
|
uniformHeight = getUniformLocation( program, "u_height" );
|
||||||
|
uniformTbo = getUniformLocation( program, "u_tbo" );
|
||||||
|
uniformPalette = getUniformLocation( program, "u_palette" );
|
||||||
|
|
||||||
|
ComputerCraft.log.info( "Loaded monitor shader." );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
ComputerCraft.log.error( "Cannot load monitor shaders", e );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int loadShader( int kind, String path ) throws IOException
|
||||||
|
{
|
||||||
|
String contents;
|
||||||
|
try( InputStream stream = TileEntityMonitorRenderer.class.getClassLoader().getResourceAsStream( path ) )
|
||||||
|
{
|
||||||
|
if( stream == null ) throw new IllegalArgumentException( "Cannot find " + path );
|
||||||
|
contents = TextureUtil.readResourceAsString( stream );
|
||||||
|
}
|
||||||
|
|
||||||
|
int shader = GLX.glCreateShader( kind );
|
||||||
|
|
||||||
|
GLX.glShaderSource( shader, contents );
|
||||||
|
GLX.glCompileShader( shader );
|
||||||
|
|
||||||
|
boolean ok = GLX.glGetShaderi( shader, GL20.GL_COMPILE_STATUS ) != 0;
|
||||||
|
String log = GLX.glGetShaderInfoLog( shader, Short.MAX_VALUE ).trim();
|
||||||
|
if( !Strings.isNullOrEmpty( log ) )
|
||||||
|
{
|
||||||
|
ComputerCraft.log.warn( "Problems when loading monitor shader {}: {}", path, log );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !ok ) throw new IllegalStateException( "Cannot compile shader " + path );
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getUniformLocation( int program, String name )
|
||||||
|
{
|
||||||
|
int uniform = GLX.glGetUniformLocation( program, name );
|
||||||
|
if( uniform == -1 ) throw new IllegalStateException( "Cannot find uniform " + name );
|
||||||
|
return uniform;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,21 +10,30 @@
|
||||||
import dan200.computercraft.client.FrameInfo;
|
import dan200.computercraft.client.FrameInfo;
|
||||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
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.ClientMonitor;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||||
|
import dan200.computercraft.shared.util.Colour;
|
||||||
import dan200.computercraft.shared.util.DirectionUtil;
|
import dan200.computercraft.shared.util.DirectionUtil;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.BufferBuilder;
|
import net.minecraft.client.renderer.BufferBuilder;
|
||||||
|
import net.minecraft.client.renderer.GLAllocation;
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
import net.minecraft.client.renderer.Tessellator;
|
||||||
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
|
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
|
||||||
|
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||||
import net.minecraft.util.Direction;
|
import net.minecraft.util.Direction;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import org.lwjgl.opengl.GL11;
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.lwjgl.opengl.GL13;
|
||||||
|
import org.lwjgl.opengl.GL15;
|
||||||
|
import org.lwjgl.opengl.GL31;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
|
||||||
import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN;
|
import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN;
|
||||||
|
|
||||||
public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||||
|
@ -92,8 +101,8 @@ public void render( @Nonnull TileMonitor monitor, double posX, double posY, doub
|
||||||
if( terminal != null )
|
if( terminal != null )
|
||||||
{
|
{
|
||||||
// Draw a terminal
|
// Draw a terminal
|
||||||
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
|
double xScale = xSize / (terminal.getWidth() * FONT_WIDTH);
|
||||||
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
|
double yScale = ySize / (terminal.getHeight() * FONT_HEIGHT);
|
||||||
|
|
||||||
GlStateManager.pushMatrix();
|
GlStateManager.pushMatrix();
|
||||||
GlStateManager.scaled( (float) xScale, (float) -yScale, 1.0f );
|
GlStateManager.scaled( (float) xScale, (float) -yScale, 1.0f );
|
||||||
|
@ -142,6 +151,52 @@ private static void renderTerminal( ClientMonitor monitor, float xMargin, float
|
||||||
|
|
||||||
switch( renderer )
|
switch( renderer )
|
||||||
{
|
{
|
||||||
|
case TBO:
|
||||||
|
{
|
||||||
|
if( !MonitorTextureBufferShader.use() ) return;
|
||||||
|
|
||||||
|
Terminal terminal = monitor.getTerminal();
|
||||||
|
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||||
|
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||||
|
|
||||||
|
if( redraw )
|
||||||
|
{
|
||||||
|
ByteBuffer monitorBuffer = GLAllocation.createDirectByteBuffer( width * height * 3 );
|
||||||
|
for( int y = 0; y < height; y++ )
|
||||||
|
{
|
||||||
|
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
|
||||||
|
for( int x = 0; x < width; x++ )
|
||||||
|
{
|
||||||
|
monitorBuffer.put( (byte) (text.charAt( x ) & 0xFF) );
|
||||||
|
monitorBuffer.put( (byte) getColour( textColour.charAt( x ), Colour.White ) );
|
||||||
|
monitorBuffer.put( (byte) getColour( background.charAt( x ), Colour.Black ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
monitorBuffer.flip();
|
||||||
|
|
||||||
|
GLX.glBindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
|
||||||
|
GLX.glBufferData( GL31.GL_TEXTURE_BUFFER, monitorBuffer, GL15.GL_STATIC_DRAW );
|
||||||
|
GLX.glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind TBO texture and set up the uniforms. We've already set up the main font above.
|
||||||
|
GlStateManager.activeTexture( MonitorTextureBufferShader.TEXTURE_INDEX );
|
||||||
|
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture );
|
||||||
|
GlStateManager.activeTexture( GL13.GL_TEXTURE0 );
|
||||||
|
|
||||||
|
MonitorTextureBufferShader.setupUniform( width, height, terminal.getPalette(), !monitor.isColour() );
|
||||||
|
|
||||||
|
buffer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
|
||||||
|
buffer.pos( -xMargin, -yMargin, 0 ).endVertex();
|
||||||
|
buffer.pos( -xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||||
|
buffer.pos( pixelWidth + xMargin, -yMargin, 0 ).endVertex();
|
||||||
|
buffer.pos( pixelWidth + xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||||
|
tessellator.draw();
|
||||||
|
|
||||||
|
GLX.glUseProgram( 0 );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case VBO:
|
case VBO:
|
||||||
{
|
{
|
||||||
VertexBuffer vbo = monitor.buffer;
|
VertexBuffer vbo = monitor.buffer;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
import dan200.computercraft.api.turtle.event.TurtleAction;
|
import dan200.computercraft.api.turtle.event.TurtleAction;
|
||||||
import dan200.computercraft.core.apis.AddressPredicate;
|
import dan200.computercraft.core.apis.AddressPredicate;
|
||||||
import dan200.computercraft.core.apis.http.websocket.Websocket;
|
import dan200.computercraft.core.apis.http.websocket.Websocket;
|
||||||
|
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||||
import net.minecraftforge.common.ForgeConfigSpec;
|
import net.minecraftforge.common.ForgeConfigSpec;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.ModLoadingContext;
|
import net.minecraftforge.fml.ModLoadingContext;
|
||||||
|
@ -74,7 +75,10 @@ public final class Config
|
||||||
private static final ConfigValue<Boolean> turtlesCanPush;
|
private static final ConfigValue<Boolean> turtlesCanPush;
|
||||||
private static final ConfigValue<List<? extends String>> turtleDisabledActions;
|
private static final ConfigValue<List<? extends String>> turtleDisabledActions;
|
||||||
|
|
||||||
private static final ForgeConfigSpec spec;
|
private static final ConfigValue<MonitorRenderer> monitorRenderer;
|
||||||
|
|
||||||
|
private static final ForgeConfigSpec commonSpec;
|
||||||
|
private static final ForgeConfigSpec clientSpec;
|
||||||
|
|
||||||
private Config() {}
|
private Config() {}
|
||||||
|
|
||||||
|
@ -260,12 +264,20 @@ private Config() {}
|
||||||
builder.pop();
|
builder.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
spec = builder.build();
|
commonSpec = builder.build();
|
||||||
|
|
||||||
|
Builder clientBuilder = new Builder();
|
||||||
|
monitorRenderer = clientBuilder
|
||||||
|
.comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if " +
|
||||||
|
"monitors have performance issues, you may wish to experiment with alternative renderers." )
|
||||||
|
.defineEnum( "monitor_renderer", MonitorRenderer.BEST );
|
||||||
|
clientSpec = clientBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void load()
|
public static void load()
|
||||||
{
|
{
|
||||||
ModLoadingContext.get().registerConfig( ModConfig.Type.COMMON, spec );
|
ModLoadingContext.get().registerConfig( ModConfig.Type.COMMON, commonSpec );
|
||||||
|
ModLoadingContext.get().registerConfig( ModConfig.Type.CLIENT, clientSpec );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sync()
|
public static void sync()
|
||||||
|
@ -315,6 +327,9 @@ public static void sync()
|
||||||
|
|
||||||
ComputerCraft.turtleDisabledActions.clear();
|
ComputerCraft.turtleDisabledActions.clear();
|
||||||
for( String value : turtleDisabledActions.get() ) ComputerCraft.turtleDisabledActions.add( getAction( value ) );
|
for( String value : turtleDisabledActions.get() ) ComputerCraft.turtleDisabledActions.add( getAction( value ) );
|
||||||
|
|
||||||
|
// Client
|
||||||
|
ComputerCraft.monitorRenderer = monitorRenderer.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
|
|
|
@ -120,6 +120,11 @@ protected ServerComputer createComputer( int instanceID, int id )
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUsable( PlayerEntity player, boolean ignoreRange )
|
public boolean isUsable( PlayerEntity player, boolean ignoreRange )
|
||||||
|
{
|
||||||
|
return isUsable( player ) && super.isUsable( player, ignoreRange );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUsable( PlayerEntity player )
|
||||||
{
|
{
|
||||||
MinecraftServer server = player.getServer();
|
MinecraftServer server = player.getServer();
|
||||||
if( server == null || !server.isCommandBlockEnabled() )
|
if( server == null || !server.isCommandBlockEnabled() )
|
||||||
|
@ -127,14 +132,12 @@ public boolean isUsable( PlayerEntity player, boolean ignoreRange )
|
||||||
player.sendStatusMessage( new TranslationTextComponent( "advMode.notEnabled" ), true );
|
player.sendStatusMessage( new TranslationTextComponent( "advMode.notEnabled" ), true );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if( !player.canUseCommandBlock() )
|
else if( ComputerCraft.commandRequireCreative ? !player.canUseCommandBlock() : !server.getPlayerList().canSendCommands( player.getGameProfile() ) )
|
||||||
{
|
{
|
||||||
player.sendStatusMessage( new TranslationTextComponent( "advMode.notAllowed" ), true );
|
player.sendStatusMessage( new TranslationTextComponent( "advMode.notAllowed" ), true );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return true;
|
||||||
return super.isUsable( player, ignoreRange );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,4 +35,3 @@ public String toString()
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package dan200.computercraft.shared.computer.inventory;
|
package dan200.computercraft.shared.computer.inventory;
|
||||||
|
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
@ -14,8 +15,6 @@
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
import net.minecraft.inventory.container.ContainerType;
|
import net.minecraft.inventory.container.ContainerType;
|
||||||
import net.minecraft.server.MinecraftServer;
|
|
||||||
import net.minecraft.util.text.TranslationTextComponent;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
@ -48,18 +47,9 @@ private static boolean canInteractWith( @Nonnull ServerComputer computer, @Nonnu
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're a command computer then ensure we're in creative
|
// If we're a command computer then ensure we're in creative
|
||||||
if( computer.getFamily() == ComputerFamily.Command )
|
if( computer.getFamily() == ComputerFamily.Command && !TileCommandComputer.isUsable( player ) )
|
||||||
{
|
{
|
||||||
MinecraftServer server = player.getServer();
|
return false;
|
||||||
if( server == null || !server.isCommandBlockEnabled() )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if( !player.canUseCommandBlock() )
|
|
||||||
{
|
|
||||||
player.sendStatusMessage( new TranslationTextComponent( "advMode.notAllowed" ), false );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -5,12 +5,18 @@
|
||||||
*/
|
*/
|
||||||
package dan200.computercraft.shared.peripheral.monitor;
|
package dan200.computercraft.shared.peripheral.monitor;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.GLX;
|
||||||
|
import com.mojang.blaze3d.platform.GlStateManager;
|
||||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||||
import dan200.computercraft.shared.common.ClientTerminal;
|
import dan200.computercraft.shared.common.ClientTerminal;
|
||||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.lwjgl.opengl.GL15;
|
||||||
|
import org.lwjgl.opengl.GL30;
|
||||||
|
import org.lwjgl.opengl.GL31;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -25,6 +31,8 @@ public final class ClientMonitor extends ClientTerminal
|
||||||
public long lastRenderFrame = -1;
|
public long lastRenderFrame = -1;
|
||||||
public BlockPos lastRenderPos = null;
|
public BlockPos lastRenderPos = null;
|
||||||
|
|
||||||
|
public int tboBuffer;
|
||||||
|
public int tboTexture;
|
||||||
public VertexBuffer buffer;
|
public VertexBuffer buffer;
|
||||||
|
|
||||||
public ClientMonitor( boolean colour, TileMonitor origin )
|
public ClientMonitor( boolean colour, TileMonitor origin )
|
||||||
|
@ -50,6 +58,26 @@ public boolean createBuffer( MonitorRenderer renderer )
|
||||||
{
|
{
|
||||||
switch( renderer )
|
switch( renderer )
|
||||||
{
|
{
|
||||||
|
case TBO:
|
||||||
|
{
|
||||||
|
if( tboBuffer != 0 ) return false;
|
||||||
|
|
||||||
|
deleteBuffers();
|
||||||
|
|
||||||
|
tboBuffer = GLX.glGenBuffers();
|
||||||
|
GLX.glBindBuffer( GL31.GL_TEXTURE_BUFFER, tboBuffer );
|
||||||
|
GL15.glBufferData( GL31.GL_TEXTURE_BUFFER, 0, GL15.GL_STATIC_DRAW );
|
||||||
|
tboTexture = GlStateManager.genTexture();
|
||||||
|
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, tboTexture );
|
||||||
|
GL31.glTexBuffer( GL31.GL_TEXTURE_BUFFER, GL30.GL_R8, tboBuffer );
|
||||||
|
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||||
|
|
||||||
|
GLX.glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||||
|
|
||||||
|
addMonitor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
case VBO:
|
case VBO:
|
||||||
if( buffer != null ) return false;
|
if( buffer != null ) return false;
|
||||||
|
|
||||||
|
@ -73,6 +101,19 @@ private void addMonitor()
|
||||||
|
|
||||||
private void deleteBuffers()
|
private void deleteBuffers()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if( tboBuffer != 0 )
|
||||||
|
{
|
||||||
|
GLX.glDeleteBuffers( tboBuffer );
|
||||||
|
tboBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( tboTexture != 0 )
|
||||||
|
{
|
||||||
|
GlStateManager.deleteTexture( tboTexture );
|
||||||
|
tboTexture = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if( buffer != null )
|
if( buffer != null )
|
||||||
{
|
{
|
||||||
buffer.deleteGlBuffers();
|
buffer.deleteGlBuffers();
|
||||||
|
@ -83,7 +124,7 @@ private void deleteBuffers()
|
||||||
@OnlyIn( Dist.CLIENT )
|
@OnlyIn( Dist.CLIENT )
|
||||||
public void destroy()
|
public void destroy()
|
||||||
{
|
{
|
||||||
if( buffer != null )
|
if( tboBuffer != 0 || buffer != null )
|
||||||
{
|
{
|
||||||
synchronized( allMonitors )
|
synchronized( allMonitors )
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
|
|
||||||
package dan200.computercraft.shared.peripheral.monitor;
|
package dan200.computercraft.shared.peripheral.monitor;
|
||||||
|
|
||||||
import com.mojang.blaze3d.platform.GLX;
|
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
|
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
|
||||||
|
import org.lwjgl.opengl.GL;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The render type to use for monitors.
|
* The render type to use for monitors.
|
||||||
|
@ -26,6 +25,13 @@ public enum MonitorRenderer
|
||||||
*/
|
*/
|
||||||
BEST,
|
BEST,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render using texture buffer objects.
|
||||||
|
*
|
||||||
|
* @see org.lwjgl.opengl.GL31#glTexBuffer(int, int, int)
|
||||||
|
*/
|
||||||
|
TBO,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render using VBOs.
|
* Render using VBOs.
|
||||||
*
|
*
|
||||||
|
@ -33,37 +39,6 @@ public enum MonitorRenderer
|
||||||
*/
|
*/
|
||||||
VBO;
|
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.
|
* Get the current renderer to use.
|
||||||
*
|
*
|
||||||
|
@ -77,15 +52,16 @@ public static MonitorRenderer current()
|
||||||
{
|
{
|
||||||
case BEST:
|
case BEST:
|
||||||
return best();
|
return best();
|
||||||
case VBO:
|
case TBO:
|
||||||
if( !GLX.useVbo() )
|
checkCapabilities();
|
||||||
|
if( !textureBuffer )
|
||||||
{
|
{
|
||||||
ComputerCraft.log.warn( "VBOs are not supported on your graphics card. Falling back to default." );
|
ComputerCraft.log.warn( "Texture buffers are not supported on your graphics card. Falling back to default." );
|
||||||
ComputerCraft.monitorRenderer = BEST;
|
ComputerCraft.monitorRenderer = BEST;
|
||||||
return best();
|
return best();
|
||||||
}
|
}
|
||||||
|
|
||||||
return VBO;
|
return TBO;
|
||||||
default:
|
default:
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
@ -93,6 +69,18 @@ public static MonitorRenderer current()
|
||||||
|
|
||||||
private static MonitorRenderer best()
|
private static MonitorRenderer best()
|
||||||
{
|
{
|
||||||
return VBO;
|
checkCapabilities();
|
||||||
|
return textureBuffer ? TBO : VBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean initialised = false;
|
||||||
|
private static boolean textureBuffer = false;
|
||||||
|
|
||||||
|
private static void checkCapabilities()
|
||||||
|
{
|
||||||
|
if( initialised ) return;
|
||||||
|
|
||||||
|
textureBuffer = GL.getCapabilities().OpenGL31;
|
||||||
|
initialised = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,4 +154,3 @@ private synchronized boolean playSound( ILuaContext context, ResourceLocation na
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,4 +78,3 @@ public boolean equals( @Nullable IPeripheral other )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,3 @@
|
||||||
"modem=true,peripheral=true": { "model": "computercraft:block/wired_modem_full_on_peripheral" }
|
"modem=true,peripheral=true": { "model": "computercraft:block/wired_modem_full_on_peripheral" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
#version 140
|
||||||
|
|
||||||
|
#define FONT_WIDTH 6.0
|
||||||
|
#define FONT_HEIGHT 9.0
|
||||||
|
|
||||||
|
uniform sampler2D u_font;
|
||||||
|
uniform int u_width;
|
||||||
|
uniform int u_height;
|
||||||
|
uniform samplerBuffer u_tbo;
|
||||||
|
uniform vec3 u_palette[16];
|
||||||
|
|
||||||
|
in vec2 f_pos;
|
||||||
|
|
||||||
|
out vec4 colour;
|
||||||
|
|
||||||
|
vec2 texture_corner(int index) {
|
||||||
|
float x = 1.0 + float(index % 16) * (FONT_WIDTH + 2.0);
|
||||||
|
float y = 1.0 + float(index / 16) * (FONT_HEIGHT + 2.0);
|
||||||
|
return vec2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 term_pos = vec2(f_pos.x / FONT_WIDTH, f_pos.y / FONT_HEIGHT);
|
||||||
|
vec2 corner = floor(term_pos);
|
||||||
|
|
||||||
|
ivec2 cell = ivec2(corner);
|
||||||
|
int index = 3 * (clamp(cell.x, 0, u_width - 1) + clamp(cell.y, 0, u_height - 1) * u_width);
|
||||||
|
|
||||||
|
// 1 if 0 <= x, y < width, height, 0 otherwise
|
||||||
|
vec2 outside = step(vec2(0.0, 0.0), vec2(cell)) * step(vec2(cell), vec2(float(u_width) - 1.0, float(u_height) - 1.0));
|
||||||
|
float mult = outside.x * outside.y;
|
||||||
|
|
||||||
|
int character = int(texelFetch(u_tbo, index).r * 255.0);
|
||||||
|
int fg = int(texelFetch(u_tbo, index + 1).r * 255.0);
|
||||||
|
int bg = int(texelFetch(u_tbo, index + 2).r * 255.0);
|
||||||
|
|
||||||
|
vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT);
|
||||||
|
vec4 img = texture2D(u_font, (texture_corner(character) + pos) / 256.0);
|
||||||
|
colour = vec4(mix(u_palette[bg], img.rgb * u_palette[fg], img.a * mult), 1.0);
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#version 140
|
||||||
|
|
||||||
|
uniform mat4 u_mv;
|
||||||
|
uniform mat4 u_p;
|
||||||
|
|
||||||
|
in vec3 v_pos;
|
||||||
|
|
||||||
|
out vec2 f_pos;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_p * u_mv * vec4(v_pos.x, v_pos.y, 0, 1);
|
||||||
|
f_pos = v_pos.xy;
|
||||||
|
}
|
|
@ -797,6 +797,12 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
|
||||||
return tEmpty
|
return tEmpty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function fs.isDriveRoot(sPath)
|
||||||
|
expect(1, sPath, "string")
|
||||||
|
-- Force the root directory to be a mount.
|
||||||
|
return fs.getDir(sPath) == ".." or fs.getDrive(sPath) ~= fs.getDrive(fs.getDir(sPath))
|
||||||
|
end
|
||||||
|
|
||||||
-- Load APIs
|
-- Load APIs
|
||||||
local bAPIError = false
|
local bAPIError = false
|
||||||
local tApis = fs.list("rom/apis")
|
local tApis = fs.list("rom/apis")
|
||||||
|
@ -932,6 +938,11 @@ settings.define("motd.path", {
|
||||||
description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]],
|
description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]],
|
||||||
type = "string",
|
type = "string",
|
||||||
})
|
})
|
||||||
|
settings.define("lua.warn_against_use_of_local", {
|
||||||
|
default = true,
|
||||||
|
description = [[Print a message when input in the Lua REPL starts with the word 'local'. Local variables defined in the Lua REPL are be inaccessable on the next input.]],
|
||||||
|
type = "boolean",
|
||||||
|
})
|
||||||
if term.isColour() then
|
if term.isColour() then
|
||||||
settings.define("bios.use_multishell", {
|
settings.define("bios.use_multishell", {
|
||||||
default = true,
|
default = true,
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
-- [mc]: https://minecraft.gamepedia.com/Commands
|
-- [mc]: https://minecraft.gamepedia.com/Commands
|
||||||
--
|
--
|
||||||
-- @module commands
|
-- @module commands
|
||||||
|
-- @usage Set the block above this computer to stone:
|
||||||
|
--
|
||||||
|
-- commands.setblock("~", "~1", "~", "minecraft:stone")
|
||||||
|
|
||||||
if not commands then
|
if not commands then
|
||||||
error("Cannot load command API on normal computer", 2)
|
error("Cannot load command API on normal computer", 2)
|
||||||
|
@ -97,4 +100,13 @@ for _, sCommandName in ipairs(native.list()) do
|
||||||
tAsync[sCommandName] = mk_command({ sCommandName }, bJSONIsNBT, native.execAsync)
|
tAsync[sCommandName] = mk_command({ sCommandName }, bJSONIsNBT, native.execAsync)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- A table containing asynchronous wrappers for all commands.
|
||||||
|
--
|
||||||
|
-- As with @{commands.execAsync}, this returns the "task id" of the enqueued
|
||||||
|
-- command.
|
||||||
|
-- @see execAsync
|
||||||
|
-- @usage Asynchronously sets the block above the computer to stone.
|
||||||
|
--
|
||||||
|
-- commands.async.setblock("~", "~1", "~", "minecraft:stone")
|
||||||
env.async = tAsync
|
env.async = tAsync
|
||||||
|
|
|
@ -75,7 +75,10 @@ handleMetatable = {
|
||||||
if not handle.read then return nil, "file is not readable" end
|
if not handle.read then return nil, "file is not readable" end
|
||||||
|
|
||||||
local args = table.pack(...)
|
local args = table.pack(...)
|
||||||
return function() return checkResult(self, self:read(table.unpack(args, 1, args.n))) end
|
return function()
|
||||||
|
if self._closed then error("file is already closed", 2) end
|
||||||
|
return checkResult(self, self:read(table.unpack(args, 1, args.n)))
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
read = function(self, ...)
|
read = function(self, ...)
|
||||||
|
@ -259,12 +262,13 @@ end
|
||||||
-- instead. In this case, the handle is not used.
|
-- instead. In this case, the handle is not used.
|
||||||
--
|
--
|
||||||
-- @tparam[opt] string filename The name of the file to extract lines from
|
-- @tparam[opt] string filename The name of the file to extract lines from
|
||||||
|
-- @param ... The argument to pass to @{Handle:read} for each line.
|
||||||
-- @treturn function():string|nil The line iterator.
|
-- @treturn function():string|nil The line iterator.
|
||||||
-- @throws If the file cannot be opened for reading
|
-- @throws If the file cannot be opened for reading
|
||||||
--
|
--
|
||||||
-- @see Handle:lines
|
-- @see Handle:lines
|
||||||
-- @see io.input
|
-- @see io.input
|
||||||
function lines(filename)
|
function lines(filename, ...)
|
||||||
expect(1, filename, "string", "nil")
|
expect(1, filename, "string", "nil")
|
||||||
if filename then
|
if filename then
|
||||||
local ok, err = open(filename, "rb")
|
local ok, err = open(filename, "rb")
|
||||||
|
@ -273,9 +277,9 @@ function lines(filename)
|
||||||
-- We set this magic flag to mark this file as being opened by io.lines and so should be
|
-- We set this magic flag to mark this file as being opened by io.lines and so should be
|
||||||
-- closed automatically
|
-- closed automatically
|
||||||
ok._autoclose = true
|
ok._autoclose = true
|
||||||
return ok:lines()
|
return ok:lines(...)
|
||||||
else
|
else
|
||||||
return currentInput:lines()
|
return currentInput:lines(...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -313,7 +317,7 @@ end
|
||||||
-- @throws If the provided filename cannot be opened for writing.
|
-- @throws If the provided filename cannot be opened for writing.
|
||||||
function output(file)
|
function output(file)
|
||||||
if type_of(file) == "string" then
|
if type_of(file) == "string" then
|
||||||
local res, err = open(file, "w")
|
local res, err = open(file, "wb")
|
||||||
if not res then error(err, 2) end
|
if not res then error(err, 2) end
|
||||||
currentOutput = res
|
currentOutput = res
|
||||||
elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then
|
elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
# New features in CC: Tweaked 1.88.0
|
||||||
|
|
||||||
|
* Computers and turtles now preserve their ID when broken.
|
||||||
|
* Add `peripheral.getName` - returns the name of a wrapped peripheral.
|
||||||
|
* Reduce network overhead of monitors and terminals.
|
||||||
|
* Add a TBO backend for monitors, with a significant performance boost.
|
||||||
|
* The Lua REPL warns when declaring locals (lupus590, exerro)
|
||||||
|
* Add config to allow using command computers in survival.
|
||||||
|
* Add fs.isDriveRoot - checks if a path is the root of a drive.
|
||||||
|
|
||||||
|
And several bug fixes:
|
||||||
|
* Fix io.lines not accepting arguments.
|
||||||
|
* Fix settings.load using an unknown global (MCJack123).
|
||||||
|
|
||||||
# New features in CC: Tweaked 1.87.1
|
# New features in CC: Tweaked 1.87.1
|
||||||
|
|
||||||
* Fix blocks not dropping items in survival.
|
* Fix blocks not dropping items in survival.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
ComputerCraft was created by Daniel "dan200" Ratcliffe, with additional code by Aaron "Cloudy" Mills.
|
ComputerCraft was created by Daniel "dan200" Ratcliffe, with additional code by Aaron "Cloudy" Mills.
|
||||||
Thanks to nitrogenfingers, GopherATL and RamiLego for program contributions.
|
Thanks to nitrogenfingers, GopherATL and RamiLego for program contributions.
|
||||||
Thanks to Mojang, the Forge team, and the MCP team.
|
Thanks to Mojang, the Forge team, and the MCP team.
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
New features in CC: Tweaked 1.87.1
|
New features in CC: Tweaked 1.88.0
|
||||||
|
|
||||||
* Fix blocks not dropping items in survival.
|
* Computers and turtles now preserve their ID when broken.
|
||||||
|
* Add `peripheral.getName` - returns the name of a wrapped peripheral.
|
||||||
|
* Reduce network overhead of monitors and terminals.
|
||||||
|
* Add a TBO backend for monitors, with a significant performance boost.
|
||||||
|
* The Lua REPL warns when declaring locals (lupus590, exerro)
|
||||||
|
* Add config to allow using command computers in survival.
|
||||||
|
* Add fs.isDriveRoot - checks if a path is the root of a drive.
|
||||||
|
|
||||||
|
And several bug fixes:
|
||||||
|
* Fix io.lines not accepting arguments.
|
||||||
|
* Fix settings.load using an unknown global (MCJack123).
|
||||||
|
|
||||||
Type "help changelog" to see the full version history.
|
Type "help changelog" to see the full version history.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
if not shell.openTab then
|
if not shell.openTab then
|
||||||
printError("Requires multishell")
|
printError("Requires multishell")
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
if not shell.openTab then
|
if not shell.openTab then
|
||||||
printError("Requires multishell")
|
printError("Requires multishell")
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs > 2 then
|
if #tArgs > 2 then
|
||||||
print("Usage: alias <alias> <program>")
|
print("Usage: alias <alias> <program>")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tApis = {}
|
local tApis = {}
|
||||||
for k, v in pairs(_G) do
|
for k, v in pairs(_G) do
|
||||||
if type(k) == "string" and type(v) == "table" and k ~= "_G" then
|
if type(k) == "string" and type(v) == "table" and k ~= "_G" then
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs < 1 then
|
if #tArgs < 1 then
|
||||||
print("Usage: cd <path>")
|
print("Usage: cd <path>")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
if not commands then
|
if not commands then
|
||||||
printError("Requires a Command Computer.")
|
printError("Requires a Command Computer.")
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if not commands then
|
if not commands then
|
||||||
printError("Requires a Command Computer.")
|
printError("Requires a Command Computer.")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs < 2 then
|
if #tArgs < 2 then
|
||||||
print("Usage: cp <source> <destination>")
|
print("Usage: cp <source> <destination>")
|
||||||
|
|
|
@ -9,9 +9,18 @@ for i = 1, args.n do
|
||||||
local files = fs.find(shell.resolve(args[i]))
|
local files = fs.find(shell.resolve(args[i]))
|
||||||
if #files > 0 then
|
if #files > 0 then
|
||||||
for _, file in ipairs(files) do
|
for _, file in ipairs(files) do
|
||||||
local ok, err = pcall(fs.delete, file)
|
if fs.isReadOnly(file) then
|
||||||
if not ok then
|
printError("Cannot delete read-only file /" .. file)
|
||||||
printError((err:gsub("^pcall: ", "")))
|
elseif fs.isDriveRoot(file) then
|
||||||
|
printError("Cannot delete mount /" .. file)
|
||||||
|
if fs.isDir(file) then
|
||||||
|
print("To delete its contents run rm /" .. fs.combine(file, "*"))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local ok, err = pcall(fs.delete, file)
|
||||||
|
if not ok then
|
||||||
|
printError((err:gsub("^pcall: ", "")))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
-- Get arguments
|
-- Get arguments
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs == 0 then
|
if #tArgs == 0 then
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tBiomes = {
|
local tBiomes = {
|
||||||
"in a forest",
|
"in a forest",
|
||||||
"in a pine forest",
|
"in a pine forest",
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
-- Display the start screen
|
-- Display the start screen
|
||||||
local w, h = term.getSize()
|
local w, h = term.getSize()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local function printUsage()
|
local function printUsage()
|
||||||
print("Usages:")
|
print("Usages:")
|
||||||
print("gps host")
|
print("gps host")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local function printUsage()
|
local function printUsage()
|
||||||
print("Usages:")
|
print("Usages:")
|
||||||
print("pastebin put <filename>")
|
print("pastebin put <filename>")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local function printUsage()
|
local function printUsage()
|
||||||
print("Usage:")
|
print("Usage:")
|
||||||
print("wget <url> [filename]")
|
print("wget <url> [filename]")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local sDrive = nil
|
local sDrive = nil
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs > 0 then
|
if #tArgs > 0 then
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local function printUsage()
|
local function printUsage()
|
||||||
print("Usages:")
|
print("Usages:")
|
||||||
print("label get")
|
print("label get")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
|
|
||||||
-- Get all the files in the directory
|
-- Get all the files in the directory
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs > 0 then
|
if #tArgs > 0 then
|
||||||
print("This is an interactive Lua prompt.")
|
print("This is an interactive Lua prompt.")
|
||||||
|
@ -67,6 +66,13 @@ while bRunning do
|
||||||
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
|
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
|
||||||
table.insert(tCommandHistory, s)
|
table.insert(tCommandHistory, s)
|
||||||
end
|
end
|
||||||
|
if settings.get("lua.warn_against_use_of_local") and s:match("^%s*local%s+") then
|
||||||
|
if term.isColour() then
|
||||||
|
term.setTextColour(colours.yellow)
|
||||||
|
end
|
||||||
|
print("To access local variables in later inputs, remove the local keyword.")
|
||||||
|
term.setTextColour(colours.white)
|
||||||
|
end
|
||||||
|
|
||||||
local nForcePrint = 0
|
local nForcePrint = 0
|
||||||
local func, e = load(s, "=lua", "t", tEnv)
|
local func, e = load(s, "=lua", "t", tEnv)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs < 2 then
|
if #tArgs < 2 then
|
||||||
print("Usage: mv <source> <destination>")
|
print("Usage: mv <source> <destination>")
|
||||||
|
@ -8,12 +7,35 @@ end
|
||||||
local sSource = shell.resolve(tArgs[1])
|
local sSource = shell.resolve(tArgs[1])
|
||||||
local sDest = shell.resolve(tArgs[2])
|
local sDest = shell.resolve(tArgs[2])
|
||||||
local tFiles = fs.find(sSource)
|
local tFiles = fs.find(sSource)
|
||||||
|
|
||||||
|
local function sanity_checks(source, dest)
|
||||||
|
if fs.exists(dest) then
|
||||||
|
printError("Destination exists")
|
||||||
|
return false
|
||||||
|
elseif fs.isReadOnly(dest) then
|
||||||
|
printError("Destination is read-only")
|
||||||
|
return false
|
||||||
|
elseif fs.isDriveRoot(source) then
|
||||||
|
printError("Cannot move mount /" .. source)
|
||||||
|
return false
|
||||||
|
elseif fs.isReadOnly(source) then
|
||||||
|
printError("Cannot move read-only file /" .. source)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
if #tFiles > 0 then
|
if #tFiles > 0 then
|
||||||
for _, sFile in ipairs(tFiles) do
|
for _, sFile in ipairs(tFiles) do
|
||||||
if fs.isDir(sDest) then
|
if fs.isDir(sDest) then
|
||||||
fs.move(sFile, fs.combine(sDest, fs.getName(sFile)))
|
local dest = fs.combine(sDest, fs.getName(sFile))
|
||||||
|
if sanity_checks(sFile, dest) then
|
||||||
|
fs.move(sFile, dest)
|
||||||
|
end
|
||||||
elseif #tFiles == 1 then
|
elseif #tFiles == 1 then
|
||||||
fs.move(sFile, sDest)
|
if sanity_checks(sFile, sDest) then
|
||||||
|
fs.move(sFile, sDest)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
printError("Cannot overwrite file multiple times")
|
printError("Cannot overwrite file multiple times")
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local bAll = false
|
local bAll = false
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs > 0 and tArgs[1] == "all" then
|
if #tArgs > 0 and tArgs[1] == "all" then
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
|
|
||||||
local function printUsage()
|
local function printUsage()
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
-- Find modems
|
-- Find modems
|
||||||
local tModems = {}
|
local tModems = {}
|
||||||
for _, sModem in ipairs(peripheral.getNames()) do
|
for _, sModem in ipairs(peripheral.getNames()) do
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
|
|
||||||
local function printUsage()
|
local function printUsage()
|
||||||
|
|
|
@ -10,6 +10,12 @@ local sDest = shell.resolve(tArgs[2])
|
||||||
if not fs.exists(sSource) then
|
if not fs.exists(sSource) then
|
||||||
printError("No matching files")
|
printError("No matching files")
|
||||||
return
|
return
|
||||||
|
elseif fs.isDriveRoot(sSource) then
|
||||||
|
printError("Can't rename mounts")
|
||||||
|
return
|
||||||
|
elseif fs.isReadOnly(sSource) then
|
||||||
|
printError("Source is read-only")
|
||||||
|
return
|
||||||
elseif fs.exists(sDest) then
|
elseif fs.exists(sDest) then
|
||||||
printError("Destination exists")
|
printError("Destination exists")
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs < 1 then
|
if #tArgs < 1 then
|
||||||
print("Usage: type <path>")
|
print("Usage: type <path>")
|
||||||
|
@ -15,4 +14,3 @@ if fs.exists(sPath) then
|
||||||
else
|
else
|
||||||
print("No such path")
|
print("No such path")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Lua IDE
|
-- Lua IDE
|
||||||
-- Made by GravityScore
|
-- Made by GravityScore
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -30,12 +30,14 @@
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
|
@ -58,6 +60,8 @@
|
||||||
*/
|
*/
|
||||||
public class ComputerTestDelegate
|
public class ComputerTestDelegate
|
||||||
{
|
{
|
||||||
|
private static final File REPORT_PATH = new File( "test-files/luacov.report.out" );
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger( ComputerTestDelegate.class );
|
private static final Logger LOG = LogManager.getLogger( ComputerTestDelegate.class );
|
||||||
|
|
||||||
private static final long TICK_TIME = TimeUnit.MILLISECONDS.toNanos( 50 );
|
private static final long TICK_TIME = TimeUnit.MILLISECONDS.toNanos( 50 );
|
||||||
|
@ -77,12 +81,15 @@ public class ComputerTestDelegate
|
||||||
|
|
||||||
private final Condition hasFinished = lock.newCondition();
|
private final Condition hasFinished = lock.newCondition();
|
||||||
private boolean finished = false;
|
private boolean finished = false;
|
||||||
|
private Map<String, Map<Double, Double>> finishedWith;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before() throws IOException
|
public void before() throws IOException
|
||||||
{
|
{
|
||||||
ComputerCraft.logPeripheralErrors = true;
|
ComputerCraft.logPeripheralErrors = true;
|
||||||
|
|
||||||
|
if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." );
|
||||||
|
|
||||||
Terminal term = new Terminal( 78, 20 );
|
Terminal term = new Terminal( 78, 20 );
|
||||||
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
|
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
|
||||||
|
|
||||||
|
@ -264,6 +271,13 @@ else if( !computer.isOn() )
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
finished = true;
|
finished = true;
|
||||||
|
if( arguments.length > 0 )
|
||||||
|
{
|
||||||
|
@SuppressWarnings( "unchecked" )
|
||||||
|
Map<String, Map<Double, Double>> finished = (Map<String, Map<Double, Double>>) arguments[0];
|
||||||
|
finishedWith = finished;
|
||||||
|
}
|
||||||
|
|
||||||
hasFinished.signal();
|
hasFinished.signal();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
@ -281,7 +295,7 @@ else if( !computer.isOn() )
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void after() throws InterruptedException
|
public void after() throws InterruptedException, IOException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -316,6 +330,14 @@ public void after() throws InterruptedException
|
||||||
// And shutdown
|
// And shutdown
|
||||||
computer.shutdown();
|
computer.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( finishedWith != null )
|
||||||
|
{
|
||||||
|
try( BufferedWriter writer = Files.newBufferedWriter( REPORT_PATH.toPath() ) )
|
||||||
|
{
|
||||||
|
new LuaCoverage( finishedWith ).write( writer );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestFactory
|
@TestFactory
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||||
|
import org.squiddev.cobalt.Prototype;
|
||||||
|
import org.squiddev.cobalt.compiler.CompileException;
|
||||||
|
import org.squiddev.cobalt.compiler.LuaC;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
class LuaCoverage
|
||||||
|
{
|
||||||
|
private static final Path ROOT = new File( "src/main/resources/assets/computercraft/lua" ).toPath();
|
||||||
|
private static final Path BIOS = ROOT.resolve( "bios.lua" );
|
||||||
|
private static final Path APIS = ROOT.resolve( "rom/apis" );
|
||||||
|
private static final Path SHELL = ROOT.resolve( "rom/programs/shell.lua" );
|
||||||
|
private static final Path MULTISHELL = ROOT.resolve( "rom/programs/advanced/multishell.lua" );
|
||||||
|
private static final Path TREASURE = ROOT.resolve( "treasure" );
|
||||||
|
|
||||||
|
private final Map<String, Map<Double, Double>> coverage;
|
||||||
|
private final String blank;
|
||||||
|
private final String zero;
|
||||||
|
private final String countFormat;
|
||||||
|
|
||||||
|
LuaCoverage( Map<String, Map<Double, Double>> coverage )
|
||||||
|
{
|
||||||
|
this.coverage = coverage;
|
||||||
|
|
||||||
|
int max = (int) coverage.values().stream()
|
||||||
|
.flatMapToDouble( x -> x.values().stream().mapToDouble( y -> y ) )
|
||||||
|
.max().orElse( 0 );
|
||||||
|
int maxLen = Math.max( 1, (int) Math.ceil( Math.log10( max ) ) );
|
||||||
|
blank = Strings.repeat( " ", maxLen + 1 );
|
||||||
|
zero = Strings.repeat( "*", maxLen ) + "0";
|
||||||
|
countFormat = "%" + (maxLen + 1) + "d";
|
||||||
|
}
|
||||||
|
|
||||||
|
void write( Writer out ) throws IOException
|
||||||
|
{
|
||||||
|
Files.find( ROOT, Integer.MAX_VALUE, ( path, attr ) -> attr.isRegularFile() && !path.startsWith( TREASURE ) ).forEach( path -> {
|
||||||
|
Path relative = ROOT.relativize( path );
|
||||||
|
String full = relative.toString().replace( '\\', '/' );
|
||||||
|
if( !full.endsWith( ".lua" ) ) return;
|
||||||
|
|
||||||
|
Map<Double, Double> files = Stream.of(
|
||||||
|
coverage.remove( "/" + full ),
|
||||||
|
path.equals( BIOS ) ? coverage.remove( "bios.lua" ) : null,
|
||||||
|
path.equals( SHELL ) ? coverage.remove( "shell.lua" ) : null,
|
||||||
|
path.equals( MULTISHELL ) ? coverage.remove( "multishell.lua" ) : null,
|
||||||
|
path.startsWith( APIS ) ? coverage.remove( path.getFileName().toString() ) : null
|
||||||
|
)
|
||||||
|
.filter( Objects::nonNull )
|
||||||
|
.flatMap( x -> x.entrySet().stream() )
|
||||||
|
.collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, Double::sum ) );
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
writeCoverageFor( out, path, files );
|
||||||
|
}
|
||||||
|
catch( IOException e )
|
||||||
|
{
|
||||||
|
throw new UncheckedIOException( e );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
for( String filename : coverage.keySet() )
|
||||||
|
{
|
||||||
|
if( filename.startsWith( "/test-rom/" ) ) continue;
|
||||||
|
ComputerCraft.log.warn( "Unknown file {}", filename );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeCoverageFor( Writer out, Path fullName, Map<Double, Double> visitedLines ) throws IOException
|
||||||
|
{
|
||||||
|
if( !Files.exists( fullName ) )
|
||||||
|
{
|
||||||
|
ComputerCraft.log.error( "Cannot locate file {}", fullName );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntSet activeLines = getActiveLines( fullName.toFile() );
|
||||||
|
|
||||||
|
out.write( "==============================================================================\n" );
|
||||||
|
out.write( fullName.toString().replace( '\\', '/' ) );
|
||||||
|
out.write( "\n" );
|
||||||
|
out.write( "==============================================================================\n" );
|
||||||
|
|
||||||
|
try( BufferedReader reader = Files.newBufferedReader( fullName ) )
|
||||||
|
{
|
||||||
|
String line;
|
||||||
|
int lineNo = 0;
|
||||||
|
while( (line = reader.readLine()) != null )
|
||||||
|
{
|
||||||
|
lineNo++;
|
||||||
|
Double count = visitedLines.get( (double) lineNo );
|
||||||
|
if( count != null )
|
||||||
|
{
|
||||||
|
out.write( String.format( countFormat, count.intValue() ) );
|
||||||
|
}
|
||||||
|
else if( activeLines.contains( lineNo ) )
|
||||||
|
{
|
||||||
|
out.write( zero );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out.write( blank );
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write( ' ' );
|
||||||
|
out.write( line );
|
||||||
|
out.write( "\n" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IntSet getActiveLines( File file ) throws IOException
|
||||||
|
{
|
||||||
|
IntSet activeLines = new IntOpenHashSet();
|
||||||
|
try( InputStream stream = new FileInputStream( file ) )
|
||||||
|
{
|
||||||
|
Prototype proto = LuaC.compile( stream, "@" + file.getPath() );
|
||||||
|
Queue<Prototype> queue = new ArrayDeque<>();
|
||||||
|
queue.add( proto );
|
||||||
|
|
||||||
|
while( (proto = queue.poll()) != null )
|
||||||
|
{
|
||||||
|
int[] lines = proto.lineinfo;
|
||||||
|
if( lines != null )
|
||||||
|
{
|
||||||
|
for( int line : lines )
|
||||||
|
{
|
||||||
|
activeLines.add( line );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( proto.p != null ) Collections.addAll( queue, proto.p );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( CompileException e )
|
||||||
|
{
|
||||||
|
throw new IllegalStateException( "Cannot compile", e );
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeLines;
|
||||||
|
}
|
||||||
|
}
|
|
@ -483,6 +483,49 @@ local function pending(name)
|
||||||
test_stack.n = n - 1
|
test_stack.n = n - 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local native_co_create, native_loadfile = coroutine.create, loadfile
|
||||||
|
local line_counts = {}
|
||||||
|
if cct_test then
|
||||||
|
local string_sub, debug_getinfo = string.sub, debug.getinfo
|
||||||
|
local function debug_hook(_, line_nr)
|
||||||
|
local name = debug_getinfo(2, "S").source
|
||||||
|
if string_sub(name, 1, 1) ~= "@" then return end
|
||||||
|
name = string_sub(name, 2)
|
||||||
|
|
||||||
|
local file = line_counts[name]
|
||||||
|
if not file then file = {} line_counts[name] = file end
|
||||||
|
file[line_nr] = (file[line_nr] or 0) + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
coroutine.create = function(...)
|
||||||
|
local co = native_co_create(...)
|
||||||
|
debug.sethook(co, debug_hook, "l")
|
||||||
|
return co
|
||||||
|
end
|
||||||
|
|
||||||
|
local expect = require "cc.expect".expect
|
||||||
|
_G.native_loadfile = native_loadfile
|
||||||
|
_G.loadfile = function(filename, mode, env)
|
||||||
|
-- Support the previous `loadfile(filename, env)` form instead.
|
||||||
|
if type(mode) == "table" and env == nil then
|
||||||
|
mode, env = nil, mode
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(1, filename, "string")
|
||||||
|
expect(2, mode, "string", "nil")
|
||||||
|
expect(3, env, "table", "nil")
|
||||||
|
|
||||||
|
local file = fs.open(filename, "r")
|
||||||
|
if not file then return nil, "File not found" end
|
||||||
|
|
||||||
|
local func, err = load(file.readAll(), "@/" .. fs.combine(filename, ""), mode, env)
|
||||||
|
file.close()
|
||||||
|
return func, err
|
||||||
|
end
|
||||||
|
|
||||||
|
debug.sethook(debug_hook, "l")
|
||||||
|
end
|
||||||
|
|
||||||
local arg = ...
|
local arg = ...
|
||||||
if arg == "--help" or arg == "-h" then
|
if arg == "--help" or arg == "-h" then
|
||||||
io.write("Usage: mcfly [DIR]\n")
|
io.write("Usage: mcfly [DIR]\n")
|
||||||
|
@ -648,4 +691,11 @@ if test_status.pending > 0 then
|
||||||
end
|
end
|
||||||
|
|
||||||
term.setTextColour(colours.white) io.write(info .. "\n")
|
term.setTextColour(colours.white) io.write(info .. "\n")
|
||||||
|
|
||||||
|
-- Restore hook stubs
|
||||||
|
debug.sethook(nil, "l")
|
||||||
|
coroutine.create = native_co_create
|
||||||
|
_G.loadfile = native_loadfile
|
||||||
|
|
||||||
|
if cct_test then cct_test.finish(line_counts) end
|
||||||
if howlci then howlci.log("debug", info) sleep(3) end
|
if howlci then howlci.log("debug", info) sleep(3) end
|
||||||
|
|
|
@ -12,6 +12,21 @@ describe("The fs library", function()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe("fs.isDriveRoot", function()
|
||||||
|
it("validates arguments", function()
|
||||||
|
fs.isDriveRoot("")
|
||||||
|
|
||||||
|
expect.error(fs.isDriveRoot, nil):eq("bad argument #1 (expected string, got nil)")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("correctly identifies drive roots", function()
|
||||||
|
expect(fs.isDriveRoot("/rom")):eq(true)
|
||||||
|
expect(fs.isDriveRoot("/")):eq(true)
|
||||||
|
expect(fs.isDriveRoot("/rom/startup.lua")):eq(false)
|
||||||
|
expect(fs.isDriveRoot("/rom/programs/delete.lua")):eq(false)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe("fs.list", function()
|
describe("fs.list", function()
|
||||||
it("fails on files", function()
|
it("fails on files", function()
|
||||||
expect.error(fs.list, "rom/startup.lua"):eq("/rom/startup.lua: Not a directory")
|
expect.error(fs.list, "rom/startup.lua"):eq("/rom/startup.lua: Not a directory")
|
||||||
|
|
|
@ -1,8 +1,44 @@
|
||||||
--- Tests the io library is (mostly) consistent with PUC Lua.
|
--- Tests the io library is (mostly) consistent with PUC Lua.
|
||||||
--
|
--
|
||||||
-- These tests are based on the tests for Lua 5.1
|
-- These tests are based on the tests for Lua 5.1 and 5.3
|
||||||
|
|
||||||
describe("The io library", function()
|
describe("The io library", function()
|
||||||
|
local file = "/test-files/tmp.txt"
|
||||||
|
local otherfile = "/test-files/tmp2.txt"
|
||||||
|
|
||||||
|
local t = '0123456789'
|
||||||
|
for _ = 1, 12 do t = t .. t end
|
||||||
|
assert(#t == 10 * 2 ^ 12)
|
||||||
|
|
||||||
|
local function read_all(f)
|
||||||
|
local h = fs.open(f, "rb")
|
||||||
|
local contents = h.readAll()
|
||||||
|
h.close()
|
||||||
|
return contents
|
||||||
|
end
|
||||||
|
|
||||||
|
local function write_file(f, contents)
|
||||||
|
local h = fs.open(f, "wb")
|
||||||
|
h.write(contents)
|
||||||
|
h.close()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setup()
|
||||||
|
write_file(file, "\"<EFBFBD>lo\"{a}\nsecond line\nthird line \n<EFBFBD>fourth_line\n\n\9\9 3450\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
describe("io.close", function()
|
||||||
|
it("cannot close stdin", function()
|
||||||
|
expect{ io.stdin:close() }:same { nil, "attempt to close standard stream" }
|
||||||
|
end)
|
||||||
|
it("cannot close stdout", function()
|
||||||
|
expect{ io.stdout:close() }:same { nil, "attempt to close standard stream" }
|
||||||
|
end)
|
||||||
|
it("cannot close stdout", function()
|
||||||
|
expect{ io.stdout:close() }:same { nil, "attempt to close standard stream" }
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
it("io.input on a handle returns that handle", function()
|
it("io.input on a handle returns that handle", function()
|
||||||
expect(io.input(io.stdin)):equals(io.stdin)
|
expect(io.input(io.stdin)):equals(io.stdin)
|
||||||
end)
|
end)
|
||||||
|
@ -11,11 +47,16 @@ describe("The io library", function()
|
||||||
expect(io.output(io.stdout)):equals(io.stdout)
|
expect(io.output(io.stdout)):equals(io.stdout)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("defines a __name field", function()
|
||||||
|
expect(getmetatable(io.input()).__name):eq("FILE*")
|
||||||
|
end)
|
||||||
|
|
||||||
describe("io.type", function()
|
describe("io.type", function()
|
||||||
it("returns file on handles", function()
|
it("returns file on handles", function()
|
||||||
local handle = io.input()
|
local handle = io.input()
|
||||||
expect(handle):type("table")
|
expect(handle):type("table")
|
||||||
expect(io.type(handle)):equals("file")
|
expect(io.type(handle)):equals("file")
|
||||||
|
expect(io.type(io.stdin)):equals("file")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("returns nil on values", function()
|
it("returns nil on values", function()
|
||||||
|
@ -33,6 +74,88 @@ describe("The io library", function()
|
||||||
expect.error(io.lines, ""):eq("/: No such file")
|
expect.error(io.lines, ""):eq("/: No such file")
|
||||||
expect.error(io.lines, false):eq("bad argument #1 (expected string, got boolean)")
|
expect.error(io.lines, false):eq("bad argument #1 (expected string, got boolean)")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("closes the file", function()
|
||||||
|
setup()
|
||||||
|
|
||||||
|
local n = 0
|
||||||
|
local f = io.lines(file)
|
||||||
|
while f() do n = n + 1 end
|
||||||
|
expect(n):eq(6)
|
||||||
|
|
||||||
|
expect.error(f):eq("file is already closed")
|
||||||
|
expect.error(f):eq("file is already closed")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("can copy a file", function()
|
||||||
|
setup()
|
||||||
|
|
||||||
|
local n = 0
|
||||||
|
io.output(otherfile)
|
||||||
|
for l in io.lines(file) do
|
||||||
|
io.write(l, "\n")
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
io.close()
|
||||||
|
expect(n):eq(6)
|
||||||
|
|
||||||
|
io.input(file)
|
||||||
|
local f = io.open(otherfile):lines()
|
||||||
|
local n = 0
|
||||||
|
for l in io.lines() do
|
||||||
|
expect(l):eq(f())
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
expect(n):eq(6)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("does not close on a normal file handle", function()
|
||||||
|
setup()
|
||||||
|
|
||||||
|
local f = assert(io.open(file))
|
||||||
|
local n = 0
|
||||||
|
for _ in f:lines() do n = n + 1 end
|
||||||
|
expect(n):eq(6)
|
||||||
|
|
||||||
|
expect(tostring(f):sub(1, 5)):eq("file ")
|
||||||
|
assert(f:close())
|
||||||
|
|
||||||
|
expect(tostring(f)):eq("file (closed)")
|
||||||
|
expect(io.type(f)):eq("closed file")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("accepts multiple arguments", function()
|
||||||
|
write_file(file, "0123456789\n")
|
||||||
|
for a, b in io.lines(file, 1, 1) do
|
||||||
|
if a == "\n" then
|
||||||
|
expect(b):eq(nil)
|
||||||
|
else
|
||||||
|
expect(tonumber(a)):eq(tonumber(b) - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for a, b, c in io.lines(file, 1, 2, "a") do
|
||||||
|
expect(a):eq("0")
|
||||||
|
expect(b):eq("12")
|
||||||
|
expect(c):eq("3456789\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
for a, b, c in io.lines(file, "a", 0, 1) do
|
||||||
|
if a == "" then break end
|
||||||
|
expect(a):eq("0123456789\n")
|
||||||
|
expect(b):eq(nil)
|
||||||
|
expect(c):eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
write_file(file, "00\n10\n20\n30\n40\n")
|
||||||
|
for a, b in io.lines(file, "n", "n") do
|
||||||
|
if a == 40 then
|
||||||
|
expect(b):eq(nil)
|
||||||
|
else
|
||||||
|
expect(a):eq(b - 10)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("io.open", function()
|
describe("io.open", function()
|
||||||
|
@ -44,6 +167,22 @@ describe("The io library", function()
|
||||||
expect.error(io.open, "", false):eq("bad argument #2 (expected string, got boolean)")
|
expect.error(io.open, "", false):eq("bad argument #2 (expected string, got boolean)")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("checks the mode", function()
|
||||||
|
io.open(file, "w"):close()
|
||||||
|
|
||||||
|
-- This really should be invalid mode, but I'll live.
|
||||||
|
expect.error(io.open, file, "rw"):str_match("Unsupported mode")
|
||||||
|
-- TODO: expect.error(io.open, file, "rb+"):str_match("Unsupported mode")
|
||||||
|
expect.error(io.open, file, "r+bk"):str_match("Unsupported mode")
|
||||||
|
expect.error(io.open, file, ""):str_match("Unsupported mode")
|
||||||
|
expect.error(io.open, file, "+"):str_match("Unsupported mode")
|
||||||
|
expect.error(io.open, file, "b"):str_match("Unsupported mode")
|
||||||
|
|
||||||
|
assert(io.open(file, "r+b")):close()
|
||||||
|
assert(io.open(file, "r+")):close()
|
||||||
|
assert(io.open(file, "rb")):close()
|
||||||
|
end)
|
||||||
|
|
||||||
it("returns an error message on non-existent files", function()
|
it("returns an error message on non-existent files", function()
|
||||||
local a, b = io.open('xuxu_nao_existe')
|
local a, b = io.open('xuxu_nao_existe')
|
||||||
expect(a):equals(nil)
|
expect(a):equals(nil)
|
||||||
|
@ -51,26 +190,139 @@ describe("The io library", function()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
pending("io.output allows redirecting and seeking", function()
|
describe("a readable handle", function()
|
||||||
fs.delete("/tmp/io_spec.txt")
|
it("cannot be written to", function()
|
||||||
|
write_file(file, "")
|
||||||
|
io.input(file)
|
||||||
|
expect { io.input():write("xuxu") }:same { nil, "file is not writable" }
|
||||||
|
io.input(io.stdin)
|
||||||
|
end)
|
||||||
|
|
||||||
io.output("/tmp/io_spec.txt")
|
it("supports various modes", function()
|
||||||
|
write_file(file, "alo\n " .. t .. " ;end of file\n")
|
||||||
|
|
||||||
expect(io.output()):not_equals(io.stdout)
|
io.input(file)
|
||||||
|
expect(io.read()):eq("alo")
|
||||||
|
expect(io.read(1)):eq(' ')
|
||||||
|
expect(io.read(#t)):eq(t)
|
||||||
|
expect(io.read(1)):eq(' ')
|
||||||
|
expect(io.read(0))
|
||||||
|
expect(io.read('*a')):eq(';end of file\n')
|
||||||
|
expect(io.read(0)):eq(nil)
|
||||||
|
expect(io.close(io.input())):eq(true)
|
||||||
|
|
||||||
expect(io.output():seek()):equal(0)
|
fs.delete(file)
|
||||||
assert(io.write("alo alo"))
|
end)
|
||||||
expect(io.output():seek()):equal(#"alo alo")
|
|
||||||
expect(io.output():seek("cur", -3)):equal(#"alo alo" - 3)
|
|
||||||
assert(io.write("joao"))
|
|
||||||
expect(io.output():seek("end"):equal(#"alo joao"))
|
|
||||||
|
|
||||||
expect(io.output():seek("set")):equal(0)
|
it("support seeking", function()
|
||||||
|
setup()
|
||||||
|
io.input(file)
|
||||||
|
|
||||||
assert(io.write('"<22>lo"', "{a}\n", "second line\n", "third line \n"))
|
expect(io.read(0)):eq("") -- not eof
|
||||||
assert(io.write('<EFBFBD>fourth_line'))
|
expect(io.read(5, '*l')):eq('"<22>lo"')
|
||||||
|
expect(io.read(0)):eq("")
|
||||||
|
expect(io.read()):eq("second line")
|
||||||
|
local x = io.input():seek()
|
||||||
|
expect(io.read()):eq("third line ")
|
||||||
|
assert(io.input():seek("set", x))
|
||||||
|
expect(io.read('*l')):eq("third line ")
|
||||||
|
expect(io.read(1)):eq("<EFBFBD>")
|
||||||
|
expect(io.read(#"fourth_line")):eq("fourth_line")
|
||||||
|
assert(io.input():seek("cur", -#"fourth_line"))
|
||||||
|
expect(io.read()):eq("fourth_line")
|
||||||
|
expect(io.read()):eq("") -- empty line
|
||||||
|
expect(io.read(8)):eq('\9\9 3450') -- FIXME: Not actually supported
|
||||||
|
expect(io.read(1)):eq('\n')
|
||||||
|
expect(io.read(0)):eq(nil) -- end of file
|
||||||
|
expect(io.read(1)):eq(nil) -- end of file
|
||||||
|
expect(({ io.read(1) })[2]):eq(nil)
|
||||||
|
expect(io.read()):eq(nil) -- end of file
|
||||||
|
expect(({ io.read() })[2]):eq(nil)
|
||||||
|
expect(io.read('*n')):eq(nil) -- end of file
|
||||||
|
expect(({ io.read('*n') })[2]):eq(nil)
|
||||||
|
expect(io.read('*a')):eq('') -- end of file (OK for `*a')
|
||||||
|
expect(io.read('*a')):eq('') -- end of file (OK for `*a')
|
||||||
|
|
||||||
io.output(io.stdout)
|
io.close(io.input())
|
||||||
expect(io.output()):equals(io.stdout)
|
end)
|
||||||
|
|
||||||
|
it("supports the 'L' mode", function()
|
||||||
|
write_file(file, "\n\nline\nother")
|
||||||
|
|
||||||
|
io.input(file)
|
||||||
|
expect(io.read"L"):eq("\n")
|
||||||
|
expect(io.read"L"):eq("\n")
|
||||||
|
expect(io.read"L"):eq("line\n")
|
||||||
|
expect(io.read"L"):eq("other")
|
||||||
|
expect(io.read"L"):eq(nil)
|
||||||
|
io.input():close()
|
||||||
|
|
||||||
|
local f = assert(io.open(file))
|
||||||
|
local s = ""
|
||||||
|
for l in f:lines("L") do s = s .. l end
|
||||||
|
expect(s):eq("\n\nline\nother")
|
||||||
|
f:close()
|
||||||
|
|
||||||
|
io.input(file)
|
||||||
|
s = ""
|
||||||
|
for l in io.lines(nil, "L") do s = s .. l end
|
||||||
|
expect(s):eq("\n\nline\nother")
|
||||||
|
io.input():close()
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
for l in io.lines(file, "L") do s = s .. l end
|
||||||
|
expect(s):eq("\n\nline\nother")
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
for l in io.lines(file, "l") do s = s .. l end
|
||||||
|
expect(s):eq("lineother")
|
||||||
|
|
||||||
|
write_file(file, "a = 10 + 34\na = 2*a\na = -a\n")
|
||||||
|
local t = {}
|
||||||
|
load(io.lines(file, "L"), nil, nil, t)()
|
||||||
|
expect(t.a):eq(-((10 + 34) * 2))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("a writable handle", function()
|
||||||
|
it("supports seeking", function()
|
||||||
|
fs.delete(file)
|
||||||
|
io.output(file)
|
||||||
|
|
||||||
|
expect(io.output()):not_equals(io.stdout)
|
||||||
|
|
||||||
|
expect(io.output():seek()):equal(0)
|
||||||
|
assert(io.write("alo alo"))
|
||||||
|
expect(io.output():seek()):equal(#"alo alo")
|
||||||
|
expect(io.output():seek("cur", -3)):equal(#"alo alo" - 3)
|
||||||
|
assert(io.write("joao"))
|
||||||
|
expect(io.output():seek("end")):equal(#"alo joao")
|
||||||
|
|
||||||
|
expect(io.output():seek("set")):equal(0)
|
||||||
|
|
||||||
|
assert(io.write('"<22>lo"', "{a}\n", "second line\n", "third line \n"))
|
||||||
|
assert(io.write('<EFBFBD>fourth_line'))
|
||||||
|
|
||||||
|
io.output(io.stdout)
|
||||||
|
expect(io.output()):equals(io.stdout)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("supports appending", function()
|
||||||
|
io.output(file)
|
||||||
|
io.write("alo\n")
|
||||||
|
io.close()
|
||||||
|
expect.error(io.write)
|
||||||
|
|
||||||
|
local f = io.open(file, "a")
|
||||||
|
io.output(f)
|
||||||
|
|
||||||
|
assert(io.write(' ' .. t .. ' '))
|
||||||
|
assert(io.write(';', 'end of file\n'))
|
||||||
|
f:flush()
|
||||||
|
io.flush()
|
||||||
|
f:close()
|
||||||
|
|
||||||
|
expect(read_all(file)):eq("alo\n " .. t .. " ;end of file\n")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -51,7 +51,7 @@ describe("The peripheral library", function()
|
||||||
|
|
||||||
it_modem("has the correct error location", function()
|
it_modem("has the correct error location", function()
|
||||||
expect.error(function() peripheral.call("top", "isOpen", false) end)
|
expect.error(function() peripheral.call("top", "isOpen", false) end)
|
||||||
:str_match("^peripheral_spec.lua:%d+: bad argument #1 %(number expected, got boolean%)$")
|
:str_match("^[^:]+:%d+: bad argument #1 %(number expected, got boolean%)$")
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ describe("The textutils library", function()
|
||||||
describe("textutils.empty_json_array", function()
|
describe("textutils.empty_json_array", function()
|
||||||
it("is immutable", function()
|
it("is immutable", function()
|
||||||
expect.error(function() textutils.empty_json_array[1] = true end)
|
expect.error(function() textutils.empty_json_array[1] = true end)
|
||||||
:eq("textutils_spec.lua:51: attempt to mutate textutils.empty_json_array")
|
:str_match("^[^:]+:51: attempt to mutate textutils.empty_json_array$")
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ describe("The Lua base library", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("loadfile", function()
|
describe("loadfile", function()
|
||||||
|
local loadfile = _G.native_loadfile or loadfile
|
||||||
|
|
||||||
local function make_file()
|
local function make_file()
|
||||||
local tmp = fs.open("test-files/out.lua", "w")
|
local tmp = fs.open("test-files/out.lua", "w")
|
||||||
tmp.write("return _ENV")
|
tmp.write("return _ENV")
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe("cc.expect", function()
|
||||||
worker()
|
worker()
|
||||||
end
|
end
|
||||||
|
|
||||||
expect.error(trampoline):eq("expect_spec.lua:27: bad argument #1 to 'worker' (expected string, got nil)")
|
expect.error(trampoline):str_match("^[^:]*expect_spec.lua:27: bad argument #1 to 'worker' %(expected string, got nil%)$")
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,15 @@ describe("The rm program", function()
|
||||||
|
|
||||||
it("errors when trying to delete a read-only file", function()
|
it("errors when trying to delete a read-only file", function()
|
||||||
expect(capture(stub, "rm /rom/startup.lua"))
|
expect(capture(stub, "rm /rom/startup.lua"))
|
||||||
:matches { ok = true, output = "", error = "/rom/startup.lua: Access denied\n" }
|
:matches { ok = true, output = "", error = "Cannot delete read-only file /rom/startup.lua\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("errors when trying to delete the root mount", function()
|
||||||
|
expect(capture(stub, "rm /")):matches {
|
||||||
|
ok = true,
|
||||||
|
output = "To delete its contents run rm /*\n",
|
||||||
|
error = "Cannot delete mount /\n",
|
||||||
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("errors when a glob fails to match", function()
|
it("errors when a glob fails to match", function()
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
local capture = require "test_helpers".capture_program
|
local capture = require "test_helpers".capture_program
|
||||||
|
|
||||||
describe("The move program", function()
|
describe("The move program", function()
|
||||||
|
local function cleanup() fs.delete("/test-files/move") end
|
||||||
local function touch(file)
|
local function touch(file)
|
||||||
io.open(file, "w"):close()
|
io.open(file, "w"):close()
|
||||||
end
|
end
|
||||||
|
|
||||||
it("move a file", function()
|
it("move a file", function()
|
||||||
|
cleanup()
|
||||||
touch("/test-files/move/a.txt")
|
touch("/test-files/move/a.txt")
|
||||||
|
|
||||||
shell.run("move /test-files/move/a.txt /test-files/move/b.txt")
|
shell.run("move /test-files/move/a.txt /test-files/move/b.txt")
|
||||||
|
@ -14,11 +16,57 @@ describe("The move program", function()
|
||||||
expect(fs.exists("/test-files/move/b.txt")):eq(true)
|
expect(fs.exists("/test-files/move/b.txt")):eq(true)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("try to move a not existing file", function()
|
it("moves a file to a directory", function()
|
||||||
|
cleanup()
|
||||||
|
touch("/test-files/move/a.txt")
|
||||||
|
fs.makeDir("/test-files/move/a")
|
||||||
|
|
||||||
|
expect(capture(stub, "move /test-files/move/a.txt /test-files/move/a"))
|
||||||
|
:matches { ok = true }
|
||||||
|
|
||||||
|
expect(fs.exists("/test-files/move/a.txt")):eq(false)
|
||||||
|
expect(fs.exists("/test-files/move/a/a.txt")):eq(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("fails when moving a file which doesn't exist", function()
|
||||||
expect(capture(stub, "move nothing destination"))
|
expect(capture(stub, "move nothing destination"))
|
||||||
:matches { ok = true, output = "", error = "No matching files\n" }
|
:matches { ok = true, output = "", error = "No matching files\n" }
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("fails when overwriting an existing file", function()
|
||||||
|
cleanup()
|
||||||
|
touch("/test-files/move/a.txt")
|
||||||
|
|
||||||
|
expect(capture(stub, "move /test-files/move/a.txt /test-files/move/a.txt"))
|
||||||
|
:matches { ok = true, output = "", error = "Destination exists\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("fails when moving to read-only locations", function()
|
||||||
|
cleanup()
|
||||||
|
touch("/test-files/move/a.txt")
|
||||||
|
|
||||||
|
expect(capture(stub, "move /test-files/move/a.txt /rom/test.txt"))
|
||||||
|
:matches { ok = true, output = "", error = "Destination is read-only\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("fails when moving from read-only locations", function()
|
||||||
|
expect(capture(stub, "move /rom/startup.lua /test-files/move/not-exist.txt"))
|
||||||
|
:matches { ok = true, output = "", error = "Cannot move read-only file /rom/startup.lua\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("fails when moving mounts", function()
|
||||||
|
expect(capture(stub, "move /rom /test-files/move/rom"))
|
||||||
|
:matches { ok = true, output = "", error = "Cannot move mount /rom\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("fails when moving a file multiple times", function()
|
||||||
|
cleanup()
|
||||||
|
touch("/test-files/move/a.txt")
|
||||||
|
touch("/test-files/move/b.txt")
|
||||||
|
expect(capture(stub, "move /test-files/move/*.txt /test-files/move/c.txt"))
|
||||||
|
:matches { ok = true, output = "", error = "Cannot overwrite file multiple times\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
it("displays the usage with no arguments", function()
|
it("displays the usage with no arguments", function()
|
||||||
expect(capture(stub, "move"))
|
expect(capture(stub, "move"))
|
||||||
:matches { ok = true, output = "Usage: mv <source> <destination>\n", error = "" }
|
:matches { ok = true, output = "Usage: mv <source> <destination>\n", error = "" }
|
||||||
|
|
|
@ -26,13 +26,23 @@ describe("The rename program", function()
|
||||||
:matches { ok = true, output = "", error = "Destination exists\n" }
|
:matches { ok = true, output = "", error = "Destination exists\n" }
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("fails when copying to read-only locations", function()
|
it("fails when renaming to read-only locations", function()
|
||||||
touch("/test-files/rename/d.txt")
|
touch("/test-files/rename/d.txt")
|
||||||
|
|
||||||
expect(capture(stub, "rename /test-files/rename/d.txt /rom/test.txt"))
|
expect(capture(stub, "rename /test-files/rename/d.txt /rom/test.txt"))
|
||||||
:matches { ok = true, output = "", error = "Destination is read-only\n" }
|
:matches { ok = true, output = "", error = "Destination is read-only\n" }
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("fails when renaming from read-only locations", function()
|
||||||
|
expect(capture(stub, "rename /rom/startup.lua /test-files/rename/d.txt"))
|
||||||
|
:matches { ok = true, output = "", error = "Source is read-only\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("fails when renaming mounts", function()
|
||||||
|
expect(capture(stub, "rename /rom /test-files/rename/rom"))
|
||||||
|
:matches { ok = true, output = "", error = "Can't rename mounts\n" }
|
||||||
|
end)
|
||||||
|
|
||||||
it("displays the usage when given no arguments", function()
|
it("displays the usage when given no arguments", function()
|
||||||
expect(capture(stub, "rename"))
|
expect(capture(stub, "rename"))
|
||||||
:matches { ok = true, output = "Usage: rename <source> <destination>\n", error = "" }
|
:matches { ok = true, output = "Usage: rename <source> <destination>\n", error = "" }
|
||||||
|
|
|
@ -23,4 +23,3 @@ describe("The type program", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,13 @@ for path in pathlib.Path("src").glob("**/*"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with path.open(encoding="utf-8") as file:
|
with path.open(encoding="utf-8") as file:
|
||||||
has_dos, has_trailing, needs_final = False, False, False
|
has_dos, has_trailing, first, count = False, False, 0, True
|
||||||
for i, line in enumerate(file):
|
for i, line in enumerate(file):
|
||||||
|
if first:
|
||||||
|
first = False
|
||||||
|
if line.strip() == "":
|
||||||
|
print("%s has empty first line" % path)
|
||||||
|
|
||||||
if len(line) >= 2 and line[-2] == "\r" and line[-1] == "\n" and not has_line:
|
if len(line) >= 2 and line[-2] == "\r" and line[-1] == "\n" and not has_line:
|
||||||
print("%s has contains '\\r\\n' on line %d" % (path, i + 1))
|
print("%s has contains '\\r\\n' on line %d" % (path, i + 1))
|
||||||
problems = has_dos = True
|
problems = has_dos = True
|
||||||
|
@ -21,8 +26,15 @@ for path in pathlib.Path("src").glob("**/*"):
|
||||||
print("%s has trailing whitespace on line %d" % (path, i + 1))
|
print("%s has trailing whitespace on line %d" % (path, i + 1))
|
||||||
problems = has_trailing = True
|
problems = has_trailing = True
|
||||||
|
|
||||||
if line is not None and len(line) >= 1 and line[-1] != "\n":
|
if len(line) == 0 or line[-1] != "\n":
|
||||||
print("%s should end with '\\n'" % path)
|
count = 0
|
||||||
|
elif line.strip() == "":
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
if count != 1:
|
||||||
|
print("%s should have 1 trailing lines, but has %d" % (path, count))
|
||||||
problems = True
|
problems = True
|
||||||
|
|
||||||
if problems:
|
if problems:
|
||||||
|
|
Loading…
Reference in New Issue