mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-06-16 10:09:55 +00:00
458 lines
15 KiB
Java
458 lines
15 KiB
Java
|
package dan200.computercraft.core.lua.waluaigi;
|
||
|
|
||
|
import cc.tweaked.waluaigi.*;
|
||
|
import dan200.computercraft.ComputerCraft;
|
||
|
import dan200.computercraft.api.lua.*;
|
||
|
import dan200.computercraft.core.asm.LuaMethod;
|
||
|
import dan200.computercraft.core.asm.ObjectSource;
|
||
|
import dan200.computercraft.core.computer.Computer;
|
||
|
import dan200.computercraft.core.computer.TimeoutState;
|
||
|
import dan200.computercraft.core.lua.LuaContext;
|
||
|
import dan200.computercraft.core.lua.MachineResult;
|
||
|
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
|
||
|
import it.unimi.dsi.fastutil.ints.IntPriorityQueue;
|
||
|
|
||
|
import javax.annotation.Nullable;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Collection;
|
||
|
import java.util.Map;
|
||
|
import java.util.Objects;
|
||
|
|
||
|
class LuaState implements ExecutionEnvironment
|
||
|
{
|
||
|
private static final int INITIAL_MEMORY = 1 * 1024 * 1024;
|
||
|
private static final int STRING_BUFFER_SIZE = 1024;
|
||
|
private static final IDynamicLuaObject[] EMPTY_OBJECTS = new IDynamicLuaObject[0];
|
||
|
|
||
|
private final ILuaContext context;
|
||
|
private final Lua lua;
|
||
|
private final Memory memory;
|
||
|
|
||
|
private final TimeoutState timeout;
|
||
|
private int insnCount = 0;
|
||
|
|
||
|
private IDynamicLuaObject[] objects = EMPTY_OBJECTS;
|
||
|
private final IntPriorityQueue freeSlots = new IntArrayFIFOQueue( 0 );
|
||
|
private int nextSlot = 0;
|
||
|
|
||
|
private final int mainState;
|
||
|
private final int childState;
|
||
|
|
||
|
private final int intBox;
|
||
|
private final int stringBuffer;
|
||
|
|
||
|
private String eventFilter;
|
||
|
|
||
|
public LuaState( int maxMemory, Computer computer, TimeoutState timeout )
|
||
|
{
|
||
|
this.context = new LuaContext( computer );
|
||
|
this.timeout = timeout;
|
||
|
this.memory = new Memory( INITIAL_MEMORY, maxMemory );
|
||
|
this.lua = LuaFactory.create( this, memory );
|
||
|
|
||
|
this.intBox = lua.malloc( 4 );
|
||
|
this.stringBuffer = lua.malloc( STRING_BUFFER_SIZE );
|
||
|
this.mainState = lua.waluaigi_new_state();
|
||
|
this.childState = lua.lua_newthread( mainState );
|
||
|
}
|
||
|
|
||
|
public boolean isOpen()
|
||
|
{
|
||
|
return mainState != 0 && childState != 0 && intBox != 0 && stringBuffer != 0;
|
||
|
}
|
||
|
|
||
|
public int allocate( IDynamicLuaObject object )
|
||
|
{
|
||
|
if( !freeSlots.isEmpty() )
|
||
|
{
|
||
|
int slot = freeSlots.dequeueInt();
|
||
|
objects[slot] = object;
|
||
|
return slot;
|
||
|
}
|
||
|
|
||
|
if( nextSlot >= objects.length )
|
||
|
{
|
||
|
objects = objects == EMPTY_OBJECTS ? new IDynamicLuaObject[16] : Arrays.copyOf( objects, objects.length * 2 );
|
||
|
}
|
||
|
|
||
|
int slot = nextSlot++;
|
||
|
objects[slot] = object;
|
||
|
return slot;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void free( int object )
|
||
|
{
|
||
|
if( object < 0 || object >= objects.length || objects[object] == null ) return;
|
||
|
objects[object] = null;
|
||
|
freeSlots.enqueue( object );
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int invoke( int luaState, int object, int method )
|
||
|
{
|
||
|
IDynamicLuaObject luaObject;
|
||
|
if( object < 0 || object >= objects.length || (luaObject = objects[object]) == null )
|
||
|
{
|
||
|
pushString( luaState, "Invalid object" );
|
||
|
lua.lua_error( luaState );
|
||
|
throw new UnreachableException();
|
||
|
}
|
||
|
|
||
|
int nargs = lua.lua_gettop( luaState );
|
||
|
Object[] args = new Object[nargs];
|
||
|
for( int i = 0; i < nargs; i++ ) args[i] = fromLua( luaState, i + 1 );
|
||
|
|
||
|
MethodResult result;
|
||
|
try
|
||
|
{
|
||
|
result = luaObject.callMethod( context, method, new ObjectArguments( args ) );
|
||
|
}
|
||
|
catch( LuaException e )
|
||
|
{
|
||
|
lua.luaL_where( luaState, e.getLevel() );
|
||
|
pushString( luaState, e.getMessage() );
|
||
|
lua.lua_concat( luaState, 2 );
|
||
|
lua.lua_error( luaState );
|
||
|
throw new UnreachableException();
|
||
|
}
|
||
|
|
||
|
if( result.getCallback() != null ) throw new IllegalStateException( "NYI: Yields" );
|
||
|
|
||
|
Object[] values = result.getResult();
|
||
|
if( values == null ) return 0;
|
||
|
|
||
|
for( Object value : values ) pushObject( luaState, value );
|
||
|
return values.length;
|
||
|
}
|
||
|
|
||
|
private Object fromLua( int luaState, int slot )
|
||
|
{
|
||
|
int type = lua.lua_type( luaState, slot );
|
||
|
switch( type )
|
||
|
{
|
||
|
case LuaConstants.LUA_TNIL:
|
||
|
return null;
|
||
|
case LuaConstants.LUA_TBOOLEAN:
|
||
|
return lua.lua_toboolean( luaState, slot ) != 0;
|
||
|
case LuaConstants.LUA_TLIGHTUSERDATA:
|
||
|
return null;
|
||
|
case LuaConstants.LUA_TNUMBER:
|
||
|
return lua.lua_tonumberx( luaState, slot, 0 );
|
||
|
case LuaConstants.LUA_TSTRING:
|
||
|
return getString( luaState, slot );
|
||
|
case LuaConstants.LUA_TTABLE:
|
||
|
ComputerCraft.log.warn( "NYI: conversion from table" );
|
||
|
// TODO: Conversion fom tables
|
||
|
return null;
|
||
|
case LuaConstants.LUA_TFUNCTION:
|
||
|
case LuaConstants.LUA_TUSERDATA:
|
||
|
case LuaConstants.LUA_TTHREAD:
|
||
|
default:
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void addAPI( ILuaAPI api )
|
||
|
{
|
||
|
int mainState = childState;
|
||
|
pushLuaObject( mainState, api, true );
|
||
|
for( String name : api.getNames() )
|
||
|
{
|
||
|
lua.lua_pushvalue( mainState, -1 );
|
||
|
setShortString( name ); // TODO: Support long strings.
|
||
|
lua.lua_setglobal( mainState, stringBuffer );
|
||
|
}
|
||
|
|
||
|
lua.lua_settop( mainState, -2 );
|
||
|
}
|
||
|
|
||
|
public MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments )
|
||
|
{
|
||
|
ComputerCraft.log.warn( "Resuming computer {} vs {}", eventName, eventFilter );
|
||
|
|
||
|
if( eventFilter != null && eventName != null && !eventName.equals( eventFilter ) && !eventName.equals( "terminate" ) )
|
||
|
{
|
||
|
return MachineResult.OK;
|
||
|
}
|
||
|
|
||
|
// If the soft abort has been cleared then we can reset our flag.
|
||
|
timeout.refresh();
|
||
|
|
||
|
int args = 0;
|
||
|
if( eventName != null )
|
||
|
{
|
||
|
args++;
|
||
|
pushString( childState, eventName );
|
||
|
}
|
||
|
if( arguments != null )
|
||
|
{
|
||
|
args += arguments.length;
|
||
|
for( Object object : arguments ) pushObject( childState, object );
|
||
|
}
|
||
|
|
||
|
int result = lua.lua_resume( childState, 0, args, intBox );
|
||
|
switch( result )
|
||
|
{
|
||
|
case LuaConstants.LUA_OK:
|
||
|
{
|
||
|
ComputerCraft.log.warn( "Coroutine finished for no reason!" );
|
||
|
return MachineResult.GENERIC_ERROR;
|
||
|
}
|
||
|
case LuaConstants.LUA_YIELD:
|
||
|
{
|
||
|
int nargs = memory.getInt( intBox );
|
||
|
eventFilter = nargs > 0 && lua.lua_type( childState, 1 ) == LuaConstants.LUA_TSTRING
|
||
|
? getString( childState, 1 )
|
||
|
: null;
|
||
|
lua.lua_settop( childState, -nargs - 1 );
|
||
|
ComputerCraft.log.warn( "Computer yielded OK with {}", eventFilter );
|
||
|
return MachineResult.OK;
|
||
|
}
|
||
|
default:
|
||
|
{
|
||
|
String error = getString( childState, -1 );
|
||
|
ComputerCraft.log.warn( "Top level coroutine errored ({})", error );
|
||
|
return MachineResult.error( error );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private boolean pushDynamicObject( int luaState, IDynamicLuaObject object )
|
||
|
{
|
||
|
lua.waluaigi_pushobject( luaState, allocate( object ) );
|
||
|
|
||
|
String[] methods = Objects.requireNonNull( object.getMethodNames(), "Methods cannot be null" );
|
||
|
for( int i = 0; i < methods.length; i++ )
|
||
|
{
|
||
|
pushString( luaState, methods[i] );
|
||
|
lua.lua_pushvalue( luaState, -2 );
|
||
|
lua.waluaigi_pushfunction( luaState, i );
|
||
|
lua.lua_rawset( luaState, -4 );
|
||
|
}
|
||
|
|
||
|
lua.lua_settop( luaState, -2 );
|
||
|
|
||
|
return methods.length > 0;
|
||
|
}
|
||
|
|
||
|
private void pushLuaObject( int luaState, Object object, boolean force )
|
||
|
{
|
||
|
boolean hasMethods = false;
|
||
|
|
||
|
lua.lua_createtable( luaState, 0, 0 );
|
||
|
|
||
|
if( object instanceof IDynamicLuaObject )
|
||
|
{
|
||
|
hasMethods = pushDynamicObject( luaState, (IDynamicLuaObject) object );
|
||
|
}
|
||
|
|
||
|
hasMethods |= pushDynamicObject( luaState, new WrappedLuaObject( object, LuaMethod.GENERATOR.getMethods( object.getClass() ) ) );
|
||
|
if( object instanceof ObjectSource )
|
||
|
{
|
||
|
for( Object extra : ((ObjectSource) object).getExtra() )
|
||
|
{
|
||
|
hasMethods |= pushDynamicObject( luaState, new WrappedLuaObject( extra, LuaMethod.GENERATOR.getMethods( extra.getClass() ) ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( hasMethods ) return;
|
||
|
|
||
|
ComputerCraft.log.warn( "Received unknown type '{}'.", object.getClass().getName() );
|
||
|
if( !force )
|
||
|
{
|
||
|
lua.lua_settop( luaState, -2 );
|
||
|
lua.lua_pushnil( luaState );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void pushObject( int luaState, Object object )
|
||
|
{
|
||
|
if( object == null )
|
||
|
{
|
||
|
lua.lua_pushnil( luaState );
|
||
|
}
|
||
|
else if( object instanceof Long || object instanceof Integer )
|
||
|
{
|
||
|
lua.lua_pushinteger( luaState, ((Number) object).longValue() );
|
||
|
}
|
||
|
else if( object instanceof Number )
|
||
|
{
|
||
|
lua.lua_pushnumber( luaState, ((Number) object).doubleValue() );
|
||
|
}
|
||
|
else if( object instanceof Boolean )
|
||
|
{
|
||
|
lua.lua_pushboolean( luaState, (Boolean) object ? 1 : 0 );
|
||
|
}
|
||
|
else if( object instanceof String )
|
||
|
{
|
||
|
pushString( luaState, (String) object );
|
||
|
}
|
||
|
else if( object instanceof byte[] )
|
||
|
{
|
||
|
byte[] b = (byte[]) object;
|
||
|
pushString( luaState, b, 0, b.length );
|
||
|
}
|
||
|
else if( object instanceof ByteBuffer )
|
||
|
{
|
||
|
pushString( luaState, (ByteBuffer) object );
|
||
|
}
|
||
|
else if( object instanceof IDynamicLuaObject )
|
||
|
{
|
||
|
pushLuaObject( luaState, object, true );
|
||
|
}
|
||
|
else if( object instanceof Map )
|
||
|
{
|
||
|
Map<?, ?> map = (Map<?, ?>) object;
|
||
|
lua.lua_createtable( luaState, 0, map.size() );
|
||
|
|
||
|
for( Map.Entry<?, ?> pair : map.entrySet() )
|
||
|
{
|
||
|
pushObject( luaState, pair.getKey() );
|
||
|
pushObject( luaState, pair.getValue() );
|
||
|
lua.lua_rawset( luaState, -3 );
|
||
|
}
|
||
|
}
|
||
|
else if( object instanceof Collection )
|
||
|
{
|
||
|
Collection<?> collection = (Collection<?>) object;
|
||
|
lua.lua_createtable( luaState, collection.size(), 0 );
|
||
|
|
||
|
int i = 0;
|
||
|
for( Object obj : collection )
|
||
|
{
|
||
|
pushObject( luaState, obj );
|
||
|
lua.lua_rawseti( luaState, -2, ++i );
|
||
|
}
|
||
|
}
|
||
|
else if( object instanceof Object[] )
|
||
|
{
|
||
|
Object[] collection = (Object[]) object;
|
||
|
lua.lua_createtable( luaState, collection.length, 0 );
|
||
|
|
||
|
for( int i = 0; i < collection.length; i++ )
|
||
|
{
|
||
|
pushObject( luaState, collection[i] );
|
||
|
lua.lua_rawseti( luaState, -2, i + 1 );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pushLuaObject( luaState, object, false );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public TimeoutMode getTimeout()
|
||
|
{
|
||
|
if( timeout.isHardAborted() ) return TimeoutMode.HALT;
|
||
|
|
||
|
if( (insnCount = (insnCount + 1) & 127) == 0 )
|
||
|
{
|
||
|
timeout.refresh();
|
||
|
if( timeout.isSoftAborted() ) return TimeoutMode.ERROR;
|
||
|
if( timeout.isPaused() ) return TimeoutMode.PAUSE;
|
||
|
}
|
||
|
|
||
|
return TimeoutMode.OK;
|
||
|
}
|
||
|
|
||
|
public MachineResult loadString( ByteBuffer contents, String name )
|
||
|
{
|
||
|
int contentsLen = contents.remaining();
|
||
|
int contentsLoc = lua.malloc( contentsLen );
|
||
|
memory.put( contentsLoc, contents );
|
||
|
|
||
|
setShortString( name );
|
||
|
|
||
|
int result = lua.luaL_loadbufferx( childState, contentsLoc, contentsLen, stringBuffer, 0 );
|
||
|
lua.free( contentsLoc );
|
||
|
|
||
|
return result == 0 ? MachineResult.OK : MachineResult.error( "Cannot parse bios.lua: " + getString( childState, 1 ) );
|
||
|
}
|
||
|
|
||
|
private void setShortString( String string )
|
||
|
{
|
||
|
int length = string.length();
|
||
|
if( length >= STRING_BUFFER_SIZE ) throw new IllegalArgumentException( "String must be <1024 characters" );
|
||
|
memory.putString( stringBuffer, string );
|
||
|
memory.put( stringBuffer + string.length(), (byte) 0 );
|
||
|
}
|
||
|
|
||
|
public void pushString( int luaState, String string )
|
||
|
{
|
||
|
int length = string.length();
|
||
|
if( length <= STRING_BUFFER_SIZE )
|
||
|
{
|
||
|
memory.putString( stringBuffer, string );
|
||
|
lua.lua_pushlstring( luaState, stringBuffer, length );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int position = lua.malloc( length + 1 );
|
||
|
if( position == 0 ) throw new OutOfMemoryException( "Cannot allocate string" );
|
||
|
|
||
|
memory.putString( position, string );
|
||
|
lua.lua_pushlstring( luaState, position, length );
|
||
|
lua.free( position );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void pushString( int luaState, ByteBuffer buffer )
|
||
|
{
|
||
|
int length = buffer.remaining();
|
||
|
if( length <= STRING_BUFFER_SIZE )
|
||
|
{
|
||
|
memory.put( stringBuffer, buffer );
|
||
|
lua.lua_pushlstring( luaState, stringBuffer, length );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int position = lua.malloc( length );
|
||
|
if( position == 0 ) throw new OutOfMemoryException( "Cannot allocate string" );
|
||
|
|
||
|
memory.put( position, buffer );
|
||
|
lua.lua_pushlstring( luaState, position, length );
|
||
|
lua.free( position );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void pushString( int luaState, byte[] bytes, int offset, int length )
|
||
|
{
|
||
|
if( length <= STRING_BUFFER_SIZE )
|
||
|
{
|
||
|
memory.put( stringBuffer, bytes, offset, length );
|
||
|
lua.lua_pushlstring( luaState, stringBuffer, length );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int position = lua.malloc( length );
|
||
|
if( position == 0 ) throw new OutOfMemoryException( "Cannot allocate string" );
|
||
|
|
||
|
memory.put( position, bytes, offset, length );
|
||
|
lua.lua_pushlstring( luaState, position, length );
|
||
|
lua.free( position );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public String getString( int luaState, int slot )
|
||
|
{
|
||
|
int stringPos = lua.luaL_checklstring( luaState, slot, intBox );
|
||
|
if( stringPos == 0 ) throw new NullPointerException( "luaL_checklstring returned 0" );
|
||
|
return memory.getString( stringPos, memory.getInt( intBox ) );
|
||
|
}
|
||
|
|
||
|
public ByteBuffer getStringAsBuffer( int luaState, int slot )
|
||
|
{
|
||
|
int stringPos = lua.luaL_checklstring( luaState, slot, intBox );
|
||
|
if( stringPos == 0 ) throw new NullPointerException( "luaL_checklstring returned 0" );
|
||
|
|
||
|
int length = memory.getInt( intBox );
|
||
|
ByteBuffer buffer = ByteBuffer.allocate( length );
|
||
|
memory.get( stringPos, buffer );
|
||
|
buffer.flip();
|
||
|
return buffer;
|
||
|
}
|
||
|
}
|