1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-11-15 22:34:54 +00:00

Migrate the computer tasks into a separate thread

- Move state management (turnOn, shutdown, etc...) event handling and
   the command queue into a ComputerExecutor

 - This means the computer thread now just handles running "work" on
   computer executors, rather than managing a separate command queue +
   requeuing it.
This commit is contained in:
SquidDev 2019-02-26 10:31:29 +00:00
parent 06e76f9b15
commit 67af7a698b
5 changed files with 724 additions and 522 deletions

View File

@ -7,57 +7,46 @@
package dan200.computercraft.core.computer; package dan200.computercraft.core.computer;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.apis.*; import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/**
* Represents a computer which may exist in-world or elsewhere.
*
* Note, this class has several (read: far, far too many) responsibilities, so can get a little unwieldy at times.
*
* <ul>
* <li>Updates the {@link Environment}.</li>
* <li>Keeps track of whether the computer is on and blinking.</li>
* <li>Monitors whether the computer's visible state (redstone, on/off/blinking) has changed.</li>
* <li>Passes commands and events to the {@link ComputerExecutor}.</li>
* </ul>
*/
public class Computer public class Computer
{ {
private enum State private static final int START_DELAY = 50;
{
Off,
Starting,
Running,
Stopping,
}
private static IMount s_romMount = null;
// Various properties of the computer
private int m_id; private int m_id;
private String m_label = null; private String m_label = null;
// Read-only fields about the computer
private final IComputerEnvironment m_environment; private final IComputerEnvironment m_environment;
private int m_ticksSinceStart = -1;
private boolean m_startRequested = false;
private State m_state = State.Off;
private boolean m_blinking = false;
private ILuaMachine m_machine = null;
private final List<ILuaAPI> m_apis = new ArrayList<>();
final TimeoutState timeout = new TimeoutState();
private final Environment m_internalEnvironment = new Environment( this );
private final Terminal m_terminal; private final Terminal m_terminal;
private FileSystem m_fileSystem = null; private final ComputerExecutor executor;
private IWritableMount m_rootMount = null;
private final AtomicBoolean externalOutputChanged = new AtomicBoolean(); // Additional state about the computer and its environment.
private boolean m_blinking = false;
private final Environment internalEnvironment = new Environment( this );
private AtomicBoolean externalOutputChanged = new AtomicBoolean();
private boolean startRequested;
private int m_ticksSinceStart = -1;
public Computer( IComputerEnvironment environment, Terminal terminal, int id ) public Computer( IComputerEnvironment environment, Terminal terminal, int id )
{ {
@ -65,24 +54,7 @@ public class Computer
m_environment = environment; m_environment = environment;
m_terminal = terminal; m_terminal = terminal;
// Ensure the computer thread is running as required. executor = new ComputerExecutor( this );
ComputerThread.start();
// Add all default APIs to the loaded list.
m_apis.add( new TermAPI( m_internalEnvironment ) );
m_apis.add( new RedstoneAPI( m_internalEnvironment ) );
m_apis.add( new FSAPI( m_internalEnvironment ) );
m_apis.add( new PeripheralAPI( m_internalEnvironment ) );
m_apis.add( new OSAPI( m_internalEnvironment ) );
if( ComputerCraft.http_enable ) m_apis.add( new HTTPAPI( m_internalEnvironment ) );
// Load in the API registered APIs.
for( ILuaAPIFactory factory : ApiFactories.getAll() )
{
ComputerSystem system = new ComputerSystem( m_internalEnvironment );
ILuaAPI api = factory.create( system );
if( api != null ) m_apis.add( new ApiWrapper( api, system ) );
}
} }
IComputerEnvironment getComputerEnvironment() IComputerEnvironment getComputerEnvironment()
@ -92,7 +64,7 @@ public class Computer
FileSystem getFileSystem() FileSystem getFileSystem()
{ {
return m_fileSystem; return executor.getFileSystem();
} }
Terminal getTerminal() Terminal getTerminal()
@ -102,40 +74,42 @@ public class Computer
public Environment getEnvironment() public Environment getEnvironment()
{ {
return m_internalEnvironment; return internalEnvironment;
} }
public IAPIEnvironment getAPIEnvironment() public IAPIEnvironment getAPIEnvironment()
{ {
return m_internalEnvironment; return internalEnvironment;
}
public void turnOn()
{
if( m_state == State.Off ) m_startRequested = true;
}
public void shutdown()
{
stopComputer( false );
}
public void reboot()
{
stopComputer( true );
} }
public boolean isOn() public boolean isOn()
{ {
synchronized( this ) return executor.isOn();
{
return m_state == State.Running;
} }
public void turnOn()
{
startRequested = true;
}
public void shutdown()
{
executor.queueStop( false, false );
}
public void reboot()
{
executor.queueStop( true, false );
} }
public void unload() public void unload()
{ {
stopComputer( false ); executor.queueStop( false, true );
}
public void queueEvent( String event, Object[] args )
{
executor.queueEvent( event, args );
} }
public int getID() public int getID()
@ -173,38 +147,28 @@ public class Computer
public void tick() public void tick()
{ {
synchronized( this ) // 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) )
{ {
// Start after a number of ticks m_ticksSinceStart = 0;
if( m_ticksSinceStart >= 0 ) startRequested = false;
{ executor.queueStart();
m_ticksSinceStart++;
}
if( m_startRequested && (m_ticksSinceStart < 0 || m_ticksSinceStart > 50) )
{
startComputer();
m_startRequested = false;
} }
if( m_state == State.Running ) executor.tick();
{
// Update the environment's internal state. // Update the environment's internal state.
m_internalEnvironment.update(); internalEnvironment.update();
// Advance our APIs // Propagate the environment's output to the world.
for( ILuaAPI api : m_apis ) api.update(); if( internalEnvironment.updateOutput() ) externalOutputChanged.set( true );
}
}
// Prepare to propagate the environment's output to the world.
if( m_internalEnvironment.updateOutput() ) externalOutputChanged.set( true );
// Set output changed if the terminal has changed from blinking to not // Set output changed if the terminal has changed from blinking to not
boolean blinking = boolean blinking = m_terminal.getCursorBlink() &&
m_terminal.getCursorBlink() &&
m_terminal.getCursorX() >= 0 && m_terminal.getCursorX() < m_terminal.getWidth() && m_terminal.getCursorX() >= 0 && m_terminal.getCursorX() < m_terminal.getWidth() &&
m_terminal.getCursorY() >= 0 && m_terminal.getCursorY() < m_terminal.getHeight(); m_terminal.getCursorY() >= 0 && m_terminal.getCursorY() < m_terminal.getHeight();
if( blinking != m_blinking ) if( blinking != m_blinking )
{ {
m_blinking = blinking; m_blinking = blinking;
@ -212,6 +176,11 @@ public class Computer
} }
} }
void markChanged()
{
externalOutputChanged.set( true );
}
public boolean pollAndResetChanged() public boolean pollAndResetChanged()
{ {
return externalOutputChanged.getAndSet( false ); return externalOutputChanged.getAndSet( false );
@ -222,336 +191,27 @@ public class Computer
return isOn() && m_blinking; return isOn() && m_blinking;
} }
public IWritableMount getRootMount() public void addApi( ILuaAPI api )
{ {
if( m_rootMount == null ) executor.addApi( api );
{
m_rootMount = m_environment.createSaveDirMount( "computer/" + assignID(), m_environment.getComputerSpaceLimit() );
}
return m_rootMount;
}
// FileSystem
private boolean initFileSystem()
{
// Create the file system
assignID();
try
{
m_fileSystem = new FileSystem( "hdd", getRootMount() );
if( s_romMount == null ) s_romMount = m_environment.createResourceMount( "computercraft", "lua/rom" );
if( s_romMount != null )
{
m_fileSystem.mount( "rom", "rom", s_romMount );
return true;
}
return false;
}
catch( FileSystemException e )
{
ComputerCraft.log.error( "Cannot mount rom", e );
return false;
}
}
// Peripherals
public void addAPI( ILuaAPI api )
{
m_apis.add( api );
}
// Lua
private void initLua()
{
// Create the lua machine
ILuaMachine machine = new CobaltLuaMachine( this, timeout );
// Add the APIs
for( ILuaAPI api : m_apis )
{
machine.addAPI( api );
api.startup();
}
// Load the bios resource
InputStream biosStream;
try
{
biosStream = m_environment.createResourceFile( "computercraft", "lua/bios.lua" );
}
catch( Exception e )
{
biosStream = null;
}
// Start the machine running the bios resource
if( biosStream != null )
{
machine.loadBios( biosStream );
try
{
biosStream.close();
}
catch( IOException e )
{
// meh
}
if( machine.isFinished() )
{
m_terminal.reset();
m_terminal.write( "Error starting bios.lua" );
m_terminal.setCursorPos( 0, 1 );
m_terminal.write( "ComputerCraft may be installed incorrectly" );
machine.close();
m_machine = null;
}
else
{
m_machine = machine;
}
}
else
{
m_terminal.reset();
m_terminal.write( "Error loading bios.lua" );
m_terminal.setCursorPos( 0, 1 );
m_terminal.write( "ComputerCraft may be installed incorrectly" );
machine.close();
m_machine = null;
}
}
private void startComputer()
{
synchronized( this )
{
if( m_state != State.Off )
{
return;
}
m_state = State.Starting;
externalOutputChanged.set( true );
m_ticksSinceStart = 0;
}
// Turn the computer on
ComputerThread.queueTask( new ITask()
{
@Nonnull
@Override
public Computer getOwner()
{
return Computer.this;
}
@Override
public void execute()
{
synchronized( this )
{
if( m_state != State.Starting )
{
return;
}
// Init terminal
m_terminal.reset();
// Init filesystem
if( !initFileSystem() )
{
// Init failed, so shutdown
m_terminal.reset();
m_terminal.write( "Error mounting lua/rom" );
m_terminal.setCursorPos( 0, 1 );
m_terminal.write( "ComputerCraft may be installed incorrectly" );
m_state = State.Running;
stopComputer( false );
return;
}
// Init lua
initLua();
if( m_machine == null )
{
m_terminal.reset();
m_terminal.write( "Error loading bios.lua" );
m_terminal.setCursorPos( 0, 1 );
m_terminal.write( "ComputerCraft may be installed incorrectly" );
// Init failed, so shutdown
m_state = State.Running;
stopComputer( false );
return;
}
// Start a new state
m_state = State.Running;
externalOutputChanged.set( true );
synchronized( m_machine )
{
m_machine.handleEvent( null, null );
}
}
}
} );
}
/**
* Abort this whole computer due to a timeout. This will immediately destroy the Lua machine,
* and then schedule a shutdown.
*/
void abort()
{
// TODO: We need to test this much more thoroughly.
ILuaMachine machine = m_machine;
if( machine != null ) machine.close();
shutdown();
}
private void stopComputer( final boolean reboot )
{
synchronized( this )
{
if( m_state != State.Running )
{
return;
}
m_state = State.Stopping;
externalOutputChanged.set( true );
}
// Turn the computercraft off
ComputerThread.queueTask( new ITask()
{
@Nonnull
@Override
public Computer getOwner()
{
return Computer.this;
}
@Override
public void execute()
{
synchronized( this )
{
if( m_state != State.Stopping )
{
return;
}
// Shutdown our APIs
synchronized( m_apis )
{
for( ILuaAPI api : m_apis )
{
api.shutdown();
}
}
// Shutdown terminal and filesystem
if( m_fileSystem != null )
{
m_fileSystem.close();
m_fileSystem = null;
}
if( m_machine != null )
{
m_terminal.reset();
synchronized( m_machine )
{
m_machine.close();
m_machine = null;
}
}
// Reset redstone output
m_internalEnvironment.resetOutput();
m_state = State.Off;
externalOutputChanged.set( true );
if( reboot )
{
m_startRequested = true;
}
}
}
} );
}
public void queueEvent( final String event, final Object[] arguments )
{
synchronized( this )
{
if( m_state != State.Running )
{
return;
}
}
ComputerThread.queueTask( new ITask()
{
@Nonnull
@Override
public Computer getOwner()
{
return Computer.this;
}
@Override
public void execute()
{
synchronized( this )
{
if( m_state != State.Running )
{
return;
}
}
synchronized( m_machine )
{
m_machine.handleEvent( event, arguments );
if( m_machine.isFinished() )
{
m_terminal.reset();
m_terminal.write( "Error resuming bios.lua" );
m_terminal.setCursorPos( 0, 1 );
m_terminal.write( "ComputerCraft may be installed incorrectly" );
stopComputer( false );
}
}
}
} );
} }
@Deprecated @Deprecated
public IPeripheral getPeripheral( int side ) public IPeripheral getPeripheral( int side )
{ {
return m_internalEnvironment.getPeripheral( side ); return internalEnvironment.getPeripheral( side );
} }
@Deprecated @Deprecated
public void setPeripheral( int side, IPeripheral peripheral ) public void setPeripheral( int side, IPeripheral peripheral )
{ {
m_internalEnvironment.setPeripheral( side, peripheral ); internalEnvironment.setPeripheral( side, peripheral );
} }
@Deprecated @Deprecated
public void addAPI( dan200.computercraft.core.apis.ILuaAPI api ) public void addAPI( dan200.computercraft.core.apis.ILuaAPI api )
{ {
addAPI( (ILuaAPI) api ); addApi( api );
} }
@Deprecated @Deprecated
@ -561,5 +221,6 @@ public class Computer
tick(); tick();
} }
@Deprecated
public static final String[] s_sideNames = IAPIEnvironment.SIDE_NAMES; public static final String[] s_sideNames = IAPIEnvironment.SIDE_NAMES;
} }

