Files
OpenNest/OpenNest.Engine/Jobs/PartGeometrySnapshot.cs
T
ajandClaude Fable 5.1 e6c87df372 perf(engine): pool best-fit pair searches by part geometry across jobs
Every Auto Nest run, and every Default-engine run, recomputed each part's
best-fit pairs: job snapshots are fresh objects and BestFitCache keyed
its entries on Drawing reference, so the Irregular engine paid
PairCatalog.Build (about half of a 118-part solve) on every run of the
same drawings.

BestFitCache now keeps a process-wide, bounded (512 sets, least recently
used first) pool of unfiltered candidate sets keyed by the exact content
of a PartGeometrySnapshot (mode and every motion value, bit for bit;
PartGeometrySnapshot.ContentComparer) and the spacing. Every drawing
DrawingJobMapper.CreateDrawing reconstructs from a snapshot declares
that geometry (BestFitCache.ShareByGeometry), so the Irregular pairs and
blocks, the cutout lattice and the Fill/Default job paths all read and
fill the pool. Candidates depend on the program geometry and spacing
alone, so a pooled set is the set the search would produce: benchmark
layouts, costs and placements are unchanged on the synthetic nests (all
engines) and on the 67-drawing, 118-part job. Failed searches are not
pooled, Invalidate leaves the pool alone, Clear empties it, and a
drawing whose Program is replaced stops vouching for its geometry.

Same 118-part job solved twice in one benchmark process (--parallel 1):
the second solve now takes 13.1 s after a 33.0 s first; before, 35.6 s
after 41.8 s.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-18 22:17:53 -04:00

145 lines
4.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using OpenNest.CNC;
namespace OpenNest.Engine.Jobs;
/// <summary>Exact immutable CNC motion values. Rapid moves retain contour/hole boundaries; arcs are not tessellated.</summary>
public sealed record PartGeometryMotion(
CodeType Type,
double X,
double Y,
double CenterX,
double CenterY,
RotationType Rotation,
LayerType Layer,
bool Suppressed
);
/// <summary>
/// Owned geometry only: no Drawing, quantity, events, or mutable CNC references are retained.
/// This initial boundary supports flat rapid/linear/arc programs and rejects other instructions explicitly.
/// Coordinates and mode are preserved without normalization, rounding, or polygon approximation.
/// </summary>
public sealed class PartGeometrySnapshot
{
private PartGeometrySnapshot(Mode mode, IEnumerable<PartGeometryMotion> motions)
{
Mode = mode;
Motions = NestJob.Own(motions);
}
private int? contentHash;
public Mode Mode { get; }
public IReadOnlyList<PartGeometryMotion> Motions { get; }
/// <summary>
/// Equality by exact content: the same mode and the same motions in the same order, every
/// value bit for bit (so 0.0 and -0.0 differ), whatever the instances. Snapshots themselves
/// still compare by reference, as job identity does; this comparer is for caches whose
/// results depend on the geometry alone, such as best-fit candidates.
/// </summary>
public static IEqualityComparer<PartGeometrySnapshot> ContentComparer { get; } = new ContentEquality();
private int ContentHash
{
get
{
if (contentHash is int cached)
return cached;
var hash = new HashCode();
hash.Add(Mode);
foreach (var motion in Motions)
{
hash.Add(motion.Type);
hash.Add(Bits(motion.X));
hash.Add(Bits(motion.Y));
hash.Add(Bits(motion.CenterX));
hash.Add(Bits(motion.CenterY));
hash.Add(motion.Rotation);
hash.Add(motion.Layer);
hash.Add(motion.Suppressed);
}
var value = hash.ToHashCode();
contentHash = value;
return value;
}
}
private static long Bits(double value) => BitConverter.DoubleToInt64Bits(value);
private static bool SameMotion(PartGeometryMotion a, PartGeometryMotion b) =>
a.Type == b.Type
&& Bits(a.X) == Bits(b.X)
&& Bits(a.Y) == Bits(b.Y)
&& Bits(a.CenterX) == Bits(b.CenterX)
&& Bits(a.CenterY) == Bits(b.CenterY)
&& a.Rotation == b.Rotation
&& a.Layer == b.Layer
&& a.Suppressed == b.Suppressed;
private sealed class ContentEquality : IEqualityComparer<PartGeometrySnapshot>
{
public bool Equals(PartGeometrySnapshot x, PartGeometrySnapshot y)
{
if (ReferenceEquals(x, y))
return true;
if (x is null || y is null || x.Mode != y.Mode || x.Motions.Count != y.Motions.Count)
return false;
for (var i = 0; i < x.Motions.Count; i++)
if (!SameMotion(x.Motions[i], y.Motions[i]))
return false;
return true;
}
public int GetHashCode(PartGeometrySnapshot snapshot) => snapshot.ContentHash;
}
/// <summary>Copies supported motion geometry immediately; later program edits cannot affect this snapshot.</summary>
public static PartGeometrySnapshot FromProgram(Program program)
{
ArgumentNullException.ThrowIfNull(program);
var motions = program.Codes.Select(code =>
code switch
{
ArcMove arc => new PartGeometryMotion(
arc.Type,
arc.EndPoint.X,
arc.EndPoint.Y,
arc.CenterPoint.X,
arc.CenterPoint.Y,
arc.Rotation,
arc.Layer,
arc.Suppressed
),
LinearMove line => new PartGeometryMotion(
line.Type,
line.EndPoint.X,
line.EndPoint.Y,
0,
0,
default,
line.Layer,
line.Suppressed
),
RapidMove rapid => new PartGeometryMotion(
rapid.Type,
rapid.EndPoint.X,
rapid.EndPoint.Y,
0,
0,
default,
default,
rapid.Suppressed
),
_ => throw new NotSupportedException(
"Geometry snapshots currently support only flat rapid/linear/arc programs."
),
}
);
return new PartGeometrySnapshot(program.Mode, motions);
}
}