mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 05:33:00 +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:
		| @@ -7,57 +7,46 @@ | ||||
| package dan200.computercraft.core.computer; | ||||
|  | ||||
| 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.ILuaAPIFactory; | ||||
| 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.FileSystemException; | ||||
| import dan200.computercraft.core.lua.CobaltLuaMachine; | ||||
| import dan200.computercraft.core.lua.ILuaMachine; | ||||
| 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; | ||||
|  | ||||
| /** | ||||
|  * 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 | ||||
| { | ||||
|     private enum State | ||||
|     { | ||||
|         Off, | ||||
|         Starting, | ||||
|         Running, | ||||
|         Stopping, | ||||
|     } | ||||
|  | ||||
|     private static IMount s_romMount = null; | ||||
|     private static final int START_DELAY = 50; | ||||
|  | ||||
|     // Various properties of the computer | ||||
|     private int m_id; | ||||
|     private String m_label = null; | ||||
|  | ||||
|     // Read-only fields about the computer | ||||
|     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 FileSystem m_fileSystem = null; | ||||
|     private IWritableMount m_rootMount = null; | ||||
|     private final ComputerExecutor executor; | ||||
|  | ||||
|     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 ) | ||||
|     { | ||||
| @@ -65,24 +54,7 @@ public class Computer | ||||
|         m_environment = environment; | ||||
|         m_terminal = terminal; | ||||
|  | ||||
|         // Ensure the computer thread is running as required. | ||||
|         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 ) ); | ||||
|         } | ||||
|         executor = new ComputerExecutor( this ); | ||||
|     } | ||||
|  | ||||
|     IComputerEnvironment getComputerEnvironment() | ||||
| @@ -92,7 +64,7 @@ public class Computer | ||||
|  | ||||
|     FileSystem getFileSystem() | ||||
|     { | ||||
|         return m_fileSystem; | ||||
|         return executor.getFileSystem(); | ||||
|     } | ||||
|  | ||||
|     Terminal getTerminal() | ||||
| @@ -102,40 +74,42 @@ public class Computer | ||||
|  | ||||
|     public Environment getEnvironment() | ||||
|     { | ||||
|         return m_internalEnvironment; | ||||
|         return internalEnvironment; | ||||
|     } | ||||
|  | ||||
|     public IAPIEnvironment getAPIEnvironment() | ||||
|     { | ||||
|         return m_internalEnvironment; | ||||
|     } | ||||
|  | ||||
|     public void turnOn() | ||||
|     { | ||||
|         if( m_state == State.Off ) m_startRequested = true; | ||||
|     } | ||||
|  | ||||
|     public void shutdown() | ||||
|     { | ||||
|         stopComputer( false ); | ||||
|     } | ||||
|  | ||||
|     public void reboot() | ||||
|     { | ||||
|         stopComputer( true ); | ||||
|         return internalEnvironment; | ||||
|     } | ||||
|  | ||||
|     public boolean isOn() | ||||
|     { | ||||
|         synchronized( this ) | ||||
|         { | ||||
|             return m_state == State.Running; | ||||
|         } | ||||
|         return executor.isOn(); | ||||
|     } | ||||
|  | ||||
|     public void turnOn() | ||||
|     { | ||||
|         startRequested = true; | ||||
|     } | ||||
|  | ||||
|     public void shutdown() | ||||
|     { | ||||
|         executor.queueStop( false, false ); | ||||
|     } | ||||
|  | ||||
|     public void reboot() | ||||
|     { | ||||
|         executor.queueStop( true, false ); | ||||
|     } | ||||
|  | ||||
|     public void unload() | ||||
|     { | ||||
|         stopComputer( false ); | ||||
|         executor.queueStop( false, true ); | ||||
|     } | ||||
|  | ||||
|     public void queueEvent( String event, Object[] args ) | ||||
|     { | ||||
|         executor.queueEvent( event, args ); | ||||
|     } | ||||
|  | ||||
|     public int getID() | ||||
| @@ -173,38 +147,28 @@ public class Computer | ||||
|  | ||||
|     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 | ||||
|             if( m_ticksSinceStart >= 0 ) | ||||
|             { | ||||
|                 m_ticksSinceStart++; | ||||
|             } | ||||
|             if( m_startRequested && (m_ticksSinceStart < 0 || m_ticksSinceStart > 50) ) | ||||
|             { | ||||
|                 startComputer(); | ||||
|                 m_startRequested = false; | ||||
|             } | ||||
|  | ||||
|             if( m_state == State.Running ) | ||||
|             { | ||||
|                 // Update the environment's internal state. | ||||
|                 m_internalEnvironment.update(); | ||||
|  | ||||
|                 // Advance our APIs | ||||
|                 for( ILuaAPI api : m_apis ) api.update(); | ||||
|             } | ||||
|             m_ticksSinceStart = 0; | ||||
|             startRequested = false; | ||||
|             executor.queueStart(); | ||||
|         } | ||||
|  | ||||
|         // Prepare to propagate the environment's output to the world. | ||||
|         if( m_internalEnvironment.updateOutput() ) externalOutputChanged.set( true ); | ||||
|         executor.tick(); | ||||
|  | ||||
|         // Update the environment's internal state. | ||||
|         internalEnvironment.update(); | ||||
|  | ||||
|         // Propagate the environment's output to the world. | ||||
|         if( internalEnvironment.updateOutput() ) externalOutputChanged.set( true ); | ||||
|  | ||||
|         // Set output changed if the terminal has changed from blinking to not | ||||
|         boolean blinking = | ||||
|             m_terminal.getCursorBlink() && | ||||
|                 m_terminal.getCursorX() >= 0 && m_terminal.getCursorX() < m_terminal.getWidth() && | ||||
|                 m_terminal.getCursorY() >= 0 && m_terminal.getCursorY() < m_terminal.getHeight(); | ||||
|  | ||||
|         boolean blinking = m_terminal.getCursorBlink() && | ||||
|             m_terminal.getCursorX() >= 0 && m_terminal.getCursorX() < m_terminal.getWidth() && | ||||
|             m_terminal.getCursorY() >= 0 && m_terminal.getCursorY() < m_terminal.getHeight(); | ||||
|         if( blinking != m_blinking ) | ||||
|         { | ||||
|             m_blinking = blinking; | ||||
| @@ -212,6 +176,11 @@ public class Computer | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void markChanged() | ||||
|     { | ||||
|         externalOutputChanged.set( true ); | ||||
|     } | ||||
|  | ||||
|     public boolean pollAndResetChanged() | ||||
|     { | ||||
|         return externalOutputChanged.getAndSet( false ); | ||||
| @@ -222,336 +191,27 @@ public class Computer | ||||
|         return isOn() && m_blinking; | ||||
|     } | ||||
|  | ||||
|     public IWritableMount getRootMount() | ||||
|     public void addApi( ILuaAPI api ) | ||||
|     { | ||||
|         if( m_rootMount == null ) | ||||
|         { | ||||
|             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 ); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } ); | ||||
|         executor.addApi( api ); | ||||
|     } | ||||
|  | ||||
|     @Deprecated | ||||
|     public IPeripheral getPeripheral( int side ) | ||||
|     { | ||||
|         return m_internalEnvironment.getPeripheral( side ); | ||||
|         return internalEnvironment.getPeripheral( side ); | ||||
|     } | ||||
|  | ||||
|     @Deprecated | ||||
|     public void setPeripheral( int side, IPeripheral peripheral ) | ||||
|     { | ||||
|         m_internalEnvironment.setPeripheral( side, peripheral ); | ||||
|         internalEnvironment.setPeripheral( side, peripheral ); | ||||
|     } | ||||
|  | ||||
|     @Deprecated | ||||
|     public void addAPI( dan200.computercraft.core.apis.ILuaAPI api ) | ||||
|     { | ||||
|         addAPI( (ILuaAPI) api ); | ||||
|         addApi( api ); | ||||
|     } | ||||
|  | ||||
|     @Deprecated | ||||
| @@ -561,5 +221,6 @@ public class Computer | ||||
|         tick(); | ||||
|     } | ||||
|  | ||||
|     @Deprecated | ||||
|     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; | ||||
|  | ||||
| import dan200.computercraft.ComputerCraft; | ||||
| import dan200.computercraft.core.tracking.Tracking; | ||||
| import dan200.computercraft.shared.util.ThreadUtils; | ||||
|  | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| import java.util.WeakHashMap; | ||||
| import javax.annotation.Nonnull; | ||||
| import java.util.concurrent.BlockingQueue; | ||||
| import java.util.concurrent.LinkedBlockingQueue; | ||||
| import java.util.concurrent.ThreadFactory; | ||||
| import java.util.concurrent.atomic.AtomicReference; | ||||
| import java.util.concurrent.locks.LockSupport; | ||||
|  | ||||
| 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}. | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  * TODO: Flesh out the documentation here. | ||||
|  */ | ||||
| public class ComputerThread | ||||
| { | ||||
| @@ -36,31 +36,15 @@ public class ComputerThread | ||||
|      */ | ||||
|     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. | ||||
|      */ | ||||
|     private static final Object threadLock = new Object(); | ||||
|  | ||||
|     /** | ||||
|      * Lock for various task operations | ||||
|      * Active executors to run | ||||
|      */ | ||||
|     private static final Object taskLock = new Object(); | ||||
|  | ||||
|     /** | ||||
|      * 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<>(); | ||||
|     private static final BlockingQueue<ComputerExecutor> computersActive = new LinkedBlockingQueue<>(); | ||||
|  | ||||
|     /** | ||||
|      * Whether the computer thread system is currently running | ||||
| @@ -130,40 +114,18 @@ public class ComputerThread | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         synchronized( taskLock ) | ||||
|         { | ||||
|             computerTaskQueues.clear(); | ||||
|             computerTasksActive.clear(); | ||||
|             computerTasksActiveSet.clear(); | ||||
|         } | ||||
|         computersActive.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(); | ||||
|         BlockingQueue<ITask> queue; | ||||
|         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 ); | ||||
|             } | ||||
|         } | ||||
|         if( !computer.onComputerQueue ) throw new IllegalStateException( "Computer must be on queue" ); | ||||
|         computersActive.add( computer ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -193,18 +155,18 @@ public class ComputerThread | ||||
|                             if( runner == null ) continue; | ||||
|  | ||||
|                             // If the runner has no work, skip | ||||
|                             Computer computer = runner.currentComputer; | ||||
|                             if( computer == null ) continue; | ||||
|                             ComputerExecutor executor = runner.currentExecutor.get(); | ||||
|                             if( executor == null ) continue; | ||||
|  | ||||
|                             // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), | ||||
|                             // 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; | ||||
|                             if( afterHardAbort < 0 ) continue; | ||||
|  | ||||
|                             // Set the hard abort flag. | ||||
|                             computer.timeout.hardAbort(); | ||||
|                             computer.abort(); | ||||
|                             executor.timeout.hardAbort(); | ||||
|                             executor.abort(); | ||||
|  | ||||
|                             if( afterHardAbort >= ABORT_TIMEOUT + ABORT_TIMEOUT ) | ||||
|                             { | ||||
| @@ -212,7 +174,9 @@ public class ComputerThread | ||||
|                                 // 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. | ||||
|                                 runner.running = false; | ||||
|                                 finishTask( computer, runner.currentQueue ); | ||||
|  | ||||
|                                 ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null ); | ||||
|                                 if( thisExecutor != null ) executor.afterWork(); | ||||
|  | ||||
|                                 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 | ||||
|                                 // the task. | ||||
|                                 timeoutTask( computer, runner.owner, afterStart ); | ||||
|                                 timeoutTask( executor, runner.owner, afterStart ); | ||||
|                                 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 | ||||
|     { | ||||
|         Thread owner; | ||||
|         volatile boolean running = true; | ||||
|  | ||||
|         BlockingQueue<ITask> currentQueue; | ||||
|         Computer currentComputer; | ||||
|         final AtomicReference<ComputerExecutor> currentExecutor = new AtomicReference<>(); | ||||
|  | ||||
|         @Override | ||||
|         public void run() | ||||
| @@ -259,10 +222,10 @@ public class ComputerThread | ||||
|             while( running && ComputerThread.running ) | ||||
|             { | ||||
|                 // Wait for an active queue to execute | ||||
|                 BlockingQueue<ITask> queue; | ||||
|                 ComputerExecutor executor; | ||||
|                 try | ||||
|                 { | ||||
|                     queue = computerTasksActive.take(); | ||||
|                     executor = computersActive.take(); | ||||
|                 } | ||||
|                 catch( InterruptedException ignored ) | ||||
|                 { | ||||
| @@ -272,36 +235,33 @@ public class ComputerThread | ||||
|                 } | ||||
|  | ||||
|                 // Pull a task from this queue, and set what we're currently executing. | ||||
|                 ITask task = queue.remove(); | ||||
|                 Computer computer = this.currentComputer = task.getOwner(); | ||||
|                 this.currentQueue = queue; | ||||
|                 currentExecutor.set( executor ); | ||||
|  | ||||
|                 // Execute the task | ||||
|                 computer.timeout.reset(); | ||||
|                 executor.beforeWork(); | ||||
|                 try | ||||
|                 { | ||||
|                     task.execute(); | ||||
|                     executor.work(); | ||||
|                 } | ||||
|                 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 | ||||
|                 { | ||||
|                     if( running ) finishTask( computer, queue ); | ||||
|                     this.currentQueue = null; | ||||
|                     this.currentComputer = null; | ||||
|                     ComputerExecutor thisExecutor = currentExecutor.getAndSet( null ); | ||||
|                     if( thisExecutor != null ) executor.afterWork(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void timeoutTask( Computer computer, Thread thread, long nanotime ) | ||||
|     private static void timeoutTask( ComputerExecutor executor, Thread thread, long nanotime ) | ||||
|     { | ||||
|         if( !ComputerCraft.logPeripheralErrors ) return; | ||||
|  | ||||
|         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( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) | ||||
|             .append( thread.getName() ) | ||||
| @@ -317,22 +277,4 @@ public class ComputerThread | ||||
|  | ||||
|         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() ) ); | ||||
|     } | ||||
|  | ||||
|     public IWritableMount getRootMount() | ||||
|     { | ||||
|         return m_computer.getRootMount(); | ||||
|     } | ||||
|  | ||||
|     public int assignID() | ||||
|     { | ||||
|         return m_computer.assignID(); | ||||
|     } | ||||
|  | ||||
|     public void setID( int id ) | ||||
|     { | ||||
|         m_computer.setID( id ); | ||||
| @@ -303,7 +293,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput | ||||
|  | ||||
|     public void addAPI( ILuaAPI api ) | ||||
|     { | ||||
|         m_computer.addAPI( api ); | ||||
|         m_computer.addApi( api ); | ||||
|     } | ||||
|  | ||||
|     @Deprecated | ||||
|   | ||||
| @@ -45,7 +45,7 @@ public class ComputerBootstrap | ||||
|         final Computer computer = new Computer( new BasicEnvironment( mount ), term, 0 ); | ||||
|  | ||||
|         AssertApi api = new AssertApi(); | ||||
|         computer.addAPI( api ); | ||||
|         computer.addApi( api ); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev