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 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();
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;
}
}

View file

@ -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...");

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.
*/
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) {
//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 <T extends EventHandlerBase> void registerTo(IEventSource source) {
Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet()));
public <T extends EventHandlerBase> org.virtualbox_6_0.IEventListener registerTo(IEventSource source) {
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 {
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;
}
}
}

View file

@ -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);
}

View file

@ -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<CraftPlayer, RenderData>) 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
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) {

View file

@ -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<GPURendererInternal> 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);
}

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