using System; using Unity.Pipeline.Models; using Unity.Pipeline.Compilation; using UnityEngine; using Unity.Pipeline.Commands; namespace Unity.Pipeline.Runtime.Commands { /// /// Command for evaluating C# code dynamically using Roslyn compilation. /// Requires security token for authorization. /// public static class CodeEvalCommand { [CliCommand("eval", "Evaluate C# code dynamically using Roslyn compiler", MainThreadRequired = true)] public static EvalResponse EvaluateCode( [CliArg("code", "C# code to evaluate", Required = true)] string code, [CliArg("timeout", "Timeout in milliseconds")] int timeout = 5000) { if (string.IsNullOrWhiteSpace(code)) { return EvalResponse.EvalFailure( "Bad Request", "Code parameter is required and cannot be empty"); } return EvaluateSource(code, timeout); } [CliCommand("eval_file", "Evaluate C# code read from a .cs file on disk", MainThreadRequired = true)] public static EvalResponse EvaluateFile( [CliArg("file", "Path to a .cs file to evaluate", Required = true)] string file, [CliArg("timeout", "Timeout in milliseconds")] int timeout = 5000) { if (string.IsNullOrWhiteSpace(file)) { return EvalResponse.EvalFailure( "Bad Request", "File parameter is required and cannot be empty"); } if (!file.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) { return EvalResponse.EvalFailure( "Bad Request", "File must have a .cs extension"); } if (!System.IO.File.Exists(file)) { return EvalResponse.EvalFailure( "Bad Request", $"File not found: {file}"); } string code; try { code = System.IO.File.ReadAllText(file); } catch (Exception ex) { return EvalResponse.EvalFailure( "Bad Request", $"Failed to read file: {ex.Message}"); } if (string.IsNullOrWhiteSpace(code)) { return EvalResponse.EvalFailure( "Bad Request", $"File is empty: {file}"); } return EvaluateSource(code, timeout); } /// /// Shared evaluation path: compiles and executes the given C# source and returns the /// response. Both the eval and eval_file commands funnel their resolved /// source string into here. /// private static EvalResponse EvaluateSource(string code, int timeout) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { // Validate timeout if (timeout <= 0 || timeout > 30000) { stopwatch.Stop(); return EvalResponse.EvalFailure( "Bad Request", "Timeout must be between 1ms and 30000ms", stopwatch.ElapsedMilliseconds); } // Execute compilation and evaluation. We're already on the main thread // (MainThreadRequired = true), so call the synchronous path directly. var result = EvalCodeCompiler.CompileAndExecuteOnMainThread(code, timeout, null); stopwatch.Stop(); // Update execution time if (result != null) { result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds; } return result ?? EvalResponse.EvalFailure( "Unknown Error", "Compilation returned null result", stopwatch.ElapsedMilliseconds); } catch (Exception ex) { stopwatch.Stop(); Debug.LogError($"Pipeline: Eval command failed: {ex.Message}"); Debug.LogError($"Pipeline: Stack trace: {ex.StackTrace}"); return EvalResponse.EvalFailure( "Execution Failed", ex.ToString(), stopwatch.ElapsedMilliseconds); } } } }