Files
2025-11-30 08:35:03 +02:00

203 lines
4.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Security.Authentication;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor;
using WebSocketSharp;
using Debug = UnityEngine.Debug;
using WebSocket = WebSocketSharp.WebSocket;
namespace Needle.Engine.Server
{
public class Connection : IDisposable
{
// [MenuItem("Needle Engine/Internal/Editor Sync/Toggle Server Debug Logs", priority = 100_000)]
// private static void ToggleDebug()
// {
// DebugLog = !DebugLog;
// Debug.Log("Needle Engine Server Debug Logs: " + DebugLog);
// }
public static bool DebugLog = false;
public static Connection Instance
{
get
{
_instance ??= new Connection();
if (!_instance.IsConnected) _instance.Connect();
return _instance;
}
}
[InitializeOnLoadMethod]
private static void Init()
{
_instance = Instance;
}
private static Connection _instance;
public bool IsConnected => client != null && client.ReadyState == WebSocketState.Open;
public bool KeepAlive = true;
public void Connect(int port = 1107)
{
if (this.port != port) client?.Close();
switch (client?.ReadyState)
{
case WebSocketState.Connecting:
return;
}
if (!IsConnected)
{
this.port = port;
if (port == 0) port = 8080;
var address = "wss://localhost:" + port;
if (!address.StartsWith("ws"))
address = "wss://" + address;
if (DebugLog)
Debug.Log("Connect to " + address);
client = new WebSocket(address);
if (address.StartsWith("wss"))
client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
client.OnOpen += OnOpen;
client.OnMessage += OnMessage;
client.OnError += OnError;
client.OnClose += OnClose;
client.ConnectAsync();
}
}
public void Close()
{
client?.Close();
client = null;
}
public event Action<RawMessage> Message;
public event Action Connected;
public event Action Closed;
public event Action<string> Error;
public void Send(string type, JToken data)
{
if (!this.client.IsAlive)
{
if (KeepAlive) ScheduleReconnect();
return;
}
var obj = new JObject();
obj["type"] = type;
obj["data"] = data;
this.client.Send(obj.ToString());
}
public void SendRaw(string str)
{
if (!this.client.IsAlive)
{
if (KeepAlive) ScheduleReconnect();
return;
}
this.client.Send(str);
}
private int port;
private WebSocket client;
private void OnOpen(object sender, EventArgs e)
{
if (DebugLog) Debug.Log("Connected!");
failedOpenAttempts = 0;
this.client.Send("needle:editor=unity");
Connected?.Invoke();
EditorApplication.update -= OnEditorUpdate;
EditorApplication.update += OnEditorUpdate;
}
private void OnClose(object sender, CloseEventArgs e)
{
failedOpenAttempts += 1;
if (DebugLog) Debug.Log($"Connection closed: {e.Code}, {e.Reason}");
Closed?.Invoke();
if (KeepAlive) ScheduleReconnect();
}
private void OnMessage(object sender, MessageEventArgs e)
{
if (DebugLog) Debug.Log($"Received message: {e.Data}");
messages.Enqueue(e);
}
private void OnError(object sender, ErrorEventArgs e)
{
failedOpenAttempts += 1;
if (DebugLog)
Debug.LogError(e.Message);
Error?.Invoke(e.Message);
if (KeepAlive) ScheduleReconnect();
}
public void Dispose()
{
client?.CloseAsync();
}
private bool reconnectScheduled;
private int failedOpenAttempts = 0;
private async void ScheduleReconnect()
{
if (reconnectScheduled) return;
int timeout = 5000 * (1 + failedOpenAttempts) * failedOpenAttempts;
if (DebugLog)
Debug.Log($"SCHEDULE RECONNECT AFTER {timeout}MS");
reconnectScheduled = true;
await Task.Delay(Math.Abs(timeout));
reconnectScheduled = false;
if (KeepAlive)
Connect(port);
}
private readonly Queue<MessageEventArgs> messages = new Queue<MessageEventArgs>();
private void OnEditorUpdate()
{
try
{
while (messages.Count > 0)
{
var i = messages.Dequeue();
if (i != null && i.Data != null)
{
if (string.IsNullOrEmpty(i.Data)) continue;
if (i.Data.StartsWith("{") || i.Data.StartsWith("["))
{
var msg = JsonConvert.DeserializeObject<RawMessage>(i.Data);
Message?.Invoke(msg);
}
else
{
var msg = new RawMessage();
msg.type = "raw";
msg.data = i.Data;
Message?.Invoke(msg);
}
}
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
}