mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-19 05:32:55 +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:
parent
988219ffca
commit
986c65f56e
@ -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 @@ object ManagedComputers : ILuaMachine.Factory {
|
||||
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) :
|
||||
|
@ -14,15 +14,16 @@ import dan200.computercraft.core.filesystem.FileSystem;
|
||||
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 @@ final class ComputerExecutor {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -14,6 +14,7 @@ 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;
|
||||
@ -27,6 +28,7 @@ import org.squiddev.cobalt.lib.*;
|
||||
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 class CobaltLuaMachine implements ILuaMachine {
|
||||
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 class CobaltLuaMachine implements ILuaMachine {
|
||||
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");
|
||||
|
@ -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 @@ import java.io.InputStream;
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -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 @@ 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
|
||||
* 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
|
||||
) {
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -7,14 +7,12 @@ package dan200.computercraft.core.lua;
|
||||
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 {
|
||||
|
@ -17,7 +17,7 @@ import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
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 class ComputerTestDelegate {
|
||||
* 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 class ComputerTestDelegate {
|
||||
}
|
||||
});
|
||||
mainRoutine.getDebugState().setHook(hook, false, true, false, 0);
|
||||
|
||||
return MachineResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 @@ class KotlinComputerManager : AutoCloseable {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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 @@ import kotlin.coroutines.CoroutineContext
|
||||
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user