1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00
CC-Tweaked/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java

693 lines
26 KiB
Java
Raw Normal View History

/*
* This file is part of ComputerCraft - http://www.computercraft.info
2019-01-01 01:10:18 +00:00
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.ThreadUtils;
2017-05-01 17:28:17 +00:00
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.compiler.CompileException;
import org.squiddev.cobalt.compiler.LoadState;
import org.squiddev.cobalt.debug.DebugFrame;
import org.squiddev.cobalt.debug.DebugHandler;
import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.LibFunction;
2017-05-01 17:28:17 +00:00
import org.squiddev.cobalt.function.LuaFunction;
import org.squiddev.cobalt.function.VarArgFunction;
import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
2017-05-06 23:07:42 +00:00
import javax.annotation.Nonnull;
2017-05-01 17:28:17 +00:00
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
2017-05-01 17:28:17 +00:00
import static org.squiddev.cobalt.Constants.NONE;
import static org.squiddev.cobalt.ValueFactory.valueOf;
import static org.squiddev.cobalt.ValueFactory.varargsOf;
public class CobaltLuaMachine implements ILuaMachine
{
private static final ThreadPoolExecutor coroutines = new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
5L, TimeUnit.MINUTES,
new SynchronousQueue<>(),
ThreadUtils.factory( "Coroutine" )
);
2017-05-01 17:28:17 +00:00
private final Computer m_computer;
private LuaState m_state;
private LuaTable m_globals;
2017-05-01 17:28:17 +00:00
private LuaThread m_mainRoutine;
2017-05-01 14:48:44 +00:00
private String m_eventFilter;
private String m_softAbortMessage;
private String m_hardAbortMessage;
2017-05-01 17:28:17 +00:00
public CobaltLuaMachine( Computer computer )
2017-05-01 14:48:44 +00:00
{
m_computer = computer;
2017-05-01 14:48:44 +00:00
// Create an environment to run in
LuaState state = this.m_state = LuaState.builder()
.resourceManipulator( new VoidResourceManipulator() )
.debug( new DebugHandler()
2017-05-01 17:28:17 +00:00
{
private int count = 0;
private boolean hasSoftAbort;
@Override
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable
2017-05-01 17:28:17 +00:00
{
int count = ++this.count;
if( count > 100000 )
{
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
if( m_hardAbortMessage != null ) throw HardAbortError.INSTANCE;
this.count = 0;
}
else
{
handleSoftAbort();
}
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
super.onInstruction( ds, di, pc );
2017-05-01 17:28:17 +00:00
}
@Override
public void poll() throws LuaError
{
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
if( m_hardAbortMessage != null ) throw HardAbortError.INSTANCE;
handleSoftAbort();
}
2017-05-01 17:28:17 +00:00
private void handleSoftAbort() throws LuaError
{
// If the soft abort has been cleared then we can reset our flags and continue.
String message = m_softAbortMessage;
if( message == null )
{
hasSoftAbort = false;
return;
}
if( hasSoftAbort && m_hardAbortMessage == null )
{
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
return;
}
hasSoftAbort = true;
throw new LuaError( message );
}
} )
.coroutineExecutor( command -> {
Tracking.addValue( m_computer, TrackingField.COROUTINES_CREATED, 1 );
coroutines.execute( () -> {
try
{
command.run();
}
finally
{
Tracking.addValue( m_computer, TrackingField.COROUTINES_DISPOSED, 1 );
}
} );
} )
.build();
2017-05-01 17:28:17 +00:00
m_globals = new LuaTable();
state.setupThread( m_globals );
// Add basic libraries
m_globals.load( state, new BaseLib() );
m_globals.load( state, new TableLib() );
m_globals.load( state, new StringLib() );
m_globals.load( state, new MathLib() );
m_globals.load( state, new CoroutineLib() );
m_globals.load( state, new Bit32Lib() );
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
2017-05-01 17:28:17 +00:00
// Register custom load/loadstring provider which automatically adds prefixes.
m_globals.rawset( "load", new PrefixWrapperFunction( m_globals.rawget( "load" ), 0 )) ;
m_globals.rawset( "loadstring", new PrefixWrapperFunction( m_globals.rawget( "loadstring" ), 1 ) );
2017-05-01 14:48:44 +00:00
// Remove globals we don't want to expose
m_globals.rawset( "collectgarbage", Constants.NIL );
2017-05-01 17:28:17 +00:00
m_globals.rawset( "dofile", Constants.NIL );
m_globals.rawset( "loadfile", Constants.NIL );
m_globals.rawset( "print", Constants.NIL );
// Add version globals
2017-05-01 17:28:17 +00:00
m_globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) );
m_globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getComputerEnvironment().getHostString() ) );
m_globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.default_computer_settings ) );
if( ComputerCraft.disable_lua51_features )
{
2017-05-01 17:28:17 +00:00
m_globals.rawset( "_CC_DISABLE_LUA51_FEATURES", Constants.TRUE );
}
2017-05-01 14:48:44 +00:00
// Our main function will go here
m_mainRoutine = null;
m_eventFilter = null;
2017-05-01 14:48:44 +00:00
m_softAbortMessage = null;
m_hardAbortMessage = null;
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
@Override
public void addAPI( ILuaAPI api )
{
// Add the methods of an API to the global table
LuaTable table = wrapLuaObject( api );
String[] names = api.getNames();
for( String name : names )
2017-05-01 14:48:44 +00:00
{
2017-05-01 17:28:17 +00:00
m_globals.rawset( name, table );
2017-05-01 14:48:44 +00:00
}
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
@Override
public void loadBios( InputStream bios )
{
// Begin executing a file (ie, the bios)
if( m_mainRoutine != null ) return;
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
try
{
2017-05-01 17:28:17 +00:00
LuaFunction value = LoadState.load( m_state, bios, "@bios.lua", m_globals );
m_mainRoutine = new LuaThread( m_state, value, m_globals );
}
catch( CompileException e )
{
unload();
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
catch( IOException e )
2017-05-01 14:48:44 +00:00
{
ComputerCraft.log.warn( "Could not load bios.lua ", e );
unload();
2017-05-01 14:48:44 +00:00
}
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
@Override
public void handleEvent( String eventName, Object[] arguments )
{
if( m_mainRoutine == null ) return;
2017-05-01 14:48:44 +00:00
if( m_eventFilter != null && eventName != null && !eventName.equals( m_eventFilter ) && !eventName.equals( "terminate" ) )
{
return;
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
try
2017-05-01 17:28:17 +00:00
{
Varargs resumeArgs = Constants.NONE;
2017-05-01 14:48:44 +00:00
if( eventName != null )
{
2017-05-01 17:28:17 +00:00
resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
Varargs results = LuaThread.run( m_mainRoutine, resumeArgs );
if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
LuaValue filter = results.first();
m_eventFilter = filter.isString() ? filter.toString() : null;
2017-05-01 17:28:17 +00:00
if( m_mainRoutine.getStatus().equals( "dead" ) ) unload();
2017-05-01 14:48:44 +00:00
}
catch( LuaError | HardAbortError | InterruptedException e )
2017-05-01 14:48:44 +00:00
{
unload();
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
ComputerCraft.log.warn( "Top level coroutine errored", e );
2017-05-01 14:48:44 +00:00
}
finally
{
m_softAbortMessage = null;
m_hardAbortMessage = null;
}
}
2017-05-01 17:28:17 +00:00
@Override
2017-05-01 14:48:44 +00:00
public void softAbort( String abortMessage )
{
m_softAbortMessage = abortMessage;
}
2017-05-01 17:28:17 +00:00
@Override
2017-05-01 14:48:44 +00:00
public void hardAbort( String abortMessage )
{
m_softAbortMessage = abortMessage;
m_hardAbortMessage = abortMessage;
}
2017-05-01 14:48:44 +00:00
@Override
public boolean saveState( OutputStream output )
{
return false;
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
@Override
public boolean restoreState( InputStream input )
{
return false;
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
@Override
public boolean isFinished()
{
return m_mainRoutine == null;
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
@Override
public void unload()
{
if( m_state == null ) return;
m_state.abandon();
m_mainRoutine = null;
m_state = null;
m_globals = null;
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
2017-05-01 14:48:44 +00:00
private LuaTable wrapLuaObject( ILuaObject object )
{
LuaTable table = new LuaTable();
String[] methods = object.getMethodNames();
for( int i = 0; i < methods.length; i++ )
2017-05-01 14:48:44 +00:00
{
if( methods[i] != null )
2017-05-01 14:48:44 +00:00
{
final int method = i;
final ILuaObject apiObject = object;
final String methodName = methods[i];
2017-05-01 17:28:17 +00:00
table.rawset( methodName, new VarArgFunction()
{
2017-05-01 14:48:44 +00:00
@Override
2017-05-01 17:28:17 +00:00
public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError
2017-05-01 14:48:44 +00:00
{
Object[] arguments = toObjects( _args, 1 );
Object[] results;
2017-05-01 14:48:44 +00:00
try
{
2017-05-01 17:28:17 +00:00
results = apiObject.callMethod( new ILuaContext()
{
2017-05-06 23:07:42 +00:00
@Nonnull
2017-05-01 14:48:44 +00:00
@Override
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) )
2017-05-01 14:48:44 +00:00
{
throw new LuaException( "Terminated", 0 );
}
return results;
}
2017-05-01 17:28:17 +00:00
2017-05-06 23:07:42 +00:00
@Nonnull
2017-05-01 14:48:44 +00:00
@Override
public Object[] pullEventRaw( String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
2017-05-01 17:28:17 +00:00
2017-05-06 23:07:42 +00:00
@Nonnull
2017-05-01 14:48:44 +00:00
@Override
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{
try
{
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
Varargs results = LuaThread.yieldBlocking( state, toValues( yieldArgs ) );
2017-05-01 14:48:44 +00:00
return toObjects( results, 1 );
}
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
catch( LuaError e )
2017-05-01 17:28:17 +00:00
{
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
throw new IllegalStateException( e );
2017-05-01 17:28:17 +00:00
}
2017-05-01 14:48:44 +00:00
}
@Override
2017-05-06 23:07:42 +00:00
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Override
public Computer getOwner()
{
return m_computer;
}
@Override
public void execute()
{
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
}
};
if( MainThread.queueTask( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
@Override
2017-05-06 23:07:42 +00:00
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
{
// Issue task
final long taskID = issueMainThreadTask( task );
// Wait for response
while( true )
{
Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
{
if( ((Number) response[1]).intValue() == taskID )
{
Object[] returnValues = new Object[response.length - 3];
if( (Boolean) response[2] )
{
// Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
return returnValues;
}
else
{
// Extract the error message from the event and raise it
if( response.length >= 4 && response[3] instanceof String )
{
throw new LuaException( (String) response[3] );
}
else
{
throw new LuaException();
}
}
}
}
}
}
2017-05-01 14:48:44 +00:00
}, method, arguments );
}
catch( InterruptedException e )
{
throw new OrphanedThread();
2017-05-01 17:28:17 +00:00
}
2017-05-01 14:48:44 +00:00
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + apiObject, t );
}
throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 );
}
2017-05-01 17:28:17 +00:00
return toValues( results );
2017-05-01 14:48:44 +00:00
}
} );
}
}
return table;
}
2017-05-01 17:28:17 +00:00
private LuaValue toValue( Object object, Map<Object, LuaValue> values )
2017-05-01 14:48:44 +00:00
{
if( object == null )
{
2017-05-01 17:28:17 +00:00
return Constants.NIL;
2017-05-01 14:48:44 +00:00
}
else if( object instanceof Number )
{
2017-05-01 17:28:17 +00:00
double d = ((Number) object).doubleValue();
return valueOf( d );
2017-05-01 14:48:44 +00:00
}
else if( object instanceof Boolean )
{
2017-05-01 17:28:17 +00:00
return valueOf( (Boolean) object );
2017-05-01 14:48:44 +00:00
}
else if( object instanceof String )
{
2017-05-01 14:48:44 +00:00
String s = object.toString();
2017-05-01 17:28:17 +00:00
return valueOf( s );
2017-05-01 14:48:44 +00:00
}
else if( object instanceof byte[] )
{
byte[] b = (byte[]) object;
2017-05-01 17:28:17 +00:00
return valueOf( Arrays.copyOf( b, b.length ) );
}
else if( object instanceof Map )
{
// Table:
// Start remembering stuff
2017-05-01 17:28:17 +00:00
if( values == null )
{
2017-05-01 17:28:17 +00:00
values = new IdentityHashMap<>();
}
2017-05-01 17:28:17 +00:00
else if( values.containsKey( object ) )
{
2017-05-01 17:28:17 +00:00
return values.get( object );
}
2017-05-01 17:28:17 +00:00
LuaTable table = new LuaTable();
values.put( object, table );
// Convert all keys
for( Map.Entry<?, ?> pair : ((Map<?, ?>) object).entrySet() )
{
2017-05-01 17:28:17 +00:00
LuaValue key = toValue( pair.getKey(), values );
LuaValue value = toValue( pair.getValue(), values );
if( !key.isNil() && !value.isNil() )
{
2017-05-01 17:28:17 +00:00
table.rawset( key, value );
}
}
2017-05-01 17:28:17 +00:00
return table;
2017-05-01 14:48:44 +00:00
}
else if( object instanceof ILuaObject )
{
2017-05-01 17:28:17 +00:00
return wrapLuaObject( (ILuaObject) object );
2017-05-01 14:48:44 +00:00
}
else
{
2017-05-01 17:28:17 +00:00
return Constants.NIL;
}
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
private Varargs toValues( Object[] objects )
2017-05-01 14:48:44 +00:00
{
2017-05-01 17:28:17 +00:00
if( objects == null || objects.length == 0 )
2017-05-01 14:48:44 +00:00
{
2017-05-01 17:28:17 +00:00
return Constants.NONE;
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
LuaValue[] values = new LuaValue[objects.length];
for( int i = 0; i < values.length; i++ )
2017-05-01 14:48:44 +00:00
{
Object object = objects[i];
values[i] = toValue( object, null );
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
return varargsOf( values );
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
private static Object toObject( LuaValue value, Map<LuaValue, Object> objects )
2017-05-01 14:48:44 +00:00
{
switch( value.type() )
{
2017-05-01 17:28:17 +00:00
case Constants.TNIL:
case Constants.TNONE:
2017-05-01 14:48:44 +00:00
{
return null;
}
2017-05-01 17:28:17 +00:00
case Constants.TINT:
case Constants.TNUMBER:
2017-05-01 14:48:44 +00:00
{
2017-05-01 17:28:17 +00:00
return value.toDouble();
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
case Constants.TBOOLEAN:
2017-05-01 14:48:44 +00:00
{
2017-05-01 17:28:17 +00:00
return value.toBoolean();
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
case Constants.TSTRING:
2017-05-01 14:48:44 +00:00
{
2017-05-01 17:28:17 +00:00
return value.toString();
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
case Constants.TTABLE:
2017-05-01 14:48:44 +00:00
{
// Table:
2017-05-01 17:28:17 +00:00
// Start remembering stuff
if( objects == null )
{
objects = new IdentityHashMap<>();
}
else if( objects.containsKey( value ) )
{
return objects.get( value );
}
Map<Object, Object> table = new HashMap<>();
objects.put( value, table );
LuaTable luaTable = (LuaTable) value;
// Convert all keys
LuaValue k = Constants.NIL;
while( true )
{
2017-05-01 17:28:17 +00:00
Varargs keyValue;
try
{
2017-05-01 17:28:17 +00:00
keyValue = luaTable.next( k );
}
2017-05-01 17:28:17 +00:00
catch( LuaError luaError )
{
2017-05-01 17:28:17 +00:00
break;
}
2017-05-01 17:28:17 +00:00
k = keyValue.first();
if( k.isNil() )
{
2017-05-01 17:28:17 +00:00
break;
}
2017-05-01 17:28:17 +00:00
LuaValue v = keyValue.arg( 2 );
Object keyObject = toObject( k, objects );
Object valueObject = toObject( v, objects );
if( keyObject != null && valueObject != null )
{
2017-05-01 17:28:17 +00:00
table.put( keyObject, valueObject );
}
}
2017-05-01 17:28:17 +00:00
return table;
2017-05-01 14:48:44 +00:00
}
default:
{
return null;
}
2017-05-01 17:28:17 +00:00
}
2017-05-01 14:48:44 +00:00
}
2017-05-01 17:28:17 +00:00
private static Object[] toObjects( Varargs values, int startIdx )
2017-05-01 14:48:44 +00:00
{
2017-05-01 17:28:17 +00:00
int count = values.count();
Object[] objects = new Object[count - startIdx + 1];
for( int n = startIdx; n <= count; n++ )
2017-05-01 14:48:44 +00:00
{
int i = n - startIdx;
2017-05-01 17:28:17 +00:00
LuaValue value = values.arg( n );
objects[i] = toObject( value, null );
2017-05-01 14:48:44 +00:00
}
return objects;
}
private static class PrefixWrapperFunction extends VarArgFunction
{
private static final LuaString FUNCTION_STR = valueOf( "function" );
private static final LuaString EQ_STR = valueOf( "=" );
private final LibFunction underlying;
public PrefixWrapperFunction(LuaValue wrap, int opcode) {
LibFunction underlying = (LibFunction)wrap;
this.underlying = underlying;
this.opcode = opcode;
this.name = underlying.debugName();
this.env = underlying.getfenv();
}
@Override
public Varargs invoke( LuaState state, Varargs args ) throws LuaError, UnwindThrowable
{
switch( opcode )
{
case 0: // "load", // ( func [,chunkname] ) -> chunk | nil, msg
{
LuaValue func = args.arg( 1 ).checkFunction();
LuaString chunkname = args.arg( 2 ).optLuaString( FUNCTION_STR );
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
{
chunkname = OperationHelper.concat( EQ_STR, chunkname );
}
return underlying.invoke(state, varargsOf(func, chunkname));
}
case 1: // "loadstring", // ( string [,chunkname] ) -> chunk | nil, msg
{
LuaString script = args.arg( 1 ).checkLuaString();
LuaString chunkname = args.arg( 2 ).optLuaString( script );
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
{
chunkname = OperationHelper.concat( EQ_STR, chunkname );
}
return underlying.invoke(state, varargsOf(script, chunkname));
}
}
return NONE;
}
}
Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement.
2019-02-10 21:46:52 +00:00
private static class HardAbortError extends Error
{
private static final long serialVersionUID = 7954092008586367501L;
public static final HardAbortError INSTANCE = new HardAbortError();
private HardAbortError()
{
super( "Hard Abort", null, true, false );
}
}
}