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) {