From 64a7776d38fe06c1552eee1798bafbe7680d1d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 3 Feb 2021 00:36:10 +0100 Subject: [PATCH] Massive rework of the VM related data structures. Feels clearer now wrt flow of logic. Tracking used address ranges to avoid use of ranges taken by serialized devices. Allow devices to request joining of worker thread; used for serialization. --- build.gradle | 4 +- .../api/bus/device/vm/InterruptAllocator.java | 10 +- .../bus/device/vm/MemoryRangeAllocator.java | 18 +- .../cil/oc2/api/bus/device/vm/VMContext.java | 14 +- .../bus/device/vm/VMLifecycleEventBus.java | 13 ++ .../device/vm/event/VMLifecycleEventBus.java | 7 - .../client/gui/ComputerTerminalScreen.java | 4 +- .../oc2/client/gui/RobotTerminalScreen.java | 4 +- .../ComputerTileEntityRenderer.java | 8 +- .../java/li/cil/oc2/common/Constants.java | 2 + ...CAdapter.java => RPCDeviceBusAdapter.java} | 12 +- .../bus/device/util/OptionalAddress.java | 6 +- .../bus/device/util/OptionalInterrupt.java | 6 +- .../common/container/ComputerContainer.java | 4 +- .../oc2/common/container/RobotContainer.java | 4 +- .../li/cil/oc2/common/entity/RobotEntity.java | 74 ++++---- .../cil/oc2/common/item/BusInterfaceItem.java | 1 - .../message/ComputerBootErrorMessage.java | 4 +- .../message/ComputerBusStateMessage.java | 4 +- .../message/ComputerRunStateMessage.java | 10 +- .../message/RobotBootErrorMessage.java | 4 +- .../network/message/RobotBusStateMessage.java | 4 +- .../message/RobotInitializationMessage.java | 18 +- .../network/message/RobotRunStateMessage.java | 10 +- .../MemoryRangeListSerializer.java | 43 +++++ .../serializers/MemoryRangeSerializer.java | 26 +++ .../serializers/MessageJsonDeserializer.java | 18 +- .../MethodInvocationJsonDeserializer.java | 8 +- ...RPCDeviceWithIdentifierJsonSerializer.java | 6 +- .../serializers/Serializers.java | 4 + .../common/tileentity/BusCableTileEntity.java | 2 +- .../common/tileentity/ComputerTileEntity.java | 100 +++++------ .../tileentity/DiskDriveTileEntity.java | 18 ++ .../li/cil/oc2/common/util/TerminalUtils.java | 27 +++ .../li/cil/oc2/common/util/WorldUtils.java | 5 +- .../common/vm/AbstractTerminalVMRunner.java | 80 +++++++++ .../AbstractTerminalVirtualMachineRunner.java | 136 --------------- ....java => AbstractVMItemStackHandlers.java} | 8 +- ...State.java => AbstractVirtualMachine.java} | 152 ++++++++++------ ...Provider.java => BaseAddressProvider.java} | 4 +- .../li/cil/oc2/common/vm/BuiltinDevices.java | 57 ++++++ .../oc2/common/vm/CommonVirtualMachine.java | 36 ---- .../li/cil/oc2/common/vm/ManagedEventBus.java | 39 ----- .../vm/ManagedMemoryRangeAllocator.java | 81 --------- .../cil/oc2/common/vm/VMDeviceBusAdapter.java | 114 ++++++++++++ ...Handlers.java => VMItemStackHandlers.java} | 2 +- .../java/li/cil/oc2/common/vm/VMRunState.java | 7 + ...irtualMachineRunner.java => VMRunner.java} | 49 +++++- .../li/cil/oc2/common/vm/VirtualMachine.java | 79 +++------ .../vm/VirtualMachineDeviceBusAdapter.java | 163 ------------------ .../oc2/common/vm/VirtualMachineState.java | 38 ---- .../oc2/common/vm/context/EventManager.java | 5 + .../common/vm/context/InterruptManager.java | 9 + .../common/vm/context/InterruptValidator.java | 7 + .../common/vm/context/MemoryRangeManager.java | 11 ++ .../context/VMContextManagerCollection.java | 9 + .../vm/context/global/GlobalEventBus.java | 53 ++++++ .../global/GlobalInterruptAllocator.java} | 81 +++++---- .../global/GlobalInterruptController.java | 55 ++++++ .../context/global/GlobalMemoryAllocator.java | 32 ++++ .../global/GlobalMemoryMap.java} | 22 +-- .../global/GlobalMemoryRangeAllocator.java | 86 +++++++++ .../vm/context/global/GlobalVMContext.java | 118 +++++++++++++ .../vm/context/global/MemoryRangeList.java | 8 + .../vm/context/global/package-info.java | 7 + .../vm/context/managed/ManagedEventBus.java | 43 +++++ .../managed/ManagedInterruptAllocator.java | 81 +++++++++ .../managed}/ManagedInterruptController.java | 36 ++-- .../managed}/ManagedMemoryAllocator.java | 5 +- .../vm/context/managed/ManagedMemoryMap.java | 80 +++++++++ .../managed/ManagedMemoryRangeAllocator.java | 78 +++++++++ .../managed}/ManagedVMContext.java | 44 +++-- .../vm/context/managed/package-info.java | 7 + .../oc2/common/vm/context/package-info.java | 7 + .../li/cil/oc2/data/ModLootTableProvider.java | 2 +- .../li/cil/oc2/common/bus/VMDeviceTests.java | 31 ++-- .../li/cil/oc2/common/vm/RPCAdapterTests.java | 8 +- 77 files changed, 1558 insertions(+), 914 deletions(-) create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventBus.java delete mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEventBus.java rename src/main/java/li/cil/oc2/common/bus/{RPCAdapter.java => RPCDeviceBusAdapter.java} (97%) create mode 100644 src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeListSerializer.java create mode 100644 src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeSerializer.java create mode 100644 src/main/java/li/cil/oc2/common/util/TerminalUtils.java create mode 100644 src/main/java/li/cil/oc2/common/vm/AbstractTerminalVMRunner.java delete mode 100644 src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java rename src/main/java/li/cil/oc2/common/vm/{AbstractVirtualMachineItemStackHandlers.java => AbstractVMItemStackHandlers.java} (94%) rename src/main/java/li/cil/oc2/common/vm/{AbstractVirtualMachineState.java => AbstractVirtualMachine.java} (68%) rename src/main/java/li/cil/oc2/common/vm/{DefaultAddressProvider.java => BaseAddressProvider.java} (52%) create mode 100644 src/main/java/li/cil/oc2/common/vm/BuiltinDevices.java delete mode 100644 src/main/java/li/cil/oc2/common/vm/CommonVirtualMachine.java delete mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedEventBus.java delete mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedMemoryRangeAllocator.java create mode 100644 src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java rename src/main/java/li/cil/oc2/common/vm/{VirtualMachineItemStackHandlers.java => VMItemStackHandlers.java} (84%) create mode 100644 src/main/java/li/cil/oc2/common/vm/VMRunState.java rename src/main/java/li/cil/oc2/common/vm/{VirtualMachineRunner.java => VMRunner.java} (64%) delete mode 100644 src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java delete mode 100644 src/main/java/li/cil/oc2/common/vm/VirtualMachineState.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/EventManager.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/InterruptManager.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/InterruptValidator.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/MemoryRangeManager.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/VMContextManagerCollection.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/global/GlobalEventBus.java rename src/main/java/li/cil/oc2/common/vm/{ManagedInterruptAllocator.java => context/global/GlobalInterruptAllocator.java} (50%) create mode 100644 src/main/java/li/cil/oc2/common/vm/context/global/GlobalInterruptController.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryAllocator.java rename src/main/java/li/cil/oc2/common/vm/{ManagedMemoryMap.java => context/global/GlobalMemoryMap.java} (74%) create mode 100644 src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryRangeAllocator.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/global/MemoryRangeList.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/global/package-info.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/managed/ManagedEventBus.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/managed/ManagedInterruptAllocator.java rename src/main/java/li/cil/oc2/common/vm/{ => context/managed}/ManagedInterruptController.java (57%) rename src/main/java/li/cil/oc2/common/vm/{ => context/managed}/ManagedMemoryAllocator.java (85%) create mode 100644 src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryMap.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryRangeAllocator.java rename src/main/java/li/cil/oc2/common/vm/{ => context/managed}/ManagedVMContext.java (60%) create mode 100644 src/main/java/li/cil/oc2/common/vm/context/managed/package-info.java create mode 100644 src/main/java/li/cil/oc2/common/vm/context/package-info.java diff --git a/build.gradle b/build.gradle index a6a3dd02..b6dcccb0 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+260' + implementation 'li.cil.oc2:oc2-sedna:0.0.1+262' // 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+93' + implementation 'li.cil.sedna:sedna:0.0.1+95' 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/vm/InterruptAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/InterruptAllocator.java index 7675fc00..3c908553 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/InterruptAllocator.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/InterruptAllocator.java @@ -13,14 +13,14 @@ import java.util.OptionalInt; */ public interface InterruptAllocator { /** - * Tries to reserve an interrupt with the specified index. The returned interrupt - * may differ from the one provided, if the interrupt has already been claimed by - * some other device. In this case, the result will be same as calling {@link #claimInterrupt()}. + * Tries to reserve an interrupt with the specified index. This may fail if the + * interrupt has already been claimed. Use {@link #claimInterrupt()} to obtain + * a free interrupt. * * @param interrupt the interrupt to claim. - * @return the interrupt that was claimed, if any. + * @return {@code true} if the interrupt could be claimed; {@code false} otherwise. */ - OptionalInt claimInterrupt(int interrupt); + boolean claimInterrupt(int interrupt); /** * Tries to claim the next free interrupt. If no more interrupts are available, diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java index 02031715..690929cb 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java @@ -15,21 +15,23 @@ import java.util.OptionalLong; public interface MemoryRangeAllocator { /** * Tries to add a {@link MemoryMappedDevice} to the memory map at the specified - * address. The returned address may differ from the address provided, if the - * device cannot fit into the memory map at the specified address. In this case, - * the result will be the same as calling {@link #claimMemoryRange(MemoryMappedDevice)}. + * address. This may fail if some other device is already mapped to part of the + * range. Use {@link #claimMemoryRange(MemoryMappedDevice)} to claim an unused + * memory range. * * @param address the address to add the specified device at. * @param device the device to add at the specified address. - * @return the address the device was added at, if any. + * @return {@code true} if the memory range could be claimed; {@code false} otherwise. */ - OptionalLong claimMemoryRange(long address, MemoryMappedDevice device); + boolean claimMemoryRange(long address, MemoryMappedDevice device); /** * Tries to add a {@link MemoryMappedDevice} to the memory map at an address - * determined by the virtual machine. This may take into account the type of - * device being added. Typically, {@link li.cil.sedna.api.device.PhysicalMemory} - * devices will be allocated in a different memory region than regular devices. + * determined by the virtual machine. + *

+ * This may take into account the type of device being added. For example, + * {@link li.cil.sedna.api.device.PhysicalMemory} devices will typically be + * allocated in a different memory region than regular devices. *

* If the device could not fit into the memory map at all, this will return * {@link OptionalLong#empty()}. 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 8b0d9d1a..c9cf2186 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,7 +1,6 @@ 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; @@ -93,4 +92,17 @@ public interface VMContext { * @return the event bus. */ VMLifecycleEventBus getEventBus(); + + /** + * Waits for the executor thread of the virtual machine to finish running. + *

+ * Events subscribers can only be registered inside {@link VMDevice#load(VMContext)}. + * Trying to register subscribers after that method has returned will result in an + * exception. + *

+ * Note that this may trigger a {@link li.cil.oc2.api.bus.device.vm.event.VMPausingEvent} + * if the virtual machine has not been paused before. Calling this on a paused virtual + * machine is a no-op. + */ + void joinWorkerThread(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventBus.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventBus.java new file mode 100644 index 00000000..7f0001c8 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventBus.java @@ -0,0 +1,13 @@ +package li.cil.oc2.api.bus.device.vm; + +/** + * Allows registering for {@link li.cil.oc2.api.bus.device.vm.event.VMLifecycleEvent}s. + */ +public interface VMLifecycleEventBus { + /** + * Registers the specified object as a subscriber for events. + * + * @param subscriber the object to subscribe methods of. + */ + void register(Object subscriber); +} 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 deleted file mode 100644 index a1614531..00000000 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMLifecycleEventBus.java +++ /dev/null @@ -1,7 +0,0 @@ -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/client/gui/ComputerTerminalScreen.java b/src/main/java/li/cil/oc2/client/gui/ComputerTerminalScreen.java index 14329aed..74cfed90 100644 --- a/src/main/java/li/cil/oc2/client/gui/ComputerTerminalScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/ComputerTerminalScreen.java @@ -36,7 +36,7 @@ public final class ComputerTerminalScreen extends Screen { terminalWidget.renderBackground(matrixStack, mouseX, mouseY); super.render(matrixStack, mouseX, mouseY, partialTicks); - terminalWidget.render(matrixStack, computer.getState().getBootError()); + terminalWidget.render(matrixStack, computer.getVirtualMachine().getBootError()); } @Override @@ -95,7 +95,7 @@ public final class ComputerTerminalScreen extends Screen { @Override protected boolean isRunning() { - return computer.getState().isRunning(); + return computer.getVirtualMachine().isRunning(); } @Override diff --git a/src/main/java/li/cil/oc2/client/gui/RobotTerminalScreen.java b/src/main/java/li/cil/oc2/client/gui/RobotTerminalScreen.java index 576b1aea..d7c5cb48 100644 --- a/src/main/java/li/cil/oc2/client/gui/RobotTerminalScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/RobotTerminalScreen.java @@ -47,7 +47,7 @@ public final class RobotTerminalScreen extends ContainerScreen { if (itemHandler.getSlots() > 0) { diff --git a/src/main/java/li/cil/oc2/common/container/RobotContainer.java b/src/main/java/li/cil/oc2/common/container/RobotContainer.java index c955e033..ecf5687c 100644 --- a/src/main/java/li/cil/oc2/common/container/RobotContainer.java +++ b/src/main/java/li/cil/oc2/common/container/RobotContainer.java @@ -2,7 +2,7 @@ package li.cil.oc2.common.container; import li.cil.oc2.api.bus.device.DeviceTypes; import li.cil.oc2.common.entity.RobotEntity; -import li.cil.oc2.common.vm.VirtualMachineItemStackHandlers; +import li.cil.oc2.common.vm.VMItemStackHandlers; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; @@ -33,7 +33,7 @@ public final class RobotContainer extends AbstractContainer { super(Containers.ROBOT_CONTAINER.get(), id); this.robot = robot; - final VirtualMachineItemStackHandlers handlers = robot.getItemStackHandlers(); + final VMItemStackHandlers handlers = robot.getItemStackHandlers(); handlers.getItemHandler(DeviceTypes.FLASH_MEMORY).ifPresent(itemHandler -> { if (itemHandler.getSlots() > 0) { diff --git a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java index 5723313f..a77d4c82 100644 --- a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java +++ b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java @@ -22,10 +22,7 @@ import li.cil.oc2.common.item.Items; import li.cil.oc2.common.network.Network; import li.cil.oc2.common.network.message.*; import li.cil.oc2.common.serialization.NBTSerialization; -import li.cil.oc2.common.util.ItemStackUtils; -import li.cil.oc2.common.util.NBTTagIds; -import li.cil.oc2.common.util.NBTUtils; -import li.cil.oc2.common.util.WorldUtils; +import li.cil.oc2.common.util.*; import li.cil.oc2.common.vm.*; import net.minecraft.block.Block; import net.minecraft.block.BlockState; @@ -106,7 +103,7 @@ public final class RobotEntity extends Entity implements Robot { private final AnimationState animationState = new AnimationState(); private final RobotActionProcessor actionProcessor = new RobotActionProcessor(); private final Terminal terminal = new Terminal(); - private final RobotVirtualMachineState state; + private final RobotVirtualMachine virtualMachine; private final RobotItemStackHandlers deviceItems = new RobotItemStackHandlers(); private final ItemStackHandler inventory = new FixedSizeItemStackHandler(INVENTORY_SIZE); private final RobotBusElement busElement = new RobotBusElement(); @@ -120,8 +117,8 @@ public final class RobotEntity extends Entity implements Robot { setNoGravity(true); final RobotBusController busController = new RobotBusController(busElement); - state = new RobotVirtualMachineState(busController, new CommonVirtualMachine(busController)); - state.virtualMachine.rtcMinecraft.setWorld(world); + virtualMachine = new RobotVirtualMachine(busController); + virtualMachine.state.builtinDevices.rtcMinecraft.setWorld(world); deviceItems.busElement.addDevice(new ObjectDevice(new RobotDevice(), "robot")); } @@ -137,11 +134,11 @@ public final class RobotEntity extends Entity implements Robot { return terminal; } - public VirtualMachineState getState() { - return state; + public VirtualMachine getVirtualMachine() { + return virtualMachine; } - public VirtualMachineItemStackHandlers getItemStackHandlers() { + public VMItemStackHandlers getItemStackHandlers() { return deviceItems; } @@ -175,7 +172,7 @@ public final class RobotEntity extends Entity implements Robot { return optional; } - for (final Device device : state.busController.getDevices()) { + for (final Device device : virtualMachine.busController.getDevices()) { if (device instanceof ICapabilityProvider) { final LazyOptional value = ((ICapabilityProvider) device).getCapability(capability, side); if (value.isPresent()) { @@ -197,7 +194,7 @@ public final class RobotEntity extends Entity implements Robot { return; } - state.start(); + virtualMachine.start(); } public void stop() { @@ -206,7 +203,7 @@ public final class RobotEntity extends Entity implements Robot { return; } - state.stop(); + virtualMachine.stop(); } public void dropSelf() { @@ -242,7 +239,7 @@ public final class RobotEntity extends Entity implements Robot { super.tick(); if (!isClient) { - state.tick(); + virtualMachine.tick(); } actionProcessor.tick(); @@ -316,7 +313,7 @@ public final class RobotEntity extends Entity implements Robot { handleUnload(); // Full unload to release out-of-nbt persisted runtime-only data such as ram. - state.virtualMachine.vmAdapter.unload(); + virtualMachine.state.vmAdapter.unload(); } @Override @@ -369,7 +366,7 @@ public final class RobotEntity extends Entity implements Robot { @Override protected void writeAdditional(final CompoundNBT tag) { - tag.put(STATE_TAG_NAME, state.serialize()); + tag.put(STATE_TAG_NAME, virtualMachine.serialize()); tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); tag.put(COMMAND_PROCESSOR_TAG_NAME, actionProcessor.serialize()); tag.put(BUS_ELEMENT_TAG_NAME, busElement.serialize()); @@ -380,7 +377,7 @@ public final class RobotEntity extends Entity implements Robot { @Override protected void readAdditional(final CompoundNBT tag) { - state.deserialize(tag.getCompound(STATE_TAG_NAME)); + virtualMachine.deserialize(tag.getCompound(STATE_TAG_NAME)); NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); actionProcessor.deserialize(tag.getCompound(COMMAND_PROCESSOR_TAG_NAME)); busElement.deserialize(tag.getCompound(BUS_ELEMENT_TAG_NAME)); @@ -448,9 +445,7 @@ public final class RobotEntity extends Entity implements Robot { } private void handleUnload() { - state.joinVirtualMachine(); - state.virtualMachine.vmAdapter.suspend(); - state.busController.dispose(); + virtualMachine.unload(); } private void openTerminalScreen(final ServerPlayerEntity player) { @@ -528,7 +523,7 @@ public final class RobotEntity extends Entity implements Robot { public float topRenderHover = -(hashCode() & 0xFFFF); // init to "random" to avoid synchronous hovering public void update(final float deltaTime, final Random random) { - if (getState().isRunning() || actionProcessor.hasQueuedActions()) { + if (getVirtualMachine().isRunning() || actionProcessor.hasQueuedActions()) { topRenderHover = topRenderHover + deltaTime * HOVER_ANIMATION_SPEED; final float topOffsetY = MathHelper.sin(topRenderHover) / 32f; @@ -696,7 +691,7 @@ public final class RobotEntity extends Entity implements Robot { return false; } - if (!getState().isRunning()) { + if (!getVirtualMachine().isRunning()) { return false; } @@ -713,7 +708,7 @@ public final class RobotEntity extends Entity implements Robot { } } - private final class RobotItemStackHandlers extends AbstractVirtualMachineItemStackHandlers { + private final class RobotItemStackHandlers extends AbstractVMItemStackHandlers { public RobotItemStackHandlers() { super( GroupDefinition.of(DeviceTypes.MEMORY, MEMORY_SLOTS), @@ -773,53 +768,54 @@ public final class RobotEntity extends Entity implements Robot { @Override protected void onBeforeScan() { - state.reload(); - state.virtualMachine.rpcAdapter.pause(); + virtualMachine.pauseAndReload(); } @Override protected void onAfterDeviceScan(final boolean didDevicesChange) { - state.virtualMachine.rpcAdapter.resume(didDevicesChange); + virtualMachine.resume(didDevicesChange); } @Override protected void onDevicesAdded(final Collection devices) { - state.virtualMachine.vmAdapter.addDevices(devices); + virtualMachine.state.vmAdapter.addDevices(devices); } @Override protected void onDevicesRemoved(final Collection devices) { - state.virtualMachine.vmAdapter.removeDevices(devices); + virtualMachine.state.vmAdapter.removeDevices(devices); } } - private final class RobotVirtualMachineRunner extends AbstractTerminalVirtualMachineRunner { - public RobotVirtualMachineRunner(final CommonVirtualMachine virtualMachine, final Terminal terminal) { + private final class RobotVMRunner extends AbstractTerminalVMRunner { + public RobotVMRunner(final AbstractVirtualMachine virtualMachine, final Terminal terminal) { super(virtualMachine, terminal); } @Override protected void sendTerminalUpdateToClient(final ByteBuffer output) { - final RobotTerminalOutputMessage message = new RobotTerminalOutputMessage(RobotEntity.this, output); - Network.sendToClientsTrackingEntity(message, RobotEntity.this); + Network.sendToClientsTrackingEntity(new RobotTerminalOutputMessage(RobotEntity.this, output), RobotEntity.this); } } - private final class RobotVirtualMachineState extends AbstractVirtualMachineState { - private RobotVirtualMachineState(final RobotBusController busController, final CommonVirtualMachine virtualMachine) { - super(busController, virtualMachine); - virtualMachine.vmAdapter.setDefaultAddressProvider(deviceItems::getDefaultDeviceAddress); + private final class RobotVirtualMachine extends AbstractVirtualMachine { + private RobotVirtualMachine(final RobotBusController busController) { + super(busController); + state.vmAdapter.setBaseAddressProvider(deviceItems::getDeviceAddressBase); } @Override - protected AbstractTerminalVirtualMachineRunner createRunner() { - return new RobotVirtualMachineRunner(virtualMachine, terminal); + protected AbstractTerminalVMRunner createRunner() { + return new RobotVMRunner(this, terminal); } @Override public void stopRunnerAndReset() { super.stopRunnerAndReset(); + TerminalUtils.resetTerminal(terminal, output -> Network.sendToClientsTrackingEntity( + new RobotTerminalOutputMessage(RobotEntity.this, output), RobotEntity.this)); + actionProcessor.clear(); } @@ -829,7 +825,7 @@ public final class RobotEntity extends Entity implements Robot { } @Override - protected void handleRunStateChanged(final RunState value) { + protected void handleRunStateChanged(final VMRunState value) { Network.sendToClientsTrackingEntity(new RobotRunStateMessage(RobotEntity.this), RobotEntity.this); } diff --git a/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java b/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java index 92adb67b..0a014a34 100644 --- a/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java +++ b/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java @@ -3,7 +3,6 @@ package li.cil.oc2.common.item; import li.cil.oc2.common.block.Blocks; import li.cil.oc2.common.block.BusCableBlock; import net.minecraft.block.BlockState; -import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.ItemUseContext; import net.minecraft.util.ActionResultType; import net.minecraft.util.Direction; diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java index a37d0d07..4fcc7f82 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java @@ -17,7 +17,7 @@ public final class ComputerBootErrorMessage { public ComputerBootErrorMessage(final ComputerTileEntity tileEntity) { this.pos = tileEntity.getPos(); - this.value = tileEntity.getState().getBootError(); + this.value = tileEntity.getVirtualMachine().getBootError(); } public ComputerBootErrorMessage(final PacketBuffer buffer) { @@ -28,7 +28,7 @@ public final class ComputerBootErrorMessage { public static boolean handleMessage(final ComputerBootErrorMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class, - (tileEntity) -> tileEntity.getState().setBootErrorClient(message.value))); + (tileEntity) -> tileEntity.getVirtualMachine().setBootErrorClient(message.value))); return true; } diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java index 0bdea435..8e4e5335 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java @@ -17,7 +17,7 @@ public final class ComputerBusStateMessage { public ComputerBusStateMessage(final ComputerTileEntity tileEntity) { this.pos = tileEntity.getPos(); - this.value = tileEntity.getState().getBusState(); + this.value = tileEntity.getVirtualMachine().getBusState(); } public ComputerBusStateMessage(final PacketBuffer buffer) { @@ -28,7 +28,7 @@ public final class ComputerBusStateMessage { public static boolean handleMessage(final ComputerBusStateMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class, - (tileEntity) -> tileEntity.getState().setBusStateClient(message.value))); + (tileEntity) -> tileEntity.getVirtualMachine().setBusStateClient(message.value))); return true; } diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java index 755217e2..edb3a5d6 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java @@ -2,7 +2,7 @@ package li.cil.oc2.common.network.message; import li.cil.oc2.common.network.MessageUtils; import li.cil.oc2.common.tileentity.ComputerTileEntity; -import li.cil.oc2.common.vm.VirtualMachineState; +import li.cil.oc2.common.vm.VMRunState; import net.minecraft.network.PacketBuffer; import net.minecraft.util.math.BlockPos; import net.minecraftforge.fml.network.NetworkEvent; @@ -11,13 +11,13 @@ import java.util.function.Supplier; public final class ComputerRunStateMessage { private BlockPos pos; - private VirtualMachineState.RunState value; + private VMRunState value; /////////////////////////////////////////////////////////////////// public ComputerRunStateMessage(final ComputerTileEntity tileEntity) { this.pos = tileEntity.getPos(); - this.value = tileEntity.getState().getRunState(); + this.value = tileEntity.getVirtualMachine().getRunState(); } public ComputerRunStateMessage(final PacketBuffer buffer) { @@ -28,13 +28,13 @@ public final class ComputerRunStateMessage { public static boolean handleMessage(final ComputerRunStateMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class, - (tileEntity) -> tileEntity.getState().setRunStateClient(message.value))); + (tileEntity) -> tileEntity.getVirtualMachine().setRunStateClient(message.value))); return true; } public void fromBytes(final PacketBuffer buffer) { pos = buffer.readBlockPos(); - value = buffer.readEnumValue(VirtualMachineState.RunState.class); + value = buffer.readEnumValue(VMRunState.class); } public static void toBytes(final ComputerRunStateMessage message, final PacketBuffer buffer) { diff --git a/src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java b/src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java index e1e7e555..56529752 100644 --- a/src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java @@ -16,7 +16,7 @@ public final class RobotBootErrorMessage { public RobotBootErrorMessage(final RobotEntity robot) { this.entityId = robot.getEntityId(); - this.value = robot.getState().getBootError(); + this.value = robot.getVirtualMachine().getBootError(); } public RobotBootErrorMessage(final PacketBuffer buffer) { @@ -27,7 +27,7 @@ public final class RobotBootErrorMessage { public static boolean handleMessage(final RobotBootErrorMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class, - (robot) -> robot.getState().setBootErrorClient(message.value))); + (robot) -> robot.getVirtualMachine().setBootErrorClient(message.value))); return true; } diff --git a/src/main/java/li/cil/oc2/common/network/message/RobotBusStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/RobotBusStateMessage.java index d6cdf62f..91eb9b67 100644 --- a/src/main/java/li/cil/oc2/common/network/message/RobotBusStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/RobotBusStateMessage.java @@ -16,7 +16,7 @@ public final class RobotBusStateMessage { public RobotBusStateMessage(final RobotEntity robot) { this.entityId = robot.getEntityId(); - this.value = robot.getState().getBusState(); + this.value = robot.getVirtualMachine().getBusState(); } public RobotBusStateMessage(final PacketBuffer buffer) { @@ -27,7 +27,7 @@ public final class RobotBusStateMessage { public static boolean handleMessage(final RobotBusStateMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class, - (robot) -> robot.getState().setBusStateClient(message.value))); + (robot) -> robot.getVirtualMachine().setBusStateClient(message.value))); return true; } diff --git a/src/main/java/li/cil/oc2/common/network/message/RobotInitializationMessage.java b/src/main/java/li/cil/oc2/common/network/message/RobotInitializationMessage.java index 96c16700..193a826f 100644 --- a/src/main/java/li/cil/oc2/common/network/message/RobotInitializationMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/RobotInitializationMessage.java @@ -4,7 +4,7 @@ import li.cil.oc2.common.bus.AbstractDeviceBusController; import li.cil.oc2.common.entity.RobotEntity; import li.cil.oc2.common.network.MessageUtils; import li.cil.oc2.common.serialization.NBTSerialization; -import li.cil.oc2.common.vm.VirtualMachineState; +import li.cil.oc2.common.vm.VMRunState; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; import net.minecraft.util.text.ITextComponent; @@ -15,7 +15,7 @@ import java.util.function.Supplier; public final class RobotInitializationMessage { private int entityId; private AbstractDeviceBusController.BusState busState; - private VirtualMachineState.RunState runState; + private VMRunState runState; private ITextComponent bootError; private CompoundNBT terminal; @@ -23,9 +23,9 @@ public final class RobotInitializationMessage { public RobotInitializationMessage(final RobotEntity robot) { this.entityId = robot.getEntityId(); - this.busState = robot.getState().getBusState(); - this.runState = robot.getState().getRunState(); - this.bootError = robot.getState().getBootError(); + this.busState = robot.getVirtualMachine().getBusState(); + this.runState = robot.getVirtualMachine().getRunState(); + this.bootError = robot.getVirtualMachine().getBootError(); this.terminal = NBTSerialization.serialize(robot.getTerminal()); } @@ -38,9 +38,9 @@ public final class RobotInitializationMessage { public static boolean handleMessage(final RobotInitializationMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class, (robot) -> { - robot.getState().setBusStateClient(message.busState); - robot.getState().setRunStateClient(message.runState); - robot.getState().setBootErrorClient(message.bootError); + robot.getVirtualMachine().setBusStateClient(message.busState); + robot.getVirtualMachine().setRunStateClient(message.runState); + robot.getVirtualMachine().setBootErrorClient(message.bootError); NBTSerialization.deserialize(message.terminal, robot.getTerminal()); })); return true; @@ -49,7 +49,7 @@ public final class RobotInitializationMessage { public void fromBytes(final PacketBuffer buffer) { entityId = buffer.readVarInt(); busState = buffer.readEnumValue(AbstractDeviceBusController.BusState.class); - runState = buffer.readEnumValue(VirtualMachineState.RunState.class); + runState = buffer.readEnumValue(VMRunState.class); bootError = buffer.readTextComponent(); terminal = buffer.readCompoundTag(); } diff --git a/src/main/java/li/cil/oc2/common/network/message/RobotRunStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/RobotRunStateMessage.java index d2ec7c02..c2c192f3 100644 --- a/src/main/java/li/cil/oc2/common/network/message/RobotRunStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/RobotRunStateMessage.java @@ -2,7 +2,7 @@ package li.cil.oc2.common.network.message; import li.cil.oc2.common.entity.RobotEntity; import li.cil.oc2.common.network.MessageUtils; -import li.cil.oc2.common.vm.VirtualMachineState; +import li.cil.oc2.common.vm.VMRunState; import net.minecraft.network.PacketBuffer; import net.minecraftforge.fml.network.NetworkEvent; @@ -10,13 +10,13 @@ import java.util.function.Supplier; public final class RobotRunStateMessage { private int entityId; - private VirtualMachineState.RunState value; + private VMRunState value; /////////////////////////////////////////////////////////////////// public RobotRunStateMessage(final RobotEntity robot) { this.entityId = robot.getEntityId(); - this.value = robot.getState().getRunState(); + this.value = robot.getVirtualMachine().getRunState(); } public RobotRunStateMessage(final PacketBuffer buffer) { @@ -27,13 +27,13 @@ public final class RobotRunStateMessage { public static boolean handleMessage(final RobotRunStateMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class, - (robot) -> robot.getState().setRunStateClient(message.value))); + (robot) -> robot.getVirtualMachine().setRunStateClient(message.value))); return true; } public void fromBytes(final PacketBuffer buffer) { entityId = buffer.readVarInt(); - value = buffer.readEnumValue(VirtualMachineState.RunState.class); + value = buffer.readEnumValue(VMRunState.class); } public static void toBytes(final RobotRunStateMessage message, final PacketBuffer buffer) { diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeListSerializer.java b/src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeListSerializer.java new file mode 100644 index 00000000..50cd0f33 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeListSerializer.java @@ -0,0 +1,43 @@ +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 li.cil.oc2.common.vm.context.global.MemoryRangeList; +import li.cil.sedna.api.memory.MemoryRange; + +import javax.annotation.Nullable; +import java.util.Arrays; + +public final class MemoryRangeListSerializer implements Serializer { + @Override + public void serialize(final SerializationVisitor visitor, final Class type, final Object value) throws SerializationException { + final MemoryRangeList list = (MemoryRangeList) value; + visitor.putObject("value", MemoryRange[].class, list.toArray(new MemoryRange[0])); + } + + @Nullable + @Override + public MemoryRangeList deserialize(final DeserializationVisitor visitor, final Class type, @Nullable final Object value) throws SerializationException { + MemoryRangeList list = (MemoryRangeList) value; + if (!visitor.exists("value")) { + return list; + } + + final MemoryRange[] array = (MemoryRange[]) visitor.getObject("value", MemoryRange[].class, null); + if (array == null) { + return null; + } + + if (list == null) { + list = new MemoryRangeList(); + } else { + list.clear(); + } + + list.addAll(Arrays.asList(array)); + + return list; + } +} diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeSerializer.java b/src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeSerializer.java new file mode 100644 index 00000000..5acd41c9 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/MemoryRangeSerializer.java @@ -0,0 +1,26 @@ +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 li.cil.sedna.api.memory.MemoryRange; +import org.jetbrains.annotations.Nullable; + +public final class MemoryRangeSerializer implements Serializer { + @Override + public void serialize(final SerializationVisitor visitor, final Class type, final Object value) throws SerializationException { + final MemoryRange range = (MemoryRange) value; + visitor.putLong("start", range.start); + visitor.putLong("end", range.end); + } + + @Override + public MemoryRange deserialize(final DeserializationVisitor visitor, final Class type, @Nullable final Object value) throws SerializationException { + if (!visitor.exists("start") || !visitor.exists("end")) { + return (MemoryRange) value; + } + + return MemoryRange.of(visitor.getLong("start"), visitor.getLong("end")); + } +} diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/MessageJsonDeserializer.java b/src/main/java/li/cil/oc2/common/serialization/serializers/MessageJsonDeserializer.java index 1e1431bb..f90efdb5 100644 --- a/src/main/java/li/cil/oc2/common/serialization/serializers/MessageJsonDeserializer.java +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/MessageJsonDeserializer.java @@ -1,35 +1,35 @@ package li.cil.oc2.common.serialization.serializers; import com.google.gson.*; -import li.cil.oc2.common.bus.RPCAdapter; +import li.cil.oc2.common.bus.RPCDeviceBusAdapter; import java.lang.reflect.Type; import java.util.UUID; -public final class MessageJsonDeserializer implements JsonDeserializer { +public final class MessageJsonDeserializer implements JsonDeserializer { @Override - public RPCAdapter.Message deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { + public RPCDeviceBusAdapter.Message deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { final JsonObject jsonObject = json.getAsJsonObject(); final String messageType = jsonObject.get("type").getAsString(); final Object messageData; switch (messageType) { - case RPCAdapter.Message.MESSAGE_TYPE_LIST: { + case RPCDeviceBusAdapter.Message.MESSAGE_TYPE_LIST: { messageData = null; break; } - case RPCAdapter.Message.MESSAGE_TYPE_METHODS: { + case RPCDeviceBusAdapter.Message.MESSAGE_TYPE_METHODS: { messageData = UUID.fromString(jsonObject.getAsJsonPrimitive("data").getAsString()); break; } - case RPCAdapter.Message.MESSAGE_TYPE_INVOKE_METHOD: { - messageData = context.deserialize(jsonObject.getAsJsonObject("data"), RPCAdapter.MethodInvocation.class); + case RPCDeviceBusAdapter.Message.MESSAGE_TYPE_INVOKE_METHOD: { + messageData = context.deserialize(jsonObject.getAsJsonObject("data"), RPCDeviceBusAdapter.MethodInvocation.class); break; } default: { - throw new JsonParseException(RPCAdapter.ERROR_UNKNOWN_MESSAGE_TYPE); + throw new JsonParseException(RPCDeviceBusAdapter.ERROR_UNKNOWN_MESSAGE_TYPE); } } - return new RPCAdapter.Message(messageType, messageData); + return new RPCDeviceBusAdapter.Message(messageType, messageData); } } diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/MethodInvocationJsonDeserializer.java b/src/main/java/li/cil/oc2/common/serialization/serializers/MethodInvocationJsonDeserializer.java index 7e252239..0d3d70f7 100644 --- a/src/main/java/li/cil/oc2/common/serialization/serializers/MethodInvocationJsonDeserializer.java +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/MethodInvocationJsonDeserializer.java @@ -1,18 +1,18 @@ package li.cil.oc2.common.serialization.serializers; import com.google.gson.*; -import li.cil.oc2.common.bus.RPCAdapter; +import li.cil.oc2.common.bus.RPCDeviceBusAdapter; import java.lang.reflect.Type; import java.util.UUID; -public final class MethodInvocationJsonDeserializer implements JsonDeserializer { +public final class MethodInvocationJsonDeserializer implements JsonDeserializer { @Override - public RPCAdapter.MethodInvocation deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { + public RPCDeviceBusAdapter.MethodInvocation deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { final JsonObject jsonObject = json.getAsJsonObject(); final UUID deviceId = context.deserialize(jsonObject.get("deviceId"), UUID.class); final String methodName = jsonObject.get("name").getAsString(); final JsonElement parameters = jsonObject.get("parameters"); - return new RPCAdapter.MethodInvocation(deviceId, methodName, parameters != null && parameters.isJsonArray() ? parameters.getAsJsonArray() : new JsonArray()); + return new RPCDeviceBusAdapter.MethodInvocation(deviceId, methodName, parameters != null && parameters.isJsonArray() ? parameters.getAsJsonArray() : new JsonArray()); } } diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/RPCDeviceWithIdentifierJsonSerializer.java b/src/main/java/li/cil/oc2/common/serialization/serializers/RPCDeviceWithIdentifierJsonSerializer.java index 2b66ed0c..97afc7ba 100644 --- a/src/main/java/li/cil/oc2/common/serialization/serializers/RPCDeviceWithIdentifierJsonSerializer.java +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/RPCDeviceWithIdentifierJsonSerializer.java @@ -1,13 +1,13 @@ package li.cil.oc2.common.serialization.serializers; import com.google.gson.*; -import li.cil.oc2.common.bus.RPCAdapter; +import li.cil.oc2.common.bus.RPCDeviceBusAdapter; import java.lang.reflect.Type; -public final class RPCDeviceWithIdentifierJsonSerializer implements JsonSerializer { +public final class RPCDeviceWithIdentifierJsonSerializer implements JsonSerializer { @Override - public JsonElement serialize(final RPCAdapter.RPCDeviceWithIdentifier src, final Type typeOfSrc, final JsonSerializationContext context) { + public JsonElement serialize(final RPCDeviceBusAdapter.RPCDeviceWithIdentifier src, final Type typeOfSrc, final JsonSerializationContext context) { if (src == null) { return JsonNull.INSTANCE; } 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 f3897b9b..98e8a5f0 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,8 @@ package li.cil.oc2.common.serialization.serializers; import com.google.gson.JsonArray; import li.cil.ceres.Ceres; +import li.cil.oc2.common.vm.context.global.MemoryRangeList; +import li.cil.sedna.api.memory.MemoryRange; import net.minecraft.util.text.ITextComponent; public final class Serializers { @@ -20,5 +22,7 @@ public final class Serializers { Ceres.putSerializer(JsonArray.class, new JsonArraySerializer()); Ceres.putSerializer(ITextComponent.class, new TextComponentSerializer()); + Ceres.putSerializer(MemoryRange.class, new MemoryRangeSerializer()); + Ceres.putSerializer(MemoryRangeList.class, new MemoryRangeListSerializer()); } } diff --git a/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java b/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java index 68c3eeb4..41290bfe 100644 --- a/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java +++ b/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java @@ -51,7 +51,7 @@ public final class BusCableTileEntity extends AbstractTileEntity { @Override public void read(final BlockState state, final CompoundNBT tag) { super.read(state, tag); - if (tag.contains(BUS_ELEMENT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + if (tag.contains(BUS_ELEMENT_TAG_NAME, NBTTagIds.TAG_LIST)) { busElement.deserializeNBT(tag.getList(BUS_ELEMENT_TAG_NAME, NBTTagIds.TAG_COMPOUND)); } } diff --git a/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java index db8de2d1..6a09128d 100644 --- a/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java +++ b/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java @@ -22,6 +22,7 @@ import li.cil.oc2.common.serialization.NBTSerialization; import li.cil.oc2.common.util.HorizontalBlockUtils; import li.cil.oc2.common.util.ItemStackUtils; import li.cil.oc2.common.util.NBTTagIds; +import li.cil.oc2.common.util.TerminalUtils; import li.cil.oc2.common.vm.*; import net.minecraft.block.BlockState; import net.minecraft.item.ItemStack; @@ -61,9 +62,8 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic private final Terminal terminal = new Terminal(); private final TileEntityDeviceBusElement busElement = new ComputerBusElement(); - - private final ComputerVirtualMachineState state; private final ComputerItemStackHandlers items = new ComputerItemStackHandlers(); + private final ComputerVirtualMachine virtualMachine = new ComputerVirtualMachine(new ComputerBusController(busElement), items::getDeviceAddressBase); /////////////////////////////////////////////////////////////////// @@ -72,20 +72,17 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic // We want to unload devices even on world unload to free global resources. setNeedsWorldUnloadEvent(); - - final ComputerBusController busController = new ComputerBusController(busElement); - state = new ComputerVirtualMachineState(busController, new CommonVirtualMachine(busController)); } public Terminal getTerminal() { return terminal; } - public VirtualMachineState getState() { - return state; + public VirtualMachine getVirtualMachine() { + return virtualMachine; } - public VirtualMachineItemStackHandlers getItemStackHandlers() { + public VMItemStackHandlers getItemStackHandlers() { return items; } @@ -95,7 +92,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic return; } - state.start(); + virtualMachine.start(); } public void stop() { @@ -104,11 +101,11 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic return; } - state.stop(); + virtualMachine.stop(); } public void handleNeighborChanged() { - state.busController.scheduleBusScan(); + virtualMachine.busController.scheduleBusScan(); } @Override @@ -123,7 +120,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic } final Direction localSide = HorizontalBlockUtils.toLocal(getBlockState(), side); - for (final Device device : state.busController.getDevices()) { + for (final Device device : virtualMachine.busController.getDevices()) { if (device instanceof ICapabilityProvider) { final LazyOptional value = ((ICapabilityProvider) device).getCapability(capability, localSide); if (value.isPresent()) { @@ -156,17 +153,17 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic world.notifyNeighborsOfStateChange(getPos(), getBlockState().getBlock()); } - state.tick(); + virtualMachine.tick(); } @Override public void remove() { super.remove(); - // Unload only suspends, but we want to do a full clean-up when we get - // destroyed, so stuff inside us can delete out-of-nbt persisted runtime- - // only data such as ram. - state.virtualMachine.vmAdapter.unload(); + // super.remove() calls onUnload. This in turn only suspends, but we want to do + // a full clean-up when we get destroyed, so stuff inside us can delete out-of-nbt + // persisted runtime-only data such as ram. + virtualMachine.state.vmAdapter.unload(); } @Override @@ -174,9 +171,9 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic final CompoundNBT tag = super.getUpdateTag(); tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); - tag.putInt(AbstractVirtualMachineState.BUS_STATE_TAG_NAME, state.getBusState().ordinal()); - tag.putInt(AbstractVirtualMachineState.RUN_STATE_TAG_NAME, state.getRunState().ordinal()); - tag.putString(AbstractVirtualMachineState.BOOT_ERROR_TAG_NAME, ITextComponent.Serializer.toJson(state.getBootError())); + tag.putInt(AbstractVirtualMachine.BUS_STATE_TAG_NAME, virtualMachine.getBusState().ordinal()); + tag.putInt(AbstractVirtualMachine.RUN_STATE_TAG_NAME, virtualMachine.getRunState().ordinal()); + tag.putString(AbstractVirtualMachine.BOOT_ERROR_TAG_NAME, ITextComponent.Serializer.toJson(virtualMachine.getBootError())); return tag; } @@ -186,16 +183,16 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic super.handleUpdateTag(blockState, tag); NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); - state.setBusStateClient(AbstractDeviceBusController.BusState.values()[tag.getInt(AbstractVirtualMachineState.BUS_STATE_TAG_NAME)]); - state.setRunStateClient(VirtualMachineState.RunState.values()[tag.getInt(AbstractVirtualMachineState.RUN_STATE_TAG_NAME)]); - state.setBootErrorClient(ITextComponent.Serializer.getComponentFromJson(tag.getString(AbstractVirtualMachineState.BOOT_ERROR_TAG_NAME))); + virtualMachine.setBusStateClient(AbstractDeviceBusController.BusState.values()[tag.getInt(AbstractVirtualMachine.BUS_STATE_TAG_NAME)]); + virtualMachine.setRunStateClient(VMRunState.values()[tag.getInt(AbstractVirtualMachine.RUN_STATE_TAG_NAME)]); + virtualMachine.setBootErrorClient(ITextComponent.Serializer.getComponentFromJson(tag.getString(AbstractVirtualMachine.BOOT_ERROR_TAG_NAME))); } @Override public CompoundNBT write(CompoundNBT tag) { tag = super.write(tag); - tag.put(STATE_TAG_NAME, state.serialize()); + tag.put(STATE_TAG_NAME, virtualMachine.serialize()); tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); tag.put(BUS_ELEMENT_TAG_NAME, NBTSerialization.serialize(busElement)); tag.put(Constants.INVENTORY_TAG_NAME, items.serialize()); @@ -207,7 +204,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic public void read(final BlockState blockState, final CompoundNBT tag) { super.read(blockState, tag); - state.deserialize(tag.getCompound(STATE_TAG_NAME)); + virtualMachine.deserialize(tag.getCompound(STATE_TAG_NAME)); NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); if (tag.contains(BUS_ELEMENT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { @@ -243,26 +240,26 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic super.loadServer(); busElement.initialize(); - state.virtualMachine.rtcMinecraft.setWorld(getWorld()); + virtualMachine.state.builtinDevices.rtcMinecraft.setWorld(getWorld()); } @Override protected void unloadServer() { super.unloadServer(); - state.joinVirtualMachine(); - state.virtualMachine.vmAdapter.suspend(); - - state.busController.dispose(); + virtualMachine.unload(); // This is necessary in case some other controller found us before our controller - // did its scan, which can happen because the scan can happen with a delay. + // did its scan, which can happen because the scan can happen with a delay. In + // that case we don't know that controller and disposing our controller won't + // notify it, so we also send out a notification through our bus element, which + // would be registered with other controllers in that case. busElement.scheduleScan(); } /////////////////////////////////////////////////////////////////// - private final class ComputerItemStackHandlers extends AbstractVirtualMachineItemStackHandlers { + private final class ComputerItemStackHandlers extends AbstractVMItemStackHandlers { public ComputerItemStackHandlers() { super( GroupDefinition.of(DeviceTypes.MEMORY, MEMORY_SLOTS), @@ -292,23 +289,22 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic @Override protected void onBeforeScan() { - state.reload(); - state.virtualMachine.rpcAdapter.pause(); + virtualMachine.pauseAndReload(); } @Override protected void onAfterDeviceScan(final boolean didDevicesChange) { - state.virtualMachine.rpcAdapter.resume(didDevicesChange); + virtualMachine.resume(didDevicesChange); } @Override protected void onDevicesAdded(final Collection devices) { - state.virtualMachine.vmAdapter.addDevices(devices); + virtualMachine.state.vmAdapter.addDevices(devices); } @Override protected void onDevicesRemoved(final Collection devices) { - state.virtualMachine.vmAdapter.removeDevices(devices); + virtualMachine.state.vmAdapter.removeDevices(devices); } } @@ -332,24 +328,24 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic } } - private final class ComputerVirtualMachineRunner extends AbstractTerminalVirtualMachineRunner { - public ComputerVirtualMachineRunner(final CommonVirtualMachine virtualMachine, final Terminal terminal) { + private final class ComputerVMRunner extends AbstractTerminalVMRunner { + public ComputerVMRunner(final AbstractVirtualMachine virtualMachine, final Terminal terminal) { super(virtualMachine, terminal); } @Override protected void sendTerminalUpdateToClient(final ByteBuffer output) { - final ComputerTerminalOutputMessage message = new ComputerTerminalOutputMessage(ComputerTileEntity.this, output); - Network.sendToClientsTrackingChunk(message, state.chunk); + Network.sendToClientsTrackingChunk(new ComputerTerminalOutputMessage(ComputerTileEntity.this, output), virtualMachine.chunk); } } - private final class ComputerVirtualMachineState extends AbstractVirtualMachineState { + private final class ComputerVirtualMachine extends AbstractVirtualMachine { private Chunk chunk; - private ComputerVirtualMachineState(final ComputerBusController busController, final CommonVirtualMachine virtualMachine) { - super(busController, virtualMachine); - virtualMachine.vmAdapter.setDefaultAddressProvider(items::getDefaultDeviceAddress); + private ComputerVirtualMachine(final AbstractDeviceBusController busController, final BaseAddressProvider baseAddressProvider) { + super(busController); + state.vmAdapter.setBaseAddressProvider(baseAddressProvider); + state.board.setStandardOutputDevice(state.builtinDevices.uart); } @Override @@ -366,8 +362,16 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic } @Override - protected AbstractTerminalVirtualMachineRunner createRunner() { - return new ComputerVirtualMachineRunner(virtualMachine, terminal); + public void stopRunnerAndReset() { + super.stopRunnerAndReset(); + + TerminalUtils.resetTerminal(terminal, output -> Network.sendToClientsTrackingChunk( + new ComputerTerminalOutputMessage(ComputerTileEntity.this, output), virtualMachine.chunk)); + } + + @Override + protected AbstractTerminalVMRunner createRunner() { + return new ComputerVMRunner(this, terminal); } @Override @@ -382,7 +386,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic } @Override - protected void handleRunStateChanged(final RunState value) { + protected void handleRunStateChanged(final VMRunState value) { // This method can be called from disposal logic, so if we are disposed quickly enough // chunk may not be initialized yet. Avoid resulting NRE in network logic. if (chunk != null) { diff --git a/src/main/java/li/cil/oc2/common/tileentity/DiskDriveTileEntity.java b/src/main/java/li/cil/oc2/common/tileentity/DiskDriveTileEntity.java index 803e5c10..39a83ea4 100644 --- a/src/main/java/li/cil/oc2/common/tileentity/DiskDriveTileEntity.java +++ b/src/main/java/li/cil/oc2/common/tileentity/DiskDriveTileEntity.java @@ -1,7 +1,9 @@ package li.cil.oc2.common.tileentity; import li.cil.oc2.api.bus.device.data.BlockDeviceData; +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.Config; import li.cil.oc2.common.Constants; import li.cil.oc2.common.block.DiskDriveBlock; @@ -186,6 +188,8 @@ public final class DiskDriveTileEntity extends AbstractTileEntity { } private final class DiskDriveVMDevice extends AbstractHardDriveVMDevice { + private VMContext context; + public DiskDriveVMDevice() { super(DiskDriveTileEntity.this); } @@ -211,6 +215,12 @@ public final class DiskDriveTileEntity extends AbstractTileEntity { updateBlockDevice(new CompoundNBT()); } + @Override + public VMDeviceLoadResult load(final VMContext context) { + this.context = context; + return super.load(context); + } + @Override protected int getSize() { return Config.maxFloppySize; @@ -245,6 +255,10 @@ public final class DiskDriveTileEntity extends AbstractTileEntity { @Override protected Optional getSerializationStream(final BlockDevice device) { + if (context != null) { + context.joinWorkerThread(); + } + if (device.isReadonly()) { return Optional.empty(); } else { @@ -254,6 +268,10 @@ public final class DiskDriveTileEntity extends AbstractTileEntity { @Override protected OutputStream getDeserializationStream(final BlockDevice device) { + if (context != null) { + context.joinWorkerThread(); + } + return device.getOutputStream(); } } diff --git a/src/main/java/li/cil/oc2/common/util/TerminalUtils.java b/src/main/java/li/cil/oc2/common/util/TerminalUtils.java new file mode 100644 index 00000000..044cdb1a --- /dev/null +++ b/src/main/java/li/cil/oc2/common/util/TerminalUtils.java @@ -0,0 +1,27 @@ +package li.cil.oc2.common.util; + +import li.cil.oc2.common.vm.Terminal; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +public final class TerminalUtils { + private static final ByteBuffer TERMINAL_RESET_SEQUENCE = ByteBuffer.wrap(new byte[]{ + // Make sure we're in normal mode. + 'J', + // Reset color and style. + '\033', '[', '0', 'm', + // Clear screen. + '\033', '[', '2', 'J' + }); + + /////////////////////////////////////////////////////////////////// + + public static void resetTerminal(final Terminal terminal, final Consumer packetSender) { + TERMINAL_RESET_SEQUENCE.clear(); + terminal.putOutput(TERMINAL_RESET_SEQUENCE); + + TERMINAL_RESET_SEQUENCE.flip(); + packetSender.accept(TERMINAL_RESET_SEQUENCE); + } +} diff --git a/src/main/java/li/cil/oc2/common/util/WorldUtils.java b/src/main/java/li/cil/oc2/common/util/WorldUtils.java index 5496cfa6..7cc753ad 100644 --- a/src/main/java/li/cil/oc2/common/util/WorldUtils.java +++ b/src/main/java/li/cil/oc2/common/util/WorldUtils.java @@ -2,8 +2,6 @@ package li.cil.oc2.common.util; import net.minecraft.block.Block; import net.minecraft.block.SoundType; -import net.minecraft.entity.Entity; -import net.minecraft.entity.item.ItemEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundCategory; @@ -13,7 +11,6 @@ import net.minecraft.util.math.ChunkPos; import net.minecraft.world.IWorld; import javax.annotation.Nullable; -import java.util.List; import java.util.function.Function; public final class WorldUtils { @@ -57,7 +54,7 @@ public final class WorldUtils { return block.getClass().getSimpleName(); } - public static void playSound(final IWorld world, final BlockPos pos, final SoundType soundType, Function soundEvent) { + public static void playSound(final IWorld world, final BlockPos pos, final SoundType soundType, final Function soundEvent) { playSound(world, pos, soundType, soundEvent.apply(soundType)); } diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVMRunner.java b/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVMRunner.java new file mode 100644 index 00000000..0fcb1502 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVMRunner.java @@ -0,0 +1,80 @@ +package li.cil.oc2.common.vm; + +import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; +import li.cil.sedna.device.serial.UART16550A; + +import java.nio.ByteBuffer; + +public abstract class AbstractTerminalVMRunner extends VMRunner { + private final UART16550A uart; + private final Terminal terminal; + + /////////////////////////////////////////////////////////////////// + + // Thread-local buffers for lock-free read/writes in inner loop. + private final ByteArrayFIFOQueue outputBuffer = new ByteArrayFIFOQueue(1024); + private final ByteArrayFIFOQueue inputBuffer = new ByteArrayFIFOQueue(32); + + /////////////////////////////////////////////////////////////////// + + public AbstractTerminalVMRunner(final AbstractVirtualMachine virtualMachine, final Terminal terminal) { + super(virtualMachine); + this.terminal = terminal; + uart = virtualMachine.state.builtinDevices.uart; + } + + /////////////////////////////////////////////////////////////////// + + protected abstract void sendTerminalUpdateToClient(final ByteBuffer output); + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleBeforeRun() { + super.handleBeforeRun(); + + int value; + while ((value = terminal.readInput()) != -1) { + inputBuffer.enqueue((byte) value); + } + } + + @Override + protected void step(final int cyclesPerStep) { + super.step(cyclesPerStep); + + while (!inputBuffer.isEmpty() && uart.canPutByte()) { + uart.putByte(inputBuffer.dequeueByte()); + } + uart.flush(); + + int value; + while ((value = uart.read()) != -1) { + outputBuffer.enqueue((byte) value); + } + } + + @Override + protected void handleAfterRun() { + super.handleAfterRun(); + + final ByteBuffer output = ByteBuffer.allocate(outputBuffer.size()); + while (!outputBuffer.isEmpty()) { + output.put(outputBuffer.dequeueByte()); + } + + output.flip(); + putTerminalOutput(output); + } + + /////////////////////////////////////////////////////////////////// + + private void putTerminalOutput(final ByteBuffer output) { + if (output.hasRemaining()) { + terminal.putOutput(output); + + output.flip(); + sendTerminalUpdateToClient(output); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java b/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java deleted file mode 100644 index 047317c3..00000000 --- a/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java +++ /dev/null @@ -1,136 +0,0 @@ -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; - -public abstract class AbstractTerminalVirtualMachineRunner extends VirtualMachineRunner { - private static final ByteBuffer TERMINAL_RESET_SEQUENCE = ByteBuffer.wrap(new byte[]{ - // Make sure we're in normal mode. - 'J', - // Reset color and style. - '\033', '[', '0', 'm', - // Clear screen. - '\033', '[', '2', 'J' - }); - - private final CommonVirtualMachine virtualMachine; - private final Terminal terminal; - - /////////////////////////////////////////////////////////////////// - - // Thread-local buffers for lock-free read/writes in inner loop. - private final ByteArrayFIFOQueue outputBuffer = new ByteArrayFIFOQueue(1024); - private final ByteArrayFIFOQueue inputBuffer = new ByteArrayFIFOQueue(32); - - private boolean firedResumeEvent; - @Serialized private boolean firedInitializationEvent; - @Serialized private ITextComponent runtimeError; - - /////////////////////////////////////////////////////////////////// - - public AbstractTerminalVirtualMachineRunner(final CommonVirtualMachine virtualMachine, final Terminal terminal) { - super(virtualMachine.board); - this.virtualMachine = virtualMachine; - this.terminal = terminal; - } - - /////////////////////////////////////////////////////////////////// - - protected abstract void sendTerminalUpdateToClient(final ByteBuffer output); - - /////////////////////////////////////////////////////////////////// - - public void resetTerminal() { - TERMINAL_RESET_SEQUENCE.clear(); - putTerminalOutput(TERMINAL_RESET_SEQUENCE); - } - - public void putTerminalOutput(final ByteBuffer output) { - if (output.hasRemaining()) { - terminal.putOutput(output); - - output.flip(); - sendTerminalUpdateToClient(output); - } - } - - public void scheduleResumeEvent() { - firedResumeEvent = false; - } - - @Nullable - @Override - public ITextComponent getRuntimeError() { - return runtimeError; - } - - @Override - public void tick() { - virtualMachine.rpcAdapter.tick(); - - super.tick(); - } - - /////////////////////////////////////////////////////////////////// - - @Override - protected void handleBeforeRun() { - if (!firedInitializationEvent) { - firedInitializationEvent = true; - try { - virtualMachine.vmAdapter.postLifecycleEvent(new VMInitializingEvent(virtualMachine.board.getDefaultProgramStart())); - } catch (final VMInitializationException e) { - virtualMachine.board.setRunning(false); - runtimeError = e.getErrorMessage().orElse(new TranslationTextComponent(Constants.COMPUTER_ERROR_UNKNOWN)); - return; - } - } - - if (!firedResumeEvent) { - firedResumeEvent = true; - virtualMachine.vmAdapter.postLifecycleEvent(new VMResumingRunningEvent()); - virtualMachine.vmAdapter.postLifecycleEvent(new VMResumedRunningEvent()); - } - - int value; - while ((value = terminal.readInput()) != -1) { - inputBuffer.enqueue((byte) value); - } - } - - @Override - protected void step(final int cyclesPerStep) { - while (!inputBuffer.isEmpty() && virtualMachine.uart.canPutByte()) { - virtualMachine.uart.putByte(inputBuffer.dequeueByte()); - } - virtualMachine.uart.flush(); - - int value; - while ((value = virtualMachine.uart.read()) != -1) { - outputBuffer.enqueue((byte) value); - } - - virtualMachine.rpcAdapter.step(cyclesPerStep); - } - - @Override - protected void handleAfterRun() { - final ByteBuffer output = ByteBuffer.allocate(outputBuffer.size()); - while (!outputBuffer.isEmpty()) { - output.put(outputBuffer.dequeueByte()); - } - - output.flip(); - putTerminalOutput(output); - } -} diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineItemStackHandlers.java b/src/main/java/li/cil/oc2/common/vm/AbstractVMItemStackHandlers.java similarity index 94% rename from src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineItemStackHandlers.java rename to src/main/java/li/cil/oc2/common/vm/AbstractVMItemStackHandlers.java index ea1f47e5..d7bc1e30 100644 --- a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineItemStackHandlers.java +++ b/src/main/java/li/cil/oc2/common/vm/AbstractVMItemStackHandlers.java @@ -19,7 +19,7 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -public abstract class AbstractVirtualMachineItemStackHandlers implements VirtualMachineItemStackHandlers { +public abstract class AbstractVMItemStackHandlers implements VMItemStackHandlers { public static final class GroupDefinition { public final DeviceType deviceType; public final int count; @@ -52,7 +52,7 @@ public abstract class AbstractVirtualMachineItemStackHandlers implements Virtual /////////////////////////////////////////////////////////////////// - public AbstractVirtualMachineItemStackHandlers(final GroupDefinition... groups) { + public AbstractVMItemStackHandlers(final GroupDefinition... groups) { for (final GroupDefinition group : groups) { itemHandlers.put(group.deviceType, new ItemHandler(group.count, this::getDevices, group.deviceType)); } @@ -77,7 +77,7 @@ public abstract class AbstractVirtualMachineItemStackHandlers implements Virtual return true; } - public OptionalLong getDefaultDeviceAddress(final VMDevice wrapper) { + public OptionalLong getDeviceAddressBase(final VMDevice wrapper) { long address = ITEM_DEVICE_BASE_ADDRESS; for (final Map.Entry entry : itemHandlers.entrySet()) { @@ -146,7 +146,7 @@ public abstract class AbstractVirtualMachineItemStackHandlers implements Virtual @Override protected void onContentsChanged(final int slot) { super.onContentsChanged(slot); - AbstractVirtualMachineItemStackHandlers.this.onContentsChanged(this, slot); + AbstractVMItemStackHandlers.this.onContentsChanged(this, slot); } } diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java similarity index 68% rename from src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java rename to src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java index c7b71f2a..2f71de7f 100644 --- a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java +++ b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java @@ -1,14 +1,18 @@ package li.cil.oc2.common.vm; +import li.cil.ceres.api.Serialized; import li.cil.oc2.api.bus.device.vm.FirmwareLoader; import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; import li.cil.oc2.api.bus.device.vm.event.VMPausingEvent; import li.cil.oc2.common.Constants; import li.cil.oc2.common.bus.AbstractDeviceBusController; +import li.cil.oc2.common.bus.RPCDeviceBusAdapter; import li.cil.oc2.common.serialization.NBTSerialization; import li.cil.oc2.common.util.NBTTagIds; import li.cil.oc2.common.util.NBTUtils; +import li.cil.oc2.common.vm.context.global.GlobalVMContext; import li.cil.sedna.api.memory.MemoryAccessException; +import li.cil.sedna.riscv.R5Board; import net.minecraft.nbt.CompoundNBT; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TranslationTextComponent; @@ -20,12 +24,12 @@ import org.apache.logging.log4j.Logger; import javax.annotation.Nullable; import java.util.Objects; -public abstract class AbstractVirtualMachineState implements VirtualMachineState { +public abstract class AbstractVirtualMachine implements VirtualMachine { private static final Logger LOGGER = LogManager.getLogger(); /////////////////////////////////////////////////////////////////// - private static final String VIRTUAL_MACHINE_TAG_NAME = "virtualMachine"; + private static final String STATE_TAG_NAME = "state"; private static final String RUNNER_TAG_NAME = "runner"; public static final String BUS_STATE_TAG_NAME = "busState"; @@ -36,28 +40,54 @@ public abstract class AbstractVirtualMachineState device instanceof FirmwareLoader)) { setBootError(new TranslationTextComponent(Constants.COMPUTER_ERROR_MISSING_FIRMWARE)); - setRunState(RunState.STOPPED); + setRunState(VMRunState.STOPPED); break; } @@ -199,28 +240,28 @@ public abstract class AbstractVirtualMachineState - uart.getInterrupt().set(interrupt, context.getInterruptController())); - context.getMemoryRangeAllocator().claimMemoryRange(uart); - board.setStandardOutputDevice(uart); - - vfs = new VirtIOFileSystemDevice(context.getMemoryMap(), "data", FileSystems.getLayeredFileSystem()); - context.getInterruptAllocator().claimInterrupt(VFS_INTERRUPT).ifPresent(interrupt -> - vfs.getInterrupt().set(interrupt, context.getInterruptController())); - context.getMemoryRangeAllocator().claimMemoryRange(vfs); - } -} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedEventBus.java b/src/main/java/li/cil/oc2/common/vm/ManagedEventBus.java deleted file mode 100644 index 175b785e..00000000 --- a/src/main/java/li/cil/oc2/common/vm/ManagedEventBus.java +++ /dev/null @@ -1,39 +0,0 @@ -package li.cil.oc2.common.vm; - -import com.google.common.eventbus.EventBus; -import li.cil.oc2.api.bus.device.vm.event.VMLifecycleEventBus; - -import java.util.ArrayList; - -@SuppressWarnings("UnstableApiUsage") -public final class ManagedEventBus implements VMLifecycleEventBus { - private final EventBus eventBus; - private final ArrayList 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/ManagedMemoryRangeAllocator.java b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryRangeAllocator.java deleted file mode 100644 index 5864d381..00000000 --- a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryRangeAllocator.java +++ /dev/null @@ -1,81 +0,0 @@ -package li.cil.oc2.common.vm; - -import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator; -import li.cil.sedna.api.Board; -import li.cil.sedna.api.device.MemoryMappedDevice; -import li.cil.sedna.api.memory.MemoryRange; - -import java.util.ArrayList; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.function.Function; - -public final class ManagedMemoryRangeAllocator implements MemoryRangeAllocator { - private final Board board; - private final Function defaultAddress; - private final ArrayList managedDevices = new ArrayList<>(); - private boolean isFrozen; - - /////////////////////////////////////////////////////////////////// - - public ManagedMemoryRangeAllocator(final Board board, final Function defaultAddress) { - this.board = board; - this.defaultAddress = defaultAddress; - } - - /////////////////////////////////////////////////////////////////// - - public void freeze() { - isFrozen = true; - } - - public void invalidate() { - for (final MemoryMappedDevice device : managedDevices) { - board.removeDevice(device); - } - managedDevices.clear(); - } - - @Override - public OptionalLong claimMemoryRange(final long address, final MemoryMappedDevice device) { - if (isFrozen) { - throw new IllegalStateException(); - } - - if (board.addDevice(address, device)) { - managedDevices.add(device); - return OptionalLong.of(address); - } - - return claimMemoryRange(device); - } - - @Override - public OptionalLong claimMemoryRange(final MemoryMappedDevice device) { - if (isFrozen) { - throw new IllegalStateException(); - } - - OptionalLong address = defaultAddress.apply(device); - if (address.isPresent()) { - if (board.addDevice(address.getAsLong(), device)) { - managedDevices.add(device); - return address; - } - - address = board.getMemoryMap().findFreeRange(address.orElse(0), Long.MAX_VALUE, device.getLength()); - if (address.isPresent() && board.addDevice(address.getAsLong(), device)) { - managedDevices.add(device); - return address; - } - } - - if (board.addDevice(device)) { - managedDevices.add(device); - final Optional range = board.getMemoryMap().getMemoryRange(device); - return OptionalLong.of(range.orElseThrow(AssertionError::new).address()); - } - - return OptionalLong.empty(); - } -} diff --git a/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java new file mode 100644 index 00000000..52848da2 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java @@ -0,0 +1,114 @@ +package li.cil.oc2.common.vm; + +import li.cil.ceres.api.Serialized; +import li.cil.oc2.api.bus.device.Device; +import li.cil.oc2.api.bus.device.vm.VMDevice; +import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; +import li.cil.oc2.common.vm.context.global.GlobalVMContext; +import li.cil.oc2.common.vm.context.managed.ManagedVMContext; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.OptionalLong; + +public final class VMDeviceBusAdapter { + private final HashMap deviceContexts = new HashMap<>(); + private final ArrayList incompleteLoads = new ArrayList<>(); + private BaseAddressProvider baseAddressProvider = unused -> OptionalLong.empty(); + + /////////////////////////////////////////////////////////////////// + + @Serialized @SuppressWarnings("FieldMayBeFinal") + private GlobalVMContext globalContext; + + /////////////////////////////////////////////////////////////////// + + public VMDeviceBusAdapter(final GlobalVMContext context) { + this.globalContext = context; + } + + /////////////////////////////////////////////////////////////////// + + public void setBaseAddressProvider(final BaseAddressProvider provider) { + baseAddressProvider = provider; + } + + public VMDeviceLoadResult load() { + for (int i = 0; i < incompleteLoads.size(); i++) { + final VMDevice device = incompleteLoads.get(i); + + final ManagedVMContext context = new ManagedVMContext(globalContext, globalContext, + () -> baseAddressProvider.getBaseAddress(device)); + + deviceContexts.put(device, context); + + final VMDeviceLoadResult result = device.load(context); + context.freeze(); + + if (!result.wasSuccessful()) { + for (; i >= 0; i--) { + deviceContexts.get(incompleteLoads.get(i)).invalidate(); + } + return result; + } + } + + incompleteLoads.clear(); + + globalContext.updateReservations(); + + return VMDeviceLoadResult.success(); + } + + public void unload() { + for (final VMDevice device : deviceContexts.keySet()) { + device.unload(); + } + + suspend(); + } + + public void suspend() { + deviceContexts.forEach((device, context) -> { + if (context != null) { + context.invalidate(); + } + }); + + incompleteLoads.clear(); + incompleteLoads.addAll(deviceContexts.keySet()); + } + + public void addDevices(final Collection devices) { + for (final Device device : devices) { + if (device instanceof VMDevice) { + final VMDevice vmDevice = (VMDevice) device; + + final ManagedVMContext context = deviceContexts.put(vmDevice, null); + if (context != null) { + context.invalidate(); + } + + incompleteLoads.add(vmDevice); + } + } + } + + public void removeDevices(final Collection devices) { + for (final Device device : devices) { + 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); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineItemStackHandlers.java b/src/main/java/li/cil/oc2/common/vm/VMItemStackHandlers.java similarity index 84% rename from src/main/java/li/cil/oc2/common/vm/VirtualMachineItemStackHandlers.java rename to src/main/java/li/cil/oc2/common/vm/VMItemStackHandlers.java index 28832870..86b5fb59 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineItemStackHandlers.java +++ b/src/main/java/li/cil/oc2/common/vm/VMItemStackHandlers.java @@ -5,7 +5,7 @@ import net.minecraftforge.items.IItemHandler; import java.util.Optional; -public interface VirtualMachineItemStackHandlers { +public interface VMItemStackHandlers { Optional getItemHandler(DeviceType deviceType); boolean isEmpty(); diff --git a/src/main/java/li/cil/oc2/common/vm/VMRunState.java b/src/main/java/li/cil/oc2/common/vm/VMRunState.java new file mode 100644 index 00000000..b98ec1ac --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/VMRunState.java @@ -0,0 +1,7 @@ +package li.cil.oc2.common.vm; + +public enum VMRunState { + STOPPED, + LOADING_DEVICES, + RUNNING, +} diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineRunner.java b/src/main/java/li/cil/oc2/common/vm/VMRunner.java similarity index 64% rename from src/main/java/li/cil/oc2/common/vm/VirtualMachineRunner.java rename to src/main/java/li/cil/oc2/common/vm/VMRunner.java index 07e0a666..27f6632b 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineRunner.java +++ b/src/main/java/li/cil/oc2/common/vm/VMRunner.java @@ -1,8 +1,16 @@ package li.cil.oc2.common.vm; 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 li.cil.oc2.common.bus.RPCDeviceBusAdapter; +import li.cil.oc2.common.vm.context.global.GlobalVMContext; import li.cil.sedna.riscv.R5Board; import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; import javax.annotation.Nullable; import java.util.concurrent.ExecutionException; @@ -11,7 +19,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; -public class VirtualMachineRunner implements Runnable { +public class VMRunner implements Runnable { private static final int TICKS_PER_SECOND = 20; private static final int TIMESLICE_IN_MS = 1000 / TICKS_PER_SECOND; @@ -25,28 +33,42 @@ public class VirtualMachineRunner implements Runnable { /////////////////////////////////////////////////////////////////// private final R5Board board; + private final GlobalVMContext context; + private final RPCDeviceBusAdapter rpcAdapter; private final AtomicInteger timeQuotaInMillis = new AtomicInteger(); private Future lastSchedule; /////////////////////////////////////////////////////////////////// + private boolean firedResumeEvent; + @Serialized private boolean firedInitializationEvent; + @Serialized private ITextComponent runtimeError; + @Serialized private long cycleLimit; @Serialized private long cycles; /////////////////////////////////////////////////////////////////// - public VirtualMachineRunner(final R5Board board) { - this.board = board; + public VMRunner(final AbstractVirtualMachine virtualMachine) { + this.board = virtualMachine.state.board; + context = virtualMachine.state.context; + rpcAdapter = virtualMachine.state.rpcAdapter; } /////////////////////////////////////////////////////////////////// + public void scheduleResumeEvent() { + firedResumeEvent = false; + } + @Nullable public ITextComponent getRuntimeError() { - return null; + return runtimeError; } public void tick() { + rpcAdapter.tick(); + cycleLimit += getCyclesPerTick(); final int timeQuota = timeQuotaInMillis.updateAndGet(x -> Math.min(x + TIMESLICE_IN_MS, TIMESLICE_IN_MS)); @@ -103,9 +125,26 @@ public class VirtualMachineRunner implements Runnable { /////////////////////////////////////////////////////////////////// protected void handleBeforeRun() { + if (!firedInitializationEvent) { + firedInitializationEvent = true; + try { + context.postEvent(new VMInitializingEvent(board.getDefaultProgramStart())); + } catch (final VMInitializationException e) { + board.setRunning(false); + runtimeError = e.getErrorMessage().orElse(new TranslationTextComponent(Constants.COMPUTER_ERROR_UNKNOWN)); + return; + } + } + + if (!firedResumeEvent) { + firedResumeEvent = true; + context.postEvent(new VMResumingRunningEvent()); + context.postEvent(new VMResumedRunningEvent()); + } } protected void step(final int cyclesPerStep) { + rpcAdapter.step(cyclesPerStep); } protected void handleAfterRun() { @@ -114,6 +153,6 @@ public class VirtualMachineRunner implements Runnable { /////////////////////////////////////////////////////////////////// private static int getCyclesPerTick() { - return VirtualMachine.CPU_FREQUENCY / TICKS_PER_SECOND; + return Constants.CPU_FREQUENCY / TICKS_PER_SECOND; } } diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java index 37091da5..ae2f2c32 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java @@ -1,74 +1,35 @@ package li.cil.oc2.common.vm; -import li.cil.ceres.api.Serialized; -import li.cil.oc2.api.bus.DeviceBusController; -import li.cil.oc2.api.bus.device.vm.InterruptAllocator; -import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator; -import li.cil.oc2.api.bus.device.vm.VMContext; -import li.cil.oc2.common.bus.RPCAdapter; -import li.cil.sedna.api.device.InterruptController; -import li.cil.sedna.device.rtc.GoldfishRTC; -import li.cil.sedna.device.rtc.SystemTimeRealTimeCounter; -import li.cil.sedna.device.virtio.VirtIOConsoleDevice; -import li.cil.sedna.riscv.R5Board; +import li.cil.oc2.common.bus.AbstractDeviceBusController; +import net.minecraft.util.text.ITextComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; -public class VirtualMachine { - public static final int CPU_FREQUENCY = 25_000_000; +import javax.annotation.Nullable; - public static final int RTC_HOST_INTERRUPT = 0x1; - public static final int RTC_MINECRAFT_INTERRUPT = 0x2; - public static final int RPC_INTERRUPT = 0x3; +public interface VirtualMachine { + AbstractDeviceBusController.BusState getBusState(); - /////////////////////////////////////////////////////////////////// + @OnlyIn(Dist.CLIENT) + void setBusStateClient(AbstractDeviceBusController.BusState value); - public final MinecraftRealTimeCounter rtcMinecraft = new MinecraftRealTimeCounter(); + VMRunState getRunState(); - /////////////////////////////////////////////////////////////////// + @OnlyIn(Dist.CLIENT) + void setRunStateClient(VMRunState value); - @Serialized public R5Board board; - @Serialized public VirtualMachineDeviceBusAdapter vmAdapter; - @Serialized public VirtIOConsoleDevice deviceBusSerialDevice; - @Serialized public RPCAdapter rpcAdapter; + @Nullable + ITextComponent getBootError(); - /////////////////////////////////////////////////////////////////// + @OnlyIn(Dist.CLIENT) + void setBootErrorClient(ITextComponent value); - public VirtualMachine(final DeviceBusController busController) { - board = new R5Board(); + boolean isRunning(); - board.getCpu().setFrequency(CPU_FREQUENCY); + void start(); - vmAdapter = new VirtualMachineDeviceBusAdapter(board); - final VMContext context = vmAdapter.getGlobalContext(); + void stop(); - final MemoryRangeAllocator memoryRangeAllocator = context.getMemoryRangeAllocator(); - final InterruptAllocator interruptAllocator = context.getInterruptAllocator(); - final InterruptController interruptController = context.getInterruptController(); + void joinWorkerThread(); - final GoldfishRTC rtcHost = new GoldfishRTC(SystemTimeRealTimeCounter.get()); - interruptAllocator.claimInterrupt(RTC_HOST_INTERRUPT).ifPresent(interrupt -> - rtcHost.getInterrupt().set(interrupt, interruptController)); - memoryRangeAllocator.claimMemoryRange(rtcHost); - - final GoldfishRTC rtcMinecraft = new GoldfishRTC(this.rtcMinecraft); - interruptAllocator.claimInterrupt(RTC_MINECRAFT_INTERRUPT).ifPresent(interrupt -> - rtcMinecraft.getInterrupt().set(interrupt, interruptController)); - memoryRangeAllocator.claimMemoryRange(rtcMinecraft); - - deviceBusSerialDevice = new VirtIOConsoleDevice(board.getMemoryMap()); - interruptAllocator.claimInterrupt(RPC_INTERRUPT).ifPresent(interrupt -> - deviceBusSerialDevice.getInterrupt().set(interrupt, interruptController)); - memoryRangeAllocator.claimMemoryRange(deviceBusSerialDevice); - - rpcAdapter = new RPCAdapter(busController, deviceBusSerialDevice); - - board.setBootArguments("root=/dev/vda rw"); - } - - /////////////////////////////////////////////////////////////////// - - public void reset() { - board.reset(); - rpcAdapter.reset(); - vmAdapter.unload(); - } } diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java deleted file mode 100644 index a3843f6a..00000000 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java +++ /dev/null @@ -1,163 +0,0 @@ -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(this::handleEventBusException); - - private final ManagedVMContext globalContext; - private final BitSet claimedInterrupts = new BitSet(); - private final HashMap deviceContexts = new HashMap<>(); - private final ArrayList incompleteLoads = new ArrayList<>(); - - private DefaultAddressProvider defaultAddressProvider = unused -> OptionalLong.empty(); - private VMInitializationException initializationException; - - /////////////////////////////////////////////////////////////////// - - // This is a superset of allocatedInterrupts. We use this so that after loading we - // avoid potentially new devices (due external code changes, etc.) to grab interrupts - // previously used by other devices. Only claiming interrupts explicitly will allow - // grabbing reserved interrupts. - @Serialized @SuppressWarnings("FieldMayBeFinal") private BitSet reservedInterrupts = new BitSet(); - - /////////////////////////////////////////////////////////////////// - - public VirtualMachineDeviceBusAdapter(final Board board) { - this.board = board; - this.globalContext = new ManagedVMContext(board, claimedInterrupts, reservedInterrupts, eventBus); - this.claimedInterrupts.set(0); - } - - /////////////////////////////////////////////////////////////////// - - public VMContext getGlobalContext() { - return globalContext; - } - - public void setDefaultAddressProvider(final DefaultAddressProvider provider) { - defaultAddressProvider = provider; - } - - public VMDeviceLoadResult load() { - for (int i = 0; i < incompleteLoads.size(); i++) { - final VMDevice device = incompleteLoads.get(i); - - final ManagedVMContext context = new ManagedVMContext( - board, claimedInterrupts, reservedInterrupts, eventBus, - (memoryMappedDevice) -> defaultAddressProvider.getDefaultAddress(device)); - - deviceContexts.put(device, context); - - final VMDeviceLoadResult result = device.load(context); - context.freeze(); - - if (!result.wasSuccessful()) { - for (; i >= 0; i--) { - deviceContexts.get(incompleteLoads.get(i)).invalidate(); - } - return result; - } - } - - incompleteLoads.clear(); - - reservedInterrupts.clear(); - reservedInterrupts.or(claimedInterrupts); - - return VMDeviceLoadResult.success(); - } - - public void unload() { - for (final VMDevice device : deviceContexts.keySet()) { - device.unload(); - } - - suspend(); - } - - public void suspend() { - deviceContexts.forEach((device, context) -> { - if (context != null) { - context.invalidate(); - } - }); - - incompleteLoads.clear(); - incompleteLoads.addAll(deviceContexts.keySet()); - } - - 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) { - for (final Device device : devices) { - if (device instanceof VMDevice) { - final VMDevice vmDevice = (VMDevice) device; - - final ManagedVMContext context = deviceContexts.put(vmDevice, null); - if (context != null) { - context.invalidate(); - } - - incompleteLoads.add(vmDevice); - } - } - } - - public void removeDevices(final Collection devices) { - for (final Device device : devices) { - 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); - } - } - } - - /////////////////////////////////////////////////////////////////// - - 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/VirtualMachineState.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachineState.java deleted file mode 100644 index 6e16a345..00000000 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineState.java +++ /dev/null @@ -1,38 +0,0 @@ -package li.cil.oc2.common.vm; - -import li.cil.oc2.common.bus.AbstractDeviceBusController; -import net.minecraft.util.text.ITextComponent; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; - -public interface VirtualMachineState { - AbstractDeviceBusController.BusState getBusState(); - - @OnlyIn(Dist.CLIENT) - void setBusStateClient(AbstractDeviceBusController.BusState value); - - RunState getRunState(); - - @OnlyIn(Dist.CLIENT) - void setRunStateClient(RunState value); - - @Nullable - ITextComponent getBootError(); - - @OnlyIn(Dist.CLIENT) - void setBootErrorClient(ITextComponent value); - - boolean isRunning(); - - void start(); - - void stop(); - - enum RunState { - STOPPED, - LOADING_DEVICES, - RUNNING, - } -} diff --git a/src/main/java/li/cil/oc2/common/vm/context/EventManager.java b/src/main/java/li/cil/oc2/common/vm/context/EventManager.java new file mode 100644 index 00000000..78ac53e3 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/EventManager.java @@ -0,0 +1,5 @@ +package li.cil.oc2.common.vm.context; + +public interface EventManager { + void unregister(Object subscriber); +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/InterruptManager.java b/src/main/java/li/cil/oc2/common/vm/context/InterruptManager.java new file mode 100644 index 00000000..f2ec1156 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/InterruptManager.java @@ -0,0 +1,9 @@ +package li.cil.oc2.common.vm.context; + +import java.util.BitSet; + +public interface InterruptManager { + int getInterruptCount(); + + void releaseInterrupts(BitSet interrupts); +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/InterruptValidator.java b/src/main/java/li/cil/oc2/common/vm/context/InterruptValidator.java new file mode 100644 index 00000000..f4dbc889 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/InterruptValidator.java @@ -0,0 +1,7 @@ +package li.cil.oc2.common.vm.context; + +public interface InterruptValidator { + boolean isMaskValid(int mask); + + int getMaskedInterrupts(int interrupts); +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/MemoryRangeManager.java b/src/main/java/li/cil/oc2/common/vm/context/MemoryRangeManager.java new file mode 100644 index 00000000..6946cc01 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/MemoryRangeManager.java @@ -0,0 +1,11 @@ +package li.cil.oc2.common.vm.context; + +import li.cil.sedna.api.device.MemoryMappedDevice; + +import java.util.OptionalLong; + +public interface MemoryRangeManager { + OptionalLong findMemoryRange(MemoryMappedDevice device, long start); + + void releaseMemoryRange(MemoryMappedDevice device); +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/VMContextManagerCollection.java b/src/main/java/li/cil/oc2/common/vm/context/VMContextManagerCollection.java new file mode 100644 index 00000000..d1b49844 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/VMContextManagerCollection.java @@ -0,0 +1,9 @@ +package li.cil.oc2.common.vm.context; + +public interface VMContextManagerCollection { + InterruptManager getInterruptManager(); + + MemoryRangeManager getMemoryRangeManager(); + + EventManager getEventManager(); +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/GlobalEventBus.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalEventBus.java new file mode 100644 index 00000000..d2c2c812 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalEventBus.java @@ -0,0 +1,53 @@ +package li.cil.oc2.common.vm.context.global; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.SubscriberExceptionContext; +import li.cil.oc2.api.bus.device.vm.VMLifecycleEventBus; +import li.cil.oc2.api.bus.device.vm.event.VMInitializationException; +import li.cil.oc2.common.vm.context.EventManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@SuppressWarnings("UnstableApiUsage") +final class GlobalEventBus implements VMLifecycleEventBus, EventManager { + private static final Logger LOGGER = LogManager.getLogger(); + + /////////////////////////////////////////////////////////////////// + + private final EventBus eventBus = new EventBus(this::handleEventBusException); + private VMInitializationException initializationException; + + /////////////////////////////////////////////////////////////////// + + public void post(final Object event) { + initializationException = null; + + eventBus.post(event); + + final VMInitializationException exception = initializationException; + initializationException = null; + if (exception != null) { + throw exception; + } + } + + @Override + public void register(final Object subscriber) { + eventBus.register(subscriber); + } + + @Override + public void unregister(final Object subscriber) { + eventBus.unregister(subscriber); + } + + /////////////////////////////////////////////////////////////////// + + 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/ManagedInterruptAllocator.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalInterruptAllocator.java similarity index 50% rename from src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java rename to src/main/java/li/cil/oc2/common/vm/context/global/GlobalInterruptAllocator.java index 628d84a7..ec1bc900 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java +++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalInterruptAllocator.java @@ -1,70 +1,51 @@ -package li.cil.oc2.common.vm; +package li.cil.oc2.common.vm.context.global; import li.cil.oc2.api.bus.device.vm.InterruptAllocator; +import li.cil.oc2.common.vm.context.InterruptManager; +import li.cil.oc2.common.vm.context.InterruptValidator; import java.util.BitSet; import java.util.OptionalInt; -public final class ManagedInterruptAllocator implements InterruptAllocator { - private final BitSet claimedInterrupts; +final class GlobalInterruptAllocator implements InterruptAllocator, InterruptValidator, InterruptManager { + private final BitSet claimedInterrupts = new BitSet(); private final BitSet reservedInterrupts; - private final BitSet managedInterrupts; private final int interruptCount; - private boolean isFrozen; private int managedMask; /////////////////////////////////////////////////////////////////// - public ManagedInterruptAllocator(final BitSet claimedInterrupts, final BitSet reservedInterrupts, final int interruptCount) { - this.claimedInterrupts = claimedInterrupts; + public GlobalInterruptAllocator(final int interruptCount, final BitSet reservedInterrupts) { this.reservedInterrupts = reservedInterrupts; - this.managedInterrupts = new BitSet(interruptCount); this.interruptCount = interruptCount; + + // Interrupt zero appears to be evil, so block it. + this.claimedInterrupts.set(0); } /////////////////////////////////////////////////////////////////// - public void freeze() { - isFrozen = true; - } - - public void invalidate() { - claimedInterrupts.andNot(managedInterrupts); - managedInterrupts.clear(); - managedMask = 0; - } - - public boolean isMaskValid(final int mask) { - return (mask & ~managedMask) == 0; + public BitSet getClaimedInterrupts() { + return claimedInterrupts; } @Override - public OptionalInt claimInterrupt(final int interrupt) { - if (isFrozen) { - throw new IllegalStateException(); - } - + public boolean claimInterrupt(final int interrupt) { if (interrupt < 1 || interrupt >= interruptCount) { throw new IllegalArgumentException(); } if (claimedInterrupts.get(interrupt)) { - return claimInterrupt(); - } else { - claimedInterrupts.set(interrupt); - reservedInterrupts.set(interrupt); - managedInterrupts.set(interrupt); - managedMask |= (1 << interrupt); - return OptionalInt.of(interrupt); + return false; } + + claimedInterrupts.set(interrupt); + managedMask |= (1 << interrupt); + return true; } @Override public OptionalInt claimInterrupt() { - if (isFrozen) { - throw new IllegalStateException(); - } - final BitSet allClaimedInterrupts = new BitSet(); allClaimedInterrupts.or(claimedInterrupts); allClaimedInterrupts.or(reservedInterrupts); @@ -75,10 +56,34 @@ public final class ManagedInterruptAllocator implements InterruptAllocator { } claimedInterrupts.set(interrupt); - reservedInterrupts.set(interrupt); - managedInterrupts.set(interrupt); managedMask |= (1 << interrupt); return OptionalInt.of(interrupt); } + + @Override + public boolean isMaskValid(final int mask) { + return (mask & ~managedMask) == 0; + } + + @Override + public int getMaskedInterrupts(final int interrupts) { + return interrupts & managedMask; + } + + @Override + public int getInterruptCount() { + return interruptCount; + } + + @Override + public void releaseInterrupts(final BitSet interrupts) { + claimedInterrupts.andNot(interrupts); + + int interrupt = interrupts.nextSetBit(0); + while (interrupt >= 0) { + managedMask &= ~(1 << interrupt); + interrupt = interrupts.nextSetBit(interrupt + 1); + } + } } diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/GlobalInterruptController.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalInterruptController.java new file mode 100644 index 00000000..3eceef90 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalInterruptController.java @@ -0,0 +1,55 @@ +package li.cil.oc2.common.vm.context.global; + +import li.cil.oc2.common.vm.context.InterruptValidator; +import li.cil.sedna.api.device.InterruptController; + +final class GlobalInterruptController implements InterruptController { + private final InterruptController parent; + private final InterruptValidator validator; + private int raisedInterruptMask; + + /////////////////////////////////////////////////////////////////// + + public GlobalInterruptController(final InterruptController parent, final InterruptValidator validator) { + this.parent = parent; + this.validator = validator; + raisedInterruptMask = validator.getMaskedInterrupts(parent.getRaisedInterrupts()); + } + + /////////////////////////////////////////////////////////////////// + + public void invalidate() { + parent.lowerInterrupts(raisedInterruptMask); + raisedInterruptMask = 0; + } + + @Override + public Object getIdentity() { + return parent.getIdentity(); + } + + @Override + public void raiseInterrupts(final int mask) { + if (validator.isMaskValid(mask)) { + parent.raiseInterrupts(mask); + raisedInterruptMask |= mask; + } else { + throw new IllegalArgumentException("Trying to raise interrupt not allocated by this context."); + } + } + + @Override + public void lowerInterrupts(final int mask) { + if (validator.isMaskValid(mask)) { + parent.lowerInterrupts(mask); + raisedInterruptMask &= ~mask; + } else { + throw new IllegalArgumentException("Trying to lower interrupt not allocated by this context."); + } + } + + @Override + public int getRaisedInterrupts() { + return raisedInterruptMask; + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryAllocator.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryAllocator.java new file mode 100644 index 00000000..a3a0352f --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryAllocator.java @@ -0,0 +1,32 @@ +package li.cil.oc2.common.vm.context.global; + +import li.cil.oc2.api.bus.device.vm.MemoryAllocator; +import li.cil.oc2.common.vm.Allocator; + +import java.util.ArrayList; +import java.util.UUID; + +final class GlobalMemoryAllocator implements MemoryAllocator { + private final ArrayList claimedMemory = new ArrayList<>(); + + /////////////////////////////////////////////////////////////////// + + public void invalidate() { + for (final UUID handle : claimedMemory) { + Allocator.freeMemory(handle); + } + + claimedMemory.clear(); + } + + @Override + public boolean claimMemory(final int size) { + final UUID handle = Allocator.createHandle(); + if (!Allocator.claimMemory(handle, size)) { + return false; + } + + claimedMemory.add(handle); + return true; + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryMap.java similarity index 74% rename from src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java rename to src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryMap.java index baad095f..5c7614f0 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java +++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryMap.java @@ -1,30 +1,25 @@ -package li.cil.oc2.common.vm; +package li.cil.oc2.common.vm.context.global; import li.cil.sedna.api.device.MemoryMappedDevice; +import li.cil.sedna.api.memory.MappedMemoryRange; import li.cil.sedna.api.memory.MemoryAccessException; import li.cil.sedna.api.memory.MemoryMap; import li.cil.sedna.api.memory.MemoryRange; import javax.annotation.Nullable; import java.util.Optional; -import java.util.OptionalLong; -final class ManagedMemoryMap implements MemoryMap { +final class GlobalMemoryMap implements MemoryMap { private final MemoryMap memoryMap; /////////////////////////////////////////////////////////////////// - ManagedMemoryMap(final MemoryMap memoryMap) { + GlobalMemoryMap(final MemoryMap memoryMap) { this.memoryMap = memoryMap; } /////////////////////////////////////////////////////////////////// - @Override - public OptionalLong findFreeRange(final long start, final long end, final int size) { - return memoryMap.findFreeRange(start, end, size); - } - @Override public boolean addDevice(final long address, final MemoryMappedDevice device) { throw new UnsupportedOperationException(); @@ -36,13 +31,18 @@ final class ManagedMemoryMap implements MemoryMap { } @Override - public Optional getMemoryRange(final MemoryMappedDevice device) { + public Optional getMemoryRange(final MemoryMappedDevice device) { return memoryMap.getMemoryRange(device); } + @Override + public Optional getMemoryRange(final MemoryRange memoryRange) { + return memoryMap.getMemoryRange(memoryRange); + } + @Nullable @Override - public MemoryRange getMemoryRange(final long address) { + public MappedMemoryRange getMemoryRange(final long address) { return memoryMap.getMemoryRange(address); } diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryRangeAllocator.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryRangeAllocator.java new file mode 100644 index 00000000..47dec45e --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalMemoryRangeAllocator.java @@ -0,0 +1,86 @@ +package li.cil.oc2.common.vm.context.global; + +import it.unimi.dsi.fastutil.objects.Object2LongArrayMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator; +import li.cil.oc2.common.vm.context.MemoryRangeManager; +import li.cil.sedna.api.Board; +import li.cil.sedna.api.device.MemoryMappedDevice; +import li.cil.sedna.api.memory.MemoryRange; +import li.cil.sedna.api.memory.MemoryRangeAllocationStrategy; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +import java.util.OptionalLong; + +final class GlobalMemoryRangeAllocator implements MemoryRangeAllocator, MemoryRangeManager { + private final Board board; + private final ArrayList reservedMemoryRanges; + private final Object2LongArrayMap claimedMemoryRanges = new Object2LongArrayMap<>(); + + /////////////////////////////////////////////////////////////////// + + public GlobalMemoryRangeAllocator(final Board board, final ArrayList reservedMemoryRanges) { + this.board = board; + this.reservedMemoryRanges = reservedMemoryRanges; + } + + /////////////////////////////////////////////////////////////////// + + public Collection getClaimedMemoryRanges() { + final ArrayList result = new ArrayList<>(); + for (final Object2LongMap.Entry entry : claimedMemoryRanges.object2LongEntrySet()) { + final MemoryMappedDevice device = entry.getKey(); + final long address = entry.getLongValue(); + result.add(MemoryRange.at(address, device.getLength())); + } + return result; + } + + public void invalidate() { + for (final MemoryMappedDevice device : claimedMemoryRanges.keySet()) { + board.removeDevice(device); + } + claimedMemoryRanges.clear(); + } + + @Override + public boolean claimMemoryRange(final long address, final MemoryMappedDevice device) { + if (board.addDevice(address, device)) { + claimedMemoryRanges.put(device, address); + return true; + } + + return false; + } + + @Override + public OptionalLong claimMemoryRange(final MemoryMappedDevice device) { + final OptionalLong address = board.addDevice(device); + if (address.isPresent()) { + claimedMemoryRanges.put(device, address.getAsLong()); + return address; + } + + return OptionalLong.empty(); + } + + @Override + public OptionalLong findMemoryRange(final MemoryMappedDevice device, final long start) { + return board.getAllocationStrategy().findMemoryRange(device, range -> { + for (final MemoryRange reservedRange : reservedMemoryRanges) { + if (reservedRange.intersects(range)) { + return Optional.of(reservedRange); + } + } + return MemoryRangeAllocationStrategy.getMemoryMapIntersectionProvider(board.getMemoryMap()).apply(range); + }, start); + } + + @Override + public void releaseMemoryRange(final MemoryMappedDevice device) { + board.removeDevice(device); + claimedMemoryRanges.removeLong(device); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java new file mode 100644 index 00000000..88225a0c --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java @@ -0,0 +1,118 @@ +package li.cil.oc2.common.vm.context.global; + +import li.cil.ceres.api.Serialized; +import li.cil.oc2.api.bus.device.vm.*; +import li.cil.oc2.common.vm.context.EventManager; +import li.cil.oc2.common.vm.context.InterruptManager; +import li.cil.oc2.common.vm.context.MemoryRangeManager; +import li.cil.oc2.common.vm.context.VMContextManagerCollection; +import li.cil.sedna.api.Board; +import li.cil.sedna.api.device.InterruptController; +import li.cil.sedna.api.memory.MemoryMap; + +import java.util.BitSet; + +public final class GlobalVMContext implements VMContext, VMContextManagerCollection { + private final GlobalMemoryMap memoryMap; + private final GlobalMemoryRangeAllocator memoryRangeAllocator; + private final GlobalInterruptAllocator interruptAllocator; + private final GlobalInterruptController interruptController; + private final GlobalMemoryAllocator memoryAllocator; + private final GlobalEventBus eventBus; + private final Runnable joinWorkerThread; + + /////////////////////////////////////////////////////////////////// + + // We track currently claimed interrupts and memory ranges so that after loading we + // avoid potentially new devices (due external code changes, etc.) to grab interrupts + // or memory ranges previously used by other devices. Only claiming interrupts and + // memory ranges explicitly will allow grabbing reserved ones. + + @Serialized @SuppressWarnings("FieldMayBeFinal") + private BitSet reservedInterrupts = new BitSet(); + + @Serialized @SuppressWarnings("FieldMayBeFinal") + private MemoryRangeList reservedMemoryRanges = new MemoryRangeList(); + + /////////////////////////////////////////////////////////////////// + + public GlobalVMContext(final Board board, final Runnable joinWorkerThread) { + this.memoryMap = new GlobalMemoryMap(board.getMemoryMap()); + this.memoryRangeAllocator = new GlobalMemoryRangeAllocator(board, reservedMemoryRanges); + this.interruptAllocator = new GlobalInterruptAllocator(board.getInterruptCount(), reservedInterrupts); + this.interruptController = new GlobalInterruptController(board.getInterruptController(), interruptAllocator); + this.memoryAllocator = new GlobalMemoryAllocator(); + this.eventBus = new GlobalEventBus(); + this.joinWorkerThread = joinWorkerThread; + } + + /////////////////////////////////////////////////////////////////// + + public void updateReservations() { + reservedInterrupts.clear(); + reservedInterrupts.or(interruptAllocator.getClaimedInterrupts()); + + reservedMemoryRanges.clear(); + reservedMemoryRanges.addAll(memoryRangeAllocator.getClaimedMemoryRanges()); + } + + public void postEvent(final Object event) { + eventBus.post(event); + } + + public void invalidate() { + memoryRangeAllocator.invalidate(); + interruptController.invalidate(); + memoryAllocator.invalidate(); + } + + @Override + public MemoryMap getMemoryMap() { + return memoryMap; + } + + @Override + public InterruptController getInterruptController() { + return interruptController; + } + + @Override + public MemoryRangeAllocator getMemoryRangeAllocator() { + return memoryRangeAllocator; + } + + @Override + public InterruptAllocator getInterruptAllocator() { + return interruptAllocator; + } + + @Override + public MemoryAllocator getMemoryAllocator() { + return memoryAllocator; + } + + @Override + public VMLifecycleEventBus getEventBus() { + return eventBus; + } + + @Override + public void joinWorkerThread() { + joinWorkerThread.run(); + } + + @Override + public InterruptManager getInterruptManager() { + return interruptAllocator; + } + + @Override + public MemoryRangeManager getMemoryRangeManager() { + return memoryRangeAllocator; + } + + @Override + public EventManager getEventManager() { + return eventBus; + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/MemoryRangeList.java b/src/main/java/li/cil/oc2/common/vm/context/global/MemoryRangeList.java new file mode 100644 index 00000000..7778379c --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/global/MemoryRangeList.java @@ -0,0 +1,8 @@ +package li.cil.oc2.common.vm.context.global; + +import li.cil.sedna.api.memory.MemoryRange; + +import java.util.ArrayList; + +public final class MemoryRangeList extends ArrayList { +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/package-info.java b/src/main/java/li/cil/oc2/common/vm/context/global/package-info.java new file mode 100644 index 00000000..bc8ea52c --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/global/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.common.vm.context.global; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedEventBus.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedEventBus.java new file mode 100644 index 00000000..3363acc5 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedEventBus.java @@ -0,0 +1,43 @@ +package li.cil.oc2.common.vm.context.managed; + +import li.cil.oc2.api.bus.device.vm.VMLifecycleEventBus; +import li.cil.oc2.common.vm.context.EventManager; + +import java.util.ArrayList; + +final class ManagedEventBus implements VMLifecycleEventBus { + private final VMLifecycleEventBus parent; + private final EventManager manager; + private final ArrayList subscribers = new ArrayList<>(); + private boolean isFrozen; + + /////////////////////////////////////////////////////////////////// + + public ManagedEventBus(final VMLifecycleEventBus parent, final EventManager manager) { + this.parent = parent; + this.manager = manager; + } + + /////////////////////////////////////////////////////////////////// + + public void freeze() { + isFrozen = true; + } + + public void invalidate() { + for (final Object subscriber : subscribers) { + manager.unregister(subscriber); + } + subscribers.clear(); + } + + @Override + public void register(final Object subscriber) { + if (isFrozen) { + throw new IllegalStateException(); + } + + parent.register(subscriber); + subscribers.add(subscriber); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedInterruptAllocator.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedInterruptAllocator.java new file mode 100644 index 00000000..0b69554e --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedInterruptAllocator.java @@ -0,0 +1,81 @@ +package li.cil.oc2.common.vm.context.managed; + +import li.cil.oc2.api.bus.device.vm.InterruptAllocator; +import li.cil.oc2.common.vm.context.InterruptManager; +import li.cil.oc2.common.vm.context.InterruptValidator; + +import java.util.BitSet; +import java.util.OptionalInt; + +final class ManagedInterruptAllocator implements InterruptAllocator, InterruptValidator { + private final InterruptAllocator parent; + private final InterruptManager interruptManager; + private final BitSet managedInterrupts; + private final int interruptCount; + private boolean isFrozen; + private int managedMask; + + /////////////////////////////////////////////////////////////////// + + public ManagedInterruptAllocator(final InterruptAllocator parent, final InterruptManager interruptManager) { + this.parent = parent; + this.interruptManager = interruptManager; + this.interruptCount = interruptManager.getInterruptCount(); + this.managedInterrupts = new BitSet(interruptCount); + } + + /////////////////////////////////////////////////////////////////// + + public void freeze() { + isFrozen = true; + } + + public void invalidate() { + interruptManager.releaseInterrupts(managedInterrupts); + managedInterrupts.clear(); + managedMask = 0; + } + + @Override + public boolean isMaskValid(final int mask) { + return (mask & ~managedMask) == 0; + } + + @Override + public int getMaskedInterrupts(final int interrupts) { + return interrupts & managedMask; + } + + @Override + public boolean claimInterrupt(final int interrupt) { + if (isFrozen) { + throw new IllegalStateException(); + } + + if (interrupt < 1 || interrupt >= interruptCount) { + throw new IllegalArgumentException(); + } + + if (!parent.claimInterrupt(interrupt)) { + return false; + } + + managedInterrupts.set(interrupt); + managedMask |= (1 << interrupt); + return true; + } + + @Override + public OptionalInt claimInterrupt() { + if (isFrozen) { + throw new IllegalStateException(); + } + + final OptionalInt result = parent.claimInterrupt(); + result.ifPresent(interrupt -> { + managedInterrupts.set(interrupt); + managedMask |= (1 << interrupt); + }); + return result; + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedInterruptController.java similarity index 57% rename from src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java rename to src/main/java/li/cil/oc2/common/vm/context/managed/ManagedInterruptController.java index 2845a3fa..52afc62c 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedInterruptController.java @@ -1,31 +1,33 @@ -package li.cil.oc2.common.vm; +package li.cil.oc2.common.vm.context.managed; +import li.cil.oc2.common.vm.context.InterruptValidator; import li.cil.sedna.api.device.InterruptController; public final class ManagedInterruptController implements InterruptController { - private final InterruptController interruptController; - private final ManagedInterruptAllocator allocator; - private int raisedInterrupts = 0; + private final InterruptController parent; + private final InterruptValidator validator; + private int raisedInterruptMask; private boolean isValid = true; /////////////////////////////////////////////////////////////////// - public ManagedInterruptController(final InterruptController interruptController, final ManagedInterruptAllocator allocator) { - this.interruptController = interruptController; - this.allocator = allocator; + public ManagedInterruptController(final InterruptController parent, final InterruptValidator validator) { + this.parent = parent; + this.validator = validator; + raisedInterruptMask = validator.getMaskedInterrupts(parent.getRaisedInterrupts()); } /////////////////////////////////////////////////////////////////// public void invalidate() { isValid = false; - interruptController.lowerInterrupts(raisedInterrupts); - raisedInterrupts = 0; + parent.lowerInterrupts(raisedInterruptMask); + raisedInterruptMask = 0; } @Override public Object getIdentity() { - return interruptController.getIdentity(); + return parent.getIdentity(); } @Override @@ -34,9 +36,9 @@ public final class ManagedInterruptController implements InterruptController { throw new IllegalStateException(); } - if (allocator.isMaskValid(mask)) { - interruptController.raiseInterrupts(mask); - raisedInterrupts |= mask; + if (validator.isMaskValid(mask)) { + parent.raiseInterrupts(mask); + raisedInterruptMask |= mask; } else { throw new IllegalArgumentException("Trying to raise interrupt not allocated by this context."); } @@ -48,9 +50,9 @@ public final class ManagedInterruptController implements InterruptController { throw new IllegalStateException(); } - if (allocator.isMaskValid(mask)) { - interruptController.lowerInterrupts(mask); - raisedInterrupts &= ~mask; + if (validator.isMaskValid(mask)) { + parent.lowerInterrupts(mask); + raisedInterruptMask &= ~mask; } else { throw new IllegalArgumentException("Trying to lower interrupt not allocated by this context."); } @@ -58,6 +60,6 @@ public final class ManagedInterruptController implements InterruptController { @Override public int getRaisedInterrupts() { - return raisedInterrupts; + return raisedInterruptMask; } } diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryAllocator.java similarity index 85% rename from src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java rename to src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryAllocator.java index bbb1c221..4f0fa2c5 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryAllocator.java @@ -1,11 +1,12 @@ -package li.cil.oc2.common.vm; +package li.cil.oc2.common.vm.context.managed; import li.cil.oc2.api.bus.device.vm.MemoryAllocator; +import li.cil.oc2.common.vm.Allocator; import java.util.ArrayList; import java.util.UUID; -public final class ManagedMemoryAllocator implements MemoryAllocator { +final class ManagedMemoryAllocator implements MemoryAllocator { private final ArrayList claimedMemory = new ArrayList<>(); private boolean isFrozen; diff --git a/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryMap.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryMap.java new file mode 100644 index 00000000..0f76635a --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryMap.java @@ -0,0 +1,80 @@ +package li.cil.oc2.common.vm.context.managed; + +import li.cil.sedna.api.device.MemoryMappedDevice; +import li.cil.sedna.api.memory.MappedMemoryRange; +import li.cil.sedna.api.memory.MemoryAccessException; +import li.cil.sedna.api.memory.MemoryMap; +import li.cil.sedna.api.memory.MemoryRange; + +import javax.annotation.Nullable; +import java.util.Optional; + +final class ManagedMemoryMap implements MemoryMap { + private final MemoryMap memoryMap; + private boolean isValid = true; + + /////////////////////////////////////////////////////////////////// + + ManagedMemoryMap(final MemoryMap memoryMap) { + this.memoryMap = memoryMap; + } + + /////////////////////////////////////////////////////////////////// + + public void invalidate() { + isValid = false; + } + + @Override + public boolean addDevice(final long address, final MemoryMappedDevice device) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeDevice(final MemoryMappedDevice device) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional getMemoryRange(final MemoryMappedDevice device) { + return memoryMap.getMemoryRange(device); + } + + @Override + public Optional getMemoryRange(final MemoryRange memoryRange) { + return memoryMap.getMemoryRange(memoryRange); + } + + @Nullable + @Override + public MappedMemoryRange getMemoryRange(final long address) { + return memoryMap.getMemoryRange(address); + } + + @Override + public void setDirty(final MemoryRange range, final int offset) { + if (!isValid) { + throw new IllegalStateException(); + } + + memoryMap.setDirty(range, offset); + } + + @Override + public long load(final long address, final int sizeLog2) throws MemoryAccessException { + if (!isValid) { + throw new IllegalStateException(); + } + + return memoryMap.load(address, sizeLog2); + } + + @Override + public void store(final long address, final long value, final int sizeLog2) throws MemoryAccessException { + if (!isValid) { + throw new IllegalStateException(); + } + + memoryMap.store(address, value, sizeLog2); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryRangeAllocator.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryRangeAllocator.java new file mode 100644 index 00000000..84dd6360 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedMemoryRangeAllocator.java @@ -0,0 +1,78 @@ +package li.cil.oc2.common.vm.context.managed; + +import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator; +import li.cil.oc2.common.vm.context.MemoryRangeManager; +import li.cil.sedna.api.device.MemoryMappedDevice; + +import java.util.ArrayList; +import java.util.OptionalLong; +import java.util.function.Supplier; + +final class ManagedMemoryRangeAllocator implements MemoryRangeAllocator { + private final MemoryRangeAllocator parent; + private final MemoryRangeManager memoryRangeManager; + private final Supplier baseAddressSupplier; + private final ArrayList managedDevices = new ArrayList<>(); + private boolean isFrozen; + + /////////////////////////////////////////////////////////////////// + + public ManagedMemoryRangeAllocator(final MemoryRangeAllocator parent, + final MemoryRangeManager memoryRangeManager, + final Supplier baseAddressSupplier) { + this.parent = parent; + this.memoryRangeManager = memoryRangeManager; + this.baseAddressSupplier = baseAddressSupplier; + } + + /////////////////////////////////////////////////////////////////// + + public void freeze() { + isFrozen = true; + } + + public void invalidate() { + for (final MemoryMappedDevice device : managedDevices) { + memoryRangeManager.releaseMemoryRange(device); + } + managedDevices.clear(); + } + + @Override + public boolean claimMemoryRange(final long address, final MemoryMappedDevice device) { + if (isFrozen) { + throw new IllegalStateException(); + } + + if (parent.claimMemoryRange(address, device)) { + managedDevices.add(device); + return true; + } + + return false; + } + + @Override + public OptionalLong claimMemoryRange(final MemoryMappedDevice device) { + if (isFrozen) { + throw new IllegalStateException(); + } + + final OptionalLong baseAddress = baseAddressSupplier.get(); + if (baseAddress.isPresent()) { + final OptionalLong address = memoryRangeManager.findMemoryRange(device, baseAddress.getAsLong()); + if (address.isPresent() && parent.claimMemoryRange(address.getAsLong(), device)) { + managedDevices.add(device); + return address; + } + } else { + final OptionalLong address = parent.claimMemoryRange(device); + if (address.isPresent()) { + managedDevices.add(device); + return address; + } + } + + return OptionalLong.empty(); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedVMContext.java similarity index 60% rename from src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java rename to src/main/java/li/cil/oc2/common/vm/context/managed/ManagedVMContext.java index 4f8bcc78..74154a68 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedVMContext.java @@ -1,22 +1,15 @@ -package li.cil.oc2.common.vm; +package li.cil.oc2.common.vm.context.managed; -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.oc2.api.bus.device.vm.*; +import li.cil.oc2.common.vm.context.VMContextManagerCollection; import li.cil.sedna.api.device.InterruptController; -import li.cil.sedna.api.device.MemoryMappedDevice; import li.cil.sedna.api.memory.MemoryMap; -import java.util.BitSet; import java.util.OptionalLong; -import java.util.function.Function; +import java.util.function.Supplier; -@SuppressWarnings("UnstableApiUsage") public final class ManagedVMContext implements VMContext { + private final VMContext parent; private final ManagedMemoryMap memoryMap; private final ManagedInterruptController interruptController; private final ManagedMemoryRangeAllocator memoryRangeAllocator; @@ -26,18 +19,14 @@ public final class ManagedVMContext implements VMContext { /////////////////////////////////////////////////////////////////// - public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts, - 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); + public ManagedVMContext(final VMContext parent, final VMContextManagerCollection managers, final Supplier baseAddressSupplier) { + this.parent = parent; + this.memoryRangeAllocator = new ManagedMemoryRangeAllocator(parent.getMemoryRangeAllocator(), managers.getMemoryRangeManager(), baseAddressSupplier); + this.interruptAllocator = new ManagedInterruptAllocator(parent.getInterruptAllocator(), managers.getInterruptManager()); + this.memoryMap = new ManagedMemoryMap(parent.getMemoryMap()); + this.interruptController = new ManagedInterruptController(parent.getInterruptController(), interruptAllocator); this.memoryAllocator = new ManagedMemoryAllocator(); - this.eventBus = new ManagedEventBus(eventBus); - } - - public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts, final EventBus eventBus) { - this(board, claimedInterrupts, reservedInterrupts, eventBus, (memoryMappedDevice) -> OptionalLong.empty()); + this.eventBus = new ManagedEventBus(parent.getEventBus(), managers.getEventManager()); } /////////////////////////////////////////////////////////////////// @@ -46,12 +35,14 @@ public final class ManagedVMContext implements VMContext { memoryRangeAllocator.freeze(); interruptAllocator.freeze(); memoryAllocator.freeze(); + eventBus.freeze(); } public void invalidate() { + memoryMap.invalidate(); memoryRangeAllocator.invalidate(); - interruptAllocator.invalidate(); interruptController.invalidate(); + interruptAllocator.invalidate(); memoryAllocator.invalidate(); eventBus.invalidate(); } @@ -85,4 +76,9 @@ public final class ManagedVMContext implements VMContext { public VMLifecycleEventBus getEventBus() { return eventBus; } + + @Override + public void joinWorkerThread() { + parent.joinWorkerThread(); + } } diff --git a/src/main/java/li/cil/oc2/common/vm/context/managed/package-info.java b/src/main/java/li/cil/oc2/common/vm/context/managed/package-info.java new file mode 100644 index 00000000..ffb66eae --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/managed/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.common.vm.context.managed; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/vm/context/package-info.java b/src/main/java/li/cil/oc2/common/vm/context/package-info.java new file mode 100644 index 00000000..cb00e331 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/context/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.common.vm.context; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/data/ModLootTableProvider.java b/src/main/java/li/cil/oc2/data/ModLootTableProvider.java index 0ddb505c..9decf4bb 100644 --- a/src/main/java/li/cil/oc2/data/ModLootTableProvider.java +++ b/src/main/java/li/cil/oc2/data/ModLootTableProvider.java @@ -21,8 +21,8 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; import static java.util.Objects.requireNonNull; -import static li.cil.oc2.common.Constants.INVENTORY_TAG_NAME; import static li.cil.oc2.common.Constants.BLOCK_ENTITY_TAG_NAME_IN_ITEM; +import static li.cil.oc2.common.Constants.INVENTORY_TAG_NAME; public final class ModLootTableProvider extends LootTableProvider { public ModLootTableProvider(final DataGenerator generator) { 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 43657d70..a97c4192 100644 --- a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java +++ b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java @@ -3,12 +3,15 @@ package li.cil.oc2.common.bus; 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.oc2.common.vm.VMDeviceBusAdapter; +import li.cil.oc2.common.vm.context.global.GlobalVMContext; import li.cil.sedna.api.Board; import li.cil.sedna.api.device.InterruptController; import li.cil.sedna.api.device.MemoryMappedDevice; import li.cil.sedna.api.memory.MemoryMap; +import li.cil.sedna.api.memory.MemoryRangeAllocationStrategy; import li.cil.sedna.memory.SimpleMemoryMap; +import li.cil.sedna.riscv.R5MemoryRangeAllocationStrategy; import li.cil.sedna.riscv.device.R5PlatformLevelInterruptController; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,12 +27,15 @@ import static org.mockito.Mockito.*; public final class VMDeviceTests { private MemoryMap memoryMap; private InterruptController interruptController; - private VirtualMachineDeviceBusAdapter adapter; + private R5MemoryRangeAllocationStrategy allocationStrategy; + private GlobalVMContext context; + private VMDeviceBusAdapter adapter; @BeforeEach public void setupEach() { memoryMap = new SimpleMemoryMap(); interruptController = new R5PlatformLevelInterruptController(); + allocationStrategy = new R5MemoryRangeAllocationStrategy(); final Board board = mock(Board.class); when(board.getMemoryMap()).thenReturn(memoryMap); @@ -37,20 +43,20 @@ public final class VMDeviceTests { when(board.getInterruptCount()).thenReturn(16); when(board.addDevice(any())).then(invocation -> { final MemoryMappedDevice device = invocation.getArgument(0); - final OptionalLong address = memoryMap.findFreeRange(0, 0xFFFFFFFF, device.getLength()); - if (address.isPresent()) { - memoryMap.addDevice(address.getAsLong(), device); - return true; - } else { - return false; + final OptionalLong address = allocationStrategy.findMemoryRange(device, MemoryRangeAllocationStrategy.getMemoryMapIntersectionProvider(memoryMap)); + if (address.isPresent() && memoryMap.addDevice(address.getAsLong(), device)) { + return address; } + return OptionalLong.empty(); }); doAnswer(invocation -> { memoryMap.removeDevice(invocation.getArgument(0)); return null; }).when(board).removeDevice(any()); - adapter = new VirtualMachineDeviceBusAdapter(board); + context = new GlobalVMContext(board, () -> { + }); + adapter = new VMDeviceBusAdapter(context); } @Test @@ -134,13 +140,12 @@ public final class VMDeviceTests { final VMDevice device = mock(VMDevice.class); when(device.load(any())).thenAnswer(invocation -> { final VMContext context = invocation.getArgument(0); - final OptionalInt interrupt = context.getInterruptAllocator().claimInterrupt(claimedInterrupt); - assertTrue(interrupt.isPresent()); - assertNotEquals(claimedInterrupt, interrupt.getAsInt()); + final boolean result = context.getInterruptAllocator().claimInterrupt(claimedInterrupt); + assertFalse(result); return VMDeviceLoadResult.success(); }); - adapter.getGlobalContext().getInterruptAllocator().claimInterrupt(claimedInterrupt); + context.getInterruptAllocator().claimInterrupt(claimedInterrupt); adapter.addDevices(Collections.singleton(device)); assertTrue(adapter.load().wasSuccessful()); diff --git a/src/test/java/li/cil/oc2/common/vm/RPCAdapterTests.java b/src/test/java/li/cil/oc2/common/vm/RPCAdapterTests.java index fb7f1123..85551dd6 100644 --- a/src/test/java/li/cil/oc2/common/vm/RPCAdapterTests.java +++ b/src/test/java/li/cil/oc2/common/vm/RPCAdapterTests.java @@ -8,7 +8,7 @@ import li.cil.oc2.api.bus.device.object.ObjectDevice; import li.cil.oc2.api.bus.device.object.Parameter; import li.cil.oc2.api.bus.device.rpc.RPCDevice; import li.cil.oc2.api.bus.device.rpc.RPCMethod; -import li.cil.oc2.common.bus.RPCAdapter; +import li.cil.oc2.common.bus.RPCDeviceBusAdapter; import li.cil.sedna.api.device.serial.SerialDevice; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,13 +29,13 @@ public class RPCAdapterTests { private TestSerialDevice serialDevice; private DeviceBusController busController; - private RPCAdapter rpcAdapter; + private RPCDeviceBusAdapter rpcAdapter; @BeforeEach public void setupEach() { serialDevice = new TestSerialDevice(); busController = mock(DeviceBusController.class); - rpcAdapter = new RPCAdapter(busController, serialDevice); + rpcAdapter = new RPCDeviceBusAdapter(serialDevice); } @Test @@ -100,7 +100,7 @@ public class RPCAdapterTests { when(busController.getDeviceIdentifiers(device)).thenReturn(singleton(deviceId)); // trigger device cache rebuild - rpcAdapter.resume(true); + rpcAdapter.resume(busController, true); } private JsonElement invokeMethod(final UUID deviceId, final String name, final Object... parameters) {