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
This commit is contained in:
Norbi Peti 2019-03-25 01:55:45 +01:00
parent c8ea718787
commit e26489d864
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
8 changed files with 85 additions and 40 deletions

View file

@ -2,13 +2,13 @@ package sznp.virtualcomputer;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.Getter; import lombok.Getter;
import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.virtualbox_6_0.*; import org.virtualbox_6_0.*;
import sznp.virtualcomputer.events.MachineEventHandler; import sznp.virtualcomputer.events.MachineEventHandler;
import sznp.virtualcomputer.events.VBoxEventHandler; import sznp.virtualcomputer.events.VBoxEventHandler;
import sznp.virtualcomputer.renderer.GPURendererInternal;
import sznp.virtualcomputer.renderer.MCFrameBuffer; import sznp.virtualcomputer.renderer.MCFrameBuffer;
import sznp.virtualcomputer.util.Scancode; import sznp.virtualcomputer.util.Scancode;
@ -23,6 +23,8 @@ public final class Computer {
private ISession session; private ISession session;
private IVirtualBox vbox; private IVirtualBox vbox;
private IMachine machine; private IMachine machine;
private MachineEventHandler handler;
private IEventListener listener;
@java.beans.ConstructorProperties({"plugin"}) @java.beans.ConstructorProperties({"plugin"})
public Computer(PluginMain plugin, ISession session, IVirtualBox vbox) { public Computer(PluginMain plugin, ISession session, IVirtualBox vbox) {
@ -49,13 +51,13 @@ public final class Computer {
session.setName("minecraft"); session.setName("minecraft");
// machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore // 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 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) { } catch (VBoxException e) {
if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited" if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited"
sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible"); 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: 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: "The object in question already exists." on second start
//TODO: This error also occurs if the machine has failed to start at least once (always reassign the machine?)
//machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); //No privileges, start the 'old' way //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))); //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."); //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) { public void onLock(CommandSender sender) {
machine = session.getMachine(); // This is the Machine object we can work with machine = session.getMachine(); // This is the Machine object we can work with
final IConsole console = session.getConsole(); final IConsole console = session.getConsole();
val handler = new MachineEventHandler(Computer.this); handler = new MachineEventHandler(Computer.this);
handler.registerTo(console.getEventSource()); listener = handler.registerTo(console.getEventSource());
IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2 IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way? handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way?
console.getDisplay().attachFramebuffer(0L, console.getDisplay().attachFramebuffer(0L,
@ -99,7 +101,6 @@ public final class Computer {
} }
sendMessage(sender, "§eStopping computer..."); sendMessage(sender, "§eStopping computer...");
session.getConsole().powerDown().waitForCompletion(2000); session.getConsole().powerDown().waitForCompletion(2000);
session.unlockMachine();
sendMessage(sender, "§eComputer stopped."); sendMessage(sender, "§eComputer stopped.");
} }
@ -192,7 +193,26 @@ public final class Computer {
} }
public void onMachineStop() { public void onMachineStop() {
session.unlockMachine(); System.out.println("Unlocking machine...");
plugin.getLogger().info("Computer powered off."); 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;
} }
} }

View file

