1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00

Further improvements to computer execution

Oh goodness, when will it end?

 - Computer errors are shown in red.

 - Lua machine operations provide whether they succeeded, and an
   optional error message (reason bios failed to load, timeout error,
   another Lua error), which is then shown to the user.

 - Clear the Cobalt "thrown soft abort" flag when resuming, rather than
   every n instructions.

 - Computers will clear their "should start" flag once the time has
   expired, irrespective of whether it turned on or not. Before
   computers would immediately restart after shutting down if the flag
   had been set much earlier.

Errors within the Lua machine are displayed in a more friendly
This commit is contained in:
SquidDev 2019-02-28 15:40:04 +00:00
parent d02575528b
commit 218f8e53bb
6 changed files with 195 additions and 90 deletions

View File

@ -150,11 +150,14 @@ public void tick()
// We keep track of the number of ticks since the last start, only
if( m_ticksSinceStart >= 0 && m_ticksSinceStart <= START_DELAY ) m_ticksSinceStart++;
if( startRequested && !executor.isOn() && (m_ticksSinceStart < 0 || m_ticksSinceStart > START_DELAY) )
if( startRequested && (m_ticksSinceStart < 0 || m_ticksSinceStart > START_DELAY) )
{
m_ticksSinceStart = 0;
startRequested = false;
executor.queueStart();
if( !executor.isOn() )
{
m_ticksSinceStart = 0;
executor.queueStart();
}
}
executor.tick();

View File

