Add tracking of and display of boot error messages.

This commit is contained in:
Florian Nücke
2020-12-27 01:43:32 +01:00
parent 3c6e9da0e4
commit 535e9cef79
9 changed files with 214 additions and 55 deletions

View File

@@ -25,5 +25,13 @@ public final class Constants {
///////////////////////////////////////////////////////////////////
public static final String SUFFIX_FORMAT = "tooltip.oc2.suffix_format";
public static final String TOOLTIP_SUFFIX_FORMAT = "tooltip.oc2.suffix_format";
///////////////////////////////////////////////////////////////////
public static final String COMPUTER_BOOT_ERROR_UNKNOWN = "gui.oc2.computer.boot_error.unknown";
public static final String COMPUTER_BOOT_ERROR_NO_RAM = "gui.oc2.computer.boot_error.no_ram";
public static final String COMPUTER_BUS_STATE_INCOMPLETE = "gui.oc2.computer.bus_state.incomplete";
public static final String COMPUTER_BUS_STATE_TOO_COMPLEX = "gui.oc2.computer.bus_state.too_complex";
public static final String COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS = "gui.oc2.computer.bus_state.multiple_controllers";
}

View File

@@ -3,11 +3,13 @@ package li.cil.oc2.client.render.tile;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import li.cil.oc2.Constants;
import li.cil.oc2.api.API;
import li.cil.oc2.client.render.OpenComputersRenderType;
import li.cil.oc2.common.block.ComputerBlock;
import li.cil.oc2.common.block.entity.ComputerTileEntity;
import li.cil.oc2.common.vm.Terminal;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.model.RenderMaterial;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
@@ -19,11 +21,17 @@ import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Quaternion;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.ITextProperties;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.List;
@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public final class ComputerTileEntityRenderer extends TileEntityRenderer<ComputerTileEntity> {
private static final ResourceLocation OVERLAY_POWER_LOCATION = new ResourceLocation(API.MOD_ID, "blocks/computer/computer_overlay_power");
@@ -80,6 +88,8 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
if (tileEntity.isRunning()) {
renderTerminal(tileEntity, stack, buffer, cameraPosition);
} else {
renderStatusText(tileEntity, stack, buffer, cameraPosition);
}
stack.translate(0, 0, -0.1f);
@@ -88,23 +98,23 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
switch (tileEntity.getBusState()) {
case SCAN_PENDING:
case INCOMPLETE:
drawStatus(matrix, buffer);
renderStatus(matrix, buffer);
break;
case TOO_COMPLEX:
drawStatus(matrix, buffer, 1000);
renderStatus(matrix, buffer, 1000);
break;
case MULTIPLE_CONTROLLERS:
drawStatus(matrix, buffer, 250);
renderStatus(matrix, buffer, 250);
break;
case READY:
switch (tileEntity.getRunState()) {
case STOPPED:
break;
case LOADING_DEVICES:
drawStatus(matrix, buffer);
renderStatus(matrix, buffer);
break;
case RUNNING:
drawPower(matrix, buffer);
renderPower(matrix, buffer);
break;
}
break;
@@ -149,27 +159,82 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer<Compute
stack.translate(0, 0, -0.9f);
final Matrix4f matrix = stack.getLast().getMatrix();
drawQuad(matrix, TEXTURE_TERMINAL.getBuffer(buffer, OpenComputersRenderType::getUnlitBlock));
renderQuad(matrix, TEXTURE_TERMINAL.getBuffer(buffer, OpenComputersRenderType::getUnlitBlock));
stack.pop();
}
}
private void drawStatus(final Matrix4f matrix, final IRenderTypeBuffer buffer) {
drawStatus(matrix, buffer, 0);
private void renderStatusText(final ComputerTileEntity tileEntity, final MatrixStack stack, final IRenderTypeBuffer buffer, final Vector3d cameraPosition) {
if (!Vector3d.copyCentered(tileEntity.getPos()).isWithinDistanceOf(cameraPosition, 6f * 6f)) {
return;
}
stack.push();
stack.translate(3, 3, -0.9f);
switch (tileEntity.getBusState()) {
case SCAN_PENDING:
case INCOMPLETE:
drawText(stack, new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_INCOMPLETE), 0xFFFFFF);
break;
case TOO_COMPLEX:
drawText(stack, new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_TOO_COMPLEX), 0xFFFFFF);
break;
case MULTIPLE_CONTROLLERS:
drawText(stack, new TranslationTextComponent(Constants.COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS), 0xFFFFFF);
break;
case READY:
switch (tileEntity.getRunState()) {
case STOPPED:
case LOADING_DEVICES:
final ITextComponent bootError = tileEntity.getBootError();
if (bootError != null) {
drawText(stack, bootError, 0xFFFFFF);
}
break;
}
break;
}
stack.pop();
}
private void drawStatus(final Matrix4f matrix, final IRenderTypeBuffer buffer, final int frequency) {
private void drawText(final MatrixStack stack, final ITextComponent text, final int color) {
final int maxWidth = 100;
stack.push();
stack.scale(10f / maxWidth, 10f / maxWidth, 10f / maxWidth);
final FontRenderer fontRenderer = renderDispatcher.getFontRenderer();
final List<ITextProperties> wrappedText = renderDispatcher.getFontRenderer().getCharacterManager().func_238362_b_(text, maxWidth, Style.EMPTY);
if (wrappedText.size() == 1) {
final int textWidth = fontRenderer.getStringPropertyWidth(text);
fontRenderer.func_243248_b(stack, text, (maxWidth - textWidth) * 0.5f, 0, 0xEE3322);
} else {
for (int i = 0; i < wrappedText.size(); i++) {
fontRenderer.drawString(stack, wrappedText.get(i).getString(), 0, i * fontRenderer.FONT_HEIGHT, 0xEE3322);
}
}
stack.pop();
}
private void renderStatus(final Matrix4f matrix, final IRenderTypeBuffer buffer) {
renderStatus(matrix, buffer, 0);
}
private void renderStatus(final Matrix4f matrix, final IRenderTypeBuffer buffer, final int frequency) {
if (frequency <= 0 || (((System.currentTimeMillis() + hashCode()) / frequency) % 2) == 1) {
drawQuad(matrix, TEXTURE_STATUS.getBuffer(buffer, OpenComputersRenderType::getUnlitBlock));
renderQuad(matrix, TEXTURE_STATUS.getBuffer(buffer, OpenComputersRenderType::getUnlitBlock));
}
}
private void drawPower(final Matrix4f matrix, final IRenderTypeBuffer buffer) {
drawQuad(matrix, TEXTURE_POWER.getBuffer(buffer, OpenComputersRenderType::getUnlitBlock));
private void renderPower(final Matrix4f matrix, final IRenderTypeBuffer buffer) {
renderQuad(matrix, TEXTURE_POWER.getBuffer(buffer, OpenComputersRenderType::getUnlitBlock));
}
private static void drawQuad(final Matrix4f matrix, final IVertexBuilder buffer) {
private static void renderQuad(final Matrix4f matrix, final IVertexBuilder buffer) {
// NB: We may get a SpriteAwareVertexBuilder here. Sadly, its chaining is broken,
// because methods may return the underlying vertex builder, so e.g. calling
// buffer.pos(...).tex(...) will not actually call SpriteAwareVertexBuilder.tex(...)

View File

@@ -71,7 +71,7 @@ public final class ComputerBlock extends HorizontalBlock {
final TileEntity tileEntity = world.getTileEntity(pos);
if (tileEntity instanceof ComputerTileEntity) {
final ComputerTileEntity busCable = (ComputerTileEntity) tileEntity;
busCable.handleNeighborChanged(changedBlockPos);
busCable.handleNeighborChanged();
}
}

View File

@@ -18,6 +18,7 @@ import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.container.DeviceItemStackHandler;
import li.cil.oc2.common.init.TileEntities;
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;
@@ -30,6 +31,7 @@ import li.cil.oc2.common.vm.VirtualMachine;
import li.cil.oc2.common.vm.VirtualMachineRunner;
import li.cil.sedna.api.device.MemoryMappedDevice;
import li.cil.sedna.api.device.PhysicalMemory;
import li.cil.sedna.api.memory.MemoryAccessException;
import li.cil.sedna.buildroot.Buildroot;
import li.cil.sedna.device.serial.UART16550A;
import li.cil.sedna.device.virtio.VirtIOFileSystemDevice;
@@ -39,7 +41,8 @@ import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
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;
@@ -62,12 +65,14 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
///////////////////////////////////////////////////////////////////
private static final String BUS_ELEMENT_NBT_TAG_NAME = "busElement";
private static final String BUS_STATE_NBT_TAG_NAME = "busState";
private static final String TERMINAL_NBT_TAG_NAME = "terminal";
private static final String VIRTUAL_MACHINE_NBT_TAG_NAME = "virtualMachine";
private static final String RUNNER_NBT_TAG_NAME = "runner";
private static final String RUN_STATE_NBT_TAG_NAME = "runState";
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 * 20; // In ticks.
@@ -88,6 +93,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
private final AbstractDeviceBusController busController;
private AbstractDeviceBusController.BusState busState;
private RunState runState;
private ITextComponent bootError;
private int loadDevicesDelay;
///////////////////////////////////////////////////////////////////
@@ -127,6 +133,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
return;
}
setBootError(null);
setRunState(RunState.LOADING_DEVICES);
loadDevicesDelay = 0;
}
@@ -157,7 +164,12 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
return runState;
}
public void handleNeighborChanged(final BlockPos pos) {
@Nullable
public ITextComponent getBootError() {
return bootError;
}
public void handleNeighborChanged() {
busController.scheduleBusScan();
}
@@ -177,6 +189,13 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
}
}
public void setBootErrorClient(final ITextComponent value) {
final World world = getWorld();
if (world != null && world.isRemote()) {
bootError = value;
}
}
@Override
public @NotNull <T> LazyOptional<T> getCapability(final @NotNull Capability<T> capability, @Nullable final Direction side) {
if (isRemoved()) {
@@ -240,18 +259,24 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
// 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) {
virtualMachine.board.reset();
try {
virtualMachine.board.reset();
virtualMachine.board.initialize();
} catch (final Throwable e) {
} 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_RAM));
setRunState(RunState.STOPPED);
return;
} catch (final MemoryAccessException e) {
LOGGER.error(e);
setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_UNKNOWN));
setRunState(RunState.STOPPED);
return;
}
virtualMachine.board.setRunning(true);
runner = new ComputerVirtualMachineRunner(virtualMachine);
}
@@ -285,9 +310,10 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
public CompoundNBT getUpdateTag() {
final CompoundNBT result = super.getUpdateTag();
result.put(TERMINAL_NBT_TAG_NAME, NBTSerialization.serialize(terminal));
result.putInt(BUS_STATE_NBT_TAG_NAME, busState.ordinal());
result.putInt(RUN_STATE_NBT_TAG_NAME, runState.ordinal());
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));
return result;
}
@@ -296,9 +322,10 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
public void handleUpdateTag(final BlockState state, final CompoundNBT tag) {
super.handleUpdateTag(state, tag);
NBTSerialization.deserialize(tag.getCompound(TERMINAL_NBT_TAG_NAME), terminal);
busState = AbstractDeviceBusController.BusState.values()[tag.getInt(BUS_STATE_NBT_TAG_NAME)];
runState = RunState.values()[tag.getInt(RUN_STATE_NBT_TAG_NAME)];
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));
}
@Override
@@ -307,15 +334,15 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
joinVirtualMachine();
compound.put(TERMINAL_NBT_TAG_NAME, NBTSerialization.serialize(terminal));
compound.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal));
compound.put(BUS_ELEMENT_NBT_TAG_NAME, NBTSerialization.serialize(busElement));
compound.put(VIRTUAL_MACHINE_NBT_TAG_NAME, NBTSerialization.serialize(virtualMachine));
compound.put(BUS_ELEMENT_TAG_NAME, NBTSerialization.serialize(busElement));
compound.put(VIRTUAL_MACHINE_TAG_NAME, NBTSerialization.serialize(virtualMachine));
if (runner != null) {
compound.put(RUNNER_NBT_TAG_NAME, NBTSerialization.serialize(runner));
compound.put(RUNNER_TAG_NAME, NBTSerialization.serialize(runner));
} else {
NBTUtils.putEnum(compound, RUN_STATE_NBT_TAG_NAME, runState);
NBTUtils.putEnum(compound, RUN_STATE_TAG_NAME, runState);
}
compound.put(Constants.BLOCK_ENTITY_INVENTORY_TAG_NAME, itemHandler.serializeNBT());
@@ -329,22 +356,22 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
joinVirtualMachine();
NBTSerialization.deserialize(compound.getCompound(TERMINAL_NBT_TAG_NAME), terminal);
NBTSerialization.deserialize(compound.getCompound(TERMINAL_TAG_NAME), terminal);
if (compound.contains(BUS_ELEMENT_NBT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
NBTSerialization.deserialize(compound.getCompound(BUS_ELEMENT_NBT_TAG_NAME), busElement);
if (compound.contains(BUS_ELEMENT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
NBTSerialization.deserialize(compound.getCompound(BUS_ELEMENT_TAG_NAME), busElement);
}
if (compound.contains(VIRTUAL_MACHINE_NBT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
NBTSerialization.deserialize(compound.getCompound(VIRTUAL_MACHINE_NBT_TAG_NAME), virtualMachine);
if (compound.contains(VIRTUAL_MACHINE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
NBTSerialization.deserialize(compound.getCompound(VIRTUAL_MACHINE_TAG_NAME), virtualMachine);
}
if (compound.contains(RUNNER_NBT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
if (compound.contains(RUNNER_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
runner = new ComputerVirtualMachineRunner(virtualMachine);
NBTSerialization.deserialize(compound.getCompound(RUNNER_NBT_TAG_NAME), runner);
NBTSerialization.deserialize(compound.getCompound(RUNNER_TAG_NAME), runner);
runState = RunState.LOADING_DEVICES;
} else {
runState = NBTUtils.getEnum(compound, RUN_STATE_NBT_TAG_NAME, RunState.class);
runState = NBTUtils.getEnum(compound, RUN_STATE_TAG_NAME, RunState.class);
if (runState == null) {
runState = RunState.STOPPED;
} else if (runState == RunState.RUNNING) {
@@ -413,6 +440,12 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
}
}
private void setBootError(@Nullable final ITextComponent value) {
bootError = value;
final ComputerBootErrorMessage message = new ComputerBootErrorMessage(this);
Network.sendToClientsTrackingChunk(message, chunk);
}
private void stopRunnerAndResetVM() {
joinVirtualMachine();
runner = null;

View File

@@ -74,11 +74,11 @@ public class HddItem extends Item {
if (baseBlockDevice != null) {
return new StringTextComponent("")
.append(super.getDisplayName(stack))
.append(new TranslationTextComponent(Constants.SUFFIX_FORMAT, baseBlockDevice));
.append(new TranslationTextComponent(Constants.TOOLTIP_SUFFIX_FORMAT, baseBlockDevice));
} else {
return new StringTextComponent("")
.append(super.getDisplayName(stack))
.append(new TranslationTextComponent(Constants.SUFFIX_FORMAT, TextFormatUtils.formatSize(getCapacity(stack))));
.append(new TranslationTextComponent(Constants.TOOLTIP_SUFFIX_FORMAT, TextFormatUtils.formatSize(getCapacity(stack))));
}
}

View File

@@ -44,7 +44,7 @@ public class RamItem extends Item {
public ITextComponent getDisplayName(final ItemStack stack) {
return new StringTextComponent("")
.append(super.getDisplayName(stack))
.append(new TranslationTextComponent(Constants.SUFFIX_FORMAT, TextFormatUtils.formatSize(getCapacity(stack))));
.append(new TranslationTextComponent(Constants.TOOLTIP_SUFFIX_FORMAT, TextFormatUtils.formatSize(getCapacity(stack))));
}
private static float getRamItemProperties(final ItemStack stack, final ClientWorld world, final LivingEntity entity) {

View File

@@ -1,10 +1,7 @@
package li.cil.oc2.common.network;
import li.cil.oc2.api.API;
import li.cil.oc2.common.network.message.ComputerBusStateMessage;
import li.cil.oc2.common.network.message.ComputerRunStateMessage;
import li.cil.oc2.common.network.message.TerminalBlockInputMessage;
import li.cil.oc2.common.network.message.TerminalBlockOutputMessage;
import li.cil.oc2.common.network.message.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.fml.network.NetworkDirection;
@@ -52,6 +49,12 @@ public final class Network {
.decoder(ComputerBusStateMessage::new)
.consumer(ComputerBusStateMessage::handleMessage)
.add();
INSTANCE.messageBuilder(ComputerBootErrorMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT)
.encoder(ComputerBootErrorMessage::toBytes)
.decoder(ComputerBootErrorMessage::new)
.consumer(ComputerBootErrorMessage::handleMessage)
.add();
}
public static <T> void sendToClientsTrackingChunk(final T message, final Chunk chunk) {

View File

@@ -0,0 +1,44 @@
package li.cil.oc2.common.network.message;
import li.cil.oc2.common.block.entity.ComputerTileEntity;
import li.cil.oc2.common.network.MessageUtils;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.fml.network.NetworkEvent;
import java.util.function.Supplier;
public class ComputerBootErrorMessage {
private BlockPos pos;
private ITextComponent value;
///////////////////////////////////////////////////////////////////
public ComputerBootErrorMessage(final ComputerTileEntity tileEntity) {
this.pos = tileEntity.getPos();
this.value = tileEntity.getBootError();
}
public ComputerBootErrorMessage(final PacketBuffer buffer) {
fromBytes(buffer);
}
///////////////////////////////////////////////////////////////////
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)));
return true;
}
public void fromBytes(final PacketBuffer buffer) {
pos = buffer.readBlockPos();
value = buffer.readTextComponent();
}
public static void toBytes(final ComputerBootErrorMessage message, final PacketBuffer buffer) {
buffer.writeBlockPos(message.pos);
buffer.writeTextComponent(message.value);
}
}

View File

@@ -11,5 +11,11 @@
"item.oc2.ram": "RAM",
"item.oc2.hdd": "HDD",
"tooltip.oc2.suffix_format": " (%s)"
"tooltip.oc2.suffix_format": " (%s)",
"gui.oc2.computer.boot_error.unknown": "Unknown Error",
"gui.oc2.computer.boot_error.no_ram": "Insufficient Memory",
"gui.oc2.computer.bus_state.incomplete": "Bus Incomplete",
"gui.oc2.computer.bus_state.too_complex": "Bus Too Complex",
"gui.oc2.computer.bus_state.multiple_controllers": "Multiple Bus Controllers"
}