Massive rework of the VM related data structures.
Feels clearer now wrt flow of logic. Tracking used address ranges to avoid use of ranges taken by serialized devices. Allow devices to request joining of worker thread; used for serialization.
This commit is contained in:
@@ -55,11 +55,11 @@ dependencies {
|
||||
|
||||
compileOnly 'org.jetbrains:annotations:16.0.2'
|
||||
|
||||
implementation 'li.cil.oc2:oc2-sedna:0.0.1+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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* If the device could not fit into the memory map at all, this will return
|
||||
* {@link OptionalLong#empty()}.
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package li.cil.oc2.api.bus.device.vm.event;
|
||||
|
||||
public interface VMLifecycleEventBus {
|
||||
void register(Object object);
|
||||
|
||||
void unregister(Object object);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -47,7 +47,7 @@ public final class RobotTerminalScreen extends ContainerScreen<RobotTerminalCont
|
||||
public void render(final MatrixStack matrixStack, final int mouseX, final int mouseY, final float partialTicks) {
|
||||
renderBackground(matrixStack);
|
||||
super.render(matrixStack, mouseX, mouseY, partialTicks);
|
||||
terminalWidget.render(matrixStack, getRobot().getState().getBootError());
|
||||
terminalWidget.render(matrixStack, getRobot().getVirtualMachine().getBootError());
|
||||
RobotContainerScreen.renderSelection(matrixStack, getRobot().getSelectedSlot(), guiLeft + SLOTS_X + 4, guiTop + SLOTS_Y + 4, 12);
|
||||
renderHoveredTooltip(matrixStack, mouseX, mouseY);
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public final class RobotTerminalScreen extends ContainerScreen<RobotTerminalCont
|
||||
|
||||
@Override
|
||||
protected boolean isRunning() {
|
||||
return getRobot().getState().isRunning();
|
||||
return getRobot().getVirtualMachine().isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -72,7 +72,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
|
||||
final float pixelScale = 1 / 16f;
|
||||
matrixStack.scale(pixelScale, pixelScale, pixelScale);
|
||||
|
||||
if (tileEntity.getState().isRunning()) {
|
||||
if (tileEntity.getVirtualMachine().isRunning()) {
|
||||
renderTerminal(tileEntity, matrixStack, buffer, cameraPosition);
|
||||
} else {
|
||||
renderStatusText(tileEntity, matrixStack, cameraPosition);
|
||||
@@ -81,7 +81,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
|
||||
matrixStack.translate(0, 0, -0.1f);
|
||||
final Matrix4f matrix = matrixStack.getLast().getMatrix();
|
||||
|
||||
switch (tileEntity.getState().getBusState()) {
|
||||
switch (tileEntity.getVirtualMachine().getBusState()) {
|
||||
case SCAN_PENDING:
|
||||
case INCOMPLETE:
|
||||
renderStatus(matrix, buffer);
|
||||
@@ -93,7 +93,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
|
||||
renderStatus(matrix, buffer, 250);
|
||||
break;
|
||||
case READY:
|
||||
switch (tileEntity.getState().getRunState()) {
|
||||
switch (tileEntity.getVirtualMachine().getRunState()) {
|
||||
case STOPPED:
|
||||
break;
|
||||
case LOADING_DEVICES:
|
||||
@@ -156,7 +156,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
|
||||
return;
|
||||
}
|
||||
|
||||
final ITextComponent bootError = tileEntity.getState().getBootError();
|
||||
final ITextComponent bootError = tileEntity.getVirtualMachine().getBootError();
|
||||
if (bootError == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ public final class Constants {
|
||||
public static final int MEGABYTE = 1024 * KILOBYTE;
|
||||
public static final int GIGABYTE = 1024 * MEGABYTE;
|
||||
|
||||
public static final int CPU_FREQUENCY = 25_000_000;
|
||||
|
||||
public static final int TICK_SECONDS = 20;
|
||||
|
||||
public static final Direction[] DIRECTIONS = Direction.values();
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.*;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public final class RPCAdapter implements Steppable {
|
||||
public final class RPCDeviceBusAdapter implements Steppable {
|
||||
private static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * Constants.KILOBYTE;
|
||||
private static final byte[] MESSAGE_DELIMITER = "\0".getBytes();
|
||||
|
||||
@@ -38,7 +38,6 @@ public final class RPCAdapter implements Steppable {
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private final DeviceBusController controller;
|
||||
private final SerialDevice serialDevice;
|
||||
private final Gson gson;
|
||||
|
||||
@@ -55,12 +54,11 @@ public final class RPCAdapter implements Steppable {
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public RPCAdapter(final DeviceBusController controller, final SerialDevice serialDevice) {
|
||||
this(controller, serialDevice, DEFAULT_MAX_MESSAGE_SIZE);
|
||||
public RPCDeviceBusAdapter(final SerialDevice serialDevice) {
|
||||
this(serialDevice, DEFAULT_MAX_MESSAGE_SIZE);
|
||||
}
|
||||
|
||||
public RPCAdapter(final DeviceBusController controller, final SerialDevice serialDevice, final int maxMessageSize) {
|
||||
this.controller = controller;
|
||||
public RPCDeviceBusAdapter(final SerialDevice serialDevice, final int maxMessageSize) {
|
||||
this.serialDevice = serialDevice;
|
||||
this.transmitBuffer = ByteBuffer.allocate(maxMessageSize);
|
||||
this.gson = RPCMethodParameterTypeAdapters.beginBuildGson()
|
||||
@@ -89,7 +87,7 @@ public final class RPCAdapter implements Steppable {
|
||||
pauseLock.unlock();
|
||||
}
|
||||
|
||||
public void resume(final boolean didDevicesChange) {
|
||||
public void resume(final DeviceBusController controller, final boolean didDevicesChange) {
|
||||
isPaused = false;
|
||||
|
||||
if (!didDevicesChange) {
|
||||
@@ -26,10 +26,10 @@ public final class OptionalAddress {
|
||||
|
||||
public boolean claim(final VMContext context, final MemoryMappedDevice device) {
|
||||
final OptionalLong claimedAddress;
|
||||
if (value == null) {
|
||||
claimedAddress = context.getMemoryRangeAllocator().claimMemoryRange(device);
|
||||
if (value != null && context.getMemoryRangeAllocator().claimMemoryRange(value, device)) {
|
||||
claimedAddress = OptionalLong.of(value);
|
||||
} else {
|
||||
claimedAddress = context.getMemoryRangeAllocator().claimMemoryRange(value, device);
|
||||
claimedAddress = context.getMemoryRangeAllocator().claimMemoryRange(device);
|
||||
}
|
||||
|
||||
if (claimedAddress.isPresent()) {
|
||||
|
||||
@@ -25,10 +25,10 @@ public final class OptionalInterrupt {
|
||||
|
||||
public boolean claim(final VMContext context) {
|
||||
final OptionalInt claimedInterrupt;
|
||||
if (value == null) {
|
||||
claimedInterrupt = context.getInterruptAllocator().claimInterrupt();
|
||||
if (value != null && context.getInterruptAllocator().claimInterrupt(value)) {
|
||||
claimedInterrupt = OptionalInt.of(value);
|
||||
} else {
|
||||
claimedInterrupt = context.getInterruptAllocator().claimInterrupt(value);
|
||||
claimedInterrupt = context.getInterruptAllocator().claimInterrupt();
|
||||
}
|
||||
|
||||
if (claimedInterrupt.isPresent()) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package li.cil.oc2.common.container;
|
||||
import li.cil.oc2.api.bus.device.DeviceTypes;
|
||||
import li.cil.oc2.common.block.Blocks;
|
||||
import li.cil.oc2.common.tileentity.ComputerTileEntity;
|
||||
import li.cil.oc2.common.vm.VirtualMachineItemStackHandlers;
|
||||
import li.cil.oc2.common.vm.VMItemStackHandlers;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
@@ -34,7 +34,7 @@ public final class ComputerContainer extends AbstractContainer {
|
||||
super(Containers.COMPUTER_CONTAINER.get(), id);
|
||||
this.computer = computer;
|
||||
|
||||
final VirtualMachineItemStackHandlers handlers = computer.getItemStackHandlers();
|
||||
final VMItemStackHandlers handlers = computer.getItemStackHandlers();
|
||||
|
||||
handlers.getItemHandler(DeviceTypes.FLASH_MEMORY).ifPresent(itemHandler -> {
|
||||
if (itemHandler.getSlots() > 0) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<T> 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<Device> devices) {
|
||||
state.virtualMachine.vmAdapter.addDevices(devices);
|
||||
virtualMachine.state.vmAdapter.addDevices(devices);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDevicesRemoved(final Collection<Device> 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<RobotBusController, CommonVirtualMachine> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<NetworkEvent.Context> context) {
|
||||
context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class,
|
||||
(tileEntity) -> tileEntity.getState().setBootErrorClient(message.value)));
|
||||
(tileEntity) -> tileEntity.getVirtualMachine().setBootErrorClient(message.value)));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NetworkEvent.Context> context) {
|
||||
context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class,
|
||||
(tileEntity) -> tileEntity.getState().setBusStateClient(message.value)));
|
||||
(tileEntity) -> tileEntity.getVirtualMachine().setBusStateClient(message.value)));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NetworkEvent.Context> 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) {
|
||||
|
||||
@@ -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<NetworkEvent.Context> context) {
|
||||
context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class,
|
||||
(robot) -> robot.getState().setBootErrorClient(message.value)));
|
||||
(robot) -> robot.getVirtualMachine().setBootErrorClient(message.value)));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NetworkEvent.Context> context) {
|
||||
context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class,
|
||||
(robot) -> robot.getState().setBusStateClient(message.value)));
|
||||
(robot) -> robot.getVirtualMachine().setBusStateClient(message.value)));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NetworkEvent.Context> 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();
|
||||
}
|
||||
|
||||
@@ -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<NetworkEvent.Context> 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) {
|
||||
|
||||
@@ -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<MemoryRangeList> {
|
||||
@Override
|
||||
public void serialize(final SerializationVisitor visitor, final Class<MemoryRangeList> 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<MemoryRangeList> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<MemoryRange> {
|
||||
@Override
|
||||
public void serialize(final SerializationVisitor visitor, final Class<MemoryRange> 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<MemoryRange> 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"));
|
||||
}
|
||||
}
|
||||
@@ -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<RPCAdapter.Message> {
|
||||
public final class MessageJsonDeserializer implements JsonDeserializer<RPCDeviceBusAdapter.Message> {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RPCAdapter.MethodInvocation> {
|
||||
public final class MethodInvocationJsonDeserializer implements JsonDeserializer<RPCDeviceBusAdapter.MethodInvocation> {
|
||||
@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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RPCAdapter.RPCDeviceWithIdentifier> {
|
||||
public final class RPCDeviceWithIdentifierJsonSerializer implements JsonSerializer<RPCDeviceBusAdapter.RPCDeviceWithIdentifier> {
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> 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<Device> devices) {
|
||||
state.virtualMachine.vmAdapter.addDevices(devices);
|
||||
virtualMachine.state.vmAdapter.addDevices(devices);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDevicesRemoved(final Collection<Device> 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<ComputerBusController, CommonVirtualMachine> {
|
||||
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) {
|
||||
|
||||
@@ -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<BlockDevice, TileEntity> {
|
||||
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<InputStream> 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();
|
||||
}
|
||||
}
|
||||
|
||||
27
src/main/java/li/cil/oc2/common/util/TerminalUtils.java
Normal file
27
src/main/java/li/cil/oc2/common/util/TerminalUtils.java
Normal file
@@ -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<ByteBuffer> packetSender) {
|
||||
TERMINAL_RESET_SEQUENCE.clear();
|
||||
terminal.putOutput(TERMINAL_RESET_SEQUENCE);
|
||||
|
||||
TERMINAL_RESET_SEQUENCE.flip();
|
||||
packetSender.accept(TERMINAL_RESET_SEQUENCE);
|
||||
}
|
||||
}
|
||||
@@ -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<SoundType, SoundEvent> soundEvent) {
|
||||
public static void playSound(final IWorld world, final BlockPos pos, final SoundType soundType, final Function<SoundType, SoundEvent> soundEvent) {
|
||||
playSound(world, pos, soundType, soundEvent.apply(soundType));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<DeviceType, DeviceItemStackHandler> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TBusController extends AbstractDeviceBusController, TVirtualMachine extends VirtualMachine> 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<TBusController extends Abstrac
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public final TBusController busController;
|
||||
public final AbstractDeviceBusController busController;
|
||||
private AbstractDeviceBusController.BusState busState = AbstractDeviceBusController.BusState.SCAN_PENDING;
|
||||
|
||||
public final TVirtualMachine virtualMachine;
|
||||
private AbstractTerminalVirtualMachineRunner runner;
|
||||
private RunState runState = RunState.STOPPED;
|
||||
private ITextComponent bootError;
|
||||
private int loadDevicesDelay;
|
||||
|
||||
@Serialized
|
||||
public static final class SerializedState {
|
||||
public R5Board board;
|
||||
public GlobalVMContext context;
|
||||
public BuiltinDevices builtinDevices;
|
||||
public RPCDeviceBusAdapter rpcAdapter;
|
||||
public VMDeviceBusAdapter vmAdapter;
|
||||
}
|
||||
|
||||
public SerializedState state = new SerializedState();
|
||||
public AbstractTerminalVMRunner runner;
|
||||
private VMRunState runState = VMRunState.STOPPED;
|
||||
private ITextComponent bootError;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public AbstractVirtualMachineState(final TBusController busController, final TVirtualMachine virtualMachine) {
|
||||
public AbstractVirtualMachine(final AbstractDeviceBusController busController) {
|
||||
this.busController = busController;
|
||||
this.virtualMachine = virtualMachine;
|
||||
|
||||
state.board = new R5Board();
|
||||
state.context = new GlobalVMContext(state.board, this::joinWorkerThread);
|
||||
|
||||
state.board.getCpu().setFrequency(Constants.CPU_FREQUENCY);
|
||||
state.board.setBootArguments("root=/dev/vda rw");
|
||||
|
||||
state.builtinDevices = new BuiltinDevices(state.context);
|
||||
|
||||
state.rpcAdapter = new RPCDeviceBusAdapter(state.builtinDevices.rpcSerialDevice);
|
||||
state.vmAdapter = new VMDeviceBusAdapter(state.context);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public void unload() {
|
||||
joinWorkerThread();
|
||||
state.vmAdapter.suspend();
|
||||
state.context.invalidate();
|
||||
busController.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return getBusState() == AbstractDeviceBusController.BusState.READY &&
|
||||
getRunState() == RunState.RUNNING;
|
||||
getRunState() == VMRunState.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,13 +102,13 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunState getRunState() {
|
||||
public VMRunState getRunState() {
|
||||
return runState;
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public void setRunStateClient(final RunState value) {
|
||||
public void setRunStateClient(final VMRunState value) {
|
||||
runState = value;
|
||||
}
|
||||
|
||||
@@ -112,12 +142,12 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (runState == RunState.RUNNING) {
|
||||
if (runState == VMRunState.RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
setBootError(null);
|
||||
setRunState(RunState.LOADING_DEVICES);
|
||||
setRunState(VMRunState.LOADING_DEVICES);
|
||||
loadDevicesDelay = 0;
|
||||
}
|
||||
|
||||
@@ -125,7 +155,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
public void stop() {
|
||||
switch (runState) {
|
||||
case LOADING_DEVICES:
|
||||
setRunState(RunState.STOPPED);
|
||||
setRunState(VMRunState.STOPPED);
|
||||
break;
|
||||
case RUNNING:
|
||||
stopRunnerAndReset();
|
||||
@@ -133,16 +163,13 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
if (runState == RunState.RUNNING) {
|
||||
runState = RunState.LOADING_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
public void joinVirtualMachine() {
|
||||
@Override
|
||||
public void joinWorkerThread() {
|
||||
if (runner != null) {
|
||||
try {
|
||||
state.context.postEvent(new VMPausingEvent());
|
||||
runner.join();
|
||||
runner.scheduleResumeEvent();
|
||||
} catch (final Throwable e) {
|
||||
LOGGER.error(e);
|
||||
runner = null;
|
||||
@@ -150,18 +177,32 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
}
|
||||
}
|
||||
|
||||
public void stopRunnerAndReset() {
|
||||
joinVirtualMachine();
|
||||
setRunState(RunState.STOPPED);
|
||||
public void pauseAndReload() {
|
||||
state.rpcAdapter.pause();
|
||||
|
||||
virtualMachine.reset();
|
||||
|
||||
if (runner != null) {
|
||||
runner.resetTerminal();
|
||||
runner = null;
|
||||
// This is typically called when a device bus starts a scan. Since scans
|
||||
// can be delayed we must adjust our run state accordingly, to avoid
|
||||
// running before the scan finishes.
|
||||
if (runState == VMRunState.RUNNING) {
|
||||
runState = VMRunState.LOADING_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
public void resume(final boolean didDevicesChange) {
|
||||
state.rpcAdapter.resume(busController, didDevicesChange);
|
||||
}
|
||||
|
||||
public void stopRunnerAndReset() {
|
||||
joinWorkerThread();
|
||||
setRunState(VMRunState.STOPPED);
|
||||
|
||||
state.board.reset();
|
||||
state.rpcAdapter.reset();
|
||||
state.vmAdapter.unload();
|
||||
|
||||
runner = null;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
busController.scan();
|
||||
setBusState(busController.getState());
|
||||
@@ -178,7 +219,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
break;
|
||||
}
|
||||
|
||||
final VMDeviceLoadResult loadResult = virtualMachine.vmAdapter.load();
|
||||
final VMDeviceLoadResult loadResult = state.vmAdapter.load();
|
||||
if (!loadResult.wasSuccessful()) {
|
||||
if (loadResult.getErrorMessage() != null) {
|
||||
setBootError(loadResult.getErrorMessage());
|
||||
@@ -191,7 +232,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
|
||||
if (busController.getDevices().stream().noneMatch(device -> 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<TBusController extends Abstrac
|
||||
// bus setup and devices to load. So we can keep using it.
|
||||
if (runner == null) {
|
||||
try {
|
||||
virtualMachine.board.reset();
|
||||
virtualMachine.board.initialize();
|
||||
virtualMachine.board.setRunning(true);
|
||||
state.board.reset();
|
||||
state.board.initialize();
|
||||
state.board.setRunning(true);
|
||||
} catch (final IllegalStateException e) {
|
||||
// FDT did not fit into memory. Technically it's possible to run with
|
||||
// a program that only uses registers. But not supporting that esoteric
|
||||
// use-case loses out against avoiding people getting confused for having
|
||||
// forgotten to add some RAM modules.
|
||||
setBootError(new TranslationTextComponent(Constants.COMPUTER_ERROR_INSUFFICIENT_MEMORY));
|
||||
setRunState(RunState.STOPPED);
|
||||
setRunState(VMRunState.STOPPED);
|
||||
break;
|
||||
} catch (final MemoryAccessException e) {
|
||||
LOGGER.error(e);
|
||||
setBootError(new TranslationTextComponent(Constants.COMPUTER_ERROR_UNKNOWN));
|
||||
setRunState(RunState.STOPPED);
|
||||
setRunState(VMRunState.STOPPED);
|
||||
break;
|
||||
}
|
||||
|
||||
runner = createRunner();
|
||||
}
|
||||
|
||||
setRunState(RunState.RUNNING);
|
||||
setRunState(VMRunState.RUNNING);
|
||||
|
||||
// Only start running next tick. This gives loaded devices one tick to do async
|
||||
// initialization. This is used by devices to restore data from disk, for example.
|
||||
@@ -233,7 +274,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
break;
|
||||
}
|
||||
|
||||
if (!virtualMachine.board.isRunning()) {
|
||||
if (!state.board.isRunning()) {
|
||||
stopRunnerAndReset();
|
||||
break;
|
||||
}
|
||||
@@ -244,51 +285,50 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
}
|
||||
|
||||
public CompoundNBT serialize() {
|
||||
final CompoundNBT tag = new CompoundNBT();
|
||||
joinWorkerThread();
|
||||
|
||||
joinVirtualMachine();
|
||||
final CompoundNBT tag = new CompoundNBT();
|
||||
|
||||
if (runner != null) {
|
||||
tag.put(RUNNER_TAG_NAME, NBTSerialization.serialize(runner));
|
||||
virtualMachine.vmAdapter.postLifecycleEvent(new VMPausingEvent());
|
||||
runner.scheduleResumeEvent(); // Allow synchronizing to async device saves.
|
||||
} else {
|
||||
NBTUtils.putEnum(tag, RUN_STATE_TAG_NAME, runState);
|
||||
}
|
||||
tag.put(VIRTUAL_MACHINE_TAG_NAME, NBTSerialization.serialize(virtualMachine));
|
||||
|
||||
tag.put(STATE_TAG_NAME, NBTSerialization.serialize(state));
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void deserialize(final CompoundNBT tag) {
|
||||
joinVirtualMachine();
|
||||
joinWorkerThread();
|
||||
|
||||
if (tag.contains(RUNNER_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
|
||||
runner = createRunner();
|
||||
NBTSerialization.deserialize(tag.getCompound(RUNNER_TAG_NAME), runner);
|
||||
runState = RunState.LOADING_DEVICES;
|
||||
runState = VMRunState.LOADING_DEVICES;
|
||||
} else {
|
||||
runState = NBTUtils.getEnum(tag, RUN_STATE_TAG_NAME, RunState.class);
|
||||
runState = NBTUtils.getEnum(tag, RUN_STATE_TAG_NAME, VMRunState.class);
|
||||
if (runState == null) {
|
||||
runState = RunState.STOPPED;
|
||||
} else if (runState == RunState.RUNNING) {
|
||||
runState = RunState.LOADING_DEVICES;
|
||||
runState = VMRunState.STOPPED;
|
||||
} else if (runState == VMRunState.RUNNING) {
|
||||
runState = VMRunState.LOADING_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag.contains(VIRTUAL_MACHINE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
|
||||
NBTSerialization.deserialize(tag.getCompound(VIRTUAL_MACHINE_TAG_NAME), virtualMachine);
|
||||
if (tag.contains(STATE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
|
||||
NBTSerialization.deserialize(tag.getCompound(STATE_TAG_NAME), state);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
protected abstract AbstractTerminalVirtualMachineRunner createRunner();
|
||||
protected abstract AbstractTerminalVMRunner createRunner();
|
||||
|
||||
protected void handleBusStateChanged(final AbstractDeviceBusController.BusState value) {
|
||||
}
|
||||
|
||||
protected void handleRunStateChanged(final RunState value) {
|
||||
protected void handleRunStateChanged(final VMRunState value) {
|
||||
}
|
||||
|
||||
protected void handleBootErrorChanged(@Nullable final ITextComponent value) {
|
||||
@@ -306,7 +346,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
|
||||
handleBusStateChanged(busState);
|
||||
}
|
||||
|
||||
private void setRunState(final RunState value) {
|
||||
private void setRunState(final VMRunState value) {
|
||||
if (value == runState) {
|
||||
return;
|
||||
}
|
||||
@@ -4,6 +4,6 @@ import li.cil.oc2.api.bus.device.vm.VMDevice;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
|
||||
public interface DefaultAddressProvider {
|
||||
OptionalLong getDefaultAddress(final VMDevice wrapper);
|
||||
public interface BaseAddressProvider {
|
||||
OptionalLong getBaseAddress(final VMDevice wrapper);
|
||||
}
|
||||
57
src/main/java/li/cil/oc2/common/vm/BuiltinDevices.java
Normal file
57
src/main/java/li/cil/oc2/common/vm/BuiltinDevices.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.ceres.api.Serialized;
|
||||
import li.cil.oc2.common.bus.device.data.FileSystems;
|
||||
import li.cil.oc2.common.vm.context.global.GlobalVMContext;
|
||||
import li.cil.sedna.device.rtc.GoldfishRTC;
|
||||
import li.cil.sedna.device.rtc.SystemTimeRealTimeCounter;
|
||||
import li.cil.sedna.device.serial.UART16550A;
|
||||
import li.cil.sedna.device.virtio.VirtIOConsoleDevice;
|
||||
import li.cil.sedna.device.virtio.VirtIOFileSystemDevice;
|
||||
|
||||
public final class BuiltinDevices {
|
||||
public static final int RTC_HOST_INTERRUPT = 0x1;
|
||||
public static final int RTC_MINECRAFT_INTERRUPT = 0x2;
|
||||
public static final int RPC_INTERRUPT = 0x3;
|
||||
private static final int UART_INTERRUPT = 0x4;
|
||||
private static final int VFS_INTERRUPT = 0x5;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public final MinecraftRealTimeCounter rtcMinecraft = new MinecraftRealTimeCounter();
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Serialized public VirtIOConsoleDevice rpcSerialDevice;
|
||||
@Serialized public UART16550A uart;
|
||||
@Serialized public VirtIOFileSystemDevice vfs;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public BuiltinDevices(final GlobalVMContext context) {
|
||||
final GoldfishRTC rtcHost = new GoldfishRTC(SystemTimeRealTimeCounter.get());
|
||||
if (!context.getInterruptAllocator().claimInterrupt(RTC_HOST_INTERRUPT)) throw new IllegalStateException();
|
||||
rtcHost.getInterrupt().set(RTC_HOST_INTERRUPT, context.getInterruptController());
|
||||
context.getMemoryRangeAllocator().claimMemoryRange(rtcHost);
|
||||
|
||||
final GoldfishRTC rtcMinecraft = new GoldfishRTC(this.rtcMinecraft);
|
||||
if (!context.getInterruptAllocator().claimInterrupt(RTC_MINECRAFT_INTERRUPT)) throw new IllegalStateException();
|
||||
rtcMinecraft.getInterrupt().set(RTC_MINECRAFT_INTERRUPT, context.getInterruptController());
|
||||
context.getMemoryRangeAllocator().claimMemoryRange(rtcMinecraft);
|
||||
|
||||
rpcSerialDevice = new VirtIOConsoleDevice(context.getMemoryMap());
|
||||
if (!context.getInterruptAllocator().claimInterrupt(RPC_INTERRUPT)) throw new IllegalStateException();
|
||||
rpcSerialDevice.getInterrupt().set(RPC_INTERRUPT, context.getInterruptController());
|
||||
context.getMemoryRangeAllocator().claimMemoryRange(rpcSerialDevice);
|
||||
|
||||
uart = new UART16550A();
|
||||
if (!context.getInterruptAllocator().claimInterrupt(UART_INTERRUPT)) throw new IllegalStateException();
|
||||
uart.getInterrupt().set(UART_INTERRUPT, context.getInterruptController());
|
||||
context.getMemoryRangeAllocator().claimMemoryRange(uart);
|
||||
|
||||
vfs = new VirtIOFileSystemDevice(context.getMemoryMap(), "data", FileSystems.getLayeredFileSystem());
|
||||
if (!context.getInterruptAllocator().claimInterrupt(VFS_INTERRUPT)) throw new IllegalStateException();
|
||||
vfs.getInterrupt().set(VFS_INTERRUPT, context.getInterruptController());
|
||||
context.getMemoryRangeAllocator().claimMemoryRange(vfs);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
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.VMContext;
|
||||
import li.cil.oc2.common.bus.device.data.FileSystems;
|
||||
import li.cil.sedna.device.serial.UART16550A;
|
||||
import li.cil.sedna.device.virtio.VirtIOFileSystemDevice;
|
||||
|
||||
public final class CommonVirtualMachine extends VirtualMachine {
|
||||
private static final int UART_INTERRUPT = 0x4;
|
||||
private static final int VFS_INTERRUPT = 0x5;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Serialized public UART16550A uart;
|
||||
@Serialized public VirtIOFileSystemDevice vfs;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public CommonVirtualMachine(final DeviceBusController busController) {
|
||||
super(busController);
|
||||
|
||||
final VMContext context = vmAdapter.getGlobalContext();
|
||||
uart = new UART16550A();
|
||||
context.getInterruptAllocator().claimInterrupt(UART_INTERRUPT).ifPresent(interrupt ->
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<Object> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<MemoryMappedDevice, OptionalLong> defaultAddress;
|
||||
private final ArrayList<MemoryMappedDevice> managedDevices = new ArrayList<>();
|
||||
private boolean isFrozen;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ManagedMemoryRangeAllocator(final Board board, final Function<MemoryMappedDevice, OptionalLong> 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<MemoryRange> range = board.getMemoryMap().getMemoryRange(device);
|
||||
return OptionalLong.of(range.orElseThrow(AssertionError::new).address());
|
||||
}
|
||||
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
}
|
||||
114
src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java
Normal file
114
src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java
Normal file
@@ -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<VMDevice, ManagedVMContext> deviceContexts = new HashMap<>();
|
||||
private final ArrayList<VMDevice> 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<Device> 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<Device> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import net.minecraftforge.items.IItemHandler;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface VirtualMachineItemStackHandlers {
|
||||
public interface VMItemStackHandlers {
|
||||
Optional<IItemHandler> getItemHandler(DeviceType deviceType);
|
||||
|
||||
boolean isEmpty();
|
||||
7
src/main/java/li/cil/oc2/common/vm/VMRunState.java
Normal file
7
src/main/java/li/cil/oc2/common/vm/VMRunState.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
public enum VMRunState {
|
||||
STOPPED,
|
||||
LOADING_DEVICES,
|
||||
RUNNING,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VMDevice, ManagedVMContext> deviceContexts = new HashMap<>();
|
||||
private final ArrayList<VMDevice> 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<Device> 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<Device> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package li.cil.oc2.common.vm.context;
|
||||
|
||||
public interface EventManager {
|
||||
void unregister(Object subscriber);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package li.cil.oc2.common.vm.context;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public interface InterruptManager {
|
||||
int getInterruptCount();
|
||||
|
||||
void releaseInterrupts(BitSet interrupts);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package li.cil.oc2.common.vm.context;
|
||||
|
||||
public interface InterruptValidator {
|
||||
boolean isMaskValid(int mask);
|
||||
|
||||
int getMaskedInterrupts(int interrupts);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package li.cil.oc2.common.vm.context;
|
||||
|
||||
public interface VMContextManagerCollection {
|
||||
InterruptManager getInterruptManager();
|
||||
|
||||
MemoryRangeManager getMemoryRangeManager();
|
||||
|
||||
EventManager getEventManager();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<UUID> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<MemoryRange> getMemoryRange(final MemoryMappedDevice device) {
|
||||
public Optional<MappedMemoryRange> getMemoryRange(final MemoryMappedDevice device) {
|
||||
return memoryMap.getMemoryRange(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MappedMemoryRange> 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);
|
||||
}
|
||||
|
||||
@@ -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<MemoryRange> reservedMemoryRanges;
|
||||
private final Object2LongArrayMap<MemoryMappedDevice> claimedMemoryRanges = new Object2LongArrayMap<>();
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public GlobalMemoryRangeAllocator(final Board board, final ArrayList<MemoryRange> reservedMemoryRanges) {
|
||||
this.board = board;
|
||||
this.reservedMemoryRanges = reservedMemoryRanges;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public Collection<MemoryRange> getClaimedMemoryRanges() {
|
||||
final ArrayList<MemoryRange> result = new ArrayList<>();
|
||||
for (final Object2LongMap.Entry<MemoryMappedDevice> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<MemoryRange> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.common.vm.context.global;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -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<Object> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<UUID> claimedMemory = new ArrayList<>();
|
||||
private boolean isFrozen;
|
||||
|
||||
@@ -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<MappedMemoryRange> getMemoryRange(final MemoryMappedDevice device) {
|
||||
return memoryMap.getMemoryRange(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MappedMemoryRange> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<OptionalLong> baseAddressSupplier;
|
||||
private final ArrayList<MemoryMappedDevice> managedDevices = new ArrayList<>();
|
||||
private boolean isFrozen;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ManagedMemoryRangeAllocator(final MemoryRangeAllocator parent,
|
||||
final MemoryRangeManager memoryRangeManager,
|
||||
final Supplier<OptionalLong> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<MemoryMappedDevice, OptionalLong> 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<OptionalLong> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.common.vm.context.managed;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.common.vm.context;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user