diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java
index 756ad66c7..beedf9b6c 100644
--- a/src/main/java/dan200/computercraft/ComputerCraft.java
+++ b/src/main/java/dan200/computercraft/ComputerCraft.java
@@ -24,6 +24,7 @@ import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.apis.http.websocket.Websocket;
+import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
@@ -304,6 +305,7 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
+ MainThread.reset();
Tracking.reset();
}
}
@@ -315,6 +317,7 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
+ MainThread.reset();
Tracking.reset();
}
}
diff --git a/src/main/java/dan200/computercraft/core/computer/Computer.java b/src/main/java/dan200/computercraft/core/computer/Computer.java
index 8bad95709..aa5365f30 100644
--- a/src/main/java/dan200/computercraft/core/computer/Computer.java
+++ b/src/main/java/dan200/computercraft/core/computer/Computer.java
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
*
Keeps track of whether the computer is on and blinking.
* Monitors whether the computer's visible state (redstone, on/off/blinking) has changed.
* Passes commands and events to the {@link ComputerExecutor}.
+ * Passes main thread tasks to the {@link MainThreadExecutor}.
*
*/
public class Computer
@@ -39,6 +40,7 @@ public class Computer
private final IComputerEnvironment m_environment;
private final Terminal m_terminal;
private final ComputerExecutor executor;
+ private final MainThreadExecutor serverExecutor;
// Additional state about the computer and its environment.
private boolean m_blinking = false;
@@ -55,6 +57,7 @@ public class Computer
m_terminal = terminal;
executor = new ComputerExecutor( this );
+ serverExecutor = new MainThreadExecutor( this );
}
IComputerEnvironment getComputerEnvironment()
@@ -112,6 +115,43 @@ public class Computer
executor.queueEvent( event, args );
}
+ /**
+ * Queue a task to be run on the main thread, using {@link MainThread}.
+ *
+ * @param runnable The task to run
+ * @return If the task was successfully queued (namely, whether there is space on it).
+ */
+ public boolean queueMainThread( Runnable runnable )
+ {
+ return serverExecutor.enqueue( runnable );
+ }
+
+ /**
+ * If this computer is allowed to execute work on the main thread.
+ *
+ * One only needs to use this if executing work outside of {@link #queueMainThread(Runnable)}.
+ *
+ * @return If we can execute work on the main thread this tick.
+ * @see #afterExecuteMainThread(long)
+ */
+ public boolean canExecuteMainThread()
+ {
+ return MainThread.canExecute() && serverExecutor.canExecuteExternal();
+ }
+
+ /**
+ * Increment the time taken to execute work this tick.
+ *
+ * One only needs to use this if executing work outside of {@link #queueMainThread(Runnable)}.
+ *
+ * @param time The time, in nanoseconds.
+ * @see #canExecuteMainThread()
+ */
+ public void afterExecuteMainThread( long time )
+ {
+ serverExecutor.afterExecuteExternal( time );
+ }
+
public int getID()
{
return m_id;
diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java
index eb55426ca..dd803f618 100644
--- a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java
+++ b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java
@@ -133,7 +133,7 @@ final class ComputerExecutor
*
* Note, this should be empty if this computer is off - it is cleared on shutdown and when turning on again.
*/
- private final Queue eventQueue = new ArrayDeque<>();
+ private final Queue eventQueue = new ArrayDeque<>( 4 );
/**
* Whether we interrupted an event and so should resume it instead of executing another task.
diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java
index 16cc6a294..aed50361f 100644
--- a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java
+++ b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java
@@ -112,7 +112,7 @@ public class ComputerThread
long at = a.virtualRuntime, bt = b.virtualRuntime;
if( at == bt ) return Integer.compare( a.hashCode(), b.hashCode() );
- return Long.compare( at, bt );
+ return at < bt ? -1 : 1;
} );
/**
diff --git a/src/main/java/dan200/computercraft/core/computer/ITask.java b/src/main/java/dan200/computercraft/core/computer/ITask.java
deleted file mode 100644
index b9ddd25d6..000000000
--- a/src/main/java/dan200/computercraft/core/computer/ITask.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * 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 javax.annotation.Nonnull;
-
-public interface ITask
-{
- @Nonnull
- Computer getOwner();
-
- void execute();
-}
diff --git a/src/main/java/dan200/computercraft/core/computer/MainThread.java b/src/main/java/dan200/computercraft/core/computer/MainThread.java
index d6a60f637..7b867d0f1 100644
--- a/src/main/java/dan200/computercraft/core/computer/MainThread.java
+++ b/src/main/java/dan200/computercraft/core/computer/MainThread.java
@@ -6,66 +6,181 @@
package dan200.computercraft.core.computer;
-import dan200.computercraft.core.tracking.Tracking;
+import dan200.computercraft.api.lua.ILuaTask;
+import javax.annotation.Nonnull;
import java.util.ArrayDeque;
+import java.util.HashSet;
import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+/**
+ * Runs tasks on the main (server) thread, ticks {@link MainThreadExecutor}s, and limits how much time is used this
+ * tick.
+ *
+ * Similar to {@link MainThreadExecutor}, the {@link MainThread} can be in one of three states: cool, hot and cooling.
+ * However, the implementation here is a little different:
+ *
+ * {@link MainThread} starts cool, and runs as many tasks as it can in the current {@link #budget}ns. Any external tasks
+ * (those run by tile entities, etc...) will also consume the budget
+ *
+ * Next tick, we put {@link #MAX_TICK_TIME} into our budget (and clamp it to that value to). If we're still over budget,
+ * then we should not execute any work (either as part of {@link MainThread} or externally).
+ */
public class MainThread
{
- private static final int MAX_TASKS_PER_TICK = 1000;
- private static final int MAX_TASKS_TOTAL = 50000;
+ /**
+ * The maximum time that can be spent executing tasks in a single tick.
+ *
+ * Note, we will quite possibly go over this limit, as there's no way to tell how long a will take - this aims
+ * to be the upper bound of the average time.
+ *
+ * @see #budget
+ */
+ private static final long MAX_TICK_TIME = TimeUnit.MILLISECONDS.toNanos( 10 );
- private static final Queue m_outstandingTasks = new ArrayDeque<>();
- private static final Object m_nextUnusedTaskIDLock = new Object();
- private static long m_nextUnusedTaskID = 0;
+ /**
+ * The ideal maximum time a computer can execute for in a tick.
+ *
+ * Note, we will quite possibly go over this limit, as there's no way to tell how long a task will take - this aims
+ * to be the upper bound of the average time.
+ */
+ static final long MAX_COMPUTER_TIME = TimeUnit.MILLISECONDS.toNanos( 5 );
+
+ /**
+ * An internal counter for {@link ILuaTask} ids.
+ *
+ * @see dan200.computercraft.api.lua.ILuaContext#issueMainThreadTask(ILuaTask)
+ * @see #getUniqueTaskID()
+ */
+ private static final AtomicLong lastTaskId = new AtomicLong();
+
+ /**
+ * The queue of {@link MainThreadExecutor}s with tasks to perform.
+ */
+ private static final Queue executors = new ArrayDeque<>();
+
+ /**
+ * The set of executors which went over budget in a previous tick, and are waiting for their time to run down.
+ *
+ * @see MainThreadExecutor#tickCooling()
+ * @see #cooling(MainThreadExecutor)
+ */
+ private static final HashSet cooling = new HashSet<>();
+
+ /**
+ * The current tick number. This is used by {@link MainThreadExecutor} to determine when to reset its own time
+ * counter.
+ *
+ * @see #currentTick()
+ */
+ private static int currentTick;
+
+ /**
+ * The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
+ */
+ private static long budget;
+
+ /**
+ * Whether we should be executing any work this tick.
+ *
+ * This is true iff {@code MAX_TICK_TIME - currentTime} was true at the beginning of the tick.
+ */
+ private static boolean canExecute = true;
public static long getUniqueTaskID()
{
- synchronized( m_nextUnusedTaskIDLock )
+ return lastTaskId.incrementAndGet();
+ }
+
+ static void queue( @Nonnull MainThreadExecutor executor )
+ {
+ synchronized( executors )
{
- return ++m_nextUnusedTaskID;
+ if( executor.onQueue ) throw new IllegalStateException( "Cannot queue already queued executor" );
+ executor.onQueue = true;
+
+ executors.add( executor );
}
}
- public static boolean queueTask( ITask task )
+ static void cooling( @Nonnull MainThreadExecutor executor )
{
- synchronized( m_outstandingTasks )
- {
- if( m_outstandingTasks.size() < MAX_TASKS_TOTAL )
- {
- m_outstandingTasks.offer( task );
- return true;
- }
- }
- return false;
+ cooling.add( executor );
+ }
+
+ static void consumeTime( long time )
+ {
+ budget -= time;
+ }
+
+ static boolean canExecute()
+ {
+ return canExecute;
+ }
+
+ static int currentTick()
+ {
+ return currentTick;
}
public static void executePendingTasks()
{
- int tasksThisTick = 0;
- while( tasksThisTick < MAX_TASKS_PER_TICK )
+ // Move onto the next tick and cool down the global executor. We're allowed to execute if we have _any_ time
+ // allocated for this tick. This means we'll stick much closer to doing MAX_TICK_TIME work every tick.
+ //
+ // Of course, we'll go over the MAX_TICK_TIME most of the time, but eventually that overrun will accumulate
+ // and we'll skip a whole tick - bringing the average back down again.
+ currentTick++;
+ budget += Math.min( budget + MAX_TICK_TIME, MAX_TICK_TIME );
+ canExecute = budget > 0;
+
+ // Cool down any warm computers.
+ cooling.removeIf( MainThreadExecutor::tickCooling );
+
+ if( !canExecute ) return;
+
+ // Run until we meet the deadline.
+ long start = System.nanoTime();
+ long deadline = start + budget;
+ while( true )
{
- ITask task = null;
- synchronized( m_outstandingTasks )
+ MainThreadExecutor executor;
+ synchronized( executors )
{
- task = m_outstandingTasks.poll();
+ executor = executors.poll();
}
- if( task != null )
- {
- long start = System.nanoTime();
- task.execute();
+ if( executor == null ) break;
- long stop = System.nanoTime();
- Computer computer = task.getOwner();
- if( computer != null ) Tracking.addServerTiming( computer, stop - start );
+ long taskStart = System.nanoTime();
+ executor.execute();
- ++tasksThisTick;
- }
- else
+ long taskStop = System.nanoTime();
+ if( executor.afterExecute( taskStop - taskStart ) )
{
- break;
+ synchronized( executors )
+ {
+ executors.add( executor );
+ }
}
+
+ if( taskStop >= deadline ) break;
+ }
+
+ consumeTime( System.nanoTime() - start );
+ }
+
+ public static void reset()
+ {
+ currentTick = 0;
+ budget = 0;
+ canExecute = true;
+ lastTaskId.set( 0 );
+ cooling.clear();
+ synchronized( executors )
+ {
+ executors.clear();
}
}
}
diff --git a/src/main/java/dan200/computercraft/core/computer/MainThreadExecutor.java b/src/main/java/dan200/computercraft/core/computer/MainThreadExecutor.java
new file mode 100644
index 000000000..d978f2c66
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/computer/MainThreadExecutor.java
@@ -0,0 +1,228 @@
+/*
+ * 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.core.tracking.Tracking;
+import dan200.computercraft.shared.turtle.core.TurtleBrain;
+import net.minecraft.tileentity.TileEntity;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+import static dan200.computercraft.core.computer.MainThread.MAX_COMPUTER_TIME;
+
+/**
+ * Keeps track of tasks that a {@link Computer} should run on the main thread and how long that has been spent executing
+ * them.
+ *
+ * This provides rate-limiting mechanism for tasks enqueued with {@link Computer#queueMainThread(Runnable)}, but also
+ * those run elsewhere (such as during the turtle's tick - see {@link TurtleBrain#update()}). In order to handle this,
+ * the executor goes through three stages:
+ *
+ * When {@link State#COOL}, the computer is allocated {@link MainThread#MAX_COMPUTER_TIME}ns to execute any work this
+ * tick. At the beginning of the tick, we execute as many {@link MainThread} tasks as possible, until our timeframe or
+ * the global time frame has expired.
+ *
+ * Then, when other objects (such as {@link TileEntity}) are ticked, we update how much time we've used using
+ * {@link Computer#afterExecuteMainThread(long)}.
+ *
+ * Now, if anywhere during this period, we use more than our allocated time slice, the executor is marked as
+ * {@link State#HOT}. This means it will no longer be able to execute {@link MainThread} tasks (though will still
+ * execute tile entity tasks, in order to prevent the main thread from exhausting work every tick).
+ *
+ * At the beginning of the next tick, we increment the budget e by {@link MainThread#MAX_COMPUTER_TIME} and any
+ * {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is
+ * fully replenished (is equal to {@link MainThread#MAX_COMPUTER_TIME}). Note, this is different to {@link MainThread},
+ * which allows running when it has any budget left. When cooling, no tasks are executed - be they on the tile
+ * entity or main thread.
+ *
+ * This mechanism means that, on average, computers will use at most {@link MainThread#MAX_COMPUTER_TIME}ns per second,
+ * but one task source will not prevent others from executing.
+ *
+ * @see MainThread
+ * @see Computer#canExecuteMainThread()
+ * @see Computer#queueMainThread(Runnable)
+ * @see Computer#afterExecuteMainThread(long)
+ */
+final class MainThreadExecutor
+{
+ /**
+ * The maximum number of {@link MainThread} tasks allowed on the queue.
+ */
+ private static final int MAX_TASKS = 5000;
+
+ private final Computer computer;
+
+ /**
+ * A lock used for any changes to {@link #tasks}, or {@link #onQueue}. This will be
+ * used on the main thread, so locks should be kept as brief as possible.
+ */
+ private final Object queueLock = new Object();
+
+ /**
+ * The queue of tasks which should be executed.
+ *
+ * @see #queueLock
+ */
+ private final Queue tasks = new ArrayDeque<>( 4 );
+
+ /**
+ * Determines if this executor is currently present on the queue.
+ *
+ * This should be true iff {@link #tasks} is non-empty.
+ *
+ * @see #queueLock
+ * @see #enqueue(Runnable)
+ * @see #afterExecute(long)
+ */
+ volatile boolean onQueue;
+
+ /**
+ * The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
+ *
+ * @see #tickCooling()
+ * @see #consumeTime(long)
+ */
+ private long budget = 0;
+
+ /**
+ * The last tick that {@link #budget} was updated.
+ *
+ * @see #tickCooling()
+ * @see #consumeTime(long)
+ */
+ private int currentTick = -1;
+
+ /**
+ * The current state of this executor.
+ *
+ * @see #canExecuteExternal()
+ */
+ private State state = State.COOL;
+
+ MainThreadExecutor( Computer computer )
+ {
+ this.computer = computer;
+ }
+
+ /**
+ * Push a task onto this executor's queue, pushing it onto the {@link MainThread} if needed.
+ *
+ * @param runnable The task to run on the main thread.
+ * @return Whether this task was enqueued (namely, was there space).
+ */
+ boolean enqueue( Runnable runnable )
+ {
+ synchronized( queueLock )
+ {
+ if( tasks.size() >= MAX_TASKS || !tasks.offer( runnable ) ) return false;
+ if( !onQueue && state == State.COOL ) MainThread.queue( this );
+ return true;
+ }
+ }
+
+ void execute()
+ {
+ if( state != State.COOL ) return;
+
+ Runnable task;
+ synchronized( queueLock )
+ {
+ task = tasks.poll();
+ }
+
+ if( task != null ) task.run();
+ }
+
+ /**
+ * Update the time taken to run an {@link #enqueue(Runnable)} task.
+ *
+ * @param time The time some task took to run.
+ * @return Whether this should be added back to the queue.
+ */
+ boolean afterExecute( long time )
+ {
+ consumeTime( time );
+
+ synchronized( queueLock )
+ {
+ if( state != State.COOL || tasks.isEmpty() ) return onQueue = false;
+ return true;
+ }
+ }
+
+ /**
+ * Update the time taken to run an external task (one not part of {@link #tasks}), incrementing the appropriate
+ * statistics.
+ *
+ * @param time The time some task took to run
+ */
+ void afterExecuteExternal( long time )
+ {
+ consumeTime( time );
+ MainThread.consumeTime( time );
+ }
+
+ /**
+ * Whether we should execute "external" tasks (ones not part of {@link #tasks}).
+ *
+ * @return Whether we can execute external tasks.
+ */
+ boolean canExecuteExternal()
+ {
+ return state != State.COOLING;
+ }
+
+ private void consumeTime( long time )
+ {
+ Tracking.addServerTiming( computer, time );
+
+ // Reset the budget if moving onto a new tick. We know this is safe, as this will only have happened if
+ // #tickCooling() isn't called, and so we didn't overrun the previous tick.
+ if( currentTick != MainThread.currentTick() )
+ {
+ currentTick = MainThread.currentTick();
+ budget = MAX_COMPUTER_TIME;
+ }
+
+ budget -= time;
+
+ // If we've gone over our limit, mark us as having to cool down.
+ if( budget < 0 && state == State.COOL )
+ {
+ state = State.HOT;
+ MainThread.cooling( this );
+ }
+ }
+
+ /**
+ * Move this executor forward one tick, replenishing the budget by {@link MainThread#MAX_COMPUTER_TIME}.
+ *
+ * @return Whether this executor has cooled down, and so is safe to run again.
+ */
+ boolean tickCooling()
+ {
+ state = State.COOLING;
+ currentTick = MainThread.currentTick();
+ budget += Math.min( budget + MAX_COMPUTER_TIME, MAX_COMPUTER_TIME );
+ if( budget < MAX_COMPUTER_TIME ) return false;
+
+ state = State.COOL;
+ synchronized( queueLock )
+ {
+ if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this );
+ }
+ return true;
+ }
+
+ private enum State
+ {
+ COOL,
+ HOT,
+ COOLING,
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java
index e7f2eff27..153de72eb 100644
--- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java
+++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java
@@ -9,7 +9,6 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.computer.Computer;
-import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.tracking.Tracking;
@@ -536,53 +535,36 @@ public class CobaltLuaMachine implements ILuaMachine
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
- final ITask iTask = new ITask()
- {
- @Nonnull
- @Override
- public Computer getOwner()
+ final Runnable iTask = () -> {
+ try
{
- return m_computer;
+ Object[] results = task.execute();
+ if( results != null )
+ {
+ Object[] eventArguments = new Object[results.length + 2];
+ eventArguments[0] = taskID;
+ eventArguments[1] = true;
+ System.arraycopy( results, 0, eventArguments, 2, results.length );
+ m_computer.queueEvent( "task_complete", eventArguments );
+ }
+ else
+ {
+ m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
+ }
}
-
- @Override
- public void execute()
+ catch( LuaException e )
{
- try
- {
- Object[] results = task.execute();
- if( results != null )
- {
- Object[] eventArguments = new Object[results.length + 2];
- eventArguments[0] = taskID;
- eventArguments[1] = true;
- System.arraycopy( results, 0, eventArguments, 2, results.length );
- m_computer.queueEvent( "task_complete", eventArguments );
- }
- else
- {
- m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
- }
- }
- catch( LuaException e )
- {
- m_computer.queueEvent( "task_complete", new Object[] {
- taskID, false, e.getMessage()
- } );
- }
- catch( Throwable t )
- {
- if( ComputerCraft.logPeripheralErrors )
- {
- ComputerCraft.log.error( "Error running task", t );
- }
- m_computer.queueEvent( "task_complete", new Object[] {
- taskID, false, "Java Exception Thrown: " + t.toString()
- } );
- }
+ m_computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } );
+ }
+ catch( Throwable t )
+ {
+ if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running task", t );
+ m_computer.queueEvent( "task_complete", new Object[] {
+ taskID, false, "Java Exception Thrown: " + t.toString()
+ } );
}
};
- if( MainThread.queueTask( iTask ) )
+ if( m_computer.queueMainThread( iTask ) )
{
return taskID;
}
diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java
index c089d8d5b..0d7efbf27 100644
--- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java
+++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java
@@ -13,7 +13,6 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.*;
-import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.computer.blocks.ComputerProxy;
import dan200.computercraft.shared.computer.blocks.TileComputerBase;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@@ -957,53 +956,50 @@ public class TurtleBrain implements ITurtleAccess
private void updateCommands()
{
- if( m_animation == TurtleAnimation.None )
+ if( m_animation != TurtleAnimation.None || m_commandQueue.isEmpty() ) return;
+
+ // If we've got a computer, ensure that we're allowed to perform work.
+ ServerComputer computer = m_owner.getServerComputer();
+ if( computer != null && !computer.getComputer().canExecuteMainThread() ) return;
+
+ // Pull a new command
+ TurtleCommandQueueEntry nextCommand = m_commandQueue.poll();
+ if( nextCommand == null ) return;
+
+ // Execute the command
+ long start = System.nanoTime();
+ TurtleCommandResult result = nextCommand.command.execute( this );
+ long end = System.nanoTime();
+
+ // Dispatch the callback
+ if( computer == null ) return;
+ computer.getComputer().afterExecuteMainThread( end - start );
+ int callbackID = nextCommand.callbackID;
+ if( callbackID < 0 ) return;
+
+ if( result != null && result.isSuccess() )
{
- // Pull a new command
- TurtleCommandQueueEntry nextCommand = m_commandQueue.poll();
- if( nextCommand != null )
+ Object[] results = result.getResults();
+ if( results != null )
{
- ServerComputer computer = m_owner.getServerComputer();
-
- // Execute the command
- long start = System.nanoTime();
- TurtleCommandResult result = nextCommand.command.execute( this );
- long end = System.nanoTime();
-
- // Dispatch the callback
- if( computer != null )
- {
- Tracking.addServerTiming( computer.getComputer(), end - start );
- int callbackID = nextCommand.callbackID;
- if( callbackID >= 0 )
- {
- if( result != null && result.isSuccess() )
- {
- Object[] results = result.getResults();
- if( results != null )
- {
- Object[] arguments = new Object[results.length + 2];
- arguments[0] = callbackID;
- arguments[1] = true;
- System.arraycopy( results, 0, arguments, 2, results.length );
- computer.queueEvent( "turtle_response", arguments );
- }
- else
- {
- computer.queueEvent( "turtle_response", new Object[] {
- callbackID, true
- } );
- }
- }
- else
- {
- computer.queueEvent( "turtle_response", new Object[] {
- callbackID, false, result != null ? result.getErrorMessage() : null
- } );
- }
- }
- }
+ Object[] arguments = new Object[results.length + 2];
+ arguments[0] = callbackID;
+ arguments[1] = true;
+ System.arraycopy( results, 0, arguments, 2, results.length );
+ computer.queueEvent( "turtle_response", arguments );
}
+ else
+ {
+ computer.queueEvent( "turtle_response", new Object[] {
+ callbackID, true
+ } );
+ }
+ }
+ else
+ {
+ computer.queueEvent( "turtle_response", new Object[] {
+ callbackID, false, result != null ? result.getErrorMessage() : null
+ } );
}
}