Pulled out a bunch more common functionality from computers for re-use in robots.

This commit is contained in:
Florian Nücke
2021-01-13 13:46:40 +01:00
parent 54bac6077c
commit efa43430b3
25 changed files with 1062 additions and 519 deletions

View File

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

View File

@@ -70,7 +70,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
final float pixelScale = 1 / 16f;
matrixStack.scale(pixelScale, pixelScale, pixelScale);
if (tileEntity.isRunning()) {
if (tileEntity.getState().isRunning()) {
renderTerminal(tileEntity, matrixStack, buffer, cameraPosition);
} else {
renderStatusText(tileEntity, matrixStack, cameraPosition);
@@ -79,7 +79,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
matrixStack.translate(0, 0, -0.1f);
final Matrix4f matrix = matrixStack.getLast().getMatrix();
switch (tileEntity.getBusState()) {
switch (tileEntity.getState().getBusState()) {
case SCAN_PENDING:
case INCOMPLETE:
renderStatus(matrix, buffer);
@@ -91,7 +91,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
renderStatus(matrix, buffer, 250);
break;
case READY:
switch (tileEntity.getRunState()) {
switch (tileEntity.getState().getRunState()) {
case STOPPED:
break;
case LOADING_DEVICES:
@@ -154,7 +154,7 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
return;
}
final ITextComponent bootError = tileEntity.getBootError();
final ITextComponent bootError = tileEntity.getState().getBootError();
if (bootError == null) {
return;
}

View File

@@ -16,7 +16,7 @@ public final class Constants {
///////////////////////////////////////////////////////////////////
public static final String BLOCK_ENTITY_TAG_NAME_IN_ITEM = "BlockEntityTag";
public static final String BLOCK_ENTITY_INVENTORY_TAG_NAME = "items";
public static final String INVENTORY_TAG_NAME = "items";
///////////////////////////////////////////////////////////////////

View File

@@ -8,8 +8,8 @@ import li.cil.oc2.common.integration.Wrenches;
import li.cil.oc2.common.item.Items;
import li.cil.oc2.common.tileentity.ComputerTileEntity;
import li.cil.oc2.common.tileentity.TileEntities;
import li.cil.oc2.common.util.TooltipUtils;
import li.cil.oc2.common.util.VoxelShapeUtils;
import li.cil.oc2.common.vm.CommonVirtualMachineItemStackHandlers;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.HorizontalBlock;
@@ -76,11 +76,7 @@ public final class ComputerBlock extends HorizontalBlock {
@Override
public void addInformation(final ItemStack stack, @Nullable final IBlockReader world, final List<ITextComponent> 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());

View File

@@ -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));
}

View File

@@ -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<Boolean> 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<ItemDeviceInfo> 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<Device> devices) {
state.virtualMachine.vmAdapter.addDevices(devices);
}
@Override
protected void onDevicesRemoved(final Collection<Device> 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<RobotBusController, CommonVirtualMachine> {
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);
}
}
}

View File

@@ -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())
));

View File

@@ -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 <T extends Entity> void withServerEntity(final Supplier<NetworkEvent.Context> context, final int id, final Class<T> type, final Consumer<T> 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 <T extends TileEntity> void withClientTileEntityAt(final BlockPos pos, final Class<T> type, final Consumer<T> callback) {
final ClientWorld world = Minecraft.getInstance().world;
@@ -39,4 +54,17 @@ public final class MessageUtils {
callback.accept((T) tileEntity);
}
}
@SuppressWarnings("unchecked")
public static <T extends Entity> void withClientEntity(final int id, final Class<T> type, final Consumer<T> 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);
}
}
}

View File

@@ -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 <T> void sendToClientsTrackingChunk(final T message, final Chunk chunk) {
Network.INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), message);
}
public static <T> void sendToClientsTrackingEntity(final T message, final Entity entity) {
Network.INSTANCE.send(PacketDistributor.TRACKING_ENTITY.with(() -> entity), message);
}
///////////////////////////////////////////////////////////////////
private static int getNextPacketId() {

View File

@@ -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);
}
}

View File

@@ -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<NetworkEvent.Context> context) {
context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class,
(tileEntity) -> tileEntity.setBootErrorClient(message.value)));
(tileEntity) -> tileEntity.getState().setBootErrorClient(message.value)));
return true;
}

View File

@@ -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<NetworkEvent.Context> context) {
context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, ComputerTileEntity.class,
(tileEntity) -> tileEntity.setBusStateClient(message.busState)));
(tileEntity) -> tileEntity.getState().setBusStateClient(message.busState)));
return true;
}

View File

@@ -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<NetworkEvent.Context> 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) {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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<NetworkEvent.Context> 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);
}
}

View File

@@ -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<NetworkEvent.Context> context) {
context.get().enqueueWork(() -> MessageUtils.withServerEntity(context, message.entityId, RobotEntity.class,
(tileEntity) -> tileEntity.getTerminal().putInput(ByteBuffer.wrap(message.data))));
return true;
}
}

View File

@@ -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<NetworkEvent.Context> context) {
context.get().enqueueWork(() -> MessageUtils.withClientEntity(message.entityId, RobotEntity.class,
robot -> robot.getTerminal().putOutput(ByteBuffer.wrap(message.data))));
return true;
}
}

View File

@@ -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<IItemHandler> 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<T> 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<ItemDeviceInfo> 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<ItemDeviceInfo> 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<ItemDeviceInfo> 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<ItemStack, List<ItemDeviceInfo>> 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<ItemDeviceInfo> 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<Device> devices) {
virtualMachine.vmAdapter.addDevices(devices);
state.virtualMachine.vmAdapter.addDevices(devices);
}
@Override
protected void onDevicesRemoved(final Collection<Device> devices) {
virtualMachine.vmAdapter.removeDevices(devices);
state.virtualMachine.vmAdapter.removeDevices(devices);
}
}
@@ -684,10 +313,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
public Optional<Collection<LazyOptional<DeviceBusElement>>> getNeighbors() {
return super.getNeighbors().map(neighbors -> {
final ArrayList<LazyOptional<DeviceBusElement>> 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<ComputerBusController, CommonVirtualMachine> {
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);
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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<TBusController extends AbstractDeviceBusController, TVirtualMachine extends VirtualMachine> 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);
}
}

View File

@@ -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<ITextComponent> 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<IItemHandler> 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<ItemDeviceInfo> 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<ItemDeviceInfo> 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<ItemDeviceInfo> 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<ItemStack, List<ItemDeviceInfo>> 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<Collection<LazyOptional<DeviceBusElement>>> getNeighbors() {
return Optional.of(Arrays.asList(
LazyOptional.of(memoryItemHandler::getBusElement),
LazyOptional.of(hardDriveItemHandler::getBusElement),
LazyOptional.of(flashMemoryItemHandler::getBusElement),
LazyOptional.of(cardItemHandler::getBusElement)
));
}
}
}

View File

@@ -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,
}
}

View File

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