using System; using System.Threading; using System.Threading.Tasks; using UnityEngine; using Stopwatch = System.Diagnostics.Stopwatch; namespace io.github.hatayama.uLoopMCP { /// /// Provides the runtime-facing facade for execute-dynamic-code. /// Entry points depend on this facade instead of reaching into factory and executor wiring directly. /// internal sealed class DynamicCodeExecutionFacade : IShutdownAwareDynamicCodeExecutionRuntime, IDisposable { private readonly ICompiledAssemblyBuilder _assemblyBuilder; private readonly IDynamicCodeExecutorPool _executorPool; private readonly DynamicCodeExecutionScheduler _executionScheduler; public DynamicCodeExecutionFacade( ICompiledAssemblyBuilder assemblyBuilder, IDynamicCodeExecutorPool executorPool) { _assemblyBuilder = assemblyBuilder ?? throw new ArgumentNullException(nameof(assemblyBuilder)); _executorPool = executorPool ?? throw new ArgumentNullException(nameof(executorPool)); _executionScheduler = new DynamicCodeExecutionScheduler(_executorPool.Dispose); } public bool SupportsAutoPrewarm() { _executionScheduler.ThrowIfDisposed(); return _assemblyBuilder.SupportsAutoPrewarm(); } public async Task ExecuteAsync( DynamicCodeExecutionRequest request, CancellationToken cancellationToken = default) { Debug.Assert(request != null, "request must not be null"); Debug.Assert(!string.IsNullOrWhiteSpace(request.Code), "request.Code must not be empty"); return await _executionScheduler.RunForegroundAsync( innerCancellationToken => ExecuteCoreAsync(request, innerCancellationToken), CreateExecutionInProgressResult, cancellationToken); } public async Task<(bool Entered, ExecutionResult Result)> TryExecuteIfIdleAsync( DynamicCodeExecutionRequest request, CancellationToken cancellationToken = default) { Debug.Assert(request != null, "request must not be null"); Debug.Assert(!string.IsNullOrWhiteSpace(request.Code), "request.Code must not be empty"); return await _executionScheduler.TryRunIfIdleAsync( request.YieldToForegroundRequests, innerCancellationToken => ExecuteCoreAsync(request, innerCancellationToken), cancellationToken); } private async Task ExecuteCoreAsync( DynamicCodeExecutionRequest request, CancellationToken cancellationToken) { Stopwatch executorAcquireStopwatch = Stopwatch.StartNew(); IDynamicCodeExecutor executor = _executorPool.GetOrCreate(request.SecurityLevel); executorAcquireStopwatch.Stop(); Stopwatch executorTotalStopwatch = Stopwatch.StartNew(); ExecutionResult result = await executor.ExecuteCodeAsync( request.Code, request.ClassName, request.Parameters, cancellationToken, request.CompileOnly); executorTotalStopwatch.Stop(); if (result.Timings == null) { result.Timings = new System.Collections.Generic.List(); } result.Timings.Add($"[Perf] ExecutorAcquire: {executorAcquireStopwatch.Elapsed.TotalMilliseconds:F1}ms"); result.Timings.Add($"[Perf] ExecutorTotal: {executorTotalStopwatch.Elapsed.TotalMilliseconds:F1}ms"); return result; } private static ExecutionResult CreateExecutionInProgressResult() { return new ExecutionResult { Success = false, ErrorMessage = McpConstants.ERROR_MESSAGE_EXECUTION_IN_PROGRESS }; } public void Dispose() { _executionScheduler.Dispose(); } public Task ShutdownAsync() { return _executionScheduler.ShutdownAsync(); } } }