using System.Text.Json; using System.Text.Json.Nodes; using DotCraft.Unity; namespace Agc.Unity.Attach; public sealed record EditorTarget(int Pid, DateTime StartedUtc, string? ProjectPath, string? EditorPath); public interface IEditorBackend : IAsyncDisposable { Task> DetectAsync(CancellationToken cancellationToken); Task ConnectAsync(EditorTarget target, CancellationToken cancellationToken); Task StatusAsync(CancellationToken cancellationToken); Task ExecuteAsync(string code, Action onDispatch, CancellationToken cancellationToken); Task WaitAsync(string executionId, CancellationToken cancellationToken); Task DisconnectAsync(CancellationToken cancellationToken); void ForgetSelection(); } internal sealed class AttachBackend : IEditorBackend { private readonly string session = Guid.NewGuid().ToString("N"); private readonly UnityAttachService service = new( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Genarrative", "UnityAttach"), ""); public Task> DetectAsync(CancellationToken cancellationToken) { if (!OperatingSystem.IsWindows() || !Environment.Is64BitProcess) throw new HostException("UnityPlatformUnsupported", "Unity Attach requires Windows x64."); cancellationToken.ThrowIfCancellationRequested(); var values = JsonSerializer.SerializeToNode(service.List())!.AsArray(); IReadOnlyList targets = values.Select(value => new EditorTarget( value!["pid"]!.GetValue(), value["startedUtc"]!.GetValue().ToUniversalTime(), value["project"]?.GetValue(), value["editor"]?.GetValue())).ToArray(); cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(targets); } public Task ConnectAsync(EditorTarget target, CancellationToken cancellationToken) => service.Connect(session, target.Pid, cancellationToken); public Task StatusAsync(CancellationToken cancellationToken) => service.Status(session, cancellationToken); public Task ExecuteAsync(string code, Action onDispatch, CancellationToken cancellationToken) => service.Execute(session, code, null, true, 0, cancellationToken, onDispatch); public Task WaitAsync(string executionId, CancellationToken cancellationToken) => service.Wait(session, executionId, 250, false, cancellationToken); public async Task DisconnectAsync(CancellationToken cancellationToken) => await service.Disconnect(session, cancellationToken); public void ForgetSelection() => service.ForgetSelection(session); public ValueTask DisposeAsync() => service.DisposeAsync(); } public sealed class HostException(string code, string message) : Exception(message) { public string Code { get; } = code; }