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 | ✔ | |
|
||||
| Empty statements | ❌ | |
|
||||
| `__len` metamethod | ✔ | |
|
||||
| `__ipairs` metamethod | ❌ | |
|
||||
| `__ipairs` metamethod | ❌ | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead. |
|
||||
| `__pairs` metamethod | ✔ | |
|
||||
| `bit32` library | ✔ | |
|
||||
| `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 | ✔ | |
|
||||
| Negative index to `select` | ✔ | |
|
||||
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
||||
| Arguments to `xpcall` | ❌ | |
|
||||
| Second return value from `coroutine.running` | ❌ | |
|
||||
| Arguments to `xpcall` | ✔ | |
|
||||
| Second return value from `coroutine.running` | ✔ | |
|
||||
| Removed `module` | ✔ | |
|
||||
| `package.loaders` -> `package.searchers` | ❌ | |
|
||||
| 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` | ✔ | |
|
||||
| Removed `package.seeall` | ✔ | |
|
||||
| `string.dump` on functions with upvalues (blanks them out) | ✔ | |
|
||||
| `string.rep` separator | ❌ | |
|
||||
| `string.rep` separator | ✔ | |
|
||||
| `%g` match group | ❌ | |
|
||||
| Removal of `%z` match group | ❌ | |
|
||||
| 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 | ❌ | |
|
||||
| Identifiers may no longer use locale-dependent letters | ✔ | |
|
||||
| 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. |
|
||||
|
||||
## 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 | ✔ | |
|
||||
| `utf8` library | ✔ | |
|
||||
| removed `__ipairs` metamethod | ✔ | |
|
||||
| `coroutine.isyieldable` | ❌ | |
|
||||
| `coroutine.isyieldable` | ✔ | |
|
||||
| `string.dump` strip argument | ✔ | |
|
||||
| `string.pack`/`string.unpack`/`string.packsize` | ✔ | |
|
||||
| `table.move` | ❌ | |
|
||||
| `table.move` | ✔ | |
|
||||
| `math.atan2` -> `math.atan` | ❌ | |
|
||||
| Removed `math.frexp`, `math.ldexp`, `math.pow`, `math.cosh`, `math.sinh`, `math.tanh` | ❌ | |
|
||||
| `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` | ❌ | |
|
||||
| Removed `bit32` library | ❌ | |
|
||||
| Remove `*` from `file:read` modes | ✔ | |
|
||||
| Metamethods respected in `table.*`, `ipairs` | 🔶 | Only `__lt` is respected. |
|
||||
| Metamethods respected in `table.*`, `ipairs` | ✔ | |
|
||||
|
||||
## Lua 5.0
|
||||
| Feature | Supported? | Notes |
|
||||
|
@ -19,7 +19,7 @@ parchmentMc = "1.19.3"
|
||||
asm = "9.3"
|
||||
autoService = "1.0.1"
|
||||
checkerFramework = "3.32.0"
|
||||
cobalt = "0.6.0"
|
||||
cobalt = "0.7.0"
|
||||
fastutil = "8.5.9"
|
||||
guava = "31.1-jre"
|
||||
jetbrainsAnnotations = "24.0.1"
|
||||
|
@ -178,8 +178,6 @@ public final class LanguageProvider implements DataProvider {
|
||||
add(Metrics.HTTP_DOWNLOAD, "HTTP download");
|
||||
add(Metrics.WEBSOCKET_INCOMING, "Websocket incoming");
|
||||
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(AggregatedMetric.TRANSLATION_PREFIX + Aggregate.MAX.id(), "%s (max)");
|
||||
|
@ -4,9 +4,13 @@
|
||||
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.lua.MachineResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@ -48,6 +52,8 @@ public final class TimeoutState {
|
||||
public static final String ABORT_MESSAGE = "Too long without yielding";
|
||||
|
||||
private final ComputerThread scheduler;
|
||||
@GuardedBy("this")
|
||||
private final List<Runnable> listeners = new ArrayList<>(0);
|
||||
|
||||
private boolean paused;
|
||||
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
|
||||
// need to handle overflow.
|
||||
var now = System.nanoTime();
|
||||
if (!paused) paused = currentDeadline - now <= 0 && scheduler.hasPendingWork(); // now >= currentDeadline
|
||||
if (!softAbort) softAbort = now - cumulativeStart - TIMEOUT >= 0; // now - cumulativeStart >= TIMEOUT
|
||||
var changed = false;
|
||||
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() {
|
||||
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".
|
||||
cumulativeElapsed = System.nanoTime() - cumulativeStart;
|
||||
paused = false;
|
||||
updateListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,5 +178,22 @@ public final class TimeoutState {
|
||||
synchronized void stopTimer() {
|
||||
cumulativeElapsed = 0;
|
||||
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.ObjectSource;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.core.util.ThreadUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.squiddev.cobalt.*;
|
||||
import org.squiddev.cobalt.compiler.CompileException;
|
||||
import org.squiddev.cobalt.compiler.LoadState;
|
||||
import org.squiddev.cobalt.debug.DebugFrame;
|
||||
import org.squiddev.cobalt.debug.DebugHandler;
|
||||
import org.squiddev.cobalt.debug.DebugState;
|
||||
import org.squiddev.cobalt.lib.*;
|
||||
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
|
||||
import org.squiddev.cobalt.interrupt.InterruptAction;
|
||||
import org.squiddev.cobalt.lib.Bit32Lib;
|
||||
import org.squiddev.cobalt.lib.CoreLibraries;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
@ -33,56 +29,41 @@ import java.io.InputStream;
|
||||
import java.io.Serial;
|
||||
import java.nio.ByteBuffer;
|
||||
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.varargsOf;
|
||||
import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKED;
|
||||
import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKYIELD;
|
||||
|
||||
public class CobaltLuaMachine implements ILuaMachine {
|
||||
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 final TimeoutState timeout;
|
||||
private final TimeoutDebugHandler debug;
|
||||
private final Runnable timeoutListener = this::updateTimeout;
|
||||
private final ILuaContext context;
|
||||
|
||||
private @Nullable LuaState state;
|
||||
private @Nullable LuaTable globals;
|
||||
private final LuaState state;
|
||||
private final LuaThread mainRoutine;
|
||||
|
||||
private volatile boolean isDisposed = false;
|
||||
private boolean thrownSoftAbort;
|
||||
|
||||
private @Nullable LuaThread mainRoutine = null;
|
||||
private @Nullable String eventFilter = null;
|
||||
|
||||
public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
|
||||
timeout = environment.timeout();
|
||||
context = environment.context();
|
||||
debug = new TimeoutDebugHandler();
|
||||
|
||||
// Create an environment to run in
|
||||
var metrics = environment.metrics();
|
||||
var state = this.state = LuaState.builder()
|
||||
.resourceManipulator(new VoidResourceManipulator())
|
||||
.debug(debug)
|
||||
.coroutineExecutor(command -> {
|
||||
metrics.observe(Metrics.COROUTINES_CREATED);
|
||||
COROUTINES.execute(() -> {
|
||||
try {
|
||||
command.run();
|
||||
} finally {
|
||||
metrics.observe(Metrics.COROUTINES_DISPOSED);
|
||||
}
|
||||
});
|
||||
.interruptHandler(() -> {
|
||||
if (timeout.isHardAborted() || isDisposed) throw new HardAbortError();
|
||||
if (timeout.isSoftAborted() && !thrownSoftAbort) {
|
||||
thrownSoftAbort = true;
|
||||
throw new LuaError(TimeoutState.ABORT_MESSAGE);
|
||||
}
|
||||
|
||||
return timeout.isPaused() ? InterruptAction.SUSPEND : InterruptAction.CONTINUE;
|
||||
})
|
||||
.errorReporter((e, msg) -> {
|
||||
if (LOG.isErrorEnabled(Logging.VM_ERROR)) {
|
||||
@ -91,35 +72,16 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
})
|
||||
.build();
|
||||
|
||||
globals = new LuaTable();
|
||||
state.setupThread(globals);
|
||||
|
||||
// Add basic libraries
|
||||
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"));
|
||||
// Set up our global table.
|
||||
var globals = state.getMainThread().getfenv();
|
||||
CoreLibraries.debugGlobals(state);
|
||||
Bit32Lib.add(state, globals);
|
||||
globals.rawset("_HOST", valueOf(environment.hostString()));
|
||||
globals.rawset("_CC_DEFAULT_SETTINGS", valueOf(CoreConfig.defaultComputerSettings));
|
||||
if (CoreConfig.disableLua51Features) {
|
||||
globals.rawset("_CC_DISABLE_LUA51_FEATURES", Constants.TRUE);
|
||||
}
|
||||
if (CoreConfig.disableLua51Features) globals.rawset("_CC_DISABLE_LUA51_FEATURES", Constants.TRUE);
|
||||
|
||||
// Add default APIs
|
||||
for (var api : environment.apis()) addAPI(api);
|
||||
for (var api : environment.apis()) addAPI(globals, api);
|
||||
|
||||
// And load the BIOS
|
||||
try {
|
||||
@ -128,11 +90,11 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
} catch (CompileException e) {
|
||||
throw new MachineException(Nullability.assertNonNull(e.getMessage()));
|
||||
}
|
||||
|
||||
timeout.addListener(timeoutListener);
|
||||
}
|
||||
|
||||
private void addAPI(ILuaAPI api) {
|
||||
if (globals == null) throw new IllegalStateException("Machine has been closed");
|
||||
|
||||
private void addAPI(LuaTable globals, ILuaAPI api) {
|
||||
// Add the methods of an API to the global table
|
||||
var table = wrapLuaObject(api);
|
||||
if (table == null) {
|
||||
@ -144,42 +106,41 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
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
|
||||
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")) {
|
||||
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 {
|
||||
Varargs resumeArgs = Constants.NONE;
|
||||
if (eventName != null) {
|
||||
resumeArgs = varargsOf(valueOf(eventName), toValues(arguments));
|
||||
}
|
||||
var resumeArgs = eventName == null ? Constants.NONE : varargsOf(valueOf(eventName), toValues(arguments));
|
||||
|
||||
// Resume the current thread, or the main one when first starting off.
|
||||
var thread = state.getCurrentThread();
|
||||
if (thread == null || thread == state.getMainThread()) thread = mainRoutine;
|
||||
|
||||
var results = LuaThread.run(thread, resumeArgs);
|
||||
if (timeout.isHardAborted()) throw HardAbortError.INSTANCE;
|
||||
if (timeout.isHardAborted()) throw new HardAbortError();
|
||||
if (results == null) return MachineResult.PAUSE;
|
||||
|
||||
var filter = results.first();
|
||||
eventFilter = filter.isString() ? filter.toString() : null;
|
||||
|
||||
if (mainRoutine.getStatus().equals("dead")) {
|
||||
if (!mainRoutine.isAlive()) {
|
||||
close();
|
||||
return MachineResult.GENERIC_ERROR;
|
||||
} else {
|
||||
return MachineResult.OK;
|
||||
}
|
||||
} catch (HardAbortError | InterruptedException e) {
|
||||
} catch (HardAbortError e) {
|
||||
close();
|
||||
return MachineResult.TIMEOUT;
|
||||
} catch (LuaError e) {
|
||||
@ -191,23 +152,13 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
|
||||
@Override
|
||||
public void printExecutionState(StringBuilder out) {
|
||||
var state = this.state;
|
||||
if (state == null) {
|
||||
out.append("CobaltLuaMachine is terminated\n");
|
||||
} else {
|
||||
state.printExecutionState(out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
var state = this.state;
|
||||
if (state == null) return;
|
||||
|
||||
state.abandon();
|
||||
mainRoutine = null;
|
||||
this.state = null;
|
||||
globals = null;
|
||||
isDisposed = true;
|
||||
state.interrupt();
|
||||
timeout.removeListener(timeoutListener);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -228,7 +179,7 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
: new ResultInterpreterFunction(this, method.getMethod(), instance, context, method.getName())));
|
||||
|
||||
try {
|
||||
if (table.keyCount() == 0) return null;
|
||||
if (table.next(Constants.NIL).first().isNil()) return null;
|
||||
} catch (LuaError ignored) {
|
||||
// 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 Boolean bool) return valueOf(bool);
|
||||
if (object instanceof String str) return valueOf(str);
|
||||
if (object instanceof byte[] b) {
|
||||
return valueOf(Arrays.copyOf(b, b.length));
|
||||
}
|
||||
if (object instanceof byte[] b) return valueOf(Arrays.copyOf(b, b.length));
|
||||
if (object instanceof ByteBuffer b) {
|
||||
var bytes = new byte[b.remaining()];
|
||||
b.get(bytes);
|
||||
@ -317,25 +266,19 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
|
||||
@Nullable
|
||||
static Object toObject(LuaValue value, @Nullable IdentityHashMap<LuaValue, Object> objects) {
|
||||
switch (value.type()) {
|
||||
case Constants.TNIL:
|
||||
case Constants.TNONE:
|
||||
return null;
|
||||
case Constants.TINT:
|
||||
case Constants.TNUMBER:
|
||||
return value.toDouble();
|
||||
case Constants.TBOOLEAN:
|
||||
return value.toBoolean();
|
||||
case Constants.TSTRING:
|
||||
return value.toString();
|
||||
case Constants.TTABLE: {
|
||||
return switch (value.type()) {
|
||||
case Constants.TNIL -> null;
|
||||
case Constants.TINT, Constants.TNUMBER -> value.toDouble();
|
||||
case Constants.TBOOLEAN -> value.toBoolean();
|
||||
case Constants.TSTRING -> value.toString();
|
||||
case Constants.TTABLE -> {
|
||||
// Table:
|
||||
// Start remembering stuff
|
||||
if (objects == null) {
|
||||
objects = new IdentityHashMap<>(1);
|
||||
} else {
|
||||
var existing = objects.get(value);
|
||||
if (existing != null) return existing;
|
||||
if (existing != null) yield existing;
|
||||
}
|
||||
Map<Object, Object> table = new HashMap<>();
|
||||
objects.put(value, table);
|
||||
@ -361,11 +304,10 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
table.put(keyObject, valueObject);
|
||||
}
|
||||
}
|
||||
return table;
|
||||
yield table;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
static Object[] toObjects(Varargs values) {
|
||||
@ -375,83 +317,12 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
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 {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 7954092008586367501L;
|
||||
|
||||
@SuppressWarnings("StaticAssignmentOfThrowable")
|
||||
static final HardAbortError INSTANCE = new 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
|
||||
public int size() {
|
||||
checkValid();
|
||||
try {
|
||||
return table.keyCount();
|
||||
} catch (LuaError e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return table.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,20 +73,16 @@ final class VarargArguments implements IArguments {
|
||||
@Override
|
||||
public ByteBuffer getBytes(int index) throws LuaException {
|
||||
var value = varargs.arg(index + 1);
|
||||
if (!(value instanceof LuaBaseString)) throw LuaValues.badArgument(index, "string", value.typeName());
|
||||
|
||||
var str = ((LuaBaseString) value).strvalue();
|
||||
return ByteBuffer.wrap(str.bytes, str.offset, str.length).asReadOnlyBuffer();
|
||||
if (!(value instanceof LuaString str)) throw LuaValues.badArgument(index, "string", value.typeName());
|
||||
return str.toBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ByteBuffer> optBytes(int index) throws LuaException {
|
||||
var value = varargs.arg(index + 1);
|
||||
if (value.isNil()) return Optional.empty();
|
||||
if (!(value instanceof LuaBaseString)) throw LuaValues.badArgument(index, "string", value.typeName());
|
||||
|
||||
var str = ((LuaBaseString) value).strvalue();
|
||||
return Optional.of(ByteBuffer.wrap(str.bytes, str.offset, str.length).asReadOnlyBuffer());
|
||||
if (!(value instanceof LuaString str)) throw LuaValues.badArgument(index, "string", value.typeName());
|
||||
return Optional.of(str.toBuffer());
|
||||
}
|
||||
|
||||
@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_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");
|
||||
|
||||
/**
|
||||
|
@ -27,11 +27,12 @@ import org.opentest4j.AssertionFailedError;
|
||||
import org.opentest4j.TestAbortedException;
|
||||
import org.slf4j.Logger;
|
||||
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.DebugHook;
|
||||
import org.squiddev.cobalt.debug.DebugState;
|
||||
import org.squiddev.cobalt.function.OneArgFunction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
@ -480,13 +481,8 @@ public class ComputerTestDelegate {
|
||||
CoverageLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
|
||||
super(environment, bios);
|
||||
|
||||
LuaTable globals;
|
||||
LuaThread mainRoutine;
|
||||
try {
|
||||
var globalField = CobaltLuaMachine.class.getDeclaredField("globals");
|
||||
globalField.setAccessible(true);
|
||||
globals = (LuaTable) globalField.get(this);
|
||||
|
||||
var threadField = CobaltLuaMachine.class.getDeclaredField("mainRoutine");
|
||||
threadField.setAccessible(true);
|
||||
mainRoutine = (LuaThread) threadField.get(this);
|
||||
@ -513,21 +509,13 @@ public class ComputerTestDelegate {
|
||||
if (frame.closure == null) return;
|
||||
|
||||
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());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user