diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltCallContext.java b/src/main/java/dan200/computercraft/core/lua/CobaltCallContext.java new file mode 100644 index 000000000..7cd9fdfd5 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/lua/CobaltCallContext.java @@ -0,0 +1,87 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2018. 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.ICallContext; +import dan200.computercraft.api.lua.ILuaTask; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.ITask; +import dan200.computercraft.core.computer.MainThread; + +import javax.annotation.Nonnull; + +class CobaltCallContext implements ICallContext +{ + private final Computer computer; + + CobaltCallContext( Computer computer ) + { + this.computer = computer; + } + + @Override + 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 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 ); + computer.queueEvent( "task_complete", eventArguments ); + } + else + { + computer.queueEvent( "task_complete", new Object[]{ taskID, true } ); + } + } + catch( LuaException e ) + { + computer.queueEvent( "task_complete", new Object[]{ + taskID, false, e.getMessage() + } ); + } + catch( Throwable t ) + { + if( ComputerCraft.logPeripheralErrors ) + { + ComputerCraft.log.error( "Error running task", t ); + } + 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" ); + } + } +} diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java index 03164189b..7a4d723e2 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java @@ -6,39 +6,57 @@ package dan200.computercraft.core.lua; -import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.ILuaContextTask; import dan200.computercraft.api.lua.ILuaTask; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.core.computer.Computer; -import dan200.computercraft.core.computer.ITask; -import dan200.computercraft.core.computer.MainThread; import org.squiddev.cobalt.LuaError; import org.squiddev.cobalt.LuaState; import org.squiddev.cobalt.LuaThread; +import org.squiddev.cobalt.UnwindThrowable; import javax.annotation.Nonnull; import java.lang.ref.WeakReference; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; -class CobaltLuaContext implements ILuaContext +/** + * An ugly wrapper for {@link ILuaContext} style calls, which executes them on a separate thread. + */ +class CobaltLuaContext extends CobaltCallContext implements ILuaContext { - private final Computer computer; + private static final ThreadGroup group = new ThreadGroup( "ComputerCraft-Lua" ); + private static final AtomicInteger threadCounter = new AtomicInteger(); + private static final ExecutorService threads = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), + task -> { + Thread thread = new Thread( group, task, group.getName() + "-" + threadCounter.incrementAndGet() ); + if( !thread.isDaemon() ) thread.setDaemon( true ); + if( thread.getPriority() != Thread.NORM_PRIORITY ) thread.setPriority( Thread.NORM_PRIORITY ); + return thread; + } + ); - boolean done = false; - Object[] values; - LuaError exception; - final Semaphore yield = new Semaphore(); - final Semaphore resume = new Semaphore(); + private boolean done = false; + private Object[] values; + private LuaError exception; + private final Semaphore yield = new Semaphore(); + private final Semaphore resume = new Semaphore(); private WeakReference thread; - public CobaltLuaContext( Computer computer, LuaState state ) + CobaltLuaContext( Computer computer, LuaState state ) { - this.computer = computer; + super( computer ); this.thread = state.getCurrentThread().getReference(); } @Nonnull @Override + @Deprecated public Object[] pullEvent( String filter ) throws LuaException, InterruptedException { Object[] results = pullEventRaw( filter ); @@ -51,6 +69,7 @@ class CobaltLuaContext implements ILuaContext @Nonnull @Override + @Deprecated public Object[] pullEventRaw( String filter ) throws InterruptedException { return yield( new Object[]{ filter } ); @@ -58,6 +77,7 @@ class CobaltLuaContext implements ILuaContext @Nonnull @Override + @Deprecated public Object[] yield( Object[] yieldArgs ) throws InterruptedException { if( done ) throw new IllegalStateException( "Cannot yield when complete" ); @@ -76,66 +96,7 @@ class CobaltLuaContext implements ILuaContext } @Override - 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 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 ); - computer.queueEvent( "task_complete", eventArguments ); - } - else - { - computer.queueEvent( "task_complete", new Object[]{ taskID, true } ); - } - } - catch( LuaException e ) - { - computer.queueEvent( "task_complete", new Object[]{ - taskID, false, e.getMessage() - } ); - } - catch( Throwable t ) - { - if( ComputerCraft.logPeripheralErrors ) - { - ComputerCraft.log.error( "Error running task", t ); - } - 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 + @Deprecated public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException { // Issue task @@ -172,4 +133,58 @@ class CobaltLuaContext implements ILuaContext } } } + + void execute( ILuaContextTask task ) + { + threads.submit( () -> { + try + { + values = task.execute( this ); + } + catch( LuaException e ) + { + exception = new LuaError( e.getMessage(), e.getLevel() ); + } + catch( InterruptedException e ) + { + exception = new LuaError( "Java Exception Thrown: " + e.toString(), 0 ); + } + finally + { + done = true; + yield.signal(); + } + } ); + } + + Object[] resume( LuaState state, CobaltLuaMachine machine, Object[] args ) throws LuaError, UnwindThrowable + { + values = args; + resume.signal(); + + if( !done ) + { + try + { + yield.await(); + } + catch( InterruptedException e ) + { + state.debug.onReturn(); + throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 ); + } + } + + if( done ) + { + state.debug.onReturn(); + if( exception != null ) throw exception; + return values; + } + else + { + LuaThread.yield( state, machine.toValues( values ) ); + throw new IllegalStateException( "Unreachable" ); + } + } } diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index e790e360c..a7827f0d9 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -291,20 +291,18 @@ public class CobaltLuaMachine implements ILuaMachine { LuaTable table = new LuaTable(); String[] methods = object.getMethodNames(); - for( int i = 0; i < methods.length; ++i ) + for( int method = 0; method < methods.length; method++ ) { - if( methods[ i ] != null ) + if( methods[method] != null ) { - final int method = i; - final ILuaObject apiObject = object; - final String methodName = methods[ i ]; - table.rawset( methodName, new CobaltWrapperFunction( this, m_computer, apiObject, method, methodName ) ); + final String methodName = methods[method]; + table.rawset( methodName, new CobaltWrapperFunction( this, m_computer, object, method, methodName ) ); } } return table; } - LuaValue toValue( Object object, Map values ) + private LuaValue toValue( Object object, Map values ) { if( object == null ) { @@ -382,7 +380,7 @@ public class CobaltLuaMachine implements ILuaMachine return varargsOf( values ); } - static Object toObject( LuaValue value, Map objects ) + private static Object toObject( LuaValue value, Map objects ) { switch( value.type() ) { diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java b/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java index b6e3312ab..8edb795ac 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java @@ -7,8 +7,10 @@ package dan200.computercraft.core.lua; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaFunction; import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.core.computer.Computer; import org.squiddev.cobalt.*; import org.squiddev.cobalt.debug.DebugFrame; @@ -16,28 +18,14 @@ import org.squiddev.cobalt.debug.DebugHandler; import org.squiddev.cobalt.debug.DebugState; import org.squiddev.cobalt.function.VarArgFunction; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.ArrayDeque; +import java.util.Deque; -class CobaltWrapperFunction extends VarArgFunction implements Resumable +class CobaltWrapperFunction extends VarArgFunction implements Resumable { - private static final ThreadGroup group = new ThreadGroup( "ComputerCraft-Lua" ); - private static final AtomicInteger threadCounter = new AtomicInteger(); - private static final ExecutorService threads = new ThreadPoolExecutor( - 4, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), - task -> { - Thread thread = new Thread( group, task, group.getName() + "-" + threadCounter.incrementAndGet() ); - if( !thread.isDaemon() ) thread.setDaemon( true ); - if( thread.getPriority() != Thread.NORM_PRIORITY ) thread.setPriority( Thread.NORM_PRIORITY ); - return thread; - } - ); - private final CobaltLuaMachine machine; private final Computer computer; + private final CobaltCallContext callContext; private final ILuaObject delegate; private final int method; @@ -47,6 +35,7 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable { - try + MethodResult future; + try + { + future = delegate.callMethod( callContext, method, CobaltLuaMachine.toObjects( args, 1 ) ); + } + catch( LuaException e ) + { + throw new LuaError( e.getMessage(), e.getLevel() ); + } + catch( Exception e ) + { + if( ComputerCraft.logPeripheralErrors ) { - context.values = delegate.callMethod( context, method, arguments ); + ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e ); } - catch( LuaException e ) + throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 ); + } + + if( future == null ) + { + ComputerCraft.log.error( "Null result from " + delegate ); + throw new LuaError( "Java Exception Thrown: Null result" ); + } + + State context = new State(); + try + { + return runFuture( state, context, future ); + } + catch( UnwindThrowable e ) + { + // Push our state onto the stack if need-be. + DebugHandler handler = state.debug; + DebugState ds = handler.getDebugState(); + DebugFrame di = handler.onCall( ds, this ); + di.state = context; + + throw e; + } + } + + @Override + public Varargs resume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable + { + try + { + Varargs result = doResume( state, context, args ); + state.debug.onReturn(); + return result; + } + catch( LuaError e ) + { + state.debug.onReturn(); + throw e; + } + catch( Exception e ) + { + state.debug.onReturn(); + throw new LuaError( e ); + } + } + + private Varargs doResume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable + { + MethodResult future = context.pending; + if( future instanceof MethodResult.OnEvent ) + { + MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future; + if( !onEvent.isRaw() && args.first().toString().equals( "terminate" ) ) { - context.exception = new LuaError( e ); + throw new LuaError( "Terminated", 0 ); } - catch( InterruptedException e ) + + return runCallback( state, context, CobaltLuaMachine.toObjects( args, 1 ) ); + } + else if( future instanceof MethodResult.OnMainThread ) + { + if( args.arg( 2 ).isNumber() && args.arg( 3 ).isBoolean() && args.arg( 2 ).toLong() == context.taskId ) { - if( ComputerCraft.logPeripheralErrors ) + if( args.arg( 3 ).toBoolean() ) { - ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e ); + // Extract the return values from the event and return them + return runCallback( state, context, CobaltLuaMachine.toObjects( args, 4 ) ); + } + else + { + // Extract the error message from the event and raise it + throw new LuaError( args.arg( 4 ) ); } - context.exception = new LuaError( "Java Exception Thrown: " + e.toString(), 0 ); - } - finally - { - context.done = true; - context.yield.signal(); - } - } ); - - try - { - return handleResult( state, context ); - } - catch( InterruptedException e ) - { - throw new LuaError( e ); - } - } - - @Override - public Varargs resume( LuaState state, CobaltLuaContext context, Varargs value ) throws LuaError, UnwindThrowable - { - context.values = CobaltLuaMachine.toObjects( value, 0 ); - context.resume.signal(); - try - { - return handleResult( state, context ); - } - catch( InterruptedException e ) - { - throw new LuaError( e ); - } - } - - @Override - public Varargs resumeError( LuaState state, CobaltLuaContext context, LuaError error ) throws LuaError - { - DebugHandler handler = state.debug; - handler.onReturn( handler.getDebugState() ); - throw error; - } - - private Varargs handleResult( LuaState state, CobaltLuaContext context ) throws InterruptedException, LuaError, UnwindThrowable - { - // We may be done if we yield when handling errors - if( !context.done ) context.yield.await(); - - if( context.done ) - { - if( context.exception != null ) - { - context.exception.fillTraceback( state ); - state.debug.onReturn(); - throw context.exception; } else { - state.debug.onReturn(); - return machine.toValues( context.values ); + LuaThread.yield( state, ValueFactory.valueOf( "task_complete" ) ); + throw new IllegalStateException( "Unreachable" ); } } + else if( future instanceof MethodResult.WithLuaContext ) + { + return runCallback( state, context, context.luaContext.resume( state, machine, CobaltLuaMachine.toObjects( args, 1 ) ) ); + } else { - LuaThread.yield( state, machine.toValues( context.values ) ); - throw new AssertionError( "Unreachable code" ); + ComputerCraft.log.error( "Unknown method result " + future ); + throw new LuaError( "Java Exception Thrown: Unknown method result" ); } } + + @Override + public Varargs resumeError( LuaState state, State context, LuaError error ) throws LuaError + { + state.debug.onReturn(); + throw error; + } + + private Varargs runFuture( LuaState state, State context, MethodResult future ) throws LuaError, UnwindThrowable + { + Deque callbacks = context.callbacks; + while( true ) + { + if( future instanceof MethodResult.AndThen ) + { + MethodResult.AndThen then = ((MethodResult.AndThen) future); + + // Thens are "unwrapped", being pushed onto a stack + if( callbacks == null ) callbacks = context.callbacks = new ArrayDeque<>(); + callbacks.addLast( then.getCallback() ); + + future = then.getPrevious(); + } + else if( future instanceof MethodResult.Immediate ) + { + Object[] values = ((MethodResult.Immediate) future).getResult(); + + // Immediate values values will attempt to call the previous "then", or return if nothing + // else needs to be done. + ILuaFunction callback = callbacks == null ? null : callbacks.pollLast(); + if( callback == null ) return machine.toValues( values ); + + future = runFunction( callback, values ); + } + else if( future instanceof MethodResult.OnEvent ) + { + MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future; + + // Mark this future as pending and yield + context.pending = future; + String filter = onEvent.getFilter(); + LuaThread.yield( state, filter == null ? Constants.NIL : ValueFactory.valueOf( filter ) ); + throw new IllegalStateException( "Unreachable" ); + } + else if( future instanceof MethodResult.OnMainThread ) + { + MethodResult.OnMainThread onMainThread = (MethodResult.OnMainThread) future; + + // Mark this future as pending and yield + context.pending = future; + try + { + context.taskId = callContext.issueMainThreadTask( () -> { + context.taskResult = onMainThread.getTask().execute(); + return null; + } ); + } + catch( LuaException e ) + { + throw new LuaError( e.getMessage(), e.getLevel() ); + } + + LuaThread.yield( state, ValueFactory.valueOf( "task_complete" ) ); + throw new IllegalStateException( "Unreachable" ); + } + else if( future instanceof MethodResult.WithLuaContext ) + { + MethodResult.WithLuaContext withContext = (MethodResult.WithLuaContext) future; + + // Mark this future as pending and execute on a separate thread. + context.pending = future; + CobaltLuaContext luaContext = context.luaContext = new CobaltLuaContext( computer, state ); + luaContext.execute( withContext.getConsumer() ); + } + else + { + ComputerCraft.log.error( "Unknown method result " + future ); + throw new LuaError( "Java Exception Thrown: Unknown method result" ); + } + } + } + + private Varargs runCallback( LuaState state, State context, Object[] args ) throws LuaError, UnwindThrowable + { + Deque callbacks = context.callbacks; + ILuaFunction callback = callbacks == null ? null : callbacks.pollLast(); + if( callback == null ) return machine.toValues( args ); + + return runFuture( state, context, runFunction( callback, args ) ); + } + + private MethodResult runFunction( ILuaFunction func, Object[] args ) throws LuaError + { + MethodResult result; + try + { + result = func.call( args ); + } + catch( LuaException e ) + { + throw new LuaError( e.getMessage(), e.getLevel() ); + } + catch( Exception e ) + { + if( ComputerCraft.logPeripheralErrors ) + { + ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e ); + } + throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 ); + } + + if( result == null ) + { + ComputerCraft.log.error( "Null result from " + func ); + throw new LuaError( "Java Exception Thrown: Null result" ); + } + + return result; + } + + static class State + { + Deque callbacks; + + MethodResult pending; + + CobaltLuaContext luaContext; + + long taskId; + MethodResult taskResult; + } }