Add support for VMInitializationException.

Allows Firmware to trigger "insufficient memory" error when copying data to memory.
This commit is contained in:
Florian Nücke
2021-01-23 12:46:50 +01:00
parent c101a7ab9a
commit 97c3a68b2d
13 changed files with 123 additions and 25 deletions

View File

@@ -55,11 +55,11 @@ dependencies {
compileOnly 'org.jetbrains:annotations:16.0.2'
implementation 'li.cil.oc2:oc2-sedna:0.0.1+7'
implementation 'li.cil.oc2:oc2-sedna:0.0.1+257'
// These three will be provided by oc2-sedna in standalone.
implementation 'li.cil.ceres:ceres:0.0.2+19'
implementation 'li.cil.sedna:sedna:0.0.1+88'
implementation 'li.cil.sedna:sedna:0.0.1+90'
implementation 'li.cil.sedna:sedna-buildroot:0.0.1+10'
compileOnly fg.deobf("mezz.jei:jei-${minecraft_version}:${jei_version}:api")

View File

@@ -1,7 +1,6 @@
package li.cil.oc2.api.bus.device.data;
import li.cil.oc2.api.API;
import li.cil.oc2.api.bus.device.vm.event.VMInitializationException;
import li.cil.sedna.api.memory.MemoryMap;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
@@ -31,9 +30,12 @@ public interface Firmware extends IForgeRegistryEntry<Firmware> {
* Runs this firmware.
* <p>
* This will usually load machine code into memory at the specified start address.
* <p>
* Typically only returns {@code false} when there was not enough memory to fit the firmware.
*
* @param memory access to the memory map of the machine.
* @param startAddress the memory address where execution will commence.
* @return {@code true} if the firmware was loaded successfully; {@code false} otherwise.
*/
boolean run(final MemoryMap memory, final long startAddress);

View File

@@ -4,7 +4,7 @@ import net.minecraft.util.text.ITextComponent;
import java.util.Optional;
public final class VMInitializationException extends Exception {
public final class VMInitializationException extends RuntimeException {
private final ITextComponent message;
public VMInitializationException(final ITextComponent message) {

View File

@@ -66,9 +66,9 @@ public final class Constants {
public static final String COMPUTER_SCREEN_CAPTURE_INPUT_DESCRIPTION = "gui.oc2.computer.capture_input.desc";
public static final String COMPUTER_SCREEN_POWER_CAPTION = "gui.oc2.computer.power.capt";
public static final String COMPUTER_SCREEN_POWER_DESCRIPTION = "gui.oc2.computer.power.desc";
public static final String COMPUTER_BOOT_ERROR_UNKNOWN = "gui.oc2.computer.boot_error.unknown";
public static final String COMPUTER_BOOT_ERROR_MISSING_FIRMWARE = "gui.oc2.computer.boot_error.missing_firmware";
public static final String COMPUTER_BOOT_ERROR_INSUFFICIENT_MEMORY = "gui.oc2.computer.boot_error.insufficient_memory";
public static final String COMPUTER_ERROR_UNKNOWN = "gui.oc2.computer.error.unknown";
public static final String COMPUTER_ERROR_MISSING_FIRMWARE = "gui.oc2.computer.error.missing_firmware";
public static final String COMPUTER_ERROR_INSUFFICIENT_MEMORY = "gui.oc2.computer.error.insufficient_memory";
public static final String COMPUTER_BUS_STATE_INCOMPLETE = "gui.oc2.computer.bus_state.incomplete";
public static final String COMPUTER_BUS_STATE_TOO_COMPLEX = "gui.oc2.computer.bus_state.too_complex";
public static final String COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS = "gui.oc2.computer.bus_state.multiple_controllers";

View File

@@ -5,7 +5,9 @@ import li.cil.oc2.api.bus.device.ItemDevice;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.oc2.api.bus.device.vm.VMDevice;
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
import li.cil.oc2.api.bus.device.vm.event.VMInitializationException;
import li.cil.oc2.api.bus.device.vm.event.VMInitializingEvent;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.sedna.api.memory.MemoryAccessException;
import li.cil.sedna.api.memory.MemoryMap;
@@ -13,18 +15,13 @@ import li.cil.sedna.device.flash.FlashMemoryDevice;
import li.cil.sedna.memory.MemoryMaps;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.util.text.TranslationTextComponent;
import java.nio.ByteBuffer;
import java.util.OptionalLong;
@SuppressWarnings("UnstableApiUsage")
public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack> implements VMDevice, ItemDevice {
private static final Logger LOGGER = LogManager.getLogger();
///////////////////////////////////////////////////////////////
public static final String DATA_TAG_NAME = "data";
///////////////////////////////////////////////////////////////
@@ -147,7 +144,7 @@ public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack
try {
MemoryMaps.store(memoryMap, startAddress, data);
} catch (final MemoryAccessException e) {
LOGGER.error(e);
throw new VMInitializationException(new TranslationTextComponent(Constants.COMPUTER_ERROR_INSUFFICIENT_MEMORY));
}
}
}

View File

@@ -43,9 +43,15 @@ public final class FirmwareFlashMemoryVMDevice extends IdentityProxy<ItemStack>
}
@Subscribe
public void handleInitializingEvent(final VMInitializingEvent event) throws VMInitializationException {
if (!firmware.run(memoryMap, event.getProgramStartAddress())) {
throw new VMInitializationException(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_INSUFFICIENT_MEMORY));
public void handleInitializingEvent(final VMInitializingEvent event) {
copyDataToMemory(event.getProgramStartAddress());
}
///////////////////////////////////////////////////////////////
private void copyDataToMemory(final long address) {
if (!firmware.run(memoryMap, address)) {
throw new VMInitializationException(new TranslationTextComponent(Constants.COMPUTER_ERROR_INSUFFICIENT_MEMORY));
}
}
}

View File

@@ -2,6 +2,7 @@ package li.cil.oc2.common.serialization.serializers;
import com.google.gson.JsonArray;
import li.cil.ceres.Ceres;
import net.minecraft.util.text.ITextComponent;
public final class Serializers {
private static boolean isInitialized = false;
@@ -18,5 +19,6 @@ public final class Serializers {
isInitialized = true;
Ceres.putSerializer(JsonArray.class, new JsonArraySerializer());
Ceres.putSerializer(ITextComponent.class, new TextComponentSerializer());
}
}

View File

@@ -0,0 +1,27 @@
package li.cil.oc2.common.serialization.serializers;
import li.cil.ceres.api.DeserializationVisitor;
import li.cil.ceres.api.SerializationException;
import li.cil.ceres.api.SerializationVisitor;
import li.cil.ceres.api.Serializer;
import net.minecraft.util.text.ITextComponent;
import javax.annotation.Nullable;
public final class TextComponentSerializer implements Serializer<ITextComponent> {
@Override
public void serialize(final SerializationVisitor visitor, final Class<ITextComponent> type, final Object value) throws SerializationException {
final String json = ITextComponent.Serializer.toJson((ITextComponent) value);
visitor.putObject("value", String.class, json);
}
@Override
public ITextComponent deserialize(final DeserializationVisitor visitor, final Class<ITextComponent> type, @Nullable final Object value) throws SerializationException {
if (!visitor.exists("value")) {
return (ITextComponent) value;
}
final String json = (String) visitor.getObject("value", String.class, null);
return ITextComponent.Serializer.getComponentFromJson(json);
}
}

View File

@@ -2,9 +2,14 @@ package li.cil.oc2.common.vm;
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
import li.cil.ceres.api.Serialized;
import li.cil.oc2.api.bus.device.vm.event.VMInitializationException;
import li.cil.oc2.api.bus.device.vm.event.VMInitializingEvent;
import li.cil.oc2.api.bus.device.vm.event.VMResumedRunningEvent;
import li.cil.oc2.api.bus.device.vm.event.VMResumingRunningEvent;
import li.cil.oc2.common.Constants;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
@@ -29,6 +34,7 @@ public abstract class AbstractTerminalVirtualMachineRunner extends VirtualMachin
private boolean firedResumeEvent;
@Serialized private boolean firedInitializationEvent;
@Serialized private ITextComponent runtimeError;
///////////////////////////////////////////////////////////////////
@@ -62,6 +68,12 @@ public abstract class AbstractTerminalVirtualMachineRunner extends VirtualMachin
firedResumeEvent = false;
}
@Nullable
@Override
public ITextComponent getRuntimeError() {
return runtimeError;
}
@Override
public void tick() {
virtualMachine.rpcAdapter.tick();
@@ -75,7 +87,13 @@ public abstract class AbstractTerminalVirtualMachineRunner extends VirtualMachin
protected void handleBeforeRun() {
if (!firedInitializationEvent) {
firedInitializationEvent = true;
virtualMachine.vmAdapter.postLifecycleEvent(new VMInitializingEvent(0x80000000L));
try {
virtualMachine.vmAdapter.postLifecycleEvent(new VMInitializingEvent(0x80000000L));
} catch (final VMInitializationException e) {
virtualMachine.board.setRunning(false);
runtimeError = e.getErrorMessage().orElse(new TranslationTextComponent(Constants.COMPUTER_ERROR_UNKNOWN));
return;
}
}
if (!firedResumeEvent) {

View File

@@ -184,7 +184,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
if (loadResult.getErrorMessage() != null) {
setBootError(loadResult.getErrorMessage());
} else {
setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_UNKNOWN));
setBootError(new TranslationTextComponent(Constants.COMPUTER_ERROR_UNKNOWN));
}
loadDevicesDelay = DEVICE_LOAD_RETRY_INTERVAL;
break;
@@ -203,12 +203,12 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
// a program that only uses registers. But not supporting that esoteric
// use-case loses out against avoiding people getting confused for having
// forgotten to add some RAM modules.
setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_INSUFFICIENT_MEMORY));
setBootError(new TranslationTextComponent(Constants.COMPUTER_ERROR_INSUFFICIENT_MEMORY));
setRunState(RunState.STOPPED);
return;
} catch (final MemoryAccessException e) {
LOGGER.error(e);
setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_UNKNOWN));
setBootError(new TranslationTextComponent(Constants.COMPUTER_ERROR_UNKNOWN));
setRunState(RunState.STOPPED);
return;
}
@@ -222,6 +222,13 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
// initialization. This is used by devices to restore data from disk, for example.
break;
case RUNNING:
final ITextComponent runtimeError = runner.getRuntimeError();
if (runtimeError != null) {
stopRunnerAndReset();
setBootError(runtimeError);
break;
}
if (!virtualMachine.board.isRunning()) {
stopRunnerAndReset();
break;

View File

@@ -1,27 +1,37 @@
package li.cil.oc2.common.vm;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionContext;
import li.cil.ceres.api.Serialized;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.oc2.api.bus.device.vm.VMDevice;
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
import li.cil.oc2.api.bus.device.vm.event.VMInitializationException;
import li.cil.oc2.api.bus.device.vm.event.VMLifecycleEvent;
import li.cil.sedna.api.Board;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.*;
@SuppressWarnings("UnstableApiUsage")
public final class VirtualMachineDeviceBusAdapter {
private static final Logger LOGGER = LogManager.getLogger();
///////////////////////////////////////////////////////////////////
private final Board board;
private final EventBus eventBus = new EventBus();
private final EventBus eventBus = new EventBus(this::handleEventBusException);
private final ManagedVMContext globalContext;
private final BitSet claimedInterrupts = new BitSet();
private final HashMap<VMDevice, ManagedVMContext> deviceContexts = new HashMap<>();
private final ArrayList<VMDevice> incompleteLoads = new ArrayList<>();
private DefaultAddressProvider defaultAddressProvider = unused -> OptionalLong.empty();
private VMInitializationException initializationException;
///////////////////////////////////////////////////////////////////
@@ -98,7 +108,15 @@ public final class VirtualMachineDeviceBusAdapter {
}
public void postLifecycleEvent(final VMLifecycleEvent event) {
initializationException = null;
eventBus.post(event);
final VMInitializationException exception = initializationException;
initializationException = null;
if (exception != null) {
throw exception;
}
}
public void addDevices(final Collection<Device> devices) {
@@ -132,4 +150,14 @@ public final class VirtualMachineDeviceBusAdapter {
}
}
}
///////////////////////////////////////////////////////////////////
private void handleEventBusException(final Throwable throwable, final SubscriberExceptionContext context) {
if (throwable instanceof VMInitializationException) {
initializationException = (VMInitializationException) throwable;
} else {
LOGGER.error(throwable);
}
}
}

