diff --git a/src/main/java/dan200/computercraft/core/ComputerContext.java b/src/main/java/dan200/computercraft/core/ComputerContext.java new file mode 100644 index 000000000..134673c22 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/ComputerContext.java @@ -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() + { + } +} diff --git a/src/main/java/dan200/computercraft/core/computer/Computer.java b/src/main/java/dan200/computercraft/core/computer/Computer.java index e897b25b1..d320a8795 100644 --- a/src/main/java/dan200/computercraft/core/computer/Computer.java +++ b/src/main/java/dan200/computercraft/core/computer/Computer.java @@ -7,16 +7,21 @@ package dan200.computercraft.core.computer; import com.google.common.base.Objects; 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.core.ComputerContext; import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.computer.mainthread.MainThreadScheduler; import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.terminal.Terminal; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; /** * 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. * *

*/ public class Computer @@ -39,7 +44,15 @@ public class Computer private final GlobalEnvironment globalEnvironment; private final Terminal terminal; 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. private boolean blinking = false; @@ -49,16 +62,16 @@ public class Computer private boolean startRequested; 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" ); this.id = id; - this.globalEnvironment = globalEnvironment; + globalEnvironment = context.globalEnvironment(); this.terminal = terminal; internalEnvironment = new Environment( this, environment ); - executor = new ComputerExecutor( this, environment ); - serverExecutor = new MainThreadExecutor( environment.getMetrics() ); + executor = new ComputerExecutor( this, environment, context ); + serverExecutor = context.mainThreadScheduler().createExecutor( environment.getMetrics() ); } 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 * @return If the task was successfully queued (namely, whether there is space on it). @@ -204,4 +217,9 @@ public class Computer { executor.addApi( api ); } + + long getUniqueTaskId() + { + return lastTaskId.incrementAndGet(); + } } diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java index 5ee8eebed..aeb2299c5 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java @@ -10,10 +10,10 @@ 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.ComputerContext; 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.lua.MachineEnvironment; 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 * 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 { - static ILuaMachine.Factory luaFactory = CobaltLuaMachine::new; private static final int QUEUE_LIMIT = 256; private final Computer computer; @@ -161,7 +160,9 @@ final class ComputerExecutor */ final AtomicReference 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. ComputerThread.start(); @@ -169,6 +170,7 @@ final class ComputerExecutor this.computer = computer; this.computerEnvironment = computerEnvironment; metrics = computerEnvironment.getMetrics(); + luaFactory = context.luaFactory(); Environment environment = computer.getEnvironment(); diff --git a/src/main/java/dan200/computercraft/core/computer/LuaContext.java b/src/main/java/dan200/computercraft/core/computer/LuaContext.java index e8b45f7ef..7d0f0a538 100644 --- a/src/main/java/dan200/computercraft/core/computer/LuaContext.java +++ b/src/main/java/dan200/computercraft/core/computer/LuaContext.java @@ -25,7 +25,7 @@ class LuaContext implements ILuaContext public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException { // Issue command - final long taskID = MainThread.getUniqueTaskID(); + final long taskID = computer.getUniqueTaskId(); final Runnable iTask = () -> { try { diff --git a/src/main/java/dan200/computercraft/core/computer/MainThread.java b/src/main/java/dan200/computercraft/core/computer/mainthread/MainThread.java similarity index 77% rename from src/main/java/dan200/computercraft/core/computer/MainThread.java rename to src/main/java/dan200/computercraft/core/computer/mainthread/MainThread.java index 222abb274..136473151 100644 --- a/src/main/java/dan200/computercraft/core/computer/MainThread.java +++ b/src/main/java/dan200/computercraft/core/computer/mainthread/MainThread.java @@ -3,43 +3,33 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.core.computer; +package dan200.computercraft.core.computer.mainthread; 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.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 * 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 add {@link ComputerCraft#maxMainGlobalTime} to our budget (clamp it to that value too). If we're still * over budget, then we should not execute any 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. */ - private static final TreeSet executors = new TreeSet<>( ( a, b ) -> { + private final TreeSet 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; @@ -53,7 +43,7 @@ public final class MainThread * @see MainThreadExecutor#tickCooling() * @see #cooling(MainThreadExecutor) */ - private static final HashSet cooling = new HashSet<>(); + private final HashSet cooling = new HashSet<>(); /** * 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() */ - 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. */ - private static long budget; + private 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; + private boolean canExecute = true; - private static long minimumTime = 0; + private long minimumTime = 0; - private MainThread() {} - - public static long getUniqueTaskID() + public MainThread() { - return lastTaskId.incrementAndGet(); } - static void queue( @Nonnull MainThreadExecutor executor, boolean sleeper ) + void queue( MainThreadExecutor executor ) { synchronized( executors ) { @@ -105,27 +92,27 @@ public final class MainThread } } - static void cooling( @Nonnull MainThreadExecutor executor ) + void cooling( MainThreadExecutor executor ) { cooling.add( executor ); } - static void consumeTime( long time ) + void consumeTime( long time ) { budget -= time; } - static boolean canExecute() + boolean canExecute() { return canExecute; } - static int currentTick() + int 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 // 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 ); } - public static void reset() + @Override + public Executor createExecutor( MetricsObserver metrics ) { - currentTick = 0; - budget = 0; - canExecute = true; - minimumTime = 0; - lastTaskId.set( 0 ); - cooling.clear(); - synchronized( executors ) - { - executors.clear(); - } + return new MainThreadExecutor( metrics, this ); } } diff --git a/src/main/java/dan200/computercraft/core/computer/MainThreadExecutor.java b/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadExecutor.java similarity index 80% rename from src/main/java/dan200/computercraft/core/computer/MainThreadExecutor.java rename to src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadExecutor.java index e37e77367..8ab783267 100644 --- a/src/main/java/dan200/computercraft/core/computer/MainThreadExecutor.java +++ b/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadExecutor.java @@ -3,16 +3,14 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.core.computer; +package dan200.computercraft.core.computer.mainthread; import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.peripheral.IWorkMonitor; +import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.metrics.Metrics; 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.Queue; 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 * 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: - * + * those run elsewhere (such as during the turtle's tick). In order to handle this, the executor goes through three + * stages: + *

