1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-07-06 12:02:52 +00:00

Construct ILuaMachines in one go

This means creating an ILuaMachine is largely atomic - it either is
created or it fails.
This commit is contained in:
Jonathan Coates 2023-03-15 22:39:51 +00:00
parent 988219ffca
commit 986c65f56e
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
10 changed files with 89 additions and 138 deletions

View File

@ -4,12 +4,10 @@
package dan200.computercraft.gametest.core package dan200.computercraft.gametest.core
import dan200.computercraft.api.lua.ILuaAPI
import dan200.computercraft.core.apis.OSAPI import dan200.computercraft.core.apis.OSAPI
import dan200.computercraft.core.lua.CobaltLuaMachine import dan200.computercraft.core.lua.CobaltLuaMachine
import dan200.computercraft.core.lua.ILuaMachine import dan200.computercraft.core.lua.ILuaMachine
import dan200.computercraft.core.lua.MachineEnvironment import dan200.computercraft.core.lua.MachineEnvironment
import dan200.computercraft.core.lua.MachineResult
import dan200.computercraft.gametest.api.thenOnComputer import dan200.computercraft.gametest.api.thenOnComputer
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
import dan200.computercraft.shared.computer.core.ServerContext import dan200.computercraft.shared.computer.core.ServerContext
@ -60,52 +58,18 @@ object ManagedComputers : ILuaMachine.Factory {
return monitor return monitor
} }
override fun create(environment: MachineEnvironment): ILuaMachine = DelegateMachine(environment) override fun create(environment: MachineEnvironment, bios: InputStream): ILuaMachine {
val os = environment.apis.asSequence().filterIsInstance(OSAPI::class.java).first()
private class DelegateMachine(private val environment: MachineEnvironment) : ILuaMachine { val id = os.computerID
private val apis = mutableListOf<ILuaAPI>() val label = os.computerLabel
private var delegate: ILuaMachine? = null return when {
id != 1 -> CobaltLuaMachine(environment, bios)
override fun addAPI(api: ILuaAPI) {
val delegate = this.delegate
if (delegate != null) return delegate.addAPI(api)
apis.add(api)
if (api is OSAPI) {
val id = api.computerID
val label = api.computerLabel
val newMachine = when {
id != 1 -> CobaltLuaMachine(environment)
label != null && label[0] != null -> KotlinMachine(environment, label[0] as String) label != null && label[0] != null -> KotlinMachine(environment, label[0] as String)
else -> { else -> {
LOGGER.error("Kotlin Lua machine must have a label") LOGGER.error("Kotlin Lua machine must have a label")
CobaltLuaMachine(environment) CobaltLuaMachine(environment, bios)
} }
} }
this.delegate = newMachine
for (api in apis) newMachine.addAPI(api)
}
}
override fun loadBios(bios: InputStream): MachineResult {
val delegate = this.delegate ?: return MachineResult.error("Computer not created")
return delegate.loadBios(bios)
}
override fun handleEvent(eventName: String?, arguments: Array<out Any>?): MachineResult {
val delegate = this.delegate ?: return MachineResult.error("Computer not created")
return delegate.handleEvent(eventName, arguments)
}
override fun printExecutionState(out: StringBuilder) {
delegate?.printExecutionState(out)
}
override fun close() {
delegate?.close()
}
} }
private class KotlinMachine(environment: MachineEnvironment, private val label: String) : private class KotlinMachine(environment: MachineEnvironment, private val label: String) :

View File

