perf(engine): key fill caches by source drawing so they hit across trials
Every DefaultPlateFiller.Fill makes a fresh canonical copy of the drawing, and BestFitCache/FillResultCache keyed by drawing reference, so fills never shared results and the static caches grew without bound. CanonicalFrame now records which drawing each canonical copy came from. Both caches key weakly on that source drawing, so every canonical copy shares one entry and released drawings can be collected. An entry is dropped when the drawing's Program instance or canonical angle changes. Best-fit candidates are computed once per (drawing, spacing) through BestFitFinder.FindCandidates and filtered per plate size with the same filter FindBestFits uses. FillResultCache keeps canonical and non-canonical callers apart. Adds Debug-only PerfCounters for best-fit runs, offset perimeter builds and Part.Intersects calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,16 @@ namespace OpenNest.Engine.Fill;
|
||||
/// Caches fill results by drawing and box dimensions so repeated fills
|
||||
/// of the same size don't recompute. Parts are stored normalized to origin
|
||||
/// and offset to the actual location on retrieval.
|
||||
///
|
||||
/// Entries are keyed weakly by the source drawing, so every canonical copy of one drawing shares
|
||||
/// them. Canonical and non-canonical callers are kept apart because cached parts are in the frame
|
||||
/// of the drawing they were computed for. An entry is dropped when the drawing's
|
||||
/// <see cref="Drawing.Program"/> instance or canonical angle changes.
|
||||
/// </summary>
|
||||
public static class FillResultCache
|
||||
{
|
||||
private static readonly ConcurrentDictionary<CacheKey, List<Part>> _cache = new();
|
||||
private static readonly ConditionalWeakTable<Drawing, Entry> _entries = new();
|
||||
private static readonly object _entriesLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a cached fill result for the given drawing and box dimensions,
|
||||
@@ -20,9 +26,13 @@ public static class FillResultCache
|
||||
/// </summary>
|
||||
public static List<Part> Get(Drawing drawing, Box targetBox, double spacing)
|
||||
{
|
||||
var key = new CacheKey(drawing, targetBox.Width, targetBox.Length, spacing);
|
||||
var source = CanonicalFrame.SourceOf(drawing);
|
||||
if (!_entries.TryGetValue(source, out var entry) || !entry.IsCurrentFor(source))
|
||||
return null;
|
||||
|
||||
if (!_cache.TryGetValue(key, out var cached) || cached.Count == 0)
|
||||
var key = new CacheKey(drawing, source, targetBox.Width, targetBox.Length, spacing);
|
||||
|
||||
if (!entry.Results.TryGetValue(key, out var cached) || cached.Count == 0)
|
||||
return null;
|
||||
|
||||
var offset = targetBox.Location;
|
||||
@@ -42,9 +52,11 @@ public static class FillResultCache
|
||||
if (parts == null || parts.Count == 0)
|
||||
return;
|
||||
|
||||
var key = new CacheKey(drawing, sourceBox.Width, sourceBox.Length, spacing);
|
||||
var source = CanonicalFrame.SourceOf(drawing);
|
||||
var entry = GetEntry(source);
|
||||
var key = new CacheKey(drawing, source, sourceBox.Width, sourceBox.Length, spacing);
|
||||
|
||||
if (_cache.ContainsKey(key))
|
||||
if (entry.Results.ContainsKey(key))
|
||||
return;
|
||||
|
||||
var offset = new Vector(-sourceBox.X, -sourceBox.Y);
|
||||
@@ -53,46 +65,68 @@ public static class FillResultCache
|
||||
foreach (var part in parts)
|
||||
normalized.Add(part.CloneAtOffset(offset));
|
||||
|
||||
_cache.TryAdd(key, normalized);
|
||||
entry.Results.TryAdd(key, normalized);
|
||||
}
|
||||
|
||||
public static void Clear() => _cache.Clear();
|
||||
|
||||
public static int Count => _cache.Count;
|
||||
|
||||
private readonly struct CacheKey : System.IEquatable<CacheKey>
|
||||
public static void Clear()
|
||||
{
|
||||
public readonly Drawing Drawing;
|
||||
public readonly double Width;
|
||||
public readonly double Height;
|
||||
public readonly double Spacing;
|
||||
lock (_entriesLock)
|
||||
_entries.Clear();
|
||||
}
|
||||
|
||||
public CacheKey(Drawing drawing, double width, double height, double spacing)
|
||||
public static int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
Drawing = drawing;
|
||||
Width = System.Math.Round(width, 2);
|
||||
Height = System.Math.Round(height, 2);
|
||||
Spacing = spacing;
|
||||
var count = 0;
|
||||
foreach (var kvp in _entries)
|
||||
count += kvp.Value.Results.Count;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private static Entry GetEntry(Drawing source)
|
||||
{
|
||||
if (_entries.TryGetValue(source, out var entry) && entry.IsCurrentFor(source))
|
||||
return entry;
|
||||
|
||||
lock (_entriesLock)
|
||||
{
|
||||
if (_entries.TryGetValue(source, out entry) && entry.IsCurrentFor(source))
|
||||
return entry;
|
||||
|
||||
entry = new Entry(source);
|
||||
_entries.AddOrUpdate(source, entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Entry
|
||||
{
|
||||
private readonly CNC.Program program;
|
||||
private readonly double sourceAngle;
|
||||
|
||||
public readonly ConcurrentDictionary<CacheKey, List<Part>> Results = new();
|
||||
|
||||
public Entry(Drawing source)
|
||||
{
|
||||
program = source.Program;
|
||||
sourceAngle = source.Source?.Angle ?? 0.0;
|
||||
}
|
||||
|
||||
public bool Equals(CacheKey other) =>
|
||||
ReferenceEquals(Drawing, other.Drawing)
|
||||
&& Width == other.Width
|
||||
&& Height == other.Height
|
||||
&& Spacing == other.Spacing;
|
||||
public bool IsCurrentFor(Drawing source) =>
|
||||
ReferenceEquals(program, source.Program)
|
||||
&& sourceAngle == (source.Source?.Angle ?? 0.0);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is CacheKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = RuntimeHelpers.GetHashCode(Drawing);
|
||||
hash = hash * 397 ^ Width.GetHashCode();
|
||||
hash = hash * 397 ^ Height.GetHashCode();
|
||||
hash = hash * 397 ^ Spacing.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
private readonly record struct CacheKey(bool IsCanonical, double Width, double Height, double Spacing)
|
||||
{
|
||||
public CacheKey(Drawing drawing, Drawing source, double width, double height, double spacing)
|
||||
: this(
|
||||
!ReferenceEquals(drawing, source),
|
||||
System.Math.Round(width, 2),
|
||||
System.Math.Round(height, 2),
|
||||
spacing
|
||||
) { }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user