* 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 * time-frame 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 + *

+ * Then, when other objects (such as block entities) are ticked, we update how much time we've used using * {@link IWorkMonitor#trackWork(long, TimeUnit)}. - * + *

* 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 ComputerCraft#maxMainComputerTime} 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 ComputerCraft#maxMainComputerTime}). 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. - * + * {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is fully + * replenished (is equal to {@link ComputerCraft#maxMainComputerTime}). 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 ComputerCraft#maxMainComputerTime}ns per * 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#queueMainThread(Runnable) */ -final class MainThreadExecutor implements IWorkMonitor +final class MainThreadExecutor implements MainThreadScheduler.Executor { /** * 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. - * + *

* This should be true iff {@link #tasks} is non-empty. * * @see #queueLock @@ -110,9 +108,12 @@ final class MainThreadExecutor implements IWorkMonitor long virtualTime; - MainThreadExecutor( MetricsObserver metrics ) + private final MainThread scheduler; + + MainThreadExecutor( MetricsObserver metrics, MainThread scheduler ) { 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. * @return Whether this task was enqueued (namely, was there space). */ - boolean enqueue( Runnable runnable ) + @Override + public boolean enqueue( Runnable runnable ) { synchronized( queueLock ) { 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; } } @@ -171,17 +173,17 @@ final class MainThreadExecutor implements IWorkMonitor @Override public boolean canWork() { - return state != State.COOLING && MainThread.canExecute(); + return state != State.COOLING && scheduler.canExecute(); } @Override public boolean shouldWork() { - return state == State.COOL && MainThread.canExecute(); + return state == State.COOL && scheduler.canExecute(); } @Override - public void trackWork( long time, @Nonnull TimeUnit unit ) + public void trackWork( long time, TimeUnit unit ) { long nanoTime = unit.toNanos( time ); synchronized( queueLock ) @@ -190,7 +192,7 @@ final class MainThreadExecutor implements IWorkMonitor } consumeTime( nanoTime ); - MainThread.consumeTime( nanoTime ); + scheduler.consumeTime( nanoTime ); } 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 // #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; } @@ -211,7 +213,7 @@ final class MainThreadExecutor implements IWorkMonitor if( budget < 0 && state == State.COOL ) { state = State.HOT; - MainThread.cooling( this ); + scheduler.cooling( this ); } } @@ -223,14 +225,14 @@ final class MainThreadExecutor implements IWorkMonitor boolean tickCooling() { state = State.COOLING; - currentTick = MainThread.currentTick(); + currentTick = scheduler.currentTick(); budget = Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime ); if( budget < ComputerCraft.maxMainComputerTime ) return false; state = State.COOL; synchronized( queueLock ) { - if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this, false ); + if( !tasks.isEmpty() && !onQueue ) scheduler.queue( this ); } return true; } diff --git a/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadScheduler.java b/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadScheduler.java new file mode 100644 index 000000000..36d7baeb5 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadScheduler.java @@ -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 ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/CommonHooks.java b/src/main/java/dan200/computercraft/shared/CommonHooks.java index a9c5547b5..e1cbbdd06 100644 --- a/src/main/java/dan200/computercraft/shared/CommonHooks.java +++ b/src/main/java/dan200/computercraft/shared/CommonHooks.java @@ -7,7 +7,6 @@ package dan200.computercraft.shared; import dan200.computercraft.ComputerCraft; import dan200.computercraft.core.apis.http.NetworkUtils; -import dan200.computercraft.core.computer.MainThread; import dan200.computercraft.core.filesystem.ResourceMount; import dan200.computercraft.shared.command.CommandComputerCraft; import dan200.computercraft.shared.computer.core.ServerContext; @@ -51,8 +50,7 @@ public final class CommonHooks { if( event.phase == TickEvent.Phase.START ) { - MainThread.executePendingTasks(); - ServerContext.get( ServerLifecycleHooks.getCurrentServer() ).registry().update(); + ServerContext.get( ServerLifecycleHooks.getCurrentServer() ).tick(); } } @@ -85,7 +83,6 @@ public final class CommonHooks private static void resetState() { ServerContext.close(); - MainThread.reset(); WirelessNetwork.resetNetworks(); NetworkUtils.reset(); } diff --git a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java index 3344fe541..86e661aa7 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java @@ -57,7 +57,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment terminal = new Terminal( terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged ); metrics = context.metrics().createMetricObserver( this ); - computer = new Computer( context.environment(), this, terminal, computerID ); + computer = new Computer( context.computerContext(), this, terminal, computerID ); computer.setLabel( label ); } diff --git a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputerRegistry.java b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputerRegistry.java index f99cab6ff..8ab52ab5c 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputerRegistry.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputerRegistry.java @@ -43,7 +43,7 @@ public class ServerComputerRegistry return sessionId == this.sessionId ? get( instanceId ) : null; } - public void update() + void update() { Iterator it = getComputers().iterator(); while( it.hasNext() ) diff --git a/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java b/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java index 63f7d3486..4c27d8760 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java @@ -9,7 +9,9 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraftAPIImpl; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.core.ComputerContext; import dan200.computercraft.core.computer.GlobalEnvironment; +import dan200.computercraft.core.computer.mainthread.MainThread; import dan200.computercraft.shared.CommonHooks; import dan200.computercraft.shared.computer.metrics.GlobalMetrics; import dan200.computercraft.shared.util.IDAssigner; @@ -44,7 +46,8 @@ public final class ServerContext private final ServerComputerRegistry registry = new ServerComputerRegistry(); private final GlobalMetrics metrics = new GlobalMetrics(); - private final GlobalEnvironment environment; + private final ComputerContext context; + private final MainThread mainThread; private final IDAssigner idAssigner; private final Path storageDir; @@ -52,7 +55,8 @@ public final class ServerContext { this.server = server; 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" ) ); } @@ -77,6 +81,7 @@ public final class ServerContext if( instance == null ) return; instance.registry.close(); + instance.context.close(); 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 NOT be called outside of {@link CommonHooks}. + */ + public void tick() + { + registry.update(); + mainThread.tick(); } /** diff --git a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java index 4de484dbc..b6533c21f 100644 --- a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java +++ b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java @@ -14,7 +14,7 @@ import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.computer.BasicEnvironment; import dan200.computercraft.core.computer.Computer; 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.FileSystemException; import dan200.computercraft.core.terminal.Terminal; @@ -76,6 +76,7 @@ public class ComputerTestDelegate private static final Pattern KEYWORD = Pattern.compile( ":([a-z_]+)" ); private final ReentrantLock lock = new ReentrantLock(); + private ComputerContext context; private Computer computer; private final Condition hasTests = lock.newCondition(); @@ -113,7 +114,8 @@ public class ComputerTestDelegate } 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.addApi( new CctTestAPI() ); @@ -272,7 +274,6 @@ public class ComputerTestDelegate private void tick() { computer.tick(); - MainThread.executePendingTasks(); } private static String formatName( String name ) diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java b/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java index 3dc253c34..a712eb667 100644 --- a/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java +++ b/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java @@ -11,6 +11,8 @@ import dan200.computercraft.api.lua.IArguments; import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.LuaException; 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.terminal.Terminal; import org.junit.jupiter.api.Assertions; @@ -46,8 +48,10 @@ public class ComputerBootstrap ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE; Terminal term = new Terminal( ComputerCraft.computerTermWidth, ComputerCraft.computerTermHeight, true ); + MainThread mainThread = new MainThread(); 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(); computer.addApi( api ); @@ -64,7 +68,7 @@ public class ComputerBootstrap long start = System.currentTimeMillis(); computer.tick(); - MainThread.executePendingTasks(); + mainThread.tick(); if( api.message != null ) { @@ -102,6 +106,10 @@ public class ComputerBootstrap { Thread.currentThread().interrupt(); } + finally + { + context.close(); + } } public static class AssertApi implements ILuaAPI diff --git a/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java b/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java index a14d4fa9c..adf0ad245 100644 --- a/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java +++ b/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java @@ -6,6 +6,7 @@ package dan200.computercraft.core.computer; import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.core.ComputerContext; import dan200.computercraft.core.lua.ILuaMachine; import dan200.computercraft.core.lua.MachineResult; import dan200.computercraft.core.terminal.Terminal; @@ -35,17 +36,16 @@ public class FakeComputerManager 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> machines = new HashMap<>(); private static final Lock errorLock = new ReentrantLock(); private static final Condition hasError = errorLock.newCondition(); private static volatile Throwable error; - static - { - ComputerExecutor.luaFactory = args -> new DummyLuaMachine( args.timeout ); - } - /** * Create a new computer which pulls from our task queue. * @@ -55,8 +55,7 @@ public class FakeComputerManager @Nonnull public static Computer create() { - BasicEnvironment environment = new BasicEnvironment(); - Computer computer = new Computer( environment, environment, new Terminal( 51, 19, true ), 0 ); + Computer computer = new Computer( context, new BasicEnvironment(), new Terminal( 51, 19, true ), 0 ); ConcurrentLinkedQueue tasks = new ConcurrentLinkedQueue<>(); computer.addApi( new QueuePassingAPI( tasks ) ); machines.put( computer, tasks ); diff --git a/src/test/java/dan200/computercraft/core/computer/FakeMainThreadScheduler.java b/src/test/java/dan200/computercraft/core/computer/FakeMainThreadScheduler.java new file mode 100644 index 000000000..fbb758ab4 --- /dev/null +++ b/src/test/java/dan200/computercraft/core/computer/FakeMainThreadScheduler.java @@ -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 ) + { + } + } +}