@ -14,15 +14,16 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException; import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.ILuaMachine; import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.MachineEnvironment; import dan200.computercraft.core.lua.MachineEnvironment;
import dan200.computercraft.core.lua.MachineException;
import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import dan200.computercraft.core.util.IoUtil;
import dan200.computercraft.core.util.Nullability; import dan200.computercraft.core.util.Nullability;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
@ -377,24 +378,20 @@ final class ComputerExecutor {
} }
// Create the lua machine // Create the lua machine
var machine = luaFactory.create(new MachineEnvironment( try (var bios = biosStream) {
new LuaContext(computer), metrics, timeout, computer.getGlobalEnvironment().getHostString() return luaFactory.create(new MachineEnvironment(
)); new LuaContext(computer), metrics, timeout,
() -> apis.stream().map(api -> api instanceof ApiWrapper wrapper ? wrapper.getDelegate() : api).iterator(),
// Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object. computer.getGlobalEnvironment().getHostString()
for (var api : apis) machine.addAPI(api instanceof ApiWrapper wrapper ? wrapper.getDelegate() : api); ), bios);
} catch (IOException e) {
// Start the machine running the bios resource LOG.error("Failed to read bios.lua", e);
var result = machine.loadBios(biosStream); displayFailure("Error loading bios.lua", null);
IoUtil.closeQuietly(biosStream); return null;
} catch (MachineException e) {
if (result.isError()) { displayFailure("Error loading bios.lua", e.getMessage());
machine.close();
displayFailure("Error loading bios.lua", result.getMessage());
return null; return null;
} }
return machine;
} }
private void turnOn() throws InterruptedException { private void turnOn() throws InterruptedException {

View File

@ -14,6 +14,7 @@ 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.metrics.Metrics;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.core.util.ThreadUtils; import dan200.computercraft.core.util.ThreadUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -27,6 +28,7 @@ import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator; import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serial; import java.io.Serial;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -62,7 +64,7 @@ public class CobaltLuaMachine implements ILuaMachine {
private @Nullable LuaThread mainRoutine = null; private @Nullable LuaThread mainRoutine = null;
private @Nullable String eventFilter = null; private @Nullable String eventFilter = null;
public CobaltLuaMachine(MachineEnvironment environment) { public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
timeout = environment.timeout(); timeout = environment.timeout();
context = environment.context(); context = environment.context();
debug = new TimeoutDebugHandler(); debug = new TimeoutDebugHandler();
@ -115,10 +117,20 @@ public class CobaltLuaMachine implements ILuaMachine {
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
for (var api : environment.apis()) addAPI(api);
// And load the BIOS
try {
var value = LoadState.load(state, bios, "@bios.lua", globals);
mainRoutine = new LuaThread(state, value, globals);
} catch (CompileException e) {
throw new MachineException(Nullability.assertNonNull(e.getMessage()));
}
} }
@Override private void addAPI(ILuaAPI api) {
public void addAPI(ILuaAPI api) {
if (globals == null) throw new IllegalStateException("Machine has been closed"); 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
@ -132,25 +144,6 @@ public class CobaltLuaMachine implements ILuaMachine {
for (var name : names) globals.rawset(name, table); for (var name : names) globals.rawset(name, table);
} }
@Override
public MachineResult loadBios(InputStream bios) {
if (mainRoutine != null) throw new IllegalStateException("Already set up the machine");
if (state == null || globals == null) throw new IllegalStateException("Machine has been destroyed.");
try {
var value = LoadState.load(state, bios, "@bios.lua", globals);
mainRoutine = new LuaThread(state, value, globals);
return MachineResult.OK;
} catch (CompileException e) {
close();
return MachineResult.error(e);
} catch (Exception e) {
LOG.warn("Could not load bios.lua", e);
close();
return MachineResult.GENERIC_ERROR;
}
}
@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 (mainRoutine == null || state == null) throw new IllegalStateException("Machine has been closed");

View File

@ -4,10 +4,8 @@
package dan200.computercraft.core.lua; package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaAPI;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** /**
@ -17,32 +15,8 @@ import java.io.InputStream;
* There should only be one concrete implementation at any one time, which is currently {@link CobaltLuaMachine}. If * There should only be one concrete implementation at any one time, which is currently {@link CobaltLuaMachine}. If
* external mod authors are interested in registering their own machines, we can look into how we can provide some * external mod authors are interested in registering their own machines, we can look into how we can provide some
* mechanism for registering these. * mechanism for registering these.
* <p>
* This should provide implementations of {@link dan200.computercraft.api.lua.ILuaContext}, and the ability to convert
* {@link IDynamicLuaObject}s into something the VM understands, as well as handling method calls.
*/ */
public interface ILuaMachine { public interface ILuaMachine {
/**
* Inject an API into the global environment of this machine. This should construct an object, as it would for any
* {@link IDynamicLuaObject} and set it to all names in {@link ILuaAPI#getNames()}.
* <p>
* Called before {@link #loadBios(InputStream)}.
*
* @param api The API to register.
*/
void addAPI(ILuaAPI api);
/**
* Create a function from the provided program, and set it up to run when {@link #handleEvent(String, Object[])} is
* called.
* <p>
* This should destroy the machine if it failed to load the bios.
*
* @param bios The stream containing the boot program.
* @return The result of loading this machine. Will either be OK, or the error message when loading the bios.
*/
MachineResult loadBios(InputStream bios);
/** /**
* Resume the machine, either starting or resuming the coroutine. * Resume the machine, either starting or resuming the coroutine.
* <p> * <p>
@ -71,6 +45,16 @@ public interface ILuaMachine {
void close(); void close();
interface Factory { interface Factory {
ILuaMachine create(MachineEnvironment environment); /**
* Attempt to create a Lua machine.
*
* @param environment The environment under which to create the machine.
* @param bios The {@link InputStream} which contains the initial function to run. This should be used to
* load the initial function - it should <em>NOT</em> be executed.
* @return The successfully created machine, or an error.
* @throws IOException If reading the underlying {@link InputStream} failed.
* @throws MachineException An error occurred while creating the machine.
*/
ILuaMachine create(MachineEnvironment environment, InputStream bios) throws IOException, MachineException;
} }
} }

View File

@ -4,6 +4,7 @@
package dan200.computercraft.core.lua; package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.core.computer.GlobalEnvironment; import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.TimeoutState; import dan200.computercraft.core.computer.TimeoutState;
@ -17,6 +18,8 @@ import dan200.computercraft.core.metrics.MetricsObserver;
* @param metrics A sink to submit metrics to. You do not need to submit task timings here, it should only be for additional * @param metrics A sink to submit metrics to. You do not need to submit task timings here, it should only be for additional
* metrics such as {@link Metrics#COROUTINES_CREATED} * metrics such as {@link Metrics#COROUTINES_CREATED}
* @param timeout The current timeout state. This should be used by the machine to interrupt its execution. * @param timeout The current timeout state. This should be used by the machine to interrupt its execution.
* @param apis APIs to inject into the global environment. Each API should be converted into a Lua object
* (following the same rules as any other value), and then set to all names in {@link ILuaAPI#getNames()}.
* @param hostString A {@linkplain GlobalEnvironment#getHostString() host string} to identify the current environment. * @param hostString A {@linkplain GlobalEnvironment#getHostString() host string} to identify the current environment.
* @see ILuaMachine.Factory * @see ILuaMachine.Factory
*/ */
@ -24,6 +27,7 @@ public record MachineEnvironment(
ILuaContext context, ILuaContext context,
MetricsObserver metrics, MetricsObserver metrics,
TimeoutState timeout, TimeoutState timeout,
Iterable<ILuaAPI> apis,
String hostString String hostString
) { ) {
} }

View File

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.lua;
import java.io.Serial;
/**
* An exception thrown by a {@link ILuaMachine}.
*/
public class MachineException extends Exception {
@Serial
private static final long serialVersionUID = 400833668352232261L;
/**
* Create a new {@link MachineException}.
*
* @param message The message to display. This should be user-friendly, and not contain any internal information -
* that should just be logged to the console.
*/
public MachineException(String message) {
super(message);
}
}

View File

@ -7,14 +7,12 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.core.computer.TimeoutState; import dan200.computercraft.core.computer.TimeoutState;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.InputStream;
/** /**
* The result of executing an action on a machine. * The result of executing an action on a machine.
* <p> * <p>
* Errors should halt the machine and display the error to the user. * Errors should halt the machine and display the error to the user.
* *
* @see ILuaMachine#loadBios(InputStream)
* @see ILuaMachine#handleEvent(String, Object[]) * @see ILuaMachine#handleEvent(String, Object[])
*/ */
public final class MachineResult { public final class MachineResult {

View File

@ -17,7 +17,7 @@ import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.WritableFileMount; import dan200.computercraft.core.filesystem.WritableFileMount;
import dan200.computercraft.core.lua.CobaltLuaMachine; import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.MachineEnvironment; import dan200.computercraft.core.lua.MachineEnvironment;
import dan200.computercraft.core.lua.MachineResult; import dan200.computercraft.core.lua.MachineException;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.test.core.computer.BasicEnvironment; import dan200.computercraft.test.core.computer.BasicEnvironment;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
@ -477,14 +477,8 @@ public class ComputerTestDelegate {
* This is a super nasty hack, but is also an order of magnitude faster than tracking this in Lua. * This is a super nasty hack, but is also an order of magnitude faster than tracking this in Lua.
*/ */
private class CoverageLuaMachine extends CobaltLuaMachine { private class CoverageLuaMachine extends CobaltLuaMachine {
CoverageLuaMachine(MachineEnvironment environment) { CoverageLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
super(environment); super(environment, bios);
}
@Override
public MachineResult loadBios(InputStream bios) {
var result = super.loadBios(bios);
if (result != MachineResult.OK) return result;
LuaTable globals; LuaTable globals;
LuaThread mainRoutine; LuaThread mainRoutine;
@ -535,8 +529,6 @@ public class ComputerTestDelegate {
} }
}); });
mainRoutine.getDebugState().setHook(hook, false, true, false, 0); mainRoutine.getDebugState().setHook(hook, false, true, false, 0);
return MachineResult.OK;
} }
} }
} }