@ -16,8 +16,10 @@
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.MachineResult;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.IoUtil;
import javax.annotation.Nonnull;
@ -183,7 +185,7 @@ void queueStart()
synchronized( queueLock )
{
// We should only schedule a start if we're not currently on and there's turn on.
if( closed || isOn || this.command != null ) return;
if( closed || isOn || command != null ) return;
command = StateCommand.TURN_ON;
enqueue();
@ -327,7 +329,7 @@ private FileSystem createFileSystem()
IMount romMount = getRomMount();
if( romMount == null )
{
displayFailure( "Cannot mount rom" );
displayFailure( "Cannot mount ROM", null );
return null;
}
@ -339,7 +341,7 @@ private FileSystem createFileSystem()
if( filesystem != null ) filesystem.close();
ComputerCraft.log.error( "Cannot mount computer filesystem", e );
displayFailure( "Cannot mount computer system" );
displayFailure( "Cannot mount computer system", null );
return null;
}
}
@ -358,7 +360,7 @@ private ILuaMachine createLuaMachine()
if( biosStream == null )
{
displayFailure( "Error loading bios.lua" );
displayFailure( "Error loading bios.lua", null );
return null;
}
@ -369,13 +371,13 @@ private ILuaMachine createLuaMachine()
for( ILuaAPI api : apis ) machine.addAPI( api );
// Start the machine running the bios resource
machine.loadBios( biosStream );
MachineResult result = machine.loadBios( biosStream );
IoUtil.closeQuietly( biosStream );
if( machine.isFinished() )
if( result.isError() )
{
machine.close();
displayFailure( "Error starting bios.lua" );
displayFailure( "Error loading bios.lua", result.getMessage() );
return null;
}
@ -421,7 +423,7 @@ private void turnOn() throws InterruptedException
}
// Now actually start the computer, now that everything is set up.
machine.handleEvent( null, null );
resumeMachine( null, null );
}
private void shutdown() throws InterruptedException
@ -556,19 +558,14 @@ void work() throws InterruptedException
case ABORT:
if( !isOn ) return;
displayFailure( TimeoutState.ABORT_MESSAGE );
displayFailure( "Error running computer", TimeoutState.ABORT_MESSAGE );
shutdown();
break;
}
}
else
else if( event != null )
{
machine.handleEvent( event.name, event.args );
if( machine.isFinished() )
{
displayFailure( "Error resuming bios.lua" );
shutdown();
}
resumeMachine( event.name, event.args );
}
}
finally
@ -577,15 +574,39 @@ void work() throws InterruptedException
}
}
private void displayFailure( String message )
private void displayFailure( String message, String extra )
{
Terminal terminal = computer.getTerminal();
boolean colour = computer.getComputerEnvironment().isColour();
terminal.reset();
// Display our primary error message
if( colour ) terminal.setTextColour( 15 - Colour.Red.ordinal() );
terminal.write( message );
terminal.setCursorPos( 0, 1 );
if( extra != null )
{
// Display any additional information. This generally comes from the Lua Machine, such as compilation or
// runtime errors.
terminal.setCursorPos( 0, terminal.getCursorY() + 1 );
terminal.write( extra );
}
// And display our generic "CC may be installed incorrectly" message.
terminal.setCursorPos( 0, terminal.getCursorY() + 1 );
if( colour ) terminal.setTextColour( 15 - Colour.White.ordinal() );
terminal.write( "ComputerCraft may be installed incorrectly" );
}
private void resumeMachine( String event, Object[] args ) throws InterruptedException
{
MachineResult result = machine.handleEvent( event, args );
if( !result.isError() ) return;
displayFailure( "Error running computer", result.getMessage() );
shutdown();
}
private enum StateCommand
{
TURN_ON,

View File

@ -28,7 +28,6 @@
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
@ -53,6 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine
private final Computer m_computer;
private final TimeoutState timeout;
private final TimeoutDebugHandler debug;
private LuaState m_state;
private LuaTable m_globals;
@ -64,48 +64,12 @@ public CobaltLuaMachine( Computer computer, TimeoutState timeout )
{
m_computer = computer;
this.timeout = timeout;
debug = new TimeoutDebugHandler();
// Create an environment to run in
LuaState state = this.m_state = LuaState.builder()
.resourceManipulator( new VoidResourceManipulator() )
.debug( new DebugHandler()
{
private int count = 0;
private boolean hasSoftAbort;
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable
{
// We check our current abort state every so 128 instructions.
if( (count = (count + 1) & 127) == 0 ) handleAbort();
super.onInstruction( ds, di, pc );
}
@Override
public void poll() throws LuaError
{
handleAbort();
}
private void handleAbort() throws LuaError
{
// If we've been hard aborted or closed then abort.
if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE;
// If the soft abort has been cleared then we can reset our flags and continue.
if( !timeout.isSoftAborted() )
{
hasSoftAbort = false;
return;
}
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
if( hasSoftAbort ) return;
hasSoftAbort = true;
throw new LuaError( TimeoutState.ABORT_MESSAGE );
}
} )
.debug( debug )
.coroutineExecutor( command -> {
Tracking.addValue( m_computer, TrackingField.COROUTINES_CREATED, 1 );
COROUTINES.execute( () -> {
@ -166,37 +130,43 @@ public void addAPI( @Nonnull ILuaAPI api )
}
@Override
public void loadBios( @Nonnull InputStream bios )
public MachineResult loadBios( @Nonnull InputStream bios )
{
// Begin executing a file (ie, the bios)
if( m_mainRoutine != null ) return;
if( m_mainRoutine != null ) return MachineResult.OK;
try
{
LuaFunction value = LoadState.load( m_state, bios, "@bios.lua", m_globals );
m_mainRoutine = new LuaThread( m_state, value, m_globals );
return MachineResult.OK;
}
catch( CompileException e )
{
close();
return MachineResult.error( e );
}
catch( IOException e )
catch( Exception e )
{
ComputerCraft.log.warn( "Could not load bios.lua ", e );
ComputerCraft.log.warn( "Could not load bios.lua", e );
close();
return MachineResult.GENERIC_ERROR;
}
}
@Override
public void handleEvent( String eventName, Object[] arguments )
public MachineResult handleEvent( String eventName, Object[] arguments )
{
if( m_mainRoutine == null ) return;
if( m_mainRoutine == null ) return MachineResult.OK;
if( m_eventFilter != null && eventName != null && !eventName.equals( m_eventFilter ) && !eventName.equals( "terminate" ) )
{
return;
return MachineResult.OK;
}
// If the soft abort has been cleared then we can reset our flag.
if( !timeout.isSoftAborted() ) debug.thrownSoftAbort = false;
try
{
Varargs resumeArgs = Constants.NONE;
@ -206,30 +176,34 @@ public void handleEvent( String eventName, Object[] arguments )
}
Varargs results = LuaThread.run( m_mainRoutine, resumeArgs );
if( timeout.isHardAborted() ) throw new LuaError( TimeoutState.ABORT_MESSAGE );
if( timeout.isHardAborted() ) throw HardAbortError.INSTANCE;
LuaValue filter = results.first();
m_eventFilter = filter.isString() ? filter.toString() : null;
if( m_mainRoutine.getStatus().equals( "dead" ) ) close();
if( m_mainRoutine.getStatus().equals( "dead" ) )
{
close();
return MachineResult.GENERIC_ERROR;
}
else
{
return MachineResult.OK;
}
}
catch( HardAbortError | InterruptedException e )
{
close();
return MachineResult.TIMEOUT;
}
catch( LuaError e )
{
close();
ComputerCraft.log.warn( "Top level coroutine errored", e );
return MachineResult.error( e );
}
}
@Override
public boolean isFinished()
{
return m_mainRoutine == null;
}
@Override
public void close()
{
@ -637,11 +611,53 @@ public Varargs invoke( LuaState state, Varargs args ) throws LuaError, UnwindThr
}
}
/**
* A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly.
*/
private class TimeoutDebugHandler extends DebugHandler
{
private final TimeoutState timeout;
private int count = 0;
boolean thrownSoftAbort;
TimeoutDebugHandler()
{
this.timeout = CobaltLuaMachine.this.timeout;
}
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable
{
// We check our current abort state every 128 instructions.
if( (count = (count + 1) & 127) == 0 ) handleAbort();
super.onInstruction( ds, di, pc );
}
@Override
public void poll() throws LuaError
{
handleAbort();
}
private void handleAbort() throws LuaError
{
// If we've been hard aborted or closed then abort.
if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE;
// If we already thrown our soft abort error then don't do it again.
if( !timeout.isSoftAborted() || thrownSoftAbort ) return;
thrownSoftAbort = true;
throw new LuaError( TimeoutState.ABORT_MESSAGE );
}
}
private static class HardAbortError extends Error
{
private static final long serialVersionUID = 7954092008586367501L;
public static final HardAbortError INSTANCE = new HardAbortError();
static final HardAbortError INSTANCE = new HardAbortError();
private HardAbortError()
{

View File

@ -38,27 +38,27 @@ public interface ILuaMachine
/**
* Create a function from the provided program, and set it up to run when {@link #handleEvent(String, Object[])} is
* called
* called.
*
* This should destroy the machine if it failed to load the bios.
*
* @param bios The stream containing the boot program.
* @return The result of loading this machine. Will either be OK, or the error message when loading the bios.
*/
void loadBios( @Nonnull InputStream bios );
MachineResult loadBios( @Nonnull InputStream bios );
/**
* Resume the machine, either starting or resuming the coroutine.
*
* This should destroy the machine if it failed to execute successfully.
*
* @param eventName The name of the event. This is {@code null} when first starting the machine. Note, this may
* do nothing if it does not match the event filter.
* @param arguments The arguments for this event.
* @return The result of loading this machine. Will either be OK, or the error message that occurrred when
* executing.
*/
void handleEvent( @Nullable String eventName, @Nullable Object[] arguments );
/**
* If this machine has finished executing, either due to an error or it just shutting down.
*
* @return If this machine is finished.
*/
boolean isFinished();
MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments );
/**
* Close the Lua machine, aborting any running functions and deleting the internal state.

View File

@ -0,0 +1,69 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.core.computer.TimeoutState;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
/**
* The result of executing an action on a machine.
*
* Errors should halt the machine and display the error to the user.
*
* @see ILuaMachine#loadBios(InputStream)
* @see ILuaMachine#handleEvent(String, Object[])
*/
public final class MachineResult
{
/**
* Represents a successful execution
*/
public static final MachineResult OK = new MachineResult( false, null );
/**
* An execution which timed out.
*/
public static final MachineResult TIMEOUT = new MachineResult( true, TimeoutState.ABORT_MESSAGE );
/**
* An error with no user-friendly error message
*/
public static final MachineResult GENERIC_ERROR = new MachineResult( true, null );
private final boolean error;
private final String message;
private MachineResult( boolean error, String message )
{
this.message = message;
this.error = error;
}
public static MachineResult error( @Nonnull String error )
{
return new MachineResult( true, error );
}
public static MachineResult error( @Nonnull Exception error )
{
return new MachineResult( true, error.getMessage() );
}
public boolean isError()
{
return error;
}
@Nullable
public String getMessage()
{
return message;
}
}

View File

@ -102,10 +102,6 @@ public static void run( String program, int shutdownAfter )
{
Thread.currentThread().interrupt();
}
finally
{
ComputerThread.stop();
}
}
private static class AssertApi implements ILuaAPI