Files
OpenNest/OpenNest.Engine/BestFit/BestFitCache.cs
AJ Isaacs 183d169cc1 feat: integrate GPU slide computation into best-fit pipeline
Thread ISlideComputer through BestFitCache → BestFitFinder →
RotationSlideStrategy. RotationSlideStrategy now collects all offsets
across 4 push directions and dispatches them in a single batch (GPU or
CPU fallback). Also improves rotation angle extraction: uses raw geometry
(line endpoints + arc cardinal extremes) instead of tessellation to avoid
flooding the hull with near-duplicate edge angles, and adds a 5-degree
deduplication threshold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 20:29:51 -04:00

110 lines
3.6 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace OpenNest.Engine.BestFit
{
public static class BestFitCache
{
private const double StepSize = 0.25;
private static readonly ConcurrentDictionary<CacheKey, List<BestFitResult>> _cache =
new ConcurrentDictionary<CacheKey, List<BestFitResult>>();
public static Func<Drawing, double, IPairEvaluator> CreateEvaluator { get; set; }
public static Func<ISlideComputer> CreateSlideComputer { get; set; }
public static List<BestFitResult> GetOrCompute(
Drawing drawing, double plateWidth, double plateHeight,
double spacing)
{
var key = new CacheKey(drawing, plateWidth, plateHeight, spacing);
if (_cache.TryGetValue(key, out var cached))
return cached;
IPairEvaluator evaluator = null;
ISlideComputer slideComputer = null;
try
{
if (CreateEvaluator != null)
{
try { evaluator = CreateEvaluator(drawing, spacing); }
catch { /* fall back to default evaluator */ }
}
if (CreateSlideComputer != null)
{
try { slideComputer = CreateSlideComputer(); }
catch { /* fall back to CPU slide computation */ }
}
var finder = new BestFitFinder(plateWidth, plateHeight, evaluator, slideComputer);
var results = finder.FindBestFits(drawing, spacing, StepSize);
_cache.TryAdd(key, results);
return results;
}
finally
{
(evaluator as IDisposable)?.Dispose();
// Slide computer is managed by the factory as a singleton — don't dispose here
}
}
public static void Invalidate(Drawing drawing)
{
foreach (var key in _cache.Keys)
{
if (ReferenceEquals(key.Drawing, drawing))
_cache.TryRemove(key, out _);
}
}
public static void Clear()
{
_cache.Clear();
}
private readonly struct CacheKey : IEquatable<CacheKey>
{
public readonly Drawing Drawing;
public readonly double PlateWidth;
public readonly double PlateHeight;
public readonly double Spacing;
public CacheKey(Drawing drawing, double plateWidth, double plateHeight, double spacing)
{
Drawing = drawing;
PlateWidth = plateWidth;
PlateHeight = plateHeight;
Spacing = spacing;
}
public bool Equals(CacheKey other)
{
return ReferenceEquals(Drawing, other.Drawing) &&
PlateWidth == other.PlateWidth &&
PlateHeight == other.PlateHeight &&
Spacing == other.Spacing;
}
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 ^ PlateWidth.GetHashCode();
hash = hash * 397 ^ PlateHeight.GetHashCode();
hash = hash * 397 ^ Spacing.GetHashCode();
return hash;
}
}
}
}
}