1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +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();
/**
* 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.
*

View File

@ -10,9 +10,8 @@
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.TreeSet;
import java.util.concurrent.atomic.AtomicLong;
/**
@ -41,7 +40,14 @@ public class MainThread
/**
* 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.
@ -71,17 +77,29 @@ public class MainThread
*/
private static boolean canExecute = true;
private static long minimumTime = 0;
public static long getUniqueTaskID()
{
return lastTaskId.incrementAndGet();
}
static void queue( @Nonnull MainThreadExecutor executor )
static void queue( @Nonnull MainThreadExecutor executor, boolean sleeper )
{
synchronized( executors )
{
if( executor.onQueue ) throw new IllegalStateException( "Cannot queue already queued executor" );
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 );
}
@ -131,7 +149,7 @@ public static void executePendingTasks()
MainThreadExecutor executor;
synchronized( executors )
{
executor = executors.poll();
executor = executors.pollFirst();
}
if( executor == null ) break;
@ -139,12 +157,19 @@ public static void executePendingTasks()
executor.execute();
long taskStop = System.nanoTime();
if( executor.afterExecute( taskStop - taskStart ) )
synchronized( executors )
{
synchronized( executors )
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() )
{
executors.add( executor );
MainThreadExecutor next = executors.first();
if( next.virtualTime < newMinimum ) newMinimum = next.virtualTime;
}
minimumTime = Math.max( minimumTime, newMinimum );
}
if( taskStop >= deadline ) break;
@ -158,6 +183,7 @@ public static void reset()
currentTick = 0;
budget = 0;
canExecute = true;
minimumTime = 0;
lastTaskId.set( 0 );
cooling.clear();
synchronized( executors )

View File

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