File size: 3,399 Bytes
19f07b8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | // XrosClient.cs — WebSocket client for the XROS spatial-shell backend.
//
// Connects to the Python backend (xros_server.py) and exposes a simple API:
// SendCommand(text) -> run a spatial-shell command, get text output back
// Send(msg) -> send an arbitrary JSON message
// OnMessage -> event fired for every JSON message from the server
//
// Threading: the receive loop runs on a background task and pushes parsed
// messages into a thread-safe queue; Update() drains it on the main thread so
// Unity API calls (spawning GameObjects, updating UI) are always safe.
//
// Dependencies: Newtonsoft.Json (com.unity.nuget.newtonsoft-json in the
// Package Manager). No other packages required.
using System;
using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using UnityEngine;
public class XrosClient : MonoBehaviour
{
[Tooltip("Backend URL, e.g. ws://192.168.1.50:8765 (your machine's LAN IP).")]
public string serverUrl = "ws://192.168.1.50:8765";
public event Action<JObject> OnMessage;
private ClientWebSocket ws;
private CancellationTokenSource cts;
private readonly ConcurrentQueue<JObject> incoming = new ConcurrentQueue<JObject>();
public bool IsConnected => ws != null && ws.State == WebSocketState.Open;
public async void Connect()
{
cts = new CancellationTokenSource();
ws = new ClientWebSocket();
try
{
await ws.ConnectAsync(new Uri(serverUrl), cts.Token);
Debug.Log($"[XrosClient] connected to {serverUrl}");
_ = ReceiveLoop();
}
catch (Exception e)
{
Debug.LogError($"[XrosClient] connect failed: {e.Message}");
}
}
public async void SendCommand(string text)
{
await Send(new JObject { ["op"] = "command", ["text"] = text });
}
public async Task Send(JObject msg)
{
if (!IsConnected) { Debug.LogWarning("[XrosClient] not connected"); return; }
var bytes = Encoding.UTF8.GetBytes(msg.ToString(Newtonsoft.Json.Formatting.None));
await ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, cts.Token);
}
private async Task ReceiveLoop()
{
var buffer = new byte[8192];
var sb = new StringBuilder();
try
{
while (ws.State == WebSocketState.Open)
{
sb.Clear();
WebSocketReceiveResult result;
do
{
result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), cts.Token);
sb.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
} while (!result.EndOfMessage);
if (result.MessageType == WebSocketMessageType.Text)
{
incoming.Enqueue(JObject.Parse(sb.ToString()));
}
}
}
catch (Exception e)
{
Debug.Log($"[XrosClient] receive loop ended: {e.Message}");
}
}
private void Update()
{
while (incoming.TryDequeue(out var msg))
{
OnMessage?.Invoke(msg);
}
}
private void OnDestroy()
{
cts?.Cancel();
ws?.Dispose();
}
}
|