1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-22 05:03:22 +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:
Jonathan Coates 2023-03-26 19:42:55 +01:00
parent 0046b095b1
commit 5bb2e8e8cd
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
9 changed files with 107 additions and 227 deletions

View File

@ -21,7 +21,7 @@ ## Lua 5.2
| 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 @@ ## Lua 5.2
| `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 @@ ## Lua 5.2
| `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 @@ ## Lua 5.2
| 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 @@ ## Lua 5.3
| `\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 @@ ## Lua 5.3
| `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 |

View File

@ -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"

View File

@ -178,8 +178,6 @@ private void addTranslations() {
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)");

View File

@ -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 synchronized void refresh() {
// 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 boolean isHardAborted() {
*/
void hardAbort() {
softAbort = hardAbort = true;
synchronized (this) {
updateListeners();
}
}
/**
@ -153,6 +169,7 @@ synchronized void pauseTimer() {
// 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 @@ synchronized void pauseTimer() {
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);
}
}

View File

@ -13,19 +13,15 @@
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.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 CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws
})
.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 CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws
} 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 @@ private void addAPI(ILuaAPI api) {
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 MachineResult handleEvent(@Nullable String eventName, @Nullable Object[]
@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 @@ private LuaTable wrapLuaObject(Object object) {
: 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 @@ private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Obje
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 @@ Varargs toValues(@Nullable Object[] objects) {
@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 @@ static Object toObject(LuaValue value, @Nullable IdentityHashMap<LuaValue, Objec
table.put(keyObject, valueObject);
}
}
return table;
yield table;
}
default:
return null;
}
default -> null;
};
}
static Object[] toObjects(Varargs values) {
@ -375,83 +317,12 @@ static Object[] toObjects(Varargs values) {
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");
}
}
}

View File

@ -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

View File

@ -73,20 +73,16 @@ public long getLong(int index) throws LuaException {
@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

View File

@ -24,9 +24,6 @@ private 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");
/**

View File

@ -27,11 +27,12 @@
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 @@ private class CoverageLuaMachine extends CobaltLuaMachine {
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 void onLine(LuaState state, DebugState ds, DebugFrame frame, int newLine)
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);
}
}