Skip to content

Commit 4144bf9

Browse files
committed
feat: support concurrent recompilation requests
1 parent bbec6a4 commit 4144bf9

File tree

1 file changed

+112
-47
lines changed

1 file changed

+112
-47
lines changed

Editor/Tools/RecompileScriptsTool.cs

Lines changed: 112 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,32 @@
1010
using UnityEngine;
1111

1212
namespace McpUnity.Tools {
13+
/// <summary>
14+
/// Tool to recompile all scripts in the Unity project
15+
/// </summary>
1316
public class RecompileScriptsTool : McpToolBase
1417
{
18+
private class CompilationRequest {
19+
public readonly bool ReturnWithLogs;
20+
public readonly int LogsLimit;
21+
public readonly TaskCompletionSource<JObject> CompletionSource;
22+
23+
public CompilationRequest(bool returnWithLogs, int logsLimit, TaskCompletionSource<JObject> completionSource)
24+
{
25+
ReturnWithLogs = returnWithLogs;
26+
LogsLimit = logsLimit;
27+
CompletionSource = completionSource;
28+
}
29+
}
30+
31+
private readonly List<CompilationRequest> _pendingRequests = new List<CompilationRequest>();
1532
private readonly List<CompilerMessage> _compilationLogs = new List<CompilerMessage>();
16-
private TaskCompletionSource<JObject> _completionSource;
1733
private int _processedAssemblies = 0;
18-
private bool _returnWithLogs = true;
19-
private int _logsLimit = 100;
2034

2135
public RecompileScriptsTool()
2236
{
2337
Name = "recompile_scripts";
24-
Description = "Recompiles all scripts in the Unity project. Use returnWithLogs parameter to control whether compilation logs are returned. Use logsLimit parameter to limit the number of logs returned when returnWithLogs is true.";
38+
Description = "Recompiles all scripts in the Unity project";
2539
IsAsync = true; // Compilation is asynchronous
2640
}
2741

@@ -32,68 +46,119 @@ public RecompileScriptsTool()
3246
/// <param name="tcs">TaskCompletionSource to set the result or exception</param>
3347
public override void ExecuteAsync(JObject parameters, TaskCompletionSource<JObject> tcs)
3448
{
35-
_completionSource = tcs;
36-
_compilationLogs.Clear();
37-
_processedAssemblies = 0;
38-
3949
// Extract and store parameters
40-
_returnWithLogs = GetBoolParameter(parameters, "returnWithLogs", true);
41-
_logsLimit = Mathf.Clamp(GetIntParameter(parameters, "logsLimit", 100), 0, 1000);
42-
43-
CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished;
44-
CompilationPipeline.compilationFinished += OnCompilationFinished;
45-
46-
if (EditorApplication.isCompiling == false) {
47-
McpLogger.LogInfo($"Recompiling all scripts in the Unity project (logsLimit: {_logsLimit})");
48-
CompilationPipeline.RequestScriptCompilation();
50+
var returnWithLogs = GetBoolParameter(parameters, "returnWithLogs", true);
51+
var logsLimit = Mathf.Clamp(GetIntParameter(parameters, "logsLimit", 100), 0, 1000);
52+
var request = new CompilationRequest(returnWithLogs, logsLimit, tcs);
53+
54+
bool hasActiveRequest = false;
55+
lock (_pendingRequests)
56+
{
57+
hasActiveRequest = _pendingRequests.Count > 0;
58+
_pendingRequests.Add(request);
4959
}
50-
else {
60+
61+
if (hasActiveRequest)
62+
{
5163
McpLogger.LogInfo("Recompilation already in progress. Waiting for completion...");
64+
return;
65+
}
66+
67+
// On first request, initialize compilation listeners and start compilation
68+
StartCompilationTracking();
69+
70+
if (EditorApplication.isCompiling == false)
71+
{
72+
McpLogger.LogInfo($"Recompiling all scripts in the Unity project (logsLimit: {logsLimit})");
73+
CompilationPipeline.RequestScriptCompilation();
5274
}
5375
}
5476

77+
/// <summary>
78+
/// Subscribe to compilation events, reset tracked state
79+
/// </summary>
80+
private void StartCompilationTracking()
81+
{
82+
_compilationLogs.Clear();
83+
_processedAssemblies = 0;
84+
CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished;
85+
CompilationPipeline.compilationFinished += OnCompilationFinished;
86+
}
87+
88+
/// <summary>
89+
/// Unsubscribe from compilation events
90+
/// </summary>
91+
private void StopCompilationTracking()
92+
{
93+
CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished;
94+
CompilationPipeline.compilationFinished -= OnCompilationFinished;
95+
}
96+
97+
/// <summary>
98+
/// Record compilation logs for every single assembly
99+
/// </summary>
100+
/// <param name="assemblyPath"></param>
101+
/// <param name="messages"></param>
55102
private void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages)
56103
{
57104
_processedAssemblies++;
58105
_compilationLogs.AddRange(messages);
59106
}
60107

108+
/// <summary>
109+
/// Complete all pending requests and stop tracking
110+
/// </summary>
111+
/// <param name="_"></param>
61112
private void OnCompilationFinished(object _)
62113
{
63114
McpLogger.LogInfo($"Recompilation completed. Processed {_processedAssemblies} assemblies with {_compilationLogs.Count} compiler messages");
64115

65-
CompilationPipeline.compilationFinished -= OnCompilationFinished;
66-
CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished;
116+
// Separate errors, warnings, and other messages
117+
List<CompilerMessage> errors = _compilationLogs.Where(m => m.type == CompilerMessageType.Error).ToList();
118+
List<CompilerMessage> warnings = _compilationLogs.Where(m => m.type == CompilerMessageType.Warning).ToList();
119+
List<CompilerMessage> others = _compilationLogs.Where(m => m.type != CompilerMessageType.Error && m.type != CompilerMessageType.Warning).ToList();
120+
121+
// Stop tracking before completing requests
122+
StopCompilationTracking();
123+
124+
// Complete all requests received before compilation end, the next received request will start a new compilation
125+
List<CompilationRequest> requestsToComplete = new List<CompilationRequest>();
126+
127+
lock (_pendingRequests)
128+
{
129+
requestsToComplete.AddRange(_pendingRequests);
130+
_pendingRequests.Clear();
131+
}
67132

68-
try
133+
foreach (var request in requestsToComplete)
69134
{
70-
// Format logs as JSON array similar to ConsoleLogsService
71-
JArray logsArray = new JArray();
72-
73-
// Separate errors, warnings, and other messages
74-
List<CompilerMessage> errors = _compilationLogs.Where(m => m.type == CompilerMessageType.Error).ToList();
75-
List<CompilerMessage> warnings = _compilationLogs.Where(m => m.type == CompilerMessageType.Warning).ToList();
76-
List<CompilerMessage> others = _compilationLogs.Where(m => m.type != CompilerMessageType.Error && m.type != CompilerMessageType.Warning).ToList();
135+
CompleteRequest(request, errors, warnings, others);
136+
}
137+
}
77138

78-
int errorCount = errors.Count;
79-
int warningCount = warnings.Count;
139+
/// <summary>
140+
/// Process a completed compilation request
141+
/// </summary>
142+
private static void CompleteRequest(CompilationRequest request, List<CompilerMessage> errors, List<CompilerMessage> warnings, List<CompilerMessage> others)
143+
{
144+
try {
145+
JArray logsArray = new JArray();
80146

81147
// Sort logs and apply logsLimit - prioritize errors if logsLimit is restrictive
82148
IEnumerable<CompilerMessage> sortedLogs;
83-
if (!_returnWithLogs || _logsLimit <= 0)
149+
if (!request.ReturnWithLogs || request.LogsLimit <= 0)
84150
{
85151
sortedLogs = Enumerable.Empty<CompilerMessage>();
86152
}
87-
else
88-
{
153+
else {
89154
// Always include all errors, then warnings, then other messages up to the logsLimit
90155
var selectedLogs = errors.ToList();
91-
var remainingSlots = _logsLimit - selectedLogs.Count;
156+
var remainingSlots = request.LogsLimit - selectedLogs.Count;
92157

93158
if (remainingSlots > 0)
94159
{
95160
selectedLogs.AddRange(warnings.Take(remainingSlots));
96-
remainingSlots = _logsLimit - selectedLogs.Count;
161+
remainingSlots = request.LogsLimit - selectedLogs.Count;
97162
}
98163

99164
if (remainingSlots > 0)
@@ -106,7 +171,7 @@ private void OnCompilationFinished(object _)
106171

107172
foreach (var message in sortedLogs)
108173
{
109-
var logObject = new JObject
174+
var logObject = new JObject
110175
{
111176
["message"] = message.message,
112177
["type"] = message.type.ToString(),
@@ -124,28 +189,28 @@ private void OnCompilationFinished(object _)
124189
logsArray.Add(logObject);
125190
}
126191

127-
bool hasErrors = errorCount > 0;
192+
bool hasErrors = errors.Count > 0;
128193
string summaryMessage = hasErrors
129-
? $"Recompilation completed with {errorCount} error(s) and {warningCount} warning(s)"
130-
: $"Successfully recompiled all scripts with {warningCount} warning(s)";
131-
132-
summaryMessage += $" (returnWithLogs: {_returnWithLogs}, logsLimit: {_logsLimit})";
194+
? $"Recompilation completed with {errors.Count} error(s) and {warnings.Count} warning(s)"
195+
: $"Successfully recompiled all scripts with {warnings.Count} warning(s)";
196+
197+
summaryMessage += $" (returnWithLogs: {request.ReturnWithLogs}, logsLimit: {request.LogsLimit})";
133198

134-
var response = new JObject
199+
var response = new JObject
135200
{
136201
["success"] = true,
137202
["type"] = "text",
138203
["message"] = summaryMessage,
139204
["logs"] = logsArray
140205
};
141206

142-
McpLogger.LogInfo($"Setting recompilation result: success={!hasErrors}, errors={errorCount}, warnings={warningCount}");
143-
_completionSource.SetResult(response);
144-
}
145-
catch (Exception ex)
207+
McpLogger.LogInfo($"Setting recompilation result: success={!hasErrors}, errors={errors.Count}, warnings={warnings.Count}");
208+
request.CompletionSource.SetResult(response);
209+
}
210+
catch (Exception ex)
146211
{
147212
McpLogger.LogError($"Error creating recompilation response: {ex.Message}\n{ex.StackTrace}");
148-
_completionSource.SetResult(McpUnitySocketHandler.CreateErrorResponse(
213+
request.CompletionSource.SetResult(McpUnitySocketHandler.CreateErrorResponse(
149214
$"Error creating recompilation response: {ex.Message}",
150215
"response_creation_error"
151216
));

0 commit comments

Comments
 (0)