1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-26 07:03:22 +00:00

Merge pull request #153 from SquidDev-CC/feature/main-thread-limits

This commit is contained in:
SquidDev 2019-03-26 11:28:35 +00:00 committed by GitHub
commit a600213b00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 731 additions and 238 deletions

View File

@ -24,6 +24,7 @@
import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.apis.http.websocket.Websocket;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
@ -95,6 +96,7 @@
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@ -133,9 +135,12 @@ public class ComputerCraft
public static boolean disable_lua51_features = false;
public static String default_computer_settings = "";
public static boolean debug_enable = true;
public static int computer_threads = 1;
public static boolean logPeripheralErrors = false;
public static int computer_threads = 1;
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
public static long maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( 5 );
public static boolean http_enable = true;
public static boolean http_websocket_enable = true;
public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST );
@ -304,6 +309,7 @@ public void onServerStart( FMLServerStartedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
MainThread.reset();
Tracking.reset();
}
}
@ -315,6 +321,7 @@ public void onServerStopped( FMLServerStoppedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
MainThread.reset();
Tracking.reset();
}
}

View File

@ -9,6 +9,8 @@
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
@ -179,7 +181,7 @@ default Map<String, IPeripheral> getAvailablePeripherals()
}
/**
* Get a reachable peripheral with the given attachement name. This is a equivalent to
* Get a reachable peripheral with the given attachment name. This is a equivalent to
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more performant.
*
* @param name The peripheral's attached name
@ -191,4 +193,23 @@ default IPeripheral getAvailablePeripheral( @Nonnull String name )
{
return null;
}
/**
* Get a {@link IWorkMonitor} for tasks your peripheral might execute on the main (server) thread.
*
* This should be used to ensure your peripheral integrates with ComputerCraft's monitoring and limiting of how much
* server time each computer consumes. You should not need to use this if you use
* {@link ILuaContext#issueMainThreadTask(ILuaTask)} - this is intended for mods with their own system for running
* work on the main thread.
*
* Please note that the returned implementation is <em>not</em> thread-safe, and should only be used from the main
* thread.
*
* @return The work monitor for the main thread, or {@code null} if this computer does not have one.
*/
@Nullable
default IWorkMonitor getMainThreadMonitor()
{
return null;
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.peripheral;
import javax.annotation.Nonnull;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* Monitors "work" associated with a computer, keeping track of how much a computer has done, and ensuring every
* computer receives a fair share of any processing time.
*
* This is primarily intended for work done by peripherals on the main thread (such as on a tile entity's tick), but
* could be used for other purposes (such as complex computations done on another thread).
*
* Before running a task, one should call {@link #canWork()} to determine if the computer is currently allowed to
* execute work. If that returns true, you should execute the task and use {@link #trackWork(long, TimeUnit)} to inform
* the monitor how long that task took.
*
* Alternatively, use {@link #runWork(Runnable)} to run and keep track of work.
*
* @see IComputerAccess#getMainThreadMonitor()
*/
public interface IWorkMonitor
{
/**
* If the owning computer is currently allowed to execute work.
*
* @return If we can execute work right now.
*/
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.
*
* @param time The time some task took to run
* @param unit The unit that {@code time} was measured in.
*/
void trackWork( long time, @Nonnull TimeUnit unit );
/**
* Run a task if possible, and inform the monitor of how long it took.
*
* @param runnable The task to run.
* @return If the task was actually run (namely, {@link #canWork()} returned {@code true}).
*/
default boolean runWork( @Nonnull Runnable runnable )
{
Objects.requireNonNull( runnable, "runnable should not be null" );
if( !canWork() ) return false;
long start = System.nanoTime();
try
{
runnable.run();
}
finally
{
trackWork( System.nanoTime() - start, TimeUnit.NANOSECONDS );
}
return true;
}
}

View File

@ -10,8 +10,7 @@
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerOwned;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
@ -21,7 +20,7 @@
import java.util.Objects;
import java.util.Set;
public abstract class ComputerAccess implements IComputerAccess, IComputerOwned
public abstract class ComputerAccess implements IComputerAccess
{
private final IAPIEnvironment m_environment;
private final Set<String> m_mounts = new HashSet<>();
@ -128,9 +127,9 @@ public void queueEvent( @Nonnull final String event, final Object[] arguments )
@Nullable
@Override
public Computer getComputer()
public IWorkMonitor getMainThreadMonitor()
{
return m_environment.getComputer();
return m_environment.getMainThreadMonitor();
}
private String findFreeLocation( String desiredLoc )

View File

@ -7,9 +7,8 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.computer.IComputerOwned;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.TrackingField;
@ -17,7 +16,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public interface IAPIEnvironment extends IComputerOwned
public interface IAPIEnvironment
{
String[] SIDE_NAMES = new String[] {
"bottom", "top", "back", "front", "right", "left",
@ -30,15 +29,14 @@ interface IPeripheralChangeListener
void onPeripheralChanged( int side, @Nullable IPeripheral newPeripheral );
}
@Nonnull
@Override
Computer getComputer();
int getComputerID();
@Nonnull
IComputerEnvironment getComputerEnvironment();
@Nonnull
IWorkMonitor getMainThreadMonitor();
@Nonnull
Terminal getTerminal();

View File

@ -9,6 +9,7 @@
import com.google.common.base.Objects;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
@ -25,6 +26,7 @@
* <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>Passes commands and events to the {@link ComputerExecutor}.</li>
* <li>Passes main thread tasks to the {@link MainThreadExecutor}.</li>
* </ul>
*/
public class Computer
@ -39,6 +41,7 @@ public class Computer
private final IComputerEnvironment m_environment;
private final Terminal m_terminal;
private final ComputerExecutor executor;
private final MainThreadExecutor serverExecutor;
// Additional state about the computer and its environment.
private boolean m_blinking = false;
@ -55,6 +58,7 @@ public Computer( IComputerEnvironment environment, Terminal terminal, int id )
m_terminal = terminal;
executor = new ComputerExecutor( this );
serverExecutor = new MainThreadExecutor( this );
}
IComputerEnvironment getComputerEnvironment()
@ -112,6 +116,22 @@ public void queueEvent( String event, Object[] args )
executor.queueEvent( event, args );
}
/**
* Queue a task to be run on the main thread, using {@link MainThread}.
*
* @param runnable The task to run
* @return If the task was successfully queued (namely, whether there is space on it).
*/
public boolean queueMainThread( Runnable runnable )
{
return serverExecutor.enqueue( runnable );
}
public IWorkMonitor getMainThreadMonitor()
{
return serverExecutor;
}
public int getID()
{
return m_id;

View File

@ -133,7 +133,7 @@ final class ComputerExecutor
*
* Note, this should be empty if this computer is off - it is cleared on shutdown and when turning on again.
*/
private final Queue<Event> eventQueue = new ArrayDeque<>();
private final Queue<Event> eventQueue = new ArrayDeque<>( 4 );
/**
* Whether we interrupted an event and so should resume it instead of executing another task.

View File

@ -112,7 +112,7 @@ public class ComputerThread
long at = a.virtualRuntime, bt = b.virtualRuntime;
if( at == bt ) return Integer.compare( a.hashCode(), b.hashCode() );
return Long.compare( at, bt );
return at < bt ? -1 : 1;
} );
/**

View File

@ -7,6 +7,7 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
@ -58,13 +59,6 @@ public final class Environment implements IAPIEnvironment
this.computer = computer;
}
@Nonnull
@Override
public Computer getComputer()
{
return computer;
}
@Override
public int getComputerID()
{
@ -78,6 +72,13 @@ public IComputerEnvironment getComputerEnvironment()
return computer.getComputerEnvironment();
}
@Nonnull
@Override
public IWorkMonitor getMainThreadMonitor()
{
return computer.getMainThreadMonitor();
}
@Nonnull
@Override
public Terminal getTerminal()

View File

@ -1,33 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.peripheral.IComputerAccess;
import javax.annotation.Nullable;
/**
* {@link IComputerOwned} marks objects which are known to belong to a computer.
*
* The primary purpose of this is to allow Plethora (and potentially other mods) to run the various tracking methods
* on {@link Computer}.
*
* You can generally assume {@link IComputerAccess} implements this interface, though you should always check first.
*
* @see dan200.computercraft.core.apis.ComputerAccess
* @see dan200.computercraft.shared.peripheral.modem.wired.WiredModemPeripheral and the peripheral wrapper
*/
public interface IComputerOwned
{
/**
* Get the computer associated with this object
*
* @return The associated object, or {@code null} if none is known.
*/
@Nullable
Computer getComputer();
}

View File

@ -1,17 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import javax.annotation.Nonnull;
public interface ITask
{
@Nonnull
Computer getOwner();
void execute();
}

View File

@ -6,66 +6,189 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaTask;
import java.util.ArrayDeque;
import java.util.Queue;
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 put {@link ComputerCraft#maxMainGlobalTime} into our budget (and clamp it to that value to). If we're
* still over budget, then we should not execute <em>any</em> work (either as part of {@link MainThread} or externally).
*/
public class MainThread
{
private static final int MAX_TASKS_PER_TICK = 1000;
private static final int MAX_TASKS_TOTAL = 50000;
/**
* An internal counter for {@link ILuaTask} ids.
*
* @see dan200.computercraft.api.lua.ILuaContext#issueMainThreadTask(ILuaTask)
* @see #getUniqueTaskID()
*/
private static final AtomicLong lastTaskId = new AtomicLong();
private static final Queue<ITask> m_outstandingTasks = new ArrayDeque<>();
private static final Object m_nextUnusedTaskIDLock = new Object();
private static long m_nextUnusedTaskID = 0;
/**
* The queue of {@link MainThreadExecutor}s with tasks to perform.
*/
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.
*
* @see MainThreadExecutor#tickCooling()
* @see #cooling(MainThreadExecutor)
*/
private static final HashSet<MainThreadExecutor> cooling = new HashSet<>();
/**
* The current tick number. This is used by {@link MainThreadExecutor} to determine when to reset its own time
* counter.
*
* @see #currentTick()
*/
private static int currentTick;
/**
* The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
*/
private static long budget;
/**
* Whether we should be executing any work this tick.
*
* This is true iff {@code MAX_TICK_TIME - currentTime} was true <em>at the beginning of the tick</em>.
*/
private static boolean canExecute = true;
private static long minimumTime = 0;
public static long getUniqueTaskID()
{
synchronized( m_nextUnusedTaskIDLock )
return lastTaskId.incrementAndGet();
}
static void queue( @Nonnull MainThreadExecutor executor, boolean sleeper )
{
synchronized( executors )
{
return ++m_nextUnusedTaskID;
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 );
}
}
public static boolean queueTask( ITask task )
static void cooling( @Nonnull MainThreadExecutor executor )
{
synchronized( m_outstandingTasks )
{
if( m_outstandingTasks.size() < MAX_TASKS_TOTAL )
{
m_outstandingTasks.offer( task );
return true;
}
}
return false;
cooling.add( executor );
}
static void consumeTime( long time )
{
budget -= time;
}
static boolean canExecute()
{
return canExecute;
}
static int currentTick()
{
return currentTick;
}
public static void executePendingTasks()
{
int tasksThisTick = 0;
while( tasksThisTick < MAX_TASKS_PER_TICK )
// Move onto the next tick and cool down the global executor. We're allowed to execute if we have _any_ time
// allocated for this tick. This means we'll stick much closer to doing MAX_TICK_TIME work every tick.
//
// Of course, we'll go over the MAX_TICK_TIME most of the time, but eventually that overrun will accumulate
// and we'll skip a whole tick - bringing the average back down again.
currentTick++;
budget += Math.min( budget + ComputerCraft.maxMainGlobalTime, ComputerCraft.maxMainGlobalTime );
canExecute = budget > 0;
// Cool down any warm computers.
cooling.removeIf( MainThreadExecutor::tickCooling );
if( !canExecute ) return;
// Run until we meet the deadline.
long start = System.nanoTime();
long deadline = start + budget;
while( true )
{
ITask task = null;
synchronized( m_outstandingTasks )
MainThreadExecutor executor;
synchronized( executors )
{
task = m_outstandingTasks.poll();
executor = executors.pollFirst();
}
if( task != null )
{
long start = System.nanoTime();
task.execute();
if( executor == null ) break;
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) Tracking.addServerTiming( computer, stop - start );
long taskStart = System.nanoTime();
executor.execute();
++tasksThisTick;
}
else
long taskStop = System.nanoTime();
synchronized( executors )
{
break;
if( executor.afterExecute( taskStop - taskStart ) ) executors.add( executor );
// Compute the new minimum time (including the next task on the queue too). Note that this may also include
// time spent in external tasks.
long newMinimum = executor.virtualTime;
if( !executors.isEmpty() )
{
MainThreadExecutor next = executors.first();
if( next.virtualTime < newMinimum ) newMinimum = next.virtualTime;
}
minimumTime = Math.max( minimumTime, newMinimum );
}
if( taskStop >= deadline ) break;
}
consumeTime( System.nanoTime() - start );
}
public static void reset()
{
currentTick = 0;
budget = 0;
canExecute = true;
minimumTime = 0;
lastTaskId.set( 0 );
cooling.clear();
synchronized( executors )
{
executors.clear();
}
}
}

View File

@ -0,0 +1,250 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.tracking.Tracking;
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;
/**
* Keeps track of tasks that a {@link Computer} should run on the main thread and how long that has been spent executing
* them.
*
* This provides rate-limiting mechanism for tasks enqueued with {@link Computer#queueMainThread(Runnable)}, but also
* those run elsewhere (such as during the turtle's tick - see {@link TurtleBrain#update()}). In order to handle this,
* the executor goes through three stages:
*
* When {@link State#COOL}, the computer is allocated {@link 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 timeframe
* or the global time frame has expired.
*
* Then, when other objects (such as {@link TileEntity}) are ticked, we update how much time we've used using
* {@link 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, <em>no</em> 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.
*
* @see MainThread
* @see IWorkMonitor
* @see Computer#getMainThreadMonitor()
* @see Computer#queueMainThread(Runnable)
*/
final class MainThreadExecutor implements IWorkMonitor
{
/**
* The maximum number of {@link MainThread} tasks allowed on the queue.
*/
private static final int MAX_TASKS = 5000;
private final Computer computer;
/**
* A lock used for any changes to {@link #tasks}, or {@link #onQueue}. This will be
* used on the main thread, so locks should be kept as brief as possible.
*/
private final Object queueLock = new Object();
/**
* The queue of tasks which should be executed.
*
* @see #queueLock
*/
private final Queue<Runnable> tasks = new ArrayDeque<>( 4 );
/**
* Determines if this executor is currently present on the queue.
*
* This should be true iff {@link #tasks} is non-empty.
*
* @see #queueLock
* @see #enqueue(Runnable)
* @see #afterExecute(long)
*/
volatile boolean onQueue;
/**
* The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
*
* @see #tickCooling()
* @see #consumeTime(long)
*/
private long budget = 0;
/**
* The last tick that {@link #budget} was updated.
*
* @see #tickCooling()
* @see #consumeTime(long)
*/
private int currentTick = -1;
/**
* The current state of this executor.
*
* @see #canWork()
*/
private State state = State.COOL;
private long pendingTime;
long virtualTime;
MainThreadExecutor( Computer computer )
{
this.computer = computer;
}
/**
* Push a task onto this executor's queue, pushing it onto the {@link MainThread} if needed.
*
* @param runnable The task to run on the main thread.
* @return Whether this task was enqueued (namely, was there space).
*/
boolean enqueue( Runnable runnable )
{
synchronized( queueLock )
{
if( tasks.size() >= MAX_TASKS || !tasks.offer( runnable ) ) return false;
if( !onQueue && state == State.COOL ) MainThread.queue( this, true );
return true;
}
}
void execute()
{
if( state != State.COOL ) return;
Runnable task;
synchronized( queueLock )
{
task = tasks.poll();
}
if( task != null ) task.run();
}
/**
* Update the time taken to run an {@link #enqueue(Runnable)} task.
*
* @param time The time some task took to run.
* @return Whether this should be added back to the queue.
*/
boolean afterExecute( long time )
{
consumeTime( time );
synchronized( queueLock )
{
virtualTime += time;
updateTime();
if( state != State.COOL || tasks.isEmpty() ) return onQueue = false;
return true;
}
}
/**
* Whether we should execute "external" tasks (ones not part of {@link #tasks}).
*
* @return Whether we can execute external tasks.
*/
@Override
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 );
}
private void consumeTime( long time )
{
Tracking.addServerTiming( computer, time );
// Reset the budget if moving onto a new tick. We know this is safe, as this will only have happened if
// #tickCooling() isn't called, and so we didn't overrun the previous tick.
if( currentTick != MainThread.currentTick() )
{
currentTick = MainThread.currentTick();
budget = ComputerCraft.maxMainComputerTime;
}
budget -= time;
// If we've gone over our limit, mark us as having to cool down.
if( budget < 0 && state == State.COOL )
{
state = State.HOT;
MainThread.cooling( this );
}
}
/**
* Move this executor forward one tick, replenishing the budget by {@link ComputerCraft#maxMainComputerTime}.
*
* @return Whether this executor has cooled down, and so is safe to run again.
*/
boolean tickCooling()
{
state = State.COOLING;
currentTick = MainThread.currentTick();
budget += Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime );
if( budget < ComputerCraft.maxMainComputerTime ) return false;
state = State.COOL;
synchronized( queueLock )
{
if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this, false );
}
return true;
}
void updateTime()
{
virtualTime += pendingTime;
pendingTime = 0;
}
private enum State
{
COOL,
HOT,
COOLING,
}
}

View File

@ -9,7 +9,6 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.tracking.Tracking;
@ -536,53 +535,36 @@ public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaExcept
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Nonnull
@Override
public Computer getOwner()
final Runnable iTask = () -> {
try
{
return m_computer;
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
@Override
public void execute()
catch( LuaException e )
{
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
m_computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running task", t );
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
};
if( MainThread.queueTask( iTask ) )
if( m_computer.queueMainThread( iTask ) )
{
return taskID;
}

View File

@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static dan200.computercraft.ComputerCraft.DEFAULT_HTTP_BLACKLIST;
import static dan200.computercraft.ComputerCraft.DEFAULT_HTTP_WHITELIST;
@ -32,6 +33,7 @@ public class Config
private static final int MODEM_MAX_RANGE = 100000;
private static final String CATEGORY_GENERAL = "general";
private static final String CATEGORY_EXECUTION = "execution";
private static final String CATEGORY_HTTP = "http";
private static final String CATEGORY_PERIPHERAL = "peripheral";
private static final String CATEGORY_TURTLE = "turtle";
@ -44,9 +46,12 @@ public class Config
private static Property disableLua51Features;
private static Property defaultComputerSettings;
private static Property debugEnabled;
private static Property computerThreads;
private static Property logComputerErrors;
private static Property computerThreads;
private static Property maxMainGlobalTime;
private static Property maxMainComputerTime;
private static Property httpEnable;
private static Property httpWebsocketEnable;
private static Property httpWhitelist;
@ -97,21 +102,16 @@ public static void load( File configFile )
maximumFilesOpen.setMinValue( 0 );
disableLua51Features = config.get( CATEGORY_GENERAL, "disable_lua51_features", ComputerCraft.disable_lua51_features );
disableLua51Features.setComment( "Set this to true to disable Lua 5.1 functions that will be removed in a future update. Useful for ensuring forward compatibility of your programs now." );
disableLua51Features.setComment( "Set this to true to disable Lua 5.1 functions that will be removed in a future " +
"update. Useful for ensuring forward compatibility of your programs now." );
defaultComputerSettings = config.get( CATEGORY_GENERAL, "default_computer_settings", ComputerCraft.default_computer_settings );
defaultComputerSettings.setComment( "A comma seperated list of default system settings to set on new computers. Example: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all autocompletion" );
defaultComputerSettings.setComment( "A comma seperated list of default system settings to set on new computers. Example: " +
"\"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all autocompletion" );
debugEnabled = config.get( CATEGORY_GENERAL, "debug_enabled", ComputerCraft.debug_enable );
debugEnabled.setComment( "Enable Lua's debug library. This is sandboxed to each computer, so is generally safe to be used by players." );
computerThreads = config.get( CATEGORY_GENERAL, "computer_threads", ComputerCraft.computer_threads );
computerThreads
.setMinValue( 1 )
.setRequiresMcRestart( true )
.setComment( "Set the number of threads computers can run on. A higher number means more computers can run at once, but may induce lag.\n" +
"Please note that some mods may not work with a thread count higher than 1. Use with caution." );
logComputerErrors = config.get( CATEGORY_GENERAL, "log_computer_errors", ComputerCraft.logPeripheralErrors );
logComputerErrors.setComment( "Log exceptions thrown by peripherals and other Lua objects.\n" +
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." );
@ -119,7 +119,42 @@ public static void load( File configFile )
setOrder(
CATEGORY_GENERAL,
computerSpaceLimit, floppySpaceLimit, maximumFilesOpen,
disableLua51Features, defaultComputerSettings, debugEnabled, computerThreads, logComputerErrors
disableLua51Features, defaultComputerSettings, debugEnabled, logComputerErrors
);
}
{ // Execution
renameProperty( CATEGORY_GENERAL, "computer_threads", CATEGORY_EXECUTION, "computer_threads" );
config.getCategory( CATEGORY_EXECUTION )
.setComment( "Controls execution behaviour of computers. This is largely intended for fine-tuning " +
"servers, and generally shouldn't need to be touched" );
computerThreads = config.get( CATEGORY_EXECUTION, "computer_threads", ComputerCraft.computer_threads );
computerThreads
.setMinValue( 1 )
.setRequiresMcRestart( true )
.setComment( "Set the number of threads computers can run on. A higher number means more computers can " +
"run at once, but may induce lag.\n" +
"Please note that some mods may not work with a thread count higher than 1. Use with caution." );
maxMainGlobalTime = config.get( CATEGORY_EXECUTION, "max_main_global_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainGlobalTime ) );
maxMainGlobalTime
.setMinValue( 1 )
.setComment( "The maximum time that can be spent executing tasks in a single tick, in milliseconds.\n" +
"Note, we will quite possibly go over this limit, as there's no way to tell how long a will take - this aims " +
"to be the upper bound of the average time." );
maxMainComputerTime = config.get( CATEGORY_EXECUTION, "max_main_computer_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainComputerTime ) );
maxMainComputerTime
.setMinValue( 1 )
.setComment( "The ideal maximum time a computer can execute for in a tick, in milliseconds.\n" +
"Note, we will quite possibly go over this limit, as there's no way to tell how long a will take - this aims " +
"to be the upper bound of the average time." );
setOrder(
CATEGORY_EXECUTION,
computerThreads, maxMainGlobalTime, maxMainComputerTime
);
}
@ -129,20 +164,28 @@ public static void load( File configFile )
renameProperty( CATEGORY_GENERAL, "http_whitelist", CATEGORY_HTTP, "whitelist" );
renameProperty( CATEGORY_GENERAL, "http_blacklist", CATEGORY_HTTP, "blacklist" );
config.getCategory( CATEGORY_HTTP )
.setComment( "Controls the HTTP API" );
httpEnable = config.get( CATEGORY_HTTP, "enabled", ComputerCraft.http_enable );
httpEnable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for more fine grained control than this)" );
httpEnable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for " +
"more fine grained control than this)" );
httpWebsocketEnable = config.get( CATEGORY_HTTP, "websocket_enabled", ComputerCraft.http_websocket_enable );
httpWebsocketEnable.setComment( "Enable use of http websockets. This requires the \"http_enable\" option to also be true." );
httpWhitelist = config.get( CATEGORY_HTTP, "whitelist", DEFAULT_HTTP_WHITELIST );
httpWhitelist.setComment( "A list of wildcards for domains or IP ranges that can be accessed through the \"http\" API on Computers.\n" +
"Set this to \"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to just subdomains of pastebin.com.\n" +
httpWhitelist.setComment( "A list of wildcards for domains or IP ranges that can be accessed through the " +
"\"http\" API on Computers.\n" +
"Set this to \"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to " +
"just subdomains of pastebin.com.\n" +
"You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\")." );
httpBlacklist = config.get( CATEGORY_HTTP, "blacklist", DEFAULT_HTTP_BLACKLIST );
httpBlacklist.setComment( "A list of wildcards for domains or IP ranges that cannot be accessed through the \"http\" API on Computers.\n" +
"If this is empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block access to all subdomains of github.com.\n" +
httpBlacklist.setComment( "A list of wildcards for domains or IP ranges that cannot be accessed through the " +
"\"http\" API on Computers.\n" +
"If this is empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block " +
"access to all subdomains of github.com.\n" +
"You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\")." );
httpTimeout = config.get( CATEGORY_HTTP, "timeout", ComputerCraft.httpTimeout );
@ -150,15 +193,18 @@ public static void load( File configFile )
httpTimeout.setMinValue( 0 );
httpMaxRequests = config.get( CATEGORY_HTTP, "max_requests", ComputerCraft.httpMaxRequests );
httpMaxRequests.setComment( "The number of http requests a computer can make at one time. Additional requests will be queued, and sent when the running requests have finished. Set to 0 for unlimited." );
httpMaxRequests.setComment( "The number of http requests a computer can make at one time. Additional requests " +
"will be queued, and sent when the running requests have finished. Set to 0 for unlimited." );
httpMaxRequests.setMinValue( 0 );
httpMaxDownload = config.get( CATEGORY_HTTP, "max_download", (int) ComputerCraft.httpMaxDownload );
httpMaxDownload.setComment( "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." );
httpMaxDownload.setComment( "The maximum size (in bytes) that a computer can download in a single request. " +
"Note that responses may receive more data than allowed, but this data will not be returned to the client." );
httpMaxDownload.setMinValue( 0 );
httpMaxUpload = config.get( CATEGORY_HTTP, "max_upload", (int) ComputerCraft.httpMaxUpload );
httpMaxUpload.setComment( "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." );
httpMaxUpload.setComment( "The maximum size (in bytes) that a computer can upload in a single request. This " +
"includes headers and POST text." );
httpMaxUpload.setMinValue( 0 );
httpMaxWebsockets = config.get( CATEGORY_HTTP, "max_websockets", ComputerCraft.httpMaxWebsockets );
@ -185,6 +231,9 @@ public static void load( File configFile )
renameProperty( CATEGORY_GENERAL, "modem_highAltitudeRangeDuringStorm", CATEGORY_PERIPHERAL, "modem_high_altitude_range_during_storm" );
renameProperty( CATEGORY_GENERAL, "maxNotesPerTick", CATEGORY_PERIPHERAL, "max_notes_per_tick" );
config.getCategory( CATEGORY_PERIPHERAL )
.setComment( "Various options relating to peripherals." );
commandBlockEnabled = config.get( CATEGORY_PERIPHERAL, "command_block_enabled", ComputerCraft.enableCommandBlock );
commandBlockEnabled.setComment( "Enable Command Block peripheral support" );
@ -226,6 +275,9 @@ public static void load( File configFile )
renameProperty( CATEGORY_GENERAL, "turtlesCanPush", CATEGORY_TURTLE, "can_push" );
renameProperty( CATEGORY_GENERAL, "turtle_disabled_actions", CATEGORY_TURTLE, "disabled_actions" );
config.getCategory( CATEGORY_HTTP )
.setComment( "Various options relating to turtles." );
turtlesNeedFuel = config.get( CATEGORY_TURTLE, "need_fuel", ComputerCraft.turtlesNeedFuel );
turtlesNeedFuel.setComment( "Set whether Turtles require fuel to move" );
@ -238,10 +290,12 @@ public static void load( File configFile )
advancedTurtleFuelLimit.setMinValue( 0 );
turtlesObeyBlockProtection = config.get( CATEGORY_TURTLE, "obey_block_protection", ComputerCraft.turtlesObeyBlockProtection );
turtlesObeyBlockProtection.setComment( "If set to true, Turtles will be unable to build, dig, or enter protected areas (such as near the server spawn point)" );
turtlesObeyBlockProtection.setComment( "If set to true, Turtles will be unable to build, dig, or enter protected " +
"areas (such as near the server spawn point)" );
turtlesCanPush = config.get( CATEGORY_TURTLE, "can_push", ComputerCraft.turtlesCanPush );
turtlesCanPush.setComment( "If set to true, Turtles will push entities out of the way instead of stopping if there is space to do so" );
turtlesCanPush.setComment( "If set to true, Turtles will push entities out of the way instead of stopping if " +
"there is space to do so" );
turtleDisabledActions = config.get( CATEGORY_TURTLE, "disabled_actions", new String[0] );
turtleDisabledActions.setComment( "A list of turtle actions which are disabled." );
@ -252,11 +306,12 @@ public static void load( File configFile )
);
}
setupLanguage( config.getCategory( CATEGORY_GENERAL ), "gui.computercraft:config" );
for( String child : config.getCategoryNames() )
{
if( child.equals( CATEGORY_GENERAL ) ) continue;
setupLanguage( config.getCategory( child ), "gui.computercraft:config." + child );
setupLanguage(
config.getCategory( child ),
child.equals( CATEGORY_GENERAL ) ? "gui.computercraft:config" : "gui.computercraft:config." + child
);
}
sync();
@ -374,9 +429,13 @@ public static void sync()
ComputerCraft.disable_lua51_features = disableLua51Features.getBoolean();
ComputerCraft.default_computer_settings = defaultComputerSettings.getString();
ComputerCraft.debug_enable = debugEnabled.getBoolean();
ComputerCraft.computer_threads = computerThreads.getInt();
ComputerCraft.logPeripheralErrors = logComputerErrors.getBoolean();
// Execution
ComputerCraft.computer_threads = computerThreads.getInt();
ComputerCraft.maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( Math.max( 1, maxMainGlobalTime.getLong() ) );
ComputerCraft.maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( Math.max( 1, maxMainComputerTime.getLong() ) );
// HTTP
ComputerCraft.http_enable = httpEnable.getBoolean();
ComputerCraft.http_websocket_enable = httpWebsocketEnable.getBoolean();

View File

@ -16,8 +16,7 @@
import dan200.computercraft.api.network.wired.IWiredSender;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerOwned;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import net.minecraft.world.World;
@ -267,7 +266,7 @@ private RemotePeripheralWrapper getWrapper( IComputerAccess computer, String rem
return wrappers == null ? null : wrappers.get( remoteName );
}
private static class RemotePeripheralWrapper implements IComputerAccess, IComputerOwned
private static class RemotePeripheralWrapper implements IComputerAccess
{
private final WiredModemElement m_element;
private final IPeripheral m_peripheral;
@ -376,6 +375,13 @@ public void queueEvent( @Nonnull String event, Object[] arguments )
m_computer.queueEvent( event, arguments );
}
@Nullable
@Override
public IWorkMonitor getMainThreadMonitor()
{
return m_computer.getMainThreadMonitor();
}
@Nonnull
@Override
public String getAttachmentName()
@ -402,12 +408,5 @@ public IPeripheral getAvailablePeripheral( @Nonnull String name )
return m_element.getRemotePeripherals().get( name );
}
}
@Nullable
@Override
public Computer getComputer()
{
return m_computer instanceof IComputerOwned ? ((IComputerOwned) m_computer).getComputer() : null;
}
}
}

