1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-09-29 23:40:46 +00:00

Convert MainThread into a priority queue

This uses a similar approach to ComputerThread: executors store how long
they've spent executing tasks. We then use that time to prioritise
executors.

One should note that we use the current runtime at the point of adding
to the queue - external tasks will not contribute towards it until a
later execution.
This commit is contained in:
SquidDev 2019-03-26 08:40:12 +00:00
parent 245bf26480
commit 7799b8d4cb
3 changed files with 70 additions and 10 deletions

View File

@ -34,6 +34,17 @@ public interface IWorkMonitor
*/ */
boolean canWork(); boolean canWork();
/**
* If the owning computer is currently allowed to execute work, and has ample time to do so.
*
* This is effectively a more restrictive form of {@link #canWork()}. One should use that in order to determine if
* you may do an initial piece of work, and {@link #shouldWork()} to determine if any additional task may be
* performed.
*
* @return If we should execute work right now.
*/
boolean shouldWork();
/** /**
* Inform the monitor how long some piece of work took to execute. * Inform the monitor how long some piece of work took to execute.
* *

View File

@ -10,9 +10,8 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaTask; import dan200.computercraft.api.lua.ILuaTask;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayDeque;
import java.util.HashSet; import java.util.HashSet;
import java.util.Queue; import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
/** /**
@ -41,7 +40,14 @@ public class MainThread
/** /**
* The queue of {@link MainThreadExecutor}s with tasks to perform. * The queue of {@link MainThreadExecutor}s with tasks to perform.
*/ */
private static final Queue<MainThreadExecutor> executors = new ArrayDeque<>(); private static final TreeSet<MainThreadExecutor> executors = new TreeSet<>( ( a, b ) -> {
if( a == b ) return 0; // Should never happen, but let's be consistent here
long at = a.virtualTime, bt = b.virtualTime;
if( at == bt ) return Integer.compare( a.hashCode(), b.hashCode() );
return at < bt ? -1 : 1;
} );
;
/** /**
* The set of executors which went over budget in a previous tick, and are waiting for their time to run down. * The set of executors which went over budget in a previous tick, and are waiting for their time to run down.
@ -71,17 +77,29 @@ public class MainThread
*/ */
private static boolean canExecute = true; private static boolean canExecute = true;
private static long minimumTime = 0;
public static long getUniqueTaskID() public static long getUniqueTaskID()
{ {
return lastTaskId.incrementAndGet(); return lastTaskId.incrementAndGet();
} }
static void queue( @Nonnull MainThreadExecutor executor ) static void queue( @Nonnull MainThreadExecutor executor, boolean sleeper )
{ {
synchronized( executors ) synchronized( executors )
{ {
if( executor.onQueue ) throw new IllegalStateException( "Cannot queue already queued executor" ); if( executor.onQueue ) throw new IllegalStateException( "Cannot queue already queued executor" );
executor.onQueue = true; executor.onQueue = true;
executor.updateTime();
// We're not currently on the queue, so update its current execution time to
// ensure its at least as high as the minimum.
long newRuntime = minimumTime;
// Slow down new computers a little bit.
if( executor.virtualTime == 0 ) newRuntime += ComputerCraft.maxMainComputerTime;
executor.virtualTime = Math.max( newRuntime, executor.virtualTime );
executors.add( executor ); executors.add( executor );
} }
@ -131,7 +149,7 @@ public class MainThread
MainThreadExecutor executor; MainThreadExecutor executor;
synchronized( executors ) synchronized( executors )
{ {
executor = executors.poll(); executor = executors.pollFirst();
} }
if( executor == null ) break; if( executor == null ) break;
@ -139,12 +157,19 @@ public class MainThread
executor.execute(); executor.execute();
long taskStop = System.nanoTime(); long taskStop = System.nanoTime();
if( executor.afterExecute( taskStop - taskStart ) )
{
synchronized( executors ) synchronized( executors )
{ {
executors.add( executor ); if( executor.afterExecute( taskStop - taskStart ) ) executors.add( executor );
// Compute the new minimum time (including the next task on the queue too). Note that this may also include
// time spent in external tasks.
long newMinimum = executor.virtualTime;
if( !executors.isEmpty() )
{
MainThreadExecutor next = executors.first();
if( next.virtualTime < newMinimum ) newMinimum = next.virtualTime;
} }
minimumTime = Math.max( minimumTime, newMinimum );
} }
if( taskStop >= deadline ) break; if( taskStop >= deadline ) break;
@ -158,6 +183,7 @@ public class MainThread
currentTick = 0; currentTick = 0;
budget = 0; budget = 0;
canExecute = true; canExecute = true;
minimumTime = 0;
lastTaskId.set( 0 ); lastTaskId.set( 0 );
cooling.clear(); cooling.clear();
synchronized( executors ) synchronized( executors )

View File

@ -106,6 +106,10 @@ final class MainThreadExecutor implements IWorkMonitor
*/ */
private State state = State.COOL; private State state = State.COOL;
private long pendingTime;
long virtualTime;
MainThreadExecutor( Computer computer ) MainThreadExecutor( Computer computer )
{ {
this.computer = computer; this.computer = computer;
@ -122,7 +126,7 @@ final class MainThreadExecutor implements IWorkMonitor
synchronized( queueLock ) synchronized( queueLock )
{ {
if( tasks.size() >= MAX_TASKS || !tasks.offer( runnable ) ) return false; if( tasks.size() >= MAX_TASKS || !tasks.offer( runnable ) ) return false;
if( !onQueue && state == State.COOL ) MainThread.queue( this ); if( !onQueue && state == State.COOL ) MainThread.queue( this, true );
return true; return true;
} }
} }
@ -152,6 +156,8 @@ final class MainThreadExecutor implements IWorkMonitor
synchronized( queueLock ) synchronized( queueLock )
{ {
virtualTime += time;
updateTime();
if( state != State.COOL || tasks.isEmpty() ) return onQueue = false; if( state != State.COOL || tasks.isEmpty() ) return onQueue = false;
return true; return true;
} }
@ -168,10 +174,21 @@ final class MainThreadExecutor implements IWorkMonitor
return state != State.COOLING && MainThread.canExecute(); return state != State.COOLING && MainThread.canExecute();
} }
@Override
public boolean shouldWork()
{
return state == State.COOL && MainThread.canExecute();
}
@Override @Override
public void trackWork( long time, @Nonnull TimeUnit unit ) public void trackWork( long time, @Nonnull TimeUnit unit )
{ {
long nanoTime = unit.toNanos( time ); long nanoTime = unit.toNanos( time );
synchronized( queueLock )
{
pendingTime += nanoTime;
}
consumeTime( nanoTime ); consumeTime( nanoTime );
MainThread.consumeTime( nanoTime ); MainThread.consumeTime( nanoTime );
} }
@ -213,11 +230,17 @@ final class MainThreadExecutor implements IWorkMonitor
state = State.COOL; state = State.COOL;
synchronized( queueLock ) synchronized( queueLock )
{ {
if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this ); if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this, false );
} }
return true; return true;
} }
void updateTime()
{
virtualTime += pendingTime;
pendingTime = 0;
}
private enum State private enum State
{ {
COOL, COOL,