From c101a7ab9afa3026ffd72cfcd3c2257a62e2f9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Fri, 22 Jan 2021 21:05:49 +0100 Subject: [PATCH] Made vm lifecycle events use an event bus and event classes. Allows passing along program start so that firmware doesn't have to hardcode this. Also made unload a method instead of an event, as it needs to be called on individual objects anyway; previous implementation was a little awkward. --- .../cil/oc2/api/bus/device/data/Firmware.java | 3 +- .../cil/oc2/api/bus/device/vm/VMContext.java | 12 ++++ .../cil/oc2/api/bus/device/vm/VMDevice.java | 16 ++++-- .../device/vm/VMDeviceLifecycleEventType.java | 57 ------------------- .../device/vm/VMDeviceLifecycleListener.java | 18 ------ .../vm/event/VMInitializationException.java | 21 +++++++ .../device/vm/event/VMInitializingEvent.java | 44 ++++++++++++++ .../bus/device/vm/event/VMLifecycleEvent.java | 4 ++ .../device/vm/event/VMLifecycleEventBus.java | 7 +++ .../bus/device/vm/event/VMPausingEvent.java | 12 ++++ .../vm/event/VMResumedRunningEvent.java | 14 +++++ .../vm/event/VMResumingRunningEvent.java | 16 ++++++ .../api/bus/device/vm/event/package-info.java | 7 +++ .../java/li/cil/oc2/common/Constants.java | 3 +- .../bus/device/data/BuildrootFirmware.java | 9 +-- .../item/AbstractHardDriveVMDevice.java | 55 +++++++++--------- .../item/ByteBufferFlashMemoryVMDevice.java | 51 +++++++++-------- .../item/FirmwareFlashMemoryVMDevice.java | 31 ++++++---- .../common/bus/device/item/MemoryDevice.java | 43 +++++++------- .../item/NetworkInterfaceCardItemDevice.java | 39 +++++++------ .../AbstractTerminalVirtualMachineRunner.java | 10 ++-- .../vm/AbstractVirtualMachineState.java | 8 +-- .../li/cil/oc2/common/vm/ManagedEventBus.java | 35 ++++++++++++ .../cil/oc2/common/vm/ManagedVMContext.java | 17 +++++- .../vm/VirtualMachineDeviceBusAdapter.java | 36 ++++++------ src/main/resources/assets/oc2/lang/en_us.json | 3 +- .../li/cil/oc2/common/bus/VMDeviceTests.java | 16 +++--- 27 files changed, 358 insertions(+), 229 deletions(-) delete mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleEventType.java delete mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleListener.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEvent.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEventBus.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java create mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedEventBus.java 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 8528910d..65b64681 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,6 +1,7 @@ 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; @@ -34,7 +35,7 @@ public interface Firmware extends IForgeRegistryEntry { * @param memory access to the memory map of the machine. * @param startAddress the memory address where execution will commence. */ - void run(final MemoryMap memory, final long startAddress); + boolean run(final MemoryMap memory, final long startAddress); /** * The display name of this firmware. May be shown in the tooltip of item devices diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java index bac92b4d..8b0d9d1a 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java @@ -1,6 +1,7 @@ package li.cil.oc2.api.bus.device.vm; import li.cil.oc2.api.bus.DeviceBus; +import li.cil.oc2.api.bus.device.vm.event.VMLifecycleEventBus; import li.cil.sedna.api.device.InterruptController; import li.cil.sedna.api.device.MemoryMappedDevice; import li.cil.sedna.api.memory.MemoryMap; @@ -81,4 +82,15 @@ public interface VMContext { * @return the memory allocator. */ MemoryAllocator getMemoryAllocator(); + + /** + * Allows registering to VM lifecycle events. + *

