using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; using OpenNest.Geometry; 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 /// instance or canonical angle changes. /// public static class FillResultCache { private static readonly ConditionalWeakTable _entries = new(); private static readonly object _entriesLock = new(); /// /// Returns a cached fill result for the given drawing and box dimensions, /// offset to the target location. Returns null on cache miss. /// public static List Get(Drawing drawing, Box targetBox, double spacing) { var source = CanonicalFrame.SourceOf(drawing); if (!_entries.TryGetValue(source, out var entry) || !entry.IsCurrentFor(source)) return null; 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; var result = new List(cached.Count); foreach (var part in cached) result.Add(part.CloneAtOffset(offset)); return result; } /// /// Stores a fill result normalized to origin (0,0). /// public static void Store(Drawing drawing, Box sourceBox, double spacing, List parts) { if (parts == null || parts.Count == 0) return; var source = CanonicalFrame.SourceOf(drawing); var entry = GetEntry(source); var key = new CacheKey(drawing, source, sourceBox.Width, sourceBox.Length, spacing); if (entry.Results.ContainsKey(key)) return; var offset = new Vector(-sourceBox.X, -sourceBox.Y); var normalized = new List(parts.Count); foreach (var part in parts) normalized.Add(part.CloneAtOffset(offset)); entry.Results.TryAdd(key, normalized); } public static void Clear() { lock (_entriesLock) _entries.Clear(); } public static int Count { get { 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> Results = new(); public Entry(Drawing source) { program = source.Program; sourceAngle = source.Source?.Angle ?? 0.0; } public bool IsCurrentFor(Drawing source) => ReferenceEquals(program, source.Program) && sourceAngle == (source.Source?.Angle ?? 0.0); } 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 ) { } } }