mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-14 20:20:30 +00:00
Update Cobalt to 0.7
- Timeouts are now driven by an interrupt system, rather than polling. While we do not impose memory limits, this should close #1333. - Update the table library to largely match Lua 5.4: - Add table.move - Table methods (with the exception of foreach/foreachi) now use metamethods (closes #1088). There's still some remaining quirks (for instance, table.insert accepts values out-of-bounds), but I think that's fine. - Cobalt's threaded-coroutine system is gone (load now supports yielding), so we no longer track coroutine metrics. - Type errors now use __name. See #1355, though this does not apply to CC methods (either on the Java or CraftOS side), so is not enough to resolve it. See https://github.com/SquidDev/Cobalt/compare/v0.6.0...v0.7.0 for the full delta.
This commit is contained in:
parent
0046b095b1
commit
5bb2e8e8cd
@ -21,7 +21,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| Hex literal fractional/exponent parts | ✔ | |
|
| Hex literal fractional/exponent parts | ✔ | |
|
||||||
| Empty statements | ❌ | |
|
| Empty statements | ❌ | |
|
||||||
| `__len` metamethod | ✔ | |
|
| `__len` metamethod | ✔ | |
|
||||||
| `__ipairs` metamethod | ❌ | |
|
| `__ipairs` metamethod | ❌ | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead. |
|
||||||
| `__pairs` metamethod | ✔ | |
|
| `__pairs` metamethod | ✔ | |
|
||||||
| `bit32` library | ✔ | |
|
| `bit32` library | ✔ | |
|
||||||
| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. |
|
| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. |
|
||||||
@ -32,8 +32,8 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| `rawlen` function | ✔ | |
|
| `rawlen` function | ✔ | |
|
||||||
| Negative index to `select` | ✔ | |
|
| Negative index to `select` | ✔ | |
|
||||||
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
||||||
| Arguments to `xpcall` | ❌ | |
|
| Arguments to `xpcall` | ✔ | |
|
||||||
| Second return value from `coroutine.running` | ❌ | |
|
| Second return value from `coroutine.running` | ✔ | |
|
||||||
| Removed `module` | ✔ | |
|
| Removed `module` | ✔ | |
|
||||||
| `package.loaders` -> `package.searchers` | ❌ | |
|
| `package.loaders` -> `package.searchers` | ❌ | |
|
||||||
| Second argument to loader functions | ✔ | |
|
| Second argument to loader functions | ✔ | |
|
||||||
@ -41,7 +41,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| `package.searchpath` | ✔ | |
|
| `package.searchpath` | ✔ | |
|
||||||
| Removed `package.seeall` | ✔ | |
|
| Removed `package.seeall` | ✔ | |
|
||||||
| `string.dump` on functions with upvalues (blanks them out) | ✔ | |
|
| `string.dump` on functions with upvalues (blanks them out) | ✔ | |
|
||||||
| `string.rep` separator | ❌ | |
|
| `string.rep` separator | ✔ | |
|
||||||
| `%g` match group | ❌ | |
|
| `%g` match group | ❌ | |
|
||||||
| Removal of `%z` match group | ❌ | |
|
| Removal of `%z` match group | ❌ | |
|
||||||
| Removed `table.maxn` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
| Removed `table.maxn` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
||||||
@ -64,7 +64,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| Removal of ambiguity error | ❌ | |
|
| Removal of ambiguity error | ❌ | |
|
||||||
| Identifiers may no longer use locale-dependent letters | ✔ | |
|
| Identifiers may no longer use locale-dependent letters | ✔ | |
|
||||||
| Ephemeron tables | ❌ | |
|
| Ephemeron tables | ❌ | |
|
||||||
| Identical functions may be reused | ❌ | |
|
| Identical functions may be reused | ❌ | Removed in Lua 5.4 |
|
||||||
| Generational garbage collector | ❌ | Cobalt uses the built-in Java garbage collector. |
|
| Generational garbage collector | ❌ | Cobalt uses the built-in Java garbage collector. |
|
||||||
|
|
||||||
## Lua 5.3
|
## Lua 5.3
|
||||||
@ -75,10 +75,10 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| `\u{XXX}` escape sequence | ✔ | |
|
| `\u{XXX}` escape sequence | ✔ | |
|
||||||
| `utf8` library | ✔ | |
|
| `utf8` library | ✔ | |
|
||||||
| removed `__ipairs` metamethod | ✔ | |
|
| removed `__ipairs` metamethod | ✔ | |
|
||||||
| `coroutine.isyieldable` | ❌ | |
|
| `coroutine.isyieldable` | ✔ | |
|
||||||
| `string.dump` strip argument | ✔ | |
|
| `string.dump` strip argument | ✔ | |
|
||||||
| `string.pack`/`string.unpack`/`string.packsize` | ✔ | |
|
| `string.pack`/`string.unpack`/`string.packsize` | ✔ | |
|
||||||
| `table.move` | ❌ | |
|
| `table.move` | ✔ | |
|
||||||
| `math.atan2` -> `math.atan` | ❌ | |
|
| `math.atan2` -> `math.atan` | ❌ | |
|
||||||
| Removed `math.frexp`, `math.ldexp`, `math.pow`, `math.cosh`, `math.sinh`, `math.tanh` | ❌ | |
|
| Removed `math.frexp`, `math.ldexp`, `math.pow`, `math.cosh`, `math.sinh`, `math.tanh` | ❌ | |
|
||||||
| `math.maxinteger`/`math.mininteger` | ❌ | |
|
| `math.maxinteger`/`math.mininteger` | ❌ | |
|
||||||
@ -87,7 +87,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| `math.ult` | ❌ | |
|
| `math.ult` | ❌ | |
|
||||||
| Removed `bit32` library | ❌ | |
|
| Removed `bit32` library | ❌ | |
|
||||||
| Remove `*` from `file:read` modes | ✔ | |
|
| Remove `*` from `file:read` modes | ✔ | |
|
||||||
| Metamethods respected in `table.*`, `ipairs` | 🔶 | Only `__lt` is respected. |
|
| Metamethods respected in `table.*`, `ipairs` | ✔ | |
|
||||||
|
|
||||||
## Lua 5.0
|
## Lua 5.0
|
||||||
| Feature | Supported? | Notes |
|
| Feature | Supported? | Notes |
|
||||||
|
@ -19,7 +19,7 @@ parchmentMc = "1.19.3"
|
|||||||
asm = "9.3"
|
asm = "9.3"
|
||||||
autoService = "1.0.1"
|
autoService = "1.0.1"
|
||||||
checkerFramework = "3.32.0"
|
checkerFramework = "3.32.0"
|
||||||
cobalt = "0.6.0"
|
cobalt = "0.7.0"
|
||||||
fastutil = "8.5.9"
|
fastutil = "8.5.9"
|
||||||
guava = "31.1-jre"
|
guava = "31.1-jre"
|
||||||
jetbrainsAnnotations = "24.0.1"
|
jetbrainsAnnotations = "24.0.1"
|
||||||
|
@ -178,8 +178,6 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add(Metrics.HTTP_DOWNLOAD, "HTTP download");
|
add(Metrics.HTTP_DOWNLOAD, "HTTP download");
|
||||||
add(Metrics.WEBSOCKET_INCOMING, "Websocket incoming");
|
add(Metrics.WEBSOCKET_INCOMING, "Websocket incoming");
|
||||||
add(Metrics.WEBSOCKET_OUTGOING, "Websocket outgoing");
|
add(Metrics.WEBSOCKET_OUTGOING, "Websocket outgoing");
|
||||||
add(Metrics.COROUTINES_CREATED, "Coroutines created");
|
|
||||||
add(Metrics.COROUTINES_DISPOSED, "Coroutines disposed");
|
|
||||||
add(Metrics.TURTLE_OPS, "Turtle operations");
|
add(Metrics.TURTLE_OPS, "Turtle operations");
|
||||||
|
|
||||||
add(AggregatedMetric.TRANSLATION_PREFIX + Aggregate.MAX.id(), "%s (max)");
|
add(AggregatedMetric.TRANSLATION_PREFIX + Aggregate.MAX.id(), "%s (max)");
|
||||||
|
@ -4,9 +4,13 @@
|
|||||||
|
|
||||||
package dan200.computercraft.core.computer;
|
package dan200.computercraft.core.computer;
|
||||||
|
|
||||||
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
import dan200.computercraft.core.lua.ILuaMachine;
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
import dan200.computercraft.core.lua.MachineResult;
|
import dan200.computercraft.core.lua.MachineResult;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,6 +52,8 @@ public final class TimeoutState {
|
|||||||
public static final String ABORT_MESSAGE = "Too long without yielding";
|
public static final String ABORT_MESSAGE = "Too long without yielding";
|
||||||
|
|
||||||
private final ComputerThread scheduler;
|
private final ComputerThread scheduler;
|
||||||
|
@GuardedBy("this")
|
||||||
|
private final List<Runnable> listeners = new ArrayList<>(0);
|
||||||
|
|
||||||
private boolean paused;
|
private boolean paused;
|
||||||
private boolean softAbort;
|
private boolean softAbort;
|
||||||
@ -92,8 +98,15 @@ public final class TimeoutState {
|
|||||||
// Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we
|
// Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we
|
||||||
// need to handle overflow.
|
// need to handle overflow.
|
||||||
var now = System.nanoTime();
|
var now = System.nanoTime();
|
||||||
if (!paused) paused = currentDeadline - now <= 0 && scheduler.hasPendingWork(); // now >= currentDeadline
|
var changed = false;
|
||||||
if (!softAbort) softAbort = now - cumulativeStart - TIMEOUT >= 0; // now - cumulativeStart >= TIMEOUT
|
if (!paused && (paused = currentDeadline - now <= 0 && scheduler.hasPendingWork())) { // now >= currentDeadline
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (!softAbort && (softAbort = now - cumulativeStart - TIMEOUT >= 0)) { // now - cumulativeStart >= TIMEOUT
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) updateListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,6 +144,9 @@ public final class TimeoutState {
|
|||||||
*/
|
*/
|
||||||
void hardAbort() {
|
void hardAbort() {
|
||||||
softAbort = hardAbort = true;
|
softAbort = hardAbort = true;
|
||||||
|
synchronized (this) {
|
||||||
|
updateListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -153,6 +169,7 @@ public final class TimeoutState {
|
|||||||
// We set the cumulative time to difference between current time and "nominal start time".
|
// We set the cumulative time to difference between current time and "nominal start time".
|
||||||
cumulativeElapsed = System.nanoTime() - cumulativeStart;
|
cumulativeElapsed = System.nanoTime() - cumulativeStart;
|
||||||
paused = false;
|
paused = false;
|
||||||
|
updateListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,5 +178,22 @@ public final class TimeoutState {
|
|||||||
synchronized void stopTimer() {
|
synchronized void stopTimer() {
|
||||||
cumulativeElapsed = 0;
|
cumulativeElapsed = 0;
|
||||||
paused = softAbort = hardAbort = false;
|
paused = softAbort = hardAbort = false;
|
||||||
|
updateListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private void updateListeners() {
|
||||||
|
for (var listener : listeners) listener.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addListener(Runnable listener) {
|
||||||
|
Objects.requireNonNull(listener, "listener cannot be null");
|
||||||
|
listeners.add(listener);
|
||||||
|
listener.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeListener(Runnable listener) {
|
||||||
|
Objects.requireNonNull(listener, "listener cannot be null");
|
||||||
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,19 +13,15 @@ import dan200.computercraft.core.Logging;
|
|||||||
import dan200.computercraft.core.asm.LuaMethod;
|
import dan200.computercraft.core.asm.LuaMethod;
|
||||||
import dan200.computercraft.core.asm.ObjectSource;
|
import dan200.computercraft.core.asm.ObjectSource;
|
||||||
import dan200.computercraft.core.computer.TimeoutState;
|
import dan200.computercraft.core.computer.TimeoutState;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
|
||||||
import dan200.computercraft.core.util.Nullability;
|
import dan200.computercraft.core.util.Nullability;
|
||||||
import dan200.computercraft.core.util.ThreadUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.squiddev.cobalt.*;
|
import org.squiddev.cobalt.*;
|
||||||
import org.squiddev.cobalt.compiler.CompileException;
|
import org.squiddev.cobalt.compiler.CompileException;
|
||||||
import org.squiddev.cobalt.compiler.LoadState;
|
import org.squiddev.cobalt.compiler.LoadState;
|
||||||
import org.squiddev.cobalt.debug.DebugFrame;
|
import org.squiddev.cobalt.interrupt.InterruptAction;
|
||||||
import org.squiddev.cobalt.debug.DebugHandler;
|
import org.squiddev.cobalt.lib.Bit32Lib;
|
||||||
import org.squiddev.cobalt.debug.DebugState;
|
import org.squiddev.cobalt.lib.CoreLibraries;
|
||||||
import org.squiddev.cobalt.lib.*;
|
|
||||||
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -33,56 +29,41 @@ import java.io.InputStream;
|
|||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.SynchronousQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.squiddev.cobalt.ValueFactory.valueOf;
|
import static org.squiddev.cobalt.ValueFactory.valueOf;
|
||||||
import static org.squiddev.cobalt.ValueFactory.varargsOf;
|
import static org.squiddev.cobalt.ValueFactory.varargsOf;
|
||||||
import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKED;
|
|
||||||
import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKYIELD;
|
|
||||||
|
|
||||||
public class CobaltLuaMachine implements ILuaMachine {
|
public class CobaltLuaMachine implements ILuaMachine {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CobaltLuaMachine.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CobaltLuaMachine.class);
|
||||||
|
|
||||||
private static final ThreadPoolExecutor COROUTINES = new ThreadPoolExecutor(
|
|
||||||
0, Integer.MAX_VALUE,
|
|
||||||
5L, TimeUnit.MINUTES,
|
|
||||||
new SynchronousQueue<>(),
|
|
||||||
ThreadUtils.factory("Coroutine")
|
|
||||||
);
|
|
||||||
|
|
||||||
private static final LuaMethod FUNCTION_METHOD = (target, context, args) -> ((ILuaFunction) target).call(args);
|
private static final LuaMethod FUNCTION_METHOD = (target, context, args) -> ((ILuaFunction) target).call(args);
|
||||||
|
|
||||||
private final TimeoutState timeout;
|
private final TimeoutState timeout;
|
||||||
private final TimeoutDebugHandler debug;
|
private final Runnable timeoutListener = this::updateTimeout;
|
||||||
private final ILuaContext context;
|
private final ILuaContext context;
|
||||||
|
|
||||||
private @Nullable LuaState state;
|
private final LuaState state;
|
||||||
private @Nullable LuaTable globals;
|
private final LuaThread mainRoutine;
|
||||||
|
|
||||||
|
private volatile boolean isDisposed = false;
|
||||||
|
private boolean thrownSoftAbort;
|
||||||
|
|
||||||
private @Nullable LuaThread mainRoutine = null;
|
|
||||||
private @Nullable String eventFilter = null;
|
private @Nullable String eventFilter = null;
|
||||||
|
|
||||||
public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
|
public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
|
||||||
timeout = environment.timeout();
|
timeout = environment.timeout();
|
||||||
context = environment.context();
|
context = environment.context();
|
||||||
debug = new TimeoutDebugHandler();
|
|
||||||
|
|
||||||
// Create an environment to run in
|
// Create an environment to run in
|
||||||
var metrics = environment.metrics();
|
|
||||||
var state = this.state = LuaState.builder()
|
var state = this.state = LuaState.builder()
|
||||||
.resourceManipulator(new VoidResourceManipulator())
|
.interruptHandler(() -> {
|
||||||
.debug(debug)
|
if (timeout.isHardAborted() || isDisposed) throw new HardAbortError();
|
||||||
.coroutineExecutor(command -> {
|
if (timeout.isSoftAborted() && !thrownSoftAbort) {
|
||||||
metrics.observe(Metrics.COROUTINES_CREATED);
|
thrownSoftAbort = true;
|
||||||
COROUTINES.execute(() -> {
|
throw new LuaError(TimeoutState.ABORT_MESSAGE);
|
||||||
try {
|
}
|
||||||
command.run();
|
|
||||||
} finally {
|
return timeout.isPaused() ? InterruptAction.SUSPEND : InterruptAction.CONTINUE;
|
||||||
metrics.observe(Metrics.COROUTINES_DISPOSED);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.errorReporter((e, msg) -> {
|
.errorReporter((e, msg) -> {
|
||||||
if (LOG.isErrorEnabled(Logging.VM_ERROR)) {
|
if (LOG.isErrorEnabled(Logging.VM_ERROR)) {
|
||||||
@ -91,35 +72,16 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
globals = new LuaTable();
|
// Set up our global table.
|
||||||
state.setupThread(globals);
|
var globals = state.getMainThread().getfenv();
|
||||||
|
CoreLibraries.debugGlobals(state);
|
||||||
// Add basic libraries
|
Bit32Lib.add(state, globals);
|
||||||
globals.load(state, new BaseLib());
|
|
||||||
globals.load(state, new TableLib());
|
|
||||||
globals.load(state, new StringLib());
|
|
||||||
globals.load(state, new MathLib());
|
|
||||||
globals.load(state, new CoroutineLib());
|
|
||||||
globals.load(state, new Bit32Lib());
|
|
||||||
globals.load(state, new Utf8Lib());
|
|
||||||
globals.load(state, new DebugLib());
|
|
||||||
|
|
||||||
// Remove globals we don't want to expose
|
|
||||||
globals.rawset("collectgarbage", Constants.NIL);
|
|
||||||
globals.rawset("dofile", Constants.NIL);
|
|
||||||
globals.rawset("loadfile", Constants.NIL);
|
|
||||||
globals.rawset("print", Constants.NIL);
|
|
||||||
|
|
||||||
// Add version globals
|
|
||||||
globals.rawset("_VERSION", valueOf("Lua 5.1"));
|
|
||||||
globals.rawset("_HOST", valueOf(environment.hostString()));
|
globals.rawset("_HOST", valueOf(environment.hostString()));
|
||||||
globals.rawset("_CC_DEFAULT_SETTINGS", valueOf(CoreConfig.defaultComputerSettings));
|
globals.rawset("_CC_DEFAULT_SETTINGS", valueOf(CoreConfig.defaultComputerSettings));
|
||||||
if (CoreConfig.disableLua51Features) {
|
if (CoreConfig.disableLua51Features) globals.rawset("_CC_DISABLE_LUA51_FEATURES", Constants.TRUE);
|
||||||
globals.rawset("_CC_DISABLE_LUA51_FEATURES", Constants.TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add default APIs
|
// Add default APIs
|
||||||
for (var api : environment.apis()) addAPI(api);
|
for (var api : environment.apis()) addAPI(globals, api);
|
||||||
|
|
||||||
// And load the BIOS
|
// And load the BIOS
|
||||||
try {
|
try {
|
||||||
@ -128,11 +90,11 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
} catch (CompileException e) {
|
} catch (CompileException e) {
|
||||||
throw new MachineException(Nullability.assertNonNull(e.getMessage()));
|
throw new MachineException(Nullability.assertNonNull(e.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timeout.addListener(timeoutListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAPI(ILuaAPI api) {
|
private void addAPI(LuaTable globals, ILuaAPI api) {
|
||||||
if (globals == null) throw new IllegalStateException("Machine has been closed");
|
|
||||||
|
|
||||||
// Add the methods of an API to the global table
|
// Add the methods of an API to the global table
|
||||||
var table = wrapLuaObject(api);
|
var table = wrapLuaObject(api);
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
@ -144,42 +106,41 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
for (var name : names) globals.rawset(name, table);
|
for (var name : names) globals.rawset(name, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateTimeout() {
|
||||||
|
if (isDisposed) return;
|
||||||
|
if (!timeout.isSoftAborted()) thrownSoftAbort = false;
|
||||||
|
if (timeout.isSoftAborted() || timeout.isPaused()) state.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MachineResult handleEvent(@Nullable String eventName, @Nullable Object[] arguments) {
|
public MachineResult handleEvent(@Nullable String eventName, @Nullable Object[] arguments) {
|
||||||
if (mainRoutine == null || state == null) throw new IllegalStateException("Machine has been closed");
|
if (isDisposed) throw new IllegalStateException("Machine has been closed");
|
||||||
|
|
||||||
if (eventFilter != null && eventName != null && !eventName.equals(eventFilter) && !eventName.equals("terminate")) {
|
if (eventFilter != null && eventName != null && !eventName.equals(eventFilter) && !eventName.equals("terminate")) {
|
||||||
return MachineResult.OK;
|
return MachineResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the soft abort has been cleared then we can reset our flag.
|
|
||||||
timeout.refresh();
|
|
||||||
if (!timeout.isSoftAborted()) debug.thrownSoftAbort = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Varargs resumeArgs = Constants.NONE;
|
var resumeArgs = eventName == null ? Constants.NONE : varargsOf(valueOf(eventName), toValues(arguments));
|
||||||
if (eventName != null) {
|
|
||||||
resumeArgs = varargsOf(valueOf(eventName), toValues(arguments));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume the current thread, or the main one when first starting off.
|
// Resume the current thread, or the main one when first starting off.
|
||||||
var thread = state.getCurrentThread();
|
var thread = state.getCurrentThread();
|
||||||
if (thread == null || thread == state.getMainThread()) thread = mainRoutine;
|
if (thread == null || thread == state.getMainThread()) thread = mainRoutine;
|
||||||
|
|
||||||
var results = LuaThread.run(thread, resumeArgs);
|
var results = LuaThread.run(thread, resumeArgs);
|
||||||
if (timeout.isHardAborted()) throw HardAbortError.INSTANCE;
|
if (timeout.isHardAborted()) throw new HardAbortError();
|
||||||
if (results == null) return MachineResult.PAUSE;
|
if (results == null) return MachineResult.PAUSE;
|
||||||
|
|
||||||
var filter = results.first();
|
var filter = results.first();
|
||||||
eventFilter = filter.isString() ? filter.toString() : null;
|
eventFilter = filter.isString() ? filter.toString() : null;
|
||||||
|
|
||||||
if (mainRoutine.getStatus().equals("dead")) {
|
if (!mainRoutine.isAlive()) {
|
||||||
close();
|
close();
|
||||||
return MachineResult.GENERIC_ERROR;
|
return MachineResult.GENERIC_ERROR;
|
||||||
} else {
|
} else {
|
||||||
return MachineResult.OK;
|
return MachineResult.OK;
|
||||||
}
|
}
|
||||||
} catch (HardAbortError | InterruptedException e) {
|
} catch (HardAbortError e) {
|
||||||
close();
|
close();
|
||||||
return MachineResult.TIMEOUT;
|
return MachineResult.TIMEOUT;
|
||||||
} catch (LuaError e) {
|
} catch (LuaError e) {
|
||||||
@ -191,23 +152,13 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void printExecutionState(StringBuilder out) {
|
public void printExecutionState(StringBuilder out) {
|
||||||
var state = this.state;
|
|
||||||
if (state == null) {
|
|
||||||
out.append("CobaltLuaMachine is terminated\n");
|
|
||||||
} else {
|
|
||||||
state.printExecutionState(out);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
var state = this.state;
|
isDisposed = true;
|
||||||
if (state == null) return;
|
state.interrupt();
|
||||||
|
timeout.removeListener(timeoutListener);
|
||||||
state.abandon();
|
|
||||||
mainRoutine = null;
|
|
||||||
this.state = null;
|
|
||||||
globals = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -228,7 +179,7 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
: new ResultInterpreterFunction(this, method.getMethod(), instance, context, method.getName())));
|
: new ResultInterpreterFunction(this, method.getMethod(), instance, context, method.getName())));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (table.keyCount() == 0) return null;
|
if (table.next(Constants.NIL).first().isNil()) return null;
|
||||||
} catch (LuaError ignored) {
|
} catch (LuaError ignored) {
|
||||||
// next should never throw on nil.
|
// next should never throw on nil.
|
||||||
}
|
}
|
||||||
@ -241,9 +192,7 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
if (object instanceof Number num) return valueOf(num.doubleValue());
|
if (object instanceof Number num) return valueOf(num.doubleValue());
|
||||||
if (object instanceof Boolean bool) return valueOf(bool);
|
if (object instanceof Boolean bool) return valueOf(bool);
|
||||||
if (object instanceof String str) return valueOf(str);
|
if (object instanceof String str) return valueOf(str);
|
||||||
if (object instanceof byte[] b) {
|
if (object instanceof byte[] b) return valueOf(Arrays.copyOf(b, b.length));
|
||||||
return valueOf(Arrays.copyOf(b, b.length));
|
|
||||||
}
|
|
||||||
if (object instanceof ByteBuffer b) {
|
if (object instanceof ByteBuffer b) {
|
||||||
var bytes = new byte[b.remaining()];
|
var bytes = new byte[b.remaining()];
|
||||||
b.get(bytes);
|
b.get(bytes);
|
||||||
@ -317,25 +266,19 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static Object toObject(LuaValue value, @Nullable IdentityHashMap<LuaValue, Object> objects) {
|
static Object toObject(LuaValue value, @Nullable IdentityHashMap<LuaValue, Object> objects) {
|
||||||
switch (value.type()) {
|
return switch (value.type()) {
|
||||||
case Constants.TNIL:
|
case Constants.TNIL -> null;
|
||||||
case Constants.TNONE:
|
case Constants.TINT, Constants.TNUMBER -> value.toDouble();
|
||||||
return null;
|
case Constants.TBOOLEAN -> value.toBoolean();
|
||||||
case Constants.TINT:
|
case Constants.TSTRING -> value.toString();
|
||||||
case Constants.TNUMBER:
|
case Constants.TTABLE -> {
|
||||||
return value.toDouble();
|
|
||||||
case Constants.TBOOLEAN:
|
|
||||||
return value.toBoolean();
|
|
||||||
case Constants.TSTRING:
|
|
||||||
return value.toString();
|
|
||||||
case Constants.TTABLE: {
|
|
||||||
// Table:
|
// Table:
|
||||||
// Start remembering stuff
|
// Start remembering stuff
|
||||||
if (objects == null) {
|
if (objects == null) {
|
||||||
objects = new IdentityHashMap<>(1);
|
objects = new IdentityHashMap<>(1);
|
||||||
} else {
|
} else {
|
||||||
var existing = objects.get(value);
|
var existing = objects.get(value);
|
||||||
if (existing != null) return existing;
|
if (existing != null) yield existing;
|
||||||
}
|
}
|
||||||
Map<Object, Object> table = new HashMap<>();
|
Map<Object, Object> table = new HashMap<>();
|
||||||
objects.put(value, table);
|
objects.put(value, table);
|
||||||
@ -361,11 +304,10 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
table.put(keyObject, valueObject);
|
table.put(keyObject, valueObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return table;
|
yield table;
|
||||||
}
|
}
|
||||||
default:
|
default -> null;
|
||||||
return null;
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Object[] toObjects(Varargs values) {
|
static Object[] toObjects(Varargs values) {
|
||||||
@ -375,83 +317,12 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly.
|
|
||||||
*/
|
|
||||||
private class TimeoutDebugHandler extends DebugHandler {
|
|
||||||
private final TimeoutState timeout;
|
|
||||||
private int count = 0;
|
|
||||||
boolean thrownSoftAbort;
|
|
||||||
|
|
||||||
private boolean isPaused;
|
|
||||||
private int oldFlags;
|
|
||||||
private boolean oldInHook;
|
|
||||||
|
|
||||||
TimeoutDebugHandler() {
|
|
||||||
timeout = CobaltLuaMachine.this.timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInstruction(DebugState ds, DebugFrame di, int pc) throws LuaError, UnwindThrowable {
|
|
||||||
di.pc = pc;
|
|
||||||
|
|
||||||
if (isPaused) resetPaused(ds, di);
|
|
||||||
|
|
||||||
// We check our current pause/abort state every 128 instructions.
|
|
||||||
if ((count = (count + 1) & 127) == 0) {
|
|
||||||
if (timeout.isHardAborted() || state == null) throw HardAbortError.INSTANCE;
|
|
||||||
if (timeout.isPaused()) handlePause(ds, di);
|
|
||||||
if (timeout.isSoftAborted()) handleSoftAbort();
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onInstruction(ds, di, pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void poll() throws LuaError {
|
|
||||||
var state = CobaltLuaMachine.this.state;
|
|
||||||
if (timeout.isHardAborted() || state == null) throw HardAbortError.INSTANCE;
|
|
||||||
if (timeout.isPaused()) LuaThread.suspendBlocking(state);
|
|
||||||
if (timeout.isSoftAborted()) handleSoftAbort();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetPaused(DebugState ds, DebugFrame di) {
|
|
||||||
// Restore the previous paused state
|
|
||||||
isPaused = false;
|
|
||||||
ds.inhook = oldInHook;
|
|
||||||
di.flags = oldFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSoftAbort() throws LuaError {
|
|
||||||
// If we already thrown our soft abort error then don't do it again.
|
|
||||||
if (thrownSoftAbort) return;
|
|
||||||
|
|
||||||
thrownSoftAbort = true;
|
|
||||||
throw new LuaError(TimeoutState.ABORT_MESSAGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handlePause(DebugState ds, DebugFrame di) throws LuaError, UnwindThrowable {
|
|
||||||
// Preserve the current state
|
|
||||||
isPaused = true;
|
|
||||||
oldInHook = ds.inhook;
|
|
||||||
oldFlags = di.flags;
|
|
||||||
|
|
||||||
// Suspend the state. This will probably throw, but we need to handle the case where it won't.
|
|
||||||
di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED;
|
|
||||||
LuaThread.suspend(ds.getLuaState());
|
|
||||||
resetPaused(ds, di);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class HardAbortError extends Error {
|
private static final class HardAbortError extends Error {
|
||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 7954092008586367501L;
|
private static final long serialVersionUID = 7954092008586367501L;
|
||||||
|
|
||||||
@SuppressWarnings("StaticAssignmentOfThrowable")
|
|
||||||
static final HardAbortError INSTANCE = new HardAbortError();
|
|
||||||
|
|
||||||
private HardAbortError() {
|
private HardAbortError() {
|
||||||
super("Hard Abort", null, true, false);
|
super("Hard Abort");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,7 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
|
|||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
checkValid();
|
checkValid();
|
||||||
try {
|
return table.size();
|
||||||
return table.keyCount();
|
|
||||||
} catch (LuaError e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,20 +73,16 @@ final class VarargArguments implements IArguments {
|
|||||||
@Override
|
@Override
|
||||||
public ByteBuffer getBytes(int index) throws LuaException {
|
public ByteBuffer getBytes(int index) throws LuaException {
|
||||||
var value = varargs.arg(index + 1);
|
var value = varargs.arg(index + 1);
|
||||||
if (!(value instanceof LuaBaseString)) throw LuaValues.badArgument(index, "string", value.typeName());
|
if (!(value instanceof LuaString str)) throw LuaValues.badArgument(index, "string", value.typeName());
|
||||||
|
return str.toBuffer();
|
||||||
var str = ((LuaBaseString) value).strvalue();
|
|
||||||
return ByteBuffer.wrap(str.bytes, str.offset, str.length).asReadOnlyBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ByteBuffer> optBytes(int index) throws LuaException {
|
public Optional<ByteBuffer> optBytes(int index) throws LuaException {
|
||||||
var value = varargs.arg(index + 1);
|
var value = varargs.arg(index + 1);
|
||||||
if (value.isNil()) return Optional.empty();
|
if (value.isNil()) return Optional.empty();
|
||||||
if (!(value instanceof LuaBaseString)) throw LuaValues.badArgument(index, "string", value.typeName());
|
if (!(value instanceof LuaString str)) throw LuaValues.badArgument(index, "string", value.typeName());
|
||||||
|
return Optional.of(str.toBuffer());
|
||||||
var str = ((LuaBaseString) value).strvalue();
|
|
||||||
return Optional.of(ByteBuffer.wrap(str.bytes, str.offset, str.length).asReadOnlyBuffer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,9 +24,6 @@ public final class Metrics {
|
|||||||
public static final Metric.Event WEBSOCKET_INCOMING = new Metric.Event("websocket_incoming", "bytes", Metric::formatBytes);
|
public static final Metric.Event WEBSOCKET_INCOMING = new Metric.Event("websocket_incoming", "bytes", Metric::formatBytes);
|
||||||
public static final Metric.Event WEBSOCKET_OUTGOING = new Metric.Event("websocket_outgoing", "bytes", Metric::formatBytes);
|
public static final Metric.Event WEBSOCKET_OUTGOING = new Metric.Event("websocket_outgoing", "bytes", Metric::formatBytes);
|
||||||
|
|
||||||
public static final Metric.Counter COROUTINES_CREATED = new Metric.Counter("coroutines_created");
|
|
||||||
public static final Metric.Counter COROUTINES_DISPOSED = new Metric.Counter("coroutines_dead");
|
|
||||||
|
|
||||||
public static final Metric.Counter TURTLE_OPS = new Metric.Counter("turtle_ops");
|
public static final Metric.Counter TURTLE_OPS = new Metric.Counter("turtle_ops");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,11 +27,12 @@ import org.opentest4j.AssertionFailedError;
|
|||||||
import org.opentest4j.TestAbortedException;
|
import org.opentest4j.TestAbortedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.squiddev.cobalt.*;
|
import org.squiddev.cobalt.LuaState;
|
||||||
|
import org.squiddev.cobalt.LuaString;
|
||||||
|
import org.squiddev.cobalt.LuaThread;
|
||||||
import org.squiddev.cobalt.debug.DebugFrame;
|
import org.squiddev.cobalt.debug.DebugFrame;
|
||||||
import org.squiddev.cobalt.debug.DebugHook;
|
import org.squiddev.cobalt.debug.DebugHook;
|
||||||
import org.squiddev.cobalt.debug.DebugState;
|
import org.squiddev.cobalt.debug.DebugState;
|
||||||
import org.squiddev.cobalt.function.OneArgFunction;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -480,13 +481,8 @@ public class ComputerTestDelegate {
|
|||||||
CoverageLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
|
CoverageLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
|
||||||
super(environment, bios);
|
super(environment, bios);
|
||||||
|
|
||||||
LuaTable globals;
|
|
||||||
LuaThread mainRoutine;
|
LuaThread mainRoutine;
|
||||||
try {
|
try {
|
||||||
var globalField = CobaltLuaMachine.class.getDeclaredField("globals");
|
|
||||||
globalField.setAccessible(true);
|
|
||||||
globals = (LuaTable) globalField.get(this);
|
|
||||||
|
|
||||||
var threadField = CobaltLuaMachine.class.getDeclaredField("mainRoutine");
|
var threadField = CobaltLuaMachine.class.getDeclaredField("mainRoutine");
|
||||||
threadField.setAccessible(true);
|
threadField.setAccessible(true);
|
||||||
mainRoutine = (LuaThread) threadField.get(this);
|
mainRoutine = (LuaThread) threadField.get(this);
|
||||||
@ -513,21 +509,13 @@ public class ComputerTestDelegate {
|
|||||||
if (frame.closure == null) return;
|
if (frame.closure == null) return;
|
||||||
|
|
||||||
var proto = frame.closure.getPrototype();
|
var proto = frame.closure.getPrototype();
|
||||||
if (!proto.source.startsWith('@')) return;
|
if (!proto.source.startsWith((byte) '@')) return;
|
||||||
|
|
||||||
var map = coverage.computeIfAbsent(proto.source, x -> new Int2IntArrayMap());
|
var map = coverage.computeIfAbsent(proto.source, x -> new Int2IntArrayMap());
|
||||||
map.put(newLine, map.get(newLine) + 1);
|
map.put(newLine, map.get(newLine) + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
((LuaTable) globals.rawget("coroutine")).rawset("create", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaState state, LuaValue arg) throws LuaError {
|
|
||||||
var thread = new LuaThread(state, arg.checkFunction(), state.getCurrentThread().getfenv());
|
|
||||||
thread.getDebugState().setHook(hook, false, true, false, 0);
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mainRoutine.getDebugState().setHook(hook, false, true, false, 0);
|
mainRoutine.getDebugState().setHook(hook, false, true, false, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user