1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-05 16:13:03 +00:00

Compare commits

...

11 Commits

Author SHA1 Message Date
Jonathan Coates
6196aae488 Merge branch 'mc-1.16.x' into mc-1.17.x 2021-12-11 07:49:33 +00:00
Jonathan Coates
92a0ef2b75 Bump CC:T version 2021-12-11 07:37:10 +00:00
Jonathan Coates
1f6e0f287d 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.
2021-12-10 13:13:31 +00:00
Jonathan Coates
0e4b7a5a75 Prevent terminal buttons stealing focus
I have shutdown my computer by accident far too many times now.
2021-12-08 23:16:53 +00:00
Jonathan Coates
47ad7a35dc 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!
2021-12-08 22:47:21 +00:00
Jonathan Coates
3eab2a9b57 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.
2021-12-07 18:27:29 +00:00
Jonathan Coates
c4024a4c4c Use an admonition instead 2021-12-02 22:41:58 +00:00
Jonathan Coates
f5fb82cd7d Merge pull request #977 from MCJack123/patch-9
Add package.searchpath
2021-12-02 12:34:07 +00:00
MCJack123
e18ba8a2c2 Add package.searchpath 2021-12-01 18:55:24 -05:00
Jonathan Coates
422bfdb60d Add 1.18 and remove 1.15 from the issue template
No, we're not pushing it to Curse yet, but while I remember.
2021-12-01 20:24:37 +00:00
Jonathan Coates
1851ed31cd 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.
2021-12-01 20:09:38 +00:00
25 changed files with 611 additions and 58 deletions

View File

@@ -8,9 +8,9 @@ body:
label: Minecraft Version label: Minecraft Version
description: What version of Minecraft are you using? description: What version of Minecraft are you using?
options: options:
- 1.15.x
- 1.16.x - 1.16.x
- 1.17.x - 1.17.x
- 1.18.x
validations: validations:
required: true required: true
- type: input - type: input

View File

@@ -1,7 +1,7 @@
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G
# Mod properties # Mod properties
mod_version=1.99.0 mod_version=1.99.1
# Minecraft properties (update mods.toml when changing) # Minecraft properties (update mods.toml when changing)
mc_version=1.17.1 mc_version=1.17.1

View File

@@ -183,6 +183,24 @@ public interface IArguments
return (Map<?, ?>) value; 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. * Get an argument as a double.
* *
@@ -314,6 +332,27 @@ public interface IArguments
return Optional.of( (Map<?, ?>) value ); 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<LuaTable<?, ?>> 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. * Get an argument as a double.
* *
@@ -404,4 +443,13 @@ public interface IArguments
{ {
return optTable( index ).orElse( def ); 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()
{
}
} }

View File

@@ -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 * Run this function on the main server thread. This should be specified for any method which interacts with
* Minecraft in a thread-unsafe manner. * 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) * @see ILuaContext#issueMainThreadTask(ILuaTask)
*/ */
boolean mainThread() default false; 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;
} }

View File

