mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-06-30 09:02:59 +00:00
Move MainThread config to its own class
This means the config is no longer stored as static fields, which is a little cleaner. Would like to move everything else in the future, but this is a good first step.
This commit is contained in:
parent
590ef86c93
commit
5082b1677c
@ -12,6 +12,7 @@ import dan200.computercraft.core.ComputerContext;
|
|||||||
import dan200.computercraft.core.computer.ComputerThread;
|
import dan200.computercraft.core.computer.ComputerThread;
|
||||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThread;
|
import dan200.computercraft.core.computer.mainthread.MainThread;
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||||
import dan200.computercraft.core.lua.ILuaMachine;
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
||||||
@ -65,7 +66,7 @@ public final class ServerContext {
|
|||||||
private ServerContext(MinecraftServer server) {
|
private ServerContext(MinecraftServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
storageDir = server.getWorldPath(FOLDER);
|
storageDir = server.getWorldPath(FOLDER);
|
||||||
mainThread = new MainThread();
|
mainThread = new MainThread(mainThreadConfig);
|
||||||
context = new ComputerContext(
|
context = new ComputerContext(
|
||||||
new Environment(server),
|
new Environment(server),
|
||||||
new ComputerThread(ConfigSpec.computerThreads.get()),
|
new ComputerThread(ConfigSpec.computerThreads.get()),
|
||||||
@ -215,4 +216,16 @@ public final class ServerContext {
|
|||||||
return ComputerCraftAPI.MOD_ID + "/" + ComputerCraftAPI.getInstalledVersion();
|
return ComputerCraftAPI.MOD_ID + "/" + ComputerCraftAPI.getInstalledVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final MainThreadConfig mainThreadConfig = new MainThreadConfig() {
|
||||||
|
@Override
|
||||||
|
public long maxGlobalTime() {
|
||||||
|
return TimeUnit.MILLISECONDS.toNanos(ConfigSpec.maxMainGlobalTime.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long maxComputerTime() {
|
||||||
|
return TimeUnit.MILLISECONDS.toNanos(ConfigSpec.maxMainComputerTime.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import dan200.computercraft.core.CoreConfig;
|
|||||||
import dan200.computercraft.core.Logging;
|
import dan200.computercraft.core.Logging;
|
||||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||||
import dan200.computercraft.core.apis.http.options.Action;
|
import dan200.computercraft.core.apis.http.options.Action;
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -155,14 +156,14 @@ public final class ConfigSpec {
|
|||||||
milliseconds.
|
milliseconds.
|
||||||
Note, we will quite possibly go over this limit, as there's no way to tell how
|
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.""")
|
long a will take - this aims to be the upper bound of the average time.""")
|
||||||
.defineInRange("max_main_global_time", (int) TimeUnit.NANOSECONDS.toMillis(CoreConfig.maxMainGlobalTime), 1, Integer.MAX_VALUE);
|
.defineInRange("max_main_global_time", (int) TimeUnit.NANOSECONDS.toMillis(MainThreadConfig.DEFAULT_MAX_GLOBAL_TIME), 1, Integer.MAX_VALUE);
|
||||||
|
|
||||||
maxMainComputerTime = builder
|
maxMainComputerTime = builder
|
||||||
.comment("""
|
.comment("""
|
||||||
The ideal maximum time a computer can execute for in a tick, in milliseconds.
|
The ideal maximum time a computer can execute for in a tick, in milliseconds.
|
||||||
Note, we will quite possibly go over this limit, as there's no way to tell how
|
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.""")
|
long a will take - this aims to be the upper bound of the average time.""")
|
||||||
.defineInRange("max_main_computer_time", (int) TimeUnit.NANOSECONDS.toMillis(CoreConfig.maxMainComputerTime), 1, Integer.MAX_VALUE);
|
.defineInRange("max_main_computer_time", (int) TimeUnit.NANOSECONDS.toMillis(MainThreadConfig.DEFAULT_MAX_COMPUTER_TIME), 1, Integer.MAX_VALUE);
|
||||||
|
|
||||||
builder.pop();
|
builder.pop();
|
||||||
}
|
}
|
||||||
@ -348,10 +349,6 @@ public final class ConfigSpec {
|
|||||||
CoreConfig.defaultComputerSettings = defaultComputerSettings.get();
|
CoreConfig.defaultComputerSettings = defaultComputerSettings.get();
|
||||||
Config.commandRequireCreative = commandRequireCreative.get();
|
Config.commandRequireCreative = commandRequireCreative.get();
|
||||||
|
|
||||||
// Execution
|
|
||||||
CoreConfig.maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos(maxMainGlobalTime.get());
|
|
||||||
CoreConfig.maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos(maxMainComputerTime.get());
|
|
||||||
|
|
||||||
// Update our log filter if needed.
|
// Update our log filter if needed.
|
||||||
var logFilter = MarkerFilter.createFilter(
|
var logFilter = MarkerFilter.createFilter(
|
||||||
Logging.COMPUTER_ERROR.getName(),
|
Logging.COMPUTER_ERROR.getName(),
|
||||||
|
@ -9,7 +9,6 @@ import dan200.computercraft.core.apis.http.options.AddressRule;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config options for ComputerCraft's Lua runtime.
|
* Config options for ComputerCraft's Lua runtime.
|
||||||
@ -25,9 +24,6 @@ public final class CoreConfig {
|
|||||||
public static boolean disableLua51Features = false;
|
public static boolean disableLua51Features = false;
|
||||||
public static String defaultComputerSettings = "";
|
public static String defaultComputerSettings = "";
|
||||||
|
|
||||||
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos(10);
|
|
||||||
public static long maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos(5);
|
|
||||||
|
|
||||||
public static boolean httpEnabled = true;
|
public static boolean httpEnabled = true;
|
||||||
public static boolean httpWebsocketEnabled = true;
|
public static boolean httpWebsocketEnabled = true;
|
||||||
public static List<AddressRule> httpRules = List.of(
|
public static List<AddressRule> httpRules = List.of(
|
||||||
|
@ -18,7 +18,6 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs.
|
* Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs.
|
||||||
*
|
*
|
||||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
|
||||||
* @see ILuaAPIFactory
|
* @see ILuaAPIFactory
|
||||||
* @see ApiWrapper
|
* @see ApiWrapper
|
||||||
*/
|
*/
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
package dan200.computercraft.core.computer.mainthread;
|
package dan200.computercraft.core.computer.mainthread;
|
||||||
|
|
||||||
import dan200.computercraft.core.CoreConfig;
|
|
||||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -20,7 +19,7 @@ import java.util.TreeSet;
|
|||||||
* {@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>
|
* <p>
|
||||||
* Next tick, we add {@link CoreConfig#maxMainGlobalTime} to our budget (clamp it to that value too). If we're still
|
* Next tick, we add {@link MainThreadConfig#maxGlobalTime()} 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 implements MainThreadScheduler {
|
public final class MainThread implements MainThreadScheduler {
|
||||||
@ -35,6 +34,8 @@ public final class MainThread implements MainThreadScheduler {
|
|||||||
return at < bt ? -1 : 1;
|
return at < bt ? -1 : 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final MainThreadConfig config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of executors which went over budget in a previous tick, and are waiting for their time to run down.
|
* The set of executors which went over budget in a previous tick, and are waiting for their time to run down.
|
||||||
*
|
*
|
||||||
@ -66,6 +67,11 @@ public final class MainThread implements MainThreadScheduler {
|
|||||||
private long minimumTime = 0;
|
private long minimumTime = 0;
|
||||||
|
|
||||||
public MainThread() {
|
public MainThread() {
|
||||||
|
this(MainThreadConfig.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainThread(MainThreadConfig config) {
|
||||||
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
void queue(MainThreadExecutor executor) {
|
void queue(MainThreadExecutor executor) {
|
||||||
@ -79,7 +85,7 @@ public final class MainThread implements MainThreadScheduler {
|
|||||||
var newRuntime = minimumTime;
|
var newRuntime = minimumTime;
|
||||||
|
|
||||||
// Slow down new computers a little bit.
|
// Slow down new computers a little bit.
|
||||||
if (executor.virtualTime == 0) newRuntime += CoreConfig.maxMainComputerTime;
|
if (executor.virtualTime == 0) newRuntime += config.maxComputerTime();
|
||||||
|
|
||||||
executor.virtualTime = Math.max(newRuntime, executor.virtualTime);
|
executor.virtualTime = Math.max(newRuntime, executor.virtualTime);
|
||||||
|
|
||||||
@ -110,7 +116,8 @@ public final class MainThread implements MainThreadScheduler {
|
|||||||
// Of course, we'll go over the MAX_TICK_TIME most of the time, but eventually that overrun will accumulate
|
// 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.
|
// and we'll skip a whole tick - bringing the average back down again.
|
||||||
currentTick++;
|
currentTick++;
|
||||||
budget = Math.min(budget + CoreConfig.maxMainGlobalTime, CoreConfig.maxMainGlobalTime);
|
var maxGlobal = config.maxGlobalTime();
|
||||||
|
budget = Math.min(budget + maxGlobal, maxGlobal);
|
||||||
canExecute = budget > 0;
|
canExecute = budget > 0;
|
||||||
|
|
||||||
// Cool down any warm computers.
|
// Cool down any warm computers.
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.core.computer.mainthread;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options to configure the {@link MainThread}.
|
||||||
|
*/
|
||||||
|
public interface MainThreadConfig {
|
||||||
|
/**
|
||||||
|
* The default value for {@link #maxGlobalTime()}.
|
||||||
|
*/
|
||||||
|
long DEFAULT_MAX_GLOBAL_TIME = TimeUnit.MILLISECONDS.toNanos(10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value for {@link #maxComputerTime()}.
|
||||||
|
*/
|
||||||
|
long DEFAULT_MAX_COMPUTER_TIME = TimeUnit.MILLISECONDS.toNanos(5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default config.
|
||||||
|
*/
|
||||||
|
MainThreadConfig DEFAULT = new Basic(DEFAULT_MAX_GLOBAL_TIME, DEFAULT_MAX_COMPUTER_TIME);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ideal maximum time that can be spent executing tasks in a tick, in nanoseconds.
|
||||||
|
*
|
||||||
|
* @return The max time a that can be spent executing tasks in a single tick.
|
||||||
|
*/
|
||||||
|
long maxGlobalTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ideal maximum time a computer can execute for in a tick, in nanoseconds.
|
||||||
|
*
|
||||||
|
* @return The max time a computer can execute in a single tick.
|
||||||
|
*/
|
||||||
|
long maxComputerTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic implementation of {@link MainThreadConfig}, which works on constant values.
|
||||||
|
*
|
||||||
|
* @param maxGlobalTime See {@link MainThreadConfig#maxGlobalTime()}.
|
||||||
|
* @param maxComputerTime See {@link MainThreadConfig#maxComputerTime()}.
|
||||||
|
*/
|
||||||
|
record Basic(long maxGlobalTime, long maxComputerTime) implements MainThreadConfig {
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@
|
|||||||
package dan200.computercraft.core.computer.mainthread;
|
package dan200.computercraft.core.computer.mainthread;
|
||||||
|
|
||||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||||
import dan200.computercraft.core.CoreConfig;
|
|
||||||
import dan200.computercraft.core.computer.Computer;
|
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;
|
||||||
@ -22,7 +21,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* those run elsewhere (such as during the turtle's tick). In order to handle this, the executor goes through three
|
* those run elsewhere (such as during the turtle's tick). In order to handle this, the executor goes through three
|
||||||
* stages:
|
* stages:
|
||||||
* <p>
|
* <p>
|
||||||
* When {@link State#COOL}, the computer is allocated {@link CoreConfig#maxMainComputerTime}ns to execute any work
|
* When {@link State#COOL}, the computer is allocated {@link MainThreadConfig#maxComputerTime()}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>
|
* <p>
|
||||||
@ -33,13 +32,13 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* {@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>
|
* <p>
|
||||||
* At the beginning of the next tick, we increment the budget e by {@link CoreConfig#maxMainComputerTime} and any
|
* At the beginning of the next tick, we increment the budget e by {@link MainThreadConfig#maxComputerTime()} and any
|
||||||
* {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is fully
|
* {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is fully
|
||||||
* replenished (is equal to {@link CoreConfig#maxMainComputerTime}). Note, this is different to {@link MainThread},
|
* replenished (is equal to {@link MainThreadConfig#maxComputerTime()}). 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
|
* 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.
|
* entity or main thread.
|
||||||
* <p>
|
* <p>
|
||||||
* This mechanism means that, on average, computers will use at most {@link CoreConfig#maxMainComputerTime}ns per
|
* This mechanism means that, on average, computers will use at most {@link MainThreadConfig#maxComputerTime()}ns per
|
||||||
* second, but one task source will not prevent others from executing.
|
* second, but one task source will not prevent others from executing.
|
||||||
*
|
*
|
||||||
* @see MainThread
|
* @see MainThread
|
||||||
@ -189,7 +188,7 @@ final class MainThreadExecutor implements MainThreadScheduler.Executor {
|
|||||||
// #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 != scheduler.currentTick()) {
|
if (currentTick != scheduler.currentTick()) {
|
||||||
currentTick = scheduler.currentTick();
|
currentTick = scheduler.currentTick();
|
||||||
budget = CoreConfig.maxMainComputerTime;
|
budget = scheduler.config.maxComputerTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
budget -= time;
|
budget -= time;
|
||||||
@ -202,15 +201,16 @@ final class MainThreadExecutor implements MainThreadScheduler.Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move this executor forward one tick, replenishing the budget by {@link CoreConfig#maxMainComputerTime}.
|
* Move this executor forward one tick, replenishing the budget by {@link MainThreadConfig#maxComputerTime()}.
|
||||||
*
|
*
|
||||||
* @return Whether this executor has cooled down, and so is safe to run again.
|
* @return Whether this executor has cooled down, and so is safe to run again.
|
||||||
*/
|
*/
|
||||||
boolean tickCooling() {
|
boolean tickCooling() {
|
||||||
state = State.COOLING;
|
state = State.COOLING;
|
||||||
currentTick = scheduler.currentTick();
|
currentTick = scheduler.currentTick();
|
||||||
budget = Math.min(budget + CoreConfig.maxMainComputerTime, CoreConfig.maxMainComputerTime);
|
var maxTime = scheduler.config.maxComputerTime();
|
||||||
if (budget < CoreConfig.maxMainComputerTime) return false;
|
budget = Math.min(budget + maxTime, maxTime);
|
||||||
|
if (budget < maxTime) return false;
|
||||||
|
|
||||||
state = State.COOL;
|
state = State.COOL;
|
||||||
synchronized (queueLock) {
|
synchronized (queueLock) {
|
||||||
|
@ -10,8 +10,8 @@ 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.ComputerContext;
|
||||||
import dan200.computercraft.core.CoreConfig;
|
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThread;
|
import dan200.computercraft.core.computer.mainthread.MainThread;
|
||||||
|
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import dan200.computercraft.test.core.computer.BasicEnvironment;
|
import dan200.computercraft.test.core.computer.BasicEnvironment;
|
||||||
import dan200.computercraft.test.core.filesystem.MemoryMount;
|
import dan200.computercraft.test.core.filesystem.MemoryMount;
|
||||||
@ -46,10 +46,8 @@ public class ComputerBootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void run(WritableMount mount, Consumer<Computer> setup, int maxTicks) {
|
public static void run(WritableMount mount, Consumer<Computer> setup, int maxTicks) {
|
||||||
CoreConfig.maxMainComputerTime = CoreConfig.maxMainGlobalTime = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
var term = new Terminal(51, 19, true);
|
var term = new Terminal(51, 19, true);
|
||||||
var mainThread = new MainThread();
|
var mainThread = new MainThread(new MainThreadConfig.Basic(Integer.MAX_VALUE, Integer.MAX_VALUE));
|
||||||
var environment = new BasicEnvironment(mount);
|
var environment = new BasicEnvironment(mount);
|
||||||
var context = new ComputerContext(environment, 1, mainThread);
|
var context = new ComputerContext(environment, 1, mainThread);
|
||||||
final var computer = new Computer(context, environment, term, 0);
|
final var computer = new Computer(context, environment, term, 0);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package dan200.computercraft.core.terminal;
|
package dan200.computercraft.core.terminal;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package dan200.computercraft.core.terminal;
|
package dan200.computercraft.core.terminal;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package dan200.computercraft.test.core;
|
package dan200.computercraft.test.core;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package dan200.computercraft.test.core.terminal;
|
package dan200.computercraft.test.core.terminal;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user