Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SolarXR IPC Socket #1247

Merged
merged 4 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 -> {
ImUrX marked this conversation as resolved.
Show resolved Hide resolved
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
Loading