View File

@@ -2,7 +2,9 @@ package li.cil.oc2.common.vm;
import li.cil.ceres.api.Serialized;
import li.cil.sedna.riscv.R5Board;
import net.minecraft.util.text.ITextComponent;
import javax.annotation.Nullable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -38,6 +40,11 @@ public class VirtualMachineRunner implements Runnable {
///////////////////////////////////////////////////////////////////
@Nullable
public ITextComponent getRuntimeError() {
return null;
}
public void tick() {
cycleLimit += getCyclesPerTick();
@@ -71,6 +78,10 @@ public class VirtualMachineRunner implements Runnable {
handleBeforeRun();
if (!board.isRunning()) {
break;
}
for (int i = 0; i < maxSteps; i++) {
cycles += cyclesPerStep;
board.step(cyclesPerStep);

View File

@@ -36,9 +36,9 @@
"config.oc2.modules.block_operations.toolLevel": "Block operations module tool level",
"config.oc2.admin.fakePlayerUUID": "Fake Player UUID",
"gui.oc2.computer.boot_error.unknown": "Unknown Error",
"gui.oc2.computer.boot_error.missing_firmware": "Missing Flash Memory",
"gui.oc2.computer.boot_error.insufficient_memory": "Insufficient Memory",
"gui.oc2.computer.error.unknown": "Unknown Error",
"gui.oc2.computer.error.missing_firmware": "Missing Flash Memory",
"gui.oc2.computer.error.insufficient_memory": "Insufficient Memory",
"gui.oc2.computer.bus_state.incomplete": "Bus Incomplete",
"gui.oc2.computer.bus_state.too_complex": "Bus Too Complex",
"gui.oc2.computer.bus_state.multiple_controllers": "Multiple Bus Controllers",