#if UNITY_EDITOR_OSX || UNITY_EDITOR_WIN
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityTcp.Editor.Helpers;
namespace Cn.Tuanjie.Codely.Editor
{
///
/// Global IPC manager shared by Codely-related Editor windows.
/// Manages one CodelyIpcClient instance and reference-counted owners.
///
internal static class CodelyIpcManager
{
private static readonly object _lockObj = new object();
private static readonly HashSet _owners = new HashSet();
private static CodelyIpcClient _client;
// Messages sent before the IPC client connects (e.g. trackEvent in
// OnEnable before AcquireIpcClient) are buffered here and flushed
// once the connection is established.
private static readonly Queue> _pendingMessages = new Queue>();
private const int MAX_PENDING_MESSAGES = 50;
public static event Action OnServerReady;
public static event Action OnSetEmbedMode;
public static event Action OnCloseCodelyEditorOnWorkspaceSwitch;
public static event Action OnConnectionChanged;
public static event Action OnUpdateRestart;
public static event Action OnDrillCompleted;
// 最近一次 server_ready 携带的 URL。后台线程在 delayCall 入队前先写入此字段,
// 让 CodelyWindow / DrillWindow 的 OnGUI backstop 能在主线程 update loop
// (失焦下被节流)尚未跑通时直接拾起此 URL 完成 WebView2 初始化。
// 字符串赋值在引用大小上原子,volatile 保证发布可见性,无需额外锁。
private static volatile string _lastServerReadyUrl;
public static string LastServerReadyUrl => _lastServerReadyUrl;
internal static void SetLastServerReadyUrl(string url) => _lastServerReadyUrl = url;
public static void Acquire(string owner)
{
if (string.IsNullOrEmpty(owner))
{
throw new ArgumentException("IPC owner must be non-empty", nameof(owner));
}
lock (_lockObj)
{
_owners.Add(owner);
EnsureClientStartedLocked();
}
}
public static void Release(string owner)
{
if (string.IsNullOrEmpty(owner))
{
return;
}
lock (_lockObj)
{
_owners.Remove(owner);
if (_owners.Count == 0)
{
StopClientLocked();
}
}
}
public static void StopAll()
{
lock (_lockObj)
{
_owners.Clear();
StopClientLocked();
}
}
public static bool TrySend(string messageType, string data = null)
{
lock (_lockObj)
{
if (_client == null || !_client.IsConnected)
{
// Buffer the message so it can be delivered once the IPC
// client connects. Without this, trackEvent calls made in
// OnEnable (before AcquireIpcClient) are silently lost.
if (_pendingMessages.Count < MAX_PENDING_MESSAGES)
{
_pendingMessages.Enqueue(new KeyValuePair(messageType, data));
}
return false;
}
return _client.SendMessage(messageType, data);
}
}
///
/// Drain the pending message queue. Must be called under _lockObj.
///
private static void FlushPendingMessagesLocked()
{
while (_pendingMessages.Count > 0)
{
if (_client == null || !_client.IsConnected)
break;
var msg = _pendingMessages.Dequeue();
_client.SendMessage(msg.Key, msg.Value);
}
}
public static bool IsConnected
{
get
{
lock (_lockObj)
{
return _client != null && _client.IsConnected;
}
}
}
private static void EnsureClientStartedLocked()
{
if (_client != null)
{
return;
}
_client = new CodelyIpcClient();
_client.OnServerReady += HandleServerReady;
_client.OnSetEmbedMode += HandleSetEmbedMode;
_client.OnCloseCodelyEditorOnWorkspaceSwitch += HandleCloseCodelyEditorOnWorkspaceSwitch;
_client.OnConnectionChanged += HandleConnectionChanged;
_client.OnUpdateRestart += HandleUpdateRestart;
_client.OnDrillCompleted += HandleDrillCompleted;
_client.Start();
CodelyLogger.Log("Codely IPC Manager: client started");
}
private static void StopClientLocked()
{
if (_client == null)
{
return;
}
FlushPendingMessagesLocked();
var clientToDispose = _client;
_client = null;
try
{
clientToDispose.OnServerReady -= HandleServerReady;
clientToDispose.OnSetEmbedMode -= HandleSetEmbedMode;
clientToDispose.OnCloseCodelyEditorOnWorkspaceSwitch -= HandleCloseCodelyEditorOnWorkspaceSwitch;
clientToDispose.OnConnectionChanged -= HandleConnectionChanged;
clientToDispose.OnUpdateRestart -= HandleUpdateRestart;
clientToDispose.OnDrillCompleted -= HandleDrillCompleted;
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"Codely IPC Manager: stop failed: {ex.Message}");
}
Task.Run(() =>
{
try
{
clientToDispose.Dispose();
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"Codely IPC Manager: async dispose failed: {ex.Message}");
}
});
CodelyLogger.Log("Codely IPC Manager: client stop scheduled");
}
private static void HandleServerReady(string url) => OnServerReady?.Invoke(url);
private static void HandleSetEmbedMode(bool embedMode) => OnSetEmbedMode?.Invoke(embedMode);
private static void HandleCloseCodelyEditorOnWorkspaceSwitch() => OnCloseCodelyEditorOnWorkspaceSwitch?.Invoke();
private static void HandleConnectionChanged(bool connected)
{
if (connected)
{
lock (_lockObj)
{
FlushPendingMessagesLocked();
}
}
OnConnectionChanged?.Invoke(connected);
}
private static void HandleUpdateRestart(string payload) => OnUpdateRestart?.Invoke(payload);
private static void HandleDrillCompleted()
{
var handler = OnDrillCompleted;
if (handler != null)
{
handler.Invoke();
}
else
{
CodelyIpcClient.ConsumePendingDrillCompleted();
}
}
}
}
#endif