Skip to content

Commit f1e2e88

Browse files
committed
SolarXR IPC
1 parent f964e4d commit f1e2e88

File tree

4 files changed

+339
-93
lines changed

4 files changed

+339
-93
lines changed

server/core/src/main/java/dev/slimevr/VRServer.kt

+7-19
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,15 @@ import java.util.concurrent.atomic.AtomicInteger
3838
import java.util.function.Consumer
3939
import kotlin.concurrent.schedule
4040

41-
typealias SteamBridgeProvider = (
41+
typealias BridgeProvider = (
4242
server: VRServer,
4343
computedTrackers: List<Tracker>,
44-
) -> ISteamVRBridge?
44+
) -> Sequence<Bridge>
4545

4646
const val SLIMEVR_IDENTIFIER = "dev.slimevr.SlimeVR"
4747

4848
class VRServer @JvmOverloads constructor(
49-
driverBridgeProvider: SteamBridgeProvider = { _, _ -> null },
50-
feederBridgeProvider: (VRServer) -> ISteamVRBridge? = { _ -> null },
49+
bridgeProvider: BridgeProvider = { _, _ -> sequence {} },
5150
serialHandlerProvider: (VRServer) -> SerialHandler = { _ -> SerialHandlerStub() },
5251
flashingHandlerProvider: (VRServer) -> SerialFlashingHandler? = { _ -> null },
5352
acquireMulticastLock: () -> Any? = { null },
@@ -135,22 +134,11 @@ class VRServer @JvmOverloads constructor(
135134
"Sensors UDP server",
136135
) { tracker: Tracker -> registerTracker(tracker) }
137136

138-
// Start bridges for SteamVR and Feeder
139-
val driverBridge = driverBridgeProvider(this, computedTrackers)
140-
if (driverBridge != null) {
141-
tasks.add(Runnable { driverBridge.startBridge() })
142-
bridges.add(driverBridge)
137+
// Start bridges and WebSocket server
138+
for (bridge in bridgeProvider(this, computedTrackers) + sequenceOf(WebSocketVRBridge(computedTrackers, this))) {
139+
tasks.add(Runnable { bridge.startBridge() })
140+
bridges.add(bridge)
143141
}
144-
val feederBridge = feederBridgeProvider(this)
145-
if (feederBridge != null) {
146-
tasks.add(Runnable { feederBridge.startBridge() })
147-
bridges.add(feederBridge)
148-
}
149-
150-
// Create WebSocket server
151-
val wsBridge = WebSocketVRBridge(computedTrackers, this)
152-
tasks.add(Runnable { wsBridge.startBridge() })
153-
bridges.add(wsBridge)
154142

155143
// Initialize OSC handlers
156144
vrcOSCHandler = VRCOSCHandler(

server/desktop/src/main/java/dev/slimevr/desktop/Main.kt

+73-74
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ package dev.slimevr.desktop
55
import dev.slimevr.Keybinding
66
import dev.slimevr.SLIMEVR_IDENTIFIER
77
import dev.slimevr.VRServer
8-
import dev.slimevr.bridge.ISteamVRBridge
8+
import dev.slimevr.bridge.Bridge
99
import dev.slimevr.desktop.firmware.DesktopSerialFlashingHandler
1010
import dev.slimevr.desktop.platform.SteamVRBridge
1111
import dev.slimevr.desktop.platform.linux.UnixSocketBridge
12+
import dev.slimevr.desktop.platform.linux.UnixSocketRpcBridge
1213
import dev.slimevr.desktop.platform.windows.WindowsNamedPipeBridge
1314
import dev.slimevr.desktop.serial.DesktopSerialHandler
1415
import dev.slimevr.desktop.tracking.trackers.hid.TrackersHID
@@ -119,8 +120,7 @@ fun main(args: Array<String>) {
119120
val configDir = resolveConfig()
120121
LogManager.info("Using config dir: $configDir")
121122
val vrServer = VRServer(
122-
::provideSteamVRBridge,
123-
::provideFeederBridge,
123+
::provideBridges,
124124
{ _ -> DesktopSerialHandler() },
125125
{ _ -> DesktopSerialFlashingHandler() },
126126
configPath = configDir,
@@ -151,90 +151,89 @@ fun main(args: Array<String>) {
151151
}
152152
}
153153

154-
fun provideSteamVRBridge(
154+
fun provideBridges(
155155
server: VRServer,
156156
computedTrackers: List<Tracker>,
157-
): ISteamVRBridge? {
158-
val driverBridge: SteamVRBridge?
159-
if (OperatingSystem.currentPlatform == OperatingSystem.WINDOWS) {
160-
// Create named pipe bridge for SteamVR driver
161-
driverBridge = WindowsNamedPipeBridge(
162-
server,
163-
"steamvr",
164-
"SteamVR Driver Bridge",
165-
"""\\.\pipe\SlimeVRDriver""",
166-
computedTrackers,
167-
)
168-
} else if (OperatingSystem.currentPlatform == OperatingSystem.LINUX) {
169-
var linuxBridge: SteamVRBridge? = null
170-
try {
171-
linuxBridge = UnixSocketBridge(
172-
server,
173-
"steamvr",
174-
"SteamVR Driver Bridge",
175-
Paths.get(OperatingSystem.socketDirectory, "SlimeVRDriver")
176-
.toString(),
177-
computedTrackers,
178-
)
179-
} catch (ex: Exception) {
180-
LogManager.severe(
181-
"Failed to initiate Unix socket, disabling driver bridge...",
182-
ex,
183-
)
184-
}
185-
driverBridge = linuxBridge
186-
if (driverBridge != null) {
187-
// Close the named socket on shutdown, or otherwise it's not going to get removed
188-
Runtime.getRuntime().addShutdownHook(
189-
Thread {
190-
try {
191-
(driverBridge as? UnixSocketBridge)?.close()
192-
} catch (e: Exception) {
193-
throw RuntimeException(e)
194-
}
195-
},
196-
)
197-
}
198-
} else {
199-
driverBridge = null
200-
}
201-
202-
return driverBridge
203-
}
204-
205-
fun provideFeederBridge(
206-
server: VRServer,
207-
): ISteamVRBridge? {
208-
val feederBridge: SteamVRBridge?
157+
): Sequence<Bridge> = sequence {
209158
when (OperatingSystem.currentPlatform) {
210159
OperatingSystem.WINDOWS -> {
160+
// Create named pipe bridge for SteamVR driver
161+
yield(
162+
WindowsNamedPipeBridge(
163+
server,
164+
"steamvr",
165+
"SteamVR Driver Bridge",
166+
"""\\.\pipe\SlimeVRDriver""",
167+
computedTrackers,
168+
),
169+
)
170+
211171
// Create named pipe bridge for SteamVR input
212-
feederBridge = WindowsNamedPipeBridge(
213-
server,
214-
"steamvr_feeder",
215-
"SteamVR Feeder Bridge",
216-
"""\\.\pipe\SlimeVRInput""",
217-
FastList(),
172+
yield(
173+
WindowsNamedPipeBridge(
174+
server,
175+
"steamvr_feeder",
176+
"SteamVR Feeder Bridge",
177+
"""\\.\pipe\SlimeVRInput""",
178+
FastList(),
179+
),
218180
)
219181
}
220182

221183
OperatingSystem.LINUX -> {
222-
feederBridge = UnixSocketBridge(
223-
server,
224-
"steamvr_feeder",
225-
"SteamVR Feeder Bridge",
226-
Paths.get(OperatingSystem.socketDirectory, "SlimeVRInput")
227-
.toString(),
228-
FastList(),
184+
var linuxBridge: SteamVRBridge? = null
185+
try {
186+
linuxBridge = UnixSocketBridge(
187+
server,
188+
"steamvr",
189+
"SteamVR Driver Bridge",
190+
Paths.get(OperatingSystem.socketDirectory, "SlimeVRDriver")
191+
.toString(),
192+
computedTrackers,
193+
)
194+
} catch (ex: Exception) {
195+
LogManager.severe(
196+
"Failed to initiate Unix socket, disabling driver bridge...",
197+
ex,
198+
)
199+
}
200+
if (linuxBridge != null) {
201+
// Close the named socket on shutdown, or otherwise it's not going to get removed
202+
Runtime.getRuntime().addShutdownHook(
203+
Thread {
204+
try {
205+
(linuxBridge as? UnixSocketBridge)?.close()
206+
} catch (e: Exception) {
207+
throw RuntimeException(e)
208+
}
209+
},
210+
)
211+
yield(linuxBridge)
212+
}
213+
214+
yield(
215+
UnixSocketBridge(
216+
server,
217+
"steamvr_feeder",
218+
"SteamVR Feeder Bridge",
219+
Paths.get(OperatingSystem.socketDirectory, "SlimeVRInput")
220+
.toString(),
221+
FastList(),
222+
),
229223
)
230-
}
231224

232-
else -> {
233-
feederBridge = null
225+
yield(
226+
UnixSocketRpcBridge(
227+
server,
228+
Paths.get(OperatingSystem.socketDirectory, "SlimeVRRpc")
229+
.toString(),
230+
computedTrackers,
231+
),
232+
)
234233
}
235-
}
236234

237-
return feederBridge
235+
else -> {}
236+
}
238237
}
239238

240239
const val CONFIG_FILENAME = "vrconfig.yml"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package dev.slimevr.desktop.platform.linux;
2+
3+
import dev.slimevr.protocol.ConnectionContext;
4+
import dev.slimevr.protocol.GenericConnection;
5+
import io.eiren.util.logging.LogManager;
6+
7+
import java.io.IOException;
8+
import java.nio.ByteBuffer;
9+
import java.nio.ByteOrder;
10+
import java.nio.channels.SocketChannel;
11+
import java.util.UUID;
12+
13+
14+
public class UnixSocketConnection implements GenericConnection {
15+
public final UUID id;
16+
public final ConnectionContext context;
17+
private final ByteBuffer dst = ByteBuffer.allocate(2048).order(ByteOrder.LITTLE_ENDIAN);
18+
private final SocketChannel channel;
19+
private int remainingBytes;
20+
21+
public UnixSocketConnection(SocketChannel channel) {
22+
this.id = UUID.randomUUID();
23+
this.context = new ConnectionContext();
24+
this.channel = channel;
25+
}
26+
27+
@Override
28+
public UUID getConnectionId() {
29+
return id;
30+
}
31+
32+
@Override
33+
public ConnectionContext getContext() {
34+
return this.context;
35+
}
36+
37+
public boolean isConnected() {
38+
return this.channel.isConnected();
39+
}
40+
41+
private void resetChannel() {
42+
try {
43+
this.channel.close();
44+
} catch (IOException e) {
45+
e.printStackTrace();
46+
}
47+
}
48+
49+
@Override
50+
public void send(ByteBuffer bytes) {
51+
if (!this.channel.isConnected())
52+
return;
53+
try {
54+
ByteBuffer[] src = new ByteBuffer[] {
55+
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN),
56+
bytes.slice(),
57+
};
58+
src[0].putInt(src[1].remaining() + 4);
59+
src[0].flip();
60+
synchronized (this) {
61+
while (src[1].hasRemaining()) {
62+
this.channel.write(src);
63+
}
64+
}
65+
} catch (IOException e) {
66+
e.printStackTrace();
67+
}
68+
}
69+
70+
public ByteBuffer read() {
71+
if (dst.position() < 4 || dst.position() < dst.getInt(0)) {
72+
if (!this.channel.isConnected())
73+
return null;
74+
try {
75+
int result = this.channel.read(dst);
76+
if (result == -1) {
77+
LogManager.info("[SolarXR Bridge] Reached end-of-stream on connection");
78+
this.resetChannel();
79+
return null;
80+
}
81+
if (dst.position() < 4) {
82+
return null;
83+
}
84+
} catch (IOException e) {
85+
e.printStackTrace();
86+
this.resetChannel();
87+
return null;
88+
}
89+
}
90+
int messageLength = dst.getInt(0);
91+
if (messageLength > 1024) {
92+
LogManager
93+
.severe(
94+
"[SolarXR Bridge] Buffer overflow on socket. Message length: " + messageLength
95+
);
96+
this.resetChannel();
97+
return null;
98+
}
99+
if (dst.position() < messageLength) {
100+
return null;
101+
}
102+
remainingBytes = dst.position() - messageLength;
103+
dst.position(4);
104+
dst.limit(messageLength);
105+
return dst;
106+
}
107+
108+
public void next() {
109+
System
110+
.arraycopy(
111+
dst.array(),
112+
dst.arrayOffset() + dst.limit(),
113+
dst.array(),
114+
dst.arrayOffset(),
115+
remainingBytes
116+
);
117+
dst.limit(dst.capacity());
118+
dst.position(remainingBytes);
119+
}
120+
}

0 commit comments

Comments
 (0)