forked from Shardstone/trail-into-darkness
Added a bunch of utilities and modfief the character data structue
This commit is contained in:
245
Packages/com.jovian.logger/Editor/RemoteLogReceiver.cs
Normal file
245
Packages/com.jovian.logger/Editor/RemoteLogReceiver.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.Logger {
|
||||
/// <summary>
|
||||
/// TCP server that receives log messages from RemoteLogSender on device builds.
|
||||
/// Managed by Custom Console — not intended for standalone use.
|
||||
/// </summary>
|
||||
internal static class RemoteLogReceiver {
|
||||
public const int DefaultPort = 9876;
|
||||
|
||||
public struct RemoteLogEntry {
|
||||
public string message;
|
||||
public string stackTrace;
|
||||
public string timestamp;
|
||||
public int frameCount;
|
||||
public JovianLogType type;
|
||||
public LogCategory logCategory;
|
||||
public bool isCustomLog;
|
||||
}
|
||||
|
||||
public struct RemoteWatchEntry {
|
||||
public string key;
|
||||
public string value;
|
||||
public LogCategory logCategory;
|
||||
public string timestamp;
|
||||
public int frameCount;
|
||||
}
|
||||
|
||||
public static event Action<RemoteLogEntry> OnRemoteLog;
|
||||
public static event Action<RemoteWatchEntry> OnRemoteWatch;
|
||||
public static event Action<string> OnRemoteUnwatch;
|
||||
public static event Action<string> OnClientConnected;
|
||||
public static event Action OnClientDisconnected;
|
||||
|
||||
private static TcpListener listener;
|
||||
private static Thread listenThread;
|
||||
private static volatile bool running;
|
||||
private static int port = DefaultPort;
|
||||
|
||||
public static bool IsRunning => running;
|
||||
public static int Port => port;
|
||||
public static string ConnectedClientName { get; private set; } = "";
|
||||
public static bool HasClient { get; private set; }
|
||||
|
||||
private static readonly ConcurrentQueue<Action> mainThreadQueue = new ConcurrentQueue<Action>();
|
||||
|
||||
public static void Start(int listenPort = DefaultPort) {
|
||||
if (running) return;
|
||||
port = listenPort;
|
||||
|
||||
try {
|
||||
listener = new TcpListener(IPAddress.Any, port);
|
||||
listener.Start();
|
||||
running = true;
|
||||
|
||||
listenThread = new Thread(ListenLoop) {
|
||||
IsBackground = true,
|
||||
Name = "RemoteLogReceiver"
|
||||
};
|
||||
listenThread.Start();
|
||||
|
||||
EditorApplication.update += ProcessMainThreadQueue;
|
||||
Debug.Log($"[RemoteLog] Listening on port {port}");
|
||||
} catch (Exception e) {
|
||||
Debug.LogError($"[RemoteLog] Failed to start listener: {e.Message}");
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop() {
|
||||
running = false;
|
||||
HasClient = false;
|
||||
ConnectedClientName = "";
|
||||
|
||||
try { listener?.Stop(); } catch { }
|
||||
listener = null;
|
||||
|
||||
EditorApplication.update -= ProcessMainThreadQueue;
|
||||
}
|
||||
|
||||
private static void ProcessMainThreadQueue() {
|
||||
while (mainThreadQueue.TryDequeue(out var action)) {
|
||||
action?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ListenLoop() {
|
||||
while (running) {
|
||||
try {
|
||||
if (!listener.Pending()) {
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
var client = listener.AcceptTcpClient();
|
||||
client.NoDelay = true;
|
||||
client.ReceiveTimeout = 0;
|
||||
HandleClient(client);
|
||||
} catch (SocketException) {
|
||||
if (!running) break;
|
||||
} catch (ObjectDisposedException) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleClient(TcpClient client) {
|
||||
var thread = new Thread(() => ClientReadLoop(client)) {
|
||||
IsBackground = true,
|
||||
Name = "RemoteLogClient"
|
||||
};
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
private static void ClientReadLoop(TcpClient client) {
|
||||
try {
|
||||
using var stream = client.GetStream();
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
|
||||
HasClient = true;
|
||||
|
||||
while (running && client.Connected) {
|
||||
string line = reader.ReadLine();
|
||||
if (line == null) break;
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
ParseAndDispatch(line);
|
||||
}
|
||||
} catch (IOException) {
|
||||
// Connection lost
|
||||
} catch (ObjectDisposedException) {
|
||||
// Shutting down
|
||||
} finally {
|
||||
try { client.Close(); } catch { }
|
||||
HasClient = false;
|
||||
ConnectedClientName = "";
|
||||
mainThreadQueue.Enqueue(() => OnClientDisconnected?.Invoke());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseAndDispatch(string json) {
|
||||
try {
|
||||
// Check for handshake
|
||||
if (json.Contains("\"handshake\"")) {
|
||||
string appName = ExtractJsonString(json, "app");
|
||||
string platform = ExtractJsonString(json, "platform");
|
||||
ConnectedClientName = $"{appName} ({platform})";
|
||||
mainThreadQueue.Enqueue(() => OnClientConnected?.Invoke(ConnectedClientName));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for watch
|
||||
if (json.Contains("\"watch\"")) {
|
||||
var watchEntry = new RemoteWatchEntry {
|
||||
key = ExtractJsonString(json, "wk"),
|
||||
value = ExtractJsonString(json, "wv"),
|
||||
logCategory = (LogCategory)ExtractJsonInt(json, "c"),
|
||||
timestamp = ExtractJsonString(json, "ts"),
|
||||
frameCount = ExtractJsonInt(json, "fc"),
|
||||
};
|
||||
mainThreadQueue.Enqueue(() => OnRemoteWatch?.Invoke(watchEntry));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for unwatch
|
||||
if (json.Contains("\"unwatch\"")) {
|
||||
string key = ExtractJsonString(json, "wk");
|
||||
mainThreadQueue.Enqueue(() => OnRemoteUnwatch?.Invoke(key));
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = new RemoteLogEntry {
|
||||
message = ExtractJsonString(json, "m"),
|
||||
stackTrace = ExtractJsonString(json, "s"),
|
||||
timestamp = ExtractJsonString(json, "ts"),
|
||||
frameCount = ExtractJsonInt(json, "fc"),
|
||||
type = (JovianLogType)ExtractJsonInt(json, "t"),
|
||||
logCategory = (LogCategory)ExtractJsonInt(json, "c"),
|
||||
isCustomLog = ExtractJsonBool(json, "f"),
|
||||
};
|
||||
|
||||
mainThreadQueue.Enqueue(() => OnRemoteLog?.Invoke(entry));
|
||||
} catch (Exception e) {
|
||||
Debug.LogWarning($"[RemoteLog] Failed to parse: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Lightweight JSON field extractors (avoids dependency on full JSON parser for simple protocol)
|
||||
private static string ExtractJsonString(string json, string key) {
|
||||
string pattern = "\"" + key + "\":\"";
|
||||
int start = json.IndexOf(pattern, StringComparison.Ordinal);
|
||||
if (start < 0) return "";
|
||||
start += pattern.Length;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = start; i < json.Length; i++) {
|
||||
char c = json[i];
|
||||
if (c == '\\' && i + 1 < json.Length) {
|
||||
char next = json[i + 1];
|
||||
switch (next) {
|
||||
case '"': sb.Append('"'); i++; break;
|
||||
case '\\': sb.Append('\\'); i++; break;
|
||||
case 'n': sb.Append('\n'); i++; break;
|
||||
case 'r': sb.Append('\r'); i++; break;
|
||||
case 't': sb.Append('\t'); i++; break;
|
||||
default: sb.Append(c); break;
|
||||
}
|
||||
} else if (c == '"') {
|
||||
break;
|
||||
} else {
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static int ExtractJsonInt(string json, string key) {
|
||||
string pattern = "\"" + key + "\":";
|
||||
int start = json.IndexOf(pattern, StringComparison.Ordinal);
|
||||
if (start < 0) return 0;
|
||||
start += pattern.Length;
|
||||
|
||||
int end = start;
|
||||
while (end < json.Length && (char.IsDigit(json[end]) || json[end] == '-')) end++;
|
||||
if (end == start) return 0;
|
||||
return int.TryParse(json.AsSpan(start, end - start), out int val) ? val : 0;
|
||||
}
|
||||
|
||||
private static bool ExtractJsonBool(string json, string key) {
|
||||
string pattern = "\"" + key + "\":";
|
||||
int start = json.IndexOf(pattern, StringComparison.Ordinal);
|
||||
if (start < 0) return false;
|
||||
start += pattern.Length;
|
||||
return start < json.Length && json[start] == 't';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user