View File

@ -0,0 +1,609 @@
/*
* 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.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.core.apis.*;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.util.IoUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/**
* The main task queue and executor for a single computer. This handles turning on and off a computer, as well as
* running events.
*
* When the computer is instructed to turn on or off, or handle an event, we queue a task and register this to be
* executed on the {@link ComputerThread}. Note, as we may be starting many events in a single tick, the external
* cannot lock on anything which may be held for a long time.
*
* The executor is effectively composed of two separate queues. Firstly, we have a "single element" queue
* {@link #command} which determines which state the computer should transition too. This is set by
* {@link #queueStart()} and {@link #queueStop(boolean, boolean)}.
*
* When a computer is on, we simply push any events onto to the {@link #eventQueue}.
*
* Both queues are run from the {@link #work()} method, which tries to execute a command if one exists, or resumes the
* machine with an event otherwise.
*
* One final responsibility for the executor is calling {@link ILuaAPI#update()} every tick, via the {@link #tick()}
* method. This should only be called when the computer is actually on ({@link #isOn}).
*/
final class ComputerExecutor
{
private static final int QUEUE_LIMIT = 256;
private static IMount romMount;
private static final Object romMountLock = new Object();
private final Computer computer;
private final List<ILuaAPI> apis = new ArrayList<>();
final TimeoutState timeout = new TimeoutState();
private FileSystem fileSystem;
private ILuaMachine machine;
/**
* Whether the computer is currently on. This is set to false when a shutdown starts, or when turning on completes
* (but just before the Lua machine is started).
*
* @see #isOnLock
*/
private volatile boolean isOn = false;
/**
* The lock to acquire when you need to modify the "on state" of a computer.
*
* We hold this lock when running any command, and attempt to hold it when updating APIs. This ensures you don't
* update APIs while also starting/stopping them.
*
* @see #isOn
* @see #tick()
* @see #turnOn()
* @see #shutdown()
*/
private final ReentrantLock isOnLock = new ReentrantLock();
/**
* A lock used for any changes to {@link #eventQueue}, {@link #command} or {@link #onComputerQueue}. This will be
* used on the main thread, so locks should be kept as brief as possible.
*/
private final Object queueLock = new Object();
/**
* Determines if this executer is present within {@link ComputerThread}.
*/
volatile boolean onComputerQueue = false;
/**
* The command that {@link #work()} should execute on the computer thread.
*
* One sets the command with {@link #queueStart()} and {@link #queueStop(boolean, boolean)}. Neither of these will
* queue a new event if there is an existing one in the queue.
*
* Note, if command is not {@code null}, then some command is scheduled to be executed. Otherwise it is not
* currently in the queue (or is currently being executed).
*/
private volatile StateCommand command;
/**
* The queue of events which should be executed when this computer is on.
*
* Note, this should be empty if this computer is off - it is cleared on shutdown and when turning on again.
*/
private final Queue<Event> eventQueue = new ArrayDeque<>();
/**
* Whether this executor has been closed, and will no longer accept any incoming commands or events.
*
* @see #queueStop(boolean, boolean)
*/
private boolean closed;
private IWritableMount rootMount;
/**
* {@code true} when inside {@link #work()}. We use this to ensure we're only doing one bit of work at one time.
*/
private final AtomicBoolean isExecuting = new AtomicBoolean( false );
ComputerExecutor( Computer computer )
{
// Ensure the computer thread is running as required.
ComputerThread.start();
this.computer = computer;
Environment environment = computer.getEnvironment();
// Add all default APIs to the loaded list.
apis.add( new TermAPI( environment ) );
apis.add( new RedstoneAPI( environment ) );
apis.add( new FSAPI( environment ) );
apis.add( new PeripheralAPI( environment ) );
apis.add( new OSAPI( environment ) );
if( ComputerCraft.http_enable ) apis.add( new HTTPAPI( environment ) );
// Load in the externally registered APIs.
for( ILuaAPIFactory factory : ApiFactories.getAll() )
{
ComputerSystem system = new ComputerSystem( environment );
ILuaAPI api = factory.create( system );
if( api != null ) apis.add( new ApiWrapper( api, system ) );
}
}
boolean isOn()
{
return isOn;
}
FileSystem getFileSystem()
{
return fileSystem;
}
Computer getComputer()
{
return computer;
}
void addApi( ILuaAPI api )
{
apis.add( api );
}
/**
* Schedule this computer to be started if not already on.
*/
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;
command = StateCommand.TURN_ON;
enqueue();
}
}
/**
* Schedule this computer to be stopped if not already on.
*
* @param reboot Reboot the computer after stopping
* @param close Close the computer after stopping.
* @see #closed
*/
void queueStop( boolean reboot, boolean close )
{
synchronized( queueLock )
{
if( closed ) return;
this.closed = close;
StateCommand newCommand = reboot ? StateCommand.REBOOT : StateCommand.SHUTDOWN;
// We should only schedule a stop if we're currently on and there's no shutdown pending.
if( !isOn || command != null )
{
// If we're closing, set the command just in case.
if( close ) command = newCommand;
return;
}
command = newCommand;
enqueue();
}
}
/**
* Abort this whole computer due to a timeout. This will immediately destroy the Lua machine,
* and then schedule a shutdown.
*/
void abort()
{
// TODO: We need to test this much more thoroughly.
ILuaMachine machine = this.machine;
if( machine != null ) machine.close();
synchronized( queueLock )
{
if( closed ) return;
command = StateCommand.ABORT;
if( isOn ) enqueue();
}
}
/**
* Queue an event if the computer is on
*
* @param event The event's name
* @param args The event's arguments
*/
void queueEvent( @Nonnull String event, @Nullable Object[] args )
{
// Events should be skipped if we're not on.
if( !isOn ) return;
synchronized( queueLock )
{
// And if we've got some command in the pipeline, then don't queue events - they'll
// probably be disposed of anyway.
// We also limit the number of events which can be queued.
if( closed || command != null || eventQueue.size() >= QUEUE_LIMIT ) return;
eventQueue.offer( new Event( event, args ) );
enqueue();
}
}
/**
* Add this executor to the {@link ComputerThread} if not already there.
*/
private void enqueue()
{
synchronized( queueLock )
{
if( onComputerQueue ) return;
onComputerQueue = true;
ComputerThread.queue( this );
}
}
/**
* Update the internals of the executor.
*/
void tick()
{
if( isOn && isOnLock.tryLock() )
{
// This horrific structure means we don't try to update APIs while the state is being changed
// (and so they may be running startup/shutdown).
// We use tryLock here, as it has minimal delay, and it doesn't matter if we miss an advance at the
// beginning or end of a computer's lifetime.
try
{
if( isOn )
{
// Advance our APIs.
for( ILuaAPI api : apis ) api.update();
}
}
finally
{
isOnLock.unlock();
}
}
}
private IMount getRomMount()
{
if( romMount != null ) return romMount;
synchronized( romMountLock )
{
if( romMount != null ) return romMount;
return romMount = computer.getComputerEnvironment().createResourceMount( "computercraft", "lua/rom" );
}
}
private FileSystem createFileSystem()
{
if( rootMount == null )
{
rootMount = computer.getComputerEnvironment().createSaveDirMount(
"computer/" + computer.assignID(),
computer.getComputerEnvironment().getComputerSpaceLimit()
);
}
FileSystem filesystem = null;
try
{
filesystem = new FileSystem( "hdd", rootMount );
IMount romMount = getRomMount();
if( romMount == null )
{
displayFailure( "Cannot mount rom" );
return null;
}
filesystem.mount( "rom", "rom", romMount );
return filesystem;
}
catch( FileSystemException e )
{
if( filesystem != null ) filesystem.close();
ComputerCraft.log.error( "Cannot mount computer filesystem", e );
displayFailure( "Cannot mount computer system" );
return null;
}
}
private ILuaMachine createLuaMachine()
{
// Load the bios resource
InputStream biosStream = null;
try
{
biosStream = computer.getComputerEnvironment().createResourceFile( "computercraft", "lua/bios.lua" );
}
catch( Exception ignored )
{
}
if( biosStream == null )
{
displayFailure( "Error loading bios.lua" );
return null;
}
// Create the lua machine
ILuaMachine machine = new CobaltLuaMachine( computer, timeout );
// Add the APIs
for( ILuaAPI api : apis ) machine.addAPI( api );
// Start the machine running the bios resource
machine.loadBios( biosStream );
IoUtil.closeQuietly( biosStream );
if( machine.isFinished() )
{
machine.close();
displayFailure( "Error starting bios.lua" );
return null;
}
return machine;
}
private void turnOn() throws InterruptedException
{
isOnLock.lockInterruptibly();
try
{
// Reset the terminal and event queue
computer.getTerminal().reset();
synchronized( queueLock )
{
eventQueue.clear();
}
// Init filesystem
if( (this.fileSystem = createFileSystem()) == null )
{
shutdown();
return;
}
// Init APIs
for( ILuaAPI api : apis ) api.startup();
// Init lua
if( (this.machine = createLuaMachine()) == null )
{
shutdown();
return;
}
// Initialisation has finished, so let's mark ourselves as on.
isOn = true;
computer.markChanged();
}
finally
{
isOnLock.unlock();
}
// Now actually start the computer, now that everything is set up.
machine.handleEvent( null, null );
}
private void shutdown() throws InterruptedException
{
isOnLock.lockInterruptibly();
try
{
isOn = false;
synchronized( queueLock )
{
eventQueue.clear();
}
// Shutdown Lua machine
if( machine != null )
{
machine.close();
machine = null;
}
// Shutdown our APIs
for( ILuaAPI api : apis ) api.shutdown();
// Unload filesystem
if( fileSystem != null )
{
fileSystem.close();
fileSystem = null;
}
computer.getEnvironment().resetOutput();
computer.markChanged();
}
finally
{
isOnLock.unlock();
}
}
/**
* Called before calling {@link #work()}, setting up any important state.
*/
void beforeWork()
{
timeout.reset();
}
/**
* Called after executing {@link #work()}. Adds this back to the {@link ComputerThread} if we have more work,
* otherwise remove it.
*/
void afterWork()
{
Tracking.addTaskTiming( getComputer(), timeout.nanoSinceStart() );
synchronized( queueLock )
{
if( eventQueue.isEmpty() && command == null )
{
onComputerQueue = false;
}
else
{
ComputerThread.queue( this );
}
}
}
/**
* The main worker function, called by {@link ComputerThread}.
*
* This either executes a {@link StateCommand} or attempts to run an event
*
* @throws InterruptedException If various locks could not be acquired.
* @see #command
* @see #eventQueue
*/
void work() throws InterruptedException
{
if( isExecuting.getAndSet( true ) )
{
throw new IllegalStateException( "Multiple threads running on computer the same time" );
}
try
{
StateCommand command;
Event event = null;
synchronized( queueLock )
{
command = this.command;
this.command = null;
// If we've no command, pull something from the event queue instead.
if( command == null )
{
if( !isOn )
{
// We're not on and had no command, but we had work queued. This should never happen, so clear
// the event queue just in case.
eventQueue.clear();
return;
}
event = eventQueue.poll();
}
}
if( command != null )
{
switch( command )
{
case TURN_ON:
if( isOn ) return;
turnOn();
break;
case SHUTDOWN:
if( !isOn ) return;
computer.getTerminal().reset();
shutdown();
break;
case REBOOT:
if( !isOn ) return;
computer.getTerminal().reset();
shutdown();
computer.turnOn();
break;
case ABORT:
if( !isOn ) return;
displayFailure( TimeoutState.ABORT_MESSAGE );
shutdown();
break;
}
}
else
{
machine.handleEvent( event.name, event.args );
if( machine.isFinished() )
{
displayFailure( "Error resuming bios.lua" );
shutdown();
}
}
}
finally
{
isExecuting.set( false );
}
}
private void displayFailure( String message )
{
Terminal terminal = computer.getTerminal();
terminal.reset();
terminal.write( message );
terminal.setCursorPos( 0, 1 );
terminal.write( "ComputerCraft may be installed incorrectly" );
}
private enum StateCommand
{
TURN_ON,
SHUTDOWN,
REBOOT,
ABORT,
}
private static class Event
{
final String name;
final Object[] args;
private Event( String name, Object[] args )
{
this.name = name;
this.args = args;
}
}
}