@ -27,6 +27,8 @@ public class PluginMain extends JavaPlugin {
private IMachine machine; private IMachine machine;
private BukkitTask screenupdatetask; private BukkitTask screenupdatetask;
private BukkitTask mousetask; private BukkitTask mousetask;
private IEventListener listener;
private IEventSource source;
public static PluginMain Instance; public static PluginMain Instance;
//public static ByteBuffer allpixels = ByteBuffer.allocate(640 * 480 * 4); // It's set on each change //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"); VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom");
vbl.RTR3InitExe(0, "", 0); vbl.RTR3InitExe(0, "", 0);
vbox = manager.getVBox(); vbox = manager.getVBox();
new VBoxEventHandler().registerTo(vbox.getEventSource()); listener = new VBoxEventHandler().registerTo(source = vbox.getEventSource());
session = manager.getSessionObject(); session = manager.getSessionObject();
new Computer(this, session, vbox); //Saves itself new Computer(this, session, vbox); //Saves itself
ccs.sendMessage("§bLoading Screen..."); ccs.sendMessage("§bLoading Screen...");
@ -109,6 +111,14 @@ public class PluginMain extends JavaPlugin {
public void onDisable() { public void onDisable() {
ConsoleCommandSender ccs = getServer().getConsoleSender(); ConsoleCommandSender ccs = getServer().getConsoleSender();
mousetask.cancel(); 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.getState() == SessionState.Locked) {
if (session.getMachine().getState().equals(MachineState.Running)) { if (session.getMachine().getState().equals(MachineState.Running)) {
ccs.sendMessage("§aSaving machine state..."); ccs.sendMessage("§aSaving machine state...");

View file

@ -22,6 +22,7 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi
* The events to listen for. It will only look for these handlers. * The events to listen for. It will only look for these handlers.
*/ */
private final Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap; private final Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap;
private boolean enabled = true;
protected EventHandlerBase(Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap) { protected EventHandlerBase(Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap) {
//this.eventMap = eventMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().value(), Map.Entry::getValue)); //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 @Override
public final void handleEvent(IEvent iEvent) { public final void handleEvent(IEvent iEvent) {
//val cl=eventMap.get((int)iEvent.getType()); - We can afford to search through the events for this handler //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(); val kv = eventMap.entrySet().stream().filter(e -> e.getKey().value() == iEvent.getType()).findAny();
if (!kv.isPresent()) return; //Event not supported if (!kv.isPresent()) return; //Event not supported
val cl = kv.get().getValue(); val cl = kv.get().getValue();
@ -50,7 +53,11 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi
} }
} }
public <T extends EventHandlerBase> void registerTo(IEventSource source) { public <T extends EventHandlerBase> org.virtualbox_6_0.IEventListener registerTo(IEventSource source) {
Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet())); return Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet()));
}
public void disable() {
enabled = false;
} }
} }

View file

