restart branch

This commit is contained in:
ColdIce
2022-02-16 01:25:15 -06:00
parent 7fa7e6c2cc
commit e18ce338e9
6 changed files with 903 additions and 1 deletions

View File

@@ -16,6 +16,8 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;
import dev.slimevr.bridge.Bridge;
import dev.slimevr.platform.linux.LinuxNamedPipeBridge;
import dev.slimevr.platform.linux.LinuxSteamVRPipeInputBridge;
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
import dev.slimevr.platform.windows.WindowsSteamVRPipeInputBridge;
import dev.slimevr.bridge.VMCBridge;
@@ -62,7 +64,7 @@ public class VRServer extends Thread {
// Start server for SlimeVR trackers
trackersServer = new TrackersUDPServer(6969, "Sensors UDP server", this::registerTracker);
// OpenVR bridge currently only supports Windows
// OpenVR bridge currently only supports Windows and Linux
if(OperatingSystem.getCurrentPlatform() == OperatingSystem.WINDOWS) {
/*
// Create named pipe bridge for SteamVR driver
@@ -78,6 +80,21 @@ public class VRServer extends Thread {
WindowsNamedPipeBridge driverBridge = new WindowsNamedPipeBridge(hmdTracker, "steamvr", "SteamVR Driver Bridge", "\\\\.\\pipe\\SlimeVRDriver", shareTrackers);
tasks.add(() -> driverBridge.startBridge());
bridges.add(driverBridge);
} else if (OperatingSystem.getCurrentPlatform() == OperatingSystem.LINUX) {
/*
// Create named pipe bridge for SteamVR driver
NamedPipeVRBridge driverBridge = new NamedPipeVRBridge(hmdTracker, shareTrackers, this);
tasks.add(() -> driverBridge.startBridge());
bridges.add(driverBridge);
//*/
// Create named pipe bridge for SteamVR input
LinuxSteamVRPipeInputBridge steamVRInput = new LinuxSteamVRPipeInputBridge(this);
tasks.add(() -> steamVRInput.startBridge());
bridges.add(steamVRInput);
//*/
LinuxNamedPipeBridge driverBridge = new LinuxNamedPipeBridge(hmdTracker, "steamvr", "SteamVR Driver Bridge", "\\\\.\\pipe\\SlimeVRDriver", shareTrackers);
tasks.add(() -> driverBridge.startBridge());
bridges.add(driverBridge);
}
// Create WebSocket server

View File

@@ -7,6 +7,7 @@ import javax.swing.event.MouseInputAdapter;
import dev.slimevr.Main;
import dev.slimevr.VRServer;
import dev.slimevr.platform.linux.LinuxNamedPipeBridge;
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
import dev.slimevr.gui.swing.ButtonTimer;
import dev.slimevr.gui.swing.EJBagNoStretch;
@@ -337,6 +338,70 @@ public class VRServerGUI extends JFrame {
add(Box.createVerticalStrut(10));
} else if(server.hasBridge(LinuxNamedPipeBridge.class)) { // Linux GUI ver
LinuxNamedPipeBridge br = server.getVRBridge(LinuxNamedPipeBridge.class);
add(l = new JLabel("SteamVR Trackers"));
l.setFont(l.getFont().deriveFont(Font.BOLD));
l.setAlignmentX(0.5f);
add(l = new JLabel("Changes may require restart of SteamVR"));
l.setFont(l.getFont().deriveFont(Font.ITALIC));
l.setAlignmentX(0.5f);
add(new EJBagNoStretch(false, true) {{
JCheckBox waistCb;
add(waistCb = new JCheckBox("Waist"), c(1, 1));
waistCb.setSelected(br.getShareSetting(TrackerRole.WAIST));
waistCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
server.queueTask(() -> {
br.changeShareSettings(TrackerRole.WAIST, waistCb.isSelected());
});
}
});
JCheckBox legsCb;
add(legsCb = new JCheckBox("Legs"), c(2, 1));
legsCb.setSelected(br.getShareSetting(TrackerRole.LEFT_FOOT) && br.getShareSetting(TrackerRole.RIGHT_FOOT));
legsCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
server.queueTask(() -> {
br.changeShareSettings(TrackerRole.LEFT_FOOT, legsCb.isSelected());
br.changeShareSettings(TrackerRole.RIGHT_FOOT, legsCb.isSelected());
});
}
});
JCheckBox chestCb;
add(chestCb = new JCheckBox("Chest"), c(1, 2));
chestCb.setSelected(br.getShareSetting(TrackerRole.CHEST));
chestCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
server.queueTask(() -> {
br.changeShareSettings(TrackerRole.CHEST, chestCb.isSelected());
});
}
});
JCheckBox kneesCb;
add(kneesCb = new JCheckBox("Knees"), c(2, 2));
kneesCb.setSelected(br.getShareSetting(TrackerRole.LEFT_KNEE) && br.getShareSetting(TrackerRole.RIGHT_KNEE));
kneesCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
server.queueTask(() -> {
br.changeShareSettings(TrackerRole.LEFT_KNEE, kneesCb.isSelected());
br.changeShareSettings(TrackerRole.RIGHT_KNEE, kneesCb.isSelected());
});
}
});
}});
add(Box.createVerticalStrut(10));
}
add(new JLabel("Skeleton data"));
add(skeletonList);