View File

@ -7,15 +7,13 @@
package dan200.computercraft.core.computer; package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.util.ThreadUtils; import dan200.computercraft.shared.util.ThreadUtils;
import java.util.HashSet; import javax.annotation.Nonnull;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import static dan200.computercraft.core.computer.TimeoutState.ABORT_TIMEOUT; import static dan200.computercraft.core.computer.TimeoutState.ABORT_TIMEOUT;
@ -24,8 +22,10 @@ import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT;
/** /**
* Responsible for running all tasks from a {@link Computer}. * Responsible for running all tasks from a {@link Computer}.
* *
* This is split into two components: the {@link TaskRunner}s, which pull a task from the queue and execute it, and * This is split into two components: the {@link TaskRunner}s, which pull an executor from the queue and execute it, and
* a single {@link Monitor} which observes all runners and kills them if they are behaving badly. * a single {@link Monitor} which observes all runners and kills them if they are behaving badly.
*
* TODO: Flesh out the documentation here.
*/ */
public class ComputerThread public class ComputerThread
{ {
@ -36,31 +36,15 @@ public class ComputerThread
*/ */
private static final int MONITOR_WAKEUP = 100; private static final int MONITOR_WAKEUP = 100;
/**
* The maximum number of entries in the event queue
*/
private static final int QUEUE_LIMIT = 256;
/** /**
* Lock used for modifications to the array of current threads. * Lock used for modifications to the array of current threads.
*/ */
private static final Object threadLock = new Object(); private static final Object threadLock = new Object();
/** /**
* Lock for various task operations * Active executors to run
*/ */
private static final Object taskLock = new Object(); private static final BlockingQueue<ComputerExecutor> computersActive = new LinkedBlockingQueue<>();
/**
* Map of objects to task list
*/
private static final WeakHashMap<Computer, BlockingQueue<ITask>> computerTaskQueues = new WeakHashMap<>();
/**
* Active queues to execute
*/
private static final BlockingQueue<BlockingQueue<ITask>> computerTasksActive = new LinkedBlockingQueue<>();
private static final Set<BlockingQueue<ITask>> computerTasksActiveSet = new HashSet<>();
/** /**
* Whether the computer thread system is currently running * Whether the computer thread system is currently running
@ -130,40 +114,18 @@ public class ComputerThread
} }
} }
synchronized( taskLock ) computersActive.clear();
{
computerTaskQueues.clear();
computerTasksActive.clear();
computerTasksActiveSet.clear();
}
} }
/** /**
* Queue a task to execute on the thread * Mark a computer as having work, enqueuing it on the thread.
* *
* @param task The task to execute * @param computer The computer to execute work on.
*/ */
static void queueTask( ITask task ) static void queue( @Nonnull ComputerExecutor computer )
{ {
Computer computer = task.getOwner(); if( !computer.onComputerQueue ) throw new IllegalStateException( "Computer must be on queue" );
BlockingQueue<ITask> queue; computersActive.add( computer );
synchronized( computerTaskQueues )
{
queue = computerTaskQueues.get( computer );
if( queue == null )
{
computerTaskQueues.put( computer, queue = new LinkedBlockingQueue<>( QUEUE_LIMIT ) );
}
}
synchronized( taskLock )
{
if( queue.offer( task ) && !computerTasksActiveSet.contains( queue ) )
{
computerTasksActive.add( queue );
computerTasksActiveSet.add( queue );
}
}
} }
/** /**
@ -193,18 +155,18 @@ public class ComputerThread
if( runner == null ) continue; if( runner == null ) continue;
// If the runner has no work, skip // If the runner has no work, skip
Computer computer = runner.currentComputer; ComputerExecutor executor = runner.currentExecutor.get();
if( computer == null ) continue; if( executor == null ) continue;
// If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT),
// then we can let the Lua machine do its work. // then we can let the Lua machine do its work.
long afterStart = computer.timeout.milliSinceStart(); long afterStart = executor.timeout.milliSinceStart();
long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT;
if( afterHardAbort < 0 ) continue; if( afterHardAbort < 0 ) continue;
// Set the hard abort flag. // Set the hard abort flag.
computer.timeout.hardAbort(); executor.timeout.hardAbort();
computer.abort(); executor.abort();
if( afterHardAbort >= ABORT_TIMEOUT + ABORT_TIMEOUT ) if( afterHardAbort >= ABORT_TIMEOUT + ABORT_TIMEOUT )
{ {
@ -212,7 +174,9 @@ public class ComputerThread
// as dead, finish off the task, and spawn a new runner. // as dead, finish off the task, and spawn a new runner.
// Note, we'll do the actual interruption of the thread in the next block. // Note, we'll do the actual interruption of the thread in the next block.
runner.running = false; runner.running = false;
finishTask( computer, runner.currentQueue );
ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null );
if( thisExecutor != null ) executor.afterWork();
synchronized( threadLock ) synchronized( threadLock )
{ {
@ -227,7 +191,7 @@ public class ComputerThread
{ {
// If we've hard aborted but we're still not dead, dump the stack trace and interrupt // If we've hard aborted but we're still not dead, dump the stack trace and interrupt
// the task. // the task.
timeoutTask( computer, runner.owner, afterStart ); timeoutTask( executor, runner.owner, afterStart );
runner.owner.interrupt(); runner.owner.interrupt();
} }
} }
@ -241,15 +205,14 @@ public class ComputerThread
} }
/** /**
* Pulls tasks from the {@link #computerTasksActive} queue and runs them. * Pulls tasks from the {@link #computersActive} queue and runs them.
*/ */
private static final class TaskRunner implements Runnable private static final class TaskRunner implements Runnable
{ {
Thread owner; Thread owner;
volatile boolean running = true; volatile boolean running = true;
BlockingQueue<ITask> currentQueue; final AtomicReference<ComputerExecutor> currentExecutor = new AtomicReference<>();
Computer currentComputer;
@Override @Override
public void run() public void run()
@ -259,10 +222,10 @@ public class ComputerThread
while( running && ComputerThread.running ) while( running && ComputerThread.running )
{ {
// Wait for an active queue to execute // Wait for an active queue to execute
BlockingQueue<ITask> queue; ComputerExecutor executor;
try try
{ {
queue = computerTasksActive.take(); executor = computersActive.take();
} }
catch( InterruptedException ignored ) catch( InterruptedException ignored )
{ {
@ -272,36 +235,33 @@ public class ComputerThread
} }
// Pull a task from this queue, and set what we're currently executing. // Pull a task from this queue, and set what we're currently executing.
ITask task = queue.remove(); currentExecutor.set( executor );
Computer computer = this.currentComputer = task.getOwner();
this.currentQueue = queue;
// Execute the task // Execute the task
computer.timeout.reset(); executor.beforeWork();
try try
{ {
task.execute(); executor.work();
} }
catch( Exception e ) catch( Exception e )
{ {
ComputerCraft.log.error( "Error running task on computer #" + computer.getID(), e ); ComputerCraft.log.error( "Error running task on computer #" + executor.getComputer().getID(), e );
} }
finally finally
{ {
if( running ) finishTask( computer, queue ); ComputerExecutor thisExecutor = currentExecutor.getAndSet( null );
this.currentQueue = null; if( thisExecutor != null ) executor.afterWork();
this.currentComputer = null;
} }
} }
} }
} }
private static void timeoutTask( Computer computer, Thread thread, long nanotime ) private static void timeoutTask( ComputerExecutor executor, Thread thread, long nanotime )
{ {
if( !ComputerCraft.logPeripheralErrors ) return; if( !ComputerCraft.logPeripheralErrors ) return;
StringBuilder builder = new StringBuilder() StringBuilder builder = new StringBuilder()
.append( "Terminating computer #" ).append( computer.getID() ) .append( "Terminating computer #" ).append( executor.getComputer().getID() )
.append( " due to timeout (running for " ).append( nanotime / 1e9 ) .append( " due to timeout (running for " ).append( nanotime / 1e9 )
.append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) .append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " )
.append( thread.getName() ) .append( thread.getName() )
@ -317,22 +277,4 @@ public class ComputerThread
ComputerCraft.log.warn( builder.toString() ); ComputerCraft.log.warn( builder.toString() );
} }
private static void finishTask( Computer computer, BlockingQueue<ITask> queue )
{
Tracking.addTaskTiming( computer, computer.timeout.nanoSinceStart() );
// Re-add it back onto the queue or remove it
synchronized( taskLock )
{
if( queue.isEmpty() )
{
computerTasksActiveSet.remove( queue );
}
else
{
computerTasksActive.add( queue );
}
}
}
} }

View File

@ -204,16 +204,6 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
NetworkHandler.sendToAllPlayers( new ComputerDeletedClientMessage( getInstanceID() ) ); NetworkHandler.sendToAllPlayers( new ComputerDeletedClientMessage( getInstanceID() ) );
} }
public IWritableMount getRootMount()
{
return m_computer.getRootMount();
}
public int assignID()
{
return m_computer.assignID();
}
public void setID( int id ) public void setID( int id )
{ {
m_computer.setID( id ); m_computer.setID( id );
@ -303,7 +293,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
public void addAPI( ILuaAPI api ) public void addAPI( ILuaAPI api )
{ {
m_computer.addAPI( api ); m_computer.addApi( api );
} }
@Deprecated @Deprecated

View File

@ -45,7 +45,7 @@ public class ComputerBootstrap
final Computer computer = new Computer( new BasicEnvironment( mount ), term, 0 ); final Computer computer = new Computer( new BasicEnvironment( mount ), term, 0 );
AssertApi api = new AssertApi(); AssertApi api = new AssertApi();
computer.addAPI( api ); computer.addApi( api );
try try
{ {