diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 5d9f31909..100c87d94 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -8,9 +8,9 @@ body: label: Minecraft Version description: What version of Minecraft are you using? options: - - 1.15.x - 1.16.x - 1.17.x + - 1.18.x validations: required: true - type: input diff --git a/gradle.properties b/gradle.properties index 4dbfc1daf..ed3ef86e5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.jvmargs=-Xmx3G # Mod properties -mod_version=1.99.0 +mod_version=1.99.1 # Minecraft properties (update mods.toml when changing) mc_version=1.18.1 diff --git a/src/main/java/dan200/computercraft/api/lua/IArguments.java b/src/main/java/dan200/computercraft/api/lua/IArguments.java index 02e8b659d..6bd53ea7d 100644 --- a/src/main/java/dan200/computercraft/api/lua/IArguments.java +++ b/src/main/java/dan200/computercraft/api/lua/IArguments.java @@ -183,6 +183,24 @@ public interface IArguments return (Map) value; } + /** + * Get an argument as a table in an unsafe manner. + * + * Classes implementing this interface may choose to implement a more optimised version which does not copy the + * table, instead returning a wrapper version, making it more efficient. However, the caller must guarantee that + * they do not access off the computer thread (and so should not be used with main-thread functions) or once the + * function call has finished (for instance, in callbacks). + * + * @param index The argument number. + * @return The argument's value. + * @throws LuaException If the value is not a table. + */ + @Nonnull + default LuaTable getTableUnsafe( int index ) throws LuaException + { + return new ObjectLuaTable( getTable( index ) ); + } + /** * Get an argument as a double. * @@ -314,6 +332,27 @@ public interface IArguments return Optional.of( (Map) value ); } + /** + * Get an argument as a table in an unsafe manner. + * + * Classes implementing this interface may choose to implement a more optimised version which does not copy the + * table, instead returning a wrapper version, making it more efficient. However, the caller must guarantee that + * they do not access off the computer thread (and so should not be used with main-thread functions) or once the + * function call has finished (for instance, in callbacks). + * + * @param index The argument number. + * @return The argument's value, or {@link Optional#empty()} if not present. + * @throws LuaException If the value is not a table. + */ + @Nonnull + default Optional> optTableUnsafe( int index ) throws LuaException + { + Object value = get( index ); + if( value == null ) return Optional.empty(); + if( !(value instanceof Map) ) throw LuaValues.badArgumentOf( index, "map", value ); + return Optional.of( new ObjectLuaTable( (Map) value ) ); + } + /** * Get an argument as a double. * @@ -404,4 +443,13 @@ public interface IArguments { return optTable( index ).orElse( def ); } + + /** + * This is called when the current function finishes, before any main thread tasks have run. + * + * Called when the current function returns, and so some values are no longer guaranteed to be safe to access. + */ + default void releaseImmediate() + { + } } diff --git a/src/main/java/dan200/computercraft/api/lua/LuaFunction.java b/src/main/java/dan200/computercraft/api/lua/LuaFunction.java index 5558b153c..0e14db58d 100644 --- a/src/main/java/dan200/computercraft/api/lua/LuaFunction.java +++ b/src/main/java/dan200/computercraft/api/lua/LuaFunction.java @@ -51,8 +51,17 @@ public @interface LuaFunction * Run this function on the main server thread. This should be specified for any method which interacts with * Minecraft in a thread-unsafe manner. * - * @return Whether this functi + * @return Whether this function should be run on the main thread. * @see ILuaContext#issueMainThreadTask(ILuaTask) */ boolean mainThread() default false; + + /** + * Allow using "unsafe" arguments, such {@link IArguments#getTableUnsafe(int)}. + * + * This is incompatible with {@link #mainThread()}. + * + * @return Whether this function supports unsafe arguments. + */ + boolean unsafe() default false; } diff --git a/src/main/java/dan200/computercraft/api/lua/LuaTable.java b/src/main/java/dan200/computercraft/api/lua/LuaTable.java new file mode 100644 index 000000000..6bbafe6fd --- /dev/null +++ b/src/main/java/dan200/computercraft/api/lua/LuaTable.java @@ -0,0 +1,114 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ +package dan200.computercraft.api.lua; + +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.util.Map; + +import static dan200.computercraft.api.lua.LuaValues.*; + +public interface LuaTable extends Map +{ + /** + * Compute the length of the array part of this table. + * + * @return This table's length. + */ + default int length() + { + int size = 0; + while( containsKey( (double) (size + 1) ) ) size++; + return size; + } + + /** + * Get an array entry as an integer. + * + * @param index The index in the table, starting at 1. + * @return The table's value. + * @throws LuaException If the value is not an integer. + */ + default long getLong( int index ) throws LuaException + { + Object value = get( (double) index ); + if( !(value instanceof Number) ) throw badTableItem( index, "number", getType( value ) ); + + Number number = (Number) value; + double asDouble = number.doubleValue(); + if( !Double.isFinite( asDouble ) ) throw badTableItem( index, "number", getNumericType( asDouble ) ); + return number.longValue(); + } + + /** + * Get a table entry as an integer. + * + * @param key The name of the field in the table. + * @return The table's value. + * @throws LuaException If the value is not an integer. + */ + default long getLong( String key ) throws LuaException + { + Object value = get( key ); + if( !(value instanceof Number) ) throw badField( key, "number", getType( value ) ); + + Number number = (Number) value; + double asDouble = number.doubleValue(); + if( !Double.isFinite( asDouble ) ) throw badField( key, "number", getNumericType( asDouble ) ); + return number.longValue(); + } + + /** + * Get an array entry as an integer. + * + * @param index The index in the table, starting at 1. + * @return The table's value. + * @throws LuaException If the value is not an integer. + */ + default int getInt( int index ) throws LuaException + { + return (int) getLong( index ); + } + + /** + * Get a table entry as an integer. + * + * @param key The name of the field in the table. + * @return The table's value. + * @throws LuaException If the value is not an integer. + */ + default int getInt( String key ) throws LuaException + { + return (int) getLong( key ); + } + + + @Nullable + @Override + default V put( K o, V o2 ) + { + throw new UnsupportedOperationException( "Cannot modify LuaTable" ); + } + + @Override + default V remove( Object o ) + { + throw new UnsupportedOperationException( "Cannot modify LuaTable" ); + } + + @Override + default void putAll( @Nonnull Map map ) + { + throw new UnsupportedOperationException( "Cannot modify LuaTable" ); + } + + @Override + default void clear() + { + throw new UnsupportedOperationException( "Cannot modify LuaTable" ); + } +} diff --git a/src/main/java/dan200/computercraft/api/lua/LuaValues.java b/src/main/java/dan200/computercraft/api/lua/LuaValues.java index 8eec784ff..bc984036e 100644 --- a/src/main/java/dan200/computercraft/api/lua/LuaValues.java +++ b/src/main/java/dan200/computercraft/api/lua/LuaValues.java @@ -102,6 +102,34 @@ public final class LuaValues return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" ); } + /** + * Construct a table item exception, from an expected and actual type. + * + * @param index The index into the table, starting from 1. + * @param expected The expected type for this table item. + * @param actual The provided type for this table item. + * @return The constructed exception, which should be thrown immediately. + */ + @Nonnull + public static LuaException badTableItem( int index, @Nonnull String expected, @Nonnull String actual ) + { + return new LuaException( "table item #" + index + " is not " + expected + " (got " + actual + ")" ); + } + + /** + * Construct a field exception, from an expected and actual type. + * + * @param key The name of the field. + * @param expected The expected type for this table item. + * @param actual The provided type for this table item. + * @return The constructed exception, which should be thrown immediately. + */ + @Nonnull + public static LuaException badField( String key, @Nonnull String expected, @Nonnull String actual ) + { + return new LuaException( "field " + key + " is not " + expected + " (got " + actual + ")" ); + } + /** * Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}. * diff --git a/src/main/java/dan200/computercraft/api/lua/MethodResult.java b/src/main/java/dan200/computercraft/api/lua/MethodResult.java index 7d15d60b7..88342fe85 100644 --- a/src/main/java/dan200/computercraft/api/lua/MethodResult.java +++ b/src/main/java/dan200/computercraft/api/lua/MethodResult.java @@ -98,7 +98,10 @@ public final class MethodResult { Objects.requireNonNull( callback, "callback cannot be null" ); return new MethodResult( new Object[] { filter }, results -> { - if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 ); + if( results.length >= 1 && Objects.equals( results[0], "terminate" ) ) + { + throw new LuaException( "Terminated", 0 ); + } return callback.resume( results ); } ); } diff --git a/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java index 6dd7e3c0f..710fc1e34 100644 --- a/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java +++ b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java @@ -5,10 +5,12 @@ */ package dan200.computercraft.api.lua; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Optional; /** * An implementation of {@link IArguments} which wraps an array of {@link Object}. @@ -16,6 +18,8 @@ import java.util.Objects; public final class ObjectArguments implements IArguments { private static final IArguments EMPTY = new ObjectArguments(); + + private boolean released = false; private final List args; @Deprecated @@ -63,4 +67,34 @@ public final class ObjectArguments implements IArguments { return args.toArray(); } + + @Nonnull + @Override + public LuaTable getTableUnsafe( int index ) throws LuaException + { + if( released ) + { + throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" ); + } + + return IArguments.super.getTableUnsafe( index ); + } + + @Nonnull + @Override + public Optional> optTableUnsafe( int index ) throws LuaException + { + if( released ) + { + throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" ); + } + + return IArguments.super.optTableUnsafe( index ); + } + + @Override + public void releaseImmediate() + { + released = true; + } } diff --git a/src/main/java/dan200/computercraft/api/lua/ObjectLuaTable.java b/src/main/java/dan200/computercraft/api/lua/ObjectLuaTable.java new file mode 100644 index 000000000..fbc80fad1 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/lua/ObjectLuaTable.java @@ -0,0 +1,73 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ +package dan200.computercraft.api.lua; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public class ObjectLuaTable implements LuaTable +{ + private final Map map; + + public ObjectLuaTable( Map map ) + { + this.map = Collections.unmodifiableMap( map ); + } + + @Override + public int size() + { + return map.size(); + } + + @Override + public boolean isEmpty() + { + return map.isEmpty(); + } + + @Override + public boolean containsKey( Object o ) + { + return map.containsKey( o ); + } + + @Override + public boolean containsValue( Object o ) + { + return map.containsKey( o ); + } + + @Override + public Object get( Object o ) + { + return map.get( o ); + } + + @Nonnull + @Override + public Set keySet() + { + return map.keySet(); + } + + @Nonnull + @Override + public Collection values() + { + return map.values(); + } + + @Nonnull + @Override + public Set> entrySet() + { + return map.entrySet(); + } +} diff --git a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java index 7d6d42bc6..1f7d437f3 100644 --- a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java +++ b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java @@ -8,6 +8,7 @@ package dan200.computercraft.client.gui; import com.mojang.blaze3d.vertex.PoseStack; import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.gui.widgets.ComputerSidebar; +import dan200.computercraft.client.gui.widgets.DynamicImageButton; import dan200.computercraft.client.gui.widgets.WidgetTerminal; import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ComputerFamily; @@ -102,6 +103,16 @@ public abstract class ComputerScreenBase extend renderTooltip( stack, mouseX, mouseY ); } + @Override + public boolean mouseClicked( double x, double y, int button ) + { + boolean changed = super.mouseClicked( x, y, button ); + // Clicking the terminate/shutdown button steals focus, which means then pressing "enter" will click the button + // again. Restore the focus to the terminal in these cases. + if( getFocused() instanceof DynamicImageButton ) setFocused( terminal ); + return changed; + } + @Override public final boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY ) { diff --git a/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java b/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java index 225a119a4..765ae97f4 100644 --- a/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java +++ b/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java @@ -10,6 +10,7 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.gui.widgets.WidgetTerminal; import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.inventory.ContainerComputerBase; +import net.minecraft.client.KeyMapping; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.MenuAccess; @@ -44,8 +45,11 @@ public class NoTermComputerScreen extends Scree protected void init() { passEvents = true; // Pass mouse vents through to the game's mouse handler. + // First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that + // grabbing unsets. minecraft.mouseHandler.grabMouse(); minecraft.screen = this; + KeyMapping.releaseAll(); super.init(); minecraft.keyboardHandler.setSendRepeatsToGui( true ); diff --git a/src/main/java/dan200/computercraft/core/asm/Generator.java b/src/main/java/dan200/computercraft/core/asm/Generator.java index 4cd2d66cf..73e5ee392 100644 --- a/src/main/java/dan200/computercraft/core/asm/Generator.java +++ b/src/main/java/dan200/computercraft/core/asm/Generator.java @@ -130,8 +130,6 @@ public final class Generator private void addMethod( List> methods, Method method, LuaFunction annotation, PeripheralType genericType, T instance ) { - if( annotation.mainThread() ) instance = wrap.apply( instance ); - String[] names = annotation.value(); boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread(); if( names.length == 0 ) @@ -183,6 +181,13 @@ public final class Generator } } + LuaFunction annotation = method.getAnnotation( LuaFunction.class ); + if( annotation.unsafe() && annotation.mainThread() ) + { + ComputerCraft.log.error( "Lua Method {} cannot use unsafe and mainThread", name ); + return Optional.empty(); + } + // We have some rather ugly handling of static methods in both here and the main generate function. Static methods // only come from generic sources, so this should be safe. Class target = Modifier.isStatic( modifiers ) ? method.getParameterTypes()[0] : method.getDeclaringClass(); @@ -190,11 +195,13 @@ public final class Generator try { String className = method.getDeclaringClass().getName() + "$cc$" + method.getName() + METHOD_ID.getAndIncrement(); - byte[] bytes = generate( className, target, method ); + byte[] bytes = generate( className, target, method, annotation.unsafe() ); if( bytes == null ) return Optional.empty(); Class klass = DeclaringClassLoader.INSTANCE.define( className, bytes, method.getDeclaringClass().getProtectionDomain() ); - return Optional.of( klass.asSubclass( base ).getDeclaredConstructor().newInstance() ); + + T instance = klass.asSubclass( base ).getDeclaredConstructor().newInstance(); + return Optional.of( annotation.mainThread() ? wrap.apply( instance ) : instance ); } catch( ReflectiveOperationException | ClassFormatError | RuntimeException e ) { @@ -205,7 +212,7 @@ public final class Generator } @Nullable - private byte[] generate( String className, Class target, Method method ) + private byte[] generate( String className, Class target, Method method, boolean unsafe ) { String internalName = className.replace( ".", "/" ); @@ -238,7 +245,7 @@ public final class Generator int argIndex = 0; for( java.lang.reflect.Type genericArg : method.getGenericParameterTypes() ) { - Boolean loadedArg = loadArg( mw, target, method, genericArg, argIndex ); + Boolean loadedArg = loadArg( mw, target, method, unsafe, genericArg, argIndex ); if( loadedArg == null ) return null; if( loadedArg ) argIndex++; } @@ -285,7 +292,7 @@ public final class Generator return cw.toByteArray(); } - private Boolean loadArg( MethodVisitor mw, Class target, Method method, java.lang.reflect.Type genericArg, int argIndex ) + private Boolean loadArg( MethodVisitor mw, Class target, Method method, boolean unsafe, java.lang.reflect.Type genericArg, int argIndex ) { if( genericArg == target ) { @@ -324,7 +331,7 @@ public final class Generator return true; } - String name = Reflect.getLuaName( Primitives.unwrap( klass ) ); + String name = Reflect.getLuaName( Primitives.unwrap( klass ), unsafe ); if( name != null ) { mw.visitVarInsn( ALOAD, 2 + context.size() ); @@ -344,7 +351,7 @@ public final class Generator return true; } - String name = arg == Object.class ? "" : Reflect.getLuaName( arg ); + String name = arg == Object.class ? "" : Reflect.getLuaName( arg, unsafe ); if( name != null ) { if( Reflect.getRawType( method, genericArg, false ) == null ) return null; diff --git a/src/main/java/dan200/computercraft/core/asm/Reflect.java b/src/main/java/dan200/computercraft/core/asm/Reflect.java index 57a624683..bc4f033e0 100644 --- a/src/main/java/dan200/computercraft/core/asm/Reflect.java +++ b/src/main/java/dan200/computercraft/core/asm/Reflect.java @@ -6,6 +6,7 @@ package dan200.computercraft.core.asm; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.LuaTable; import org.objectweb.asm.MethodVisitor; import javax.annotation.Nullable; @@ -25,7 +26,7 @@ final class Reflect } @Nullable - static String getLuaName( Class klass ) + static String getLuaName( Class klass, boolean unsafe ) { if( klass.isPrimitive() ) { @@ -39,6 +40,7 @@ final class Reflect if( klass == Map.class ) return "Table"; if( klass == String.class ) return "String"; if( klass == ByteBuffer.class ) return "Bytes"; + if( klass == LuaTable.class && unsafe ) return "TableUnsafe"; } return null; diff --git a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java index f515cd455..5749a79cf 100644 --- a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java @@ -59,6 +59,10 @@ class BasicFunction extends VarArgFunction } throw new LuaError( "Java Exception Thrown: " + t, 0 ); } + finally + { + arguments.releaseImmediate(); + } if( results.getCallback() != null ) { diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index f444729e2..ce78cf454 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -15,6 +15,7 @@ import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.shared.util.ThreadUtils; import org.squiddev.cobalt.*; +import org.squiddev.cobalt.LuaTable; import org.squiddev.cobalt.compiler.CompileException; import org.squiddev.cobalt.compiler.LoadState; import org.squiddev.cobalt.debug.DebugFrame; diff --git a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java index b2cbb021c..cf62eef59 100644 --- a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java @@ -69,6 +69,10 @@ class ResultInterpreterFunction extends ResumableVarArgFunction +{ + private final VarargArguments arguments; + private final LuaTable table; + private Map backingMap; + + TableImpl( VarargArguments arguments, LuaTable table ) + { + this.arguments = arguments; + this.table = table; + } + + @Override + public int size() + { + checkValid(); + try + { + return table.keyCount(); + } + catch( LuaError e ) + { + throw new IllegalStateException( e ); + } + } + + @Override + public int length() + { + return table.length(); + } + + @Override + public long getLong( int index ) throws LuaException + { + LuaValue value = table.rawget( index ); + if( !(value instanceof LuaNumber) ) throw LuaValues.badTableItem( index, "number", value.typeName() ); + if( value instanceof LuaInteger ) return value.toInteger(); + + double number = value.toDouble(); + if( !Double.isFinite( number ) ) throw badTableItem( index, "number", getNumericType( number ) ); + return (long) number; + } + + @Override + public boolean isEmpty() + { + checkValid(); + try + { + return table.next( Constants.NIL ).first().isNil(); + } + catch( LuaError e ) + { + throw new IllegalStateException( e ); + } + } + + @Nonnull + private LuaValue getImpl( Object o ) + { + checkValid(); + if( o instanceof String ) return table.rawget( (String) o ); + if( o instanceof Integer ) return table.rawget( (Integer) o ); + return Constants.NIL; + } + + @Override + public boolean containsKey( Object o ) + { + return !getImpl( o ).isNil(); + } + + @Override + public Object get( Object o ) + { + return CobaltLuaMachine.toObject( getImpl( o ), null ); + } + + @Nonnull + private Map getBackingMap() + { + checkValid(); + if( backingMap != null ) return backingMap; + return backingMap = Collections.unmodifiableMap( + Objects.requireNonNull( (Map) CobaltLuaMachine.toObject( table, null ) ) + ); + } + + @Override + public boolean containsValue( Object o ) + { + return getBackingMap().containsKey( o ); + } + + @Nonnull + @Override + public Set keySet() + { + return getBackingMap().keySet(); + } + + @Nonnull + @Override + public Collection values() + { + return getBackingMap().values(); + } + + @Nonnull + @Override + public Set> entrySet() + { + return getBackingMap().entrySet(); + } + + private void checkValid() + { + if( arguments.released ) + { + throw new IllegalStateException( "Cannot use LuaTable after IArguments has been released" ); + } + } +} diff --git a/src/main/java/dan200/computercraft/core/lua/VarargArguments.java b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java index 0079e41c7..dc7eb5e2c 100644 --- a/src/main/java/dan200/computercraft/core/lua/VarargArguments.java +++ b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java @@ -19,6 +19,7 @@ class VarargArguments implements IArguments { static final IArguments EMPTY = new VarargArguments( Constants.NONE ); + boolean released; private final Varargs varargs; private Object[] cache; @@ -98,4 +99,39 @@ class VarargArguments implements IArguments LuaString str = ((LuaBaseString) value).strvalue(); return Optional.of( ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer() ); } + + @Nonnull + @Override + public dan200.computercraft.api.lua.LuaTable getTableUnsafe( int index ) throws LuaException + { + if( released ) + { + throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" ); + } + + LuaValue value = varargs.arg( index + 1 ); + if( !(value instanceof LuaTable) ) throw LuaValues.badArgument( index, "table", value.typeName() ); + return new TableImpl( this, (LuaTable) value ); + } + + @Nonnull + @Override + public Optional> optTableUnsafe( int index ) throws LuaException + { + if( released ) + { + throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" ); + } + + LuaValue value = varargs.arg( index + 1 ); + if( value.isNil() ) return Optional.empty(); + if( !(value instanceof LuaTable) ) throw LuaValues.badArgument( index, "table", value.typeName() ); + return Optional.of( new TableImpl( this, (LuaTable) value ) ); + } + + @Override + public void releaseImmediate() + { + released = true; + } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/Expander.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/Expander.java index 21290ccfb..a76e03274 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/Expander.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/Expander.java @@ -94,9 +94,12 @@ class Expander if( !isPositive ) { BlockEntity otherOrigin = level.getBlockEntity( otherMonitor.toWorldPos( 0, 0 ) ); - if( otherOrigin == null || !origin.isCompatible( (TileMonitor) otherOrigin ) ) return false; + if( !(otherOrigin instanceof TileMonitor originMonitor) || !origin.isCompatible( originMonitor ) ) + { + return false; + } - origin = (TileMonitor) otherOrigin; + origin = originMonitor; } this.width = width; diff --git a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua index f3c74a1c2..cb12e6da6 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua @@ -28,9 +28,11 @@ Once you have the name of a peripheral, you can call functions on it using the @{peripheral.call} function. This takes the name of our peripheral, the name of the function we want to call, and then its arguments. -> Some bits of the peripheral API call peripheral functions *methods* instead -> (for example, the @{peripheral.getMethods} function). Don't worry, they're the -> same thing! +:::info +Some bits of the peripheral API call peripheral functions *methods* instead +(for example, the @{peripheral.getMethods} function). Don't worry, they're the +same thing! +::: Let's say we have a monitor above our computer (and so "top") and want to @{monitor.write|write some text to it}. We'd write the following: diff --git a/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/src/main/resources/data/computercraft/lua/rom/help/changelog.md index cf2e4558d..bf949acc4 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,14 @@ +# New features in CC: Tweaked 1.99.1 + +* Add package.searchpath to the cc.require API. (MCJack123) +* Provide a more efficient way for the Java API to consume Lua tables in certain restricted cases. + +Several bug fixes: +* Fix keys being "sticky" when opening the off-hand pocket computer GUI. +* Correctly handle broken coroutine managers resuming Java code with a `nil` event. +* Prevent computer buttons stealing focus from the terminal. +* Fix a class cast exception when a monitor is malformed in ways I do not quite understand. + # New features in CC: Tweaked 1.99.0 * Pocket computers in their offhand will open without showing a terminal. You can look around and interact with the world, but your keyboard will be forwarded to the computer. (Wojbie, MagGen-hub). diff --git a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index 5c906b4f9..ca27e718e 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,32 +1,12 @@ -New features in CC: Tweaked 1.99.0 +New features in CC: Tweaked 1.99.1 -* Pocket computers in their offhand will open without showing a terminal. You can look around and interact with the world, but your keyboard will be forwarded to the computer. (Wojbie, MagGen-hub). -* Peripherals can now have multiple types. `peripheral.getType` now returns multiple values, and `peripheral.hasType` checks if a peripheral has a specific type. -* Add several missing keys to the `keys` table. (ralphgod3) -* Add feature introduction/changed version information to the documentation. (MCJack123) -* Increase the file upload limit to 512KiB. -* Rednet can now handle computer IDs larger than 65535. (Ale32bit) -* Optimise deduplication of rednet messages (MCJack123) -* Make `term.blit` colours case insensitive. (Ocawesome101) -* Add a new `about` program for easier version identification. (MCJack123) -* Optimise peripheral calls in `rednet.run`. (xAnavrins) -* Add dimension parameter to `commands.getBlockInfo`. -* Add `cc.pretty.pretty_print` helper function (Lupus590). -* Add back JEI integration. -* Turtle and pocket computer upgrades can now be added and modified with data packs. -* Various translation updates (MORIMORI3017, Ale2Bit, mindy15963) +* Add package.searchpath to the cc.require API. (MCJack123) +* Provide a more efficient way for the Java API to consume Lua tables in certain restricted cases. -And several bug fixes: -* Fix various computer commands failing when OP level was 4. -* Various documentation fixes. (xXTurnerLP, MCJack123) -* Fix `textutils.serialize` not serialising infinity and nan values. (Wojbie) -* Wired modems now correctly clean up mounts when a peripheral is detached. -* Fix incorrect turtle and pocket computer upgrade recipes in the recipe book. -* Fix speakers not playing sounds added via resource packs which are not registered in-game. -* Fix speaker upgrades sending packets after the server has stopped. -* Monitor sizing has been rewritten, hopefully making it more stable. -* Peripherals are now invalidated when the computer ticks, rather than when the peripheral changes. -* Fix printouts and pocket computers rendering at fullbright when in item frames. -* All mod blocks now have an effective tool (pickaxe). +Several bug fixes: +* Fix keys being "sticky" when opening the off-hand pocket computer GUI. +* Correctly handle broken coroutine managers resuming Java code with a `nil` event. +* Prevent computer buttons stealing focus from the terminal. +* Fix a class cast exception when a monitor is malformed in ways I do not quite understand. Type "help changelog" to see the full version history. diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua index 24c27b15c..ecf17fe8e 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua @@ -31,22 +31,37 @@ local function preload(package) end end -local function from_file(package, env, dir) +local function from_file(package, env) return function(name) - local fname = string.gsub(name, "%.", "/") + local sPath, sError = package.searchpath(name, package.path) + if not sPath then + return nil, sError + end + local fnFile, sError = loadfile(sPath, nil, env) + if fnFile then + return fnFile, sPath + else + return nil, sError + end + end +end + +local function make_searchpath(dir) + return function(name, path, sep, rep) + expect(1, name, "string") + expect(2, path, "string") + sep = expect(3, sep, "string", "nil") or "." + rep = expect(4, rep, "string", "nil") or "/" + + local fname = string.gsub(name, sep:gsub("%.", "%%%."), rep) local sError = "" - for pattern in string.gmatch(package.path, "[^;]+") do + for pattern in string.gmatch(path, "[^;]+") do local sPath = string.gsub(pattern, "%?", fname) if sPath:sub(1, 1) ~= "/" then sPath = fs.combine(dir, sPath) end if fs.exists(sPath) and not fs.isDir(sPath) then - local fnFile, sError = loadfile(sPath, nil, env) - if fnFile then - return fnFile, sPath - else - return nil, sError - end + return sPath else if #sError > 0 then sError = sError .. "\n " @@ -118,7 +133,8 @@ local function make_package(env, dir) end package.config = "/\n;\n?\n!\n-" package.preload = {} - package.loaders = { preload(package), from_file(package, env, dir) } + package.loaders = { preload(package), from_file(package, env) } + package.searchpath = make_searchpath(dir) return make_require(package), package end diff --git a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java index 75a3461e3..59c3ad20c 100644 --- a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java +++ b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java @@ -97,7 +97,7 @@ public class ComputerTestDelegate if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." ); - Terminal term = new Terminal( 80, 30 ); + Terminal term = new Terminal( 80, 100 ); IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 ); // Remove any existing files diff --git a/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java index 21aec5288..7b817b14e 100644 --- a/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java +++ b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java @@ -120,6 +120,13 @@ public class GeneratorTest contramap( notNullValue(), "callback", MethodResult::getCallback ) ); } + @Test + public void testUnsafe() + { + List> methods = LuaMethod.GENERATOR.getMethods( Unsafe.class ); + assertThat( methods, contains( named( "withUnsafe" ) ) ); + } + public static class Basic { @LuaFunction @@ -222,6 +229,21 @@ public class GeneratorTest {} } + public static class Unsafe + { + @LuaFunction( unsafe = true ) + public final void withUnsafe( LuaTable table ) + {} + + @LuaFunction + public final void withoutUnsafe( LuaTable table ) + {} + + @LuaFunction( unsafe = true, mainThread = true ) + public final void invalid( LuaTable table ) + {} + } + private static T find( Collection> methods, String name ) { return methods.stream()