/* ┌──────────────────────────────────────────────────────────────────┐ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ │ Copyright (c) 2025 Ivan Murzak │ │ Licensed under the Apache License, Version 2.0. │ │ See the LICENSE file in the project root for more information. │ └──────────────────────────────────────────────────────────────────┘ */ #nullable enable using System.Collections.Generic; using System.Linq; using com.IvanMurzak.McpPlugin.Common.Model; using UnityEditor; namespace com.IvanMurzak.Unity.MCP.Editor.Utils { /// /// Utility class for handling package operations that may trigger domain reloads. /// Uses SessionState to persist notification data across domain reloads. /// public static class PackageUtils { private const string PendingNotificationKeysKey = "MCP_PendingPackageNotificationKeys"; private const string NotificationDataSeparator = ""; private static bool _processPendingScheduled = false; public static void Init() { // Process any pending notifications after domain reload ScheduleProcessPendingNotifications(); } private static void ScheduleProcessPendingNotifications() { if (_processPendingScheduled) return; _processPendingScheduled = true; EditorApplication.update += ProcessPendingNotificationsOnce; } private static void ProcessPendingNotificationsOnce() { EditorApplication.update -= ProcessPendingNotificationsOnce; if (!_processPendingScheduled) return; _processPendingScheduled = false; ProcessPendingNotifications(); } /// /// Schedules a notification to be sent after Unity domain reload completes. /// Uses SessionState to persist across domain reloads. /// /// The request ID to track /// The package identifier that was operated on /// The type of operation performed (Add, Remove) /// Whether the operation was expected to succeed before domain reload public static void SchedulePostDomainReloadNotification(string requestId, string packageIdentifier, string operationType, bool expectedResult) { var notificationKey = $"MCP_PendingPackageNotification_{requestId}"; var notificationData = $"{requestId}{NotificationDataSeparator}{packageIdentifier}{NotificationDataSeparator}{operationType}{NotificationDataSeparator}{expectedResult}"; // Store the notification data SessionState.SetString(notificationKey, notificationData); // Add to the key list var existingKeys = SessionState.GetString(PendingNotificationKeysKey, string.Empty); var keyList = string.IsNullOrEmpty(existingKeys) ? new List() : existingKeys.Split(',').Where(k => !string.IsNullOrEmpty(k)).ToList(); if (!keyList.Contains(notificationKey)) { keyList.Add(notificationKey); SessionState.SetString(PendingNotificationKeysKey, string.Join(",", keyList)); } } /// /// Called by InitializeOnLoad to process any pending notifications after domain reload. /// public static void ProcessPendingNotifications() { // Get the list of pending notification keys from SessionState var pendingKeys = SessionState.GetString(PendingNotificationKeysKey, string.Empty); if (string.IsNullOrEmpty(pendingKeys)) return; var keys = pendingKeys.Split(',').Where(k => !string.IsNullOrEmpty(k)).ToList(); if (keys.Count == 0) return; var processedKeys = new List(); foreach (var key in keys) { var notificationData = SessionState.GetString(key, string.Empty); if (string.IsNullOrEmpty(notificationData)) { processedKeys.Add(key); continue; } var parts = notificationData.Split(NotificationDataSeparator); if (parts.Length != 4) { processedKeys.Add(key); continue; } var requestId = parts[0]; var packageIdentifier = parts[1]; var operationType = parts[2]; var expectedResult = bool.Parse(parts[3]); // Check for compilation errors after package operation var hasCompilationErrors = ScriptUtils.HasCompilationErrors(); ResponseCallTool response; if (hasCompilationErrors) { var errorDetails = ScriptUtils.GetCompilationErrorDetails(); var message = $"[Warning] Package {operationType} completed: {packageIdentifier}, but compilation errors occurred after domain reload. Details:\n{errorDetails}"; response = ResponseCallTool.Success(message).SetRequestID(requestId); } else if (expectedResult) { var message = $"[Success] Package {operationType} completed: {packageIdentifier}. Domain reload finished successfully."; response = ResponseCallTool.Success(message).SetRequestID(requestId); } else { var message = $"[Error] Package {operationType} failed: {packageIdentifier}"; response = ResponseCallTool.Error(message).SetRequestID(requestId); } // Send notification and mark for cleanup _ = UnityMcpPluginEditor.NotifyToolRequestCompleted(new RequestToolCompletedData { RequestId = requestId, Result = response }); processedKeys.Add(key); } // Clean up processed keys foreach (var key in processedKeys) SessionState.EraseString(key); // Update the key list var remainingKeys = keys.Except(processedKeys).ToList(); if (remainingKeys.Count > 0) { SessionState.SetString(PendingNotificationKeysKey, string.Join(",", remainingKeys)); } else { SessionState.EraseString(PendingNotificationKeysKey); } } } }