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:
parent
06e76f9b15
commit
67af7a698b
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user