using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using OpenNest.Engine.Jobs.Cutouts;
namespace OpenNest.Engine.Jobs.Remote;
///
/// The nest server worker's latest progress report for a running job: the stage of the solve
/// in progress, without its plan (plans travel as ). Committed
/// counts follow : authoritative only after a commit.
///
public sealed class RemoteNestProgress
{
public const int CurrentVersion = 1;
public int Version { get; set; } = CurrentVersion;
public NestJobStage Stage { get; set; }
public string StockId { get; set; } = "";
public int PlateIndex { get; set; }
public int CommittedPlates { get; set; }
public int CommittedParts { get; set; }
public static RemoteNestProgress From(NestJobProgress progress)
{
ArgumentNullException.ThrowIfNull(progress);
return new RemoteNestProgress
{
Stage = progress.Stage,
StockId = progress.StockId ?? "",
PlateIndex = progress.PlateIndex,
CommittedPlates = progress.CommittedPlates,
CommittedParts = progress.CommittedParts,
};
}
public NestJobProgress ToProgress() => new(Stage, StockId, PlateIndex, CommittedPlates, CommittedParts);
public string ToJson() => JsonSerializer.Serialize(this, NestSolveTranscript.Options);
/// The report, or null when is empty or not one this build reads.
public static RemoteNestProgress FromJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
return null;
try
{
var progress = JsonSerializer.Deserialize(json, NestSolveTranscript.Options);
return progress?.Version == CurrentVersion ? progress : null;
}
catch (JsonException)
{
return null;
}
}
}
///
/// The engine's best whole-job plan so far on the worker (),
/// as raw placements. The desktop draws it with ; like a local
/// plan it is unvalidated and never kept.
///
public sealed class RemoteNestPlan
{
public const int CurrentVersion = 1;
public int Version { get; set; } = CurrentVersion;
public NestSolveTranscript.ResultJson Plan { get; set; }
public static RemoteNestPlan From(NestJobResult plan)
{
ArgumentNullException.ThrowIfNull(plan);
return new RemoteNestPlan { Plan = NestSolveTranscript.ResultJson.From(plan) };
}
public string ToJson() => JsonSerializer.Serialize(this, NestSolveTranscript.Options);
/// The plan, or null when is not one this build reads.
public static RemoteNestPlan FromJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
return null;
try
{
var plan = JsonSerializer.Deserialize(json, NestSolveTranscript.Options);
return plan?.Version == CurrentVersion && plan.Plan?.Plates != null ? plan : null;
}
catch (JsonException)
{
return null;
}
}
}
///
/// Draws the worker's best plan so far on the desktop, the way a local run's
/// are drawn: the request's job is rebuilt here (the
/// same part ids the worker solved), and parts go on detached drawings built from its geometry,
/// so nothing touches the caller's drawings or quantities. A bundled cutout trial's frame proxies
/// are bundled here too, once, the first time a plan names one. Not thread-safe: use one
/// preview from one thread at a time.
///
public sealed class RemoteNestPlanPreview
{
private readonly NestJob job;
private readonly IReadOnlyDictionary callerDrawings;
private readonly Dictionary parts;
private readonly Dictionary drawings = new(StringComparer.Ordinal);
private bool bundled;
public RemoteNestPlanPreview(NestPipelineRequest request, CancellationToken token = default)
{
(job, var byPart) = NestPipeline.CreateJob(request, token);
callerDrawings = byPart;
parts = job.Parts.ToDictionary(p => p.Id, StringComparer.Ordinal);
}
/// The plan as sheets, or null when is unreadable or
/// names parts this request does not have.
public IReadOnlyList Sheets(string planJson, CancellationToken token = default)
{
if (RemoteNestPlan.FromJson(planJson)?.Plan is not { } plan)
return null;
if (!Known(plan) && !bundled)
{
bundled = true;
if (CutoutPipelinePrepass.Prepare(job, token)?.EngineJob is { } engineJob)
{
foreach (var part in engineJob.Parts)
parts.TryAdd(part.Id, part);
}
}
if (!Known(plan))
return null;
try
{
return NestPipeline.PlanSheets(plan.ToResult(job), parts, callerDrawings, drawings, token);
}
catch (FormatException)
{
return null;
}
}
private bool Known(NestSolveTranscript.ResultJson plan) =>
plan.Plates.All(s => s?.Placements != null && s.Placements.All(p => p.PartId != null && parts.ContainsKey(p.PartId)));
}