xros-shell / XrosClient.cs
LoveLogicAI's picture
unity websocket client
19f07b8 verified
Raw History Blame Contribute Delete
3.4 kB
// 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();
}
}