diff --git a/src/main/java/li/cil/oc2/Constants.java b/src/main/java/li/cil/oc2/Constants.java new file mode 100644 index 00000000..776c904d --- /dev/null +++ b/src/main/java/li/cil/oc2/Constants.java @@ -0,0 +1,5 @@ +package li.cil.oc2; + +public final class Constants { + public static final String COMPUTER_BLOCK_NAME = "computer"; +} diff --git a/src/main/java/li/cil/oc2/OpenComputers.java b/src/main/java/li/cil/oc2/OpenComputers.java index 4c6c2b86..4e60d8db 100644 --- a/src/main/java/li/cil/oc2/OpenComputers.java +++ b/src/main/java/li/cil/oc2/OpenComputers.java @@ -1,8 +1,22 @@ package li.cil.oc2; import li.cil.oc2.api.API; +import li.cil.oc2.client.ClientSetup; +import li.cil.oc2.common.CommonSetup; +import li.cil.oc2.common.block.ComputerBlock; +import li.cil.oc2.common.container.ComputerContainer; import li.cil.oc2.common.item.RISCVTesterItem; +import li.cil.oc2.common.tile.ComputerTileEntity; +import net.minecraft.block.Block; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.item.BlockItem; import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.extensions.IForgeContainerType; import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; @@ -11,10 +25,37 @@ import net.minecraftforge.registries.ForgeRegistries; @Mod(API.MOD_ID) public final class OpenComputers { + public static final ItemGroup ITEM_GROUP = new ItemGroup(API.MOD_ID) { + @Override + public ItemStack createIcon() { + return new ItemStack(COMPUTER_ITEM.get()); + } + }; + + public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, API.MOD_ID); + public static final RegistryObject COMPUTER_BLOCK = BLOCKS.register(Constants.COMPUTER_BLOCK_NAME, ComputerBlock::new); + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, API.MOD_ID); public static final RegistryObject RISCV_TESTER = ITEMS.register("riscv_tester", RISCVTesterItem::new); + public static final RegistryObject COMPUTER_ITEM = ITEMS.register(Constants.COMPUTER_BLOCK_NAME, () -> new BlockItem(COMPUTER_BLOCK.get(), new Item.Properties().group(ITEM_GROUP))); + + public static final DeferredRegister> TILES = DeferredRegister.create(ForgeRegistries.TILE_ENTITIES, API.MOD_ID); + public static final RegistryObject> COMPUTER_TILE_ENTITY = TILES.register(Constants.COMPUTER_BLOCK_NAME, () -> TileEntityType.Builder.create(ComputerTileEntity::new, COMPUTER_BLOCK.get()).build(null)); + + public static final DeferredRegister> CONTAINERS = DeferredRegister.create(ForgeRegistries.CONTAINERS, API.MOD_ID); + public static final RegistryObject> COMPUTER_CONTAINER = CONTAINERS.register(Constants.COMPUTER_BLOCK_NAME, () -> IForgeContainerType.create((id, inventory, data) -> { + final BlockPos pos = data.readBlockPos(); + final TileEntity tileEntity = inventory.player.getEntityWorld().getTileEntity(pos); + return new ComputerContainer(id, tileEntity); + })); public OpenComputers() { ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus()); + BLOCKS.register(FMLJavaModLoadingContext.get().getModEventBus()); + TILES.register(FMLJavaModLoadingContext.get().getModEventBus()); + CONTAINERS.register(FMLJavaModLoadingContext.get().getModEventBus()); + + FMLJavaModLoadingContext.get().getModEventBus().addListener(CommonSetup::run); + FMLJavaModLoadingContext.get().getModEventBus().addListener(ClientSetup::run); } } diff --git a/src/main/java/li/cil/oc2/client/ClientSetup.java b/src/main/java/li/cil/oc2/client/ClientSetup.java new file mode 100644 index 00000000..6d356e31 --- /dev/null +++ b/src/main/java/li/cil/oc2/client/ClientSetup.java @@ -0,0 +1,12 @@ +package li.cil.oc2.client; + +import li.cil.oc2.OpenComputers; +import li.cil.oc2.client.gui.ComputerScreen; +import net.minecraft.client.gui.ScreenManager; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; + +public final class ClientSetup { + public static void run(final FMLClientSetupEvent event) { + ScreenManager.registerFactory(OpenComputers.COMPUTER_CONTAINER.get(), ComputerScreen::new); + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/ComputerScreen.java b/src/main/java/li/cil/oc2/client/gui/ComputerScreen.java new file mode 100644 index 00000000..4a8c517c --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/ComputerScreen.java @@ -0,0 +1,41 @@ +package li.cil.oc2.client.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import li.cil.oc2.api.API; +import li.cil.oc2.common.container.ComputerContainer; +import net.minecraft.client.gui.screen.inventory.ContainerScreen; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; + +import java.util.Objects; + +public final class ComputerScreen extends ContainerScreen { + private static final ResourceLocation BACKGROUND = new ResourceLocation(API.MOD_ID, "textures/gui/container/computer.png"); + + public ComputerScreen(final ComputerContainer container, final PlayerInventory inventory, final ITextComponent title) { + super(container, inventory, title); + xSize = 196; + ySize = 197; + } + + @Override + public void render(final int mouseX, final int mouseY, final float partialTicks) { + renderBackground(); + super.render(mouseX, mouseY, partialTicks); + RenderSystem.disableBlend(); + + // TODO Render terminal text. + + renderHoveredToolTip(mouseX, mouseY); + } + + @Override + protected void drawGuiContainerBackgroundLayer(final float partialTicks, final int mouseX, final int mouseY) { + RenderSystem.color4f(1f, 1f, 1f, 1f); + Objects.requireNonNull(minecraft).getTextureManager().bindTexture(BACKGROUND); + final int x = (width - xSize) / 2; + final int y = (height - ySize) / 2; + blit(x, y, 0, 0, xSize, ySize); + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/package-info.java b/src/main/java/li/cil/oc2/client/gui/package-info.java index e9ef594e..1501a019 100644 --- a/src/main/java/li/cil/oc2/client/gui/package-info.java +++ b/src/main/java/li/cil/oc2/client/gui/package-info.java @@ -1,4 +1,7 @@ @ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault package li.cil.oc2.client.gui; +import mcp.MethodsReturnNonnullByDefault; + import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/client/gui/terminal/package-info.java b/src/main/java/li/cil/oc2/client/gui/terminal/package-info.java index f73f531f..34cec7ac 100644 --- a/src/main/java/li/cil/oc2/client/gui/terminal/package-info.java +++ b/src/main/java/li/cil/oc2/client/gui/terminal/package-info.java @@ -1,4 +1,7 @@ @ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault package li.cil.oc2.client.gui.terminal; +import mcp.MethodsReturnNonnullByDefault; + import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/client/render/font/package-info.java b/src/main/java/li/cil/oc2/client/render/font/package-info.java index f8e59f0a..215dd2d2 100644 --- a/src/main/java/li/cil/oc2/client/render/font/package-info.java +++ b/src/main/java/li/cil/oc2/client/render/font/package-info.java @@ -1,4 +1,7 @@ @ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault package li.cil.oc2.client.render.font; +import mcp.MethodsReturnNonnullByDefault; + import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/CommonSetup.java b/src/main/java/li/cil/oc2/common/CommonSetup.java new file mode 100644 index 00000000..baab3568 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/CommonSetup.java @@ -0,0 +1,9 @@ +package li.cil.oc2.common; + +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; + +public final class CommonSetup { + public static void run(final FMLCommonSetupEvent event) { + + } +} diff --git a/src/main/java/li/cil/oc2/common/block/ComputerBlock.java b/src/main/java/li/cil/oc2/common/block/ComputerBlock.java new file mode 100644 index 00000000..8d354ed0 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/ComputerBlock.java @@ -0,0 +1,71 @@ +package li.cil.oc2.common.block; + +import li.cil.oc2.OpenComputers; +import li.cil.oc2.common.container.ComputerContainer; +import li.cil.oc2.common.tile.ComputerTileEntity; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.inventory.container.Container; +import net.minecraft.inventory.container.INamedContainerProvider; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.IBlockReader; +import net.minecraft.world.World; +import net.minecraftforge.fml.network.NetworkHooks; + +import javax.annotation.Nullable; + +public final class ComputerBlock extends Block { + public ComputerBlock() { + super(Properties.create(Material.IRON).sound(SoundType.METAL)); + } + + @Override + public boolean hasTileEntity(final BlockState state) { + return true; + } + + @Nullable + @Override + public TileEntity createTileEntity(final BlockState state, final IBlockReader world) { + return OpenComputers.COMPUTER_TILE_ENTITY.get().create(); + } + + @SuppressWarnings("deprecation") + @Override + public ActionResultType onBlockActivated(final BlockState state, final World world, final BlockPos pos, final PlayerEntity player, final Hand hand, final BlockRayTraceResult hit) { + if (!world.isRemote()) { + if (!(player instanceof ServerPlayerEntity)) { + throw new IllegalArgumentException(); + } + + final TileEntity tileEntity = world.getTileEntity(pos); + if (!(tileEntity instanceof ComputerTileEntity)) { + throw new IllegalStateException(); + } + + NetworkHooks.openGui((ServerPlayerEntity) player, new INamedContainerProvider() { + @Override + public ITextComponent getDisplayName() { + return new TranslationTextComponent("blah"); + } + + @Override + public Container createMenu(final int id, final PlayerInventory inventory, final PlayerEntity player) { + return new ComputerContainer(id, tileEntity); + } + }, tileEntity.getPos()); + } + return ActionResultType.SUCCESS; + } +} diff --git a/src/main/java/li/cil/oc2/common/block/package-info.java b/src/main/java/li/cil/oc2/common/block/package-info.java new file mode 100644 index 00000000..94c669c3 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.common.block; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/container/ComputerContainer.java b/src/main/java/li/cil/oc2/common/container/ComputerContainer.java new file mode 100644 index 00000000..3ad346f1 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/container/ComputerContainer.java @@ -0,0 +1,27 @@ +package li.cil.oc2.common.container; + +import li.cil.oc2.OpenComputers; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.container.Container; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.IWorldPosCallable; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public final class ComputerContainer extends Container { + private final TileEntity tileEntity; + + public ComputerContainer(final int id, @Nullable final TileEntity tileEntity) { + super(OpenComputers.COMPUTER_CONTAINER.get(), id); + this.tileEntity = tileEntity; + } + + @Override + public boolean canInteractWith(final PlayerEntity player) { + if (tileEntity == null) return false; + final World world = tileEntity.getWorld(); + if (world == null) return false; + return isWithinUsableDistance(IWorldPosCallable.of(world, tileEntity.getPos()), player, OpenComputers.COMPUTER_BLOCK.get()); + } +} diff --git a/src/main/java/li/cil/oc2/common/container/package-info.java b/src/main/java/li/cil/oc2/common/container/package-info.java new file mode 100644 index 00000000..9a1e52c5 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/container/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.common.container; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/item/RISCVTesterItem.java b/src/main/java/li/cil/oc2/common/item/RISCVTesterItem.java index f7f9a58e..7526358f 100644 --- a/src/main/java/li/cil/oc2/common/item/RISCVTesterItem.java +++ b/src/main/java/li/cil/oc2/common/item/RISCVTesterItem.java @@ -1,11 +1,11 @@ package li.cil.oc2.common.item; +import li.cil.oc2.OpenComputers; import li.cil.oc2.client.gui.RISCVTestScreen; import net.minecraft.client.Minecraft; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; -import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUseContext; import net.minecraft.util.ActionResult; @@ -15,7 +15,7 @@ import net.minecraft.world.World; public final class RISCVTesterItem extends Item { public RISCVTesterItem() { - super(new Properties().group(ItemGroup.MISC).maxStackSize(1)); + super(new Properties().group(OpenComputers.ITEM_GROUP).maxStackSize(1)); } @Override diff --git a/src/main/java/li/cil/oc2/common/item/package-info.java b/src/main/java/li/cil/oc2/common/item/package-info.java index 60b8e52b..eb086ec7 100644 --- a/src/main/java/li/cil/oc2/common/item/package-info.java +++ b/src/main/java/li/cil/oc2/common/item/package-info.java @@ -1,4 +1,7 @@ @ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault package li.cil.oc2.common.item; +import mcp.MethodsReturnNonnullByDefault; + import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/network/Network.java b/src/main/java/li/cil/oc2/common/network/Network.java new file mode 100644 index 00000000..64c49be6 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -0,0 +1,4 @@ +package li.cil.oc2.common.network; + +public final class Network { +} diff --git a/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java new file mode 100644 index 00000000..ed7d0cec --- /dev/null +++ b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java @@ -0,0 +1,180 @@ +package li.cil.oc2.common.tile; + +import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; +import li.cil.oc2.OpenComputers; +import li.cil.oc2.client.gui.terminal.Terminal; +import li.cil.oc2.common.vm.VirtualMachineRunner; +import li.cil.sedna.api.Sizes; +import li.cil.sedna.api.device.PhysicalMemory; +import li.cil.sedna.buildroot.Buildroot; +import li.cil.sedna.device.block.ByteBufferBlockDevice; +import li.cil.sedna.device.memory.Memory; +import li.cil.sedna.device.serial.UART16550A; +import li.cil.sedna.device.virtio.VirtIOBlockDevice; +import li.cil.sedna.riscv.R5Board; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.ITickableTileEntity; +import net.minecraft.tileentity.TileEntity; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedInputStream; +import java.io.InputStream; + +public final class ComputerTileEntity extends TileEntity implements ITickableTileEntity { + private static final Logger LOGGER = LogManager.getLogger(); + + private final Terminal terminal = new Terminal(); + + private VirtualMachineRunner runner; + private R5Board board; + private PhysicalMemory rom; + private PhysicalMemory ram; + private UART16550A uart; + private VirtIOBlockDevice hdd; + + public ComputerTileEntity() { + super(OpenComputers.COMPUTER_TILE_ENTITY.get()); + } + + public void start() { + startVirtualMachine(); + } + + public void stop() { + stopVirtualMachine(); + } + + @Override + public void tick() { + if (world == null || world.isRemote()) { + return; + } + + if (runner != null) { + runner.tick(); + } + } + + @Override + public void remove() { + super.remove(); + stopVirtualMachine(); + } + + @Override + public void onChunkUnloaded() { + stopVirtualMachine(); + } + + @Override + public void read(final CompoundNBT compound) { + super.read(compound); + joinVirtualMachine(); + // TODO deserialize VM + } + + @Override + public CompoundNBT write(final CompoundNBT compound) { + final CompoundNBT result = super.write(compound); + joinVirtualMachine(); + // todo serialize VM + return result; + } + + private void startVirtualMachine() { + if (runner == null) { + try { + createVirtualMachine(); + } catch (final Throwable e) { + LOGGER.warn(e); + } + } + } + + private void stopVirtualMachine() { + joinVirtualMachine(); + runner = null; + } + + private void joinVirtualMachine() { + if (runner != null) { + try { + runner.join(); + } catch (final Throwable e) { + LOGGER.warn(e); + runner = null; + } + } + } + + private void createVirtualMachine() throws Throwable { + board = new R5Board(); + rom = Memory.create(128 * 1024); + ram = Memory.create(32 * 1024 * 1204); + hdd = new VirtIOBlockDevice(board.getMemoryMap(), + ByteBufferBlockDevice.createFromStream(Buildroot.getRootFilesystem(), true)); + uart = new UART16550A(); + + hdd.getInterrupt().set(0x1, board.getInterruptController()); + uart.getInterrupt().set(0x2, board.getInterruptController()); + + board.addDevice(uart); + board.addDevice(hdd); + board.addDevice(0x80000000, rom); + board.addDevice(0x80000000 + 0x400000, ram); + + board.setBootargs("console=ttyS0 root=/dev/vda ro"); + + board.reset(); + + loadProgramFile(rom, Buildroot.getFirmware()); + loadProgramFile(ram, Buildroot.getLinuxImage()); + + runner = new ConsoleRunner(board); + } + + private static void loadProgramFile(final PhysicalMemory memory, final InputStream stream) throws Throwable { + final BufferedInputStream bis = new BufferedInputStream(stream); + for (int address = 0, value = bis.read(); value != -1; value = bis.read(), address++) { + memory.store(address, (byte) value, Sizes.SIZE_8_LOG2); + } + } + + private final class ConsoleRunner extends VirtualMachineRunner { + // Thread-local buffers for lock-free read/writes in inner loop. + private final ByteArrayFIFOQueue outputBuffer = new ByteArrayFIFOQueue(1024); + private final ByteArrayFIFOQueue inputBuffer = new ByteArrayFIFOQueue(32); + + public ConsoleRunner(final R5Board board) { + super(board); + } + + @Override + protected void handleBeforeRun() { + int value; + while ((value = terminal.readInput()) != -1) { + inputBuffer.enqueue((byte) value); + } + } + + @Override + protected void step() { + while (!inputBuffer.isEmpty() && uart.canPutByte()) { + uart.putByte(inputBuffer.dequeueByte()); + } + + int value; + while ((value = uart.read()) != -1) { + outputBuffer.enqueue((byte) value); + } + } + + @Override + protected void handleAfterRun() { + while (!outputBuffer.isEmpty()) { + terminal.putOutput(outputBuffer.dequeueByte()); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/tile/package-info.java b/src/main/java/li/cil/oc2/common/tile/package-info.java new file mode 100644 index 00000000..ebf4625f --- /dev/null +++ b/src/main/java/li/cil/oc2/common/tile/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.common.tile; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/common/vm/package-info.java b/src/main/java/li/cil/oc2/common/vm/package-info.java index 3a46fbbe..0c92df0a 100644 --- a/src/main/java/li/cil/oc2/common/vm/package-info.java +++ b/src/main/java/li/cil/oc2/common/vm/package-info.java @@ -1,4 +1,7 @@ @ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault package li.cil.oc2.common.vm; +import mcp.MethodsReturnNonnullByDefault; + import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/resources/assets/oc2/textures/gui/computer.png b/src/main/resources/assets/oc2/textures/gui/computer.png new file mode 100644 index 00000000..81568131 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/gui/computer.png differ