[Add] Synaptic AI Pro
https://assetstore.unity.com/packages/tools/generative-ai/synaptic-ai-pro-natural-language-control-for-unity-336030
This commit is contained in:
@@ -0,0 +1,420 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using SynapticAIPro;
|
||||
using UnityEditor;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SynapticPro
|
||||
{
|
||||
/// <summary>
|
||||
/// Port management system per project
|
||||
/// Assigns and manages unique ports for each Unity project
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public static class NexusProjectPortManager
|
||||
{
|
||||
private static string projectId;
|
||||
private static int assignedPort = -1;
|
||||
private static readonly string MAPPING_FILE_PATH;
|
||||
|
||||
// Project-port mapping information
|
||||
[Serializable]
|
||||
public class ProjectPortMapping
|
||||
{
|
||||
public Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ProjectInfo
|
||||
{
|
||||
public string projectName;
|
||||
public string projectPath;
|
||||
public int port;
|
||||
public DateTime lastUpdated;
|
||||
public bool isActive;
|
||||
}
|
||||
|
||||
static NexusProjectPortManager()
|
||||
{
|
||||
// Mapping file path (saved in user home directory)
|
||||
string homeDir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
|
||||
MAPPING_FILE_PATH = Path.Combine(homeDir, ".config", "nexus", "project_port_mapping.json");
|
||||
|
||||
// Initialize
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private static void Initialize()
|
||||
{
|
||||
// Generate project ID (hash value of project path)
|
||||
string projectPath = Application.dataPath;
|
||||
projectId = GetProjectId(projectPath);
|
||||
|
||||
SynLog.Info($"[Nexus Port Manager] Project ID: {projectId}");
|
||||
SynLog.Info($"[Nexus Port Manager] Project Path: {projectPath}");
|
||||
|
||||
// Assign port
|
||||
AssignPort();
|
||||
|
||||
// Update status periodically
|
||||
EditorApplication.update += UpdateProjectStatus;
|
||||
EditorApplication.quitting += OnEditorQuitting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate project ID
|
||||
/// </summary>
|
||||
private static string GetProjectId(string projectPath)
|
||||
{
|
||||
// Generate unique ID from project path
|
||||
using (var md5 = System.Security.Cryptography.MD5.Create())
|
||||
{
|
||||
byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(projectPath);
|
||||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant().Substring(0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign port
|
||||
/// </summary>
|
||||
private static void AssignPort()
|
||||
{
|
||||
var mapping = LoadMapping();
|
||||
|
||||
// Check existing port assignment
|
||||
if (mapping.projects.ContainsKey(projectId))
|
||||
{
|
||||
var info = mapping.projects[projectId];
|
||||
assignedPort = info.port;
|
||||
info.lastUpdated = DateTime.Now;
|
||||
info.isActive = true;
|
||||
info.projectName = PlayerSettings.productName;
|
||||
SynLog.Info($"[Nexus Port Manager] Using existing port: {assignedPort}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assign new port
|
||||
assignedPort = FindAvailablePort(mapping);
|
||||
mapping.projects[projectId] = new ProjectInfo
|
||||
{
|
||||
projectName = PlayerSettings.productName,
|
||||
projectPath = Application.dataPath,
|
||||
port = assignedPort,
|
||||
lastUpdated = DateTime.Now,
|
||||
isActive = true
|
||||
};
|
||||
SynLog.Info($"[Nexus Port Manager] Assigned new port: {assignedPort}");
|
||||
}
|
||||
|
||||
SaveMapping(mapping);
|
||||
|
||||
// Notify port to NexusEditorMCPService
|
||||
UpdateMCPServicePort();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find available port
|
||||
/// </summary>
|
||||
private static int FindAvailablePort(ProjectPortMapping mapping)
|
||||
{
|
||||
int[] candidatePorts = { 8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099 };
|
||||
|
||||
foreach (int port in candidatePorts)
|
||||
{
|
||||
bool isUsed = false;
|
||||
foreach (var project in mapping.projects.Values)
|
||||
{
|
||||
if (project.port == port && project.isActive)
|
||||
{
|
||||
isUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isUsed)
|
||||
{
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
// If all in use, search sequentially from 8090
|
||||
return 8090 + mapping.projects.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set port for MCP service
|
||||
/// </summary>
|
||||
private static void UpdateMCPServicePort()
|
||||
{
|
||||
if (assignedPort > 0)
|
||||
{
|
||||
// Check actual port of current MCP server
|
||||
string currentUrl = NexusEditorMCPService.GetServerUrl();
|
||||
if (!string.IsNullOrEmpty(currentUrl) && currentUrl.Contains("localhost:"))
|
||||
{
|
||||
// Get actually running port
|
||||
int startIndex = currentUrl.IndexOf("localhost:") + "localhost:".Length;
|
||||
int endIndex = currentUrl.IndexOf("/", startIndex);
|
||||
if (endIndex == -1) endIndex = currentUrl.Length;
|
||||
|
||||
if (int.TryParse(currentUrl.Substring(startIndex, endIndex - startIndex), out int actualPort))
|
||||
{
|
||||
// If actual port differs from assigned port, prioritize actual port
|
||||
if (actualPort != assignedPort && IsPortInUse(actualPort))
|
||||
{
|
||||
SynLog.Info($"[Nexus Port Manager] MCP Server is actually running on port {actualPort}, not {assignedPort}. Keeping actual port.");
|
||||
assignedPort = actualPort;
|
||||
|
||||
// Update mapping
|
||||
var mapping = LoadMapping();
|
||||
if (mapping.projects.ContainsKey(projectId))
|
||||
{
|
||||
mapping.projects[projectId].port = actualPort;
|
||||
SaveMapping(mapping);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only use saved port if actual server not found
|
||||
string serverUrl = $"ws://localhost:{assignedPort}";
|
||||
NexusEditorMCPService.SetServerUrl(serverUrl);
|
||||
SynLog.Info($"[Nexus Port Manager] Updated MCP Service URL: {serverUrl}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if specified port is in use
|
||||
/// </summary>
|
||||
private static bool IsPortInUse(int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var tcpClient = new System.Net.Sockets.TcpClient())
|
||||
{
|
||||
var result = tcpClient.BeginConnect("localhost", port, null, null);
|
||||
bool success = result.AsyncWaitHandle.WaitOne(100);
|
||||
if (success)
|
||||
{
|
||||
tcpClient.EndConnect(result);
|
||||
tcpClient.Close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update project status
|
||||
/// </summary>
|
||||
private static void UpdateProjectStatus()
|
||||
{
|
||||
// Update status every 5 minutes
|
||||
if (EditorApplication.timeSinceStartup % 300 < 1)
|
||||
{
|
||||
var mapping = LoadMapping();
|
||||
if (mapping.projects.ContainsKey(projectId))
|
||||
{
|
||||
mapping.projects[projectId].lastUpdated = DateTime.Now;
|
||||
mapping.projects[projectId].isActive = true;
|
||||
SaveMapping(mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processing on editor exit
|
||||
/// </summary>
|
||||
private static void OnEditorQuitting()
|
||||
{
|
||||
var mapping = LoadMapping();
|
||||
if (mapping.projects.ContainsKey(projectId))
|
||||
{
|
||||
mapping.projects[projectId].isActive = false;
|
||||
SaveMapping(mapping);
|
||||
}
|
||||
|
||||
EditorApplication.update -= UpdateProjectStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load mapping information
|
||||
/// </summary>
|
||||
private static ProjectPortMapping LoadMapping()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(MAPPING_FILE_PATH))
|
||||
{
|
||||
string json = File.ReadAllText(MAPPING_FILE_PATH);
|
||||
|
||||
// Validate and clean JSON string
|
||||
json = json.Trim();
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
SynLog.Warn("[Nexus Port Manager] Mapping file is empty, creating new mapping.");
|
||||
return new ProjectPortMapping();
|
||||
}
|
||||
|
||||
// Check JSON syntax
|
||||
if (!json.StartsWith("{") || !json.EndsWith("}"))
|
||||
{
|
||||
Debug.LogError($"[Nexus Port Manager] Invalid JSON format in mapping file. Content: {json.Substring(0, Math.Min(100, json.Length))}...");
|
||||
return new ProjectPortMapping();
|
||||
}
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ProjectPortMapping>(json);
|
||||
return result ?? new ProjectPortMapping();
|
||||
}
|
||||
}
|
||||
catch (JsonException jsonEx)
|
||||
{
|
||||
Debug.LogError($"[Nexus Port Manager] JSON parsing error: {jsonEx.Message}");
|
||||
Debug.LogError($"[Nexus Port Manager] Recreating mapping file due to corruption.");
|
||||
|
||||
// Move-aside corrupted file, then write a fresh empty mapping so
|
||||
// the next LoadMapping doesn't hit the same corrupt content and
|
||||
// spam the console at frame rate.
|
||||
// Bugs the previous implementation had:
|
||||
// 1. `File.Move` to an existing `.backup` throws on Windows,
|
||||
// the silent catch left the corrupt file in place → loop.
|
||||
// 2. Even on success, no fresh file was written, so any later
|
||||
// reader before a SaveMapping run kept hitting the error
|
||||
// (relevant when MAPPING_FILE_PATH still existed under
|
||||
// filesystem latency on slow disks).
|
||||
try
|
||||
{
|
||||
string backupPath = MAPPING_FILE_PATH + ".backup";
|
||||
if (File.Exists(backupPath))
|
||||
{
|
||||
try { File.Delete(backupPath); } catch { }
|
||||
}
|
||||
if (File.Exists(MAPPING_FILE_PATH))
|
||||
{
|
||||
try { File.Move(MAPPING_FILE_PATH, backupPath); }
|
||||
catch
|
||||
{
|
||||
// Move failed (permissions, fs lock) — delete the
|
||||
// corrupt source outright so we don't loop.
|
||||
try { File.Delete(MAPPING_FILE_PATH); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// Immediately materialize a clean mapping file so concurrent
|
||||
// readers (and the next Update tick) see valid JSON.
|
||||
var fresh = new ProjectPortMapping();
|
||||
var freshJson = JsonConvert.SerializeObject(fresh, Formatting.Indented);
|
||||
File.WriteAllText(MAPPING_FILE_PATH, freshJson);
|
||||
|
||||
SynLog.Info($"[Nexus Port Manager] Corrupted file backed up to: {backupPath}");
|
||||
return fresh;
|
||||
}
|
||||
catch (Exception writeErr)
|
||||
{
|
||||
Debug.LogError($"[Nexus Port Manager] Failed to reset mapping file: {writeErr.Message}");
|
||||
return new ProjectPortMapping();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Nexus Port Manager] Failed to load mapping: {e.Message}");
|
||||
}
|
||||
|
||||
return new ProjectPortMapping();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save mapping information
|
||||
/// </summary>
|
||||
private static void SaveMapping(ProjectPortMapping mapping)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Clean up old entries (not updated for more than 30 days)
|
||||
var keysToRemove = new List<string>();
|
||||
foreach (var kvp in mapping.projects)
|
||||
{
|
||||
if ((DateTime.Now - kvp.Value.lastUpdated).TotalDays > 30)
|
||||
{
|
||||
keysToRemove.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in keysToRemove)
|
||||
{
|
||||
mapping.projects.Remove(key);
|
||||
}
|
||||
|
||||
// Create directory
|
||||
string directory = Path.GetDirectoryName(MAPPING_FILE_PATH);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// Save
|
||||
string json = JsonConvert.SerializeObject(mapping, Formatting.Indented);
|
||||
File.WriteAllText(MAPPING_FILE_PATH, json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Nexus Port Manager] Failed to save mapping: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get port for current project
|
||||
/// </summary>
|
||||
public static int GetAssignedPort()
|
||||
{
|
||||
return assignedPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current project ID
|
||||
/// </summary>
|
||||
public static string GetProjectId()
|
||||
{
|
||||
return projectId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display mapping information (for debugging)
|
||||
/// </summary>
|
||||
[MenuItem("Tools/Synaptic Pro/Show Port Mapping")]
|
||||
public static void ShowPortMapping()
|
||||
{
|
||||
var mapping = LoadMapping();
|
||||
|
||||
System.Text.StringBuilder info = new System.Text.StringBuilder();
|
||||
info.AppendLine("🔌 Nexus Project Port Mapping");
|
||||
info.AppendLine("================================");
|
||||
info.AppendLine($"Current Project ID: {projectId}");
|
||||
info.AppendLine($"Assigned Port: {assignedPort}");
|
||||
info.AppendLine("\nAll Projects:");
|
||||
|
||||
foreach (var kvp in mapping.projects)
|
||||
{
|
||||
var project = kvp.Value;
|
||||
info.AppendLine($"\n📁 {project.projectName}");
|
||||
info.AppendLine($" ID: {kvp.Key}");
|
||||
info.AppendLine($" Port: {project.port}");
|
||||
info.AppendLine($" Path: {project.projectPath}");
|
||||
info.AppendLine($" Active: {project.isActive}");
|
||||
info.AppendLine($" Last Updated: {project.lastUpdated}");
|
||||
}
|
||||
|
||||
SynLog.Info(info.ToString());
|
||||
EditorUtility.DisplayDialog("Port Mapping", info.ToString(), "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user