@ -9,6 +9,7 @@ import sznp.virtualcomputer.Computer;
public class MachineEventHandler extends EventHandlerBase { public class MachineEventHandler extends EventHandlerBase {
private final Computer computer; private final Computer computer;
private boolean starting = false;
public MachineEventHandler(Computer computer) { public MachineEventHandler(Computer computer) {
super(ImmutableMap.of(VBoxEventType.OnStateChanged, IStateChangedEvent.class, super(ImmutableMap.of(VBoxEventType.OnStateChanged, IStateChangedEvent.class,
@ -18,13 +19,26 @@ public class MachineEventHandler extends EventHandlerBase {
@EventHandler @EventHandler
public void handleStateChange(IStateChangedEvent event) { //https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#a80b08f71210afe16038e904a656ed9eb 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()) { switch (event.getState()) {
case Stuck: case Stuck:
computer.Stop(null); computer.Stop(null);
break; break;
case PoweredOff: case PoweredOff:
case Saved: 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(); computer.onMachineStop();
} break;
case Starting:
starting = true;
break;
case Running:
System.out.println("Computer is running.");
starting = false;
break;
}
} }
} }

View file

@ -22,10 +22,7 @@ public class VBoxEventHandler extends EventHandlerBase {
@EventHandler @EventHandler
public void onSessionStateChange(ISessionStateChangedEvent event) { 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; 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 if (event.getState() == SessionState.Locked) //Need to check here, because we can't access the console yet
Computer.getInstance().onLock(sender); Computer.getInstance().onLock(sender);
} }

View file

@ -1,10 +1,7 @@
package sznp.virtualcomputer.renderer; package sznp.virtualcomputer.renderer;
import lombok.val;
import net.minecraft.server.v1_12_R1.WorldMap; import net.minecraft.server.v1_12_R1.WorldMap;
import org.bukkit.World; 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.entity.Player;
import org.bukkit.map.MapCanvas; import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapPalette; import org.bukkit.map.MapPalette;
@ -14,23 +11,20 @@ import sznp.virtualcomputer.util.Timing;
import java.awt.*; import java.awt.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Map;
public class GPURenderer extends MapRenderer implements IRenderer { public class GPURenderer extends MapRenderer implements IRenderer {
private byte[] buffer; private byte[] buffer;
private GPURendererInternal kernel; private GPURendererInternal kernel;
private WorldMap wmap;
//Store at central location after conversion //Store at central location after conversion
private static int[] colors_; private static int[] colors_;
public GPURenderer(short id, World world, int mapx, int mapy) throws Exception { public GPURenderer(short id, World world, int mapx, int mapy) throws Exception {
MapView map = IRenderer.prepare(id, world); MapView map = IRenderer.prepare(id, world);
if (map == null) return; //Testing if (map == null) return; //Testing
Field field = map.getClass().getDeclaredField("renderCache");
field.setAccessible(true);
@SuppressWarnings("unchecked") val renderCache = (Map<CraftPlayer, RenderData>) field.get(map);
if (colors_ == null) { if (colors_ == null) {
field = MapPalette.class.getDeclaredField("colors"); Field field = MapPalette.class.getDeclaredField("colors");
field.setAccessible(true); field.setAccessible(true);
Color[] cs = (Color[]) field.get(null); Color[] cs = (Color[]) field.get(null);
colors_ = new int[cs.length]; colors_ = new int[cs.length];
@ -38,15 +32,11 @@ public class GPURenderer extends MapRenderer implements IRenderer {
colors_[i] = cs[i].getRGB(); 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_); 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"); //System.setProperty("com.codegen.config.enable.NEW", "true");
map.addRenderer(this); map.addRenderer(this);
@ -57,13 +47,12 @@ public class GPURenderer extends MapRenderer implements IRenderer {
Timing t = new Timing(); Timing t = new Timing();
try { try {
if (kernel.isRendered()) return; //TODO: Stop rendering after computer is stopped if (kernel.isRendered()) return; //TODO: Stop rendering after computer is stopped
Field field = canvas.getClass().getDeclaredField("buffer"); if (buffer == null) { //The buffer remains the same, as the canvas remains the same
field.setAccessible(true); Field field = canvas.getClass().getDeclaredField("buffer");
buffer = (byte[]) field.get(canvas); field.setAccessible(true);
buffer = (byte[]) field.get(canvas);
}
kernel.render(buffer); kernel.render(buffer);
field = map.getClass().getDeclaredField("worldMap");
field.setAccessible(true);
WorldMap wmap = (WorldMap) field.get(map);
wmap.flagDirty(0, 0); wmap.flagDirty(0, 0);
wmap.flagDirty(127, 127); // Send the whole image - TODO: Only send changes wmap.flagDirty(127, 127); // Send the whole image - TODO: Only send changes
} catch (Exception e) { } catch (Exception e) {

View file

@ -20,7 +20,7 @@ public class GPURendererInternal extends Kernel {
private byte[] buffer; //References the map buffer private byte[] buffer; //References the map buffer
private Range range; private Range range;
@Getter @Getter
private boolean rendered = true; private boolean rendered;
private static ArrayList<GPURendererInternal> renderers = new ArrayList<>(); private static ArrayList<GPURendererInternal> renderers = new ArrayList<>();
//public static byte[] test=new byte[1]; - LAMBDAS //public static byte[] test=new byte[1]; - LAMBDAS
@ -29,6 +29,12 @@ public class GPURendererInternal extends Kernel {
this.mapy = mapy; this.mapy = mapy;
this.colors = colors; this.colors = colors;
range = Range.create2D(128, 128); 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); renderers.add(this);
} }

View file

@ -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, VBoxEventType... types) {
public static void registerListener(IEventSource source, IEventListener listener, List<VBoxEventType> types) { public static org.virtualbox_6_0.IEventListener registerListener(IEventSource source, IEventListener listener, List<VBoxEventType> types) {
source.registerListener(new org.virtualbox_6_0.IEventListener(listener), types, true); val ret = new org.virtualbox_6_0.IEventListener(listener);
source.registerListener(ret, types, true);
return ret;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")