Skip to content

Commit 2235be3

Browse files
committed
SolarXR IPC
1 parent 16e4eb4 commit 2235be3

File tree

5 files changed

+338
-93
lines changed

5 files changed

+338
-93
lines changed

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

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

39-
typealias SteamBridgeProvider = (
39+
typealias BridgeProvider = (
4040
server: VRServer,
4141
computedTrackers: List<Tracker>,
42-
) -> ISteamVRBridge?
42+
) -> Sequence<Bridge>
4343

4444
const val SLIMEVR_IDENTIFIER = "dev.slimevr.SlimeVR"
4545

4646
class VRServer @JvmOverloads constructor(
47-
driverBridgeProvider: SteamBridgeProvider = { _, _ -> null },
48-
feederBridgeProvider: (VRServer) -> ISteamVRBridge? = { _ -> null },
47+
bridgeProvider: BridgeProvider = { _, _ -> sequence {} },
4948
serialHandlerProvider: (VRServer) -> SerialHandler = { _ -> SerialHandlerStub() },
5049
acquireMulticastLock: () -> Any? = { null },
5150
configPath: String,
@@ -123,22 +122,11 @@ class VRServer @JvmOverloads constructor(
123122
"Sensors UDP server",
124123
) { tracker: Tracker -> registerTracker(tracker) }
125124

126-
// Start bridges for SteamVR and Feeder
127-
val driverBridge = driverBridgeProvider(this, computedTrackers)
128-
if (driverBridge != null) {
129-
tasks.add(Runnable { driverBridge.startBridge() })
130-
bridges.add(driverBridge)
125+
// Start bridges and WebSocket server
126+
for (bridge in bridgeProvider(this, computedTrackers) + sequenceOf(WebSocketVRBridge(computedTrackers, this))) {
127+
tasks.add(Runnable { bridge.startBridge() })
128+
bridges.add(bridge)
131129
}
132-
val feederBridge = feederBridgeProvider(this)
133-
if (feederBridge != null) {
134-
tasks.add(Runnable { feederBridge.startBridge() })
135-
bridges.add(feederBridge)
136-
}
137-
138-
// Create WebSocket server
139-
val wsBridge = WebSocketVRBridge(computedTrackers, this)
140-
tasks.add(Runnable { wsBridge.startBridge() })
141-
bridges.add(wsBridge)
142130

143131
// Initialize OSC handlers
144132
vrcOSCHandler = VRCOSCHandler(

server/core/src/main/java/dev/slimevr/protocol/ProtocolAPI.java

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.nio.ByteBuffer;
1313
import java.util.ArrayList;
14+
import java.util.Arrays;
1415
import java.util.List;
1516

1617

@@ -31,6 +32,10 @@ public ProtocolAPI(VRServer server) {
3132
}
3233

3334
public void onMessage(GenericConnection conn, ByteBuffer message) {
35+
if (message.position() != 0)
36+
message = ByteBuffer
37+
.wrap(Arrays.copyOfRange(message.array(), message.position(), message.limit()));
38+
3439
MessageBundle messageBundle = MessageBundle.getRootAsMessageBundle(message);
3540

3641
try {

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

+73-74
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ 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.platform.SteamVRBridge
1010
import dev.slimevr.desktop.platform.linux.UnixSocketBridge
11+
import dev.slimevr.desktop.platform.linux.UnixSocketRpcBridge
1112
import dev.slimevr.desktop.platform.windows.WindowsNamedPipeBridge
1213
import dev.slimevr.desktop.serial.DesktopSerialHandler
1314
import dev.slimevr.desktop.tracking.trackers.hid.TrackersHID
@@ -118,8 +119,7 @@ fun main(args: Array<String>) {
118119
val configDir = resolveConfig()
119120
LogManager.info("Using config dir: $configDir")
120121
val vrServer = VRServer(
121-
::provideSteamVRBridge,
122-
::provideFeederBridge,
122+
::provideBridges,
123123
{ _ -> DesktopSerialHandler() },
124124
configPath = configDir,
125125
)
@@ -149,90 +149,89 @@ fun main(args: Array<String>) {
149149
}
150150
}
151151

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

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

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

235-
return feederBridge
233+
else -> {}
234+
}
236235
}
237236

238237
const val CONFIG_FILENAME = "vrconfig.yml"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
20+
public UnixSocketConnection(SocketChannel channel) {
21+
this.id = UUID.randomUUID();
22+
this.context = new ConnectionContext();
23+
this.channel = channel;
24+
}
25+
26+
@Override
27+
public UUID getConnectionId() {
28+
return id;
29+
}
30+
31+
@Override
32+
public ConnectionContext getContext() {
33+
return this.context;
34+
}
35+
36+
public boolean isConnected() {
37+
return this.channel.isConnected();
38+
}
39+
40+
private void resetChannel() {
41+
try {
42+
this.channel.close();
43+
} catch (IOException e) {
44+
e.printStackTrace();
45+
}
46+
}
47+
48+
@Override
49+
public void send(ByteBuffer bytes) {
50+
if (!this.channel.isConnected())
51+
return;
52+
try {
53+
ByteBuffer[] src = new ByteBuffer[] {
54+
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN),
55+
bytes.slice(),
56+
};
57+
src[0].putInt(src[1].remaining() + 4);
58+
src[0].flip();
59+
synchronized (this) {
60+
while (src[1].hasRemaining()) {
61+
this.channel.write(src);
62+
}
63+
}
64+
} catch (IOException e) {
65+
e.printStackTrace();
66+
}
67+
}
68+
69+
public ByteBuffer read() {
70+
if (dst.position() < 4) {
71+
if (!this.channel.isConnected())
72+
return null;
73+
try {
74+
int result = this.channel.read(dst);
75+
if (result == -1) {
76+
LogManager.info("[SolarXR Bridge] Reached end-of-stream on connection");
77+
this.resetChannel();
78+
return null;
79+
}
80+
if (result == 0 || dst.position() < 4) {
81+
return null;
82+
}
83+
} catch (IOException e) {
84+
e.printStackTrace();
85+
this.resetChannel();
86+
return null;
87+
}
88+
}
89+
int messageLength = dst.getInt(0);
90+
if (messageLength > 1024) {
91+
LogManager
92+
.severe(
93+
"[SolarXR Bridge] Buffer overflow on socket. Message length: " + messageLength
94+
);
95+
this.resetChannel();
96+
return null;
97+
}
98+
if (dst.position() < messageLength) {
99+
return null;
100+
}
101+
ByteBuffer message = dst.slice();
102+
message.position(4);
103+
message.limit(messageLength);
104+
return message;
105+
}
106+
107+
public void next() {
108+
int messageLength = dst.getInt(0);
109+
int originalpos = dst.position();
110+
dst.position(messageLength);
111+
dst.compact();
112+
dst.position(originalpos - messageLength);
113+
}
114+
}

0 commit comments

Comments
 (0)