mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-15 03:35:42 +00:00
Refactor out main thread tasks into an interface
Computers now use a MainThreadScheduler to construct a MainThreadScheduler.Executor, which is used to submit tasks. Our previous (singleton) MainThread and MainThreadExecutor now implement these interfaces. The main purpose of this is to better manage the lifetime of the server thread tasks. We've had at least one bug caused by us failing to reset its state, so good to avoid those! This also allows us to use a fake implementation in tests where we don't expect main thread tasks to run. As we're now passing a bunch of arguments into our Computer, we bundle the "global" ones into ComputerContext (which now also includes the Lua machine factory!). This definitely isn't the nicest API, so we might want to rethink this one day.
This commit is contained in:
parent
68da044ff2
commit
e9cde9e1bf
77
src/main/java/dan200/computercraft/core/ComputerContext.java
Normal file
77
src/main/java/dan200/computercraft/core/ComputerContext.java
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.core;
|
||||||
|
|
||||||
|
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
|
||||||
|
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||||
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global context under which computers run.
|
||||||
|
*/
|
||||||
|
public final class ComputerContext implements AutoCloseable
|
||||||
|
{
|
||||||
|
private final GlobalEnvironment globalEnvironment;
|
||||||
|
private final MainThreadScheduler mainThreadScheduler;
|
||||||
|
private final ILuaMachine.Factory factory;
|
||||||
|
|
||||||
|
public ComputerContext( GlobalEnvironment globalEnvironment, MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory factory )
|
||||||
|
{
|
||||||
|
this.globalEnvironment = globalEnvironment;
|
||||||
|
this.mainThreadScheduler = mainThreadScheduler;
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a default {@link ComputerContext} with the given global environment.
|
||||||
|
*
|
||||||
|
* @param environment The current global environment.
|
||||||
|
* @param mainThreadScheduler The main thread scheduler to use.
|
||||||
|
*/
|
||||||
|
public ComputerContext( GlobalEnvironment environment, MainThreadScheduler mainThreadScheduler )
|
||||||
|
{
|
||||||
|
this( environment, mainThreadScheduler, CobaltLuaMachine::new );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global environment.
|
||||||
|
*
|
||||||
|
* @return The current global environment.
|
||||||
|
*/
|
||||||
|
public GlobalEnvironment globalEnvironment()
|
||||||
|
{
|
||||||
|
return globalEnvironment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link MainThreadScheduler} instance used to run main-thread tasks.
|
||||||
|
*
|
||||||
|
* @return The current main thread scheduler.
|
||||||
|
*/
|
||||||
|
public MainThreadScheduler mainThreadScheduler()
|
||||||
|
{
|
||||||
|
return mainThreadScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The factory to create new Lua machines.
|
||||||
|
*
|
||||||
|
* @return The current Lua machine factory.
|
||||||
|
*/
|
||||||
|
public ILuaMachine.Factory luaFactory()
|
||||||
|
{
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the current {@link ComputerContext}, disposing of any resources inside.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -7,16 +7,21 @@ package dan200.computercraft.core.computer;
|
|||||||
|
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import dan200.computercraft.api.lua.ILuaAPI;
|
import dan200.computercraft.api.lua.ILuaAPI;
|
||||||
|
import dan200.computercraft.api.lua.ILuaContext;
|
||||||
|
import dan200.computercraft.api.lua.ILuaTask;
|
||||||
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
||||||
|
import dan200.computercraft.core.ComputerContext;
|
||||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
|
||||||
import dan200.computercraft.core.filesystem.FileSystem;
|
import dan200.computercraft.core.filesystem.FileSystem;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a computer which may exist in-world or elsewhere.
|
* Represents a computer which may exist in-world or elsewhere.
|
||||||
*
|
* <p>
|
||||||
* Note, this class has several (read: far, far too many) responsibilities, so can get a little unwieldy at times.
|
* Note, this class has several (read: far, far too many) responsibilities, so can get a little unwieldy at times.
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
@ -24,7 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* <li>Keeps track of whether the computer is on and blinking.</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>Monitors whether the computer's visible state (redstone, on/off/blinking) has changed.</li>
|
||||||
* <li>Passes commands and events to the {@link ComputerExecutor}.</li>
|
* <li>Passes commands and events to the {@link ComputerExecutor}.</li>
|
||||||
* <li>Passes main thread tasks to the {@link MainThreadExecutor}.</li>
|
* <li>Passes main thread tasks to the {@link MainThreadScheduler.Executor}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class Computer
|
public class Computer
|
||||||
@ -39,7 +44,15 @@ public class Computer
|
|||||||
private final GlobalEnvironment globalEnvironment;
|
private final GlobalEnvironment globalEnvironment;
|
||||||
private final Terminal terminal;
|
private final Terminal terminal;
|
||||||
private final ComputerExecutor executor;
|
private final ComputerExecutor executor;
|
||||||
private final MainThreadExecutor serverExecutor;
|
private final MainThreadScheduler.Executor serverExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An internal counter for {@link ILuaTask} ids.
|
||||||
|
*
|
||||||
|
* @see ILuaContext#issueMainThreadTask(ILuaTask)
|
||||||
|
* @see #getUniqueTaskId()
|
||||||
|
*/
|
||||||
|
private final AtomicLong lastTaskId = new AtomicLong();
|
||||||
|
|
||||||
// Additional state about the computer and its environment.
|
// Additional state about the computer and its environment.
|
||||||
private boolean blinking = false;
|
private boolean blinking = false;
|
||||||
@ -49,16 +62,16 @@ public class Computer
|
|||||||
private boolean startRequested;
|
private boolean startRequested;
|
||||||
private int ticksSinceStart = -1;
|
private int ticksSinceStart = -1;
|
||||||
|
|
||||||
public Computer( GlobalEnvironment globalEnvironment, ComputerEnvironment environment, Terminal terminal, int id )
|
public Computer( ComputerContext context, ComputerEnvironment environment, Terminal terminal, int id )
|
||||||
{
|
{
|
||||||
if( id < 0 ) throw new IllegalStateException( "Id has not been assigned" );
|
if( id < 0 ) throw new IllegalStateException( "Id has not been assigned" );
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.globalEnvironment = globalEnvironment;
|
globalEnvironment = context.globalEnvironment();
|
||||||
this.terminal = terminal;
|
this.terminal = terminal;
|
||||||
|
|
||||||
internalEnvironment = new Environment( this, environment );
|
internalEnvironment = new Environment( this, environment );
|
||||||
executor = new ComputerExecutor( this, environment );
|
executor = new ComputerExecutor( this, environment, context );
|
||||||
serverExecutor = new MainThreadExecutor( environment.getMetrics() );
|
serverExecutor = context.mainThreadScheduler().createExecutor( environment.getMetrics() );
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalEnvironment getGlobalEnvironment()
|
GlobalEnvironment getGlobalEnvironment()
|
||||||
@ -117,7 +130,7 @@ public class Computer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue a task to be run on the main thread, using {@link MainThread}.
|
* Queue a task to be run on the main thread, using {@link MainThreadScheduler}.
|
||||||
*
|
*
|
||||||
* @param runnable The task to run
|
* @param runnable The task to run
|
||||||
* @return If the task was successfully queued (namely, whether there is space on it).
|
* @return If the task was successfully queued (namely, whether there is space on it).
|
||||||
@ -204,4 +217,9 @@ public class Computer
|
|||||||
{
|
{
|
||||||
executor.addApi( api );
|
executor.addApi( api );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long getUniqueTaskId()
|
||||||
|
{
|
||||||
|
return lastTaskId.incrementAndGet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import dan200.computercraft.api.filesystem.IMount;
|
|||||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||||
import dan200.computercraft.api.lua.ILuaAPI;
|
import dan200.computercraft.api.lua.ILuaAPI;
|
||||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||||
|
import dan200.computercraft.core.ComputerContext;
|
||||||
import dan200.computercraft.core.apis.*;
|
import dan200.computercraft.core.apis.*;
|
||||||
import dan200.computercraft.core.filesystem.FileSystem;
|
import dan200.computercraft.core.filesystem.FileSystem;
|
||||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
|
||||||
import dan200.computercraft.core.lua.ILuaMachine;
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
import dan200.computercraft.core.lua.MachineEnvironment;
|
import dan200.computercraft.core.lua.MachineEnvironment;
|
||||||
import dan200.computercraft.core.lua.MachineResult;
|
import dan200.computercraft.core.lua.MachineResult;
|
||||||
@ -36,26 +36,25 @@ 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
|
* The main task queue and executor for a single computer. This handles turning on and off a computer, as well as
|
||||||
* running events.
|
* running events.
|
||||||
*
|
* <p>
|
||||||
* When the computer is instructed to turn on or off, or handle an event, we queue a task and register this to be
|
* 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
|
* 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.
|
* cannot lock on anything which may be held for a long time.
|
||||||
*
|
* <p>
|
||||||
* The executor is effectively composed of two separate queues. Firstly, we have a "single element" queue
|
* 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 #command} which determines which state the computer should transition too. This is set by
|
||||||
* {@link #queueStart()} and {@link #queueStop(boolean, boolean)}.
|
* {@link #queueStart()} and {@link #queueStop(boolean, boolean)}.
|
||||||
*
|
* <p>
|
||||||
* When a computer is on, we simply push any events onto to the {@link #eventQueue}.
|
* When a computer is on, we simply push any events onto to the {@link #eventQueue}.
|
||||||
*
|
* <p>
|
||||||
* Both queues are run from the {@link #work()} method, which tries to execute a command if one exists, or resumes the
|
* 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.
|
* machine with an event otherwise.
|
||||||
*
|
* <p>
|
||||||
* One final responsibility for the executor is calling {@link ILuaAPI#update()} every tick, via the {@link #tick()}
|
* 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}).
|
* method. This should only be called when the computer is actually on ({@link #isOn}).
|
||||||
*/
|
*/
|
||||||
final class ComputerExecutor
|
final class ComputerExecutor
|
||||||
{
|
{
|
||||||
static ILuaMachine.Factory luaFactory = CobaltLuaMachine::new;
|
|
||||||
private static final int QUEUE_LIMIT = 256;
|
private static final int QUEUE_LIMIT = 256;
|
||||||
|
|
||||||
private final Computer computer;
|
private final Computer computer;
|
||||||
@ -161,7 +160,9 @@ final class ComputerExecutor
|
|||||||
*/
|
*/
|
||||||
final AtomicReference<Thread> executingThread = new AtomicReference<>();
|
final AtomicReference<Thread> executingThread = new AtomicReference<>();
|
||||||
|
|
||||||
ComputerExecutor( Computer computer, ComputerEnvironment computerEnvironment )
|
private final ILuaMachine.Factory luaFactory;
|
||||||
|
|
||||||
|
ComputerExecutor( Computer computer, ComputerEnvironment computerEnvironment, ComputerContext context )
|
||||||
{
|
{
|
||||||
// Ensure the computer thread is running as required.
|
// Ensure the computer thread is running as required.
|
||||||
ComputerThread.start();
|
ComputerThread.start();
|
||||||
@ -169,6 +170,7 @@ final class ComputerExecutor
|
|||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.computerEnvironment = computerEnvironment;
|
this.computerEnvironment = computerEnvironment;
|
||||||
metrics = computerEnvironment.getMetrics();
|
metrics = computerEnvironment.getMetrics();
|
||||||
|
luaFactory = context.luaFactory();
|
||||||
|
|
||||||
Environment environment = computer.getEnvironment();
|
Environment environment = computer.getEnvironment();
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class LuaContext implements ILuaContext
|
|||||||
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
|
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
|
||||||
{
|
{
|
||||||
// Issue command
|
// Issue command
|
||||||
final long taskID = MainThread.getUniqueTaskID();
|
final long taskID = computer.getUniqueTaskId();
|
||||||
final Runnable iTask = () -> {
|
final Runnable iTask = () -> {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -3,43 +3,33 @@
|
|||||||
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||||
* Send enquiries to dratcliffe@gmail.com
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
*/
|
*/
|
||||||
package dan200.computercraft.core.computer;
|
package dan200.computercraft.core.computer.mainthread;
|
||||||
|
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
import dan200.computercraft.api.lua.ILuaTask;
|
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
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
|
* Runs tasks on the main (server) thread, ticks {@link MainThreadExecutor}s, and limits how much time is used this
|
||||||
* tick.
|
* tick.
|
||||||
*
|
* <p>
|
||||||
* Similar to {@link MainThreadExecutor}, the {@link MainThread} can be in one of three states: cool, hot and cooling.
|
* 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:
|
* However, the implementation here is a little different:
|
||||||
*
|
* <p>
|
||||||
* {@link MainThread} starts cool, and runs as many tasks as it can in the current {@link #budget}ns. Any external tasks
|
* {@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
|
* (those run by tile entities, etc...) will also consume the budget
|
||||||
*
|
* <p>
|
||||||
* Next tick, we add {@link ComputerCraft#maxMainGlobalTime} to our budget (clamp it to that value too). If we're still
|
* Next tick, we add {@link ComputerCraft#maxMainGlobalTime} to our budget (clamp it to that value too). If we're still
|
||||||
* over budget, then we should not execute <em>any</em> work (either as part of {@link MainThread} or externally).
|
* over budget, then we should not execute <em>any</em> work (either as part of {@link MainThread} or externally).
|
||||||
*/
|
*/
|
||||||
public final class MainThread
|
public final class MainThread implements MainThreadScheduler
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* 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.
|
* The queue of {@link MainThreadExecutor}s with tasks to perform.
|
||||||
*/
|
*/
|
||||||
private static final TreeSet<MainThreadExecutor> executors = new TreeSet<>( ( a, b ) -> {
|
private final TreeSet<MainThreadExecutor> executors = new TreeSet<>( ( a, b ) -> {
|
||||||
if( a == b ) return 0; // Should never happen, but let's be consistent here
|
if( a == b ) return 0; // Should never happen, but let's be consistent here
|
||||||
|
|
||||||
long at = a.virtualTime, bt = b.virtualTime;
|
long at = a.virtualTime, bt = b.virtualTime;
|
||||||
@ -53,7 +43,7 @@ public final class MainThread
|
|||||||
* @see MainThreadExecutor#tickCooling()
|
* @see MainThreadExecutor#tickCooling()
|
||||||
* @see #cooling(MainThreadExecutor)
|
* @see #cooling(MainThreadExecutor)
|
||||||
*/
|
*/
|
||||||
private static final HashSet<MainThreadExecutor> cooling = new HashSet<>();
|
private final HashSet<MainThreadExecutor> cooling = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current tick number. This is used by {@link MainThreadExecutor} to determine when to reset its own time
|
* The current tick number. This is used by {@link MainThreadExecutor} to determine when to reset its own time
|
||||||
@ -61,30 +51,27 @@ public final class MainThread
|
|||||||
*
|
*
|
||||||
* @see #currentTick()
|
* @see #currentTick()
|
||||||
*/
|
*/
|
||||||
private static int currentTick;
|
private int currentTick;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
|
* The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
|
||||||
*/
|
*/
|
||||||
private static long budget;
|
private long budget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether we should be executing any work this tick.
|
* Whether we should be executing any work this tick.
|
||||||
*
|
* <p>
|
||||||
* This is true iff {@code MAX_TICK_TIME - currentTime} was true <em>at the beginning of the tick</em>.
|
* This is true iff {@code MAX_TICK_TIME - currentTime} was true <em>at the beginning of the tick</em>.
|
||||||
*/
|
*/
|
||||||
private static boolean canExecute = true;
|
private boolean canExecute = true;
|
||||||
|
|
||||||
private static long minimumTime = 0;
|
private long minimumTime = 0;
|
||||||
|
|
||||||
private MainThread() {}
|
public MainThread()
|
||||||
|
|
||||||
public static long getUniqueTaskID()
|
|
||||||
{
|
{
|
||||||
return lastTaskId.incrementAndGet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void queue( @Nonnull MainThreadExecutor executor, boolean sleeper )
|
void queue( MainThreadExecutor executor )
|
||||||
{
|
{
|
||||||
synchronized( executors )
|
synchronized( executors )
|
||||||
{
|
{
|
||||||
@ -105,27 +92,27 @@ public final class MainThread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cooling( @Nonnull MainThreadExecutor executor )
|
void cooling( MainThreadExecutor executor )
|
||||||
{
|
{
|
||||||
cooling.add( executor );
|
cooling.add( executor );
|
||||||
}
|
}
|
||||||
|
|
||||||
static void consumeTime( long time )
|
void consumeTime( long time )
|
||||||
{
|
{
|
||||||
budget -= time;
|
budget -= time;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean canExecute()
|
boolean canExecute()
|
||||||
{
|
{
|
||||||
return canExecute;
|
return canExecute;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int currentTick()
|
int currentTick()
|
||||||
{
|
{
|
||||||
return currentTick;
|
return currentTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void executePendingTasks()
|
public void tick()
|
||||||
{
|
{
|
||||||
// Move onto the next tick and cool down the global executor. We're allowed to execute if we have _any_ time
|
// 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.
|
// allocated for this tick. This means we'll stick much closer to doing MAX_TICK_TIME work every tick.
|
||||||
@ -178,17 +165,9 @@ public final class MainThread
|
|||||||
consumeTime( System.nanoTime() - start );
|
consumeTime( System.nanoTime() - start );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reset()
|
@Override
|
||||||
|
public Executor createExecutor( MetricsObserver metrics )
|
||||||
{
|
{
|
||||||
currentTick = 0;
|
return new MainThreadExecutor( metrics, this );
|
||||||
budget = 0;
|
|
||||||
canExecute = true;
|
|
||||||
minimumTime = 0;
|
|
||||||
lastTaskId.set( 0 );
|
|
||||||
cooling.clear();
|
|
||||||
synchronized( executors )
|
|
||||||
{
|
|
||||||
executors.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,16 +3,14 @@
|
|||||||
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||||
* Send enquiries to dratcliffe@gmail.com
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
*/
|
*/
|
||||||
package dan200.computercraft.core.computer;
|
package dan200.computercraft.core.computer.mainthread;
|
||||||
|
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
||||||
|
import dan200.computercraft.core.computer.Computer;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
import dan200.computercraft.core.metrics.Metrics;
|
||||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||||
import dan200.computercraft.shared.turtle.core.TurtleBrain;
|
|
||||||
import net.minecraft.tileentity.TileEntity;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -20,28 +18,28 @@ import java.util.concurrent.TimeUnit;
|
|||||||
/**
|
/**
|
||||||
* Keeps track of tasks that a {@link Computer} should run on the main thread and how long that has been spent executing
|
* Keeps track of tasks that a {@link Computer} should run on the main thread and how long that has been spent executing
|
||||||
* them.
|
* them.
|
||||||
*
|
* <p>
|
||||||
* This provides rate-limiting mechanism for tasks enqueued with {@link Computer#queueMainThread(Runnable)}, but also
|
* 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,
|
* those run elsewhere (such as during the turtle's tick). In order to handle this, the executor goes through three
|
||||||
* the executor goes through three stages:
|
* stages:
|
||||||
*
|
* <p>
|
||||||
* When {@link State#COOL}, the computer is allocated {@link ComputerCraft#maxMainComputerTime}ns to execute any work
|
* When {@link State#COOL}, the computer is allocated {@link ComputerCraft#maxMainComputerTime}ns to execute any work
|
||||||
* this tick. At the beginning of the tick, we execute as many {@link MainThread} tasks as possible, until our
|
* this tick. At the beginning of the tick, we execute as many {@link MainThread} tasks as possible, until our
|
||||||
* time-frame or the global time frame has expired.
|
* time-frame or the global time frame has expired.
|
||||||
*
|
* <p>
|
||||||
* Then, when other objects (such as {@link TileEntity}) are ticked, we update how much time we've used using
|
* Then, when other objects (such as block entities) are ticked, we update how much time we've used using
|
||||||
* {@link IWorkMonitor#trackWork(long, TimeUnit)}.
|
* {@link IWorkMonitor#trackWork(long, TimeUnit)}.
|
||||||
*
|
* <p>
|
||||||
* Now, if anywhere during this period, we use more than our allocated time slice, the executor is marked as
|
* 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
|
* {@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).
|
* execute tile entity tasks, in order to prevent the main thread from exhausting work every tick).
|
||||||
*
|
* <p>
|
||||||
* At the beginning of the next tick, we increment the budget e by {@link ComputerCraft#maxMainComputerTime} and any
|
* At the beginning of the next tick, we increment the budget e by {@link ComputerCraft#maxMainComputerTime} and any
|
||||||
* {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is
|
* {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is fully
|
||||||
* fully replenished (is equal to {@link ComputerCraft#maxMainComputerTime}). Note, this is different to
|
* replenished (is equal to {@link ComputerCraft#maxMainComputerTime}). Note, this is different to {@link MainThread},
|
||||||
* {@link MainThread}, which allows running when it has any budget left. When cooling, <em>no</em> tasks are executed -
|
* which allows running when it has any budget left. When cooling, <em>no</em> tasks are executed - be they on the tile
|
||||||
* be they on the tile entity or main thread.
|
* entity or main thread.
|
||||||
*
|
* <p>
|
||||||
* This mechanism means that, on average, computers will use at most {@link ComputerCraft#maxMainComputerTime}ns per
|
* This mechanism means that, on average, computers will use at most {@link ComputerCraft#maxMainComputerTime}ns per
|
||||||
* second, but one task source will not prevent others from executing.
|
* second, but one task source will not prevent others from executing.
|
||||||
*
|
*
|
||||||
@ -50,7 +48,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @see Computer#getMainThreadMonitor()
|
* @see Computer#getMainThreadMonitor()
|
||||||
* @see Computer#queueMainThread(Runnable)
|
* @see Computer#queueMainThread(Runnable)
|
||||||
*/
|
*/
|
||||||
final class MainThreadExecutor implements IWorkMonitor
|
final class MainThreadExecutor implements MainThreadScheduler.Executor
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The maximum number of {@link MainThread} tasks allowed on the queue.
|
* The maximum number of {@link MainThread} tasks allowed on the queue.
|
||||||
@ -74,7 +72,7 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if this executor is currently present on the queue.
|
* Determines if this executor is currently present on the queue.
|
||||||
*
|
* <p>
|
||||||
* This should be true iff {@link #tasks} is non-empty.
|
* This should be true iff {@link #tasks} is non-empty.
|
||||||
*
|
*
|
||||||
* @see #queueLock
|
* @see #queueLock
|
||||||
@ -110,9 +108,12 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
|
|
||||||
long virtualTime;
|
long virtualTime;
|
||||||
|
|
||||||
MainThreadExecutor( MetricsObserver metrics )
|
private final MainThread scheduler;
|
||||||
|
|
||||||
|
MainThreadExecutor( MetricsObserver metrics, MainThread scheduler )
|
||||||
{
|
{
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
|
this.scheduler = scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,12 +122,13 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
* @param runnable The task to run on the main thread.
|
* @param runnable The task to run on the main thread.
|
||||||
* @return Whether this task was enqueued (namely, was there space).
|
* @return Whether this task was enqueued (namely, was there space).
|
||||||
*/
|
*/
|
||||||
boolean enqueue( Runnable runnable )
|
@Override
|
||||||
|
public boolean enqueue( Runnable runnable )
|
||||||
{
|
{
|
||||||
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, true );
|
if( !onQueue && state == State.COOL ) scheduler.queue( this );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,17 +173,17 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
@Override
|
@Override
|
||||||
public boolean canWork()
|
public boolean canWork()
|
||||||
{
|
{
|
||||||
return state != State.COOLING && MainThread.canExecute();
|
return state != State.COOLING && scheduler.canExecute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldWork()
|
public boolean shouldWork()
|
||||||
{
|
{
|
||||||
return state == State.COOL && MainThread.canExecute();
|
return state == State.COOL && scheduler.canExecute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void trackWork( long time, @Nonnull TimeUnit unit )
|
public void trackWork( long time, TimeUnit unit )
|
||||||
{
|
{
|
||||||
long nanoTime = unit.toNanos( time );
|
long nanoTime = unit.toNanos( time );
|
||||||
synchronized( queueLock )
|
synchronized( queueLock )
|
||||||
@ -190,7 +192,7 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
}
|
}
|
||||||
|
|
||||||
consumeTime( nanoTime );
|
consumeTime( nanoTime );
|
||||||
MainThread.consumeTime( nanoTime );
|
scheduler.consumeTime( nanoTime );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void consumeTime( long time )
|
private void consumeTime( long time )
|
||||||
@ -199,9 +201,9 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
|
|
||||||
// Reset the budget if moving onto a new tick. We know this is safe, as this will only have happened if
|
// 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.
|
// #tickCooling() isn't called, and so we didn't overrun the previous tick.
|
||||||
if( currentTick != MainThread.currentTick() )
|
if( currentTick != scheduler.currentTick() )
|
||||||
{
|
{
|
||||||
currentTick = MainThread.currentTick();
|
currentTick = scheduler.currentTick();
|
||||||
budget = ComputerCraft.maxMainComputerTime;
|
budget = ComputerCraft.maxMainComputerTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +213,7 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
if( budget < 0 && state == State.COOL )
|
if( budget < 0 && state == State.COOL )
|
||||||
{
|
{
|
||||||
state = State.HOT;
|
state = State.HOT;
|
||||||
MainThread.cooling( this );
|
scheduler.cooling( this );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,14 +225,14 @@ final class MainThreadExecutor implements IWorkMonitor
|
|||||||
boolean tickCooling()
|
boolean tickCooling()
|
||||||
{
|
{
|
||||||
state = State.COOLING;
|
state = State.COOLING;
|
||||||
currentTick = MainThread.currentTick();
|
currentTick = scheduler.currentTick();
|
||||||
budget = Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime );
|
budget = Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime );
|
||||||
if( budget < ComputerCraft.maxMainComputerTime ) return false;
|
if( budget < ComputerCraft.maxMainComputerTime ) return false;
|
||||||
|
|
||||||
state = State.COOL;
|
state = State.COOL;
|
||||||
synchronized( queueLock )
|
synchronized( queueLock )
|
||||||
{
|
{
|
||||||
if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this, false );
|
if( !tasks.isEmpty() && !onQueue ) scheduler.queue( this );
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.core.computer.mainthread;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
||||||
|
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||||
|
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link MainThreadScheduler} is responsible for running work on the main thread, for instance the server thread in
|
||||||
|
* Minecraft.
|
||||||
|
*
|
||||||
|
* @see MainThread is the default implementation
|
||||||
|
*/
|
||||||
|
public interface MainThreadScheduler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create an executor for a computer. This should only be called once for a single computer.
|
||||||
|
*
|
||||||
|
* @param observer A sink for metrics, used to monitor task timings.
|
||||||
|
* @return The executor for this computer.
|
||||||
|
*/
|
||||||
|
Executor createExecutor( MetricsObserver observer );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link Executor} is responsible for managing scheduled tasks for a single computer.
|
||||||
|
*/
|
||||||
|
interface Executor extends IWorkMonitor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Schedule a task to be run on the main thread. This can be called from any thread.
|
||||||
|
*
|
||||||
|
* @param task The task to schedule.
|
||||||
|
* @return The task ID if the task could be scheduled, or {@link OptionalLong#empty()} if the task failed to
|
||||||
|
* be scheduled.
|
||||||
|
*/
|
||||||
|
boolean enqueue( Runnable task );
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ package dan200.computercraft.shared;
|
|||||||
|
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||||
import dan200.computercraft.core.computer.MainThread;
|
|
||||||
import dan200.computercraft.core.filesystem.ResourceMount;
|
import dan200.computercraft.core.filesystem.ResourceMount;
|
||||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
@ -51,8 +50,7 @@ public final class CommonHooks
|
|||||||
{
|
{
|
||||||
if( event.phase == TickEvent.Phase.START )
|
if( event.phase == TickEvent.Phase.START )
|
||||||
{
|
{
|
||||||
MainThread.executePendingTasks();
|
ServerContext.get( ServerLifecycleHooks.getCurrentServer() ).tick();
|
||||||
ServerContext.get( ServerLifecycleHooks.getCurrentServer() ).registry().update();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +83,6 @@ public final class CommonHooks
|
|||||||
private static void resetState()
|
private static void resetState()
|
||||||
{
|
{
|
||||||
ServerContext.close();
|
ServerContext.close();
|
||||||
MainThread.reset();
|
|
||||||
WirelessNetwork.resetNetworks();
|
WirelessNetwork.resetNetworks();
|
||||||
NetworkUtils.reset();
|
NetworkUtils.reset();
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment
|
|||||||
terminal = new Terminal( terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged );
|
terminal = new Terminal( terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged );
|
||||||
metrics = context.metrics().createMetricObserver( this );
|
metrics = context.metrics().createMetricObserver( this );
|
||||||
|
|
||||||
computer = new Computer( context.environment(), this, terminal, computerID );
|
computer = new Computer( context.computerContext(), this, terminal, computerID );
|
||||||
computer.setLabel( label );
|
computer.setLabel( label );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class ServerComputerRegistry
|
|||||||
return sessionId == this.sessionId ? get( instanceId ) : null;
|
return sessionId == this.sessionId ? get( instanceId ) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update()
|
void update()
|
||||||
{
|
{
|
||||||
Iterator<ServerComputer> it = getComputers().iterator();
|
Iterator<ServerComputer> it = getComputers().iterator();
|
||||||
while( it.hasNext() )
|
while( it.hasNext() )
|
||||||
|
@ -9,7 +9,9 @@ import dan200.computercraft.ComputerCraft;
|
|||||||
import dan200.computercraft.ComputerCraftAPIImpl;
|
import dan200.computercraft.ComputerCraftAPIImpl;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.filesystem.IMount;
|
import dan200.computercraft.api.filesystem.IMount;
|
||||||
|
import dan200.computercraft.core.ComputerContext;
|
||||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThread;
|
||||||
import dan200.computercraft.shared.CommonHooks;
|
import dan200.computercraft.shared.CommonHooks;
|
||||||
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
||||||
import dan200.computercraft.shared.util.IDAssigner;
|
import dan200.computercraft.shared.util.IDAssigner;
|
||||||
@ -44,7 +46,8 @@ public final class ServerContext
|
|||||||
|
|
||||||
private final ServerComputerRegistry registry = new ServerComputerRegistry();
|
private final ServerComputerRegistry registry = new ServerComputerRegistry();
|
||||||
private final GlobalMetrics metrics = new GlobalMetrics();
|
private final GlobalMetrics metrics = new GlobalMetrics();
|
||||||
private final GlobalEnvironment environment;
|
private final ComputerContext context;
|
||||||
|
private final MainThread mainThread;
|
||||||
private final IDAssigner idAssigner;
|
private final IDAssigner idAssigner;
|
||||||
private final Path storageDir;
|
private final Path storageDir;
|
||||||
|
|
||||||
@ -52,7 +55,8 @@ public final class ServerContext
|
|||||||
{
|
{
|
||||||
this.server = server;
|
this.server = server;
|
||||||
storageDir = server.getWorldPath( FOLDER );
|
storageDir = server.getWorldPath( FOLDER );
|
||||||
environment = new Environment( server );
|
mainThread = new MainThread();
|
||||||
|
context = new ComputerContext( new Environment( server ), mainThread );
|
||||||
idAssigner = new IDAssigner( storageDir.resolve( "ids.json" ) );
|
idAssigner = new IDAssigner( storageDir.resolve( "ids.json" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +81,7 @@ public final class ServerContext
|
|||||||
if( instance == null ) return;
|
if( instance == null ) return;
|
||||||
|
|
||||||
instance.registry.close();
|
instance.registry.close();
|
||||||
|
instance.context.close();
|
||||||
|
|
||||||
ServerContext.instance = null;
|
ServerContext.instance = null;
|
||||||
}
|
}
|
||||||
@ -102,13 +107,22 @@ public final class ServerContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current {@link GlobalEnvironment} computers should run under.
|
* Get the current {@link ComputerContext} computers should run under.
|
||||||
*
|
*
|
||||||
* @return The current {@link GlobalEnvironment}.
|
* @return The current {@link ComputerContext}.
|
||||||
*/
|
*/
|
||||||
GlobalEnvironment environment()
|
ComputerContext computerContext()
|
||||||
{
|
{
|
||||||
return environment;
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
|
||||||
|
*/
|
||||||
|
public void tick()
|
||||||
|
{
|
||||||
|
registry.update();
|
||||||
|
mainThread.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
|||||||
import dan200.computercraft.core.computer.BasicEnvironment;
|
import dan200.computercraft.core.computer.BasicEnvironment;
|
||||||
import dan200.computercraft.core.computer.Computer;
|
import dan200.computercraft.core.computer.Computer;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.core.computer.MainThread;
|
import dan200.computercraft.core.computer.FakeMainThreadScheduler;
|
||||||
import dan200.computercraft.core.filesystem.FileMount;
|
import dan200.computercraft.core.filesystem.FileMount;
|
||||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
@ -76,6 +76,7 @@ public class ComputerTestDelegate
|
|||||||
private static final Pattern KEYWORD = Pattern.compile( ":([a-z_]+)" );
|
private static final Pattern KEYWORD = Pattern.compile( ":([a-z_]+)" );
|
||||||
|
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
private ComputerContext context;
|
||||||
private Computer computer;
|
private Computer computer;
|
||||||
|
|
||||||
private final Condition hasTests = lock.newCondition();
|
private final Condition hasTests = lock.newCondition();
|
||||||
@ -113,7 +114,8 @@ public class ComputerTestDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
BasicEnvironment environment = new BasicEnvironment( mount );
|
BasicEnvironment environment = new BasicEnvironment( mount );
|
||||||
computer = new Computer( environment, environment, term, 0 );
|
context = new ComputerContext( environment, new FakeMainThreadScheduler() );
|
||||||
|
computer = new Computer( context, environment, term, 0 );
|
||||||
computer.getEnvironment().setPeripheral( ComputerSide.TOP, new FakeModem() );
|
computer.getEnvironment().setPeripheral( ComputerSide.TOP, new FakeModem() );
|
||||||
computer.addApi( new CctTestAPI() );
|
computer.addApi( new CctTestAPI() );
|
||||||
|
|
||||||
@ -272,7 +274,6 @@ public class ComputerTestDelegate
|
|||||||
private void tick()
|
private void tick()
|
||||||
{
|
{
|
||||||
computer.tick();
|
computer.tick();
|
||||||
MainThread.executePendingTasks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatName( String name )
|
private static String formatName( String name )
|
||||||
|
@ -11,6 +11,8 @@ import dan200.computercraft.api.lua.IArguments;
|
|||||||
import dan200.computercraft.api.lua.ILuaAPI;
|
import dan200.computercraft.api.lua.ILuaAPI;
|
||||||
import dan200.computercraft.api.lua.LuaException;
|
import dan200.computercraft.api.lua.LuaException;
|
||||||
import dan200.computercraft.api.lua.LuaFunction;
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
|
import dan200.computercraft.core.ComputerContext;
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThread;
|
||||||
import dan200.computercraft.core.filesystem.MemoryMount;
|
import dan200.computercraft.core.filesystem.MemoryMount;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
@ -46,8 +48,10 @@ public class ComputerBootstrap
|
|||||||
ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE;
|
ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE;
|
||||||
|
|
||||||
Terminal term = new Terminal( ComputerCraft.computerTermWidth, ComputerCraft.computerTermHeight, true );
|
Terminal term = new Terminal( ComputerCraft.computerTermWidth, ComputerCraft.computerTermHeight, true );
|
||||||
|
MainThread mainThread = new MainThread();
|
||||||
BasicEnvironment environment = new BasicEnvironment( mount );
|
BasicEnvironment environment = new BasicEnvironment( mount );
|
||||||
final Computer computer = new Computer( environment, environment, term, 0 );
|
ComputerContext context = new ComputerContext( environment, mainThread );
|
||||||
|
final Computer computer = new Computer( context, environment, term, 0 );
|
||||||
|
|
||||||
AssertApi api = new AssertApi();
|
AssertApi api = new AssertApi();
|
||||||
computer.addApi( api );
|
computer.addApi( api );
|
||||||
@ -64,7 +68,7 @@ public class ComputerBootstrap
|
|||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
computer.tick();
|
computer.tick();
|
||||||
MainThread.executePendingTasks();
|
mainThread.tick();
|
||||||
|
|
||||||
if( api.message != null )
|
if( api.message != null )
|
||||||
{
|
{
|
||||||
@ -102,6 +106,10 @@ public class ComputerBootstrap
|
|||||||
{
|
{
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AssertApi implements ILuaAPI
|
public static class AssertApi implements ILuaAPI
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package dan200.computercraft.core.computer;
|
package dan200.computercraft.core.computer;
|
||||||
|
|
||||||
import dan200.computercraft.api.lua.ILuaAPI;
|
import dan200.computercraft.api.lua.ILuaAPI;
|
||||||
|
import dan200.computercraft.core.ComputerContext;
|
||||||
import dan200.computercraft.core.lua.ILuaMachine;
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
import dan200.computercraft.core.lua.MachineResult;
|
import dan200.computercraft.core.lua.MachineResult;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
@ -35,17 +36,16 @@ public class FakeComputerManager
|
|||||||
MachineResult run( TimeoutState state ) throws Exception;
|
MachineResult run( TimeoutState state ) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final ComputerContext context = new ComputerContext(
|
||||||
|
new BasicEnvironment(), new FakeMainThreadScheduler(),
|
||||||
|
args -> new DummyLuaMachine( args.timeout )
|
||||||
|
);
|
||||||
private static final Map<Computer, Queue<Task>> machines = new HashMap<>();
|
private static final Map<Computer, Queue<Task>> machines = new HashMap<>();
|
||||||
|
|
||||||
private static final Lock errorLock = new ReentrantLock();
|
private static final Lock errorLock = new ReentrantLock();
|
||||||
private static final Condition hasError = errorLock.newCondition();
|
private static final Condition hasError = errorLock.newCondition();
|
||||||
private static volatile Throwable error;
|
private static volatile Throwable error;
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
ComputerExecutor.luaFactory = args -> new DummyLuaMachine( args.timeout );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new computer which pulls from our task queue.
|
* Create a new computer which pulls from our task queue.
|
||||||
*
|
*
|
||||||
@ -55,8 +55,7 @@ public class FakeComputerManager
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
public static Computer create()
|
public static Computer create()
|
||||||
{
|
{
|
||||||
BasicEnvironment environment = new BasicEnvironment();
|
Computer computer = new Computer( context, new BasicEnvironment(), new Terminal( 51, 19, true ), 0 );
|
||||||
Computer computer = new Computer( environment, environment, new Terminal( 51, 19, true ), 0 );
|
|
||||||
ConcurrentLinkedQueue<Task> tasks = new ConcurrentLinkedQueue<>();
|
ConcurrentLinkedQueue<Task> tasks = new ConcurrentLinkedQueue<>();
|
||||||
computer.addApi( new QueuePassingAPI( tasks ) );
|
computer.addApi( new QueuePassingAPI( tasks ) );
|
||||||
machines.put( computer, tasks );
|
machines.put( computer, tasks );
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.core.computer;
|
||||||
|
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
|
||||||
|
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link MainThreadScheduler} which fails when a computer tries to enqueue work.
|
||||||
|
*/
|
||||||
|
public class FakeMainThreadScheduler implements MainThreadScheduler
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Executor createExecutor( MetricsObserver observer )
|
||||||
|
{
|
||||||
|
return new ExecutorImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ExecutorImpl implements Executor
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean enqueue( Runnable task )
|
||||||
|
{
|
||||||
|
throw new IllegalStateException( "Cannot schedule tasks" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canWork()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldWork()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trackWork( long time, @Nonnull TimeUnit unit )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user