mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 00:01:36 -05:00
Large nests tie up the workstation that runs them. In Database mode, Auto Nest can now queue the job on the nest server, where a worker container solves one job at a time under its own CPU and memory limits. The job has no time limit, so a throttled worker finds the same nest a fast one does, only later. - Server: /api/nest-jobs queue (schema v15). The worker holds a job on a heartbeat lease. When the lease lapses, the job goes back to the queue, or fails after three attempts. Only one job runs at a time. Packages and results are opaque to the HTTP server, which stays off Engine and IO. - Worker: OpenNest.Server.Worker in the same image (OPENNEST_ROLE=worker), reaching the server over HTTP only. It records each engine answer with RecordingNestingEngine and gives its job back on shutdown. - Desktop: "Nest on the server" in the Auto Nest dialog, for built-in engines only. The client replays the recorded answers with RemoteNestSubmission, so validation, the review and Keep run locally against the client's own drawings, exactly as after a local run. A drawing edited after sending refuses the result. If the server cannot take the job, the user may nest locally instead. - compose.server.yaml adds the throttled worker service. The Dockerfile, .dockerignore, CI paths and the deploy script now include the worker's projects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
4.7 KiB
C#
106 lines
4.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace OpenNest.Engine.Jobs.Remote;
|
|
|
|
/// <summary>
|
|
/// Every engine solve one <see cref="NestPipeline.Run(INestingEngine, string, NestPipelineRequest, IProgress{NestJobProgress}, System.Threading.CancellationToken)"/>
|
|
/// made, in order: what it asked (part and stock ids with their quantities) and the raw result
|
|
/// the engine returned. A remote solve sends this back instead of bound plates, so the client
|
|
/// replays the pipeline against its own drawings and validates and binds the result itself
|
|
/// (<see cref="RemoteNestSubmission"/>). Poses are kept bit for bit, nonfinite ones included,
|
|
/// so the client's validation sees exactly what the engine returned.
|
|
/// </summary>
|
|
public sealed class NestSolveTranscript
|
|
{
|
|
public const int CurrentVersion = 1;
|
|
|
|
private static readonly JsonSerializerOptions Options = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
PropertyNameCaseInsensitive = true,
|
|
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
|
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
|
|
};
|
|
|
|
public int Version { get; set; } = CurrentVersion;
|
|
public List<SolveJson> Solves { get; set; } = new();
|
|
|
|
public string ToJson() => JsonSerializer.Serialize(this, Options);
|
|
|
|
/// <summary>Reads a transcript; throws <see cref="FormatException"/> when it is not one this build understands.</summary>
|
|
public static NestSolveTranscript FromJson(string json)
|
|
{
|
|
NestSolveTranscript transcript;
|
|
try { transcript = JsonSerializer.Deserialize<NestSolveTranscript>(json, Options); }
|
|
catch (JsonException ex) { throw new FormatException("The server's nest result is not valid JSON.", ex); }
|
|
if (transcript?.Solves == null || transcript.Solves.Any(s => s?.Result == null))
|
|
throw new FormatException("The server's nest result is incomplete.");
|
|
if (transcript.Version != CurrentVersion)
|
|
throw new FormatException($"The server's nest result is version {transcript.Version}; this build reads {CurrentVersion}.");
|
|
return transcript;
|
|
}
|
|
|
|
internal void Add(NestJob job, NestJobResult result) =>
|
|
Solves.Add(new SolveJson { Request = Fingerprint(job), Result = ResultJson.From(result) });
|
|
|
|
/// <summary>What a solve was asked: the job's part and stock ids with their quantities, in order.</summary>
|
|
internal static List<string> Fingerprint(NestJob job) =>
|
|
job.Parts.Select(p => $"part:{p.Id}:{p.Quantity}")
|
|
.Concat(job.Plates.Select(s => $"stock:{s.Id}:{s.Quantity?.ToString() ?? "*"}"))
|
|
.ToList();
|
|
|
|
public sealed class SolveJson
|
|
{
|
|
public List<string> Request { get; set; } = new();
|
|
public ResultJson Result { get; set; }
|
|
}
|
|
|
|
public sealed class ResultJson
|
|
{
|
|
public NestJobStatus Status { get; set; }
|
|
public NestJobStopReason StopReason { get; set; }
|
|
public List<PlateJson> Plates { get; set; } = new();
|
|
public List<PartFulfillment> Fulfillment { get; set; } = new();
|
|
public List<StockUsage> StockUsage { get; set; } = new();
|
|
|
|
internal static ResultJson From(NestJobResult result) => new()
|
|
{
|
|
Status = result.Status,
|
|
StopReason = result.StopReason,
|
|
Plates = result.Plates.Select(p => new PlateJson
|
|
{
|
|
PlateIndex = p.PlateIndex,
|
|
Stock = NestStockJson.From(p.Stock),
|
|
Placements = p.Placements.ToList(),
|
|
}).ToList(),
|
|
Fulfillment = result.Fulfillment.ToList(),
|
|
StockUsage = result.StockUsage.ToList(),
|
|
};
|
|
|
|
/// <summary>The raw result, with each sheet that matches one of <paramref name="job"/>'s
|
|
/// stock entries using that entry itself, as an engine solving the job would.</summary>
|
|
internal NestJobResult ToResult(NestJob job)
|
|
{
|
|
var plates = new List<NestJobPlateResult>(Plates.Count);
|
|
foreach (var plate in Plates)
|
|
{
|
|
var stock = plate.Stock?.ToStock() ?? throw new FormatException("A result sheet has no stock.");
|
|
var own = job.Plates.FirstOrDefault(s => RepeatedPlateGrouping.SameStock(s, stock));
|
|
plates.Add(new NestJobPlateResult(plate.PlateIndex, own ?? stock, plate.Placements ?? new()));
|
|
}
|
|
return new NestJobResult(Status, StopReason, plates, Fulfillment ?? new(), StockUsage ?? new());
|
|
}
|
|
}
|
|
|
|
public sealed class PlateJson
|
|
{
|
|
public int PlateIndex { get; set; }
|
|
public NestStockJson Stock { get; set; }
|
|
public List<NestJobPlacement> Placements { get; set; } = new();
|
|
}
|
|
}
|