View File

@@ -0,0 +1,221 @@
package dev.slimevr.platform.linux;
import com.google.protobuf.CodedOutputStream;
import java.nio.*;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import dev.slimevr.Main;
import dev.slimevr.bridge.BridgeThread;
import dev.slimevr.bridge.PipeState;
import dev.slimevr.bridge.ProtobufBridge;
import dev.slimevr.bridge.ProtobufMessages;
import dev.slimevr.util.ann.VRServerThread;
import dev.slimevr.vr.trackers.*;
import io.eiren.util.logging.LogManager;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.List;
public class LinuxNamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnable {
private final TrackerRole[] defaultRoles = new TrackerRole[] {TrackerRole.WAIST, TrackerRole.LEFT_FOOT, TrackerRole.RIGHT_FOOT};
private final byte[] buffArray = new byte[2048];
protected LinuxPipe pipe;
protected final String pipeName;
protected final String bridgeSettingsKey;
protected final Thread runnerThread;
private final List<? extends ShareableTracker> shareableTrackers;
public LinuxNamedPipeBridge(HMDTracker hmd, String bridgeSettingsKey, String bridgeName, String pipeName, List<? extends ShareableTracker> shareableTrackers) {
super(bridgeName, hmd);
this.pipeName = pipeName;
this.bridgeSettingsKey = bridgeSettingsKey;
this.runnerThread = new Thread(this, "Named pipe thread");
this.shareableTrackers = shareableTrackers;
}
@Override
@VRServerThread
public void startBridge() {
for(TrackerRole role : defaultRoles) {
changeShareSettings(role, Main.vrServer.config.getBoolean("bridge." + bridgeSettingsKey + ".trackers." + role.name().toLowerCase(), true));
}
for(int i = 0; i < shareableTrackers.size(); ++i) {
ShareableTracker tr = shareableTrackers.get(i);
TrackerRole role = tr.getTrackerRole();
changeShareSettings(role, Main.vrServer.config.getBoolean("bridge." + bridgeSettingsKey + ".trackers." + role.name().toLowerCase(), false));
}
runnerThread.start();
}
@VRServerThread
public boolean getShareSetting(TrackerRole role) {
for(int i = 0; i < shareableTrackers.size(); ++i) {
ShareableTracker tr = shareableTrackers.get(i);
if(tr.getTrackerRole() == role) {
return sharedTrackers.contains(tr);
}
}
return false;
}
@VRServerThread
public void changeShareSettings(TrackerRole role, boolean share) {
if(role == null)
return;
for(int i = 0; i < shareableTrackers.size(); ++i) {
ShareableTracker tr = shareableTrackers.get(i);
if(tr.getTrackerRole() == role) {
if(share) {
addSharedTracker(tr);
} else {
removeSharedTracker(tr);
}
Main.vrServer.config.setProperty("bridge." + bridgeSettingsKey + ".trackers." + role.name().toLowerCase(), share);
Main.vrServer.saveConfig();
}
}
}
@Override
@VRServerThread
protected VRTracker createNewTracker(ProtobufMessages.TrackerAdded trackerAdded) {
VRTracker tracker = new VRTracker(trackerAdded.getTrackerId(), trackerAdded.getTrackerSerial(), trackerAdded.getTrackerName(), true, true);
TrackerRole role = TrackerRole.getById(trackerAdded.getTrackerRole());
if(role != null) {
tracker.setBodyPosition(TrackerPosition.getByRole(role));
}
return tracker;
}
@Override
@BridgeThread
public void run() {
try {
createPipe();
while(true) {
boolean pipesUpdated = false;
if(pipe.state == PipeState.CREATED) {
tryOpeningPipe(pipe);
}
if(pipe.state == PipeState.OPEN) {
pipesUpdated = updatePipe();
updateMessageQueue();
}
if(pipe.state == PipeState.ERROR) {
resetPipe();
}
if(!pipesUpdated) {
try {
Thread.sleep(5); // Up to 200Hz
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
} catch(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
@BridgeThread
protected boolean sendMessageReal(ProtobufMessages.ProtobufMessage message) {
if(pipe.state == PipeState.OPEN) {
try {
int size = message.getSerializedSize();
CodedOutputStream os = CodedOutputStream.newInstance(buffArray, 4, size);
message.writeTo(os);
size += 4;
buffArray[0] = (byte) (size & 0xFF);
buffArray[1] = (byte) ((size >> 8) & 0xFF);
buffArray[2] = (byte) ((size >> 16) & 0xFF);
buffArray[3] = (byte) ((size >> 24) & 0xFF);
try {
pipe.pipe.write(ByteBuffer.wrap(buffArray));
return true;
} catch (IOException e) {
pipe.state = PipeState.ERROR;
LogManager.log.severe("[" + bridgeName + "] Pipe error: " + Native.getLastError());
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private boolean updatePipe() throws IOException {
if(pipe.state == PipeState.OPEN) {
boolean readAnything = false;
IntByReference bytesAvailable = new IntByReference(0);
while(pipe.pipe.isOpen()) {
if(bytesAvailable.getValue() >= 4) { // Got size
int messageLength = (buffArray[3] << 24) | (buffArray[2] << 16) | (buffArray[1] << 8) | buffArray[0];
if(messageLength > 1024) { // Overflow
LogManager.log.severe("[" + bridgeName + "] Pipe overflow. Message length: " + messageLength);
pipe.state = PipeState.ERROR;
return readAnything;
}
if(bytesAvailable.getValue() >= messageLength) {
if(pipe.pipe.read(ByteBuffer.wrap(buffArray)) == 0) {
ProtobufMessages.ProtobufMessage message = ProtobufMessages.ProtobufMessage.parser().parseFrom(buffArray, 4, messageLength - 4);
messageReceived(message);
readAnything = true;
} else {
pipe.state = PipeState.ERROR;
LogManager.log.severe("[" + bridgeName + "] Pipe error: " + Native.getLastError());
return readAnything;
}
} else {
return readAnything; // Wait for more data
}
} else {
return readAnything; // Wait for more data
}
}
pipe.state = PipeState.ERROR;
LogManager.log.severe("[" + bridgeName + "] Pipe error: " + Native.getLastError());
}
return false;
}
private void resetPipe() {
LinuxPipe.safeDisconnect(pipe);
pipe.state = PipeState.CREATED;
Main.vrServer.queueTask(this::disconnected);
}
private void createPipe() throws IOException {
try {
RandomAccessFile rw = new RandomAccessFile(pipeName, "rw");
FileChannel fc = rw.getChannel();
pipe = new LinuxPipe(fc, pipeName); // lpSecurityAttributes
LogManager.log.info("[SteamVRPipeInputBridge] Pipe " + pipe.name + " created");
//if(pipe.pipe.)
// throw new IOException("Can't open " + pipeName + " pipe: " + Native.getLastError());
LogManager.log.info("[SteamVRPipeInputBridge] Pipes are open");
} catch(IOException e) {
LinuxPipe.safeDisconnect(pipe);
throw e;
}
}
private boolean tryOpeningPipe(LinuxPipe pipe) {
if(pipe.pipe.isOpen()) {
pipe.state = PipeState.OPEN;
LogManager.log.info("[" + bridgeName + "] Pipe " + pipe.name + " is open");
Main.vrServer.queueTask(this::reconnected);
return true;
}
LogManager.log.info("[" + bridgeName + "] Error connecting to pipe " + pipe.name + ": " + Native.getLastError());
return false;
}
}

View File

@@ -0,0 +1,282 @@
package dev.slimevr.platform.linux;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.Bridge;
import dev.slimevr.bridge.PipeState;
import dev.slimevr.vr.trackers.*;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class LinuxNamedPipeVRBridge extends Thread implements Bridge {
private static final int MAX_COMMAND_LENGTH = 2048;
public static final String HMDPipeName = "\\\\.\\pipe\\HMDPipe";
public static final String TrackersPipeName = "\\\\.\\pipe\\TrackPipe";
public static final Charset ASCII = Charset.forName("ASCII");
private final byte[] buffArray = new byte[1024];
private final StringBuilder commandBuilder = new StringBuilder(1024);
private final StringBuilder sbBuffer = new StringBuilder(1024);
private final Vector3f vBuffer = new Vector3f();
private final Vector3f vBuffer2 = new Vector3f();
private final Quaternion qBuffer = new Quaternion();
private final Quaternion qBuffer2 = new Quaternion();
private LinuxPipe hmdPipe;
private final HMDTracker hmd;
private final List<LinuxPipe> trackerPipes;
private final List<? extends Tracker> shareTrackers;
private final List<ComputedTracker> internalTrackers;
private final HMDTracker internalHMDTracker = new HMDTracker("internal://HMD");
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
public LinuxNamedPipeVRBridge(HMDTracker hmd, List<? extends Tracker> shareTrackers, VRServer server) {
super("Named Pipe VR Bridge");
this.hmd = hmd;
this.shareTrackers = new FastList<>(shareTrackers);
this.trackerPipes = new FastList<>(shareTrackers.size());
this.internalTrackers = new FastList<>(shareTrackers.size());
for(int i = 0; i < shareTrackers.size(); ++i) {
Tracker t = shareTrackers.get(i);
ComputedTracker ct = new ComputedTracker(t.getTrackerId(), "internal://" + t.getName(), true, true);
ct.setStatus(TrackerStatus.OK);
this.internalTrackers.add(ct);
}
}
@Override
public void run() {
try {
createPipes();
while(true) {
waitForPipesToOpen();
if(areAllPipesOpen()) {
boolean hmdUpdated = updateHMD(); // Update at HMDs frequency
for(int i = 0; i < trackerPipes.size(); ++i) {
updateTracker(i, hmdUpdated);
}
if(!hmdUpdated) {
Thread.sleep(5); // Up to 200Hz
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
@Override
public void dataRead() {
if(newHMDData.compareAndSet(true, false)) {
hmd.position.set(internalHMDTracker.position);
hmd.rotation.set(internalHMDTracker.rotation);
hmd.dataTick();
}
}
@Override
public void dataWrite() {
for(int i = 0; i < shareTrackers.size(); ++i) {
Tracker t = shareTrackers.get(i);
ComputedTracker it = this.internalTrackers.get(i);
if(t.getPosition(vBuffer2))
it.position.set(vBuffer2);
if(t.getRotation(qBuffer2))
it.rotation.set(qBuffer2);
}
}
private void waitForPipesToOpen() {
if(hmdPipe.state == PipeState.CREATED) {
if(tryOpeningPipe(hmdPipe))
initHMDPipe(hmdPipe);
}
for(int i = 0; i < trackerPipes.size(); ++i) {
LinuxPipe trackerPipe = trackerPipes.get(i);
if(trackerPipe.state == PipeState.CREATED) {
if(tryOpeningPipe(trackerPipe))
initTrackerPipe(trackerPipe, i);
}
}
}
public boolean updateHMD() throws IOException {
if(hmdPipe.state == PipeState.OPEN) {
IntByReference bytesAvailable = new IntByReference(0);
if(bytesAvailable.getValue() > 0) {
while(hmdPipe.pipe.read(ByteBuffer.wrap(buffArray)) != 0) {
int bytesRead = bytesAvailable.getValue();
for(int i = 0; i < bytesRead; ++i) {
char c = (char) buffArray[i];
if(c == '\n') {
executeHMDInput();
commandBuilder.setLength(0);
} else {
commandBuilder.append(c);
if(commandBuilder.length() >= MAX_COMMAND_LENGTH) {
LogManager.log.severe("[VRBridge] Command from the pipe is too long, flushing buffer");
commandBuilder.setLength(0);
}
}
}
if(bytesRead < buffArray.length)
break; // Don't repeat, we read all available bytes
}
return true;
}
}
return false;
}
private void executeHMDInput() throws IOException {
String[] split = commandBuilder.toString().split(" ");
if(split.length < 7) {
LogManager.log.severe("[VRBridge] Short HMD data received: " + commandBuilder.toString());
return;
}
try {
double x = Double.parseDouble(split[0]);
double y = Double.parseDouble(split[1]);
double z = Double.parseDouble(split[2]);
double qw = Double.parseDouble(split[3]);
double qx = Double.parseDouble(split[4]);
double qy = Double.parseDouble(split[5]);
double qz = Double.parseDouble(split[6]);
internalHMDTracker.position.set((float) x, (float) y, (float) z);
internalHMDTracker.rotation.set((float) qx, (float) qy, (float) qz, (float) qw);
internalHMDTracker.dataTick();
newHMDData.set(true);
} catch(NumberFormatException e) {
e.printStackTrace();
}
}
public void updateTracker(int trackerId, boolean hmdUpdated) {
Tracker sensor = internalTrackers.get(trackerId);
if(sensor.getStatus().sendData) {
LinuxPipe trackerPipe = trackerPipes.get(trackerId);
if(hmdUpdated && trackerPipe.state == PipeState.OPEN) {
sbBuffer.setLength(0);
sensor.getPosition(vBuffer);
sensor.getRotation(qBuffer);
sbBuffer.append(vBuffer.x).append(' ').append(vBuffer.y).append(' ').append(vBuffer.z).append(' ');
sbBuffer.append(qBuffer.getW()).append(' ').append(qBuffer.getX()).append(' ').append(qBuffer.getY()).append(' ').append(qBuffer.getZ()).append('\n');
String str = sbBuffer.toString();
System.arraycopy(str.getBytes(ASCII), 0, buffArray, 0, str.length());
buffArray[str.length()] = '\0';
IntByReference lpNumberOfBytesWritten = new IntByReference(0);
try {
trackerPipe.pipe.write(ByteBuffer.wrap(buffArray));
} catch(IOException e) {
}
}
}
}
private void initHMDPipe(LinuxPipe pipe) {
hmd.setStatus(TrackerStatus.OK);
}
private void initTrackerPipe(LinuxPipe pipe, int trackerId) {
String trackerHello = this.shareTrackers.size() + " 0";
System.arraycopy(trackerHello.getBytes(ASCII), 0, buffArray, 0, trackerHello.length());
buffArray[trackerHello.length()] = '\0';
IntByReference lpNumberOfBytesWritten = new IntByReference(0);
try {
pipe.pipe.write(ByteBuffer.wrap(buffArray));
} catch(IOException e) {
}
}
private boolean tryOpeningPipe(LinuxPipe pipe) {
if(pipe.pipe.isOpen()) {
pipe.state = PipeState.OPEN;
LogManager.log.info("[VRBridge] Pipe " + pipe.name + " is open");
return true;
}
LogManager.log.info("[VRBridge] Error connecting to pipe " + pipe.name + ": " + Native.getLastError());
return false;
}
private boolean areAllPipesOpen() {
if(hmdPipe == null || hmdPipe.state == PipeState.CREATED) {
return false;
}
for(int i = 0; i < trackerPipes.size(); ++i) {
if(trackerPipes.get(i).state == PipeState.CREATED)
return false;
}
return true;
}
private void createPipes() throws IOException {
try {
RandomAccessFile rw = new RandomAccessFile(HMDPipeName, "rw");
FileChannel fc = rw.getChannel();
hmdPipe = new LinuxPipe(fc, HMDPipeName); // lpSecurityAttributes
LogManager.log.info("[VRBridge] Pipe " + hmdPipe.name + " created");
//if(WinBase.INVALID_HANDLE_VALUE.equals(hmdPipe.pipeHandle))
// throw new IOException("Can't open " + HMDPipeName + " pipe: " + Native.getLastError());
for(int i = 0; i < this.shareTrackers.size(); ++i) {
String pipeName = TrackersPipeName + i;
RandomAccessFile Trw = new RandomAccessFile(pipeName, "rw");
FileChannel Tfc = Trw.getChannel();
LinuxPipe pipeHandle = new LinuxPipe(Tfc, pipeName+i); // lpSecurityAttributes
//if(WinBase.INVALID_HANDLE_VALUE.equals(pipeHandle))
// throw new IOException("Can't open " + pipeName + " pipe: " + Native.getLastError());
LogManager.log.info("[VRBridge] Pipe " + pipeName + " created");
trackerPipes.add(new LinuxPipe(pipeHandle.pipe, pipeName));
}
LogManager.log.info("[VRBridge] Pipes are open");
} catch(IOException e) {
safeDisconnect(hmdPipe);
for(int i = 0; i < trackerPipes.size(); ++i)
safeDisconnect(trackerPipes.get(i));
trackerPipes.clear();
throw e;
}
}
public static void safeDisconnect(LinuxPipe pipe) {
try {
if(pipe != null && pipe.pipe != null) {
pipe.pipe.close();
}
} catch (Exception e) {
}
}
@Override
public void addSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
@Override
public void removeSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
@Override
public void startBridge() {
start();
}
}

View File

@@ -0,0 +1,26 @@
package dev.slimevr.platform.linux;
import dev.slimevr.bridge.PipeState;
import java.nio.channels.FileChannel;
public class LinuxPipe {
public final String name;
public FileChannel pipe;
public PipeState state = PipeState.CREATED;
public LinuxPipe(FileChannel pipe, String name) {
this.pipe = pipe;
this.name = name;
}
public static void safeDisconnect(LinuxPipe pipe) {
try {
if(pipe != null && pipe.pipe != null) {
pipe.pipe.close();
}
} catch (Exception e) {
}
}
}

View File

@@ -0,0 +1,291 @@
package dev.slimevr.platform.linux;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.sun.jna.Native;
import com.sun.jna.platform.linux.Fcntl;
import com.sun.jna.platform.linux.ErrNo;
import com.sun.jna.ptr.IntByReference;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.Bridge;
import dev.slimevr.bridge.PipeState;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.TrackerPosition;
import dev.slimevr.vr.trackers.TrackerStatus;
import dev.slimevr.vr.trackers.VRTracker;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public class LinuxSteamVRPipeInputBridge extends Thread implements Bridge {
private static final int MAX_COMMAND_LENGTH = 2048;
public static final String PipeName = "\\\\.\\pipe\\SlimeVRInput";
private final byte[] buffArray = new byte[1024];
private final VRServer server;
private final StringBuilder commandBuilder = new StringBuilder(1024);
private final List<VRTracker> trackers = new FastList<>();
private final Map<Integer, VRTracker> trackersInternal = new HashMap<>();
private AtomicBoolean newData = new AtomicBoolean(false);
private final Vector3f vBuffer = new Vector3f();
private final Quaternion qBuffer = new Quaternion();
private LinuxPipe pipe;
public LinuxSteamVRPipeInputBridge(VRServer server) {
this.server = server;
}
@Override
public void run() {
try {
createPipes();
while(true) {
boolean pipesUpdated = false;
if(pipe.state == PipeState.CREATED) {
tryOpeningPipe(pipe);
}
if(pipe.state == PipeState.OPEN) {
pipesUpdated = updatePipes();
}
if(pipe.state == PipeState.ERROR) {
resetPipe();
}
if(!pipesUpdated) {
try {
Thread.sleep(5); // Up to 200Hz
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
public boolean updatePipes() throws IOException {
if(pipe.state == PipeState.OPEN) {
IntByReference bytesAvailable = new IntByReference(0);
if(bytesAvailable.getValue() > 0) {
while(pipe.pipe.write(ByteBuffer.wrap(buffArray)) != 0) {
int bytesRead = bytesAvailable.getValue();
for(int i = 0; i < bytesRead; ++i) {
char c = (char) buffArray[i];
if(c == '\n') {
executeInputCommand();
commandBuilder.setLength(0);
} else {
commandBuilder.append(c);
if(commandBuilder.length() >= MAX_COMMAND_LENGTH) {
LogManager.log.severe("[SteamVRPipeInputBridge] Command from the pipe is too long, flushing buffer");
commandBuilder.setLength(0);
}
}
}
if(bytesRead < buffArray.length)
return true; // All pipe data read
}
} else {
return false; // Pipe was empty, it's okay
}
// PeekNamedPipe or ReadFile returned an error
pipe.state = PipeState.ERROR;
LogManager.log.severe("[SteamVRPipeInputBridge] Pipe error: " + Native.getLastError());
}
return false;
}
private void executeInputCommand() throws IOException {
String[] command = commandBuilder.toString().split(" ");
switch(command[0]) {
case "ADD": // Add new tracker
if(command.length < 4) {
LogManager.log.severe("[SteamVRPipeInputBridge] Error in ADD command. Command requires at least 4 arguments. Supplied: " + commandBuilder.toString());
return;
}
VRTracker internalTracker = new VRTracker(Integer.parseInt(command[1]), StringUtils.join(command, " ", 3, command.length), true, true);
int roleId = Integer.parseInt(command[2]);
if(roleId >= 0 && roleId < LinuxSteamVRPipeInputBridge.SteamVRInputRoles.values.length) {
LinuxSteamVRPipeInputBridge.SteamVRInputRoles svrRole = LinuxSteamVRPipeInputBridge.SteamVRInputRoles.values[roleId];
internalTracker.bodyPosition = svrRole.bodyPosition;
}
VRTracker oldTracker;
synchronized(trackersInternal) {
oldTracker = trackersInternal.put(internalTracker.getTrackerId(), internalTracker);
}
if(oldTracker != null) {
LogManager.log.severe("[SteamVRPipeInputBridge] New tracker added with the same id. Supplied: " + commandBuilder.toString());
return;
}
newData.set(true);
break;
case "UPD": // Update tracker data
if(command.length < 9) {
LogManager.log.severe("[SteamVRPipeInputBridge] Error in UPD command. Command requires at least 9 arguments. Supplied: " + commandBuilder.toString());
return;
}
int id = Integer.parseInt(command[1]);
double x = Double.parseDouble(command[2]);
double y = Double.parseDouble(command[3]);
double z = Double.parseDouble(command[4]);
double qw = Double.parseDouble(command[5]);
double qx = Double.parseDouble(command[6]);
double qy = Double.parseDouble(command[7]);
double qz = Double.parseDouble(command[8]);
internalTracker = trackersInternal.get(id);
if(internalTracker != null) {
internalTracker.position.set((float) x, (float) y, (float) z);
internalTracker.rotation.set((float) qx, (float) qy, (float) qz, (float) qw);
internalTracker.dataTick();
newData.set(true);
}
break;
case "STA": // Update tracker status
if(command.length < 3) {
LogManager.log.severe("[SteamVRPipeInputBridge] Error in STA command. Command requires at least 3 arguments. Supplied: " + commandBuilder.toString());
return;
}
id = Integer.parseInt(command[1]);
int status = Integer.parseInt(command[2]);
TrackerStatus st = TrackerStatus.getById(status);
if(st == null) {
LogManager.log.severe("[SteamVRPipeInputBridge] Unrecognized status id. Supplied: " + commandBuilder.toString());
return;
}
internalTracker = trackersInternal.get(id);
if(internalTracker != null) {
internalTracker.setStatus(st);
newData.set(true);
}
break;
}
}
@Override
public void dataRead() {
if(newData.getAndSet(false)) {
if(trackers.size() < trackersInternal.size()) {
// Add new trackers
synchronized(trackersInternal) {
Iterator<VRTracker> iterator = trackersInternal.values().iterator();
internal: while(iterator.hasNext()) {
VRTracker internalTracker = iterator.next();
for(int i = 0; i < trackers.size(); ++i) {
VRTracker t = trackers.get(i);
if(t.getTrackerId() == internalTracker.getTrackerId())
continue internal;
}
// Tracker is not found in current trackers
VRTracker tracker = new VRTracker(internalTracker.getTrackerId(), internalTracker.getName(), true, true);
tracker.bodyPosition = internalTracker.bodyPosition;
trackers.add(tracker);
server.registerTracker(tracker);
}
}
}
for(int i = 0; i < trackers.size(); ++i) {
VRTracker tracker = trackers.get(i);
VRTracker internal = trackersInternal.get(tracker.getTrackerId());
if(internal == null)
throw new NullPointerException("Lost internal tracker somehow: " + tracker.getTrackerId()); // Shouldn't really happen even, but better to catch it like this
if(internal.getPosition(vBuffer))
tracker.position.set(vBuffer);
if(internal.getRotation(qBuffer))
tracker.rotation.set(qBuffer);
tracker.setStatus(internal.getStatus());
tracker.dataTick();
}
}
}
@Override
public void dataWrite() {
// Not used, only input
}
private void resetPipe() {
LinuxPipe.safeDisconnect(pipe);
pipe.state = PipeState.CREATED;
//Main.vrServer.queueTask(this::disconnected);
}
private boolean tryOpeningPipe(LinuxPipe pipe) {
if(pipe.pipe.isOpen()) {
pipe.state = PipeState.OPEN;
LogManager.log.info("[SteamVRPipeInputBridge] Pipe " + pipe.name + " is open");
return true;
}
LogManager.log.info("[SteamVRPipeInputBridge] Error connecting to pipe " + pipe.name + ": " + Native.getLastError());
return false;
}
private void createPipes() throws IOException {
try {
RandomAccessFile rw = new RandomAccessFile(PipeName, "rw");
FileChannel fc = rw.getChannel();
pipe = new LinuxPipe(fc, PipeName); // lpSecurityAttributes
LogManager.log.info("[SteamVRPipeInputBridge] Pipe " + pipe.name + " created");
if(pipe.pipe == null)
throw new IOException("Can't open " + PipeName + " pipe: " + Native.getLastError());
LogManager.log.info("[SteamVRPipeInputBridge] Pipes are open");
} catch(IOException e) {
LinuxPipe.safeDisconnect(pipe);
throw e;
}
}
@Override
public void addSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
@Override
public void removeSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
public enum SteamVRInputRoles {
HEAD(TrackerPosition.HMD),
LEFT_HAND(TrackerPosition.LEFT_CONTROLLER),
RIGHT_HAND(TrackerPosition.RIGHT_CONTROLLER),
LEFT_FOOT(TrackerPosition.LEFT_FOOT),
RIGHT_FOOT(TrackerPosition.RIGHT_FOOT),
LEFT_SHOULDER(TrackerPosition.NONE),
RIGHT_SHOULDER(TrackerPosition.NONE),
LEFT_ELBOW(TrackerPosition.NONE),
RIGHT_ELBOW(TrackerPosition.NONE),
LEFT_KNEE(TrackerPosition.LEFT_LEG),
RIGHT_KNEE(TrackerPosition.RIGHT_LEG),
WAIST(TrackerPosition.WAIST),
CHEST(TrackerPosition.CHEST),
;
private static final LinuxSteamVRPipeInputBridge.SteamVRInputRoles[] values = values();
public final TrackerPosition bodyPosition;
private SteamVRInputRoles(TrackerPosition slimeVrPosition) {
this.bodyPosition = slimeVrPosition;
}
}
@Override
public void startBridge() {
start();
}
}