Skip to content

Commit

Permalink
SolarXR IPC Socket (#1247)
Browse files Browse the repository at this point in the history
  • Loading branch information
rcelyte authored Jan 22, 2025
1 parent b4df1d1 commit fcd8232
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 94 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 @@ -38,16 +38,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() },
flashingHandlerProvider: (VRServer) -> SerialFlashingHandler? = { _ -> null },
acquireMulticastLock: () -> Any? = { null },
Expand Down Expand Up @@ -135,22 +134,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 @@ -28,7 +28,7 @@ public ConnectionContext getContext() {
@Override
public void send(ByteBuffer bytes) {
if (this.conn.isOpen())
this.conn.send(bytes);
this.conn.send(bytes.slice());
}

@Override
Expand Down
147 changes: 73 additions & 74 deletions server/desktop/src/main/java/dev/slimevr/desktop/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ package dev.slimevr.desktop
import dev.slimevr.Keybinding
import dev.slimevr.SLIMEVR_IDENTIFIER
import dev.slimevr.VRServer
import dev.slimevr.bridge.ISteamVRBridge
import dev.slimevr.bridge.Bridge
import dev.slimevr.desktop.firmware.DesktopSerialFlashingHandler
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 @@ -119,8 +120,7 @@ fun main(args: Array<String>) {
val configDir = resolveConfig()
LogManager.info("Using config dir: $configDir")
val vrServer = VRServer(
::provideSteamVRBridge,
::provideFeederBridge,
::provideBridges,
{ _ -> DesktopSerialHandler() },
{ _ -> DesktopSerialFlashingHandler() },
configPath = configDir,
Expand Down Expand Up @@ -151,90 +151,89 @@ 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(
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,
)
}
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?
): Sequence<Bridge> = sequence {
when (OperatingSystem.currentPlatform) {
OperatingSystem.WINDOWS -> {
// Create named pipe bridge for SteamVR driver
yield(
WindowsNamedPipeBridge(
server,
"steamvr",
"SteamVR Driver Bridge",
"""\\.\pipe\SlimeVRDriver""",
computedTrackers,
),
)

// Create named pipe bridge for SteamVR input
feederBridge = WindowsNamedPipeBridge(
server,
"steamvr_feeder",
"SteamVR Feeder Bridge",
"""\\.\pipe\SlimeVRInput""",
FastList(),
yield(
WindowsNamedPipeBridge(
server,
"steamvr_feeder",
"SteamVR Feeder Bridge",
"""\\.\pipe\SlimeVRInput""",
FastList(),
),
)
}

OperatingSystem.LINUX -> {
feederBridge = UnixSocketBridge(
server,
"steamvr_feeder",
"SteamVR Feeder Bridge",
Paths.get(OperatingSystem.socketDirectory, "SlimeVRInput")
.toString(),
FastList(),
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,114 @@
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.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;
private int remainingBytes;

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 || dst.position() < dst.getInt(0)) {
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 (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;
}
remainingBytes = dst.position() - messageLength;
dst.position(4);
dst.limit(messageLength);
return dst;
}

public void next() {
dst.position(dst.limit());
dst.limit(dst.limit() + remainingBytes);
dst.compact();
dst.limit(dst.capacity());
}
}
Loading

0 comments on commit fcd8232

Please sign in to comment.