@@ -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<K, V> extends Map<K, V>
{
/**
* 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<? extends K, ? extends V> map )
{
throw new UnsupportedOperationException( "Cannot modify LuaTable" );
}
@Override
default void clear()
{
throw new UnsupportedOperationException( "Cannot modify LuaTable" );
}
}

View File

@@ -102,6 +102,34 @@ public final class LuaValues
return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" ); 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}. * Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}.
* *

View File

@@ -98,7 +98,10 @@ public final class MethodResult
{ {
Objects.requireNonNull( callback, "callback cannot be null" ); Objects.requireNonNull( callback, "callback cannot be null" );
return new MethodResult( new Object[] { filter }, results -> { 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 ); return callback.resume( results );
} ); } );
} }

View File

@@ -5,10 +5,12 @@
*/ */
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
/** /**
* An implementation of {@link IArguments} which wraps an array of {@link Object}. * 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 public final class ObjectArguments implements IArguments
{ {
private static final IArguments EMPTY = new ObjectArguments(); private static final IArguments EMPTY = new ObjectArguments();
private boolean released = false;
private final List<Object> args; private final List<Object> args;
@Deprecated @Deprecated
@@ -63,4 +67,34 @@ public final class ObjectArguments implements IArguments
{ {
return args.toArray(); 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<LuaTable<?, ?>> 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;
}
} }

View File

@@ -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<Object, Object>
{
private final Map<Object, Object> 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<Object> keySet()
{
return map.keySet();
}
@Nonnull
@Override
public Collection<Object> values()
{
return map.values();
}
@Nonnull
@Override
public Set<Entry<Object, Object>> entrySet()
{
return map.entrySet();
}
}

View File

@@ -8,6 +8,7 @@ package dan200.computercraft.client.gui;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.ComputerSidebar; import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
import dan200.computercraft.client.gui.widgets.WidgetTerminal; import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
@@ -102,6 +103,16 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
renderTooltip( stack, mouseX, mouseY ); 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 @Override
public final boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY ) public final boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{ {

View File

@@ -10,6 +10,7 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.WidgetTerminal; import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.inventory.ContainerComputerBase; import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.gui.Font; import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.MenuAccess; import net.minecraft.client.gui.screens.inventory.MenuAccess;
@@ -44,8 +45,11 @@ public class NoTermComputerScreen<T extends ContainerComputerBase> extends Scree
protected void init() protected void init()
{ {
passEvents = true; // Pass mouse vents through to the game's mouse handler. 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.mouseHandler.grabMouse();
minecraft.screen = this; minecraft.screen = this;
KeyMapping.releaseAll();
super.init(); super.init();
minecraft.keyboardHandler.setSendRepeatsToGui( true ); minecraft.keyboardHandler.setSendRepeatsToGui( true );

View File

@@ -130,8 +130,6 @@ public final class Generator<T>
private void addMethod( List<NamedMethod<T>> methods, Method method, LuaFunction annotation, PeripheralType genericType, T instance ) private void addMethod( List<NamedMethod<T>> methods, Method method, LuaFunction annotation, PeripheralType genericType, T instance )
{ {
if( annotation.mainThread() ) instance = wrap.apply( instance );
String[] names = annotation.value(); String[] names = annotation.value();
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread(); boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
if( names.length == 0 ) if( names.length == 0 )
@@ -183,6 +181,13 @@ public final class Generator<T>
} }
} }
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 // 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. // only come from generic sources, so this should be safe.
Class<?> target = Modifier.isStatic( modifiers ) ? method.getParameterTypes()[0] : method.getDeclaringClass(); Class<?> target = Modifier.isStatic( modifiers ) ? method.getParameterTypes()[0] : method.getDeclaringClass();
@@ -190,11 +195,13 @@ public final class Generator<T>
try try
{ {
String className = method.getDeclaringClass().getName() + "$cc$" + method.getName() + METHOD_ID.getAndIncrement(); 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(); if( bytes == null ) return Optional.empty();
Class<?> klass = DeclaringClassLoader.INSTANCE.define( className, bytes, method.getDeclaringClass().getProtectionDomain() ); 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 ) catch( ReflectiveOperationException | ClassFormatError | RuntimeException e )
{ {
@@ -205,7 +212,7 @@ public final class Generator<T>
} }
@Nullable @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( ".", "/" ); String internalName = className.replace( ".", "/" );
@@ -238,7 +245,7 @@ public final class Generator<T>
int argIndex = 0; int argIndex = 0;
for( java.lang.reflect.Type genericArg : method.getGenericParameterTypes() ) 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 == null ) return null;
if( loadedArg ) argIndex++; if( loadedArg ) argIndex++;
} }
@@ -285,7 +292,7 @@ public final class Generator<T>
return cw.toByteArray(); 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 ) if( genericArg == target )
{ {
@@ -324,7 +331,7 @@ public final class Generator<T>
return true; return true;
} }
String name = Reflect.getLuaName( Primitives.unwrap( klass ) ); String name = Reflect.getLuaName( Primitives.unwrap( klass ), unsafe );
if( name != null ) if( name != null )
{ {
mw.visitVarInsn( ALOAD, 2 + context.size() ); mw.visitVarInsn( ALOAD, 2 + context.size() );
@@ -344,7 +351,7 @@ public final class Generator<T>
return true; return true;
} }
String name = arg == Object.class ? "" : Reflect.getLuaName( arg ); String name = arg == Object.class ? "" : Reflect.getLuaName( arg, unsafe );
if( name != null ) if( name != null )
{ {
if( Reflect.getRawType( method, genericArg, false ) == null ) return null; if( Reflect.getRawType( method, genericArg, false ) == null ) return null;

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.asm; package dan200.computercraft.core.asm;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.LuaTable;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -25,7 +26,7 @@ final class Reflect
} }
@Nullable @Nullable
static String getLuaName( Class<?> klass ) static String getLuaName( Class<?> klass, boolean unsafe )
{ {
if( klass.isPrimitive() ) if( klass.isPrimitive() )
{ {
@@ -39,6 +40,7 @@ final class Reflect
if( klass == Map.class ) return "Table"; if( klass == Map.class ) return "Table";
if( klass == String.class ) return "String"; if( klass == String.class ) return "String";
if( klass == ByteBuffer.class ) return "Bytes"; if( klass == ByteBuffer.class ) return "Bytes";
if( klass == LuaTable.class && unsafe ) return "TableUnsafe";
} }
return null; return null;

View File

@@ -59,6 +59,10 @@ class BasicFunction extends VarArgFunction
} }
throw new LuaError( "Java Exception Thrown: " + t, 0 ); throw new LuaError( "Java Exception Thrown: " + t, 0 );
} }
finally
{
arguments.releaseImmediate();
}
if( results.getCallback() != null ) if( results.getCallback() != null )
{ {

View File

@@ -15,6 +15,7 @@ import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.ThreadUtils; import dan200.computercraft.shared.util.ThreadUtils;
import org.squiddev.cobalt.*; import org.squiddev.cobalt.*;
import org.squiddev.cobalt.LuaTable;
import org.squiddev.cobalt.compiler.CompileException; import org.squiddev.cobalt.compiler.CompileException;
import org.squiddev.cobalt.compiler.LoadState; import org.squiddev.cobalt.compiler.LoadState;
import org.squiddev.cobalt.debug.DebugFrame; import org.squiddev.cobalt.debug.DebugFrame;

View File

@@ -69,6 +69,10 @@ class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterprete
} }
throw new LuaError( "Java Exception Thrown: " + t, 0 ); throw new LuaError( "Java Exception Thrown: " + t, 0 );
} }
finally
{
arguments.releaseImmediate();
}
ILuaCallback callback = results.getCallback(); ILuaCallback callback = results.getCallback();
Varargs ret = machine.toValues( results.getResult() ); Varargs ret = machine.toValues( results.getResult() );

View File

@@ -0,0 +1,141 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues;
import org.squiddev.cobalt.*;
import javax.annotation.Nonnull;
import java.util.*;
import static dan200.computercraft.api.lua.LuaValues.badTableItem;
import static dan200.computercraft.api.lua.LuaValues.getNumericType;
class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
{
private final VarargArguments arguments;
private final LuaTable table;
private Map<Object, Object> 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<Object, Object> 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<Object> keySet()
{
return getBackingMap().keySet();
}
@Nonnull
@Override
public Collection<Object> values()
{
return getBackingMap().values();
}
@Nonnull
@Override
public Set<Entry<Object, Object>> entrySet()
{
return getBackingMap().entrySet();
}
private void checkValid()
{
if( arguments.released )
{
throw new IllegalStateException( "Cannot use LuaTable after IArguments has been released" );
}
}
}

View File

@@ -19,6 +19,7 @@ class VarargArguments implements IArguments
{ {
static final IArguments EMPTY = new VarargArguments( Constants.NONE ); static final IArguments EMPTY = new VarargArguments( Constants.NONE );
boolean released;
private final Varargs varargs; private final Varargs varargs;
private Object[] cache; private Object[] cache;
@@ -98,4 +99,39 @@ class VarargArguments implements IArguments
LuaString str = ((LuaBaseString) value).strvalue(); LuaString str = ((LuaBaseString) value).strvalue();
return Optional.of( ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer() ); 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<dan200.computercraft.api.lua.LuaTable<?, ?>> 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;
}
} }

View File

@@ -94,9 +94,12 @@ class Expander
if( !isPositive ) if( !isPositive )
{ {
BlockEntity otherOrigin = level.getBlockEntity( otherMonitor.toWorldPos( 0, 0 ) ); 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; this.width = width;

View File

@@ -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 @{peripheral.call} function. This takes the name of our peripheral, the name of
the function we want to call, and then its arguments. the function we want to call, and then its arguments.
> Some bits of the peripheral API call peripheral functions *methods* instead :::info
> (for example, the @{peripheral.getMethods} function). Don't worry, they're the Some bits of the peripheral API call peripheral functions *methods* instead
> same thing! (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 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: @{monitor.write|write some text to it}. We'd write the following:

View File

@@ -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 # 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). * 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).

View File

@@ -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). * Add package.searchpath to the cc.require API. (MCJack123)
* Peripherals can now have multiple types. `peripheral.getType` now returns multiple values, and `peripheral.hasType` checks if a peripheral has a specific type. * Provide a more efficient way for the Java API to consume Lua tables in certain restricted cases.
* 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)
And several bug fixes: Several bug fixes:
* Fix various computer commands failing when OP level was 4. * Fix keys being "sticky" when opening the off-hand pocket computer GUI.
* Various documentation fixes. (xXTurnerLP, MCJack123) * Correctly handle broken coroutine managers resuming Java code with a `nil` event.
* Fix `textutils.serialize` not serialising infinity and nan values. (Wojbie) * Prevent computer buttons stealing focus from the terminal.
* Wired modems now correctly clean up mounts when a peripheral is detached. * Fix a class cast exception when a monitor is malformed in ways I do not quite understand.
* 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).
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

@@ -31,22 +31,37 @@ local function preload(package)
end end
end end
local function from_file(package, env, dir) local function from_file(package, env)
return function(name) 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 = "" local sError = ""
for pattern in string.gmatch(package.path, "[^;]+") do for pattern in string.gmatch(path, "[^;]+") do
local sPath = string.gsub(pattern, "%?", fname) local sPath = string.gsub(pattern, "%?", fname)
if sPath:sub(1, 1) ~= "/" then if sPath:sub(1, 1) ~= "/" then
sPath = fs.combine(dir, sPath) sPath = fs.combine(dir, sPath)
end end
if fs.exists(sPath) and not fs.isDir(sPath) then if fs.exists(sPath) and not fs.isDir(sPath) then
local fnFile, sError = loadfile(sPath, nil, env) return sPath
if fnFile then
return fnFile, sPath
else
return nil, sError
end
else else
if #sError > 0 then if #sError > 0 then
sError = sError .. "\n " sError = sError .. "\n "
@@ -118,7 +133,8 @@ local function make_package(env, dir)
end end
package.config = "/\n;\n?\n!\n-" package.config = "/\n;\n?\n!\n-"
package.preload = {} 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 return make_require(package), package
end end

View File

@@ -97,7 +97,7 @@ public class ComputerTestDelegate
if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." ); 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 ); IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files // Remove any existing files

View File

@@ -120,6 +120,13 @@ public class GeneratorTest
contramap( notNullValue(), "callback", MethodResult::getCallback ) ); contramap( notNullValue(), "callback", MethodResult::getCallback ) );
} }
@Test
public void testUnsafe()
{
List<NamedMethod<LuaMethod>> methods = LuaMethod.GENERATOR.getMethods( Unsafe.class );
assertThat( methods, contains( named( "withUnsafe" ) ) );
}
public static class Basic public static class Basic
{ {
@LuaFunction @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> T find( Collection<NamedMethod<T>> methods, String name ) private static <T> T find( Collection<NamedMethod<T>> methods, String name )
{ {
return methods.stream() return methods.stream()