From e26489d864758abcc968f67ae1ce7f68b5563677 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 25 Mar 2019 01:55:45 +0100 Subject: [PATCH] Black screen, event fixes, error detection Only getting map fields once to speed things up Rendering black screen on first render, which also helps initialize Aparapi Disabling event handlers on stop (can't unregister) Removed and moved unlockMachine() calls from event handlers Detecting failed machine start Added note about sudo --- .../java/sznp/virtualcomputer/Computer.java | 38 ++++++++++++++----- .../java/sznp/virtualcomputer/PluginMain.java | 12 +++++- .../events/EventHandlerBase.java | 11 +++++- .../events/MachineEventHandler.java | 16 +++++++- .../events/VBoxEventHandler.java | 3 -- .../virtualcomputer/renderer/GPURenderer.java | 31 +++++---------- .../renderer/GPURendererInternal.java | 8 +++- .../java/sznp/virtualcomputer/util/Utils.java | 6 ++- 8 files changed, 85 insertions(+), 40 deletions(-) diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/Computer.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/Computer.java index 73bbd7d..70885c4 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/Computer.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/Computer.java @@ -2,13 +2,13 @@ package sznp.virtualcomputer; import com.google.common.collect.Lists; import lombok.Getter; -import lombok.val; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.virtualbox_6_0.*; import sznp.virtualcomputer.events.MachineEventHandler; import sznp.virtualcomputer.events.VBoxEventHandler; +import sznp.virtualcomputer.renderer.GPURendererInternal; import sznp.virtualcomputer.renderer.MCFrameBuffer; import sznp.virtualcomputer.util.Scancode; @@ -23,6 +23,8 @@ public final class Computer { private ISession session; private IVirtualBox vbox; private IMachine machine; + private MachineEventHandler handler; + private IEventListener listener; @java.beans.ConstructorProperties({"plugin"}) public Computer(PluginMain plugin, ISession session, IVirtualBox vbox) { @@ -49,13 +51,13 @@ public final class Computer { session.setName("minecraft"); // machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore machine.lockMachine(session, LockType.VM); // We want the machine inside *our* process <-- Need the VM type to have console access - VBoxEventHandler.getInstance().setup(machine.getId(), sender); + VBoxEventHandler.getInstance().setup(machine.getId(), sender); //TODO: Sometimes null } catch (VBoxException e) { if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited" sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible"); + 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: Can't detect if machine fails to start because of hardening issues - //TODO: This error also occurs if the machine has failed to start at least once (always reassign the machine?) + //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 MCFrameBuffer(session.getConsole().getDisplay(), false))); //sendMessage(sender, "§6Computer started with slower screen. Run as root to use a faster method."); @@ -74,8 +76,8 @@ public final class Computer { public void onLock(CommandSender sender) { machine = session.getMachine(); // This is the Machine object we can work with final IConsole console = session.getConsole(); - val handler = new MachineEventHandler(Computer.this); - handler.registerTo(console.getEventSource()); + handler = new MachineEventHandler(Computer.this); + listener = handler.registerTo(console.getEventSource()); IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2 handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way? console.getDisplay().attachFramebuffer(0L, @@ -99,7 +101,6 @@ public final class Computer { } sendMessage(sender, "§eStopping computer..."); session.getConsole().powerDown().waitForCompletion(2000); - session.unlockMachine(); sendMessage(sender, "§eComputer stopped."); } @@ -192,7 +193,26 @@ public final class Computer { } public void onMachineStop() { - session.unlockMachine(); - plugin.getLogger().info("Computer powered off."); + System.out.println("Unlocking machine..."); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + if (session.getState() == SessionState.Locked) { + System.out.println("Unlocking..."); + session.unlockMachine(); //Needs to be outside of the event handler + System.out.println("Machine unlocked."); + } + }); + System.out.println("Setting pixels..."); + GPURendererInternal.setPixels(new byte[1], 0, 0); //Black screen + System.out.println("Stopping events..."); + stopEvents(); + plugin.getLogger().info("Computer powered off."); + } + + public void stopEvents() { + /*if(session!=null && listener!=null) + session.getConsole().getEventSource().unregisterListener(listener);*/ + if (listener != null) + handler.disable(); + listener = null; } } diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/PluginMain.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/PluginMain.java index 37e86ce..b4ec080 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/PluginMain.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/PluginMain.java @@ -27,6 +27,8 @@ public class PluginMain extends JavaPlugin { private IMachine machine; private BukkitTask screenupdatetask; private BukkitTask mousetask; + private IEventListener listener; + private IEventSource source; public static PluginMain Instance; //public static ByteBuffer allpixels = ByteBuffer.allocate(640 * 480 * 4); // It's set on each change @@ -70,7 +72,7 @@ public class PluginMain extends JavaPlugin { VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom"); vbl.RTR3InitExe(0, "", 0); vbox = manager.getVBox(); - new VBoxEventHandler().registerTo(vbox.getEventSource()); + listener = new VBoxEventHandler().registerTo(source = vbox.getEventSource()); session = manager.getSessionObject(); new Computer(this, session, vbox); //Saves itself ccs.sendMessage("§bLoading Screen..."); @@ -109,6 +111,14 @@ public class PluginMain extends JavaPlugin { public void onDisable() { ConsoleCommandSender ccs = getServer().getConsoleSender(); mousetask.cancel(); + /*try { + source.unregisterListener(listener); + } catch (VBoxException e) { //"Listener was never registered" + e.printStackTrace(); - VBox claims the listener was never registered (can double register as well) + }*/ + ((VBoxEventHandler) listener.getTypedWrapped()).disable(); //The save progress wait locks with the event + if (Computer.getInstance() != null) + Computer.getInstance().stopEvents(); if (session.getState() == SessionState.Locked) { if (session.getMachine().getState().equals(MachineState.Running)) { ccs.sendMessage("§aSaving machine state..."); diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/events/EventHandlerBase.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/events/EventHandlerBase.java index 475aa23..2aafd3d 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/events/EventHandlerBase.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/events/EventHandlerBase.java @@ -22,6 +22,7 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi * The events to listen for. It will only look for these handlers. */ private final Map> eventMap; + private boolean enabled = true; protected EventHandlerBase(Map> eventMap) { //this.eventMap = eventMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().value(), Map.Entry::getValue)); @@ -31,6 +32,8 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi @Override public final void handleEvent(IEvent iEvent) { //val cl=eventMap.get((int)iEvent.getType()); - We can afford to search through the events for this handler + if (!enabled) + return; val kv = eventMap.entrySet().stream().filter(e -> e.getKey().value() == iEvent.getType()).findAny(); if (!kv.isPresent()) return; //Event not supported val cl = kv.get().getValue(); @@ -50,7 +53,11 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi } } - public void registerTo(IEventSource source) { - Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet())); + public org.virtualbox_6_0.IEventListener registerTo(IEventSource source) { + return Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet())); + } + + public void disable() { + enabled = false; } } diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java index 131dcd9..cf42dd5 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/events/MachineEventHandler.java @@ -9,6 +9,7 @@ import sznp.virtualcomputer.Computer; public class MachineEventHandler extends EventHandlerBase { private final Computer computer; + private boolean starting = false; public MachineEventHandler(Computer computer) { super(ImmutableMap.of(VBoxEventType.OnStateChanged, IStateChangedEvent.class, @@ -18,13 +19,26 @@ public class MachineEventHandler extends EventHandlerBase { @EventHandler public void handleStateChange(IStateChangedEvent event) { //https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#a80b08f71210afe16038e904a656ed9eb + System.out.println("State event: " + event.getState()); switch (event.getState()) { case Stuck: computer.Stop(null); break; case PoweredOff: case Saved: + if (starting) { + System.out.println("Failed to start computer! See the VM's log for more details."); + starting = false; //TODO: Sender + } computer.onMachineStop(); - } + break; + case Starting: + starting = true; + break; + case Running: + System.out.println("Computer is running."); + starting = false; + break; + } } } diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/events/VBoxEventHandler.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/events/VBoxEventHandler.java index cf2991a..3a597f5 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/events/VBoxEventHandler.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/events/VBoxEventHandler.java @@ -22,10 +22,7 @@ public class VBoxEventHandler extends EventHandlerBase { @EventHandler public void onSessionStateChange(ISessionStateChangedEvent event) { - System.out.println("Session change event: " + event); - System.out.println("ID1: " + event.getMachineId() + " - ID2: " + machineID); if (!event.getMachineId().equals(machineID)) return; - System.out.println("State: " + event.getState()); if (event.getState() == SessionState.Locked) //Need to check here, because we can't access the console yet Computer.getInstance().onLock(sender); } diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURenderer.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURenderer.java index 4606246..e67a101 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURenderer.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURenderer.java @@ -1,10 +1,7 @@ package sznp.virtualcomputer.renderer; -import lombok.val; import net.minecraft.server.v1_12_R1.WorldMap; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_12_R1.map.RenderData; import org.bukkit.entity.Player; import org.bukkit.map.MapCanvas; import org.bukkit.map.MapPalette; @@ -14,23 +11,20 @@ import sznp.virtualcomputer.util.Timing; import java.awt.*; import java.lang.reflect.Field; -import java.util.Map; public class GPURenderer extends MapRenderer implements IRenderer { private byte[] buffer; private GPURendererInternal kernel; + private WorldMap wmap; //Store at central location after conversion private static int[] colors_; public GPURenderer(short id, World world, int mapx, int mapy) throws Exception { MapView map = IRenderer.prepare(id, world); if (map == null) return; //Testing - Field field = map.getClass().getDeclaredField("renderCache"); - field.setAccessible(true); - @SuppressWarnings("unchecked") val renderCache = (Map) field.get(map); if (colors_ == null) { - field = MapPalette.class.getDeclaredField("colors"); + Field field = MapPalette.class.getDeclaredField("colors"); field.setAccessible(true); Color[] cs = (Color[]) field.get(null); colors_ = new int[cs.length]; @@ -38,15 +32,11 @@ public class GPURenderer extends MapRenderer implements IRenderer { colors_[i] = cs[i].getRGB(); } } + Field field = map.getClass().getDeclaredField("worldMap"); + field.setAccessible(true); + wmap = (WorldMap) field.get(map); kernel = new GPURendererInternal(mapx, mapy, colors_); - RenderData render = renderCache.get(null); - - if (render == null) - renderCache.put(null, render = new RenderData()); - - this.buffer = render.buffer; - //System.setProperty("com.codegen.config.enable.NEW", "true"); map.addRenderer(this); @@ -57,13 +47,12 @@ public class GPURenderer extends MapRenderer implements IRenderer { Timing t = new Timing(); try { if (kernel.isRendered()) return; //TODO: Stop rendering after computer is stopped - Field field = canvas.getClass().getDeclaredField("buffer"); - field.setAccessible(true); - buffer = (byte[]) field.get(canvas); + if (buffer == null) { //The buffer remains the same, as the canvas remains the same + Field field = canvas.getClass().getDeclaredField("buffer"); + field.setAccessible(true); + buffer = (byte[]) field.get(canvas); + } kernel.render(buffer); - field = map.getClass().getDeclaredField("worldMap"); - field.setAccessible(true); - WorldMap wmap = (WorldMap) field.get(map); wmap.flagDirty(0, 0); wmap.flagDirty(127, 127); // Send the whole image - TODO: Only send changes } catch (Exception e) { diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURendererInternal.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURendererInternal.java index 61dfdfa..636bdf3 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURendererInternal.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/renderer/GPURendererInternal.java @@ -20,7 +20,7 @@ public class GPURendererInternal extends Kernel { private byte[] buffer; //References the map buffer private Range range; @Getter - private boolean rendered = true; + private boolean rendered; private static ArrayList renderers = new ArrayList<>(); //public static byte[] test=new byte[1]; - LAMBDAS @@ -29,6 +29,12 @@ public class GPURendererInternal extends Kernel { this.mapy = mapy; this.colors = colors; range = Range.create2D(128, 128); + + //Do an intial draw of a black screen with Aparapi so it doesn't lag at start + pixels = new byte[1]; + width = height = 0; + rendered = false; + renderers.add(this); } diff --git a/VirtualComputer/src/main/java/sznp/virtualcomputer/util/Utils.java b/VirtualComputer/src/main/java/sznp/virtualcomputer/util/Utils.java index 4269fec..dfe919e 100644 --- a/VirtualComputer/src/main/java/sznp/virtualcomputer/util/Utils.java +++ b/VirtualComputer/src/main/java/sznp/virtualcomputer/util/Utils.java @@ -40,8 +40,10 @@ public class Utils { } //public static void registerListener(IEventSource source, IEventListener listener, VBoxEventType... types) { - public static void registerListener(IEventSource source, IEventListener listener, List types) { - source.registerListener(new org.virtualbox_6_0.IEventListener(listener), types, true); + public static org.virtualbox_6_0.IEventListener registerListener(IEventSource source, IEventListener listener, List types) { + val ret = new org.virtualbox_6_0.IEventListener(listener); + source.registerListener(ret, types, true); + return ret; } @SuppressWarnings("unchecked")