From 7cc77cb1ed96a8b6948a4dad8990695c0144872f Mon Sep 17 00:00:00 2001 From: SquidDev Date: Sat, 1 Sep 2018 18:29:18 +0100 Subject: [PATCH] Add a basic implementation of the single-thread Lua --- build.gradle | 3 +- .../core/lua/CobaltLuaContext.java | 41 +++--- .../core/lua/CobaltLuaMachine.java | 25 ++-- .../core/lua/CobaltWrapperFunction.java | 120 +++++++++++++++--- .../computercraft/core/lua/Semaphore.java | 38 ++++++ 5 files changed, 179 insertions(+), 48 deletions(-) create mode 100644 src/main/java/dan200/computercraft/core/lua/Semaphore.java diff --git a/build.gradle b/build.gradle index 7b15605a8..628eb784d 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ } repositories { + mavenLocal() maven { name = "JEI" url = "http://dvs1.progwml6.com/files/maven" @@ -66,7 +67,7 @@ runtime "mezz.jei:jei_1.12.2:4.8.5.159" - shade 'org.squiddev:Cobalt:0.3.2' + shade 'org.squiddev:Cobalt:0.3.2-nothread' testCompile 'junit:junit:4.11' diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java index 72bae3ff8..03164189b 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaContext.java @@ -13,24 +13,28 @@ 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.OrphanedThread; -import org.squiddev.cobalt.Varargs; import javax.annotation.Nonnull; +import java.lang.ref.WeakReference; class CobaltLuaContext implements ILuaContext { - private CobaltLuaMachine machine; private final Computer computer; - private final LuaState state; - public CobaltLuaContext( CobaltLuaMachine machine, Computer computer, LuaState state ) + boolean done = false; + Object[] values; + LuaError exception; + final Semaphore yield = new Semaphore(); + final Semaphore resume = new Semaphore(); + private WeakReference thread; + + public CobaltLuaContext( Computer computer, LuaState state ) { - this.machine = machine; this.computer = computer; - this.state = state; + this.thread = state.getCurrentThread().getReference(); } @Nonnull @@ -56,19 +60,19 @@ public Object[] pullEventRaw( String filter ) throws InterruptedException @Override public Object[] yield( Object[] yieldArgs ) throws InterruptedException { - try + if( done ) throw new IllegalStateException( "Cannot yield when complete" ); + + values = yieldArgs; + yield.signal(); + + // Every 30 seconds check to see if the coroutine has been GCed + // if so then abort this task. + while( !resume.await( 30000 ) ) { - Varargs results = LuaThread.yield( state, machine.toValues( yieldArgs ) ); - return CobaltLuaMachine.toObjects( results, 1 ); - } - catch( OrphanedThread e ) - { - throw new InterruptedException(); - } - catch( Throwable e ) - { - throw new RuntimeException( e ); + if( thread.get() == null ) throw new InterruptedException( "Orphaned async task" ); } + + return values; } @Override @@ -167,6 +171,5 @@ public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws Lua } } } - } } diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index cc3b806d8..e790e360c 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -70,7 +70,7 @@ public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int count = ++this.count; if( count > 100000 ) { - if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE ); + if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage ); this.count = 0; } else @@ -84,7 +84,7 @@ public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, @Override public void poll() throws LuaError { - if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE ); + if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage ); handleSoftAbort(); } @@ -119,7 +119,7 @@ private void handleSoftAbort() throws LuaError { if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() ); // Register custom load/loadstring provider which automatically adds prefixes. - LibFunction.bind( state, m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } ); + LibFunction.bind( m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } ); // Remove globals we don't want to expose m_globals.rawset( "collectgarbage", Constants.NIL ); @@ -210,18 +210,13 @@ public void handleEvent( String eventName, Object[] arguments ) resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) ); } - Varargs results = m_mainRoutine.resume( resumeArgs ); + LuaValue filter = LuaThread.run( m_mainRoutine, resumeArgs ).first(); if( m_hardAbortMessage != null ) { throw new LuaError( m_hardAbortMessage ); } - else if( !results.first().checkBoolean() ) - { - throw new LuaError( results.arg( 2 ).checkString() ); - } else { - LuaValue filter = results.arg( 2 ); if( filter.isString() ) { m_eventFilter = filter.toString(); @@ -530,13 +525,23 @@ public int read() throws IOException if( remaining <= 0 ) { LuaValue s; + state.getCurrentThread().disableYield(); try { s = OperationHelper.call( state, func ); - } catch (LuaError e) + } + catch( UnwindThrowable e ) + { + throw new IOException( "Impossible yield within load" ); + } + catch( LuaError e ) { throw new IOException( e ); } + finally + { + state.getCurrentThread().enableYield(); + } if( s.isNil() ) { diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java b/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java index 3a321d4aa..b6e3312ab 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltWrapperFunction.java @@ -10,14 +10,32 @@ import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.core.computer.Computer; -import org.squiddev.cobalt.LuaError; -import org.squiddev.cobalt.LuaState; -import org.squiddev.cobalt.OrphanedThread; -import org.squiddev.cobalt.Varargs; +import org.squiddev.cobalt.*; +import org.squiddev.cobalt.debug.DebugFrame; +import org.squiddev.cobalt.debug.DebugHandler; +import org.squiddev.cobalt.debug.DebugState; import org.squiddev.cobalt.function.VarArgFunction; -class CobaltWrapperFunction extends 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; + +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; @@ -25,7 +43,7 @@ class CobaltWrapperFunction extends VarArgFunction private final int method; private final String methodName; - public CobaltWrapperFunction( CobaltLuaMachine machine, Computer computer, ILuaObject delegate, int method, String methodName ) + CobaltWrapperFunction( CobaltLuaMachine machine, Computer computer, ILuaObject delegate, int method, String methodName ) { this.machine = machine; this.computer = computer; @@ -35,30 +53,96 @@ public CobaltWrapperFunction( CobaltLuaMachine machine, Computer computer, ILuaO } @Override - public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError + public Varargs invoke( final LuaState state, Varargs args ) throws LuaError, UnwindThrowable { - Object[] arguments = CobaltLuaMachine.toObjects( _args, 1 ); - Object[] results; + Object[] arguments = CobaltLuaMachine.toObjects( args, 1 ); + CobaltLuaContext context = new CobaltLuaContext( computer, state ); + + DebugHandler handler = state.debug; + DebugState ds = handler.getDebugState(); + DebugFrame di = handler.onCall( ds, this ); + di.state = context; + + threads.submit( () -> { + try + { + context.values = delegate.callMethod( context, method, arguments ); + } + catch( LuaException e ) + { + context.exception = new LuaError( e ); + } + catch( InterruptedException e ) + { + if( ComputerCraft.logPeripheralErrors ) + { + ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e ); + } + context.exception = new LuaError( "Java Exception Thrown: " + e.toString(), 0 ); + } + finally + { + context.done = true; + context.yield.signal(); + } + } ); + try { - results = delegate.callMethod( new CobaltLuaContext( machine, computer, state ), method, arguments ); + return handleResult( state, context ); } catch( InterruptedException e ) { - throw new OrphanedThread(); + throw new LuaError( e ); } - catch( LuaException e ) + } + + @Override + public Varargs resume( LuaState state, CobaltLuaContext context, Varargs value ) throws LuaError, UnwindThrowable + { + context.values = CobaltLuaMachine.toObjects( value, 0 ); + context.resume.signal(); + try { - throw new LuaError( e.getMessage(), e.getLevel() ); + return handleResult( state, context ); } - catch( Throwable t ) + catch( InterruptedException e ) { - if( ComputerCraft.logPeripheralErrors ) + 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 ) { - ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, t ); + context.exception.fillTraceback( state ); + state.debug.onReturn(); + throw context.exception; + } + else + { + state.debug.onReturn(); + return machine.toValues( context.values ); } - throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 ); } - return machine.toValues( results ); + else + { + LuaThread.yield( state, machine.toValues( context.values ) ); + throw new AssertionError( "Unreachable code" ); + } } } diff --git a/src/main/java/dan200/computercraft/core/lua/Semaphore.java b/src/main/java/dan200/computercraft/core/lua/Semaphore.java new file mode 100644 index 000000000..6fe403d64 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/lua/Semaphore.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * A trivial way of signalling + */ +public final class Semaphore +{ + private volatile boolean state = false; + + public synchronized void signal() + { + state = true; + notify(); + } + + public synchronized void await() throws InterruptedException + { + while( !state ) wait(); + state = false; + } + + public synchronized boolean await( long timeout ) throws InterruptedException + { + if( !state ) + { + wait( timeout ); + if( !state ) return false; + } + state = false; + return true; + } +}