mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 05:33:00 +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:
		| @@ -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; | ||||
|             } | ||||
|             default: | ||||
|                 return null; | ||||
|                 yield table; | ||||
|             } | ||||
|             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); | ||||
|         } | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates