From 97c3a68b2d20ca28a94eb810c9fc355c37caf9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 23 Jan 2021 12:46:50 +0100 Subject: [PATCH] Add support for VMInitializationException. Allows Firmware to trigger "insufficient memory" error when copying data to memory. --- build.gradle | 4 +-- .../cil/oc2/api/bus/device/data/Firmware.java | 4 ++- .../vm/event/VMInitializationException.java | 2 +- .../java/li/cil/oc2/common/Constants.java | 6 ++-- .../item/ByteBufferFlashMemoryVMDevice.java | 11 +++---- .../item/FirmwareFlashMemoryVMDevice.java | 12 ++++++-- .../serializers/Serializers.java | 2 ++ .../serializers/TextComponentSerializer.java | 27 +++++++++++++++++ .../AbstractTerminalVirtualMachineRunner.java | 20 ++++++++++++- .../vm/AbstractVirtualMachineState.java | 13 ++++++-- .../vm/VirtualMachineDeviceBusAdapter.java | 30 ++++++++++++++++++- .../oc2/common/vm/VirtualMachineRunner.java | 11 +++++++ src/main/resources/assets/oc2/lang/en_us.json | 6 ++-- 13 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 src/main/java/li/cil/oc2/common/serialization/serializers/TextComponentSerializer.java diff --git a/build.gradle b/build.gradle index 8bf79845..ab0615aa 100644 --- a/build.gradle +++ b/build.gradle @@ -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") diff --git a/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java b/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java index 65b64681..05b21a96 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java +++ b/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java @@ -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 { * Runs this firmware. *

* This will usually load machine code into memory at the specified start address. + *

+ * 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); diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java index 259e6bc1..762f955f 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java @@ -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) { diff --git a/src/main/java/li/cil/oc2/common/Constants.java b/src/main/java/li/cil/oc2/common/Constants.java index f5dbb4b8..d31e4312 100644 --- a/src/main/java/li/cil/oc2/common/Constants.java +++ b/src/main/java/li/cil/oc2/common/Constants.java @@ -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"; diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/ByteBufferFlashMemoryVMDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/ByteBufferFlashMemoryVMDevice.java index 99cbfdd5..a1e9b9fb 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/item/ByteBufferFlashMemoryVMDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/item/ByteBufferFlashMemoryVMDevice.java @@ -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 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 } @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)); } } } diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/Serializers.java b/src/main/java/li/cil/oc2/common/serialization/serializers/Serializers.java index 0a4a7cc1..f3897b9b 100644 --- a/src/main/java/li/cil/oc2/common/serialization/serializers/Serializers.java +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/Serializers.java @@ -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()); } } diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/TextComponentSerializer.java b/src/main/java/li/cil/oc2/common/serialization/serializers/TextComponentSerializer.java new file mode 100644 index 00000000..8e6a0a44 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/TextComponentSerializer.java @@ -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 { + @Override + public void serialize(final SerializationVisitor visitor, final Class 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 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); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java b/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java index d09ffe68..35be7430 100644 --- a/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java +++ b/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java @@ -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) { diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java index 83c45768..a2945e1f 100644 --- a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java +++ b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java @@ -184,7 +184,7 @@ public abstract class AbstractVirtualMachineState deviceContexts = new HashMap<>(); private final ArrayList 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 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); + } + } } diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineRunner.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachineRunner.java index 705a121a..608f892e 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineRunner.java +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachineRunner.java @@ -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); diff --git a/src/main/resources/assets/oc2/lang/en_us.json b/src/main/resources/assets/oc2/lang/en_us.json index 262495f8..0516b13d 100644 --- a/src/main/resources/assets/oc2/lang/en_us.json +++ b/src/main/resources/assets/oc2/lang/en_us.json @@ -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",