View File

@ -31,7 +31,7 @@ class KotlinComputerManager : AutoCloseable {
BasicEnvironment(), BasicEnvironment(),
ComputerThread(1), ComputerThread(1),
NoWorkMainThreadScheduler(), NoWorkMainThreadScheduler(),
) { DummyLuaMachine(it) } ) { env, _ -> DummyLuaMachine(env) }
private val errorLock: Lock = ReentrantLock() private val errorLock: Lock = ReentrantLock()
private val hasError = errorLock.newCondition() private val hasError = errorLock.newCondition()
@ -153,15 +153,11 @@ class KotlinComputerManager : AutoCloseable {
} }
private inner class DummyLuaMachine(private val environment: MachineEnvironment) : KotlinLuaMachine(environment) { private inner class DummyLuaMachine(private val environment: MachineEnvironment) : KotlinLuaMachine(environment) {
private var tasks: Queue<FakeComputerTask>? = null private val tasks: Queue<FakeComputerTask> =
override fun addAPI(api: ILuaAPI) { environment.apis.asSequence().filterIsInstance(QueuePassingAPI::class.java).first().tasks
super.addAPI(api)
if (api is QueuePassingAPI) tasks = api.tasks
}
override fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? { override fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? {
try { try {
val tasks = this.tasks ?: throw NullPointerException("Not received tasks yet")
val task = tasks.remove() val task = tasks.remove()
return { return {
try { try {

View File

@ -4,13 +4,11 @@
package dan200.computercraft.test.core.computer package dan200.computercraft.test.core.computer
import dan200.computercraft.api.lua.ILuaAPI
import dan200.computercraft.api.lua.ILuaContext import dan200.computercraft.api.lua.ILuaContext
import dan200.computercraft.core.lua.ILuaMachine import dan200.computercraft.core.lua.ILuaMachine
import dan200.computercraft.core.lua.MachineEnvironment import dan200.computercraft.core.lua.MachineEnvironment
import dan200.computercraft.core.lua.MachineResult import dan200.computercraft.core.lua.MachineResult
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.InputStream
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@ -19,9 +17,9 @@ import kotlin.coroutines.CoroutineContext
abstract class KotlinLuaMachine(environment: MachineEnvironment) : ILuaMachine, AbstractLuaTaskContext() { abstract class KotlinLuaMachine(environment: MachineEnvironment) : ILuaMachine, AbstractLuaTaskContext() {
override val context: ILuaContext = environment.context override val context: ILuaContext = environment.context
override fun addAPI(api: ILuaAPI) = addApi(api) init {
for (api in environment.apis) addApi(api)
override fun loadBios(bios: InputStream): MachineResult = MachineResult.OK }
override fun handleEvent(eventName: String?, arguments: Array<out Any>?): MachineResult { override fun handleEvent(eventName: String?, arguments: Array<out Any>?): MachineResult {
if (hasEventListeners) { if (hasEventListeners) {