View File

@ -13,7 +13,6 @@
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.*;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.computer.blocks.ComputerProxy;
import dan200.computercraft.shared.computer.blocks.TileComputerBase;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@ -41,6 +40,7 @@
import javax.annotation.Nonnull;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class TurtleBrain implements ITurtleAccess
{
@ -957,53 +957,50 @@ private void updatePeripherals( ServerComputer serverComputer )
private void updateCommands()
{
if( m_animation == TurtleAnimation.None )
if( m_animation != TurtleAnimation.None || m_commandQueue.isEmpty() ) return;
// If we've got a computer, ensure that we're allowed to perform work.
ServerComputer computer = m_owner.getServerComputer();
if( computer != null && !computer.getComputer().getMainThreadMonitor().canWork() ) return;
// Pull a new command
TurtleCommandQueueEntry nextCommand = m_commandQueue.poll();
if( nextCommand == null ) return;
// Execute the command
long start = System.nanoTime();
TurtleCommandResult result = nextCommand.command.execute( this );
long end = System.nanoTime();
// Dispatch the callback
if( computer == null ) return;
computer.getComputer().getMainThreadMonitor().trackWork( end - start, TimeUnit.NANOSECONDS );
int callbackID = nextCommand.callbackID;
if( callbackID < 0 ) return;
if( result != null && result.isSuccess() )
{
// Pull a new command
TurtleCommandQueueEntry nextCommand = m_commandQueue.poll();
if( nextCommand != null )
Object[] results = result.getResults();
if( results != null )
{
ServerComputer computer = m_owner.getServerComputer();
// Execute the command
long start = System.nanoTime();
TurtleCommandResult result = nextCommand.command.execute( this );
long end = System.nanoTime();
// Dispatch the callback
if( computer != null )
{
Tracking.addServerTiming( computer.getComputer(), end - start );
int callbackID = nextCommand.callbackID;
if( callbackID >= 0 )
{
if( result != null && result.isSuccess() )
{
Object[] results = result.getResults();
if( results != null )
{
Object[] arguments = new Object[results.length + 2];
arguments[0] = callbackID;
arguments[1] = true;
System.arraycopy( results, 0, arguments, 2, results.length );
computer.queueEvent( "turtle_response", arguments );
}
else
{
computer.queueEvent( "turtle_response", new Object[] {
callbackID, true
} );
}
}
else
{
computer.queueEvent( "turtle_response", new Object[] {
callbackID, false, result != null ? result.getErrorMessage() : null
} );
}
}
}
Object[] arguments = new Object[results.length + 2];
arguments[0] = callbackID;
arguments[1] = true;
System.arraycopy( results, 0, arguments, 2, results.length );
computer.queueEvent( "turtle_response", arguments );
}
else
{
computer.queueEvent( "turtle_response", new Object[] {
callbackID, true
} );
}
}
else
{
computer.queueEvent( "turtle_response", new Object[] {
callbackID, false, result != null ? result.getErrorMessage() : null
} );
}
}

View File

@ -58,7 +58,7 @@ commands.computercraft.dump.usage=[ID]
commands.computercraft.dump.action=Zeigt mehr Informationen über einen Computer
commands.computercraft.shutdown.synopsis=Fährt den Computer aus der Ferne herunter.
commands.computercraft.shutdown.desc=Fährt die angegebenen Computer herunter. Falls keine Computer angegeben sind, werden alle heruntergefahren. Der Computer kann entweder über seine Instanz ID (z.B. 123), seine Computer ID (z.B. #123) oder seinen Namen (z.B. "@Mein Computer") angegeben werden.
commands.computercraft.shutdown.desc=Fährt die angegebenen Computer herunter. Falls keine Computer angegeben sind, werden alle heruntergefahren. Der Computer kann entweder über seine Instanz ID (z.B. 123), seine Computer ID (z.B. #123) oder seinen Namen (z.B. "@Mein Computer") angegeben werden.
commands.computercraft.shutdown.usage=[IDs...]
commands.computercraft.shutdown.done=Fährt die Computer %s/%s herunter
@ -156,9 +156,10 @@ gui.computercraft:config.maximum_open_files=Maximalanzahl an gleichzeitig offene
gui.computercraft:config.disable_lua51_features=Lua 5.1-Funktionen deaktivieren
gui.computercraft:config.default_computer_settings=Computer-Standardeinstellungen
gui.computercraft:config.debug_enabled=Debug-Library aktivieren
gui.computercraft:config.computer_threads=Computer Threads
gui.computercraft:config.log_computer_errors=Computerfehler loggen
gui.computercraft:config.execution.computer_threads=Computer Threads
gui.computercraft:config.http=HTTP
gui.computercraft:config.http.enabled=HTTP-API aktivieren
gui.computercraft:config.http.websocket_enabled=Websockets aktivieren

View File

@ -156,9 +156,13 @@ gui.computercraft:config.maximum_open_files=Maximum files open per computer
gui.computercraft:config.disable_lua51_features=Disable Lua 5.1 features
gui.computercraft:config.default_computer_settings=Default Computer settings
gui.computercraft:config.debug_enabled=Enable debug library
gui.computercraft:config.computer_threads=Computer threads
gui.computercraft:config.log_computer_errors=Log computer errors
gui.computercraft:config.execution=Execution
gui.computercraft:config.execution.computer_threads=Computer threads
gui.computercraft:config.execution.max_main_global_time=Server tick global time limit
gui.computercraft:config.execution.max_main_computer_time=Server tick computer time limit
gui.computercraft:config.http=HTTP
gui.computercraft:config.http.enabled=Enable the HTTP API
gui.computercraft:config.http.websocket_enabled=Enable websockets

View File

@ -56,9 +56,10 @@ gui.computercraft:config.maximum_open_files=Massimo file aperti per computer
gui.computercraft:config.disable_lua51_features=Disattiva features Lua 5.1
gui.computercraft:config.default_computer_settings=Impostazioni Computer predefinite
gui.computercraft:config.debug_enabled=Attiva libreria di debug
gui.computercraft:config.computer_threads=Threads computer
gui.computercraft:config.log_computer_errors=Salva errori computer
gui.computercraft:config.execution.computer_threads=Threads computer
gui.computercraft:config.http=HTTP
gui.computercraft:config.http.enabled=Attiva l'API HTTP
gui.computercraft:config.http.websocket_enabled=Attiva websocket

View File

@ -56,9 +56,10 @@ gui.computercraft:config.maximum_open_files=Número máximo de arquivos em um co
gui.computercraft:config.disable_lua51_features=Desabilitar funcionalidade da Lua 5.1
gui.computercraft:config.default_computer_settings=Configurações padrão para Computadores
gui.computercraft:config.debug_enabled=Habilitar biblioteca de debug
gui.computercraft:config.computer_threads=Threads por computador
gui.computercraft:config.log_computer_errors=Registrar erros de computadores
gui.computercraft:config.execution.computer_threads=Threads por computador
gui.computercraft:config.http=HTTP
gui.computercraft:config.http.enabled=Habilitar a biblioteca de HTTP
gui.computercraft:config.http.websocket_enabled=Habilitar websockets

View File

@ -56,9 +56,10 @@ gui.computercraft:config.maximum_open_files=Max antal filer öppna per dator
gui.computercraft:config.disable_lua51_features=Avaktivera Lua 5.1 funktioner
gui.computercraft:config.default_computer_settings=Standard Datorinställningar
gui.computercraft:config.debug_enabled=Aktivera debug bibliotek
gui.computercraft:config.computer_threads=Dator trådar
gui.computercraft:config.log_computer_errors=Logga datorfel
gui.computercraft:config.execution.computer_threads=Dator trådar
gui.computercraft:config.http=HTTP
gui.computercraft:config.http.enabled=Aktivera HTTP API
gui.computercraft:config.http.websocket_enabled=Aktivera websockets