1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-26 07:03:22 +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
import dan200.computercraft.api.lua.ILuaAPI
import dan200.computercraft.core.apis.OSAPI
import dan200.computercraft.core.lua.CobaltLuaMachine
import dan200.computercraft.core.lua.ILuaMachine
import dan200.computercraft.core.lua.MachineEnvironment
import dan200.computercraft.core.lua.MachineResult
import dan200.computercraft.gametest.api.thenOnComputer
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
import dan200.computercraft.shared.computer.core.ServerContext
@ -60,52 +58,18 @@ internal fun enqueue(test: GameTestInfo, label: String, task: suspend LuaTaskCon
return monitor
}
override fun create(environment: MachineEnvironment): ILuaMachine = DelegateMachine(environment)
private class DelegateMachine(private val environment: MachineEnvironment) : ILuaMachine {
private val apis = mutableListOf<ILuaAPI>()
private var delegate: ILuaMachine? = null
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)
else -> {
LOGGER.error("Kotlin Lua machine must have a label")
CobaltLuaMachine(environment)
}
}
this.delegate = newMachine
for (api in apis) newMachine.addAPI(api)
override fun create(environment: MachineEnvironment, bios: InputStream): ILuaMachine {
val os = environment.apis.asSequence().filterIsInstance(OSAPI::class.java).first()
val id = os.computerID
val label = os.computerLabel
return when {
id != 1 -> CobaltLuaMachine(environment, bios)
label != null && label[0] != null -> KotlinMachine(environment, label[0] as String)
else -> {
LOGGER.error("Kotlin Lua machine must have a label")
CobaltLuaMachine(environment, bios)
}
}
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) :

View File

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

View File

@ -14,6 +14,7 @@
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;
@ -27,6 +28,7 @@
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serial;
import java.nio.ByteBuffer;
@ -62,7 +64,7 @@ public class CobaltLuaMachine implements ILuaMachine {
private @Nullable LuaThread mainRoutine = null;
private @Nullable String eventFilter = null;
public CobaltLuaMachine(MachineEnvironment environment) {
public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
timeout = environment.timeout();
context = environment.context();
debug = new TimeoutDebugHandler();
@ -115,10 +117,20 @@ public CobaltLuaMachine(MachineEnvironment environment) {
if (CoreConfig.disableLua51Features) {
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
public void addAPI(ILuaAPI api) {
private void addAPI(ILuaAPI api) {
if (globals == null) throw new IllegalStateException("Machine has been closed");
// Add the methods of an API to the global table
@ -132,25 +144,6 @@ public void addAPI(ILuaAPI api) {
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
public MachineResult handleEvent(@Nullable String eventName, @Nullable Object[] arguments) {
if (mainRoutine == null || state == null) throw new IllegalStateException("Machine has been closed");

View File

@ -4,10 +4,8 @@
package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaAPI;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
/**
@ -17,32 +15,8 @@
* 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
* 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 {
/**
* 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.
* <p>
@ -71,6 +45,16 @@ public interface ILuaMachine {
void close();
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;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.TimeoutState;
@ -17,6 +18,8 @@
* @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}
* @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.
* @see ILuaMachine.Factory
*/
@ -24,6 +27,7 @@ public record MachineEnvironment(
ILuaContext context,
MetricsObserver metrics,
TimeoutState timeout,
Iterable<ILuaAPI> apis,
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 @@
import dan200.computercraft.core.computer.TimeoutState;
import javax.annotation.Nullable;
import java.io.InputStream;
/**
* The result of executing an action on a machine.
* <p>
* Errors should halt the machine and display the error to the user.
*
* @see ILuaMachine#loadBios(InputStream)
* @see ILuaMachine#handleEvent(String, Object[])
*/
public final class MachineResult {

View File

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

View File

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

View File

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