From 1851ed31cdc04517e88b48e5f917fdeeac77bd0c Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 1 Dec 2021 20:09:38 +0000 Subject: [PATCH 1/9] Release keys when opening the offhand pocket computer screen Opening a screen KeyBinding.releaseAll(), which forces all inputs to be considered released. However, our init() function then calls grabMouse(), which calls Keybinding.setAll(), undoing this work. The fix we're going for here is to call releaseAll() one more time[^1] after grabbing the mouse. I think if this becomes any more of a problem, we should roll our own grabMouse which _doesn't_ implement any specific behaviour. Fixes #975 [^1]: Obvious problem here is that we do minecraft.screen=xyz rather than setScreen. We need to - otherwise we'd just hit a stack overflow - but it's not great. --- .../computercraft/client/gui/NoTermComputerScreen.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java b/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java index 49f101fb0..05000c837 100644 --- a/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java +++ b/src/main/java/dan200/computercraft/client/gui/NoTermComputerScreen.java @@ -13,6 +13,7 @@ import dan200.computercraft.shared.computer.inventory.ContainerComputerBase; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.IHasContainer; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.settings.KeyBinding; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.util.IReorderingProcessor; import net.minecraft.util.text.ITextComponent; @@ -43,9 +44,12 @@ public class NoTermComputerScreen extends Scree @Override protected void init() { - this.passEvents = true; // to allow gui click events pass through mouseHelper protection (see MouseHelper.OnPres:105 code string) + 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; + KeyBinding.releaseAll(); super.init(); minecraft.keyboardHandler.setSendRepeatsToGui( true ); From 422bfdb60de67723a12954df1295ee3e3ea19911 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 1 Dec 2021 20:24:37 +0000 Subject: [PATCH 2/9] Add 1.18 and remove 1.15 from the issue template No, we're not pushing it to Curse yet, but while I remember. --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e18ba8a2c2ccc81f5c7df838d47c3127dbc088a0 Mon Sep 17 00:00:00 2001 From: MCJack123 Date: Wed, 1 Dec 2021 18:55:24 -0500 Subject: [PATCH 3/9] Add package.searchpath --- .../lua/rom/modules/main/cc/require.lua | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) 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 From c4024a4c4c26cb2d9c08c31519903e2cf82bf614 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 2 Dec 2021 22:41:23 +0000 Subject: [PATCH 4/9] Use an admonition instead --- .../data/computercraft/lua/rom/apis/peripheral.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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: From 3eab2a9b57968525296b93e0228711866267a88f Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 7 Dec 2021 18:27:29 +0000 Subject: [PATCH 5/9] Add support for a zero-copy Lua table The API is entirely designed for the needs of the speaker right now, so doesn't do much else. --- .../computercraft/api/lua/IArguments.java | 48 ++++++ .../computercraft/api/lua/LuaFunction.java | 11 +- .../computercraft/api/lua/LuaTable.java | 114 ++++++++++++++ .../computercraft/api/lua/LuaValues.java | 28 ++++ .../api/lua/ObjectArguments.java | 34 +++++ .../computercraft/api/lua/ObjectLuaTable.java | 73 +++++++++ .../computercraft/core/asm/Generator.java | 25 ++-- .../computercraft/core/asm/Reflect.java | 4 +- .../computercraft/core/lua/BasicFunction.java | 4 + .../core/lua/CobaltLuaMachine.java | 1 + .../core/lua/ResultInterpreterFunction.java | 4 + .../computercraft/core/lua/TableImpl.java | 141 ++++++++++++++++++ .../core/lua/VarargArguments.java | 36 +++++ .../core/ComputerTestDelegate.java | 2 +- .../computercraft/core/asm/GeneratorTest.java | 22 +++ 15 files changed, 535 insertions(+), 12 deletions(-) create mode 100644 src/main/java/dan200/computercraft/api/lua/LuaTable.java create mode 100644 src/main/java/dan200/computercraft/api/lua/ObjectLuaTable.java create mode 100644 src/main/java/dan200/computercraft/core/lua/TableImpl.java 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/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/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 4517fecc3..b7928d4c0 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 c6b2932e7..d76f27b47 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 495d1a2d1..ffc2ea444 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/test/java/dan200/computercraft/core/ComputerTestDelegate.java b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java index 18d04dac8..a0adb6250 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() From 47ad7a35dcfea8363e6b51fe242e3900e46da837 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 8 Dec 2021 22:45:23 +0000 Subject: [PATCH 6/9] Fix NPE when pulling an event with no type I assume people have broken coroutine dispatchers - I didn't think it was possible to queue an actual event with no type. See cc-tweaked/cc-restitched#31. Will fix it too once merged downstream! --- src/main/java/dan200/computercraft/api/lua/MethodResult.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 ); } ); } From 0e4b7a5a753d7e9b54022c70aa5da0427513f6d4 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 8 Dec 2021 23:16:53 +0000 Subject: [PATCH 7/9] Prevent terminal buttons stealing focus I have shutdown my computer by accident far too many times now. --- .../computercraft/client/gui/ComputerScreenBase.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java index 95dea7f96..90329531f 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.matrix.MatrixStack; 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 ) { From 1f6e0f287d0aa64d133ac9588829259df27ed103 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 10 Dec 2021 13:13:31 +0000 Subject: [PATCH 8/9] Ensure the origin monitor is valid too Blurh, still not sure if this is Correct or anything, but have no clue what's causing this. Fixes #985. Hopefully. --- .../computercraft/shared/peripheral/monitor/Expander.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 94aaf0d49..c99b05a0e 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/Expander.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/Expander.java @@ -95,7 +95,10 @@ class Expander if( !isPositive ) { TileEntity otherOrigin = level.getBlockEntity( otherMonitor.toWorldPos( 0, 0 ) ); - if( otherOrigin == null || !origin.isCompatible( (TileMonitor) otherOrigin ) ) return false; + if( !(otherOrigin instanceof TileMonitor) || !origin.isCompatible( (TileMonitor) otherOrigin ) ) + { + return false; + } origin = (TileMonitor) otherOrigin; } From 92a0ef2b754827ce2dd42c9dfb36de25d49c2330 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sat, 11 Dec 2021 07:37:10 +0000 Subject: [PATCH 9/9] Bump CC:T version --- gradle.properties | 2 +- .../computercraft/lua/rom/help/changelog.md | 11 +++++++ .../computercraft/lua/rom/help/whatsnew.md | 32 +++++-------------- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/gradle.properties b/gradle.properties index 300298aca..2340513f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Mod properties -mod_version=1.99.0 +mod_version=1.99.1 # Minecraft properties (update mods.toml when changing) mc_version=1.16.5 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 7aa09fcdf..0d713e569 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 31d94fbfe..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,28 +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). -* 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. +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.