Add support for VMInitializationException.
Allows Firmware to trigger "insufficient memory" error when copying data to memory.
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user