+ * Registered subscribers will automatically be unsubscribed when the {@link VMDevice} + * that registered them is unloaded, e.g. because it is removed from the {@link DeviceBus} + * of the VM stopped. + * + * @return the event bus. + */ + VMLifecycleEventBus getEventBus(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java index 41f5c1bf..1c13ca71 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java @@ -12,11 +12,9 @@ import li.cil.sedna.api.device.MemoryMappedDevice; * implemented through this interface will require explicit driver support * in the guest system. *

- * To listen to lifecycle events of the VM and the device, implement the - * {@link VMDeviceLifecycleListener} interface. This is particularly useful - * for releasing unmanaged resources acquired in {@link #load(VMContext)}. + * To listen to lifecycle events of the VM and the device, register to the event + * bus provided via {@link VMContext#getEventBus()} in {@link #load(VMContext)}. * - * @see VMDeviceLifecycleListener * @see li.cil.oc2.api.bus.device.provider.BlockDeviceProvider * @see li.cil.oc2.api.bus.device.provider.ItemDeviceProvider */ @@ -36,4 +34,14 @@ public interface VMDevice extends Device { * @return {@code true} if the device was loaded successfully; {@code false} otherwise. */ VMDeviceLoadResult load(VMContext context); + + /** + * Called when the device is removed from the context it was loaded with. + *

+ * This can happen because the VM was stopped or the device was removed from + * the device bus that connected it to the VM, for example. + *

+ * Intended for releasing resources acquired in {@link #load(VMContext)}. + */ + void unload(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleEventType.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleEventType.java deleted file mode 100644 index 2b57d6be..00000000 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleEventType.java +++ /dev/null @@ -1,57 +0,0 @@ -package li.cil.oc2.api.bus.device.vm; - -public enum VMDeviceLifecycleEventType { - /** - * Fired exactly once, when the VM first starts running. - *

- * Fired after all devices reported success from {@link VMDevice#load(VMContext)}. - *

- * If a running VM is restored from a saved state, this event will not be fired. It is - * intended for initializing the VM state on boot, e.g. by loading initial executable - * code into memory. - *

- * This is invoked from the worker thread running the VM. - */ - INITIALIZING, - - /** - * Fired when the VM is paused, typically before state is persisted. - *

- * Allows devices that offer interaction to external code-flow to suspend - * such interactions until {@link #RESUMED_RUNNING} is fired. This is required - * if such interactions may modify VM state, to prevent corrupting data being - * serialized asynchronously. - */ - PAUSING, - - /** - * Fired when the VM resumes running. - *

- * Fired after all devices reported success from {@link VMDevice#load(VMContext)}. - *

- * Fired on initial boot-up as well as when the VM resumes after being restored - * from a saved state as well as when continuing to run after being paused for - * a save. It is intended for awaiting asynchronous load and store operations. - */ - RESUME_RUNNING, - - /** - * Fired when the VM resumed running. - *

- * Fired after {@link #RESUME_RUNNING} has been fired and handled by all devices. - *

- * Allows device initialization that relies on all other devices having fully loaded. - *

- * Typically this is used in combination with {@link #PAUSING}, to re-enable external - * interactions after VM state is guaranteed to be safe to modify again. - */ - RESUMED_RUNNING, - - /** - * Fired when the device is disposed, either because the VM is disposed or the source - * of the device is disconnected / removed from the current VM. - *

- * Intended for releasing resources acquired in {@link VMDevice#load(VMContext)}. - */ - UNLOAD, -} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleListener.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleListener.java deleted file mode 100644 index 392d5666..00000000 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLifecycleListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package li.cil.oc2.api.bus.device.vm; - -/** - * Specialization of {@link VMDevice} for devices that require additional - * lifecycle events to function correctly. - *

- * A typical use case is to add custom synchronization points for asynchronous - * operations, such as loading blobs, and for disposing unmanaged resources - * acquired in {@link VMDevice#load(VMContext)}. - */ -public interface VMDeviceLifecycleListener extends VMDevice { - /** - * Called to notify the device of a lifecycle event. - * - * @param event the type of the event. - */ - void handleLifecycleEvent(VMDeviceLifecycleEventType event); -} 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 new file mode 100644 index 00000000..259e6bc1 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java @@ -0,0 +1,21 @@ +package li.cil.oc2.api.bus.device.vm.event; + +import net.minecraft.util.text.ITextComponent; + +import java.util.Optional; + +public final class VMInitializationException extends Exception { + private final ITextComponent message; + + public VMInitializationException(final ITextComponent message) { + this.message = message; + } + + public VMInitializationException() { + this.message = null; + } + + public Optional getErrorMessage() { + return Optional.ofNullable(message); + } +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java new file mode 100644 index 00000000..25265042 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java @@ -0,0 +1,44 @@ +package li.cil.oc2.api.bus.device.vm.event; + +import li.cil.oc2.api.bus.device.vm.VMContext; +import li.cil.oc2.api.bus.device.vm.VMDevice; + +/** + * Fired exactly once, when the VM first starts running. + *

+ * Fired after all devices reported success from {@link VMDevice#load(VMContext)}. + *

+ * If a running VM is restored from a saved state, this event will not be fired. It is + * intended for initializing the VM state on boot, e.g. by loading initial executable + * code into memory. + *

+ * Listeners of this event may throw a {@link VMInitializationException} in case + * initialization fails. For some devices it may be too costly to perform a full + * validity check in {@link VMDevice#load(VMContext)}. These devices may still cause + * a startup to fail this way. + *

+ * This is invoked from the worker thread running the VM. + */ +public final class VMInitializingEvent extends VMLifecycleEvent { + private final long programStartAddress; + + /////////////////////////////////////////////////////////////// + + public VMInitializingEvent(final long programStartAddress) { + this.programStartAddress = programStartAddress; + } + + /////////////////////////////////////////////////////////////// + + /** + * The address where code execution will begin. + *

+ * Some VM implementations may perform some early setup before jumping to this + * memory address. + * + * @return the memory address where code execution begins. + */ + public long getProgramStartAddress() { + return programStartAddress; + } +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEvent.java new file mode 100644 index 00000000..46d10cb2 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEvent.java @@ -0,0 +1,4 @@ +package li.cil.oc2.api.bus.device.vm.event; + +public class VMLifecycleEvent { +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEventBus.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEventBus.java new file mode 100644 index 00000000..a1614531 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEventBus.java @@ -0,0 +1,7 @@ +package li.cil.oc2.api.bus.device.vm.event; + +public interface VMLifecycleEventBus { + void register(Object object); + + void unregister(Object object); +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java new file mode 100644 index 00000000..572e97f3 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java @@ -0,0 +1,12 @@ +package li.cil.oc2.api.bus.device.vm.event; + +/** + * Fired when the VM is paused, typically before state is persisted. + *

+ * Allows devices that offer interaction to external code-flow to suspend + * such interactions until {@link VMResumedRunningEvent} is fired. This is required + * if such interactions may modify VM state, to prevent corrupting data being + * serialized asynchronously. + */ +public final class VMPausingEvent extends VMLifecycleEvent { +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java new file mode 100644 index 00000000..7a8ebe36 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java @@ -0,0 +1,14 @@ +package li.cil.oc2.api.bus.device.vm.event; + +/** + * Fired when the VM resumed running. + *

+ * Fired after {@link VMResumingRunningEvent} has been fired and handled by all devices. + *

+ * Allows device initialization that relies on all other devices having fully loaded. + *

+ * Typically this is used in combination with {@link VMPausingEvent}, to re-enable external + * interactions after VM state is guaranteed to be safe to modify again. + */ +public final class VMResumedRunningEvent extends VMLifecycleEvent { +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java new file mode 100644 index 00000000..0513ac1b --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java @@ -0,0 +1,16 @@ +package li.cil.oc2.api.bus.device.vm.event; + +import li.cil.oc2.api.bus.device.vm.VMContext; +import li.cil.oc2.api.bus.device.vm.VMDevice; + +/** + * Fired when the VM resumes running. + *

+ * Fired after all devices reported success from {@link VMDevice#load(VMContext)}. + *

+ * Fired on initial boot-up as well as when the VM resumes after being restored + * from a saved state as well as when continuing to run after being paused for + * a save. It is intended for awaiting asynchronous load and store operations. + */ +public final class VMResumingRunningEvent extends VMLifecycleEvent { +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java new file mode 100644 index 00000000..6142ef42 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.api.bus.device.vm.event; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/Constants.java b/src/main/java/li/cil/oc2/common/Constants.java index b92e623e..f5dbb4b8 100644 --- a/src/main/java/li/cil/oc2/common/Constants.java +++ b/src/main/java/li/cil/oc2/common/Constants.java @@ -67,7 +67,8 @@ public final class Constants { 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_NO_MEMORY = "gui.oc2.computer.boot_error.no_memory"; + 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_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/data/BuildrootFirmware.java b/src/main/java/li/cil/oc2/common/bus/device/data/BuildrootFirmware.java index 18860544..0c6b8024 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/data/BuildrootFirmware.java +++ b/src/main/java/li/cil/oc2/common/bus/device/data/BuildrootFirmware.java @@ -7,21 +7,18 @@ import li.cil.sedna.memory.MemoryMaps; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; import net.minecraftforge.registries.ForgeRegistryEntry; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import java.io.IOException; public final class BuildrootFirmware extends ForgeRegistryEntry implements Firmware { - private static final Logger LOGGER = LogManager.getLogger(); - @Override - public void run(final MemoryMap memory, final long startAddress) { + public boolean run(final MemoryMap memory, final long startAddress) { try { MemoryMaps.store(memory, startAddress, Buildroot.getFirmware()); MemoryMaps.store(memory, startAddress + 0x200000, Buildroot.getLinuxImage()); + return true; } catch (final IOException e) { - LOGGER.error(e); + return false; } } diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractHardDriveVMDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractHardDriveVMDevice.java index 268594b6..42a1a3a8 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractHardDriveVMDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractHardDriveVMDevice.java @@ -1,7 +1,11 @@ package li.cil.oc2.common.bus.device.item; +import com.google.common.eventbus.Subscribe; import li.cil.oc2.api.bus.device.ItemDevice; -import li.cil.oc2.api.bus.device.vm.*; +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.VMResumingRunningEvent; import li.cil.oc2.common.bus.device.util.IdentityProxy; import li.cil.oc2.common.bus.device.util.OptionalAddress; import li.cil.oc2.common.bus.device.util.OptionalInterrupt; @@ -18,7 +22,8 @@ import java.io.OutputStream; import java.util.Optional; import java.util.UUID; -public abstract class AbstractHardDriveVMDevice extends IdentityProxy implements VMDevice, VMDeviceLifecycleListener, ItemDevice { +@SuppressWarnings("UnstableApiUsage") +public abstract class AbstractHardDriveVMDevice extends IdentityProxy implements VMDevice, ItemDevice { private static final String DEVICE_TAG_NAME = "device"; private static final String ADDRESS_TAG_NAME = "address"; private static final String INTERRUPT_TAG_NAME = "interrupt"; @@ -64,21 +69,33 @@ public abstract class AbstractHardDriveVMDevice extends I return VMDeviceLoadResult.fail(); } + context.getEventBus().register(this); + loadPersistedState(); return VMDeviceLoadResult.success(); } @Override - public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) { - switch (event) { - case RESUME_RUNNING: - awaitStorageOperation(); - break; - case UNLOAD: - unload(); - break; - } + public void unload() { + // Since we cannot serialize the data in a regular serialize call due to the + // actual data being unloaded at that point, but want to permanently persist + // it (it's the contents of the block device) we need to serialize it in the + // unload, too. Don't need to wait for the job, though. + serializeData(); + + data = null; + jobHandle = null; + + device = null; + deviceNbt = null; + address.clear(); + interrupt.clear(); + } + + @Subscribe + public void handleResumingRunningEvent(final VMResumingRunningEvent event) { + awaitStorageOperation(); } @Override @@ -179,22 +196,6 @@ public abstract class AbstractHardDriveVMDevice extends I } } - private void unload() { - // Since we cannot serialize the data in a regular serialize call due to the - // actual data being unloaded at that point, but want to permanently persist - // it (it's the contents of the block device) we need to serialize it in the - // unload, too. Don't need to wait for the job, though. - serializeData(); - - data = null; - jobHandle = null; - - device = null; - deviceNbt = null; - address.clear(); - interrupt.clear(); - } - private void serializeData() { if (data != null) { final Optional optional = getSerializationStream(data); 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 8d6b137a..99cbfdd5 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 @@ -1,7 +1,11 @@ package li.cil.oc2.common.bus.device.item; +import com.google.common.eventbus.Subscribe; import li.cil.oc2.api.bus.device.ItemDevice; -import li.cil.oc2.api.bus.device.vm.*; +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.VMInitializingEvent; import li.cil.oc2.common.bus.device.util.IdentityProxy; import li.cil.sedna.api.memory.MemoryAccessException; import li.cil.sedna.api.memory.MemoryMap; @@ -15,7 +19,8 @@ import org.apache.logging.log4j.Logger; import java.nio.ByteBuffer; import java.util.OptionalLong; -public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy implements VMDevice, VMDeviceLifecycleListener, ItemDevice { +@SuppressWarnings("UnstableApiUsage") +public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy implements VMDevice, ItemDevice { private static final Logger LOGGER = LogManager.getLogger(); /////////////////////////////////////////////////////////////// @@ -58,36 +63,42 @@ public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy implements VMDevice, VMDeviceLifecycleListener, ItemDevice { +@SuppressWarnings("UnstableApiUsage") +public final class FirmwareFlashMemoryVMDevice extends IdentityProxy implements VMDevice, ItemDevice { private final Firmware firmware; private MemoryMap memoryMap; @@ -24,19 +32,20 @@ public final class FirmwareFlashMemoryVMDevice extends IdentityProxy public VMDeviceLoadResult load(final VMContext context) { memoryMap = context.getMemoryMap(); + context.getEventBus().register(this); + return VMDeviceLoadResult.success(); } @Override - public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) { - switch (event) { - case INITIALIZING: - // TODO Have start address passed with event? - firmware.run(memoryMap, 0x80000000L); - break; - case UNLOAD: - memoryMap = null; - break; + public void unload() { + memoryMap = null; + } + + @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)); } } } diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java index 105de6c4..06143434 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java @@ -1,7 +1,11 @@ package li.cil.oc2.common.bus.device.item; +import com.google.common.eventbus.Subscribe; import li.cil.oc2.api.bus.device.ItemDevice; -import li.cil.oc2.api.bus.device.vm.*; +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.VMResumingRunningEvent; import li.cil.oc2.common.Config; import li.cil.oc2.common.bus.device.util.IdentityProxy; import li.cil.oc2.common.bus.device.util.OptionalAddress; @@ -18,7 +22,8 @@ import net.minecraft.util.math.MathHelper; import java.util.UUID; -public final class MemoryDevice extends IdentityProxy implements VMDevice, VMDeviceLifecycleListener, ItemDevice { +@SuppressWarnings("UnstableApiUsage") +public final class MemoryDevice extends IdentityProxy implements VMDevice, ItemDevice { private static final String BLOB_HANDLE_TAG_NAME = "blob"; private static final String ADDRESS_TAG_NAME = "address"; @@ -54,19 +59,25 @@ public final class MemoryDevice extends IdentityProxy implements VMDe loadPersistedState(); + context.getEventBus().register(this); + return VMDeviceLoadResult.success(); } @Override - public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) { - switch (event) { - case RESUME_RUNNING: - awaitStorageOperation(); - break; - case UNLOAD: - unload(); - break; - } + public void unload() { + // Memory is volatile, so free up our persisted blob when device is unloaded. + BlobStorage.freeHandle(blobHandle); + blobHandle = null; + jobHandle = null; + + device = null; + address.clear(); + } + + @Subscribe + public void handleResumingRunningEvent(final VMResumingRunningEvent event) { + awaitStorageOperation(); } @Override @@ -120,14 +131,4 @@ public final class MemoryDevice extends IdentityProxy implements VMDe jobHandle = null; } } - - private void unload() { - // Memory is volatile, so free up our persisted blob when device is unloaded. - BlobStorage.freeHandle(blobHandle); - blobHandle = null; - jobHandle = null; - - device = null; - address.clear(); - } } diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java index e035f46a..1e91fa1c 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java @@ -1,7 +1,11 @@ package li.cil.oc2.common.bus.device.item; +import com.google.common.eventbus.Subscribe; import li.cil.oc2.api.bus.device.ItemDevice; -import li.cil.oc2.api.bus.device.vm.*; +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.VMResumingRunningEvent; import li.cil.oc2.api.capabilities.NetworkInterface; import li.cil.oc2.common.bus.device.util.IdentityProxy; import li.cil.oc2.common.bus.device.util.OptionalAddress; @@ -19,7 +23,8 @@ import net.minecraftforge.common.util.LazyOptional; import javax.annotation.Nullable; -public final class NetworkInterfaceCardItemDevice extends IdentityProxy implements VMDevice, VMDeviceLifecycleListener, ItemDevice, ICapabilityProvider { +@SuppressWarnings("UnstableApiUsage") +public final class NetworkInterfaceCardItemDevice extends IdentityProxy implements VMDevice, ItemDevice, ICapabilityProvider { private static final String DEVICE_TAG_NAME = "device"; private static final String ADDRESS_TAG_NAME = "address"; private static final String INTERRUPT_TAG_NAME = "interrupt"; @@ -69,19 +74,22 @@ public final class NetworkInterfaceCardItemDevice extends IdentityProxy subscribers = new ArrayList<>(); + + public ManagedEventBus(final EventBus eventBus) { + this.eventBus = eventBus; + } + + public void invalidate() { + for (final Object subscriber : subscribers) { + eventBus.unregister(subscriber); + } + subscribers.clear(); + } + + @Override + public void register(final Object object) { + eventBus.register(object); + subscribers.add(object); + } + + @Override + public void unregister(final Object object) { + eventBus.unregister(object); + subscribers.remove(object); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java index e5559c65..4f8bcc78 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java +++ b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java @@ -1,9 +1,11 @@ package li.cil.oc2.common.vm; +import com.google.common.eventbus.EventBus; import li.cil.oc2.api.bus.device.vm.InterruptAllocator; import li.cil.oc2.api.bus.device.vm.MemoryAllocator; import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator; import li.cil.oc2.api.bus.device.vm.VMContext; +import li.cil.oc2.api.bus.device.vm.event.VMLifecycleEventBus; import li.cil.sedna.api.Board; import li.cil.sedna.api.device.InterruptController; import li.cil.sedna.api.device.MemoryMappedDevice; @@ -13,26 +15,29 @@ import java.util.BitSet; import java.util.OptionalLong; import java.util.function.Function; +@SuppressWarnings("UnstableApiUsage") public final class ManagedVMContext implements VMContext { private final ManagedMemoryMap memoryMap; private final ManagedInterruptController interruptController; private final ManagedMemoryRangeAllocator memoryRangeAllocator; private final ManagedInterruptAllocator interruptAllocator; private final ManagedMemoryAllocator memoryAllocator; + private final ManagedEventBus eventBus; /////////////////////////////////////////////////////////////////// public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts, - final Function defaultAddress) { + final EventBus eventBus, final Function defaultAddress) { this.memoryRangeAllocator = new ManagedMemoryRangeAllocator(board, defaultAddress); this.interruptAllocator = new ManagedInterruptAllocator(claimedInterrupts, reservedInterrupts, board.getInterruptCount()); this.memoryMap = new ManagedMemoryMap(board.getMemoryMap()); this.interruptController = new ManagedInterruptController(board.getInterruptController(), interruptAllocator); this.memoryAllocator = new ManagedMemoryAllocator(); + this.eventBus = new ManagedEventBus(eventBus); } - public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts) { - this(board, claimedInterrupts, reservedInterrupts, (memoryMappedDevice) -> OptionalLong.empty()); + public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts, final EventBus eventBus) { + this(board, claimedInterrupts, reservedInterrupts, eventBus, (memoryMappedDevice) -> OptionalLong.empty()); } /////////////////////////////////////////////////////////////////// @@ -48,6 +53,7 @@ public final class ManagedVMContext implements VMContext { interruptAllocator.invalidate(); interruptController.invalidate(); memoryAllocator.invalidate(); + eventBus.invalidate(); } @Override @@ -74,4 +80,9 @@ public final class ManagedVMContext implements VMContext { public MemoryAllocator getMemoryAllocator() { return memoryAllocator; } + + @Override + public VMLifecycleEventBus getEventBus() { + return eventBus; + } } diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java index 35553891..30cf01a6 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java @@ -1,21 +1,26 @@ package li.cil.oc2.common.vm; +import com.google.common.eventbus.EventBus; import li.cil.ceres.api.Serialized; import li.cil.oc2.api.bus.device.Device; -import li.cil.oc2.api.bus.device.vm.*; +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.VMLifecycleEvent; import li.cil.sedna.api.Board; import java.util.*; +@SuppressWarnings("UnstableApiUsage") public final class VirtualMachineDeviceBusAdapter { private final Board board; + private final EventBus eventBus = new EventBus(); private final ManagedVMContext globalContext; private final BitSet claimedInterrupts = new BitSet(); private final HashMap deviceContexts = new HashMap<>(); private final ArrayList incompleteLoads = new ArrayList<>(); - private final HashSet lifecycleEventListeners = new HashSet<>(); private DefaultAddressProvider defaultAddressProvider = unused -> OptionalLong.empty(); /////////////////////////////////////////////////////////////////// @@ -30,8 +35,7 @@ public final class VirtualMachineDeviceBusAdapter { public VirtualMachineDeviceBusAdapter(final Board board) { this.board = board; - this.globalContext = new ManagedVMContext( - board, claimedInterrupts, reservedInterrupts); + this.globalContext = new ManagedVMContext(board, claimedInterrupts, reservedInterrupts, eventBus); this.claimedInterrupts.set(0); } @@ -50,7 +54,7 @@ public final class VirtualMachineDeviceBusAdapter { final VMDevice device = incompleteLoads.get(i); final ManagedVMContext context = new ManagedVMContext( - board, claimedInterrupts, reservedInterrupts, + board, claimedInterrupts, reservedInterrupts, eventBus, (memoryMappedDevice) -> defaultAddressProvider.getDefaultAddress(device)); deviceContexts.put(device, context); @@ -75,7 +79,9 @@ public final class VirtualMachineDeviceBusAdapter { } public void unload() { - fireLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD); + for (final VMDevice device : deviceContexts.keySet()) { + device.unload(); + } suspend(); } @@ -91,10 +97,8 @@ public final class VirtualMachineDeviceBusAdapter { incompleteLoads.addAll(deviceContexts.keySet()); } - public void fireLifecycleEvent(final VMDeviceLifecycleEventType event) { - for (final VMDeviceLifecycleListener tickListener : lifecycleEventListeners) { - tickListener.handleLifecycleEvent(event); - } + public void postLifecycleEvent(final VMLifecycleEvent event) { + eventBus.post(event); } public void addDevices(final Collection devices) { @@ -108,10 +112,6 @@ public final class VirtualMachineDeviceBusAdapter { } incompleteLoads.add(vmDevice); - - if (vmDevice instanceof VMDeviceLifecycleListener) { - lifecycleEventListeners.add((VMDeviceLifecycleListener) vmDevice); - } } } } @@ -121,18 +121,14 @@ public final class VirtualMachineDeviceBusAdapter { if (device instanceof VMDevice) { final VMDevice vmDevice = (VMDevice) device; + vmDevice.unload(); + final ManagedVMContext context = deviceContexts.remove(vmDevice); if (context != null) { context.invalidate(); } incompleteLoads.remove(vmDevice); - - if (vmDevice instanceof VMDeviceLifecycleListener) { - final VMDeviceLifecycleListener eventListener = (VMDeviceLifecycleListener) vmDevice; - lifecycleEventListeners.remove(eventListener); - eventListener.handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD); - } } } } diff --git a/src/main/resources/assets/oc2/lang/en_us.json b/src/main/resources/assets/oc2/lang/en_us.json index df7470c4..262495f8 100644 --- a/src/main/resources/assets/oc2/lang/en_us.json +++ b/src/main/resources/assets/oc2/lang/en_us.json @@ -37,7 +37,8 @@ "config.oc2.admin.fakePlayerUUID": "Fake Player UUID", "gui.oc2.computer.boot_error.unknown": "Unknown Error", - "gui.oc2.computer.boot_error.no_memory": "Insufficient Memory", + "gui.oc2.computer.boot_error.missing_firmware": "Missing Flash Memory", + "gui.oc2.computer.boot_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", diff --git a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java index 27d4eaaf..43657d70 100644 --- a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java +++ b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java @@ -1,6 +1,8 @@ package li.cil.oc2.common.bus; -import li.cil.oc2.api.bus.device.vm.*; +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.common.vm.VirtualMachineDeviceBusAdapter; import li.cil.sedna.api.Board; import li.cil.sedna.api.device.InterruptController; @@ -71,31 +73,31 @@ public final class VMDeviceTests { @Test public void removedDevicesHaveUnloadCalled() { - final VMDeviceLifecycleListener device = mock(VMDeviceLifecycleListener.class); + final VMDevice device = mock(VMDevice.class); when(device.load(any())).thenReturn(VMDeviceLoadResult.success()); adapter.addDevices(Collections.singleton(device)); assertTrue(adapter.load().wasSuccessful()); adapter.removeDevices(Collections.singleton(device)); - verify(device).handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD); + verify(device).unload(); } @Test public void devicesHaveUnloadCalledOnGlobalUnload() { - final VMDeviceLifecycleListener device = mock(VMDeviceLifecycleListener.class); + final VMDevice device = mock(VMDevice.class); when(device.load(any())).thenReturn(VMDeviceLoadResult.success()); adapter.addDevices(Collections.singleton(device)); assertTrue(adapter.load().wasSuccessful()); adapter.unload(); - verify(device).handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD); + verify(device).unload(); } @Test public void devicesHaveLoadCalledAfterGlobalUnload() { - final VMDeviceLifecycleListener device = mock(VMDeviceLifecycleListener.class); + final VMDevice device = mock(VMDevice.class); when(device.load(any())).thenReturn(VMDeviceLoadResult.success()); adapter.addDevices(Collections.singleton(device)); @@ -103,7 +105,7 @@ public final class VMDeviceTests { verify(device).load(any()); adapter.unload(); - verify(device).handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD); + verify(device).unload(); assertTrue(adapter.load().wasSuccessful()); verify(device, times(2)).load(any());