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