Skip to content

Commit

Permalink
SolarXR IPC
Browse files Browse the repository at this point in the history
  • Loading branch information
rcelyte committed Nov 21, 2024
1 parent 1e5b7c7 commit d1c1418
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 80 deletions.
26 changes: 7 additions & 19 deletions server/core/src/main/java/dev/slimevr/VRServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,15 @@ import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer
import kotlin.concurrent.schedule

typealias SteamBridgeProvider = (
typealias BridgeProvider = (
server: VRServer,
computedTrackers: List<Tracker>,
) -> ISteamVRBridge?
) -> Sequence<Bridge>

const val SLIMEVR_IDENTIFIER = "dev.slimevr.SlimeVR"

class VRServer @JvmOverloads constructor(
driverBridgeProvider: SteamBridgeProvider = { _, _ -> null },
feederBridgeProvider: (VRServer) -> ISteamVRBridge? = { _ -> null },
bridgeProvider: BridgeProvider = { _, _ -> sequence {} },
serialHandlerProvider: (VRServer) -> SerialHandler = { _ -> SerialHandlerStub() },
acquireMulticastLock: () -> Any? = { null },
configPath: String,
Expand Down Expand Up @@ -123,22 +122,11 @@ class VRServer @JvmOverloads constructor(
"Sensors UDP server",
) { tracker: Tracker -> registerTracker(tracker) }

// Start bridges for SteamVR and Feeder
val driverBridge = driverBridgeProvider(this, computedTrackers)
if (driverBridge != null) {
tasks.add(Runnable { driverBridge.startBridge() })
bridges.add(driverBridge)
// Start bridges and WebSocket server
for (bridge in bridgeProvider(this, computedTrackers) + sequenceOf(WebSocketVRBridge(computedTrackers, this))) {
tasks.add(Runnable { bridge.startBridge() })
bridges.add(bridge)
}
val feederBridge = feederBridgeProvider(this)
if (feederBridge != null) {
tasks.add(Runnable { feederBridge.startBridge() })
bridges.add(feederBridge)
}

// Create WebSocket server
val wsBridge = WebSocketVRBridge(computedTrackers, this)
tasks.add(Runnable { wsBridge.startBridge() })
bridges.add(wsBridge)

// Initialize OSC handlers
vrcOSCHandler = VRCOSCHandler(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


Expand All @@ -31,6 +32,9 @@ public ProtocolAPI(VRServer server) {
}

public void onMessage(GenericConnection conn, ByteBuffer message) {
if(message.position() != 0)
message = ByteBuffer.wrap(Arrays.copyOfRange(message.array(), message.position(), message.limit()));

MessageBundle messageBundle = MessageBundle.getRootAsMessageBundle(message);

try {
Expand Down
114 changes: 53 additions & 61 deletions server/desktop/src/main/java/dev/slimevr/desktop/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ package dev.slimevr.desktop
import dev.slimevr.Keybinding
import dev.slimevr.SLIMEVR_IDENTIFIER
import dev.slimevr.VRServer
import dev.slimevr.bridge.Bridge
import dev.slimevr.bridge.ISteamVRBridge
import dev.slimevr.desktop.platform.SteamVRBridge
import dev.slimevr.desktop.platform.linux.UnixSocketBridge
import dev.slimevr.desktop.platform.linux.UnixSocketRpcBridge
import dev.slimevr.desktop.platform.windows.WindowsNamedPipeBridge
import dev.slimevr.desktop.serial.DesktopSerialHandler
import dev.slimevr.desktop.tracking.trackers.hid.TrackersHID
Expand Down Expand Up @@ -118,8 +120,7 @@ fun main(args: Array<String>) {
val configDir = resolveConfig()
LogManager.info("Using config dir: $configDir")
val vrServer = VRServer(
::provideSteamVRBridge,
::provideFeederBridge,
::provideBridges,
{ _ -> DesktopSerialHandler() },
configPath = configDir,
)
Expand Down Expand Up @@ -149,90 +150,81 @@ fun main(args: Array<String>) {
}
}

fun provideSteamVRBridge(
fun provideBridges(
server: VRServer,
computedTrackers: List<Tracker>,
): ISteamVRBridge? {
val driverBridge: SteamVRBridge?
if (OperatingSystem.currentPlatform == OperatingSystem.WINDOWS) {
// Create named pipe bridge for SteamVR driver
driverBridge = WindowsNamedPipeBridge(
server,
"steamvr",
"SteamVR Driver Bridge",
"""\\.\pipe\SlimeVRDriver""",
computedTrackers,
)
} else if (OperatingSystem.currentPlatform == OperatingSystem.LINUX) {
var linuxBridge: SteamVRBridge? = null
try {
linuxBridge = UnixSocketBridge(
): Sequence<Bridge> = sequence {
when (OperatingSystem.currentPlatform) {
OperatingSystem.WINDOWS -> {
// Create named pipe bridge for SteamVR driver
yield(WindowsNamedPipeBridge(
server,
"steamvr",
"SteamVR Driver Bridge",
Paths.get(OperatingSystem.socketDirectory, "SlimeVRDriver")
.toString(),
"""\\.\pipe\SlimeVRDriver""",
computedTrackers,
)
} catch (ex: Exception) {
LogManager.severe(
"Failed to initiate Unix socket, disabling driver bridge...",
ex,
)
}
driverBridge = linuxBridge
if (driverBridge != null) {
// Close the named socket on shutdown, or otherwise it's not going to get removed
Runtime.getRuntime().addShutdownHook(
Thread {
try {
(driverBridge as? UnixSocketBridge)?.close()
} catch (e: Exception) {
throw RuntimeException(e)
}
},
)
}
} else {
driverBridge = null
}
))

return driverBridge
}

fun provideFeederBridge(
server: VRServer,
): ISteamVRBridge? {
val feederBridge: SteamVRBridge?
when (OperatingSystem.currentPlatform) {
OperatingSystem.WINDOWS -> {
// Create named pipe bridge for SteamVR input
feederBridge = WindowsNamedPipeBridge(
yield(WindowsNamedPipeBridge(
server,
"steamvr_feeder",
"SteamVR Feeder Bridge",
"""\\.\pipe\SlimeVRInput""",
FastList(),
)
))
}

OperatingSystem.LINUX -> {
feederBridge = UnixSocketBridge(
var linuxBridge: SteamVRBridge? = null
try {
linuxBridge = UnixSocketBridge(
server,
"steamvr",
"SteamVR Driver Bridge",
Paths.get(OperatingSystem.socketDirectory, "SlimeVRDriver")
.toString(),
computedTrackers,
)
} catch (ex: Exception) {
LogManager.severe(
"Failed to initiate Unix socket, disabling driver bridge...",
ex,
)
}
if (linuxBridge != null) {
// Close the named socket on shutdown, or otherwise it's not going to get removed
Runtime.getRuntime().addShutdownHook(
Thread {
try {
(linuxBridge as? UnixSocketBridge)?.close()
} catch (e: Exception) {
throw RuntimeException(e)
}
},
)
yield(linuxBridge);
}

yield(UnixSocketBridge(
server,
"steamvr_feeder",
"SteamVR Feeder Bridge",
Paths.get(OperatingSystem.socketDirectory, "SlimeVRInput")
.toString(),
FastList(),
)
}
))

else -> {
feederBridge = null
yield(UnixSocketRpcBridge(
server,
Paths.get(OperatingSystem.socketDirectory, "SlimeVRRpc")
.toString(),
computedTrackers,
))
}
}

return feederBridge
else -> {}
}
}

const val CONFIG_FILENAME = "vrconfig.yml"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package dev.slimevr.desktop.platform.linux;

import dev.slimevr.protocol.ConnectionContext;
import dev.slimevr.protocol.GenericConnection;
import io.eiren.util.logging.LogManager;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.UUID;

public class UnixSocketConnection implements GenericConnection {
public final UUID id;
public final ConnectionContext context;
private final ByteBuffer dst = ByteBuffer.allocate(2048).order(ByteOrder.LITTLE_ENDIAN);
private final SocketChannel channel;

public UnixSocketConnection(SocketChannel channel) {
this.id = UUID.randomUUID();
this.context = new ConnectionContext();
this.channel = channel;
}

@Override
public UUID getConnectionId() {
return id;
}

@Override
public ConnectionContext getContext() {
return this.context;
}

public boolean isConnected() {
return this.channel.isConnected();
}

private void resetChannel() {
try {
this.channel.close();
} catch(IOException e) {
e.printStackTrace();
}
}

@Override
public void send(ByteBuffer bytes) {
if (!this.channel.isConnected())
return;
try {
ByteBuffer[] src = new ByteBuffer[] {
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN),
bytes.slice(),
};
src[0].putInt(src[1].remaining() + 4);
src[0].flip();
synchronized(this) {
while (src[1].hasRemaining()) {
this.channel.write(src);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

public ByteBuffer read() {
if(dst.position() < 4) {
if (!this.channel.isConnected())
return null;
try {
int result = this.channel.read(dst);
if (result == -1) {
LogManager.info("[SolarXR Bridge] Reached end-of-stream on connection");
this.resetChannel();
return null;
}
if (result == 0 || dst.position() < 4) {
return null;
}
} catch(IOException e) {
e.printStackTrace();
this.resetChannel();
return null;
}
}
int messageLength = dst.getInt(0);
if (messageLength > 1024) {
LogManager.severe("[SolarXR Bridge] Buffer overflow on socket. Message length: " + messageLength);
this.resetChannel();
return null;
}
if (dst.position() < messageLength) {
return null;
}
ByteBuffer message = dst.slice();
message.position(4);
message.limit(messageLength);
return message;
}

public void next() {
int messageLength = dst.getInt(0);
int originalpos = dst.position();
dst.position(messageLength);
dst.compact();
dst.position(originalpos - messageLength);
}
}
Loading

0 comments on commit d1c1418

Please sign in to comment.