diff --git a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java index 1f60dc5..a601633 100644 --- a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java +++ b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java @@ -17,6 +17,7 @@ import sznp.virtualcomputer.util.Scancode; import javax.annotation.Nullable; import java.util.Arrays; +import java.util.Collections; import java.util.stream.Collectors; public final class Computer { @@ -60,9 +61,11 @@ public final class Computer { } session.setName("minecraft"); VBoxEventHandler.getInstance().setup(machine.getId(), sender); //TODO: Sometimes null - // machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore synchronized (session) { - machine.lockMachine(session, LockType.VM); // We want the machine inside *our* process <-- Need the VM type to have console access + if (plugin.runEmbedded.get()) + machine.lockMachine(session, LockType.VM); //Run in our process <-- Need the VM type to have console access + else + machine.launchVMProcess(session, "headless", Collections.emptyList()); //Run in a separate process } } catch (VBoxException e) { if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited" @@ -70,9 +73,6 @@ public final class Computer { sendMessage(sender, "§6Make sure that the server is running as root (sudo)"); //TODO: If we have VirtualBox open, it won't close the server's port //TODO: "The object in question already exists." on second start - //machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); //No privileges, start the 'old' way - //session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new COMFrameBuffer(session.getConsole().getDisplay(), false))); - //sendMessage(sender, "§6Computer started with slower screen. Run as root to use a faster method."); } else { sendMessage(sender, "§cFailed to start computer: " + e.getMessage()); } @@ -108,7 +108,8 @@ public final class Computer { handler.setProgress(progress); handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way? val fb = new MCFrameBuffer(console.getDisplay()); - fb.start(); + if (plugin.runEmbedded.get()) + fb.start(); String fbid = console.getDisplay().attachFramebuffer(0L, COMUtils.gimmeAFramebuffer(fb)); fb.setId(fbid); diff --git a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/ComputerCommand.java b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/ComputerCommand.java index 72e04e3..227f788 100644 --- a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/ComputerCommand.java +++ b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/ComputerCommand.java @@ -224,6 +224,7 @@ public class ComputerCommand extends ICommand2MC { switch (enableDisable) { case "enable": sender.sendMessage("§bEnabling plugin..."); + PluginMain.Instance.reloadConfig(); PluginMain.Instance.pluginEnableInternal(); sender.sendMessage("§bPlugin enabled! More info on console."); break; diff --git a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/PluginMain.java b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/PluginMain.java index fb9411f..ef17a84 100644 --- a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/PluginMain.java +++ b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/PluginMain.java @@ -48,9 +48,14 @@ public class PluginMain extends ButtonPlugin { */ public final ConfigData startID = getIConfig().getData("startID", (short) 0); /** - * If true, uses the GPU to accelerate screen rendering. Requires root on Linux. + * If true, uses the GPU to accelerate screen rendering. May require root on Linux. */ private final ConfigData useGPU = getIConfig().getData("useGPU", true); + /** + * The virtual machine will be hosted inside the server process if this option is enabled. + * This can improve performance but may cause stability and other issues. + */ + public final ConfigData runEmbedded = getIConfig().getData("runEmbedded", true); /** * Determines the keyboard layout to use for /c show keyboard. Layouts can be defined in VirtualComputer/layouts/. */ diff --git a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java index 8115710..7616ee7 100644 --- a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java +++ b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java @@ -40,14 +40,15 @@ public class MachineEventHandler extends EventHandlerBase { sender.sendMessage("§cFailed to start computer! See the console for more details."); starting = false; Bukkit.getScheduler().runTaskAsynchronously(PluginMain.Instance, () -> { + if (progress == null) return; progress.waitForCompletion(-1); - if (progress != null && progress.getCompleted() && progress.getResultCode() != 0) { + if (progress.getCompleted() && progress.getResultCode() != 0) { Logger l = PluginMain.Instance.getLogger(); l.warning("Result code: " + Integer.toHexString(progress.getResultCode())); for (var info = progress.getErrorInfo(); info != null; info = info.getNext()) { l.warning("----------------"); if (info.getResultCode() == 0x80004005 && info.getResultDetail() == 0xFFFFF88B) - l.warning("The server cannot access the VirtualBox driver, run it with sudo. Make sure to only run plugins you trust."); + l.warning("The server cannot access the VirtualBox driver. Either run it as root or disable the 'runEmbedded' config option. Make sure to only run plugins you trust as root."); else { l.warning("VBox: " + info.getText()); l.warning("Component: " + info.getComponent()); diff --git a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/renderer/MCFrameBuffer.java b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/renderer/MCFrameBuffer.java index 1c88134..201aa52 100644 --- a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/renderer/MCFrameBuffer.java +++ b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/renderer/MCFrameBuffer.java @@ -22,7 +22,14 @@ public class MCFrameBuffer implements IMCFrameBuffer { private final IDisplay display; private final Holder holder = new Holder<>(); private BukkitTask tt; + /** + * Used when running embedded + */ private Pointer pointer; + /** + * Used when not running embedded + */ + private byte[] screenImage; //TODO: Remove PluginMain.allpixels (and other PluginMain references) private int width; private int height; @Getter @@ -38,7 +45,14 @@ public class MCFrameBuffer implements IMCFrameBuffer { tt = Bukkit.getScheduler().runTaskAsynchronously(PluginMain.Instance, () -> { synchronized (this) { //If a change occurs twice, then wait for it try { - //System.out.println("Change: " + xOrigin + " " + yOrigin + " - " + width + " " + height); + if (!PluginMain.Instance.runEmbedded.get()) { //Running separately + this.width = (int) width; + this.height = (int) height; + if (screenImage == null || screenImage.length != width * height * 4) + screenImage = new byte[(int) (width * height * 4)]; + updateScreen(screenImage); + return; + } display.querySourceBitmap(0L, holder); long[] ptr = new long[1], w = new long[1], h = new long[1], bpp = new long[1], bpl = new long[1], pf = new long[1]; COMUtils.queryBitmapInfo(holder.value, ptr, w, h, bpp, bpl, pf); @@ -46,9 +60,6 @@ public class MCFrameBuffer implements IMCFrameBuffer { pointer = new Pointer(ptr[0]); this.width = (int) w[0]; this.height = (int) h[0]; - //System.out.println("Actual sizes: " + this.width + " " + this.height); - /*if (this.width > 1024 || this.height > 768) - return;*/ GPURenderer.update(pointer.getByteArray(0L, (int) (w[0] * h[0] * 4)), (int) w[0], (int) h[0], 0, 0, this.width, this.height); } else { PluginMain.allpixels = new Pointer(ptr[0]).getByteBuffer(0L, width * height * 4); @@ -65,9 +76,7 @@ public class MCFrameBuffer implements IMCFrameBuffer { e.printStackTrace(); } catch (Throwable t) { t.printStackTrace(); - } /*finally { - System.out.println("Change finished"); - }*/ + } } }); } @@ -87,6 +96,13 @@ public class MCFrameBuffer implements IMCFrameBuffer { @Override public void notifyUpdateImage(long x, long y, long width, long height, byte[] image) { System.out.println("Update image!"); + if (this.width == 0 || this.height == 0) { + PluginMain.Instance.getLogger().warning("Received screen image before resolution change!"); + return; + } + for (int i = 0; i < height; i++) //Copy lines of the screen in a fast way + System.arraycopy(image, (int) (i * width * 4), screenImage, (int) (x + y * this.width * 4), (int) width * 4); + updateScreen(image); } public void start() { @@ -105,14 +121,8 @@ public class MCFrameBuffer implements IMCFrameBuffer { continue; } if (!running) return; - //System.out.println("Update: " + x + " " + y + " - " + width + " " + height); - Timing t = new Timing(); //TODO: Add support for only sending changed fragments - GPURenderer.update(pointer.getByteArray(0L, this.width * this.height * 4), this.width, this.height, (int) 0, (int) 0, (int) width, (int) height); - if (t.elapsedMS() > 60) //Typically 1ms max - System.out.println("Update took " + t.elapsedMS() + "ms"); + updateScreen(pointer.getByteArray(0L, this.width * this.height * 4)); shouldUpdate.set(false); - /*else - System.out.println("Update finished");*/ } } } catch (InterruptedException ignored) { @@ -120,6 +130,13 @@ public class MCFrameBuffer implements IMCFrameBuffer { }); } + private void updateScreen(byte[] pixels) { + Timing t = new Timing(); //TODO: Add support for only sending changed fragments + GPURenderer.update(pixels, this.width, this.height, (int) 0, (int) 0, (int) width, (int) height); + if (t.elapsedMS() > 60) //Typically 1ms max + PluginMain.Instance.getLogger().warning("Update took " + t.elapsedMS() + "ms"); + } + public void stop() { synchronized (this) { running = false;