Skip to content

Commit 476e42c

Browse files
committed
fix(screenshot): wait for end-of-frame without PlayerLoop re-entry
Play-mode include_image capture now finishes from WaitForEndOfFrame instead of reading the current backbuffer, and the capturer destroys itself on timeout so paused sessions cannot leak or hang the command. Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com>
1 parent a02ff2a commit 476e42c

7 files changed

Lines changed: 358 additions & 66 deletions

File tree

MCPForUnity/Editor/Tools/CommandRegistry.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,18 @@ public static object ExecuteCommand(string commandName, JObject @params, TaskCom
302302
throw new InvalidOperationException($"Handler for '{commandName}' does not provide a synchronous implementation");
303303
}
304304

305-
return handlerInfo.SyncHandler(@params);
305+
object result = handlerInfo.SyncHandler(@params);
306+
if (result is Task<object> returnedTask)
307+
{
308+
ExecuteAsyncHandler(
309+
new HandlerInfo(commandName, null, _ => returnedTask),
310+
@params,
311+
commandName,
312+
tcs);
313+
return null;
314+
}
315+
316+
return result;
306317
}
307318

308319
/// <summary>
@@ -332,6 +343,11 @@ public static Task<object> InvokeCommandAsync(string commandName, JObject @param
332343
}
333344

334345
object result = handlerInfo.SyncHandler(payload);
346+
if (result is Task<object> returnedTask)
347+
{
348+
return returnedTask;
349+
}
350+
335351
return Task.FromResult(result);
336352
}
337353

MCPForUnity/Editor/Tools/ManageScene.cs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Threading.Tasks;
56
using MCPForUnity.Editor.Helpers; // For Response class
67
using MCPForUnity.Runtime.Helpers; // For ScreenshotUtility
78
using Newtonsoft.Json.Linq;
@@ -613,18 +614,7 @@ private static object CaptureScreenshot(SceneCommand cmd)
613614
if (includeImage && Application.isPlaying)
614615
{
615616
if (!Application.isBatchMode) EnsureGameView();
616-
617-
string folderOverride = ScreenshotPreferences.Resolve(cmd.outputFolder);
618-
ScreenshotCaptureResult result = ScreenshotUtility.CaptureComposited(
619-
fileName, resolvedSuperSize, ensureUniqueFileName: true,
620-
includeImage: true, maxResolution: maxResolution,
621-
folderOverride: folderOverride);
622-
623-
if (ScreenshotUtility.IsUnderAssets(result.ProjectRelativePath))
624-
AssetDatabase.ImportAsset(result.ProjectRelativePath, ImportAssetOptions.ForceSynchronousImport);
625-
string cameraName = Camera.main != null ? Camera.main.name : "composited";
626-
string message = $"Screenshot captured to '{result.ProjectRelativePath}' (camera: {cameraName}).";
627-
return new SuccessResponse(message, BuildScreenshotResponseData(result, cameraName, includeImage: true));
617+
return CaptureCompositedScreenshotAsync(cmd, fileName, resolvedSuperSize, maxResolution);
628618
}
629619

630620
if (includeImage)
@@ -756,6 +746,38 @@ private static Dictionary<string, object> BuildScreenshotResponseData(
756746
return data;
757747
}
758748

749+
private static async Task<object> CaptureCompositedScreenshotAsync(
750+
SceneCommand cmd,
751+
string fileName,
752+
int resolvedSuperSize,
753+
int maxResolution)
754+
{
755+
string folderOverride = ScreenshotPreferences.Resolve(cmd.outputFolder);
756+
ScreenshotCaptureResult result;
757+
try
758+
{
759+
result = await ScreenshotUtility.CaptureCompositedAsync(
760+
fileName, resolvedSuperSize, ensureUniqueFileName: true,
761+
includeImage: true, maxResolution: maxResolution,
762+
folderOverride: folderOverride).ConfigureAwait(true);
763+
}
764+
catch (TimeoutException ex)
765+
{
766+
return new ErrorResponse(ex.Message);
767+
}
768+
catch (InvalidOperationException ex)
769+
{
770+
return new ErrorResponse(ex.Message);
771+
}
772+
773+
if (ScreenshotUtility.IsUnderAssets(result.ProjectRelativePath))
774+
AssetDatabase.ImportAsset(result.ProjectRelativePath, ImportAssetOptions.ForceSynchronousImport);
775+
776+
string cameraName = Camera.main != null ? Camera.main.name : "composited";
777+
string message = $"Screenshot captured to '{result.ProjectRelativePath}' (camera: {cameraName}).";
778+
return new SuccessResponse(message, BuildScreenshotResponseData(result, cameraName, includeImage: true));
779+
}
780+
759781
private static object CaptureSceneViewScreenshot(
760782
SceneCommand cmd,
761783
string fileName,

MCPForUnity/Editor/Tools/ManageUI.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,14 @@ private static object RenderUI(JObject @params)
866866
playFullPath = EnsureUniqueFilePath(playFullPath);
867867
string playProjectRelPath = ScreenshotUtility.ToProjectRelativePath(playFullPath);
868868

869+
if (s_pendingCaptureDone && s_pendingCaptureTex == null)
870+
{
871+
s_pendingCaptureDone = false;
872+
s_pendingCaptureStarted = false;
873+
return new ErrorResponse(
874+
"Play-mode screenshot timed out or captured nothing. Keep the Game view visible and the editor unpaused.");
875+
}
876+
869877
// ── Case 1: capture is ready ──────────────────────────────────────
870878
if (s_pendingCaptureDone && s_pendingCaptureTex != null)
871879
{

0 commit comments

Comments
 (0)