From efa43430b3d43667b43d4263287ef2bcd1072c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 13 Jan 2021 13:46:40 +0100 Subject: [PATCH] Pulled out a bunch more common functionality from computers for re-use in robots. --- .../li/cil/oc2/client/gui/TerminalScreen.java | 14 +- .../ComputerTileEntityRenderer.java | 8 +- .../java/li/cil/oc2/common/Constants.java | 2 +- .../cil/oc2/common/block/ComputerBlock.java | 12 +- .../common/container/ComputerContainer.java | 10 +- .../li/cil/oc2/common/entity/RobotEntity.java | 164 ++++- .../li/cil/oc2/common/item/ItemGroup.java | 10 +- .../cil/oc2/common/network/MessageUtils.java | 28 + .../li/cil/oc2/common/network/Network.java | 39 +- .../AbstractTerminalEntityMessage.java | 34 ++ .../message/ComputerBootErrorMessage.java | 4 +- .../message/ComputerBusStateMessage.java | 4 +- .../message/ComputerRunStateMessage.java | 9 +- ...java => ComputerTerminalInputMessage.java} | 6 +- ...ava => ComputerTerminalOutputMessage.java} | 6 +- .../message/RobotBootErrorMessage.java | 43 ++ .../message/RobotTerminalInputMessage.java | 27 + .../message/RobotTerminalOutputMessage.java | 27 + .../common/tileentity/ComputerTileEntity.java | 563 ++++-------------- .../cil/oc2/common/util/ItemStackUtils.java | 12 +- .../AbstractTerminalVirtualMachineRunner.java | 14 + .../vm/AbstractVirtualMachineState.java | 317 ++++++++++ ...CommonVirtualMachineItemStackHandlers.java | 184 ++++++ .../oc2/common/vm/VirtualMachineState.java | 38 ++ src/main/java/li/cil/oc2/data/LootTables.java | 6 +- 25 files changed, 1062 insertions(+), 519 deletions(-) create mode 100644 src/main/java/li/cil/oc2/common/network/message/AbstractTerminalEntityMessage.java rename src/main/java/li/cil/oc2/common/network/message/{TerminalBlockInputMessage.java => ComputerTerminalInputMessage.java} (76%) rename src/main/java/li/cil/oc2/common/network/message/{TerminalBlockOutputMessage.java => ComputerTerminalOutputMessage.java} (75%) create mode 100644 src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java create mode 100644 src/main/java/li/cil/oc2/common/network/message/RobotTerminalInputMessage.java create mode 100644 src/main/java/li/cil/oc2/common/network/message/RobotTerminalOutputMessage.java create mode 100644 src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java create mode 100644 src/main/java/li/cil/oc2/common/vm/CommonVirtualMachineItemStackHandlers.java create mode 100644 src/main/java/li/cil/oc2/common/vm/VirtualMachineState.java diff --git a/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java index e6eae153..64613277 100644 --- a/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java @@ -9,7 +9,7 @@ import li.cil.oc2.client.gui.widget.ToggleImageButton; import li.cil.oc2.common.Constants; import li.cil.oc2.common.network.Network; import li.cil.oc2.common.network.message.ComputerPowerMessage; -import li.cil.oc2.common.network.message.TerminalBlockInputMessage; +import li.cil.oc2.common.network.message.ComputerTerminalInputMessage; import li.cil.oc2.common.tileentity.ComputerTileEntity; import li.cil.oc2.common.vm.Terminal; import net.minecraft.client.entity.player.ClientPlayerEntity; @@ -84,13 +84,13 @@ public final class TerminalScreen extends Screen { super.render(matrixStack, mouseX, mouseY, partialTicks); - if (tileEntity.isRunning()) { + if (tileEntity.getState().isRunning()) { final MatrixStack stack = new MatrixStack(); stack.translate(windowLeft + TERMINAL_AREA_X, windowTop + TERMINAL_AREA_Y, this.itemRenderer.zLevel); stack.scale(TERMINAL_AREA_WIDTH / (float) terminal.getWidth(), TERMINAL_AREA_HEIGHT / (float) terminal.getHeight(), 1f); terminal.render(stack); } else { - final ITextComponent bootError = tileEntity.getBootError(); + final ITextComponent bootError = tileEntity.getState().getBootError(); if (bootError != null) { final int textWidth = font.getStringPropertyWidth(bootError); final int textOffsetX = (TERMINAL_AREA_WIDTH - textWidth) / 2; @@ -110,7 +110,7 @@ public final class TerminalScreen extends Screen { final ByteBuffer input = terminal.getInput(); if (input != null) { - Network.INSTANCE.sendToServer(new TerminalBlockInputMessage(tileEntity, input)); + Network.INSTANCE.sendToServer(new ComputerTerminalInputMessage(tileEntity, input)); } assert minecraft != null; @@ -185,13 +185,13 @@ public final class TerminalScreen extends Screen { @Override public void onPress() { super.onPress(); - final ComputerPowerMessage message = new ComputerPowerMessage(tileEntity, !tileEntity.isRunning()); + final ComputerPowerMessage message = new ComputerPowerMessage(tileEntity, !tileEntity.getState().isRunning()); Network.INSTANCE.sendToServer(message); } @Override public boolean isToggled() { - return tileEntity.isRunning(); + return tileEntity.getState().isRunning(); } }); @@ -220,7 +220,7 @@ public final class TerminalScreen extends Screen { /////////////////////////////////////////////////////////////////// private boolean shouldCaptureInput() { - return isMouseOverTerminal && enableInputCapture && tileEntity.isRunning(); + return isMouseOverTerminal && enableInputCapture && tileEntity.getState().isRunning(); } private boolean isPointInRegion(final int x, final int y, final int width, final int height, double mouseX, double mouseY) { diff --git a/src/main/java/li/cil/oc2/client/renderer/tileentity/ComputerTileEntityRenderer.java b/src/main/java/li/cil/oc2/client/renderer/tileentity/ComputerTileEntityRenderer.java index a293ea60..4520575d 100644 --- a/src/main/java/li/cil/oc2/client/renderer/tileentity/ComputerTileEntityRenderer.java +++ b/src/main/java/li/cil/oc2/client/renderer/tileentity/ComputerTileEntityRenderer.java @@ -70,7 +70,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer tooltip, final ITooltipFlag advanced) { super.addInformation(stack, world, tooltip, advanced); - TooltipUtils.addInventoryInformation(stack, tooltip, - ComputerTileEntity.MEMORY_TAG_NAME, - ComputerTileEntity.HARD_DRIVE_TAG_NAME, - ComputerTileEntity.FLASH_MEMORY_TAG_NAME, - ComputerTileEntity.CARD_TAG_NAME); + CommonVirtualMachineItemStackHandlers.addInformation(stack, tooltip); } @Override @@ -182,8 +178,8 @@ public final class ComputerBlock extends HorizontalBlock { final TileEntity tileEntity = world.getTileEntity(pos); if (!world.isRemote() && tileEntity instanceof ComputerTileEntity) { final ComputerTileEntity computer = (ComputerTileEntity) tileEntity; - if (!computer.isEmpty()) { - computer.exportDeviceDataToItemStacks(); + if (!computer.getItemHandlers().isEmpty()) { + computer.getItemHandlers().exportDeviceDataToItemStacks(); if (player.isCreative()) { final ItemStack stack = new ItemStack(Items.COMPUTER_ITEM.get()); diff --git a/src/main/java/li/cil/oc2/common/container/ComputerContainer.java b/src/main/java/li/cil/oc2/common/container/ComputerContainer.java index e5b10e1b..8452bf65 100644 --- a/src/main/java/li/cil/oc2/common/container/ComputerContainer.java +++ b/src/main/java/li/cil/oc2/common/container/ComputerContainer.java @@ -3,6 +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.CommonVirtualMachineItemStackHandlers; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.network.PacketBuffer; @@ -36,25 +37,26 @@ public final class ComputerContainer extends AbstractContainer { this.world = inventory.player.getEntityWorld(); this.pos = tileEntity.getPos(); - tileEntity.getItemHandler(DeviceTypes.FLASH_MEMORY).ifPresent(itemHandler -> { + final CommonVirtualMachineItemStackHandlers itemHandlers = tileEntity.getItemHandlers(); + itemHandlers.getItemHandler(DeviceTypes.FLASH_MEMORY).ifPresent(itemHandler -> { if (itemHandler.getSlots() > 0) { this.addSlot(new TypedSlotItemHandler(itemHandler, DeviceTypes.FLASH_MEMORY, 0, 64, 78)); } }); - tileEntity.getItemHandler(DeviceTypes.MEMORY).ifPresent(itemHandler -> { + itemHandlers.getItemHandler(DeviceTypes.MEMORY).ifPresent(itemHandler -> { for (int slot = 0; slot < itemHandler.getSlots(); slot++) { this.addSlot(new TypedSlotItemHandler(itemHandler, DeviceTypes.MEMORY, slot, 64 + slot * SLOT_SIZE, 24)); } }); - tileEntity.getItemHandler(DeviceTypes.HARD_DRIVE).ifPresent(itemHandler -> { + itemHandlers.getItemHandler(DeviceTypes.HARD_DRIVE).ifPresent(itemHandler -> { for (int slot = 0; slot < itemHandler.getSlots(); slot++) { this.addSlot(new TypedSlotItemHandler(itemHandler, DeviceTypes.HARD_DRIVE, slot, 100 + (slot % 2) * SLOT_SIZE, 60 + (slot / 2) * SLOT_SIZE)); } }); - tileEntity.getItemHandler(DeviceTypes.CARD).ifPresent(itemHandler -> { + itemHandlers.getItemHandler(DeviceTypes.CARD).ifPresent(itemHandler -> { for (int slot = 0; slot < itemHandler.getSlots(); slot++) { this.addSlot(new TypedSlotItemHandler(itemHandler, DeviceTypes.CARD, slot, 38, 24 + slot * SLOT_SIZE)); } diff --git a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java index 123c1214..09d6fadf 100644 --- a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java +++ b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java @@ -1,9 +1,20 @@ package li.cil.oc2.common.entity; +import li.cil.oc2.api.bus.DeviceBusElement; +import li.cil.oc2.api.bus.device.Device; +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.bus.AbstractDeviceBusController; +import li.cil.oc2.common.bus.device.util.Devices; +import li.cil.oc2.common.bus.device.util.ItemDeviceInfo; import li.cil.oc2.common.entity.robot.*; import li.cil.oc2.common.integration.Wrenches; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.RobotBootErrorMessage; +import li.cil.oc2.common.network.message.RobotTerminalOutputMessage; +import li.cil.oc2.common.serialization.NBTSerialization; import li.cil.oc2.common.util.NBTTagIds; import li.cil.oc2.common.util.WorldUtils; +import li.cil.oc2.common.vm.*; import net.minecraft.block.SoundType; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -18,27 +29,37 @@ import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.util.ActionResultType; import net.minecraft.util.Hand; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.ITextComponent; import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.network.NetworkHooks; import javax.annotation.Nullable; -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.Random; +import java.nio.ByteBuffer; +import java.util.*; public final class RobotEntity extends Entity { - public static int MAX_QUEUED_ACTIONS = 16; - + private static final String TERMINAL_TAG_NAME = "terminal"; + private static final String STATE_TAG_NAME = "state"; private static final String COMMAND_PROCESSOR_TAG_NAME = "commands"; private static final DataParameter IS_RUNNING = EntityDataManager.createKey(RobotEntity.class, DataSerializers.BOOLEAN); + private static final int MAX_QUEUED_ACTIONS = 16; + + private static final int MEMORY_SLOTS = 4; + private static final int HARD_DRIVE_SLOTS = 2; + private static final int FLASH_MEMORY_SLOTS = 1; + private static final int CARD_SLOTS = 2; /////////////////////////////////////////////////////////////////// private final AnimationState animationState = new AnimationState(); private final CommandProcessor commandProcessor = new CommandProcessor(); + private final Terminal terminal = new Terminal(); + + private final RobotVirtualMachineState state; + private final RobotItemStackHandlers items = new RobotItemStackHandlers(); /////////////////////////////////////////////////////////////////// @@ -46,6 +67,9 @@ public final class RobotEntity extends Entity { super(type, world); this.preventEntitySpawning = true; setNoGravity(true); + + final RobotBusController busController = new RobotBusController(items.busElement); + state = new RobotVirtualMachineState(busController, new CommonVirtualMachine(busController)); } /////////////////////////////////////////////////////////////////// @@ -55,18 +79,38 @@ public final class RobotEntity extends Entity { return animationState; } + public Terminal getTerminal() { + return terminal; + } + + public VirtualMachineState getState() { + return state; + } + + public CommonVirtualMachineItemStackHandlers getItemHandlers() { + return items; + } + public boolean isRunning() { return dataManager.get(IS_RUNNING); } public void start() { - // todo start vm + final World world = getEntityWorld(); + if (world == null || world.isRemote()) { + return; + } + + state.start(); } public void stop() { - // todo stop vm + final World world = getEntityWorld(); + if (world == null || world.isRemote()) { + return; + } - commandProcessor.clear(); + state.stop(); } @Override @@ -90,10 +134,7 @@ public final class RobotEntity extends Entity { } } else { if (player.isSneaking()) { - // TODO start machine - if (!world.isRemote()) { - dataManager.set(IS_RUNNING, !dataManager.get(IS_RUNNING)); - } + start(); } else { // if (rand.nextBoolean()) { // commandProcessor.move(MovementDirection.values()[rand.nextInt(MovementDirection.values().length)]); @@ -146,13 +187,22 @@ public final class RobotEntity extends Entity { } @Override - protected void writeAdditional(final CompoundNBT compound) { - compound.put(COMMAND_PROCESSOR_TAG_NAME, commandProcessor.serialize()); + protected void writeAdditional(final CompoundNBT tag) { + tag.put(STATE_TAG_NAME, state.serialize()); + tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); + tag.put(COMMAND_PROCESSOR_TAG_NAME, commandProcessor.serialize()); + tag.put(Constants.INVENTORY_TAG_NAME, items.serialize()); } @Override - protected void readAdditional(final CompoundNBT compound) { - commandProcessor.deserialize(compound.getCompound(COMMAND_PROCESSOR_TAG_NAME)); + protected void readAdditional(final CompoundNBT tag) { + state.deserialize(tag.getCompound(STATE_TAG_NAME)); + NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); + commandProcessor.deserialize(tag.getCompound(COMMAND_PROCESSOR_TAG_NAME)); + + if (tag.contains(Constants.INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + items.deserialize(tag.getCompound(Constants.INVENTORY_TAG_NAME)); + } } @Override @@ -301,8 +351,6 @@ public final class RobotEntity extends Entity { } } - /////////////////////////////////////////////////////////////////// - private boolean addAction(final AbstractRobotAction action) { if (getEntityWorld().isRemote()) { return false; @@ -320,4 +368,84 @@ public final class RobotEntity extends Entity { } } } + + private final class RobotItemStackHandlers extends CommonVirtualMachineItemStackHandlers { + public RobotItemStackHandlers() { + super(MEMORY_SLOTS, HARD_DRIVE_SLOTS, FLASH_MEMORY_SLOTS, CARD_SLOTS); + } + + @Override + protected List getDevices(final ItemStack stack) { + return Devices.getDevices(RobotEntity.this, stack); + } + } + + private final class RobotBusController extends AbstractDeviceBusController { + public RobotBusController(final DeviceBusElement root) { + super(root); + } + + @Override + protected void onBeforeScan() { + state.reload(); + state.virtualMachine.rpcAdapter.pause(); + } + + @Override + protected void onAfterDeviceScan(final boolean didDevicesChange) { + state.virtualMachine.rpcAdapter.resume(didDevicesChange); + } + + @Override + protected void onDevicesAdded(final Collection devices) { + state.virtualMachine.vmAdapter.addDevices(devices); + } + + @Override + protected void onDevicesRemoved(final Collection devices) { + state.virtualMachine.vmAdapter.removeDevices(devices); + } + } + + private final class RobotVirtualMachineRunner extends AbstractTerminalVirtualMachineRunner { + public RobotVirtualMachineRunner(final CommonVirtualMachine 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); + } + } + + private final class RobotVirtualMachineState extends AbstractVirtualMachineState { + private RobotVirtualMachineState(final RobotBusController busController, final CommonVirtualMachine virtualMachine) { + super(busController, virtualMachine); + virtualMachine.vmAdapter.setDefaultAddressProvider(items::getDefaultDeviceAddress); + } + + @Override + protected AbstractTerminalVirtualMachineRunner createRunner() { + return new RobotVirtualMachineRunner(virtualMachine, terminal); + } + + @Override + public void stopRunnerAndReset() { + super.stopRunnerAndReset(); + + commandProcessor.clear(); + } + + @Override + protected void handleRunStateChanged(final RunState value) { + dataManager.set(IS_RUNNING, isRunning()); + } + + @Override + protected void handleBootErrorChanged(@Nullable final ITextComponent value) { + final RobotBootErrorMessage message = new RobotBootErrorMessage(RobotEntity.this); + Network.sendToClientsTrackingEntity(message, RobotEntity.this); + } + } } diff --git a/src/main/java/li/cil/oc2/common/item/ItemGroup.java b/src/main/java/li/cil/oc2/common/item/ItemGroup.java index fd60d635..775489bc 100644 --- a/src/main/java/li/cil/oc2/common/item/ItemGroup.java +++ b/src/main/java/li/cil/oc2/common/item/ItemGroup.java @@ -4,8 +4,8 @@ import li.cil.oc2.api.API; import li.cil.oc2.common.Constants; import li.cil.oc2.common.bus.device.data.BaseBlockDevices; import li.cil.oc2.common.bus.device.data.Firmwares; -import li.cil.oc2.common.tileentity.ComputerTileEntity; import li.cil.oc2.common.util.ItemStackUtils; +import li.cil.oc2.common.vm.CommonVirtualMachineItemStackHandlers; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; import net.minecraft.util.NonNullList; @@ -46,18 +46,18 @@ public final class ItemGroup { final ItemStack computer = new ItemStack(Items.COMPUTER_ITEM.get()); final CompoundNBT computerItems = ItemStackUtils.getOrCreateTileEntityInventoryTag(computer); - computerItems.put(ComputerTileEntity.MEMORY_TAG_NAME, makeInventoryTag( + computerItems.put(CommonVirtualMachineItemStackHandlers.MEMORY_TAG_NAME, makeInventoryTag( MemoryItem.withCapacity(8 * Constants.MEGABYTE), MemoryItem.withCapacity(8 * Constants.MEGABYTE), MemoryItem.withCapacity(8 * Constants.MEGABYTE) )); - computerItems.put(ComputerTileEntity.HARD_DRIVE_TAG_NAME, makeInventoryTag( + computerItems.put(CommonVirtualMachineItemStackHandlers.HARD_DRIVE_TAG_NAME, makeInventoryTag( HardDriveItem.withBase(BaseBlockDevices.BUILDROOT.get()) )); - computerItems.put(ComputerTileEntity.FLASH_MEMORY_TAG_NAME, makeInventoryTag( + computerItems.put(CommonVirtualMachineItemStackHandlers.FLASH_MEMORY_TAG_NAME, makeInventoryTag( FlashMemoryItem.withFirmware(Firmwares.BUILDROOT.get()) )); - computerItems.put(ComputerTileEntity.CARD_TAG_NAME, makeInventoryTag( + computerItems.put(CommonVirtualMachineItemStackHandlers.CARD_TAG_NAME, makeInventoryTag( new ItemStack(Items.NETWORK_INTERFACE_CARD_ITEM.get()) )); diff --git a/src/main/java/li/cil/oc2/common/network/MessageUtils.java b/src/main/java/li/cil/oc2/common/network/MessageUtils.java index e8eeb5eb..d7927a37 100644 --- a/src/main/java/li/cil/oc2/common/network/MessageUtils.java +++ b/src/main/java/li/cil/oc2/common/network/MessageUtils.java @@ -3,6 +3,7 @@ package li.cil.oc2.common.network; import li.cil.oc2.common.util.WorldUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; @@ -27,6 +28,20 @@ public final class MessageUtils { } } + @SuppressWarnings("unchecked") + public static void withServerEntity(final Supplier context, final int id, final Class type, final Consumer callback) { + final ServerPlayerEntity player = context.get().getSender(); + if (player == null) { + return; + } + + final ServerWorld world = player.getServerWorld(); + final Entity entity = world.getEntityByID(id); + if (type.isInstance(entity)) { + callback.accept((T) entity); + } + } + @SuppressWarnings("unchecked") public static void withClientTileEntityAt(final BlockPos pos, final Class type, final Consumer callback) { final ClientWorld world = Minecraft.getInstance().world; @@ -39,4 +54,17 @@ public final class MessageUtils { callback.accept((T) tileEntity); } } + + @SuppressWarnings("unchecked") + public static void withClientEntity(final int id, final Class type, final Consumer callback) { + final ClientWorld world = Minecraft.getInstance().world; + if (world == null) { + return; + } + + final Entity entity = world.getEntityByID(id); + if (type.isInstance(entity)) { + callback.accept((T) entity); + } + } } diff --git a/src/main/java/li/cil/oc2/common/network/Network.java b/src/main/java/li/cil/oc2/common/network/Network.java index e0ac7745..56aef763 100644 --- a/src/main/java/li/cil/oc2/common/network/Network.java +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -2,6 +2,7 @@ package li.cil.oc2.common.network; import li.cil.oc2.api.API; import li.cil.oc2.common.network.message.*; +import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; import net.minecraft.world.chunk.Chunk; import net.minecraftforge.fml.network.NetworkDirection; @@ -26,16 +27,16 @@ public final class Network { /////////////////////////////////////////////////////////////////// public static void setup() { - INSTANCE.messageBuilder(TerminalBlockOutputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) - .encoder(TerminalBlockOutputMessage::toBytes) - .decoder(TerminalBlockOutputMessage::new) - .consumer(TerminalBlockOutputMessage::handleMessage) + INSTANCE.messageBuilder(ComputerTerminalOutputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(ComputerTerminalOutputMessage::toBytes) + .decoder(ComputerTerminalOutputMessage::new) + .consumer(ComputerTerminalOutputMessage::handleMessage) .add(); - INSTANCE.messageBuilder(TerminalBlockInputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_SERVER) - .encoder(TerminalBlockInputMessage::toBytes) - .decoder(TerminalBlockInputMessage::new) - .consumer(TerminalBlockInputMessage::handleMessage) + INSTANCE.messageBuilder(ComputerTerminalInputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(ComputerTerminalInputMessage::toBytes) + .decoder(ComputerTerminalInputMessage::new) + .consumer(ComputerTerminalInputMessage::handleMessage) .add(); INSTANCE.messageBuilder(ComputerRunStateMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) @@ -67,12 +68,34 @@ public final class Network { .decoder(NetworkConnectorConnectionsMessage::new) .consumer(NetworkConnectorConnectionsMessage::handleMessage) .add(); + + INSTANCE.messageBuilder(RobotTerminalOutputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(RobotTerminalOutputMessage::toBytes) + .decoder(RobotTerminalOutputMessage::new) + .consumer(RobotTerminalOutputMessage::handleMessage) + .add(); + + INSTANCE.messageBuilder(RobotTerminalInputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(RobotTerminalInputMessage::toBytes) + .decoder(RobotTerminalInputMessage::new) + .consumer(RobotTerminalInputMessage::handleMessage) + .add(); + + INSTANCE.messageBuilder(RobotBootErrorMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(RobotBootErrorMessage::toBytes) + .decoder(RobotBootErrorMessage::new) + .consumer(RobotBootErrorMessage::handleMessage) + .add(); } public static void sendToClientsTrackingChunk(final T message, final Chunk chunk) { Network.INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), message); } + public static void sendToClientsTrackingEntity(final T message, final Entity entity) { + Network.INSTANCE.send(PacketDistributor.TRACKING_ENTITY.with(() -> entity), message); + } + /////////////////////////////////////////////////////////////////// private static int getNextPacketId() { diff --git a/src/main/java/li/cil/oc2/common/network/message/AbstractTerminalEntityMessage.java b/src/main/java/li/cil/oc2/common/network/message/AbstractTerminalEntityMessage.java new file mode 100644 index 00000000..3089a61e --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/AbstractTerminalEntityMessage.java @@ -0,0 +1,34 @@ +package li.cil.oc2.common.network.message; + +import net.minecraft.entity.Entity; +import net.minecraft.network.PacketBuffer; + +import java.nio.ByteBuffer; + +public abstract class AbstractTerminalEntityMessage { + protected int entityId; + protected byte[] data; + + /////////////////////////////////////////////////////////////////// + + protected AbstractTerminalEntityMessage(final Entity entity, final ByteBuffer data) { + this.entityId = entity.getEntityId(); + this.data = data.array(); + } + + protected AbstractTerminalEntityMessage(final PacketBuffer buffer) { + fromBytes(buffer); + } + + /////////////////////////////////////////////////////////////////// + + public void fromBytes(final PacketBuffer buffer) { + entityId = buffer.readVarInt(); + data = buffer.readByteArray(); + } + + public static void toBytes(final AbstractTerminalEntityMessage message, final PacketBuffer buffer) { + buffer.writeVarInt(message.entityId); + buffer.writeByteArray(message.data); + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java index b7e74d40..a37d0d07 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java @@ -17,7 +17,7 @@ public final class ComputerBootErrorMessage { public ComputerBootErrorMessage(final ComputerTileEntity tileEntity) { this.pos = tileEntity.getPos(); - this.value = tileEntity.getBootError(); + this.value = tileEntity.getState().getBootError(); } public ComputerBootErrorMessage(final PacketBuffer buffer) { @@ -28,7 +28,7 @@ public final class ComputerBootErrorMessage { public static boolean handleMessage(final ComputerBootErrorMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class, - (tileEntity) -> tileEntity.setBootErrorClient(message.value))); + (tileEntity) -> tileEntity.getState().setBootErrorClient(message.value))); return true; } diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java index e9bc0a77..6a6b9c8c 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java @@ -17,7 +17,7 @@ public final class ComputerBusStateMessage { public ComputerBusStateMessage(final ComputerTileEntity tileEntity) { this.pos = tileEntity.getPos(); - this.busState = tileEntity.getBusState(); + this.busState = tileEntity.getState().getBusState(); } public ComputerBusStateMessage(final PacketBuffer buffer) { @@ -28,7 +28,7 @@ public final class ComputerBusStateMessage { public static boolean handleMessage(final ComputerBusStateMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class, - (tileEntity) -> tileEntity.setBusStateClient(message.busState))); + (tileEntity) -> tileEntity.getState().setBusStateClient(message.busState))); return true; } diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java index 722c5405..5ca26e32 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java @@ -2,6 +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 net.minecraft.network.PacketBuffer; import net.minecraft.util.math.BlockPos; import net.minecraftforge.fml.network.NetworkEvent; @@ -10,13 +11,13 @@ import java.util.function.Supplier; public final class ComputerRunStateMessage { private BlockPos pos; - private ComputerTileEntity.RunState runState; + private VirtualMachineState.RunState runState; /////////////////////////////////////////////////////////////////// public ComputerRunStateMessage(final ComputerTileEntity tileEntity) { this.pos = tileEntity.getPos(); - this.runState = tileEntity.getRunState(); + this.runState = tileEntity.getState().getRunState(); } public ComputerRunStateMessage(final PacketBuffer buffer) { @@ -27,13 +28,13 @@ public final class ComputerRunStateMessage { public static boolean handleMessage(final ComputerRunStateMessage message, final Supplier context) { context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class, - (tileEntity) -> tileEntity.setRunStateClient(message.runState))); + (tileEntity) -> tileEntity.getState().setRunStateClient(message.runState))); return true; } public void fromBytes(final PacketBuffer buffer) { pos = buffer.readBlockPos(); - runState = buffer.readEnumValue(ComputerTileEntity.RunState.class); + runState = buffer.readEnumValue(VirtualMachineState.RunState.class); } public static void toBytes(final ComputerRunStateMessage message, final PacketBuffer buffer) { diff --git a/src/main/java/li/cil/oc2/common/network/message/TerminalBlockInputMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerTerminalInputMessage.java similarity index 76% rename from src/main/java/li/cil/oc2/common/network/message/TerminalBlockInputMessage.java rename to src/main/java/li/cil/oc2/common/network/message/ComputerTerminalInputMessage.java index 280f32eb..90d82538 100644 --- a/src/main/java/li/cil/oc2/common/network/message/TerminalBlockInputMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerTerminalInputMessage.java @@ -8,12 +8,12 @@ import net.minecraftforge.fml.network.NetworkEvent; import java.nio.ByteBuffer; import java.util.function.Supplier; -public final class TerminalBlockInputMessage extends AbstractTerminalBlockMessage { - public TerminalBlockInputMessage(final ComputerTileEntity tileEntity, final ByteBuffer data) { +public final class ComputerTerminalInputMessage extends AbstractTerminalBlockMessage { + public ComputerTerminalInputMessage(final ComputerTileEntity tileEntity, final ByteBuffer data) { super(tileEntity, data); } - public TerminalBlockInputMessage(final PacketBuffer buffer) { + public ComputerTerminalInputMessage(final PacketBuffer buffer) { super(buffer); } diff --git a/src/main/java/li/cil/oc2/common/network/message/TerminalBlockOutputMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerTerminalOutputMessage.java similarity index 75% rename from src/main/java/li/cil/oc2/common/network/message/TerminalBlockOutputMessage.java rename to src/main/java/li/cil/oc2/common/network/message/ComputerTerminalOutputMessage.java index 71e1db26..4acaa6d7 100644 --- a/src/main/java/li/cil/oc2/common/network/message/TerminalBlockOutputMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerTerminalOutputMessage.java @@ -8,12 +8,12 @@ import net.minecraftforge.fml.network.NetworkEvent; import java.nio.ByteBuffer; import java.util.function.Supplier; -public final class TerminalBlockOutputMessage extends AbstractTerminalBlockMessage { - public TerminalBlockOutputMessage(final ComputerTileEntity tileEntity, final ByteBuffer data) { +public final class ComputerTerminalOutputMessage extends AbstractTerminalBlockMessage { + public ComputerTerminalOutputMessage(final ComputerTileEntity tileEntity, final ByteBuffer data) { super(tileEntity, data); } - public TerminalBlockOutputMessage(final PacketBuffer buffer) { + public ComputerTerminalOutputMessage(final PacketBuffer buffer) { super(buffer); } diff --git a/src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java b/src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java new file mode 100644 index 00000000..e1e7e555 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/RobotBootErrorMessage.java @@ -0,0 +1,43 @@ +package li.cil.oc2.common.network.message; + +import li.cil.oc2.common.entity.RobotEntity; +import li.cil.oc2.common.network.MessageUtils; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.text.ITextComponent; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public final class RobotBootErrorMessage { + private int entityId; + private ITextComponent value; + + /////////////////////////////////////////////////////////////////// + + public RobotBootErrorMessage(final RobotEntity robot) { + this.entityId = robot.getEntityId(); + this.value = robot.getState().getBootError(); + } + + public RobotBootErrorMessage(final PacketBuffer buffer) { + fromBytes(buffer); + } + + /////////////////////////////////////////////////////////////////// + + public static boolean handleMessage(final RobotBootErrorMessage message, final Supplier context) { + context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class, + (robot) -> robot.getState().setBootErrorClient(message.value))); + return true; + } + + public void fromBytes(final PacketBuffer buffer) { + entityId = buffer.readVarInt(); + value = buffer.readTextComponent(); + } + + public static void toBytes(final RobotBootErrorMessage message, final PacketBuffer buffer) { + buffer.writeVarInt(message.entityId); + buffer.writeTextComponent(message.value); + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/RobotTerminalInputMessage.java b/src/main/java/li/cil/oc2/common/network/message/RobotTerminalInputMessage.java new file mode 100644 index 00000000..52578804 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/RobotTerminalInputMessage.java @@ -0,0 +1,27 @@ +package li.cil.oc2.common.network.message; + +import li.cil.oc2.common.entity.RobotEntity; +import li.cil.oc2.common.network.MessageUtils; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.nio.ByteBuffer; +import java.util.function.Supplier; + +public final class RobotTerminalInputMessage extends AbstractTerminalEntityMessage { + public RobotTerminalInputMessage(final RobotEntity robot, final ByteBuffer data) { + super(robot, data); + } + + public RobotTerminalInputMessage(final PacketBuffer buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + public static boolean handleMessage(final AbstractTerminalEntityMessage message, final Supplier context) { + context.get().enqueueWork(() -> MessageUtils.withServerEntity(context, message.entityId, RobotEntity.class, + (tileEntity) -> tileEntity.getTerminal().putInput(ByteBuffer.wrap(message.data)))); + return true; + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/RobotTerminalOutputMessage.java b/src/main/java/li/cil/oc2/common/network/message/RobotTerminalOutputMessage.java new file mode 100644 index 00000000..69cecb78 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/RobotTerminalOutputMessage.java @@ -0,0 +1,27 @@ +package li.cil.oc2.common.network.message; + +import li.cil.oc2.common.entity.RobotEntity; +import li.cil.oc2.common.network.MessageUtils; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.nio.ByteBuffer; +import java.util.function.Supplier; + +public final class RobotTerminalOutputMessage extends AbstractTerminalEntityMessage { + public RobotTerminalOutputMessage(final RobotEntity robot, final ByteBuffer data) { + super(robot, data); + } + + public RobotTerminalOutputMessage(final PacketBuffer buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + public static boolean handleMessage(final AbstractTerminalEntityMessage message, final Supplier context) { + context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class, + robot -> robot.getTerminal().putOutput(ByteBuffer.wrap(message.data)))); + return true; + } +} diff --git a/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java index 7040099c..f510a9d7 100644 --- a/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java +++ b/src/main/java/li/cil/oc2/common/tileentity/ComputerTileEntity.java @@ -2,11 +2,6 @@ package li.cil.oc2.common.tileentity; import li.cil.oc2.api.bus.DeviceBusElement; import li.cil.oc2.api.bus.device.Device; -import li.cil.oc2.api.bus.device.DeviceType; -import li.cil.oc2.api.bus.device.DeviceTypes; -import li.cil.oc2.api.bus.device.vm.VMDevice; -import li.cil.oc2.api.bus.device.vm.VMDeviceLifecycleEventType; -import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; import li.cil.oc2.common.Constants; import li.cil.oc2.common.block.ComputerBlock; import li.cil.oc2.common.bus.AbstractDeviceBusController; @@ -17,115 +12,56 @@ import li.cil.oc2.common.bus.device.util.Devices; import li.cil.oc2.common.bus.device.util.ItemDeviceInfo; import li.cil.oc2.common.capabilities.Capabilities; import li.cil.oc2.common.container.DeviceItemStackHandler; -import li.cil.oc2.common.container.TypedDeviceItemStackHandler; import li.cil.oc2.common.network.Network; import li.cil.oc2.common.network.message.ComputerBootErrorMessage; import li.cil.oc2.common.network.message.ComputerBusStateMessage; import li.cil.oc2.common.network.message.ComputerRunStateMessage; -import li.cil.oc2.common.network.message.TerminalBlockOutputMessage; +import li.cil.oc2.common.network.message.ComputerTerminalOutputMessage; 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.NBTUtils; -import li.cil.oc2.common.vm.AbstractTerminalVirtualMachineRunner; -import li.cil.oc2.common.vm.CommonVirtualMachine; -import li.cil.oc2.common.vm.Terminal; -import li.cil.sedna.api.memory.MemoryAccessException; +import li.cil.oc2.common.vm.*; import net.minecraft.block.BlockState; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; import net.minecraft.tileentity.ITickableTileEntity; import net.minecraft.util.Direction; import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.TranslationTextComponent; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.items.IItemHandler; -import net.minecraftforge.items.wrapper.CombinedInvWrapper; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import javax.annotation.Nullable; import java.nio.ByteBuffer; -import java.util.*; -import java.util.function.Function; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; public final class ComputerTileEntity extends AbstractTileEntity implements ITickableTileEntity { - private static final Logger LOGGER = LogManager.getLogger(); - - /////////////////////////////////////////////////////////////////// - - public static final String MEMORY_TAG_NAME = "memory"; - public static final String HARD_DRIVE_TAG_NAME = "hard_drive"; - public static final String FLASH_MEMORY_TAG_NAME = "flash_memory"; - public static final String CARD_TAG_NAME = "card"; - private static final String BUS_ELEMENT_TAG_NAME = "busElement"; private static final String TERMINAL_TAG_NAME = "terminal"; - private static final String VIRTUAL_MACHINE_TAG_NAME = "virtualMachine"; - private static final String RUNNER_TAG_NAME = "runner"; - - private static final String BUS_STATE_TAG_NAME = "busState"; - private static final String RUN_STATE_TAG_NAME = "runState"; - private static final String BOOT_ERROR_TAG_NAME = "bootError"; - - private static final int DEVICE_LOAD_RETRY_INTERVAL = 10 * Constants.TICK_SECONDS; + private static final String STATE_TAG_NAME = "state"; private static final int MEMORY_SLOTS = 4; private static final int HARD_DRIVE_SLOTS = 4; private static final int FLASH_MEMORY_SLOTS = 1; private static final int CARD_SLOTS = 4; - private static final long ITEM_DEVICE_BASE_ADDRESS = 0x40000000L; - private static final int ITEM_DEVICE_STRIDE = 0x1000; - - 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 enum RunState { - STOPPED, - LOADING_DEVICES, - RUNNING, - } - - /////////////////////////////////////////////////////////////////// - - private Chunk chunk; - private final AbstractDeviceBusController busController; - private AbstractDeviceBusController.BusState busState; - private RunState runState; - private ITextComponent bootError; - private int loadDevicesDelay; private boolean hasAddedOwnDevices; private boolean isNeighborUpdateScheduled; /////////////////////////////////////////////////////////////////// - private final DeviceItemStackHandler memoryItemHandler = new ComputerItemHandler(MEMORY_SLOTS, this::getDevices, DeviceTypes.MEMORY); - private final DeviceItemStackHandler hardDriveItemHandler = new ComputerItemHandler(HARD_DRIVE_SLOTS, this::getDevices, DeviceTypes.HARD_DRIVE); - private final DeviceItemStackHandler flashMemoryItemHandler = new ComputerItemHandler(FLASH_MEMORY_SLOTS, this::getDevices, DeviceTypes.FLASH_MEMORY); - private final DeviceItemStackHandler cardItemHandler = new ComputerItemHandler(CARD_SLOTS, this::getDevices, DeviceTypes.CARD); - - private final IItemHandler itemHandlers = new CombinedInvWrapper(memoryItemHandler, hardDriveItemHandler, flashMemoryItemHandler, cardItemHandler); - private final Terminal terminal = new Terminal(); private final TileEntityDeviceBusElement busElement = new ComputerBusElement(); - private final CommonVirtualMachine virtualMachine; - private ComputerVirtualMachineRunner runner; + + private final ComputerVirtualMachineState state; + private final ComputerItemStackHandlers items = new ComputerItemStackHandlers(); /////////////////////////////////////////////////////////////////// @@ -135,124 +71,42 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic // We want to unload devices even on world unload to free global resources. setNeedsWorldUnloadEvent(); - busController = new ComputerBusController(busElement); - busState = AbstractDeviceBusController.BusState.SCAN_PENDING; - runState = RunState.STOPPED; - - virtualMachine = new CommonVirtualMachine(busController); - virtualMachine.vmAdapter.setDefaultAddressProvider(this::getDefaultDeviceAddress); - } - - public Optional getItemHandler(final DeviceType deviceType) { - if (deviceType == DeviceTypes.MEMORY) { - return Optional.of(memoryItemHandler); - } else if (deviceType == DeviceTypes.HARD_DRIVE) { - return Optional.of(hardDriveItemHandler); - } else if (deviceType == DeviceTypes.FLASH_MEMORY) { - return Optional.of(flashMemoryItemHandler); - } else if (deviceType == DeviceTypes.CARD) { - return Optional.of(cardItemHandler); - } - return Optional.empty(); + final ComputerBusController busController = new ComputerBusController(busElement); + state = new ComputerVirtualMachineState(busController, new CommonVirtualMachine(busController)); } public Terminal getTerminal() { return terminal; } - public void start() { - if (runState == RunState.RUNNING) { - return; - } + public VirtualMachineState getState() { + return state; + } + public CommonVirtualMachineItemStackHandlers getItemHandlers() { + return items; + } + + public void start() { final World world = getWorld(); if (world == null || world.isRemote()) { return; } - setBootError(null); - setRunState(RunState.LOADING_DEVICES); - loadDevicesDelay = 0; + state.start(); } public void stop() { - if (runState == RunState.STOPPED) { - return; - } - final World world = getWorld(); if (world == null || world.isRemote()) { return; } - if (runState == RunState.LOADING_DEVICES) { - setRunState(RunState.STOPPED); - return; - } - - stopRunnerAndReset(); - } - - public boolean isRunning() { - return getBusState() == AbstractDeviceBusController.BusState.READY && - getRunState() == RunState.RUNNING; - } - - public AbstractDeviceBusController.BusState getBusState() { - return busState; - } - - public RunState getRunState() { - return runState; - } - - @Nullable - public ITextComponent getBootError() { - switch (getBusState()) { - case SCAN_PENDING: - case INCOMPLETE: - return new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_INCOMPLETE); - case TOO_COMPLEX: - return new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_TOO_COMPLEX); - case MULTIPLE_CONTROLLERS: - return new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS); - case READY: - switch (getRunState()) { - case STOPPED: - case LOADING_DEVICES: - return bootError; - } - break; - } - return null; + state.stop(); } public void handleNeighborChanged() { - busController.scheduleBusScan(); - } - - @OnlyIn(Dist.CLIENT) - public void setRunStateClient(final RunState value) { - final World world = getWorld(); - if (world != null && world.isRemote()) { - runState = value; - } - } - - @OnlyIn(Dist.CLIENT) - public void setBusStateClient(final AbstractDeviceBusController.BusState value) { - final World world = getWorld(); - if (world != null && world.isRemote()) { - busState = value; - } - } - - @OnlyIn(Dist.CLIENT) - public void setBootErrorClient(final ITextComponent value) { - final World world = getWorld(); - if (world != null && world.isRemote()) { - bootError = value; - } + state.busController.scheduleBusScan(); } @Override @@ -267,7 +121,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic } final Direction localSide = HorizontalBlockUtils.toLocal(getBlockState(), side); - for (final Device device : busController.getDevices()) { + for (final Device device : state.busController.getDevices()) { if (device instanceof ICapabilityProvider) { final LazyOptional value = ((ICapabilityProvider) device).getCapability(capability, localSide); if (value.isPresent()) { @@ -286,10 +140,6 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic return; } - if (chunk == null) { - chunk = world.getChunkAt(getPos()); - } - // Always add devices provided for the computer itself, even if there's no // adjacent cable. Because that would just be weird. if (!hasAddedOwnDevices) { @@ -304,79 +154,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic world.notifyNeighborsOfStateChange(getPos(), getBlockState().getBlock()); } - final AbstractDeviceBusController.BusState oldBusState = busController.getState(); - busController.scan(); - setBusState(busController.getState()); - if (busState != AbstractDeviceBusController.BusState.READY) { - return; - } - - if (oldBusState != AbstractDeviceBusController.BusState.READY) { - // Bus just became ready, meaning new devices may be available, meaning new - // capabilities may be available, so we need to tell our neighbors. - world.notifyNeighborsOfStateChange(getPos(), getBlockState().getBlock()); - } - - switch (runState) { - case STOPPED: - break; - case LOADING_DEVICES: - if (loadDevicesDelay > 0) { - loadDevicesDelay--; - break; - } - - final VMDeviceLoadResult loadResult = virtualMachine.vmAdapter.load(); - if (!loadResult.wasSuccessful()) { - if (loadResult.getErrorMessage() != null) { - setBootError(loadResult.getErrorMessage()); - } else { - setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_UNKNOWN)); - } - loadDevicesDelay = DEVICE_LOAD_RETRY_INTERVAL; - break; - } - - // May have a valid runner after load. In which case we just had to wait for - // 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); - } 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_BOOT_ERROR_NO_MEMORY)); - setRunState(RunState.STOPPED); - return; - } catch (final MemoryAccessException e) { - LOGGER.error(e); - setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_UNKNOWN)); - setRunState(RunState.STOPPED); - return; - } - - runner = new ComputerVirtualMachineRunner(virtualMachine, terminal); - } - - setRunState(RunState.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. - break; - case RUNNING: - if (!virtualMachine.board.isRunning()) { - stopRunnerAndReset(); - break; - } - - runner.tick(); - chunk.markDirty(); - break; - } + state.tick(); } @Override @@ -385,127 +163,68 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic // 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 data. - virtualMachine.vmAdapter.unload(); + state.virtualMachine.vmAdapter.unload(); } @Override public CompoundNBT getUpdateTag() { - final CompoundNBT result = super.getUpdateTag(); + final CompoundNBT tag = super.getUpdateTag(); - result.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); - result.putInt(BUS_STATE_TAG_NAME, busState.ordinal()); - result.putInt(RUN_STATE_TAG_NAME, runState.ordinal()); - result.putString(BOOT_ERROR_TAG_NAME, ITextComponent.Serializer.toJson(bootError)); + 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())); - return result; + return tag; } @Override - public void handleUpdateTag(final BlockState state, final CompoundNBT tag) { - super.handleUpdateTag(state, tag); + public void handleUpdateTag(final BlockState blockState, final CompoundNBT tag) { + super.handleUpdateTag(blockState, tag); NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); - busState = AbstractDeviceBusController.BusState.values()[tag.getInt(BUS_STATE_TAG_NAME)]; - runState = RunState.values()[tag.getInt(RUN_STATE_TAG_NAME)]; - bootError = ITextComponent.Serializer.getComponentFromJson(tag.getString(BOOT_ERROR_TAG_NAME)); + 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))); } @Override public CompoundNBT write(CompoundNBT tag) { tag = super.write(tag); - joinVirtualMachine(); - - if (runner != null) { - tag.put(RUNNER_TAG_NAME, NBTSerialization.serialize(runner)); - virtualMachine.vmAdapter.fireLifecycleEvent(VMDeviceLifecycleEventType.PAUSING); - runner.scheduleResumeEvent(); // Allow synchronizing to async device saves. - } else { - NBTUtils.putEnum(tag, RUN_STATE_TAG_NAME, runState); - } - + tag.put(STATE_TAG_NAME, state.serialize()); tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); - tag.put(BUS_ELEMENT_TAG_NAME, NBTSerialization.serialize(busElement)); - tag.put(VIRTUAL_MACHINE_TAG_NAME, NBTSerialization.serialize(virtualMachine)); - - final CompoundNBT items = new CompoundNBT(); - items.put(MEMORY_TAG_NAME, memoryItemHandler.serializeNBT()); - items.put(HARD_DRIVE_TAG_NAME, hardDriveItemHandler.serializeNBT()); - items.put(FLASH_MEMORY_TAG_NAME, flashMemoryItemHandler.serializeNBT()); - items.put(CARD_TAG_NAME, cardItemHandler.serializeNBT()); - tag.put(Constants.BLOCK_ENTITY_INVENTORY_TAG_NAME, items); + tag.put(Constants.INVENTORY_TAG_NAME, items.serialize()); return tag; } @Override - public void read(final BlockState state, final CompoundNBT tag) { - super.read(state, tag); - - joinVirtualMachine(); + public void read(final BlockState blockState, final CompoundNBT tag) { + super.read(blockState, tag); + state.deserialize(tag.getCompound(STATE_TAG_NAME)); NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); if (tag.contains(BUS_ELEMENT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { NBTSerialization.deserialize(tag.getCompound(BUS_ELEMENT_TAG_NAME), busElement); } - if (tag.contains(VIRTUAL_MACHINE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { - NBTSerialization.deserialize(tag.getCompound(VIRTUAL_MACHINE_TAG_NAME), virtualMachine); + if (tag.contains(Constants.INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + items.deserialize(tag.getCompound(Constants.INVENTORY_TAG_NAME)); } - - if (tag.contains(RUNNER_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { - runner = new ComputerVirtualMachineRunner(virtualMachine, terminal); - NBTSerialization.deserialize(tag.getCompound(RUNNER_TAG_NAME), runner); - runState = RunState.LOADING_DEVICES; - } else { - runState = NBTUtils.getEnum(tag, RUN_STATE_TAG_NAME, RunState.class); - if (runState == null) { - runState = RunState.STOPPED; - } else if (runState == RunState.RUNNING) { - runState = RunState.LOADING_DEVICES; - } - } - - if (tag.contains(Constants.BLOCK_ENTITY_INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { - final CompoundNBT items = tag.getCompound(Constants.BLOCK_ENTITY_INVENTORY_TAG_NAME); - memoryItemHandler.deserializeNBT(items.getCompound(MEMORY_TAG_NAME)); - hardDriveItemHandler.deserializeNBT(items.getCompound(HARD_DRIVE_TAG_NAME)); - flashMemoryItemHandler.deserializeNBT(items.getCompound(FLASH_MEMORY_TAG_NAME)); - cardItemHandler.deserializeNBT(items.getCompound(CARD_TAG_NAME)); - } - } - - public boolean isEmpty() { - for (int slot = 0; slot < itemHandlers.getSlots(); slot++) { - if (!itemHandlers.getStackInSlot(slot).isEmpty()) { - return false; - } - } - return true; - } - - public void exportDeviceDataToItemStacks() { - memoryItemHandler.exportDeviceDataToItemStacks(); - hardDriveItemHandler.exportDeviceDataToItemStacks(); - flashMemoryItemHandler.exportDeviceDataToItemStacks(); - cardItemHandler.exportDeviceDataToItemStacks(); } public void exportToItemStack(final ItemStack stack) { - final CompoundNBT items = ItemStackUtils.getOrCreateTileEntityInventoryTag(stack); - items.put(MEMORY_TAG_NAME, memoryItemHandler.serializeNBT()); - items.put(HARD_DRIVE_TAG_NAME, hardDriveItemHandler.serializeNBT()); - items.put(FLASH_MEMORY_TAG_NAME, flashMemoryItemHandler.serializeNBT()); - items.put(CARD_TAG_NAME, cardItemHandler.serializeNBT()); + items.exportToItemStack(stack); } /////////////////////////////////////////////////////////////////// @Override protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) { - collector.offer(Capabilities.ITEM_HANDLER, itemHandlers); + collector.offer(Capabilities.ITEM_HANDLER, items.itemHandlers); collector.offer(Capabilities.DEVICE_BUS_ELEMENT, busElement); } @@ -521,17 +240,17 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic super.loadServer(); busElement.initialize(); - virtualMachine.rtcMinecraft.setWorld(getWorld()); + state.virtualMachine.rtcMinecraft.setWorld(getWorld()); } @Override protected void unloadServer() { super.unloadServer(); - joinVirtualMachine(); - virtualMachine.vmAdapter.suspend(); + state.joinVirtualMachine(); + state.virtualMachine.vmAdapter.suspend(); - busController.dispose(); + state.busController.dispose(); // 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. @@ -540,106 +259,19 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic /////////////////////////////////////////////////////////////////// - private List getDevices(final ItemStack stack) { - return Devices.getDevices(this, stack); - } - - private void setBusState(final AbstractDeviceBusController.BusState value) { - if (value == busState) { - return; - } - - busState = value; - - final ComputerBusStateMessage message = new ComputerBusStateMessage(this); - Network.sendToClientsTrackingChunk(message, chunk); - } - - private void setRunState(final RunState value) { - if (value == runState) { - return; - } - - runState = 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) { - final ComputerRunStateMessage message = new ComputerRunStateMessage(this); - Network.sendToClientsTrackingChunk(message, chunk); - } - } - - private void setBootError(@Nullable final ITextComponent value) { - if (Objects.equals(value, bootError)) { - return; - } - - bootError = value; - final ComputerBootErrorMessage message = new ComputerBootErrorMessage(this); - Network.sendToClientsTrackingChunk(message, chunk); - } - - private void stopRunnerAndReset() { - joinVirtualMachine(); - setRunState(RunState.STOPPED); - - virtualMachine.reset(); - - TERMINAL_RESET_SEQUENCE.clear(); - runner.putTerminalOutput(TERMINAL_RESET_SEQUENCE); - runner = null; - } - - private void joinVirtualMachine() { - if (runner != null) { - try { - runner.join(); - } catch (final Throwable e) { - LOGGER.error(e); - runner = null; - } - } - } - - private OptionalLong getDefaultDeviceAddress(final VMDevice wrapper) { - long address = ITEM_DEVICE_BASE_ADDRESS; - - for (int slot = 0; slot < hardDriveItemHandler.getSlots(); slot++) { - final Collection devices = hardDriveItemHandler.getBusElement().getDeviceGroup(slot); - for (final ItemDeviceInfo info : devices) { - if (Objects.equals(info.device, wrapper)) { - return OptionalLong.of(address); - } - } - - address += ITEM_DEVICE_STRIDE; - } - - for (int slot = 0; slot < cardItemHandler.getSlots(); slot++) { - final Collection devices = cardItemHandler.getBusElement().getDeviceGroup(slot); - for (final ItemDeviceInfo info : devices) { - if (Objects.equals(info.device, wrapper)) { - return OptionalLong.of(address); - } - } - - address += ITEM_DEVICE_STRIDE; - } - - return OptionalLong.empty(); - } - - /////////////////////////////////////////////////////////////////// - - private final class ComputerItemHandler extends TypedDeviceItemStackHandler { - public ComputerItemHandler(final int size, final Function> deviceLookup, final DeviceType deviceType) { - super(size, deviceLookup, deviceType); + private final class ComputerItemStackHandlers extends CommonVirtualMachineItemStackHandlers { + public ComputerItemStackHandlers() { + super(MEMORY_SLOTS, HARD_DRIVE_SLOTS, FLASH_MEMORY_SLOTS, CARD_SLOTS); } @Override - protected void onContentsChanged(final int slot) { - super.onContentsChanged(slot); + protected List getDevices(final ItemStack stack) { + return Devices.getDevices(ComputerTileEntity.this, stack); + } + + @Override + protected void onContentsChanged(final DeviceItemStackHandler itemStackHandler, final int slot) { + super.onContentsChanged(itemStackHandler, slot); markDirty(); isNeighborUpdateScheduled = true; } @@ -652,26 +284,23 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic @Override protected void onBeforeScan() { - if (runState == RunState.RUNNING) { - runState = RunState.LOADING_DEVICES; - } - - virtualMachine.rpcAdapter.pause(); + state.reload(); + state.virtualMachine.rpcAdapter.pause(); } @Override protected void onAfterDeviceScan(final boolean didDevicesChange) { - virtualMachine.rpcAdapter.resume(didDevicesChange); + state.virtualMachine.rpcAdapter.resume(didDevicesChange); } @Override protected void onDevicesAdded(final Collection devices) { - virtualMachine.vmAdapter.addDevices(devices); + state.virtualMachine.vmAdapter.addDevices(devices); } @Override protected void onDevicesRemoved(final Collection devices) { - virtualMachine.vmAdapter.removeDevices(devices); + state.virtualMachine.vmAdapter.removeDevices(devices); } } @@ -684,10 +313,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic public Optional>> getNeighbors() { return super.getNeighbors().map(neighbors -> { final ArrayList> list = new ArrayList<>(neighbors); - list.add(LazyOptional.of(flashMemoryItemHandler::getBusElement)); - list.add(LazyOptional.of(memoryItemHandler::getBusElement)); - list.add(LazyOptional.of(hardDriveItemHandler::getBusElement)); - list.add(LazyOptional.of(cardItemHandler::getBusElement)); + list.add(LazyOptional.of(() -> items.busElement)); return list; }); } @@ -705,7 +331,62 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic @Override protected void sendTerminalUpdateToClient(final ByteBuffer output) { - final TerminalBlockOutputMessage message = new TerminalBlockOutputMessage(ComputerTileEntity.this, output); + final ComputerTerminalOutputMessage message = new ComputerTerminalOutputMessage(ComputerTileEntity.this, output); + Network.sendToClientsTrackingChunk(message, state.chunk); + } + } + + private final class ComputerVirtualMachineState extends AbstractVirtualMachineState { + private Chunk chunk; + + private ComputerVirtualMachineState(final ComputerBusController busController, final CommonVirtualMachine virtualMachine) { + super(busController, virtualMachine); + virtualMachine.vmAdapter.setDefaultAddressProvider(items::getDefaultDeviceAddress); + } + + @Override + public void tick() { + if (chunk == null) { + chunk = world.getChunkAt(getPos()); + } + + if (isRunning()) { + chunk.markDirty(); + } + + super.tick(); + } + + @Override + protected AbstractTerminalVirtualMachineRunner createRunner() { + return new ComputerVirtualMachineRunner(virtualMachine, terminal); + } + + @Override + protected void handleBusStateChanged(final AbstractDeviceBusController.BusState value) { + final ComputerBusStateMessage message = new ComputerBusStateMessage(ComputerTileEntity.this); + Network.sendToClientsTrackingChunk(message, chunk); + + if (value == AbstractDeviceBusController.BusState.READY) { + // Bus just became ready, meaning new devices may be available, meaning new + // capabilities may be available, so we need to tell our neighbors. + world.notifyNeighborsOfStateChange(getPos(), getBlockState().getBlock()); + } + } + + @Override + protected void handleRunStateChanged(final RunState 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) { + final ComputerRunStateMessage message = new ComputerRunStateMessage(ComputerTileEntity.this); + Network.sendToClientsTrackingChunk(message, chunk); + } + } + + @Override + protected void handleBootErrorChanged(@Nullable final ITextComponent value) { + final ComputerBootErrorMessage message = new ComputerBootErrorMessage(ComputerTileEntity.this); Network.sendToClientsTrackingChunk(message, chunk); } } diff --git a/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java b/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java index 63c26c63..09d4cd44 100644 --- a/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java +++ b/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java @@ -13,7 +13,7 @@ import javax.annotation.Nullable; import java.util.Optional; import java.util.Random; -import static li.cil.oc2.common.Constants.BLOCK_ENTITY_INVENTORY_TAG_NAME; +import static li.cil.oc2.common.Constants.INVENTORY_TAG_NAME; import static li.cil.oc2.common.Constants.BLOCK_ENTITY_TAG_NAME_IN_ITEM; public final class ItemStackUtils { @@ -48,19 +48,19 @@ public final class ItemStackUtils { @Nullable public static CompoundNBT getTileEntityInventoryTag(final ItemStack stack) { final CompoundNBT tag = getTileEntityTag(stack); - return tag != null && tag.contains(BLOCK_ENTITY_INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND) - ? tag.getCompound(BLOCK_ENTITY_INVENTORY_TAG_NAME) : null; + return tag != null && tag.contains(INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND) + ? tag.getCompound(INVENTORY_TAG_NAME) : null; } @Nullable public static CompoundNBT getOrCreateTileEntityInventoryTag(final ItemStack stack) { final CompoundNBT tag = getOrCreateTileEntityTag(stack); - if (tag.contains(BLOCK_ENTITY_INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { - return tag.getCompound(BLOCK_ENTITY_INVENTORY_TAG_NAME); + if (tag.contains(INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + return tag.getCompound(INVENTORY_TAG_NAME); } final CompoundNBT inventoryNbt = new CompoundNBT(); - tag.put(BLOCK_ENTITY_INVENTORY_TAG_NAME, inventoryNbt); + tag.put(INVENTORY_TAG_NAME, inventoryNbt); return inventoryNbt; } diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java b/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java index b918bc36..07134888 100644 --- a/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java +++ b/src/main/java/li/cil/oc2/common/vm/AbstractTerminalVirtualMachineRunner.java @@ -7,6 +7,15 @@ import li.cil.oc2.api.bus.device.vm.VMDeviceLifecycleEventType; 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; @@ -33,6 +42,11 @@ public abstract class AbstractTerminalVirtualMachineRunner extends VirtualMachin /////////////////////////////////////////////////////////////////// + public void resetTerminal() { + TERMINAL_RESET_SEQUENCE.clear(); + putTerminalOutput(TERMINAL_RESET_SEQUENCE); + } + public void putTerminalOutput(final ByteBuffer output) { if (output.hasRemaining()) { terminal.putOutput(output); diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java new file mode 100644 index 00000000..3c1e04ef --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachineState.java @@ -0,0 +1,317 @@ +package li.cil.oc2.common.vm; + +import li.cil.oc2.api.bus.device.vm.VMDeviceLifecycleEventType; +import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.bus.AbstractDeviceBusController; +import li.cil.oc2.common.serialization.NBTSerialization; +import li.cil.oc2.common.util.NBTTagIds; +import li.cil.oc2.common.util.NBTUtils; +import li.cil.sedna.api.memory.MemoryAccessException; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import java.util.Objects; + +public abstract class AbstractVirtualMachineState implements VirtualMachineState { + private static final Logger LOGGER = LogManager.getLogger(); + + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + + private static final String VIRTUAL_MACHINE_TAG_NAME = "virtualMachine"; + private static final String RUNNER_TAG_NAME = "runner"; + + public static final String BUS_STATE_TAG_NAME = "busState"; + public static final String RUN_STATE_TAG_NAME = "runState"; + public static final String BOOT_ERROR_TAG_NAME = "bootError"; + + private static final int DEVICE_LOAD_RETRY_INTERVAL = 10 * Constants.TICK_SECONDS; + + /////////////////////////////////////////////////////////////////// + + public final TBusController 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; + + /////////////////////////////////////////////////////////////////// + + public AbstractVirtualMachineState(final TBusController busController, final TVirtualMachine virtualMachine) { + this.busController = busController; + this.virtualMachine = virtualMachine; + + } + + /////////////////////////////////////////////////////////////////// + + @Override + public boolean isRunning() { + return getBusState() == AbstractDeviceBusController.BusState.READY && + getRunState() == RunState.RUNNING; + } + + @Override + public AbstractDeviceBusController.BusState getBusState() { + return busState; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void setBusStateClient(final AbstractDeviceBusController.BusState value) { + busState = value; + } + + @Override + public RunState getRunState() { + return runState; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void setRunStateClient(final RunState value) { + runState = value; + } + + @Override + @Nullable + public ITextComponent getBootError() { + switch (busState) { + case SCAN_PENDING: + case INCOMPLETE: + return new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_INCOMPLETE); + case TOO_COMPLEX: + return new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_TOO_COMPLEX); + case MULTIPLE_CONTROLLERS: + return new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS); + case READY: + switch (runState) { + case STOPPED: + case LOADING_DEVICES: + return bootError; + } + break; + } + return null; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void setBootErrorClient(final ITextComponent value) { + bootError = value; + } + + @Override + public void start() { + if (runState == RunState.RUNNING) { + return; + } + + setBootError(null); + setRunState(RunState.LOADING_DEVICES); + loadDevicesDelay = 0; + } + + @Override + public void stop() { + switch (runState) { + case LOADING_DEVICES: + setRunState(RunState.STOPPED); + break; + case RUNNING: + stopRunnerAndReset(); + break; + } + } + + public void reload() { + if (runState == RunState.RUNNING) { + runState = RunState.LOADING_DEVICES; + } + } + + public void joinVirtualMachine() { + if (runner != null) { + try { + runner.join(); + } catch (final Throwable e) { + LOGGER.error(e); + runner = null; + } + } + } + + public void stopRunnerAndReset() { + joinVirtualMachine(); + setRunState(RunState.STOPPED); + + virtualMachine.reset(); + + if (runner != null) { + runner.resetTerminal(); + runner = null; + } + } + + public void tick() { + busController.scan(); + setBusState(busController.getState()); + if (busState != AbstractDeviceBusController.BusState.READY) { + return; + } + + switch (runState) { + case STOPPED: + break; + case LOADING_DEVICES: + if (loadDevicesDelay > 0) { + loadDevicesDelay--; + break; + } + + final VMDeviceLoadResult loadResult = virtualMachine.vmAdapter.load(); + if (!loadResult.wasSuccessful()) { + if (loadResult.getErrorMessage() != null) { + setBootError(loadResult.getErrorMessage()); + } else { + setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_UNKNOWN)); + } + loadDevicesDelay = DEVICE_LOAD_RETRY_INTERVAL; + break; + } + + // May have a valid runner after load. In which case we just had to wait for + // 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); + } 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_BOOT_ERROR_NO_MEMORY)); + setRunState(RunState.STOPPED); + return; + } catch (final MemoryAccessException e) { + LOGGER.error(e); + setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_UNKNOWN)); + setRunState(RunState.STOPPED); + return; + } + + runner = createRunner(); + } + + setRunState(RunState.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. + break; + case RUNNING: + if (!virtualMachine.board.isRunning()) { + stopRunnerAndReset(); + break; + } + + runner.tick(); + break; + } + } + + public CompoundNBT serialize() { + final CompoundNBT tag = new CompoundNBT(); + + joinVirtualMachine(); + + if (runner != null) { + tag.put(RUNNER_TAG_NAME, NBTSerialization.serialize(runner)); + virtualMachine.vmAdapter.fireLifecycleEvent(VMDeviceLifecycleEventType.PAUSING); + 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)); + + return tag; + } + + public void deserialize(final CompoundNBT tag) { + joinVirtualMachine(); + + if (tag.contains(RUNNER_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + runner = createRunner(); + NBTSerialization.deserialize(tag.getCompound(RUNNER_TAG_NAME), runner); + runState = RunState.LOADING_DEVICES; + } else { + runState = NBTUtils.getEnum(tag, RUN_STATE_TAG_NAME, RunState.class); + if (runState == null) { + runState = RunState.STOPPED; + } else if (runState == RunState.RUNNING) { + runState = RunState.LOADING_DEVICES; + } + } + + if (tag.contains(VIRTUAL_MACHINE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + NBTSerialization.deserialize(tag.getCompound(VIRTUAL_MACHINE_TAG_NAME), virtualMachine); + } + } + + /////////////////////////////////////////////////////////////////// + + protected abstract AbstractTerminalVirtualMachineRunner createRunner(); + + protected void handleBusStateChanged(final AbstractDeviceBusController.BusState value) { + } + + protected void handleRunStateChanged(final RunState value) { + } + + protected void handleBootErrorChanged(@Nullable final ITextComponent value) { + } + + /////////////////////////////////////////////////////////////////// + + private void setBusState(final AbstractDeviceBusController.BusState value) { + if (value == busState) { + return; + } + + busState = value; + + handleBusStateChanged(busState); + } + + private void setRunState(final RunState value) { + if (value == runState) { + return; + } + + runState = value; + + handleRunStateChanged(value); + } + + private void setBootError(@Nullable final ITextComponent value) { + if (Objects.equals(value, bootError)) { + return; + } + + bootError = value; + + handleBootErrorChanged(value); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/CommonVirtualMachineItemStackHandlers.java b/src/main/java/li/cil/oc2/common/vm/CommonVirtualMachineItemStackHandlers.java new file mode 100644 index 00000000..a2c3eb39 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/CommonVirtualMachineItemStackHandlers.java @@ -0,0 +1,184 @@ +package li.cil.oc2.common.vm; + +import li.cil.oc2.api.bus.DeviceBusElement; +import li.cil.oc2.api.bus.device.DeviceType; +import li.cil.oc2.api.bus.device.DeviceTypes; +import li.cil.oc2.api.bus.device.vm.VMDevice; +import li.cil.oc2.common.bus.AbstractDeviceBusElement; +import li.cil.oc2.common.bus.device.util.ItemDeviceInfo; +import li.cil.oc2.common.container.DeviceItemStackHandler; +import li.cil.oc2.common.container.TypedDeviceItemStackHandler; +import li.cil.oc2.common.util.ItemStackUtils; +import li.cil.oc2.common.util.TooltipUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.text.ITextComponent; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.wrapper.CombinedInvWrapper; + +import java.util.*; +import java.util.function.Function; + +public abstract class CommonVirtualMachineItemStackHandlers { + private static final long ITEM_DEVICE_BASE_ADDRESS = 0x40000000L; + private static final int ITEM_DEVICE_STRIDE = 0x1000; + + public static final String MEMORY_TAG_NAME = "memory"; + public static final String HARD_DRIVE_TAG_NAME = "hard_drive"; + public static final String FLASH_MEMORY_TAG_NAME = "flash_memory"; + public static final String CARD_TAG_NAME = "card"; + + /////////////////////////////////////////////////////////////////// + + public static void addInformation(final ItemStack stack, final List tooltip) { + TooltipUtils.addInventoryInformation(stack, tooltip, + MEMORY_TAG_NAME, + HARD_DRIVE_TAG_NAME, + FLASH_MEMORY_TAG_NAME, + CARD_TAG_NAME); + } + + /////////////////////////////////////////////////////////////////// + + public final DeviceBusElement busElement = new BusElement(); + + public final DeviceItemStackHandler memoryItemHandler; + public final DeviceItemStackHandler hardDriveItemHandler; + public final DeviceItemStackHandler flashMemoryItemHandler; + public final DeviceItemStackHandler cardItemHandler; + + public final IItemHandler itemHandlers; + + /////////////////////////////////////////////////////////////////// + + public CommonVirtualMachineItemStackHandlers(final int memorySlots, + final int hardDriveSlots, + final int flashMemorySlots, + final int cardSlots) { + memoryItemHandler = new ItemHandler(memorySlots, this::getDevices, DeviceTypes.MEMORY); + hardDriveItemHandler = new ItemHandler(hardDriveSlots, this::getDevices, DeviceTypes.HARD_DRIVE); + flashMemoryItemHandler = new ItemHandler(flashMemorySlots, this::getDevices, DeviceTypes.FLASH_MEMORY); + cardItemHandler = new ItemHandler(cardSlots, this::getDevices, DeviceTypes.CARD); + + itemHandlers = new CombinedInvWrapper(memoryItemHandler, hardDriveItemHandler, flashMemoryItemHandler, cardItemHandler); + } + + /////////////////////////////////////////////////////////////////// + + public Optional getItemHandler(final DeviceType deviceType) { + if (deviceType == DeviceTypes.MEMORY) { + return Optional.of(memoryItemHandler); + } else if (deviceType == DeviceTypes.HARD_DRIVE) { + return Optional.of(hardDriveItemHandler); + } else if (deviceType == DeviceTypes.FLASH_MEMORY) { + return Optional.of(flashMemoryItemHandler); + } else if (deviceType == DeviceTypes.CARD) { + return Optional.of(cardItemHandler); + } + return Optional.empty(); + } + + public boolean isEmpty() { + for (int slot = 0; slot < itemHandlers.getSlots(); slot++) { + if (!itemHandlers.getStackInSlot(slot).isEmpty()) { + return false; + } + } + return true; + } + + public OptionalLong getDefaultDeviceAddress(final VMDevice wrapper) { + long address = ITEM_DEVICE_BASE_ADDRESS; + + for (int slot = 0; slot < hardDriveItemHandler.getSlots(); slot++) { + final Collection devices = hardDriveItemHandler.getBusElement().getDeviceGroup(slot); + for (final ItemDeviceInfo info : devices) { + if (Objects.equals(info.device, wrapper)) { + return OptionalLong.of(address); + } + } + + address += ITEM_DEVICE_STRIDE; + } + + for (int slot = 0; slot < cardItemHandler.getSlots(); slot++) { + final Collection devices = cardItemHandler.getBusElement().getDeviceGroup(slot); + for (final ItemDeviceInfo info : devices) { + if (Objects.equals(info.device, wrapper)) { + return OptionalLong.of(address); + } + } + + address += ITEM_DEVICE_STRIDE; + } + + return OptionalLong.empty(); + } + + public void exportDeviceDataToItemStacks() { + memoryItemHandler.exportDeviceDataToItemStacks(); + hardDriveItemHandler.exportDeviceDataToItemStacks(); + flashMemoryItemHandler.exportDeviceDataToItemStacks(); + cardItemHandler.exportDeviceDataToItemStacks(); + } + + public void exportToItemStack(final ItemStack stack) { + final CompoundNBT items = ItemStackUtils.getOrCreateTileEntityInventoryTag(stack); + items.put(MEMORY_TAG_NAME, memoryItemHandler.serializeNBT()); + items.put(HARD_DRIVE_TAG_NAME, hardDriveItemHandler.serializeNBT()); + items.put(FLASH_MEMORY_TAG_NAME, flashMemoryItemHandler.serializeNBT()); + items.put(CARD_TAG_NAME, cardItemHandler.serializeNBT()); + } + + public CompoundNBT serialize() { + final CompoundNBT tag = new CompoundNBT(); + + tag.put(MEMORY_TAG_NAME, memoryItemHandler.serializeNBT()); + tag.put(HARD_DRIVE_TAG_NAME, hardDriveItemHandler.serializeNBT()); + tag.put(FLASH_MEMORY_TAG_NAME, flashMemoryItemHandler.serializeNBT()); + tag.put(CARD_TAG_NAME, cardItemHandler.serializeNBT()); + + return tag; + } + + public void deserialize(final CompoundNBT tag) { + memoryItemHandler.deserializeNBT(tag.getCompound(MEMORY_TAG_NAME)); + hardDriveItemHandler.deserializeNBT(tag.getCompound(HARD_DRIVE_TAG_NAME)); + flashMemoryItemHandler.deserializeNBT(tag.getCompound(FLASH_MEMORY_TAG_NAME)); + cardItemHandler.deserializeNBT(tag.getCompound(CARD_TAG_NAME)); + } + + /////////////////////////////////////////////////////////////////// + + protected abstract List getDevices(final ItemStack stack); + + protected void onContentsChanged(final DeviceItemStackHandler itemHandler, final int slot) { + } + + /////////////////////////////////////////////////////////////////// + + private final class ItemHandler extends TypedDeviceItemStackHandler { + public ItemHandler(final int size, final Function> deviceLookup, final DeviceType deviceType) { + super(size, deviceLookup, deviceType); + } + + @Override + protected void onContentsChanged(final int slot) { + super.onContentsChanged(slot); + CommonVirtualMachineItemStackHandlers.this.onContentsChanged(this, slot); + } + } + + private final class BusElement extends AbstractDeviceBusElement { + @Override + public Optional>> getNeighbors() { + return Optional.of(Arrays.asList( + LazyOptional.of(memoryItemHandler::getBusElement), + LazyOptional.of(hardDriveItemHandler::getBusElement), + LazyOptional.of(flashMemoryItemHandler::getBusElement), + LazyOptional.of(cardItemHandler::getBusElement) + )); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineState.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachineState.java new file mode 100644 index 00000000..42494e7d --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachineState.java @@ -0,0 +1,38 @@ +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 { + boolean isRunning(); + + 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); + + void start(); + + void stop(); + + public enum RunState { + STOPPED, + LOADING_DEVICES, + RUNNING, + } +} diff --git a/src/main/java/li/cil/oc2/data/LootTables.java b/src/main/java/li/cil/oc2/data/LootTables.java index e19b0b01..63a21a5b 100644 --- a/src/main/java/li/cil/oc2/data/LootTables.java +++ b/src/main/java/li/cil/oc2/data/LootTables.java @@ -21,7 +21,7 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; import static java.util.Objects.requireNonNull; -import static li.cil.oc2.common.Constants.BLOCK_ENTITY_INVENTORY_TAG_NAME; +import static li.cil.oc2.common.Constants.INVENTORY_TAG_NAME; import static li.cil.oc2.common.Constants.BLOCK_ENTITY_TAG_NAME_IN_ITEM; public final class LootTables extends LootTableProvider { @@ -63,8 +63,8 @@ public final class LootTables extends LootTableProvider { .rolls(ConstantRange.of(1)) .addEntry(ItemLootEntry.builder(block) .acceptFunction(CopyNbt.builder(CopyNbt.Source.BLOCK_ENTITY) - .addOperation(BLOCK_ENTITY_INVENTORY_TAG_NAME, - concat(BLOCK_ENTITY_TAG_NAME_IN_ITEM, BLOCK_ENTITY_INVENTORY_TAG_NAME), + .addOperation(INVENTORY_TAG_NAME, + concat(BLOCK_ENTITY_TAG_NAME_IN_ITEM, INVENTORY_TAG_NAME), CopyNbt.Action.REPLACE) ) )